Source code for diffsptk.modules.root_pol

# ------------------------------------------------------------------------ #
# Copyright 2022 SPTK Working Group                                        #
#                                                                          #
# 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.                                           #
# ------------------------------------------------------------------------ #

import torch

from ..typing import Callable, Precomputed
from ..utils.private import check_size, get_values
from .base import BaseFunctionalModule


[docs] class PolynomialToRoots(BaseFunctionalModule): """See `this page <https://sp-nitech.github.io/sptk/latest/main/root_pol.html>`_ for details. Parameters ---------- order : int >= 1 The order of the polynomial. eps : float >= 0 or None If the absolute values of the imaginary parts of the roots are all less than this value, they are considered as real roots. out_format : ['rectangular', 'polar'] The output format. """ def __init__( self, order: int, *, eps: float | None = None, out_format: str | int = "rectangular", ) -> None: super().__init__() self.in_dim = order + 1 self.values, _, tensors = self._precompute(*get_values(locals())) self.register_buffer("eye", tensors[0])
[docs] def forward(self, a: torch.Tensor) -> torch.Tensor: """Find the roots of the input polynomial. Parameters ---------- a : Tensor [shape=(..., M+1)] The polynomial coefficients. Returns ------- out : Tensor [shape=(..., M)] The roots. Examples -------- >>> a = torch.tensor([3, 4, 5]) >>> root_pol = diffsptk.PolynomialToRoots(a.size(-1) - 1) >>> x = root_pol(a) >>> x tensor([[-0.6667+1.1055j, -0.6667-1.1055j]]) """ check_size(a.size(-1), self.in_dim, "order of polynomial") return self._forward(a, *self.values, **self._buffers)
@staticmethod def _func(a: torch.Tensor, *args, **kwargs) -> torch.Tensor: values, _, tensors = PolynomialToRoots._precompute( a.size(-1) - 1, *args, **kwargs, dtype=a.dtype, device=a.device ) return PolynomialToRoots._forward(a, *values, *tensors) @staticmethod def _takes_input_size() -> bool: return True @staticmethod def _check(order: int, eps: float | None) -> None: if order <= 0: raise ValueError("order must be positive.") if eps is not None and eps < 0: raise ValueError("eps must be non-negative.") @staticmethod def _precompute( order: int, eps: float | None = None, out_format: str | int = "rectangular", device: torch.device | None = None, dtype: torch.dtype | None = None, ) -> Precomputed: PolynomialToRoots._check(order, eps) if eps is None: eps = 1e-5 if torch.get_default_dtype() == torch.float else 1e-8 if out_format in (0, "rectangular"): formatter = lambda x: x elif out_format in (1, "polar"): formatter = lambda x: torch.complex(x.abs(), x.angle()) else: raise ValueError(f"out_format {out_format} is not supported.") eye = torch.eye(order - 1, order, device=device, dtype=dtype) return (eps, formatter), None, (eye,) @staticmethod def _forward( a: torch.Tensor, eps: float, formatter: Callable, eye: torch.Tensor ) -> torch.Tensor: if torch.any(a[..., 0] == 0): raise ValueError("Leading coefficient must be non-zero.") # Make companion matrix. a = -a[..., 1:] / a[..., :1] # (..., M) E = eye.expand(a.size()[:-1] + eye.size()) A = torch.cat((a.unsqueeze(-2), E), dim=-2) # (..., M, M) # Find roots as eigenvalues. x, _ = torch.linalg.eig(A) if torch.is_complex(x) and torch.all(x.imag.abs() < eps): x = x.real x = formatter(x) return x