def get_initial_polarity_for_expression(expression: Expr, program: Program) -> (bool, bool): """ Returns a sound estimate whether a given expression can initial be positive and negative. It does so by substituting the variable power in the given expression with all possible combination of lower and upper bounds of their respective supports. """ variables = expression.free_symbols.intersection(program.variables) # First we can replace n and all variables which are deterministic initially (meaning they have exactly one branch) expression = expression.xreplace( {symbols("n", integer=True, positive=True): 0}) for v in variables: if not expression.free_symbols & variables: continue if hasattr(program.initial_values[v], "branches") and len( program.initial_values[v].branches) == 1: expression = expression.subs( {v: program.initial_values[v].branches[0][0]}) variables = expression.free_symbols.intersection(program.variables) if not variables: maybePos = bool( expression > 0) if (expression > 0).is_Boolean else True maybeNeg = bool( expression < 0) if (expression < 0).is_Boolean else True return maybePos, maybeNeg expression = expression.as_poly(variables) var_powers = set() monoms = get_monoms(expression) for m in monoms: m = m.as_poly(variables) powers = m.monoms()[0] var_powers.update([(v, p) for v, p in zip(m.gens, powers) if p > 0]) initial_supports = get_initial_supports_for_variable_powers( var_powers, program) possible_substitutions = flatten_substitution_choices(initial_supports) expression = expression.as_expr() possible_initial_polarities = [] for ps in possible_substitutions: pos = expression.subs(ps) > 0 if pos.is_Boolean: possible_initial_polarities.append(bool(pos)) else: possible_initial_polarities.append(None) allPositive = all([v is True for v in possible_initial_polarities]) allNegative = all([v is False for v in possible_initial_polarities]) maybePos = not allNegative maybeNeg = not allPositive return maybePos, maybeNeg
def is_deterministic_invariant(expression: Expr) -> bool: """ Checks whether an expression only containing n eventually stays <= 0 """ n = symbols("n", integer=True, positive=True) max_0 = get_max_0(expression, n) return expression.subs({n: max_0 + 1}) <= 0