def _arc(p,q,s,**kwds): #rewrite this to use polar_plot and get points to do filled triangles from sage.misc.functional import det from sage.plot.line import line from sage.misc.functional import norm from sage.symbolic.all import pi from sage.plot.arc import arc p,q,s = map( lambda x: vector(x), [p,q,s]) # to avoid running into division by 0 we set to be colinear vectors that are # almost colinear if abs(det(matrix([p-s,q-s])))<0.01: return line((p,q),**kwds) (cx,cy)=var('cx','cy') equations=[ 2*cx*(s[0]-p[0])+2*cy*(s[1]-p[1]) == s[0]**2+s[1]**2-p[0]**2-p[1]**2, 2*cx*(s[0]-q[0])+2*cy*(s[1]-q[1]) == s[0]**2+s[1]**2-q[0]**2-q[1]**2 ] c = vector( [solve( equations, (cx,cy), solution_dict=True )[0][i] for i in [cx,cy]] ) r = norm(p-c) a_p, a_q, a_s = map(lambda x: atan2(x[1],x[0]), [p-c,q-c,s-c]) a_p, a_q = sorted([a_p,a_q]) if a_s < a_p or a_s > a_q: return arc( c, r, angle=a_q, sector=(0,2*pi-a_q+a_p), **kwds) return arc( c, r, angle=a_p, sector=(0,a_q-a_p), **kwds)
def _arc(p, q, s, **kwds): #rewrite this to use polar_plot and get points to do filled triangles from sage.misc.functional import det from sage.plot.line import line from sage.misc.functional import norm from sage.symbolic.all import pi from sage.plot.arc import arc p, q, s = map(lambda x: vector(x), [p, q, s]) # to avoid running into division by 0 we set to be colinear vectors that are # almost colinear if abs(det(matrix([p - s, q - s]))) < 0.01: return line((p, q), **kwds) (cx, cy) = var('cx', 'cy') equations = [ 2 * cx * (s[0] - p[0]) + 2 * cy * (s[1] - p[1]) == s[0]**2 + s[1]**2 - p[0]**2 - p[1]**2, 2 * cx * (s[0] - q[0]) + 2 * cy * (s[1] - q[1]) == s[0]**2 + s[1]**2 - q[0]**2 - q[1]**2 ] c = vector([ solve(equations, (cx, cy), solution_dict=True)[0][i] for i in [cx, cy] ]) r = norm(p - c) a_p, a_q, a_s = map(lambda x: atan2(x[1], x[0]), [p - c, q - c, s - c]) a_p, a_q = sorted([a_p, a_q]) if a_s < a_p or a_s > a_q: return arc(c, r, angle=a_q, sector=(0, 2 * pi - a_q + a_p), **kwds) return arc(c, r, angle=a_p, sector=(0, a_q - a_p), **kwds)
def refresh(vars): """ Return a list of replacement of given variables with fresh ones. """ if len(vars) == 0: return [] elif len(vars) == 1: vars = list(vars) * 2 return solve([x == x for x in vars], list(vars))[0]
def inverse(self, chartname1=None, chartname2=None): r""" Returns the inverse diffeomorphism. INPUT: - ``chartname1`` -- (default: None) string defining the chart in which the computation of the inverse is performed; if none is provided, the default chart of self.manifold1 will be used - ``chartname2`` -- (default: None) string defining the chart in which the computation of the inverse is performed; if none is provided, the default chart of self.manifold2 will be used OUTPUT: - the inverse diffeomorphism EXAMPLES: The inverse of a rotation in the plane:: sage: m = Manifold(2, "plane") sage: c_cart = Chart(m, 'x y', 'cart') sage: # A pi/3 rotation around the origin: sage: rot = Diffeomorphism(m, m, ((x - sqrt(3)*y)/2, (sqrt(3)*x + y)/2)) sage: p = Point(m,(1,2)) sage: q = rot(p) sage: irot = rot.inverse() sage: p1 = irot(q) sage: p1 == p True """ from sage.symbolic.ring import SR from sage.symbolic.relation import solve from utilities import simplify_chain if self._inverse is not None: return self._inverse if chartname1 is None: chartname1 = self.manifold1.def_chart.name if chartname2 is None: chartname2 = self.manifold2.def_chart.name coord_map = self.coord_expression[(chartname1, chartname2)] chart1 = self.manifold1.atlas[chartname1] chart2 = self.manifold2.atlas[chartname2] n1 = len(chart1.xx) n2 = len(chart2.xx) # New symbolic variables (different from chart2.xx to allow for a # correct solution even when chart2 = chart1): x2 = [ SR.var('xxxx' + str(i)) for i in range(n2) ] equations = [x2[i] == coord_map.functions[i] for i in range(n2) ] solutions = solve(equations, chart1.xx, solution_dict=True) if len(solutions) == 0: raise ValueError("No solution found") if len(solutions) > 1: raise ValueError("Non-unique solution found") #!# This should be the Python 2.7 form: # substitutions = {x2[i]: chart2.xx[i] for i in range(n2)} # # Here we use a form compatible with Python 2.6: substitutions = dict([(x2[i], chart2.xx[i]) for i in range(n2)]) inv_functions = [solutions[0][chart1.xx[i]].subs(substitutions) for i in range(n1)] for i in range(n1): x = inv_functions[i] try: inv_functions[i] = simplify_chain(x) except AttributeError: pass self._inverse = Diffeomorphism(self.manifold2, self.manifold1, inv_functions, chartname2, chartname1) return self._inverse
def inverse(self, chart1=None, chart2=None): r""" Return the inverse diffeomorphism. INPUT: - ``chart1`` -- (default: None) chart in which the computation of the inverse is performed if necessary; if none is provided, the default chart of the start domain will be used - ``chart2`` -- (default: None) chart in which the computation of the inverse is performed if necessary; if none is provided, the default chart of the arrival domain will be used OUTPUT: - the inverse diffeomorphism EXAMPLES: The inverse of a rotation in the Euclidean plane:: sage: m = Manifold(2, 'R^2', r'\RR^2') sage: c_cart.<x,y> = m.chart('x y') sage: # A pi/3 rotation around the origin: sage: rot = Diffeomorphism(m, m, ((x - sqrt(3)*y)/2, (sqrt(3)*x + y)/2), name='R') sage: rot.inverse() diffeomorphism 'R^(-1)' on the 2-dimensional manifold 'R^2' sage: rot.inverse().view() R^(-1): R^2 --> R^2, (x, y) |--> (1/2*sqrt(3)*y + 1/2*x, -1/2*sqrt(3)*x + 1/2*y) Checking that applying successively the diffeomorphism and its inverse results in the identity:: sage: (a, b) = var('a b') sage: p = Point(m, (a,b)) # a generic point on M sage: q = rot(p) sage: p1 = rot.inverse()(q) sage: p1 == p True """ from sage.symbolic.ring import SR from sage.symbolic.relation import solve from utilities import simplify_chain if self._inverse is not None: return self._inverse if chart1 is None: chart1 = self.domain1.def_chart if chart2 is None: chart2 = self.domain2.def_chart coord_map = self.coord_expression[(chart1, chart2)] n1 = len(chart1.xx) n2 = len(chart2.xx) # New symbolic variables (different from chart2.xx to allow for a # correct solution even when chart2 = chart1): x2 = [ SR.var('xxxx' + str(i)) for i in range(n2) ] equations = [ x2[i] == coord_map.functions[i].express for i in range(n2) ] solutions = solve(equations, chart1.xx, solution_dict=True) if len(solutions) == 0: raise ValueError("No solution found") if len(solutions) > 1: raise ValueError("Non-unique solution found") #!# This should be the Python 2.7 form: # substitutions = {x2[i]: chart2.xx[i] for i in range(n2)} # # Here we use a form compatible with Python 2.6: substitutions = dict([(x2[i], chart2.xx[i]) for i in range(n2)]) inv_functions = [solutions[0][chart1.xx[i]].subs(substitutions) for i in range(n1)] for i in range(n1): x = inv_functions[i] try: inv_functions[i] = simplify_chain(x) except AttributeError: pass if self.name is None: name = None else: name = self.name + '^(-1)' if self.latex_name is None: latex_name = None else: latex_name = self.latex_name + r'^{-1}' self._inverse = Diffeomorphism(self.domain2, self.domain1, inv_functions, chart2, chart1, name=name, latex_name=latex_name) return self._inverse
def edges_intersection(P, i, cmatrix=None): r"""Return the point in the plane where the edges adjacent to the input edge intersect. INPUT: ``P`` - a polygon (Polyhedron in 2d). ``i`` - integer, index of edge in ``P.inequalities_list()``. ``cmatrix`` - (optional) if None, the constraints matrix corresponding to P is computed inside the function. OUTPUT: ``p`` - coordinates of the intersection of the edges that are adjacent to i. ``neighbor_constraints`` - indices of the edges that are adjacent to it. The edges are indexed according to P.inequalities_list(). NOTES: - This has been tested for P in QQ and RDF. """ from sage.symbolic.ring import SR from sage.symbolic.relation import solve if cmatrix is None: cmatrix = vertex_connections(P) got_QQ = True if P.base_ring() == QQ else False #constraint_i = P.inequalities_list()[i] neighbor_constraints = [] # vertices associated to the given edge i vert_i = cmatrix[i] for j, cj in enumerate(cmatrix): if (vert_i[0] in cj or vert_i[1] in cj) and (j != i): neighbor_constraints.append(j) # first one constr_1 = P.inequalities_list()[neighbor_constraints[0]] # second one constr_2 = P.inequalities_list()[neighbor_constraints[1]] # write and solve the intersection of the two lines x1 = SR.var('x1') x2 = SR.var('x2') eq1 = constr_1[1] * x1 + constr_1[2] * x2 == -constr_1[0] eq2 = constr_2[1] * x1 + constr_2[2] * x2 == -constr_2[0] p = solve([eq1, eq2], x1, x2) p = [p[0][0].right_hand_side(), p[0][1].right_hand_side()] if not got_QQ: # assuming RDF # transform to RDF (because solve produces in general rational answers) p = [RDF(pi) for pi in p] return p, neighbor_constraints
def inverse(self, chartname1=None, chartname2=None): r""" Returns the inverse diffeomorphism. INPUT: - ``chartname1`` -- (default: None) string defining the chart in which the computation of the inverse is performed; if none is provided, the default chart of self.manifold1 will be used - ``chartname2`` -- (default: None) string defining the chart in which the computation of the inverse is performed; if none is provided, the default chart of self.manifold2 will be used OUTPUT: - the inverse diffeomorphism EXAMPLES: The inverse of a rotation in the plane:: sage: m = Manifold(2, "plane") sage: c_cart = Chart(m, 'x y', 'cart') sage: # A pi/3 rotation around the origin: sage: rot = Diffeomorphism(m, m, ((x - sqrt(3)*y)/2, (sqrt(3)*x + y)/2)) sage: p = Point(m,(1,2)) sage: q = rot(p) sage: irot = rot.inverse() sage: p1 = irot(q) sage: p1 == p True """ from sage.symbolic.ring import SR from sage.symbolic.relation import solve from utilities import simplify_chain if self._inverse is not None: return self._inverse if chartname1 is None: chartname1 = self.manifold1.def_chart.name if chartname2 is None: chartname2 = self.manifold2.def_chart.name coord_map = self.coord_expression[(chartname1, chartname2)] chart1 = self.manifold1.atlas[chartname1] chart2 = self.manifold2.atlas[chartname2] n1 = len(chart1.xx) n2 = len(chart2.xx) # New symbolic variables (different from chart2.xx to allow for a # correct solution even when chart2 = chart1): x2 = [SR.var('xxxx' + str(i)) for i in range(n2)] equations = [x2[i] == coord_map.functions[i] for i in range(n2)] solutions = solve(equations, chart1.xx, solution_dict=True) if len(solutions) == 0: raise ValueError("No solution found") if len(solutions) > 1: raise ValueError("Non-unique solution found") #!# This should be the Python 2.7 form: # substitutions = {x2[i]: chart2.xx[i] for i in range(n2)} # # Here we use a form compatible with Python 2.6: substitutions = dict([(x2[i], chart2.xx[i]) for i in range(n2)]) inv_functions = [ solutions[0][chart1.xx[i]].subs(substitutions) for i in range(n1) ] for i in range(n1): x = inv_functions[i] try: inv_functions[i] = simplify_chain(x) except AttributeError: pass self._inverse = Diffeomorphism(self.manifold2, self.manifold1, inv_functions, chartname2, chartname1) return self._inverse
def _compute_init_vector(self, point, pt0, pr0, pth0, pph0, r_increase, th_increase, verbose): r""" Computes the initial 4-momentum vector `p` from the constants of motion """ BLchart = self._spacetime.boyer_lindquist_coordinates() basis = BLchart.frame().at(point) r, th = BLchart(point)[1:3] a, m = self._a, self._m r2 = r**2 a2 = a**2 rho2 = r2 + (a * cos(th))**2 Delta = r2 - 2 * m * r + a2 if pt0 is None: if (self._mu is not None and pr0 is not None and pth0 is not None and pph0 is not None): xxx = SR.var('xxx') v = self._spacetime.tangent_space(point)( (xxx, pr0, pth0, pph0), basis=basis) muv2 = -self._spacetime.metric().at(point)(v, v) muv2 = muv2.substitute(self._numerical_substitutions()) solutions = solve(muv2 == self._mu**2, xxx, solution_dict=True) if verbose: print("Solutions for p^t:") pretty_print(solutions) for sol in solutions: if sol[xxx] > 0: pt0 = sol[xxx] break else: # pt0 <= 0 might occur in the ergoregion pt0 = solutions[0][xxx] try: pt0 = RR(pt0) except TypeError: # pt0 contains some symbolic expression pass else: if self._E is None: raise ValueError("the constant E must be provided") if self._L is None: raise ValueError("the constant L must be provided") E, L = self._E, self._L pt0 = ((r2 + a2) / Delta * ((r2 + a2) * E - a * L) + a * (L - a * E * sin(th)**2)) / rho2 if pph0 is None: if self._E is None: raise ValueError("the constant E must be provided") if self._L is None: raise ValueError("the constant L must be provided") E, L = self._E, self._L pph0 = (L / sin(th)**2 - a * E + a / Delta * ((r2 + a2) * E - a * L)) / rho2 if pr0 is None: if self._E is None: raise ValueError("the constant E must be provided") if self._L is None: raise ValueError("the constant L must be provided") if self._mu is None: raise ValueError("the constant mu must be provided") if self._Q is None: raise ValueError("the constant Q must be provided") E, L, Q = self._E, self._L, self._Q mu2 = self._mu**2 E2_mu2 = E**2 - mu2 pr0 = sqrt((E2_mu2) * r**4 + 2 * m * mu2 * r**3 + (a2 * E2_mu2 - L**2 - Q) * r**2 + 2 * m * (Q + (L - a * E)**2) * r - a2 * Q) / rho2 if not r_increase: pr0 = -pr0 if pth0 is None: if self._E is None: raise ValueError("the constant E must be provided") if self._L is None: raise ValueError("the constant L must be provided") if self._mu is None: raise ValueError("the constant mu must be provided") if self._Q is None: raise ValueError("the constant Q must be provided") E2 = self._E**2 L2 = self._L**2 mu2 = self._mu**2 Q = self._Q pth0 = sqrt(Q + cos(th)**2 * (a2 * (E2 - mu2) - L2 / sin(th)**2)) / rho2 if not th_increase: pth0 = -pth0 return self._spacetime.tangent_space(point)((pt0, pr0, pth0, pph0), basis=basis, name='p')
def find(expressions, vars, conditions=None, solver=None): """ Generate assignments of values to variables such that the values of the expressions are integral and subject to the specified lower and upper bounds. Assumes that the expressions and conditions are linear in the variables. """ expressions = dict(expressions) conditions = set() if conditions is None else set(conditions) vars = set(vars) for c in set(conditions): if verify(c): conditions.discard(c) elif verify(c.negation()): return if len(vars) > 0: _, opt = min((Infinity if None in (u, l) else u - l, e) for e, (l, u) in expressions.items() if not is_constant(e)) _, x = min((abs(opt.coefficient(y)), y) for y in variables(opt)) s = symbol("__opt") xsol = solve(s == opt, x)[0] rest = vars - {x} zero = [z == 0 for z in vars] lp = MixedIntegerLinearProgram(maximization=False, solver=solver) v = lp.new_variable(real=True) w = lp.new_variable(integer=True) lp.add_constraint(lp[1] == 1) def makeLPExpression(e): return sum(e.coefficient(y) * v[str(y)] for y in vars) \ + e.subs(zero) * lp[1] lpopt = makeLPExpression(opt) def addCondition(c): op = c.operator() if op is operator.gt: op = operator.ge elif op is operator.lt: op = operator.le elif op not in [operator.eq, operator.ge, operator.le]: return lp.add_constraint( op(makeLPExpression(c.lhs()), makeLPExpression(c.rhs()))) else: x = None delete = set() for i, (e, (l, u)) in enumerate(expressions.items()): if is_constant(e): try: integralize(e) except TypeError: return if e < l or e > u: return delete.add(e) continue elif x is None: return lp.add_constraint(w[i] == makeLPExpression(e)) lp.set_min(w[i], l) lp.set_max(w[i], u) for e in delete: del expressions[e] if x is None: yield () return for c in conditions: addCondition(c) lp.set_objective(-lpopt) try: vmax = round(-lp.solve()) except MIPSolverException as ex: if len(ex.args) == 0 or 'feasible' in ex.args[0]: return lp.set_objective(lpopt) vnew = vmin = round(lp.solve()) while vmin <= vmax: eq = xsol.subs(s == vmin) g = find(make_expressions( (e.subs(eq), l, u) for e, (l, u) in expressions.items()), vars=rest, conditions={c.subs(eq) for c in conditions}) try: while vnew == vmin: sol = next(g) t = (yield (eq.subs(sol), ) + sol) while t is not None: b, c = t if b: t = (yield find(expressions, vars=vars, conditions=conditions | {c})) else: if c in conditions or verify(c): t = yield continue elif verify(c.negation()): return conditions.add(c) addCondition(c) lp.set_objective(-lpopt) try: vmax = round(-lp.solve()) except MIPSolverException: return lp.set_objective(lpopt) vnew = round(lp.solve()) if vnew == vmin: g.send((False, c.subs(eq))) t = yield vmin = vnew except StopIteration: vmin += 1 vnew = vmin finally: g.close()
def cluster_expansion(self, beta): if beta == 0: return dict() coefficients=beta.monomial_coefficients() if any ( x < 0 for x in coefficients.values() ): alpha = [ -x for x in self.initial_cluster() ] negative_part = dict( [(-alpha[x],-coefficients[x]) for x in coefficients if coefficients[x] < 0 ] ) positive_part = sum( [ coefficients[x]*alpha[x] for x in coefficients if coefficients[x] > 0 ] ) return dict( negative_part.items() + self.cluster_expansion(positive_part).items() ) if self.is_affine(): if self.gamma().associated_coroot().scalar(beta) < 0: shifted_expansion = self.cluster_expansion( self.tau_c()(beta) ) return dict( [ (self.tau_c_inverse()(x),shifted_expansion[x]) for x in shifted_expansion ] ) elif self.gamma().associated_coroot().scalar(beta) > 0: shifted_expansion = self.cluster_expansion( self.tau_c_inverse()(beta) ) return dict( [ (self.tau_c()(x),shifted_expansion[x]) for x in shifted_expansion ] ) else: ### # Assumptions # # Two cases are possible for vectors in the interior of the cone # according to how many tubes there are: # 1) If there is only one tube then its extremal rays are linearly # independent, therefore a point is in the interior of the cone # if and only if it is a linear combination of all the extremal # rays with strictly positive coefficients. In this case solve() # should produce only one solution. # 2) If there are two or three tubes then the extreme rays are # linearly dependent. A vector is in the interior of the cone if # and only if it can be written as a strictly positive linear # combination of all the rays of at least one tube. In this case # solve() should return at least two solutions. # # If a vector is on one face of the cone than it can be written # uniquely as linear combination of the rays of that face (they # are linearly independent). solve() should return only one # solution no matter how many tubes there are. rays = flatten([ t[0] for t in self.affine_tubes() ]) system = matrix( map( vector, rays ) ).transpose() x = vector( var ( ['x%d'%i for i in range(len(rays))] ) ) eqs = [ (system*x)[i] == vector(beta)[i] for i in range(self._n)] ieqs = [ y >= 0 for y in x ] solutions = solve( eqs+ieqs, x, solution_dict=True ) if not solutions: # we are outside the cone shifted_expansion = self.cluster_expansion( self.tau_c()(beta) ) return dict( [ (self.tau_c_inverse()(v),shifted_expansion[v]) for v in shifted_expansion ] ) if len(solutions) > 1 or all( v > 0 for v in solutions[0].values() ): # we are in the interior of the cone raise ValueError("Vectors in the interior of the cone do " "not have a cluster expansion") # we are on the boundary of the cone solution_dict=dict( [(rays[i],solutions[0][x[i]]) for i in range(len(rays)) ] ) tube_bases = [ t[0] for t in self.affine_tubes() ] connected_components = [] index = 0 for t in tube_bases: component = [] for a in t: if solution_dict[a] == 0: if component: connected_components.append( component ) component = [] else: component.append( (a,solution_dict[a]) ) if component: if connected_components: connected_components[index] = ( component + connected_components[index] ) else: connected_components.append( component ) index = len(connected_components) expansion = dict() while connected_components: component = connected_components.pop() c = min( [ a[1] for a in component] ) expansion[sum( [a[0] for a in component])] = c component = [ (a[0],a[1]-c) for a in component ] new_component = [] for a in component: if a[1] == 0: if new_component: connected_components.append( new_component ) new_component = [] else: new_component.append( a ) if new_component: connected_components.append( new_component ) return expansion if self.is_finite(): shifted_expansion = self.cluster_expansion( self.tau_c()(beta) ) return dict( [ (self.tau_c_inverse()(x),shifted_expansion[x]) for x in shifted_expansion ] )
def cluster_expansion(self, beta): if beta == 0: return dict() coefficients = beta.monomial_coefficients() if any(x < 0 for x in coefficients.values()): alpha = [-x for x in self.initial_cluster()] negative_part = dict([(-alpha[x], -coefficients[x]) for x in coefficients if coefficients[x] < 0]) positive_part = sum([ coefficients[x] * alpha[x] for x in coefficients if coefficients[x] > 0 ]) return dict(negative_part.items() + self.cluster_expansion(positive_part).items()) if self.is_affine(): if self.gamma().associated_coroot().scalar(beta) < 0: shifted_expansion = self.cluster_expansion(self.tau_c()(beta)) return dict([(self.tau_c_inverse()(x), shifted_expansion[x]) for x in shifted_expansion]) elif self.gamma().associated_coroot().scalar(beta) > 0: shifted_expansion = self.cluster_expansion( self.tau_c_inverse()(beta)) return dict([(self.tau_c()(x), shifted_expansion[x]) for x in shifted_expansion]) else: ### # Assumptions # # Two cases are possible for vectors in the interior of the cone # according to how many tubes there are: # 1) If there is only one tube then its extremal rays are linearly # independent, therefore a point is in the interior of the cone # if and only if it is a linear combination of all the extremal # rays with strictly positive coefficients. In this case solve() # should produce only one solution. # 2) If there are two or three tubes then the extreme rays are # linearly dependent. A vector is in the interior of the cone if # and only if it can be written as a strictly positive linear # combination of all the rays of at least one tube. In this case # solve() should return at least two solutions. # # If a vector is on one face of the cone than it can be written # uniquely as linear combination of the rays of that face (they # are linearly independent). solve() should return only one # solution no matter how many tubes there are. rays = flatten([t[0] for t in self.affine_tubes()]) system = matrix(map(vector, rays)).transpose() x = vector(var(['x%d' % i for i in range(len(rays))])) eqs = [(system * x)[i] == vector(beta)[i] for i in range(self._n)] ieqs = [y >= 0 for y in x] solutions = solve(eqs + ieqs, x, solution_dict=True) if not solutions: # we are outside the cone shifted_expansion = self.cluster_expansion( self.tau_c()(beta)) return dict([(self.tau_c_inverse()(v), shifted_expansion[v]) for v in shifted_expansion]) if len(solutions) > 1 or all(v > 0 for v in solutions[0].values()): # we are in the interior of the cone raise ValueError("Vectors in the interior of the cone do " "not have a cluster expansion") # we are on the boundary of the cone solution_dict = dict([(rays[i], solutions[0][x[i]]) for i in range(len(rays))]) tube_bases = [t[0] for t in self.affine_tubes()] connected_components = [] index = 0 for t in tube_bases: component = [] for a in t: if solution_dict[a] == 0: if component: connected_components.append(component) component = [] else: component.append((a, solution_dict[a])) if component: if connected_components: connected_components[index] = ( component + connected_components[index]) else: connected_components.append(component) index = len(connected_components) expansion = dict() while connected_components: component = connected_components.pop() c = min([a[1] for a in component]) expansion[sum([a[0] for a in component])] = c component = [(a[0], a[1] - c) for a in component] new_component = [] for a in component: if a[1] == 0: if new_component: connected_components.append(new_component) new_component = [] else: new_component.append(a) if new_component: connected_components.append(new_component) return expansion if self.is_finite(): shifted_expansion = self.cluster_expansion(self.tau_c()(beta)) return dict([(self.tau_c_inverse()(x), shifted_expansion[x]) for x in shifted_expansion])