Source code for prodigy.analysis.evtinvariants.heuristics.positivity.rational_function

import logging
from typing import Optional

import sympy

from prodigy.analysis.evtinvariants.heuristics.positivity.polynomial_positivity import FinitePolynomialPositivity
from prodigy.analysis.evtinvariants.heuristics.positivity.positivity import PositivityHeuristic
from prodigy.analysis.exceptions import HeuristicsError
from prodigy.distribution import CommonDistributionsFactory
from prodigy.util.logger import log_setup


[docs] class SingleRationalFunction(PositivityHeuristic): logger = log_setup("SingleRationalFunction", logging.DEBUG) def __init__(self, sub_heuristic: Optional[PositivityHeuristic] = None): super().__init__(sub_heuristic) self._poly_heuristic = FinitePolynomialPositivity(sub_heuristic) def _num_denom_check(self, numerator: sympy.Expr, denominator: sympy.Expr) -> Optional[bool]: s_denom_cterm, s_denom_rest = denominator.as_coeff_Add() numerator_positivity = self._poly_heuristic.is_positive(str(numerator)) if numerator_positivity is False: return None if s_denom_cterm <= 0: return None if any(coefficient > 0 for coefficient in s_denom_rest.as_poly().coeffs()): return None return True def _is_positive(self, f: str) -> Optional[bool]: s_f: sympy.Expr = sympy.S(f) if not s_f.is_rational_function(): raise HeuristicsError(f"The given function is not rational {f=}") if s_f.is_polynomial(): self.logger.debug("Forward to poly heuristic") return self._poly_heuristic.is_positive(str(s_f)) if isinstance(s_f, sympy.Add): raise HeuristicsError(f"The given function is a sum of rational functions {f=}.") self.logger.debug("try do decide positivity for %s", f) s_numerator, s_denominator = s_f.as_numer_denom() res1 = self._num_denom_check(s_numerator, s_denominator) res2 = self._num_denom_check(-s_numerator, -s_denominator) self.logger.debug("result: %s", res1 or res2) return res1 or res2
[docs] class RationalFunctionDenomSign(PositivityHeuristic): logger = log_setup("RationalFunctionDenomSign", logging.DEBUG) def __init__(self, dist_fact: CommonDistributionsFactory, sub_heuristic: Optional[PositivityHeuristic] = None): super().__init__(sub_heuristic) self.dist_fact = dist_fact def _is_positive(self, f: sympy.Expr) -> Optional[bool]: """ Checks heuristically whether a univariate rational function _f_ has a non-negative power series expansion. ..returns: - None: We do not know - True: It has a non-negative power series expansion - False: There exists at least one negative coefficient in the power series expansion """ self.logger.debug("check non-negativity for %s", f) # TODO f might be a sum of rational functions (partial fraction decomposition). if isinstance(f, sympy.Add): self.logger.debug("Try to solve positivity by looking at sum terms individually.") for sub_f in f.args: res = self.is_positive(sub_f.apart()) if res is not True: self.logger.debug("Sum decomposition did not work. Try together()") return self.is_positive(f.together()) return True # Getting f as numerator/denominator and check whether it is indeed a rational function. numerator, denominator = f.as_numer_denom() # Check whether f is univariate (others not supported currently) if len(denominator.free_symbols) > 1: self.logger.info("Multivariate functions like %s are currently not supported", f) return None self.logger.debug("\nnumerator (non-poly) %s\ndenominator (non-poly) %s", numerator, denominator) if not (numerator.is_polynomial() and denominator.is_polynomial()): self.logger.info("%s is not a rational function.", f) return None # convert numerator and denominator into polynomial objects # as_poly() fails converting constant polynomials without specified variables # so we give it a _DUMMY_ variable. maybe_numerator = numerator.as_poly(*numerator.free_symbols) numerator = maybe_numerator if maybe_numerator else numerator.as_poly(sympy.S("DUMMY")) maybe_denominator = denominator.as_poly(*denominator.free_symbols) denominator = maybe_denominator if maybe_denominator else denominator.as_poly(sympy.S("DUMMY")) self.logger.debug("\nnumerator %s\ndenominator %s", numerator, denominator) # Check the coefficients of the denominator d_coeffs = denominator.all_coeffs() n_coeffs = numerator.all_coeffs() # Check the constant coefficient and factor a minus sign if necessary if d_coeffs[-1] < 0: self.logger.debug("Try factoring a minus sign in %s", f) d_coeffs = [-x for x in d_coeffs] n_coeffs = [-x for x in n_coeffs] if all(map(lambda x: x <= 0, d_coeffs[:-1])): # are all other coefficients non-positive? if all(map(lambda x: x >= 0, n_coeffs)): # are all nominator coefficients non-negative? self.logger.info("Invariant validates as non-negative FPS.") return True # We don't know otherwise self.logger.info("Heuristic failed, we dont know!") return None