def centroid(self): """The centroid of the polygon. Returns ------- centroid : Point See Also -------- Point Examples -------- >>> from sympy import Point, Polygon >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)]) >>> poly = Polygon(p1, p2, p3, p4) >>> poly.centroid Point(31/18, 11/18) """ A = 1/(6*self.area) cx, cy = 0, 0 for i in xrange(len(self)): pt1 = self[i - 1] pt2 = self[i] v = pt1[0]*pt2[1] - pt2[0]*pt1[1] cx += v*(pt1[0] + pt2[0]) cy += v*(pt1[1] + pt2[1]) return Point(simplify(A*cx), simplify(A*cy))
def centroid(self): """The centroid of the polygon. Returns ------- centroid : Point See Also -------- Point Examples -------- >>> from sympy import Point, Polygon >>> p1, p2, p3, p4 = map(Point, [(0, 0), (1, 0), (5, 1), (0, 1)]) >>> poly = Polygon(p1, p2, p3, p4) >>> poly.centroid Point(31/18, 11/18) """ A = 1 / (6*self.area) cx,cy = 0,0 for ind in xrange(-1, len(self.vertices)-1): pi = self.vertices[ind] pii = self.vertices[ind+1] v = pi[0]*pii[1]-pii[0]*pi[1] cx += v*(pi[0] + pii[0]) cy += v*(pi[1] + pii[1]) return Point(simplify(A*cx), simplify(A*cy))
def test_reciprocal_frame_test(): with GA_Printer(): metric = '1 # #,' + \ '# 1 #,' + \ '# # 1,' (e1, e2, e3) = MV.setup('e1 e2 e3', metric) E = e1 ^ e2 ^ e3 Esq = (E*E).scalar() assert str(E) == 'e1^e2^e3' assert str(Esq) == '(e1.e2)**2 - 2*(e1.e2)*(e1.e3)*(e2.e3) + (e1.e3)**2 + (e2.e3)**2 - 1' Esq_inv = 1/Esq E1 = (e2 ^ e3)*E E2 = (-1)*(e1 ^ e3)*E E3 = (e1 ^ e2)*E assert str(E1) == '((e2.e3)**2 - 1)*e1 + ((e1.e2) - (e1.e3)*(e2.e3))*e2 + (-(e1.e2)*(e2.e3) + (e1.e3))*e3' assert str(E2) == '((e1.e2) - (e1.e3)*(e2.e3))*e1 + ((e1.e3)**2 - 1)*e2 + (-(e1.e2)*(e1.e3) + (e2.e3))*e3' assert str(E3) == '(-(e1.e2)*(e2.e3) + (e1.e3))*e1 + (-(e1.e2)*(e1.e3) + (e2.e3))*e2 + ((e1.e2)**2 - 1)*e3' w = (E1 | e2) w = w.expand() assert str(w) == '0' w = (E1 | e3) w = w.expand() assert str(w) == '0' w = (E2 | e1) w = w.expand() assert str(w) == '0' w = (E2 | e3) w = w.expand() assert str(w) == '0' w = (E3 | e1) w = w.expand() assert str(w) == '0' w = (E3 | e2) w = w.expand() assert str(w) == '0' w = (E1 | e1) w = (w.expand()).scalar() Esq = expand(Esq) assert str(simplify(w/Esq)) == '1' w = (E2 | e2) w = (w.expand()).scalar() assert str(simplify(w/Esq)) == '1' w = (E3 | e3) w = (w.expand()).scalar() assert str(simplify(w/Esq)) == '1' return
def custom_intersection(c1,c2): #check if they are the same circle if c1.center == c2.center: if c2.radius == c1.radius: return c2 return [] dx, dy = (c2.center - c1.center).args d = (dy**2 + dx**2)**(.5) a = (c1.radius**2 - c2.radius**2 + d**2) / (2*d) x2 = c1.center.x + (dx * a/d) y2 = c1.center.y + (dy * a/d) h = (c1.radius**2 - a**2)**(.5) rx = -dy * (h/d) ry = dx * (h/d) xi_1 = simplify(x2 + rx) xi_2 = simplify(x2 - rx) yi_1 = simplify(y2 + ry) yi_2 = simplify(y2 - ry) ret = [Point(xi_1, yi_1)] if xi_1 != xi_2 or yi_1 != yi_2: ret.append(Point(xi_2, yi_2)) return ret
def is_collinear(*points): """Is a sequence of points collinear? Test whether or not a set of points are collinear. Returns True if the set of points are collinear, or False otherwise. Parameters ---------- points : sequence of Point Returns ------- is_collinear : boolean Notes -------------------------- Slope is preserved everywhere on a line, so the slope between any two points on the line should be the same. Take the first two points, p1 and p2, and create a translated point v1 with p1 as the origin. Now for every other point we create a translated point, vi with p1 also as the origin. Note that these translations preserve slope since everything is consistently translated to a new origin of p1. Since slope is preserved then we have the following equality: v1_slope = vi_slope => v1.y/v1.x = vi.y/vi.x (due to translation) => v1.y*vi.x = vi.y*v1.x => v1.y*vi.x - vi.y*v1.x = 0 (*) Hence, if we have a vi such that the equality in (*) is False then the points are not collinear. We do this test for every point in the list, and if all pass then they are collinear. Examples -------- >>> from sympy import Point >>> from sympy.abc import x >>> p1, p2 = Point(0, 0), Point(1, 1) >>> p3, p4, p5 = Point(2, 2), Point(x, x), Point(1, 2) >>> Point.is_collinear(p1, p2, p3, p4) True >>> Point.is_collinear(p1, p2, p3, p5) False """ points = GeometryEntity.extract_entities(points) if len(points) == 0: return False if len(points) <= 2: return True # two points always form a line # XXX Cross product is used now, but that only extends to three # dimensions. If the concept needs to extend to greater # dimensions then another method would have to be used p1 = points[0] p2 = points[1] v1 = p2 - p1 for p3 in points[2:]: v2 = p3 - p1 test = simplify(v1[0]*v2[1] - v1[1]*v2[0]) if simplify(test) != 0: return False return True
def test_reciprocal_frame(): """ Test of formula for general reciprocal frame of three vectors. Let three independent vectors be e1, e2, and e3. The reciprocal vectors E1, E2, and E3 obey the relations: e_i.E_j = delta_ij*(e1^e2^e3)**2 """ g = '1 # #,'+ \ '# 1 #,'+ \ '# # 1' g3dn = Ga('e1 e2 e3',g=g) (e1,e2,e3) = g3dn.mv() E = e1^e2^e3 Esq = (E*E).scalar() Esq_inv = 1 / Esq E1 = (e2^e3)*E E2 = (-1)*(e1^e3)*E E3 = (e1^e2)*E w = (E1|e2) w = w.expand() assert w.scalar() == 0 w = (E1|e3) w = w.expand() assert w.scalar() == 0 w = (E2|e1) w = w.expand() assert w.scalar() == 0 w = (E2|e3) w = w.expand() assert w.scalar() == 0 w = (E3|e1) w = w.expand() assert w.scalar() == 0 w = (E3|e2) w = w.expand() assert w.scalar() == 0 w = (E1|e1) w = (w.expand()).scalar() Esq = expand(Esq) assert simplify(w/Esq) == 1 w = (E2|e2) w = (w.expand()).scalar() assert simplify(w/Esq) == 1 w = (E3|e3) w = (w.expand()).scalar() assert simplify(w/Esq) == 1
def tangent_line(self, p): """ If p is on the ellipse, returns the tangent line through point p. Otherwise, returns the tangent line(s) from p to the ellipse, or None if no tangent line is possible (e.g., p inside ellipse). Example: In [1]: e = Ellipse(Point(0,0), 3, 2) In [2]: t = e.tangent_line(e.random_point()) In [3]: p = Plot() In [4]: p[0] = e In [5]: p[1] = t The above will plot an ellipse together with a tangent line. """ if p in self: rise = (self.vradius ** 2)*(self.center[0] - p[0]) run = (self.hradius ** 2)*(p[1] - self.center[1]) p2 = Point(simplify(p[0] + run), simplify(p[1] + rise)) return Line(p, p2) else: # TODO If p is not on the ellipse, attempt to create the # tangent(s) from point p to the ellipse..? raise NotImplementedError("Cannot find tangent lines when p is not on the ellipse")
def arbitrary_point(self, parameter_name='t'): """A parametrised point on the Line. Parameters ---------- parameter_name : str, optional The name of the parameter which will be used for the parametric point. The default value is 't'. Returns ------- point : Point See Also -------- Point Examples -------- >>> from sympy import Point, Line >>> p1, p2 = Point(1, 0), Point(5, 3) >>> l1 = Line(p1, p2) >>> l1.arbitrary_point() Point(1 + 4*t, 3*t) """ t = C.Symbol(parameter_name, real=True) x = simplify(self.p1[0] + t*(self.p2[0] - self.p1[0])) y = simplify(self.p1[1] + t*(self.p2[1] - self.p1[1])) return Point(x, y)
def incenter(self): """The center of the incircle. The incircle is the circle which lies inside the triangle and touches all three sides. Returns ------- incenter : Point See Also -------- incircle Point Examples -------- >>> from sympy.geometry import Point, Triangle >>> p1, p2, p3 = Point(0, 0), Point(1, 0), Point(0, 1) >>> t = Triangle(p1, p2, p3) >>> t.incenter Point(-sqrt(2)/2 + 1, -sqrt(2)/2 + 1) """ s = self.sides v = self.vertices A,B,C = v[0],v[1],v[2] a,b,c = s[1].length,s[2].length,s[0].length x = simplify((a*A[0] + b*B[0] + c*C[0]) / (a+b+c)) y = simplify((a*A[1] + b*B[1] + c*C[1]) / (a+b+c)) return Point(x, y)
def __new__(cls, radius=1, center=[0,0,0], direction=[0,0,1], closed=False, **kwargs): """ >>> from sympy import * >>> from symplus.strplus import init_mprinting >>> init_mprinting() >>> InfiniteCylinder() InfiniteCylinder(1, [0 0 0]', [0 0 1]', False) >>> InfiniteCylinder(2, [0,0,0], [0,1,1]) InfiniteCylinder(2, [0 0 0]', [0 -sqrt(2)/2 -sqrt(2)/2]', False) >>> InfiniteCylinder().contains((1,1,1)) False >>> InfiniteCylinder(2, [0,0,0], [0,1,1]).contains((1,1,1)) True """ normalization = kwargs.pop("normalization", True) radius = sympify(abs(radius)) direction = Mat(direction) if normalization: if norm(direction) == 0: raise ValueError direction = simplify(normalize(direction)) direction = max(direction, -direction, key=hash) center = Mat(center) if normalization: center = simplify(center - project(center, direction)) closed = sympify(bool(closed)) return Basic.__new__(cls, radius, center, direction, closed)
def _separate(eq, dep, others): """Separate expression into two parts based on dependencies of variables.""" # FIRST PASS # Extract derivatives depending our separable variable... terms = set() for term in eq.args: if term.is_Mul: for i in term.args: if i.is_Derivative and not i.has_any_symbols(*others): terms.add(term) continue elif term.is_Derivative and not term.has_any_symbols(*others): terms.add(term) # Find the factor that we need to divide by div = set() for term in terms: ext, sep = term.expand().as_independent(dep) # Failed? if sep.has_any_symbols(*others): return None div.add(ext) # FIXME: Find lcm() of all the divisors and divide with it, instead of # current hack :( # http://code.google.com/p/sympy/issues/detail?id=1498 if len(div) > 0: final = 0 for term in eq.args: eqn = 0 for i in div: eqn += term / i final += simplify(eqn) eq = final # SECOND PASS - separate the derivatives div = set() lhs = rhs = 0 for term in eq.args: # Check, whether we have already term with independent variable... if not term.has_any_symbols(*others): lhs += term continue # ...otherwise, try to separate temp, sep = term.expand().as_independent(dep) # Failed? if sep.has_any_symbols(*others): return None # Extract the divisors div.add(sep) rhs -= term.expand() # Do the division fulldiv = reduce(operator.add, div) lhs = simplify(lhs/fulldiv).expand() rhs = simplify(rhs/fulldiv).expand() # ...and check whether we were successful :) if lhs.has_any_symbols(*others) or rhs.has_any_symbols(dep): return None return [lhs, rhs]
def test_reciprocal_frame_test(): with GA_Printer(): metric = "1 # #," + "# 1 #," + "# # 1," (e1, e2, e3) = MV.setup("e1 e2 e3", metric) E = e1 ^ e2 ^ e3 Esq = (E * E).scalar() assert str(E) == "e1^e2^e3" assert str(Esq) == "(e1.e2)**2 - 2*(e1.e2)*(e1.e3)*(e2.e3) + (e1.e3)**2 + (e2.e3)**2 - 1" Esq_inv = 1 / Esq E1 = (e2 ^ e3) * E E2 = (-1) * (e1 ^ e3) * E E3 = (e1 ^ e2) * E assert str(E1) == "((e2.e3)**2 - 1)*e1 + ((e1.e2) - (e1.e3)*(e2.e3))*e2 + (-(e1.e2)*(e2.e3) + (e1.e3))*e3" assert str(E2) == "((e1.e2) - (e1.e3)*(e2.e3))*e1 + ((e1.e3)**2 - 1)*e2 + (-(e1.e2)*(e1.e3) + (e2.e3))*e3" assert str(E3) == "(-(e1.e2)*(e2.e3) + (e1.e3))*e1 + (-(e1.e2)*(e1.e3) + (e2.e3))*e2 + ((e1.e2)**2 - 1)*e3" w = E1 | e2 w = w.expand() assert str(w) == "0" w = E1 | e3 w = w.expand() assert str(w) == "0" w = E2 | e1 w = w.expand() assert str(w) == "0" w = E2 | e3 w = w.expand() assert str(w) == "0" w = E3 | e1 w = w.expand() assert str(w) == "0" w = E3 | e2 w = w.expand() assert str(w) == "0" w = E1 | e1 w = (w.expand()).scalar() Esq = expand(Esq) assert str(simplify(w / Esq)) == "1" w = E2 | e2 w = (w.expand()).scalar() assert str(simplify(w / Esq)) == "1" w = E3 | e3 w = (w.expand()).scalar() assert str(simplify(w / Esq)) == "1" return
def test_simplify(): x, y = R2_r.coord_functions() dx, dy = R2_r.base_oneforms() ex, ey = R2_r.base_vectors() assert simplify(x) == x assert simplify(x*y) == x*y assert simplify(dx*dy) == dx*dy assert simplify(ex*ey) == ex*ey assert ((1-x)*dx)/(1-x)**2 == dx/(1-x)
def incenter(self): """The incenter of the triangle.""" s = self.sides v = self.vertices A,B,C = v[0],v[1],v[2] a,b,c = s[1].length,s[2].length,s[0].length x = simplify( (a*A[0] + b*B[0] + c*C[0]) / (a+b+c) ) y = simplify( (a*A[1] + b*B[1] + c*C[1]) / (a+b+c) ) return Point(x, y)
def _eval_rewrite_as_cos(self, n, m, theta, phi): # This method can be expensive due to extensive use of simplification! from sympy.simplify import simplify, trigsimp # TODO: Make sure n \in N # TODO: Assert |m| <= n ortherwise we should return 0 term = simplify(self.expand(func=True)) # We can do this because of the range of theta term = term.xreplace({Abs(sin(theta)):sin(theta)}) return simplify(trigsimp(term))
def _image(self, func): if isinstance(func, EuclideanTransformation): direction = simplify(qrotate(func.rquat, func.parity*self.direction)) offset = simplify(self.offset + dot(func.tvec, direction)) closed = self.closed return Halfspace( offset=offset, direction=direction, closed=closed, normalization=False)
def cleanup(self): self.cleanState = symbols(self.dofNames) self.cleanVelocity = symbols(['d'+name for name in self.dofNames]) symdict = dict(zip(self.state+self.velocity, self.cleanState+self.cleanVelocity)) cleanMatrix = Matrix([simplify(element).subs(symdict) for element in self.mass]) self.cleanMass = list([cleanMatrix.reshape(self.degreesOfFreedom, self.degreesOfFreedom)]) self.cleanForce = [simplify(element).subs(symdict) for element in self.force] self.cleanConstraint = simplify(self.constraint_equation.subs(symdict)) self.cleanConstraintForce = [simplify(element).subs(symdict) for element in self.virtualForce] self.cleanNullForce = self.nullForce
def cleanup(self): symbolDictionary = dict(zip(self.state + self.velocity, self.cleanState + self.cleanVelocity)) N = len(self._fullCoordinate) cleanMatrix = Matrix([simplify(element).subs(symbolDictionary) for element in self.mass]) self._cleanMass = list([cleanMatrix.reshape(N, N)]) self._cleanForce = [simplify(element).subs(symbolDictionary) for element in self.force] self.SpecialFunctions(symbolDictionary) self.cleanConstraint = simplify(self.constraint_equation.subs(symbolDictionary)) self.cleanConstraintForce = [simplify(element).subs(symbolDictionary) for element in self.virtualForce] self.cleanNullForce = self.nullForce
def __add__(self, other): """Add two points, or add a factor to this point's coordinates.""" if isinstance(other, Point): if len(other) == len(self): return Point([simplify(a + b) for a, b in zip(self, other)]) else: raise TypeError("Points must have the same number of dimensions") else: other = sympify(other) return Point([simplify(a + other) for a in self])
def intersection(self, o): if isinstance(o, Circle): dx,dy = o.center - self.center d = sqrt( simplify(dy**2 + dx**2) ) a = simplify((self.radius**2 - o.radius**2 + d**2) / (2*d)) x2 = self.center[0] + (dx * a/d) y2 = self.center[1] + (dy * a/d) h = sqrt( simplify(self.radius**2 - a**2) ) rx = -dy * (h/d) ry = dx * (h/d) xi_1 = simplify(x2 + rx) xi_2 = simplify(x2 - rx) yi_1 = simplify(y2 + ry) yi_2 = simplify(y2 - ry) ret = [Point(xi_1, yi_1)] if xi_1 != xi_2 or yi_1 != yi_2: ret.append(Point(xi_2, yi_2)) return ret elif isinstance(o, Ellipse): a, b, r = o.hradius, o.vradius, self.radius x = a*sqrt(simplify((r**2 - b**2)/(a**2 - b**2))) y = b*sqrt(simplify((a**2 - r**2)/(a**2 - b**2))) return list(set([Point(x,y), Point(x,-y), Point(-x,y), Point(-x,-y)])) return Ellipse.intersection(self, o)
def arbitrary_point(self, parameter='t'): """A parameterized point on the Segment. Parameters ========== parameter : str, optional The name of the parameter which will be used for the parametric point. The default value is 't'. Returns ======= point : Point Parameters ========== parameter : str, optional The name of the parameter which will be used for the parametric point. The default value is 't'. Returns ======= point : Point Raises ====== ValueError When ``parameter`` already appears in the Segment's definition. See Also ======== sympy.geometry.point.Point Examples ======== >>> from sympy import Point, Segment >>> p1, p2 = Point(1, 0), Point(5, 3) >>> s1 = Segment(p1, p2) >>> s1.arbitrary_point() Point(4*t + 1, 3*t) """ t = _symbol(parameter) if t.name in (f.name for f in self.free_symbols): raise ValueError('Symbol %s already appears in object and cannot be used as a parameter.' % t.name) x = simplify(self.p1.x + t*(self.p2.x - self.p1.x)) y = simplify(self.p1.y + t*(self.p2.y - self.p1.y)) return Point(x, y)
def create_klann_geometry(orientation = 1, phase = 1, evaluate= True): O = Point(0,0) mOA = 60 OA = Point(0,-mOA) rotA = orientation*66.28*np.pi/180 A = OA.rotate(rotA) OB = Point(0,1.121*mOA) #.611 rotB = orientation*-41.76*np.pi/180 # -63.76 B = simplify(OB.rotate(rotB)) M_circ = Circle(O, .412*mOA) M = M_circ.arbitrary_point() if evaluate: st = {M.free_symbols.pop():phase} M = M.evalf(subs=st) C_circ1 = Circle(M,1.143*mOA) C_circ2 = Circle(A, .909*mOA) C_ints = custom_intersection(C_circ1,C_circ2) i= 0 if orientation == -1: i = 1 C = C_ints[i] #the circles have two intersections, check its the right value C = simplify(C) MC_length = M.distance(C) CD_length = .726*mOA Dx = C.x + ((C.x - M.x)/ MC_length)*CD_length Dy = C.y + ((C.y - M.y)/ MC_length)*CD_length D = Point(Dx, Dy) D = simplify(D) D_circ = Circle(D, .93*mOA) #.93 B_circ = Circle(B, .8*mOA) # 1.323 #.8 E_ints = custom_intersection(B_circ, D_circ) E=E_ints[i] #same deal E = simplify(E) ED_length = E.distance(D) DF_length = 2.577*mOA Fx = D.x + ((D.x - E.x)/ ED_length)*DF_length Fy = D.y + ((D.y - E.y)/ ED_length)*DF_length F = Point(Fx, Fy) F = simplify(F) b1 = Segment(M,D) b2 = Segment(B,E) b3 = Segment(A,C) b4 = Segment(E,F) conn = Segment(O,M) items = [O,A,B, C, D, E, F,M, b1,b2,b3,b4,conn] return items
def centroid(self): """The centroid of the polygon.""" A = 1 / (6*self.area) cx,cy = 0,0 for ind in xrange(-1, len(self.vertices)-1): pi = self.vertices[ind] pii = self.vertices[ind+1] v = pi[0]*pii[1]-pii[0]*pi[1] cx += v*(pi[0] + pii[0]) cy += v*(pi[1] + pii[1]) return Point(simplify(A*cx), simplify(A*cy))
def __contains__(self, o): """Return True if o is on this Line, or False otherwise.""" if isinstance(o, Line): return self.__eq__(o) elif isinstance(o, Point): x = C.Symbol('x', real=True) y = C.Symbol('y', real=True) r = self.equation().subs({x: o[0], y: o[1]}) x = simplify(r) return simplify(x) == 0 else: return False
def _image(self, func): if isinstance(func, EuclideanTransformation): radius = self.radius direction = simplify(qrotate(func.rquat, func.parity*self.direction)) center = simplify(qrotate(func.rquat, func.parity*self.center)) center = simplify(center + func.tvec - project(func.tvec, direction)) closed = self.closed return InfiniteCylinder( radius=radius, center=center, direction=direction, closed=closed, normalization=False)
def __add__(self, other): """ Create a new point where each coordinate in this point is increased by the corresponding coordinate in other. """ if isinstance(other, Point): if len(other) == len(self): return Point( [simplify(a+b) for a,b in zip(self, other)] ) else: raise TypeError("Points must have the same number of dimensions") else: other = sympify(other) return Point( [simplify(a+other) for a in self] )
def tangent_line(self, p): """Tangent lines between `p` and the ellipse. If `p` is on the ellipse, returns the tangent line through point `p`. Otherwise, returns the tangent line(s) from `p` to the ellipse, or None if no tangent line is possible (e.g., `p` inside ellipse). Parameters ---------- p : Point Returns ------- tangent_line : Line Raises ------ NotImplementedError Can only find tangent lines for a point, `p`, on the ellipse. See Also -------- Point Line Examples -------- >>> from sympy import Point, Ellipse >>> e1 = Ellipse(Point(0, 0), 3, 2) >>> e1.tangent_line(Point(3, 0)) Line(Point(3, 0), Point(3, -12)) >>> # This will plot an ellipse together with a tangent line. >>> from sympy import Point, Ellipse, Plot >>> e = Ellipse(Point(0,0), 3, 2) >>> t = e.tangent_line(e.random_point()) # doctest: +SKIP >>> p = Plot() # doctest: +SKIP >>> p[0] = e # doctest: +SKIP >>> p[1] = t # doctest: +SKIP """ if p in self: rise = (self.vradius ** 2)*(self.center[0] - p[0]) run = (self.hradius ** 2)*(p[1] - self.center[1]) p2 = Point(simplify(p[0] + run), simplify(p[1] + rise)) return Line(p, p2) else: # TODO If p is not on the ellipse, attempt to create the # tangent(s) from point p to the ellipse..? raise NotImplementedError("Cannot find tangent lines when p is not on the ellipse")
def arbitrary_point(self, parameter='t'): """A parameterized point on the Segment. Parameters ---------- parameter : str, optional The name of the parameter which will be used for the parametric point. The default value is 't'. Returns ------- point : Point Parameters ---------- parameter : str, optional The name of the parameter which will be used for the parametric point. The default value is 't'. Returns ------- point : Point Raises ------ ValueError When `parameter` already appears in the Segment's definition. See Also -------- Point Examples -------- >>> from sympy import Point, Segment >>> p1, p2 = Point(1, 0), Point(5, 3) >>> s1 = Segment(p1, p2) >>> s1.arbitrary_point() Point(1 + 4*t, 3*t) """ t = _symbol(parameter) if t.name in (f.name for f in self.free_symbols): raise ValueError('Symbol %s already appears in object and cannot be used as a parameter.' % t.name) x = simplify(self.p1[0] + t*(self.p2[0] - self.p1[0])) y = simplify(self.p1[1] + t*(self.p2[1] - self.p1[1])) return Point(x, y)
def midpoint(self, p): """The midpoint between self and point p. Parameters ========== p : Point Returns ======= midpoint : Point See Also ======== sympy.geometry.line.Segment.midpoint Examples ======== >>> from sympy import Point3D >>> p1, p2 = Point3D(1, 1, 1), Point3D(13, 5, 1) >>> p1.midpoint(p2) Point3D(7, 3, 1) """ p = Point3D(p) return Point3D([simplify((a + b)*S.Half) for a, b in zip(self.args, p.args)])
def midpoint(self, p): """The midpoint between self and point p. Parameters ========== p : Point Returns ======= midpoint : Point See Also ======== sympy.geometry.line.Segment.midpoint Examples ======== >>> from sympy.geometry import Point >>> p1, p2 = Point(1, 1), Point(13, 5) >>> p1.midpoint(p2) Point2D(7, 3) """ s, p = Point._normalize_dimension(self, Point(p)) return Point([simplify((a + b)*S.Half) for a, b in zip(s, p)])
def rsolve(f, y, init=None): """ Solve univariate recurrence with rational coefficients. Given `k`-th order linear recurrence `\operatorname{L} y = f`, or equivalently: .. math:: a_{k}(n) y(n+k) + a_{k-1}(n) y(n+k-1) + \dots + a_{0}(n) y(n) = f(n) where `a_{i}(n)`, for `i=0, \dots, k`, are polynomials or rational functions in `n`, and `f` is a hypergeometric function or a sum of a fixed number of pairwise dissimilar hypergeometric terms in `n`, finds all solutions or returns ``None``, if none were found. Initial conditions can be given as a dictionary in two forms: (1) ``{ n_0 : v_0, n_1 : v_1, ..., n_m : v_m }`` (2) ``{ y(n_0) : v_0, y(n_1) : v_1, ..., y(n_m) : v_m }`` or as a list ``L`` of values: ``L = [ v_0, v_1, ..., v_m ]`` where ``L[i] = v_i``, for `i=0, \dots, m`, maps to `y(n_i)`. Examples ======== Lets consider the following recurrence: .. math:: (n - 1) y(n + 2) - (n^2 + 3 n - 2) y(n + 1) + 2 n (n + 1) y(n) = 0 >>> from sympy import Function, rsolve >>> from sympy.abc import n >>> y = Function('y') >>> f = (n - 1)*y(n + 2) - (n**2 + 3*n - 2)*y(n + 1) + 2*n*(n + 1)*y(n) >>> rsolve(f, y(n)) 2**n*C0 + C1*factorial(n) >>> rsolve(f, y(n), { y(0):0, y(1):3 }) 3*2**n - 3*factorial(n) See Also ======== rsolve_poly, rsolve_ratio, rsolve_hyper """ if isinstance(f, Equality): f = f.lhs - f.rhs n = y.args[0] k = Wild('k', exclude=(n, )) # Preprocess user input to allow things like # y(n) + a*(y(n + 1) + y(n - 1))/2 f = f.expand().collect(y.func(Wild('m', integer=True))) h_part = defaultdict(lambda: S.Zero) i_part = S.Zero for g in Add.make_args(f): coeff = S.One kspec = None for h in Mul.make_args(g): if h.is_Function: if h.func == y.func: result = h.args[0].match(n + k) if result is not None: kspec = int(result[k]) else: raise ValueError("'%s(%s+k)' expected, got '%s'" % (y.func, n, h)) else: raise ValueError("'%s' expected, got '%s'" % (y.func, h.func)) else: coeff *= h if kspec is not None: h_part[kspec] += coeff else: i_part += coeff for k, coeff in h_part.items(): h_part[k] = simplify(coeff) common = S.One for coeff in h_part.values(): if coeff.is_rational_function(n): if not coeff.is_polynomial(n): common = lcm(common, coeff.as_numer_denom()[1], n) else: raise ValueError( "Polynomial or rational function expected, got '%s'" % coeff) i_numer, i_denom = i_part.as_numer_denom() if i_denom.is_polynomial(n): common = lcm(common, i_denom, n) if common is not S.One: for k, coeff in h_part.items(): numer, denom = coeff.as_numer_denom() h_part[k] = numer * quo(common, denom, n) i_part = i_numer * quo(common, i_denom, n) K_min = min(h_part.keys()) if K_min < 0: K = abs(K_min) H_part = defaultdict(lambda: S.Zero) i_part = i_part.subs(n, n + K).expand() common = common.subs(n, n + K).expand() for k, coeff in h_part.items(): H_part[k + K] = coeff.subs(n, n + K).expand() else: H_part = h_part K_max = max(H_part.keys()) coeffs = [H_part[i] for i in xrange(K_max + 1)] result = rsolve_hyper(coeffs, -i_part, n, symbols=True) if result is None: return None solution, symbols = result if init == {} or init == []: init = None if symbols and init is not None: if type(init) is list: init = dict([(i, init[i]) for i in xrange(len(init))]) equations = [] for k, v in init.items(): try: i = int(k) except TypeError: if k.is_Function and k.func == y.func: i = int(k.args[0]) else: raise ValueError("Integer or term expected, got '%s'" % k) try: eq = solution.limit(n, i) - v except NotImplementedError: eq = solution.subs(n, i) - v equations.append(eq) result = solve(equations, *symbols) if not result: return None else: solution = solution.subs(result) return solution
def hodge_star(T, g): """The calculation actions on the forms of the Hodge operator's. Examples: ========= >>> from sympy import symbols, Matrix >>> from sympy.tensor.arraypy import Arraypy, TensorArray >>> from sympy.tensor.tensor_fields import hodge_star >>> x1, x2, x3 = symbols('x1 x2 x3') >>> y3 = TensorArray(Arraypy((3, 3, 3)), (-1, -1, -1)) >>> y3[0, 1, 2] = 3 >>> y3[0, 2, 1] = -3 >>> y3[1, 0, 2] = -3 >>> y3[1, 2, 0] = 3 >>> y3[2, 0, 1] = 3 >>> y3[2, 1, 0] = -3 >>> g = Matrix([[2,1,0],[1,3,0],[0,0,1]]) >>> print(hodge_star(y3,g)) 96*sqrt(5)/5 """ if not isinstance(T, (TensorArray)): raise ValueError("The type of tensor must be TensorArray") if (len(T.type_pq) == 1 and T.type_pq[0] != 1) or (len(T.type_pq) > 1 and T.type_pq[0] != 0): raise ValueError("The valency of tensor must be (0,q)") if T.rank > 1: if not is_asymmetric(T): raise ValueError("The tensor must be a skew-symmetric") # Handling of the metric tensor check_metric_tensor(g) if isinstance(g, (TensorArray, Arraypy)): idx_start_g = g.start_index[0] det_g = det(g.to_matrix()) else: idx_start_g = 0 det_g = det(g) g = matrix2tensor(g) # The definition of the start index idx_start_T = T.start_index[0] if idx_start_T != idx_start_g: raise ValueError( "The start index of the tensor and metric must be equal") # 1. Calculating of tensor mu n = T.shape[0] # the dimension of the input array k = T.rank sqrt_det_g = simplify(sqrt(abs(det_g))) valence_list_mu = [(-1) for i in range(n)] mu = Arraypy([n, n, idx_start_g]).to_tensor(valence_list_mu) for idx in mu.index_list: mu[idx] = simplify(sqrt_det_g * sign_permutations(list(idx))) # 2. Tensor product mu and T uT = tensor_product(mu, T) # 3.Convolution by the first k-index and the last k-index # low_idx_numbers it is a list with the positions on which are the lower # indices low_idx_numbers = [i + 1 for i in range(k)] # Upping of the first k-lower indices of a tensor for position in low_idx_numbers: uT = raise_index(uT, g, position) # Convolution for i in range(k): uT = uT.contract(1, k + 1) k = k - 1 return uT
def diverg(X, args, g=None): """Return the divergence of a vector field X. Compute divergence of vector field consisting of N elements. Examples: ========= >>> from sympy.tensor.tensor_fields import diverg >>> from sympy import symbols, cos >>> from sympy.matrices import Matrix >>> x1, x2, x3 = symbols('x1 x2 x3') X is a vector field, args it's a list of symbol arguments of the vector field X. It's can be in list, array of arraypy or contravariant tensor: >>> X = [x1*x2**3,x2-cos(x3),x3**3-x1] >>> arg = [x1, x2, x3] g - optional parameter, metric tensor, which can be a matrix "Matrix", array of arraypy or covariant tensor: >>> g = Matrix([[2,1,0],[1,3,0],[0,0,1]]) >>> dv = diverg(X,arg,g) >>> print(dv) x2**3 + 3*x3**2 + 1 """ # Handling of a vector of arguments check_vector_of_arguments(args) if isinstance(args, list): idx_args = 0 else: idx_args = args.start_index[0] # Handling of the first vector field check_the_vector_field(X) if isinstance(X, (TensorArray, Arraypy)): idx_X = X.start_index[0] else: idx_X = 0 if idx_args != idx_X: raise ValueError( "The start index of vector field and vector of arguments must be \ equal") # Handling of the metric tensor if g is not None: if isinstance(g, (TensorArray, Arraypy)): g = g.to_matrix() else: g = eye(len(args)) # Calculation sq = sqrt(abs(det(g))) diver = 0 for k in range(len(args)): diver += simplify(1 / sq * sum([diff(X[k + idx_X] * sq, args[k + idx_X])])) # Output return diver
def tangent_lines(self, p): """Tangent lines between `p` and the ellipse. If `p` is on the ellipse, returns the tangent line through point `p`. Otherwise, returns the tangent line(s) from `p` to the ellipse, or None if no tangent line is possible (e.g., `p` inside ellipse). Parameters ========== p : Point Returns ======= tangent_lines : list with 1 or 2 Lines Raises ====== NotImplementedError Can only find tangent lines for a point, `p`, on the ellipse. See Also ======== sympy.geometry.point.Point, sympy.geometry.line.Line Examples ======== >>> from sympy import Point, Ellipse >>> e1 = Ellipse(Point(0, 0), 3, 2) >>> e1.tangent_lines(Point(3, 0)) [Line2D(Point2D(3, 0), Point2D(3, -12))] """ p = Point(p, dim=2) if self.encloses_point(p): return [] if p in self: delta = self.center - p rise = (self.vradius**2) * delta.x run = -(self.hradius**2) * delta.y p2 = Point(simplify(p.x + run), simplify(p.y + rise)) return [Line(p, p2)] else: if len(self.foci) == 2: f1, f2 = self.foci maj = self.hradius test = (2 * maj - Point.distance(f1, p) - Point.distance(f2, p)) else: test = self.radius - Point.distance(self.center, p) if test.is_number and test.is_positive: return [] # else p is outside the ellipse or we can't tell. In case of the # latter, the solutions returned will only be valid if # the point is not inside the ellipse; if it is, nan will result. x, y = Dummy('x'), Dummy('y') eq = self.equation(x, y) dydx = idiff(eq, y, x) slope = Line(p, Point(x, y)).slope # TODO: Replace solve with solveset, when this line is tested tangent_points = solve([slope - dydx, eq], [x, y]) # handle horizontal and vertical tangent lines if len(tangent_points) == 1: assert tangent_points[0][0] == p.x or tangent_points[0][ 1] == p.y return [Line(p, p + Point(1, 0)), Line(p, p + Point(0, 1))] # others return [Line(p, tangent_points[0]), Line(p, tangent_points[1])]
def rsolve_ratio(coeffs, f, n, **hints): """ Given linear recurrence operator `\operatorname{L}` of order `k` with polynomial coefficients and inhomogeneous equation `\operatorname{L} y = f`, where `f` is a polynomial, we seek for all rational solutions over field `K` of characteristic zero. This procedure accepts only polynomials, however if you are interested in solving recurrence with rational coefficients then use ``rsolve`` which will pre-process the given equation and run this procedure with polynomial arguments. The algorithm performs two basic steps: (1) Compute polynomial `v(n)` which can be used as universal denominator of any rational solution of equation `\operatorname{L} y = f`. (2) Construct new linear difference equation by substitution `y(n) = u(n)/v(n)` and solve it for `u(n)` finding all its polynomial solutions. Return ``None`` if none were found. Algorithm implemented here is a revised version of the original Abramov's algorithm, developed in 1989. The new approach is much simpler to implement and has better overall efficiency. This method can be easily adapted to q-difference equations case. Besides finding rational solutions alone, this functions is an important part of Hyper algorithm were it is used to find particular solution of inhomogeneous part of a recurrence. Examples ======== >>> from sympy.abc import x >>> from sympy.solvers.recurr import rsolve_ratio >>> rsolve_ratio([-2*x**3 + x**2 + 2*x - 1, 2*x**3 + x**2 - 6*x, ... - 2*x**3 - 11*x**2 - 18*x - 9, 2*x**3 + 13*x**2 + 22*x + 8], 0, x) C2*(2*x - 3)/(2*(x**2 - 1)) References ========== .. [1] S. A. Abramov, Rational solutions of linear difference and q-difference equations with polynomial coefficients, in: T. Levelt, ed., Proc. ISSAC '95, ACM Press, New York, 1995, 285-289 See Also ======== rsolve_hyper """ f = sympify(f) if not f.is_polynomial(n): return None coeffs = list(map(sympify, coeffs)) r = len(coeffs) - 1 A, B = coeffs[r], coeffs[0] A = A.subs(n, n - r).expand() h = Dummy('h') res = resultant(A, B.subs(n, n + h), n) if not res.is_polynomial(h): p, q = res.as_numer_denom() res = quo(p, q, h) nni_roots = list( roots(res, h, filter='Z', predicate=lambda r: r >= 0).keys()) if not nni_roots: return rsolve_poly(coeffs, f, n, **hints) else: C, numers = S.One, [S.Zero] * (r + 1) for i in xrange(int(max(nni_roots)), -1, -1): d = gcd(A, B.subs(n, n + i), n) A = quo(A, d, n) B = quo(B, d.subs(n, n - i), n) C *= Mul(*[d.subs(n, n - j) for j in xrange(0, i + 1)]) denoms = [C.subs(n, n + i) for i in range(0, r + 1)] for i in range(0, r + 1): g = gcd(coeffs[i], denoms[i], n) numers[i] = quo(coeffs[i], g, n) denoms[i] = quo(denoms[i], g, n) for i in xrange(0, r + 1): numers[i] *= Mul(*(denoms[:i] + denoms[i + 1:])) result = rsolve_poly(numers, f * Mul(*denoms), n, **hints) if result is not None: if hints.get('symbols', False): return (simplify(result[0] / C), result[1]) else: return simplify(result / C) else: return None
def test_matmul_simplify(): A = MatrixSymbol('A', 1, 1) assert simplify(MatMul(A, ImmutableMatrix([[sin(x)**2 + cos(x)**2]]))) == \ MatMul(A, ImmutableMatrix([[1]]))
def arbitrary_point(self, parameter='t'): """A parameterized point on the Ray. Parameters ========== parameter : str, optional The name of the parameter which will be used for the parametric point. The default value is 't'. Returns ======= point : Point Raises ====== ValueError When `parameter` already appears in the Ray's definition. See Also ======== sympy.geometry.point.Point Examples ======== >>> from sympy import Ray, Point, Segment, S, simplify, solve >>> from sympy.abc import t >>> r = Ray(Point(0, 0), Point(2, 3)) >>> p = r.arbitrary_point(t) The parameter `t` used in the arbitrary point maps 0 to the origin of the ray and 1 to the end of the ray at infinity (which will show up as NaN). >>> p.subs(t, 0), p.subs(t, 1) (Point(0, 0), Point(oo, oo)) The unit that `t` moves you is based on the spacing of the points used to define the ray. >>> p.subs(t, 1/(S(1) + 1)) # one unit Point(2, 3) >>> p.subs(t, 2/(S(1) + 2)) # two units out Point(4, 6) >>> p.subs(t, S.Half/(S(1) + S.Half)) # half a unit out Point(1, 3/2) If you want to be located a distance of 1 from the origin of the ray, what value of `t` is needed? a) find the unit length and pick t accordingly >>> u = Segment(r[0], p.subs(t, S.Half)).length # S.Half = 1/(1 + 1) >>> want = 1 >>> t_need = want/u >>> p_want = p.subs(t, t_need/(1 + t_need)) >>> simplify(Segment(r[0], p_want).length) 1 b) find the t that makes the length from origin to p equal to 1 >>> l = Segment(r[0], p).length >>> t_need = solve(l**2 - want**2, t) # use the square to remove abs() if it is there >>> t_need = [w for w in t_need if w.n() > 0][0] # take positive t >>> p_want = p.subs(t, t_need) >>> simplify(Segment(r[0], p_want).length) 1 """ t = _symbol(parameter) if t.name in (f.name for f in self.free_symbols): raise ValueError( 'Symbol %s already appears in object and cannot be used as a parameter.' % t.name) x = simplify(self.p1[0] + t / (1 - t) * (self.p2[0] - self.p1[0])) y = simplify(self.p1[1] + t / (1 - t) * (self.p2[1] - self.p1[1])) return Point(x, y)
def intersection(self, o): """The intersection with another geometrical entity. Parameters ========== o : Point or LinearEntity Returns ======= intersection : list of geometrical entities See Also ======== sympy.geometry.point.Point Examples ======== >>> from sympy import Point, Line, Segment >>> p1, p2, p3 = Point(0, 0), Point(1, 1), Point(7, 7) >>> l1 = Line(p1, p2) >>> l1.intersection(p3) [Point(7, 7)] >>> p4, p5 = Point(5, 0), Point(0, 3) >>> l2 = Line(p4, p5) >>> l1.intersection(l2) [Point(15/8, 15/8)] >>> p6, p7 = Point(0, 5), Point(2, 6) >>> s1 = Segment(p6, p7) >>> l1.intersection(s1) [] """ if isinstance(o, Point): if o in self: return [o] else: return [] elif isinstance(o, LinearEntity): a1, b1, c1 = self.coefficients a2, b2, c2 = o.coefficients t = simplify(a1 * b2 - a2 * b1) if t == 0: # are parallel? if isinstance(self, Line): if o.p1 in self: return [o] return [] elif isinstance(o, Line): if self.p1 in o: return [self] return [] elif isinstance(self, Ray): if isinstance(o, Ray): # case 1, rays in the same direction if self.xdirection == o.xdirection: if self.source[0] < o.source[0]: return [o] return [self] # case 2, rays in the opposite directions else: if o.source in self: if self.source == o.source: return [self.source] return [Segment(o.source, self.source)] return [] elif isinstance(o, Segment): if o.p1 in self: if o.p2 in self: return [o] return [Segment(o.p1, self.source)] elif o.p2 in self: return [Segment(o.p2, self.source)] return [] elif isinstance(self, Segment): if isinstance(o, Ray): return o.intersection(self) elif isinstance(o, Segment): # A reminder that the points of Segments are ordered # in such a way that the following works. See # Segment.__new__ for details on the ordering. if self.p1 not in o: if self.p2 not in o: # Neither of the endpoints are in o so either # o is contained in this segment or it isn't if o in self: return [self] return [] else: # p1 not in o but p2 is. Either there is a # segment as an intersection, or they only # intersect at an endpoint if self.p2 == o.p1: return [o.p1] return [Segment(o.p1, self.p2)] elif self.p2 not in o: # p2 not in o but p1 is. Either there is a # segment as an intersection, or they only # intersect at an endpoint if self.p1 == o.p2: return [o.p2] return [Segment(o.p2, self.p1)] # Both points of self in o so the whole segment # is in o return [self] # Unknown linear entity return [] # Not parallel, so find the point of intersection px = simplify((b1 * c2 - c1 * b2) / t) py = simplify((a2 * c1 - a1 * c2) / t) inter = Point(px, py) if inter in self and inter in o: return [inter] return [] return o.intersection(self)
def _eval_simplify(self, **kwargs): if self.is_Atom: return self else: return self.__class__(*[simplify(x, **kwargs) for x in self.args])
def linodesolve(A, t, b=None, B=None, type="auto", doit=False): r""" System of n equations linear first-order differential equations Explanation =========== This solver solves the system of ODEs of the follwing form: .. math:: X'(t) = A(t) X(t) + b(t) Here, $A(t)$ is the coefficient matrix, $X(t)$ is the vector of n independent variables, $b(t)$ is the non-homogeneous term and $X'(t)$ is the derivative of $X(t)$ Depending on the properties of $A(t)$ and $b(t)$, this solver evaluates the solution differently. When $A(t)$ is constant coefficient matrix and $b(t)$ is zero vector i.e. system is homogeneous, the solution is: .. math:: X(t) = \exp(A t) C Here, $C$ is a vector of constants and $A$ is the constant coefficient matrix. When $A(t)$ is constant coefficient matrix and $b(t)$ is non-zero i.e. system is non-homogeneous, the solution is: .. math:: X(t) = e^{A t} ( \int e^{- A t} b \,dt + C) When $A(t)$ is coefficient matrix such that its commutative with its antiderivative $B(t)$ and $b(t)$ is a zero vector i.e. system is homogeneous, the solution is: .. math:: X(t) = \exp(B(t)) C When $A(t)$ is commutative with its antiderivative $B(t)$ and $b(t)$ is non-zero i.e. system is non-homogeneous, the solution is: .. math:: X(t) = e^{B(t)} ( \int e^{-B(t)} b(t) \,dt + C) The final solution is the general solution for all the four equations since a constant coefficient matrix is always commutative with its antidervative. Parameters ========== A : Matrix Coefficient matrix of the system of linear first order ODEs. t : Symbol Independent variable in the system of ODEs. b : Matrix or None Non-homogeneous term in the system of ODEs. If None is passed, a homogeneous system of ODEs is assumed. B : Matrix or None Antiderivative of the coefficient matrix. If the antiderivative is not passed and the solution requires the term, then the solver would compute it internally. type : String Type of the system of ODEs passed. Depending on the type, the solution is evaluated. The type values allowed and the corresponding system it solves are: "type1" for constant coefficient homogeneous "type2" for constant coefficient non-homogeneous, "type3" for non-constant coefficient homogeneous and "type4" for non-constant coefficient non-homogeneous. The default value is "auto" which will let the solver decide the correct type of the system passed. doit : Boolean Evaluate the solution if True, default value is False Examples ======== To solve the system of ODEs using this function directly, several things must be done in the right order. Wrong inputs to the function will lead to incorrect results. >>> from sympy import symbols, Function, Eq >>> from sympy.solvers.ode.systems import canonical_odes, linear_ode_to_matrix, linodesolve, linodesolve_type >>> from sympy.solvers.ode.subscheck import checksysodesol >>> f, g = symbols("f, g", cls=Function) >>> x, a = symbols("x, a") >>> funcs = [f(x), g(x)] >>> eqs = [Eq(f(x).diff(x) - f(x), a*g(x) + 1), Eq(g(x).diff(x) + g(x), a*f(x))] Here, it is important to note that before we derive the coefficient matrix, it is important to get the system of ODEs into the desired form. For that we will use :obj:`sympy.solvers.ode.systems.canonical_odes()`. >>> eqs = canonical_odes(eqs, funcs, x) >>> eqs [Eq(Derivative(f(x), x), a*g(x) + f(x) + 1), Eq(Derivative(g(x), x), a*f(x) - g(x))] Now, we will use :obj:`sympy.solvers.ode.systems.linear_ode_to_matrix()` to get the coefficient matrix and the non-homogeneous term if it is there. >>> (A1, A0), b = linear_ode_to_matrix(eqs, funcs, x, 1) >>> A = -A0 We have the coefficient matrices and the non-homogeneous term ready. Now, we can use :obj:`sympy.solvers.ode.systems.linodesolve_type()` to get the information for the system of ODEs to finally pass it to the solver. >>> system_info = linodesolve_type(A, x, b=b) >>> sol_vector = linodesolve(A, x, b=b, B=system_info['antiderivative'], type=system_info['type']) Now, we can prove if the solution is correct or not by using :obj:`sympy.solvers.ode.subscheck.checksysodesol()` >>> sol = [Eq(f, s) for f, s in zip(funcs, sol_vector)] >>> checksysodesol(eqs, sol) (True, [0, 0]) We can also use the doit method to evaluate the solutions passed by the function. >>> sol_vector_evaluated = linodesolve(A, x, b=b, type="type2", doit=True) Now, we will look at a system of ODEs which is non-constant. >>> eqs = [Eq(f(x).diff(x), f(x) + x*g(x)), Eq(g(x).diff(x), -x*f(x) + g(x))] The system defined above is already in the desired form, so we don't have to convert it. >>> (A1, A0), b = linear_ode_to_matrix(eqs, funcs, x, 1) >>> A = -A0 A user can also pass the commutative antidervative required for type3 and type4 system of ODEs. Passing an incorrect one will lead to incorrect results. If the coefficient matrix is not commutative with its antiderivative, then :obj:`sympy.solvers.ode.systems.linodesolve_type()` raises a NotImplementedError. If it does have a commutative antiderivative, then the function just returns the information about the system. >>> system_info = linodesolve_type(A, x, b=b) Now, we can pass the antiderivative as an argument to get the solution. If the system information is not passed, then the solver will compute the required arguments internally. >>> sol_vector = linodesolve(A, x, b=b) Once again, we can verify the solution obtained. >>> sol = [Eq(f, s) for f, s in zip(funcs, sol_vector)] >>> checksysodesol(eqs, sol) (True, [0, 0]) Returns ======= List Raises ====== ValueError This error is raised when the coefficient matrix, non-homogeneous term or the antiderivative, if passed, aren't a matrix or don't have correct dimensions NonSquareMatrixError When the coefficient matrix or its antiderivative, if passed isn't a square matrix NotImplementedError If the coefficient matrix doesn't have a commutative antiderivative See Also ======== linear_ode_to_matrix: Coefficient matrix computation function canonical_odes: System of ODEs representation change linodesolve_type: Getting information about systems of ODEs to pass in this solver """ if not isinstance(A, MatrixBase): raise ValueError( filldedent('''\ The coefficients of the system of ODEs should be of type Matrix ''')) if not A.is_square: raise NonSquareMatrixError( filldedent('''\ The coefficient matrix must be a square ''')) if b is not None: if not isinstance(b, MatrixBase): raise ValueError( filldedent('''\ The non-homogeneous terms of the system of ODEs should be of type Matrix ''')) if A.rows != b.rows: raise ValueError( filldedent('''\ The system of ODEs should have the same number of non-homogeneous terms and the number of equations ''')) if B is not None: if not isinstance(B, MatrixBase): raise ValueError( filldedent('''\ The antiderivative of coefficients of the system of ODEs should be of type Matrix ''')) if not B.is_square: raise NonSquareMatrixError( filldedent('''\ The antiderivative of the coefficient matrix must be a square ''')) if A.rows != B.rows: raise ValueError( filldedent('''\ The coefficient matrix and its antiderivative should have same dimensions ''')) if not any(type == "type{}".format(i) for i in range(1, 5)) and not type == "auto": raise ValueError( filldedent('''\ The input type should be a valid one ''')) n = A.rows constants = numbered_symbols(prefix='C', cls=Symbol, start=1) Cvect = Matrix(list(next(constants) for _ in range(n))) if (type == "type2" or type == "type4") and b is None: b = zeros(n, 1) if type == "auto": system_info = linodesolve_type(A, t, b=b) type = system_info["type"] B = system_info["antiderivative"] if type == "type1" or type == "type2": P, J = matrix_exp_jordan_form(A, t) P = simplify(P) if type == "type1": sol_vector = P * (J * Cvect) else: sol_vector = P * J * ( (J.inv() * P.inv() * b).applyfunc(lambda x: Integral(x, t)) + Cvect) else: if B is None: B, _ = _is_commutative_anti_derivative(A, t) if type == "type3": sol_vector = B.exp() * Cvect else: sol_vector = B.exp() * (( (-B).exp() * b).applyfunc(lambda x: Integral(x, t)) + Cvect) gens = sol_vector.atoms(exp) if type != "type1": sol_vector = [expand_mul(s) for s in sol_vector] sol_vector = [collect(s, ordered(gens), exact=True) for s in sol_vector] if doit: sol_vector = [s.doit() for s in sol_vector] return sol_vector
def rsolve_hyper(coeffs, f, n, **hints): """ Given linear recurrence operator `\operatorname{L}` of order `k` with polynomial coefficients and inhomogeneous equation `\operatorname{L} y = f` we seek for all hypergeometric solutions over field `K` of characteristic zero. The inhomogeneous part can be either hypergeometric or a sum of a fixed number of pairwise dissimilar hypergeometric terms. The algorithm performs three basic steps: (1) Group together similar hypergeometric terms in the inhomogeneous part of `\operatorname{L} y = f`, and find particular solution using Abramov's algorithm. (2) Compute generating set of `\operatorname{L}` and find basis in it, so that all solutions are linearly independent. (3) Form final solution with the number of arbitrary constants equal to dimension of basis of `\operatorname{L}`. Term `a(n)` is hypergeometric if it is annihilated by first order linear difference equations with polynomial coefficients or, in simpler words, if consecutive term ratio is a rational function. The output of this procedure is a linear combination of fixed number of hypergeometric terms. However the underlying method can generate larger class of solutions - D'Alembertian terms. Note also that this method not only computes the kernel of the inhomogeneous equation, but also reduces in to a basis so that solutions generated by this procedure are linearly independent Examples ======== >>> from sympy.solvers import rsolve_hyper >>> from sympy.abc import x >>> rsolve_hyper([-1, -1, 1], 0, x) C0*(1/2 + sqrt(5)/2)**x + C1*(-sqrt(5)/2 + 1/2)**x >>> rsolve_hyper([-1, 1], 1 + x, x) C0 + x*(x + 1)/2 References ========== .. [1] M. Petkovsek, Hypergeometric solutions of linear recurrences with polynomial coefficients, J. Symbolic Computation, 14 (1992), 243-264. .. [2] M. Petkovsek, H. S. Wilf, D. Zeilberger, A = B, 1996. """ coeffs = list(map(sympify, coeffs)) f = sympify(f) r, kernel, symbols = len(coeffs) - 1, [], set() if not f.is_zero: if f.is_Add: similar = {} for g in f.expand().args: if not g.is_hypergeometric(n): return None for h in similar.keys(): if hypersimilar(g, h, n): similar[h] += g break else: similar[g] = S.Zero inhomogeneous = [] for g, h in similar.items(): inhomogeneous.append(g + h) elif f.is_hypergeometric(n): inhomogeneous = [f] else: return None for i, g in enumerate(inhomogeneous): coeff, polys = S.One, coeffs[:] denoms = [S.One] * (r + 1) s = hypersimp(g, n) for j in xrange(1, r + 1): coeff *= s.subs(n, n + j - 1) p, q = coeff.as_numer_denom() polys[j] *= p denoms[j] = q for j in xrange(0, r + 1): polys[j] *= Mul(*(denoms[:j] + denoms[j + 1:])) R = rsolve_poly(polys, Mul(*denoms), n) if not (R is None or R is S.Zero): inhomogeneous[i] *= R else: return None result = Add(*inhomogeneous) else: result = S.Zero Z = Dummy('Z') p, q = coeffs[0], coeffs[r].subs(n, n - r + 1) p_factors = [z for z in roots(p, n).keys()] q_factors = [z for z in roots(q, n).keys()] factors = [(S.One, S.One)] for p in p_factors: for q in q_factors: if p.is_integer and q.is_integer and p <= q: continue else: factors += [(n - p, n - q)] p = [(n - p, S.One) for p in p_factors] q = [(S.One, n - q) for q in q_factors] factors = p + factors + q for A, B in factors: polys, degrees = [], [] D = A * B.subs(n, n + r - 1) for i in xrange(0, r + 1): a = Mul(*[A.subs(n, n + j) for j in xrange(0, i)]) b = Mul(*[B.subs(n, n + j) for j in xrange(i, r)]) poly = quo(coeffs[i] * a * b, D, n) polys.append(poly.as_poly(n)) if not poly.is_zero: degrees.append(polys[i].degree()) d, poly = max(degrees), S.Zero for i in xrange(0, r + 1): coeff = polys[i].nth(d) if coeff is not S.Zero: poly += coeff * Z**i for z in roots(poly, Z).keys(): if z.is_zero: continue (C, s) = rsolve_poly([polys[i] * z**i for i in xrange(r + 1)], 0, n, symbols=True) if C is not None and C is not S.Zero: symbols |= set(s) ratio = z * A * C.subs(n, n + 1) / B / C ratio = simplify(ratio) # If there is a nonnegative root in the denominator of the ratio, # this indicates that the term y(n_root) is zero, and one should # start the product with the term y(n_root + 1). n0 = 0 for n_root in roots(ratio.as_numer_denom()[1], n).keys(): if (n0 < (n_root + 1)) is True: n0 = n_root + 1 K = product(ratio, (n, n0, n - 1)) if K.has(factorial, FallingFactorial, RisingFactorial): K = simplify(K) if casoratian(kernel + [K], n, zero=False) != 0: kernel.append(K) kernel.sort(key=default_sort_key) sk = list(zip(numbered_symbols('C'), kernel)) if sk: for C, ker in sk: result += C * ker else: return None if hints.get('symbols', False): symbols |= set([s for s, k in sk]) return (result, list(symbols)) else: return result
def _simplify(expr): if dom.is_Composite: return factor(expr) else: return simplify(expr)
def equivalence_hypergeometric(A, B, func): # This method for finding the equivalence is only for 2F1 type. # We can extend it for 1F1 and 0F1 type also. x = func.args[0] # making given equation in normal form I1 = factor(cancel(A.diff(x) / 2 + A**2 / 4 - B)) # computing shifted invariant(J1) of the equation J1 = factor(cancel(x**2 * I1 + S(1) / 4)) num, dem = J1.as_numer_denom() num = powdenest(expand(num)) dem = powdenest(expand(dem)) # this function will compute the different powers of variable(x) in J1. # then it will help in finding value of k. k is power of x such that we can express # J1 = x**k * J0(x**k) then all the powers in J0 become integers. def _power_counting(num): _pow = {0} for val in num: if val.has(x): if isinstance(val, Pow) and val.as_base_exp()[0] == x: _pow.add(val.as_base_exp()[1]) elif val == x: _pow.add(val.as_base_exp()[1]) else: _pow.update(_power_counting(val.args)) return _pow pow_num = _power_counting((num, )) pow_dem = _power_counting((dem, )) pow_dem.update(pow_num) _pow = pow_dem k = gcd(_pow) # computing I0 of the given equation I0 = powdenest(simplify(factor(((J1 / k**2) - S(1) / 4) / ((x**k)**2))), force=True) I0 = factor(cancel(powdenest(I0.subs(x, x**(S(1) / k)), force=True))) num, dem = I0.as_numer_denom() max_num_pow = max(_power_counting((num, ))) dem_args = dem.args sing_point = [] dem_pow = [] # calculating singular point of I0. for arg in dem_args: if arg.has(x): if isinstance(arg, Pow): # (x-a)**n dem_pow.append(arg.as_base_exp()[1]) sing_point.append( list(roots(arg.as_base_exp()[0], x).keys())[0]) else: # (x-a) type dem_pow.append(arg.as_base_exp()[1]) sing_point.append(list(roots(arg, x).keys())[0]) dem_pow.sort() # checking if equivalence is exists or not. if equivalence(max_num_pow, dem_pow) == "2F1": return {'I0': I0, 'k': k, 'sing_point': sing_point, 'type': "2F1"} else: return None
def match_2nd_2F1_hypergeometric(I, k, sing_point, func): x = func.args[0] a = Wild("a") b = Wild("b") c = Wild("c") t = Wild("t") s = Wild("s") r = Wild("r") alpha = Wild("alpha") beta = Wild("beta") gamma = Wild("gamma") delta = Wild("delta") # I0 of the standerd 2F1 equation. I0 = ((a - b + 1) * (a - b - 1) * x**2 + 2 * ((1 - a - b) * c + 2 * a * b) * x + c * (c - 2)) / (4 * x**2 * (x - 1)**2) if sing_point != [0, 1]: # If singular point is [0, 1] then we have standerd equation. eqs = [] sing_eqs = [ -beta / alpha, -delta / gamma, (delta - beta) / (alpha - gamma) ] # making equations for the finding the mobius transformation for i in range(3): if i < len(sing_point): eqs.append(Eq(sing_eqs[i], sing_point[i])) else: eqs.append(Eq(1 / sing_eqs[i], 0)) # solving above equations for the mobius transformation _beta = -alpha * sing_point[0] _delta = -gamma * sing_point[1] _gamma = alpha if len(sing_point) == 3: _gamma = (_beta + sing_point[2] * alpha) / (sing_point[2] - sing_point[1]) mob = (alpha * x + beta) / (gamma * x + delta) mob = mob.subs(beta, _beta) mob = mob.subs(delta, _delta) mob = mob.subs(gamma, _gamma) mob = cancel(mob) t = (beta - delta * x) / (gamma * x - alpha) t = cancel(((t.subs(beta, _beta)).subs(delta, _delta)).subs(gamma, _gamma)) else: mob = x t = x # applying mobius transformation in I to make it into I0. I = I.subs(x, t) I = I * (t.diff(x))**2 I = factor(I) dict_I = {x**2: 0, x: 0, 1: 0} I0_num, I0_dem = I0.as_numer_denom() # collecting coeff of (x**2, x), of the standerd equation. # substituting (a-b) = s, (a+b) = r dict_I0 = { x**2: s**2 - 1, x: (2 * (1 - r) * c + (r + s) * (r - s)), 1: c * (c - 2) } # collecting coeff of (x**2, x) from I0 of the given equation. dict_I.update( collect(expand(cancel(I * I0_dem)), [x**2, x], evaluate=False)) eqs = [] # We are comparing the coeff of powers of different x, for finding the values of # parameters of standerd equation. for key in [x**2, x, 1]: eqs.append(Eq(dict_I[key], dict_I0[key])) # We can have many possible roots for the equation. # I am selecting the root on the basis that when we have # standard equation eq = x*(x-1)*f(x).diff(x, 2) + ((a+b+1)*x-c)*f(x).diff(x) + a*b*f(x) # then root should be a, b, c. _c = 1 - factor(sqrt(1 + eqs[2].lhs)) if not _c.has(Symbol): _c = min(list(roots(eqs[2], c))) _s = factor(sqrt(eqs[0].lhs + 1)) _r = _c - factor(sqrt(_c**2 + _s**2 + eqs[1].lhs - 2 * _c)) _a = (_r + _s) / 2 _b = (_r - _s) / 2 rn = { 'a': simplify(_a), 'b': simplify(_b), 'c': simplify(_c), 'k': k, 'mobius': mob, 'type': "2F1" } return rn
def solve_linear_system(system, *symbols, **flags): """Solve system of N linear equations with M variables, which means both Cramer and over defined systems are supported. The possible number of solutions is zero, one or infinite. Respectively this procedure will return None or dictionary with solutions. In the case of over-defined systems all arbitrary parameters are skipped. This may cause situation in which an empty dictionary is returned. In this case it means all symbols can be assigned arbitrary values. Input to this functions is a Nx(M+1) matrix, which means it has to be in augmented form. If you prefer to enter N equations and M unknowns then use 'solve(Neqs, *Msymbols)' instead. Note: a local copy of the matrix is made by this routine so the matrix that is passed will not be modified. The algorithm used here is fraction-free Gaussian elimination, which results, after elimination, in an upper-triangular matrix. Then solutions are found using back-substitution. This approach is more efficient and compact than the Gauss-Jordan method. >>> from sympy import Matrix, solve_linear_system >>> from sympy.abc import x, y Solve the following system: x + 4 y == 2 -2 x + y == 14 >>> system = Matrix(( (1, 4, 2), (-2, 1, 14))) >>> solve_linear_system(system, x, y) {x: -6, y: 2} """ matrix = system[:,:] syms = list(symbols) i, m = 0, matrix.cols-1 # don't count augmentation while i < matrix.rows: if i == m: # an overdetermined system if any(matrix[i:,m]): return None # no solutions else: # remove trailing rows matrix = matrix[:i,:] break if not matrix[i, i]: # there is no pivot in current column # so try to find one in other columns for k in xrange(i+1, m): if matrix[i, k]: break else: if matrix[i, m]: return None # no solutions else: # zero row or was a linear combination of # other rows so now we can safely skip it matrix.row_del(i) continue # we want to change the order of colums so # the order of variables must also change syms[i], syms[k] = syms[k], syms[i] matrix.col_swap(i, k) pivot_inv = S.One / matrix [i, i] # divide all elements in the current row by the pivot matrix.row(i, lambda x, _: x * pivot_inv) for k in xrange(i+1, matrix.rows): if matrix[k, i]: coeff = matrix[k, i] # subtract from the current row the row containing # pivot and multiplied by extracted coefficient matrix.row(k, lambda x, j: simplify(x - matrix[i, j]*coeff)) i += 1 # if there weren't any problems, augmented matrix is now # in row-echelon form so we can check how many solutions # there are and extract them using back substitution simplified = flags.get('simplified', True) if len(syms) == matrix.rows: # this system is Cramer equivalent so there is # exactly one solution to this system of equations k, solutions = i-1, {} while k >= 0: content = matrix[k, m] # run back-substitution for variables for j in xrange(k+1, m): content -= matrix[k, j]*solutions[syms[j]] if simplified: solutions[syms[k]] = simplify(content) else: solutions[syms[k]] = content k -= 1 return solutions elif len(syms) > matrix.rows: # this system will have infinite number of solutions # dependent on exactly len(syms) - i parameters k, solutions = i-1, {} while k >= 0: content = matrix[k, m] # run back-substitution for variables for j in xrange(k+1, i): content -= matrix[k, j]*solutions[syms[j]] # run back-substitution for parameters for j in xrange(i, m): content -= matrix[k, j]*syms[j] if simplified: solutions[syms[k]] = simplify(content) else: solutions[syms[k]] = content k -= 1 return solutions else: return None # no solutions
def singularities(expression, symbol): """ Find singularities of a given function. Parameters ========== expression : Expr The target function in which singularities need to be found. symbol : Symbol The symbol over the values of which the singularity in expression in being searched for. Returns ======= Set A set of values for ``symbol`` for which ``expression`` has a singularity. An ``EmptySet`` is returned if ``expression`` has no singularities for any given value of ``Symbol``. Raises ====== NotImplementedError The algorithm to find singularities for irrational functions has not been implemented yet. Notes ===== This function does not find non-isolated singularities nor does it find branch points of the expression. Currently supported functions are: - univariate rational (real or complex) functions References ========== .. [1] https://en.wikipedia.org/wiki/Mathematical_singularity Examples ======== >>> from sympy.calculus.singularities import singularities >>> from sympy import Symbol >>> x = Symbol('x', real=True) >>> y = Symbol('y', real=False) >>> singularities(x**2 + x + 1, x) EmptySet() >>> singularities(1/(x + 1), x) {-1} >>> singularities(1/(y**2 + 1), y) {-I, I} >>> singularities(1/(y**3 + 1), y) {-1, 1/2 - sqrt(3)*I/2, 1/2 + sqrt(3)*I/2} """ if not expression.is_rational_function(symbol): raise NotImplementedError( "Algorithms finding singularities for non-rational" " functions are not yet implemented.") else: domain = S.Reals if symbol.is_real else S.Complexes return solveset(simplify(1 / expression), symbol, domain)
def _solve(f, *symbols, **flags): """ Return a checked solution for f in terms of one or more of the symbols.""" if not iterable(f): if len(symbols) != 1: soln = None free = f.free_symbols ex = free - set(symbols) if len(ex) == 1: ex = ex.pop() try: # may come back as dict or list (if non-linear) soln = solve_undetermined_coeffs(f, symbols, ex) except NotImplementedError: pass if not soln is None: return soln # find first successful solution failed = [] for s in symbols: n, d = solve_linear(f, x=[s]) if n.is_Symbol: soln = {n: cancel(d)} return soln failed.append(s) for s in failed: try: soln = _solve(f, s, **flags) return soln except NotImplementedError: pass else: msg = "No algorithms are implemented to solve equation %s" raise NotImplementedError(msg % f) symbol = symbols[0] # first see if it really depends on symbol and whether there # is a linear solution f_num, sol = solve_linear(f, x=symbols) if not symbol in f_num.free_symbols: return [] elif f_num.is_Symbol: return [cancel(sol)] strategy = guess_solve_strategy(f, symbol) result = False # no solution was obtained if strategy == GS_POLY: poly = f.as_poly(symbol) if poly is None: msg = "Cannot solve equation %s for %s" % (f, symbol) else: # for cubics and quartics, if the flag wasn't set, DON'T do it # by default since the results are quite long. Perhaps one could # base this decision on a certain critical length of the roots. if poly.degree() > 2: flags['simplified'] = flags.get('simplified', False) result = roots(poly, cubics=True, quartics=True).keys() elif strategy == GS_RATIONAL: P, _ = f.as_numer_denom() dens = denoms(f, x=symbols) try: soln = _solve(P, symbol, **flags) except NotImplementedError: msg = "Cannot solve equation %s for %s" % (P, symbol) result = [] else: if dens: # reject any result that makes any denom. affirmatively 0; # if in doubt, keep it result = [s for s in soln if all(not checksol(den, {symbol: s}) for den in dens)] else: result = soln elif strategy == GS_POLY_CV_1: args = list(f.args) if isinstance(f, Pow): result = _solve(args[0], symbol, **flags) elif isinstance(f, Add): # we must search for a suitable change of variables # collect exponents exponents_denom = list() for arg in args: if isinstance(arg, Pow): exponents_denom.append(arg.exp.q) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): exponents_denom.append(mul_arg.exp.q) assert len(exponents_denom) > 0 if len(exponents_denom) == 1: m = exponents_denom[0] else: # get the LCM of the denominators m = reduce(ilcm, exponents_denom) # x -> y**m. # we assume positive for simplification purposes t = Dummy('t', positive=True) f_ = f.subs(symbol, t**m) if guess_solve_strategy(f_, t) != GS_POLY: msg = "Could not convert to a polynomial equation: %s" % f_ result = [] else: soln = [s**m for s in _solve(f_, t)] # we might have introduced solutions from another branch # when changing variables; check and keep solutions # unless they definitely aren't a solution result = [s for s in soln if checksol(f, {symbol: s}) is not False] elif isinstance(f, Mul): result = [] for m in f.args: result.extend(_solve(m, symbol, **flags) or []) elif strategy == GS_POLY_CV_2: m = 0 args = list(f.args) if isinstance(f, Add): for arg in args: if isinstance(arg, Pow): m = min(m, arg.exp) elif isinstance(arg, Mul): for mul_arg in arg.args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) elif isinstance(f, Mul): for mul_arg in args: if isinstance(mul_arg, Pow): m = min(m, mul_arg.exp) if m and m != 1: f_ = simplify(f*symbol**(-m)) try: sols = _solve(f_, symbol) except NotImplementedError: msg = 'Could not solve %s for %s' % (f_, symbol) else: # we might have introduced unwanted solutions # when multiplying by x**-m; check and keep solutions # unless they definitely aren't a solution if sols: result = [s for s in sols if checksol(f, {symbol: s}) is not False] else: msg = 'CV_2 calculated %d but it should have been other than 0 or 1' % m elif strategy == GS_PIECEWISE: result = set() for expr, cond in f.args: candidates = _solve(expr, *symbols) if isinstance(cond, bool) or cond.is_Number: if not cond: continue # Only include solutions that do not match the condition # of any of the other pieces. for candidate in candidates: matches_other_piece = False for other_expr, other_cond in f.args: if isinstance(other_cond, bool) \ or other_cond.is_Number: continue if bool(other_cond.subs(symbol, candidate)): matches_other_piece = True break if not matches_other_piece: result.add(candidate) else: for candidate in candidates: if bool(cond.subs(symbol, candidate)): result.add(candidate) result = list(result) elif strategy == -1: raise ValueError('Could not parse expression %s' % f) # this is the fallback for not getting any other solution if result is False or strategy == GS_TRANSCENDENTAL: soln = tsolve(f_num, symbol) dens = denoms(f, x=symbols) if not dens: result = soln else: # reject any result that makes any denom. affirmatively 0; # if in doubt, keep it result = [s for s in soln if all(not checksol(den, {symbol: s}) for den in dens)] if result is False: raise NotImplementedError(msg + "\nNo algorithms are implemented to solve equation %s" % f) if flags.get('simplified', True) and strategy != GS_RATIONAL: result = map(simplify, result) return result else: if not f: return [] else: polys = [] for g in f: poly = g.as_poly(*symbols, **{'extension': True}) if poly is not None: polys.append(poly) else: raise NotImplementedError() if all(p.is_linear for p in polys): n, m = len(f), len(symbols) matrix = zeros((n, m + 1)) for i, poly in enumerate(polys): for monom, coeff in poly.terms(): try: j = list(monom).index(1) matrix[i, j] = coeff except ValueError: matrix[i, m] = -coeff # a dictionary of symbols: values or None result = solve_linear_system(matrix, *symbols, **flags) return result else: # a list of tuples, T, where T[i] [j] corresponds to the ith solution for symbols[j] result = solve_poly_system(polys) return result
def checksol(f, symbol, sol=None, **flags): """Checks whether sol is a solution of equation f == 0. Input can be either a single symbol and corresponding value or a dictionary of symbols and values. Examples: --------- >>> from sympy import symbols >>> from sympy.solvers import checksol >>> x, y = symbols('x,y') >>> checksol(x**4-1, x, 1) True >>> checksol(x**4-1, x, 0) False >>> checksol(x**2 + y**2 - 5**2, {x:3, y: 4}) True None is returned if checksol() could not conclude. flags: 'numerical=True (default)' do a fast numerical check if f has only one symbol. 'minimal=True (default is False)' a very fast, minimal testing. 'warning=True (default is False)' print a warning if checksol() could not conclude. 'simplified=True (default)' solution should be simplified before substituting into function and function should be simplified after making substitution. 'force=True (default is False)' make positive all symbols without assumptions regarding sign. """ if sol is not None: sol = {symbol: sol} elif isinstance(symbol, dict): sol = symbol else: msg = 'Expecting sym, val or {sym: val}, None but got %s, %s' raise ValueError(msg % (symbol, sol)) if hasattr(f, '__iter__') and hasattr(f, '__len__'): if not f: raise ValueError('no functions to check') rv = set() for fi in f: check = checksol(fi, sol, **flags) if check is False: return False rv.add(check) if None in rv: # rv might contain True and/or None return None assert len(rv) == 1 # True return True if isinstance(f, Poly): f = f.as_expr() elif isinstance(f, Equality): f = f.lhs - f.rhs if not f: return True if not f.has(*sol.keys()): return False attempt = -1 numerical = flags.get('numerical', True) while 1: attempt += 1 if attempt == 0: val = f.subs(sol) elif attempt == 1: if not val.atoms(Symbol) and numerical: # val is a constant, so a fast numerical test may suffice if val not in [S.Infinity, S.NegativeInfinity]: # issue 2088 shows that +/-oo chops to 0 val = val.evalf(36).n(30, chop=True) elif attempt == 2: if flags.get('minimal', False): return # the flag 'simplified=False' is used in solve to avoid # simplifying the solution. So if it is set to False there # the simplification will not be attempted here, either. But # if the simplification is done here then the flag should be # set to False so it isn't done again there. if flags.get('simplified', True): for k in sol: sol[k] = simplify(sympify(sol[k])) flags['simplified'] = False val = simplify(f.subs(sol)) if flags.get('force', False): val = posify(val)[0] elif attempt == 3: val = powsimp(val) elif attempt == 4: val = cancel(val) elif attempt == 5: val = val.expand() elif attempt == 6: val = together(val) elif attempt == 7: val = powsimp(val) else: break if val.is_zero: return True elif attempt > 0 and numerical and val.is_nonzero: return False if flags.get('warning', False): print("Warning: could not verify solution %s." % sol)