def compute_solution_for_recurrence(recurr_coeff: Expr, inhom_part_solution: Expr, initial_value: Expr): """ Computes the (unique) solution to the recurrence relation: f(0) = initial_value; f(n+1) = recurr_coeff * f(n) + inhom_part_solution """ log( f"Start compute solution for recurrence, { recurr_coeff }, { inhom_part_solution }, { initial_value }", LOG_VERBOSE) n = symbols('n', integer=True, positive=True) if recurr_coeff.is_zero: return expand(inhom_part_solution.xreplace({n: n - 1})) hom_solution = (recurr_coeff**n) * initial_value k = symbols('_k', integer=True, positive=True) summand = simplify( (recurr_coeff**k) * inhom_part_solution.xreplace({n: (n - 1) - k})) particular_solution = summation(summand, (k, 0, (n - 1))) particular_solution = without_piecewise(particular_solution) solution = simplify(hom_solution + particular_solution) log( f"End compute solution for recurrence, { recurr_coeff }, { inhom_part_solution }, { initial_value }", LOG_VERBOSE) return solution
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