Source code for pysiglib.transform_path
# Copyright 2025 Daniil Shmelev
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# =========================================================================
from typing import Union
import numpy as np
import torch
from .data_handlers import PathOutputHandler
from .param_checks import check_type, check_n_jobs
from .error_codes import err_msg
from .dtypes import CPSIG_TRANSFORM_PATH, CUSIG_TRANSFORM_PATH_CUDA
from .data_handlers import PathInputHandler
[docs]
def transform_path(
path : Union[np.ndarray, torch.tensor],
*,
time_aug : bool = False,
lead_lag : bool = False,
end_time : float = 1.,
n_jobs : int = 1
) -> Union[np.ndarray, torch.tensor]:
"""
This function applies time-augmentation and/or the lead-lag transformation to a path. Given a
:math:`d`-dimensional path :math:`(X_{t_i})_{i=1}^L`, the time-augmented path is formed by adding time as the
last channel of the path,
.. math::
\\widehat{X}_{t_i} := (X_{t_i}, t_i).
The lead-lag transformation is defined by
.. math::
X^{LL}_{t_i} := (X^{\\text{Lag}}_{t_i}, X^{\\text{Lead}}_{t_i})
.. math::
X^{\\text{Lead}}_{t_i} :=
\\begin{cases}
X_{t_k} & \\text{if } i = 2k, \\\\
X_{t_k} & \\text{if } i = 2k - 1,
\\end{cases}
.. math::
X^{\\text{Lag}}_{t_i} :=
\\begin{cases}
X_{t_k} & \\text{if } i = 2k, \\\\
X_{t_k} & \\text{if } i = 2k + 1,
\\end{cases}
so that
.. math::
(X^{\\text{Lag}}_{t_i})_{i=0}^L = (X_{t_0}, X_{t_0}, X_{t_1}, X_{t_1}, X_{t_2}, \\ldots),
.. math::
(X^{\\text{Lead}}_{t_i})_{i=0}^L = (X_{t_0}, X_{t_1}, X_{t_1}, X_{t_2}, X_{t_2}, \\ldots).
When both ``time_aug`` and ``lead_lag`` are set to ``True``, time-augmentation is applied
after the lead-lag transformation.
:param path: The underlying path or batch of paths, of shape ``(..., length, dimension)``.
:type path: numpy.ndarray | torch.tensor
:param time_aug: If ``True``, applies time-augmentation by adding a linear channel to the path
spanning :math:`[0, t_L]`. :math:`t_L` is given by the parameter ``end_time`` and defaults to 1.
:type time_aug: bool
:param lead_lag: If ``True``, applies the lead-lag transform.
:type lead_lag: bool
:param end_time: End time for time-augmentation, :math:`t_L`.
:type end_time: float
:param n_jobs: Number of threads to run in parallel. If n_jobs = 1, the computation is run serially.
If set to -1, all available threads are used. For n_jobs below -1, (max_threads + 1 + n_jobs)
threads are used. For example if n_jobs = -2, all threads but one are used.
:type n_jobs: int
:return: Transformed paths.
:rtype: numpy.ndarray | torch.tensor
.. note::
Note that in the definition of the lead-lag transformation, we have intentionally chosen
:math:`X^{LL} := (X^{\\text{Lag}}, X^{\\text{Lead}})` rather than the more commonly used
order of channels :math:`X^{LL} := (X^{\\text{Lead}}, X^{\\text{Lag}})`.
.. important::
This function is provided for convenience only, and one should prefer the in-built flags for
these transformations within ``pysiglib`` functions where available. For example, running
``pysiglib.sig`` with ``lead_lag=True`` will be faster and more memory-efficient than
pre-computing the lead-lag transform and passing it to ``pysiglib.sig``, as the former
method will never explicitly compute or store the lead-lag transform, and will instead
modify the signature computation directly.
Example:
---------
.. code-block:: python
import torch
import pysiglib
path = torch.tensor([[0., 1.], [2., 3.]])
transformed = pysiglib.transform_path(path, time_aug=True, lead_lag=True)
print(transformed)
"""
check_type(time_aug, "time_aug", bool)
check_type(lead_lag, "lead_lag", bool)
check_type(end_time, "end_time", float)
if (not time_aug) and (not lead_lag):
return path
check_n_jobs(n_jobs)
data = PathInputHandler(path, time_aug, lead_lag, end_time, "path")
if time_aug and data.data_length < 2:
raise ValueError(
f"transform_path(time_aug=True) requires path length >= 2, got {data.data_length}. "
)
result = PathOutputHandler(data.length, data.dimension, data)
if data.batch_size == 0:
return result.data
if data.device == "cpu":
err_code = CPSIG_TRANSFORM_PATH[data.dtype](
data.data_ptr, result.data_ptr, data.batch_size, data.data_dimension,
data.data_length, data.time_aug, data.lead_lag, data.end_time, n_jobs)
else:
err_code = CUSIG_TRANSFORM_PATH_CUDA[data.dtype](
data.data_ptr, result.data_ptr, data.batch_size, data.data_dimension,
data.data_length, data.time_aug, data.lead_lag, data.end_time)
if err_code:
raise Exception("Error in pysiglib.transform_path: " + err_msg(err_code))
return result.data