Source code for rational_linkages.FactorizationProvider

from typing import Union
from warnings import warn

import biquaternion_py
import numpy

from sympy import Symbol, Rational

from .DualQuaternion import DualQuaternion
from .MotionFactorization import MotionFactorization
from .RationalCurve import RationalCurve


[docs] class FactorizationProvider: """Provide motion factorizations for polynomial or curve inputs. This class provides methods to factorize a polynomial or a :class:`.RationalCurve` into motion factorizations. It integrates with the external ``biquaternion_py`` project (BiQuaternions_py). References ---------- BiQuaternions_py https://git.uibk.ac.at/geometrie-vermessung/biquaternion_py """ def __init__(self, use_rationals: bool = False): """Create a FactorizationProvider. Parameters ---------- use_rationals, optional If True, attempt to perform polynomial factorization in the rational domain (QQ); otherwise use floating-point domain (RR). Notes ----- The instance attribute ``domain`` is set to either ``'QQ'`` or ``'RR'`` depending on this flag. """ self.domain = 'QQ' if use_rationals else 'RR'
[docs] def factorize_motion_curve(self, curve: Union[RationalCurve, biquaternion_py.polynomials.Poly]) -> list[MotionFactorization]: """Factorize a motion curve or biquaternion polynomial. Parameters ---------- curve A :class:`.RationalCurve` or a ``biquaternion_py.polynomials.Poly`` representing the motion polynomial to factorize. Returns ------- list[MotionFactorization] Two alternative :class:`.MotionFactorization` instances describing possible motion decompositions. Warns ----- If the input polynomial contains non-rational coefficients while the provider was configured for rational factorization, a warning is emitted and the factorization is performed in floating point. """ t = Symbol("t") if isinstance(curve, RationalCurve): bi_quat = biquaternion_py.BiQuaternion(curve.extract_expressions()) bi_poly = biquaternion_py.polynomials.Poly(bi_quat, t) else: bi_poly = curve # check if the given curve has rational numbers as input if self.domain == 'QQ': poly_coeffs = bi_poly.all_coeffs() for i in range(len(poly_coeffs)): for j in range(len(poly_coeffs[i].args)): if not isinstance(poly_coeffs[i].args[j], Rational): warn('The given curve has not only rational numbers as input. The factorization will be performed with floating point numbers, but may be instable.') break factorizations = self.factorize_polynomial(bi_poly) factors1 = [self.factor2rotation_axis(factor) for factor in factorizations[0]] factors2 = [self.factor2rotation_axis(factor) for factor in factorizations[1]] return [MotionFactorization(factors1), MotionFactorization(factors2)]
[docs] def factorize_for_motion_factorization(self, factorization: MotionFactorization) \ -> list[MotionFactorization]: """Generate alternative factorizations from an existing factorization. Parameters ---------- factorization The :class:`.MotionFactorization` to be analyzed and re-factored. Returns ------- list[MotionFactorization] A list of alternative :class:`.MotionFactorization` instances. Warns ----- Emits a warning if a rational-domain factorization was requested but the dual-quaternion factors are not rational; in that case the computation proceeds in floating-point arithmetic. """ # check if the given factorization has input DualQuaternions as rational numbers if self.domain == 'QQ': for i in range(factorization.number_of_factors): if not factorization.dq_axes[i].is_rational: warn('The given motion factorization has not only rational numbers ' 'as input. The factorization will be performed with floating ' 'point numbers, but may be instable.') t = Symbol("t") bi_poly = t - biquaternion_py.BiQuaternion(factorization.dq_axes[0].array()) for i in range(1, factorization.number_of_factors): bi_poly = bi_poly * (t - biquaternion_py.BiQuaternion(factorization.dq_axes[i].array())) bi_poly = biquaternion_py.polynomials.Poly(bi_poly, t) return self.factorize_motion_curve(bi_poly)
[docs] def factorize_polynomial(self, poly: biquaternion_py.polynomials.Poly) -> ( list)[biquaternion_py.polynomials.Poly]: """Factorize a biquaternion polynomial into irreducible factors. Parameters ---------- poly A ``biquaternion_py.polynomials.Poly`` instance to factorize. Returns ------- list[biquaternion_py.polynomials.Poly] A list of factorization polynomials used to derive motion factorizations. Raises ------ ValueError If the polynomial cannot be factorized into multiple irreducible factors. """ # Calculate the norm polynomial. To avoid numerical problems, extract # the scalar part, since the norm should be purely real norm_poly = poly.norm() norm_poly = biquaternion_py.polynomials.Poly(norm_poly.poly.scal, *norm_poly.indets) # Calculate the irreducible factors, that determine the different factorizations _, factors = biquaternion_py.irreducible_factors(norm_poly, domain=self.domain) # The different permutations of the irreducible factors then generate # the different factorizations of the motion. if len(factors) <= 1: raise ValueError('The factorization failed for the given input.') factorization1 = biquaternion_py.factorize_from_list(poly, factors) factorization2 = biquaternion_py.factorize_from_list(poly, factors[::-1]) return [factorization1, factorization2]
[docs] def factor2rotation_axis(self, factor: biquaternion_py.polynomials.Poly) -> ( DualQuaternion): """Convert a polynomial factor into a dual-quaternion rotation axis. Parameters ---------- factor A ``biquaternion_py.polynomials.Poly`` representing a linear factor in the motion polynomial (typically of the form ``t - axis``). Returns ------- DualQuaternion A :class:`.DualQuaternion` instance representing the rotation axis (parameter removed). """ t = Symbol("t") t_dq = DualQuaternion([t, 0, 0, 0, 0, 0, 0, 0]) factor_dq = DualQuaternion(factor.poly.coeffs) # subtract the parameter from the factor axis_h = t_dq - factor_dq if self.domain == 'QQ': return DualQuaternion(axis_h.array()) else: axis_h = numpy.asarray(axis_h.array(), dtype='float64') return DualQuaternion(axis_h)