def message_upperbound(self, tree, spins, subtheta): """Determine an upper bound on the energy of the elimination tree. Args: tree (dict): The current elimination tree spins (dict): The current fixed spins subtheta (dict): Theta with spins fixed. Returns: The formula for the energy of the tree. """ energy_sources = set() for v, subtree in tree.items(): assert all(u in spins for u in self._ancestors[v]) # build an iterable over all of the energies contributions # that we can exactly determine given v and our known spins # in these contributions we assume that v is positive def energy_contributions(): yield subtheta.linear[v] for u, bias in subtheta.adj[v].items(): if u in spins: yield Times(limitReal(spins[u]), bias) energy = Plus(energy_contributions()) # if there are no more variables in the order, we can stop # otherwise we need the next message variable if subtree: spins[v] = 1. plus = self.message_upperbound(subtree, spins, subtheta) spins[v] = -1. minus = self.message_upperbound(subtree, spins, subtheta) del spins[v] else: plus = minus = limitReal(0.0) # we now need a real-valued smt variable to be our message m = FreshSymbol(REAL) self.assertions.update({ LE(m, Plus(energy, plus)), LE(m, Plus(Times(energy, limitReal(-1.)), minus)) }) energy_sources.add(m) return Plus(energy_sources)
def gap_bound_assertion(self, gap_lowerbound): """The formula that lower bounds the gap. Args: gap_lowerbound (float): Return the formula that sets a lower bound on the gap. """ return GE(self.gap, limitReal(gap_lowerbound))
def test_energy_ranges_K5(self): """Check that the energy ranges were set the way we expect""" linear_ranges = defaultdict(lambda: (-2., 2.)) quadratic_ranges = defaultdict(lambda: (-1., 1.)) graph = nx.complete_graph(5) theta = Theta.from_graph(graph, linear_ranges, quadratic_ranges) for v, bias in theta.linear.items(): min_, max_ = linear_ranges[v] self.assertUnsat( And(GT(bias, limitReal(max_)), And(theta.assertions))) self.assertUnsat( And(LT(bias, limitReal(min_)), And(theta.assertions))) for (u, v), bias in theta.quadratic.items(): min_, max_ = quadratic_ranges[(u, v)] self.assertUnsat( And(GT(bias, limitReal(max_)), And(theta.assertions))) self.assertUnsat( And(LT(bias, limitReal(min_)), And(theta.assertions)))
def set_energy(self, spins, target_energy): """Set the energy of Theta with spins fixed to target_energy. Args: spins (dict): Spin values for a subset of the variables in Theta. target_energy (float): The desired energy for Theta with spins fixed. Notes: Add equality constraint to assertions. """ spin_energy = self.energy(spins) self.assertions.add(Equals(spin_energy, limitReal(target_energy)))
def energy_contributions(): yield subtheta.linear[v] for u, bias in subtheta.adj[v].items(): if u in spins: yield Times(limitReal(spins[u]), bias)