def symbolic_integrator(self, var: FNode, terminal_node: TerminalNode, d: Diagram): algebra = self.pool.algebra sym = algebra.symbol(var.symbol_name()) lb = algebra.symbol("_lb") ub = algebra.symbol("_ub") expression_bounds = algebra.times(algebra.greater_than_equal(sym, lb), algebra.less_than_equal(sym, ub)) result = algebra.integrate( None, algebra.times(expression_bounds, terminal_node.expression), sym) return self.pool.terminal(result)
def concrete_integrate(self, expression: Expression, var: FNode, lb: FNode, ub: FNode, prefix) -> Expression: logger.debug("%s integrate %s, %s <= %s <= %s", prefix, expression, lb, var, ub) algebra = self.pool.algebra lb = Polynomial.from_smt(lb).to_expression(algebra) ub = Polynomial.from_smt(ub).to_expression(algebra) var_name = var.symbol_name() result = algebra.integrate_poly(expression, [var_name], {var_name: (lb, ub)}) logger.debug("%s \t = %s", prefix, result) return result
def resolve_lb_ub( self, node_id: int, var: FNode, ub: Optional[FNode] = None, lb: Optional[FNode] = None, prefix="", ) -> int: new_prefix = " " + prefix method = self.method # "fast_smt" # prefix = rl * "." + "({})({})({})".format(node_id, ub, lb) # print(prefix + " enter") logger.debug("%s resolve %s var=%s lb=%s ub=%s", prefix, node_id, var, lb, ub) if self.cache_result: key = (var.symbol_name(), node_id, ub, lb) self.cache_calls += 1 # print key, self.cache_calls, self.cache_hits if key in self.resolve_cache: # print("cache hit", self.cache_hits) self.cache_hits += 1 # print("Cache hit for key={}".format(key)) return self.resolve_cache[key] cache_result = partial(self.add_to_cache, key) else: cache_result = lambda r: r node = self.pool.get_node(node_id) # print "ub_lb_resolve node: {}, ub: {}, lb: {}, {} : {}".format(node, ub, lb, hash(str(ub)), hash(str(lb))) # leaf algebra = self.pool.algebra if node.is_terminal(): if node.node_id == self.pool.zero_id: return self.pool.zero_id if var.symbol_type() == BOOL: return self.pool.terminal( algebra.times(algebra.real(2), node.expression)) if ub is None or lb is None: # TODO: to deal with unbounded constraints, we should either return 0 if we've seen bounds # or f(inf) if we haven't seen bounds return cache_result(self.pool.zero_id) else: # ub_sub = self.operator_to_bound(ub, var) # lb_sub = self.operator_to_bound(lb, var) if self.symbolic_integration_enabled: raise NotImplementedError() else: expression = self.concrete_integrate( node.expression, var, lb, ub, prefix) # print "->", self.pool.get_node(res) return cache_result(self.pool.terminal(expression)) # not leaf assert isinstance(node, InternalNode) if var in node.decision.variables: # Variable occurs in test if var.symbol_type() == BOOL: return self.pool.apply(Summation, node.child_true, node.child_false) var_coefficient = node.decision.inequality.coefficient(str(var)) if var_coefficient > 0: # True branch is upper-bound ub_inequality = node.decision.inequality ub_branch = node.child_true lb_branch = node.child_false else: # False branch is upper-bound ub_inequality = node.decision.inequality.inverted() ub_branch = node.child_false lb_branch = node.child_true # ub_at_node = self.operator_to_bound(operator, var) # lb_at_node = self.operator_to_bound((~operator).to_canonical(), var) lb_inequality = ub_inequality.inverted() var_name = var.symbol_name() new_bound = self.operator_to_bound(ub_inequality, var_name) # lb_expr = self.operator_to_bound(lb_inequality, var_name) # consistency_test = simplify(lb_expr <= ub_expr) pass_ub = False if lb is not None: consistency_test = simplify(lb < new_bound) if consistency_test == FALSE(): # this branch is infeasible ub_consistency = self.pool.zero_id some_or_best_ub = self.pool.zero_id pass_ub = True elif consistency_test == TRUE(): ub_consistency = self.pool.one_id else: ub_consistency = self.pool.bool_test( Decision(consistency_test)) else: ub_consistency = self.pool.one_id if ub is not None and not pass_ub: tighter_ub_test = simplify(new_bound < ub) if tighter_ub_test == TRUE(): some_ub = self.pool.zero_id else: some_ub = self.resolve_lb_ub(ub_branch, var, ub=ub, lb=lb, prefix=new_prefix) if tighter_ub_test == FALSE(): best_ub = self.pool.zero_id else: best_ub = self.resolve_lb_ub(ub_branch, var, ub=new_bound, lb=lb, prefix=new_prefix) best_ub = ( self.pool.diagram(best_ub).reduce(method=method).root_id ) # RED some_ub = ( self.pool.diagram(some_ub).reduce(method=method).root_id ) # RED some_or_best_ub = self.pool.internal(Decision(tighter_ub_test), best_ub, some_ub) elif not pass_ub: some_or_best_ub = self.resolve_lb_ub(ub_branch, var, ub=new_bound, lb=lb, prefix=new_prefix) pass_lb = False if ub is not None: consistency_test = simplify(new_bound < ub) if consistency_test == FALSE(): # this branch is infeasible lb_consistency = self.pool.zero_id some_or_best_lb = self.pool.zero_id pass_lb = True if consistency_test == TRUE(): lb_consistency = self.pool.one_id else: lb_consistency = self.pool.bool_test( Decision(consistency_test)) else: lb_consistency = self.pool.one_id if lb is not None and not pass_lb: tighter_lb_test = simplify(new_bound > lb) if tighter_lb_test == TRUE(): some_lb = self.pool.zero_id else: some_lb = self.resolve_lb_ub(lb_branch, var, ub=ub, lb=lb, prefix=new_prefix) if tighter_lb_test == FALSE(): best_lb = self.pool.zero_id else: best_lb = self.resolve_lb_ub(lb_branch, var, ub=ub, lb=new_bound, prefix=new_prefix) best_lb = ( self.pool.diagram(best_lb).reduce(method=method).root_id ) # RED some_lb = ( self.pool.diagram(some_lb).reduce(method=method).root_id ) # RED some_or_best_lb = self.pool.internal(Decision(tighter_lb_test), best_lb, some_lb) elif not pass_lb: some_or_best_lb = self.resolve_lb_ub(lb_branch, var, ub=ub, lb=new_bound, prefix=new_prefix) lb_branch = self.pool.apply(Multiplication, some_or_best_lb, lb_consistency) ub_branch = self.pool.apply(Multiplication, some_or_best_ub, ub_consistency) # print(prefix + " lb done") if self.reduce_strategy[1]: lb_branch = ( self.pool.diagram(lb_branch).reduce(method=method).root_id ) # RED ub_branch = ( self.pool.diagram(ub_branch).reduce(method=method).root_id ) # RED # self.export(res, "res{}_{}_{}".format(node_id, hash(str(ub)), hash(str(lb)))) result = self.pool.apply(Summation, lb_branch, ub_branch) if self.reduce_strategy[2]: result = self.pool.diagram(result).reduce( method=method).root_id return cache_result(result) else: true_branch_id = self.resolve_lb_ub(node.child_true, var, ub=ub, lb=lb) false_branch_id = self.resolve_lb_ub(node.child_false, var, ub=ub, lb=lb) return cache_result( self.pool.internal(node.decision, true_branch_id, false_branch_id))