Yeah, I see the advantage in that. That's really cool. Here's another one. I see the Derivative class. But cos already has fdiff and an _eval_derivative from class inheritance. (Not sure what the difference between the two methods is, but anyway) So why is a Derivative class needed? Couldn't you just make it a function, something like:
@sympify_it def derivative(f, with_respect_to): return f.fdiff(with_respect_to) The Derivative class has extra methods like _eval_nseries, but wouldn't those just get subsumed with whatever a derivative function returns? For example, if you call fdiff on cos, it returns -sin, which should have a _eval_nseries method anyhow. What's the point of having a separate Derivative class? -- Jeff Pickhardt (650) 530-0036 [email protected] On Thu, Jun 23, 2011 at 11:55 PM, Aaron Meurer <[email protected]> wrote: > On Fri, Jun 24, 2011 at 12:33 AM, Jeff Pickhardt <[email protected]> > wrote: > > Thanks guys, this is great stuff. You might want to memorialize this on > a > > wiki or something for newcomers to SymPy. > > That's a good idea. > > > > > So a little bit about my background with Python: I'm a pretty experienced > > Python programming, I understand operator overloading, I see your > > @_sympifyit decorators all over the place (I'm guessing they're to > convert 2 > > to Integer(2), as Aaron explained). In fact, I started writing my own > > Python symbolics math library years ago. > > OK. I just wanted to be sure to mention it, since to people who don't > know about operator overloading, it can be the most mysterious part of > the whole thing (how does it "know" to convert x + x into 2*x?) > > > > > One follow-up question is: > > > > Is there an advantage to having your own classes for Pow, Add, Mul, and > > other operators? Why didn't you just absorb those in classes for > > expressions, functions, etc, and have an expression + expression return > an > > expression? > > It makes for much better object oriented programming. You can see all > the methods of Add, Mul, and Pow specifically handle the behavior on > that type. > > Let's use differentiation as an example. diff(expr, x) is implemented > as expr._eval_derivative(x) (technically, this is not 100% true > because there's also fdiff, but let's suppose that it's just done this > way for simplicity). If you do it this way, it's a very simple > recursive algorithm. You define your base cases, and define > > Add > > def _eval_derivative(self, x): > return Add(*[i._eval_derivative(x) for i in self.args]) > > Mul > > def _eval_derivative(self, x): > return Add(*(Mul(*(self.args[:i] + > [self.args[i]._eval_derivative(x)] + self.args[i+1:])))) > > Pow > > def _eval_derivative(self, x): > # I hope I have the rule correct here > return self.exp*self.base._eval_derivative(x)*self.base**(self.exp > - 1) + log(self.base)*self.exp._eval_derivative(x)*self.base**self.exp > > (note, I didn't copy this from the actual SymPy code, so it might be a > little different, but it will be similar). > > This way, the code is all very simple, because each class only needs > to know how to deal with itself. > > This also makes it much more modular. If you want to extend SymPy, > you just create your own class. You can make that class work with > diff() just by defining ._eval_derivative() on that class. With your > method, if someone wants to extend the code, they have to extend the > Expression class (which will be huge, btw). > > Your method goes half way, because it has a separate "Add" class, > PolyTerm, but you are still keeping things like x and x**2 and x*y as > the same class, when the first should be a Symbol, the second a Pow, > and the third a Mul. > > By the way, if you are interested in other ways to implement symbolics > in Python, you might look at the sympycore project, which is a > research project that tries to implement symbolics in the most > efficient way possible (and is often faster than SymPy as a result). > See http://code.google.com/p/sympycore/. > > Aaron Meurer > > > > > Here's an example. Specifically, x^2, instead of being Pow(x, 2), would > be > > something like: > > MonoTerm (self.units = {x: 1}).__pow__(2) becomes MonoTerm, self.units = > {x: > > 2}. > > > > For example, digging up my old code, when I was making a symbolics math > > library, I started doing it this way: > > > > class MonoTerm: > > """A MonoTerm is an instance of terms multiplied together, but > without > > any sums or differences. As an example, 2 could be a MonoTerm, as could > 3 > > z, but 2 x + 3 y must be a PolyTerm. MonoTerms can only be operated on > by > > MonoTerms with the same dimensional units.""" > > def __init__(self, string=''): > > # instance variables > > self.number=0 > > self.units={} > > # initialize to the string, with the become method > > self.become(string) > > > > def become(self, string=''): > > """Handles the parsing of the MonoTerm into number and units.""" > > if string=='': > > # nothing should happen; this is the "empty term" > > return > > if m.search('^\s*('+patterns.number+')', string): > > self.number = float(m[0]) > > else: > > self.number = 1.0 # 'kg' == '1.0 kg' > > unitSearch = > > > re.findall('('+patterns.unit+')\s*(?:(?:\^|\*\*)\s*('+patterns.number+'))?', > > string) > > for unit, exponent in unitSearch: > > if unit == '': > > continue > > if exponent == '': > > self.units[unit]=1.0 > > else: > > self.units[unit]=float(exponent) > > > > def __add__(self, other): > > """Adds two MonoTerms together, provided they have the same > > units.""" > > if self.units != other.units: > > # can only subtract MonoTerms with MonoTerms > > raise Exception('Cannot add two MonoTerms with incompatible > > units. Use PolyTerms instead.') > > try: > > newTerm = MonoTerm() > > newTerm.number = self.number + other.number > > newTerm.units = self.units > > newTerm.prune() > > return newTerm > > except: > > return other+self > > (etc) > > > > class PolyTerm: > > """A PolyTerm is a term made up of the sum of two or more > MonoTerms.""" > > def __init__(self, string=''): > > # instance variables > > self.terms={} > > self.become(string) > > > > def become(self, string=''): > > # assumes the string is well-formed various terms split by + > > # Actually, for now, assume the string is really a MonoTerm... > this > > will be updated later. > > newTerm = MonoTerm(string) > > self.terms[newTerm.getLabel()]=newTerm > > > > def __add__(self, other): > > """Adds two PolyTerms together.""" > > newTerm = copy.deepcopy(self) > > for term in other: > > if term.getLabel() in newTerm.terms: > > newTerm.terms[term.getLabel()] = > > newTerm.terms[term.getLabel()] + term > > else: > > newTerm.terms[term.getLabel()] = copy.deepcopy(term) > > newTerm.prune() > > return newTerm > > (etc) > > > > term1 = PolyTerm('1 x y') > > term2 = PolyTerm('1 y z') > > term3 = PolyTerm('2 x y^2 z') > > term4 = PolyTerm('1 y^2') > > > > print ((term1+term2)**2 - term3) / term4 # ((xy+yz)^2 - 2xy^2z)/y^2 == > x^2 + > > z^2 > > > > > > -- > > Jeff Pickhardt > > (650) 530-0036 > > [email protected] > > > > > > On Thu, Jun 23, 2011 at 10:33 PM, Aaron Meurer <[email protected]> > wrote: > >> > >> Well, you picked a particularly tricky example because it uses both > >> the main core and the polys. > >> > >> So let's start with my_expresion (the core part). You already know > >> that x is defined to be a Symbol object (because you did this > >> yourself). You should also know that sin and cos are Function > >> objects, which you can think of as container objects that hold > >> arguments and which also have various mathematical relations defined > >> on them. > >> > >> As you may know, Python let's you override the behavior of the > >> built-in operations *, +, /, -, etc. on your own objects. So all > >> objects have method __mul__, __add__, etc. defined on them. When you > >> call x + y, this reduces to x.__add__(y). In SymPy, a.__add__(b) is > >> converted to Add(a, b). The same is true for __mul__ and Mul. So in > >> sin(x)**2 + 2*sin(x) + 1, sin(x) creates a sin object (when it is > >> called). This reduces to > >> > >> sin(x).__pow__(2) + sin(x).__rmul__(2) + 1 > >> Pow(x, 2).__add__(Mul(2, sin(x))).__add__(1) > >> Add(Pow(x, 2), Mul(2, sin(x)), 1) > >> > >> which is what you will get if you call srepr(my_expression). Note > >> that 2*sin(x) actually calls __rmul__. That's because 2 (type int) > >> doesn't know how to multiply sin(x) (type sin), so sin(x)'s __rmul__ > >> method is called. This brings up an important point. All Python > >> types are converted in this process to SymPy types, through the > >> sympify() function. So, for example, sin(x).__pow__(2) reduces to > >> sin(x).__pow__(sympify(2)), which results in > >> sin(x).__pow__(Integer(2)). > >> > >> Now, Mul, Pow, and Add's __new__ methods contain logic to do automatic > >> simplifications like x + 2*x => 3*x or x*x => x**2. In this case, no > >> such simplifications needed to be applied. > >> > >> All the args for any SymPy expression are stored in expr.args. So you > >> would have > >> > >> >>> my_expression.args > >> (1, sin(x)**2, 2*sin(x)) > >> >>> my_expression.args[1].args > >> (sin(x), 2) > >> > >> If you want to see the code for all of this, you can look at the files > >> in sympy/core. The __op__ stuff is mostly in basic.py and expr.py. > >> The flattening routines are in add.py, pow.py, and mul.py. The code > >> for functions that sin is built off of is in function.py. > >> > >> Now, to the factoring part. my_expression.factor() is a shortcut to > >> factor(my_expression). Because factoring is a polynomial algorithm, > >> the expression has to be converted to a Poly first. Poly is able to > >> represent polynomials with arbitrary symbolic "generators". In this > >> case, it determines that it should use sin(x) as a generator, so it > >> creates Poly(sin(x)**2 + 2*sin(x) + 1, sin(x)), which you can think of > >> as a wrapper around the polynomial y**2 + 2*y + 1, where y is set to > >> be sin(x) (for the purposes of Poly, it does not matter what the > >> generators are, other than that coefficients cannot contain symbols > >> from them, so you can think of it in this way). > >> > >> Then, it calls the factorization algorithm on Poly. If you are > >> interested in how this works, I suggest you read the code and the > >> papers referenced there. In this case, it is able to factor the > >> polynomial using the squarefree factorization algorithm, which is > >> actually not too difficult to understand. In the general case, it > >> uses a complicated multivariate factorization algorithm that factors > >> any multivariate polynomial into irreducibles. > >> > >> Anyway, Poly.factorlist returns something like [(Poly(sin(x) + 1), > >> 2)]. factor() converts this into a normal SymPy expression (also > >> called a Basic expression or Expr expression) by passing it to Mul and > >> Pow (something like Mul(*[Pow(b, e) for b, e in expr]) would do it, I > >> think). > >> > >> All the Poly stuff lives in sympy/polys. If you are interested, I can > >> explain a little how they work (internal representation, etc.). The > >> code for the Poly class lives in polytools.py, though the actual > >> factorization algorithm lives in sqftools.py and factortools.py. > >> > >> And one thing that I didn't mention (and maybe you didn't even think > >> of) is the printing. You do not use pretty printing, so the printing > >> is rather simple (just recursively print the objects using the proper > >> operators). If you are interested, you can look at the code in > >> sympy/printing. > >> > >> Let me know if this made sense, and if there are bits that you still > >> would like to know about. Also, remember that SymPy is written in a > >> fairly modular way, so it's completely unnecessary to know how a > >> module works unless you want to work on that module specifically > >> (e.g., you don't need to how the core works to write some > >> simplification algorithm, like in simplify.py). > >> > >> Aaron Meurer > >> > >> On Thu, Jun 23, 2011 at 10:42 PM, Jeff Pickhardt <[email protected]> > >> wrote: > >> > Hey guys, > >> > > >> > I'm reading through the SymPy code and, well, it's somewhat > overwhelming > >> > if > >> > you're new to the project because there's so much going on. (That's a > >> > good > >> > thing too - it means its robust!) > >> > > >> > Can someone help explain how this works? > >> > > >> >>>> from sympy import * > >> >>>> x = Symbol("x") > >> >>>> my_expression = sin(x)**2 + 2*sin(x) + 1 > >> >>>> my_expression.factor() > >> > (1 + sin(x))**2 > >> >>>> > >> > > >> > For instance, what data structures happen when I create my_expression, > >> > what > >> > happens when I factor it, etc. A high-level walk through would help. > I > >> > see > >> > there's stuff going on at polytools.py, and I think _symbolic_factor > >> > gets > >> > called. It's just confusing to keep everything in my head when I > don't > >> > yet > >> > have a high level understanding of how sympy expressions and what not > >> > actually work. > >> > > >> > Also, the new quantum mechanics stuff looks really cool. Wish I had > had > >> > that a few years ago! > >> > > >> > Thanks, > >> > Jeff > >> > > >> > -- > >> > Jeff Pickhardt > >> > (650) 530-0036 > >> > [email protected] > >> > > >> > -- > >> > You received this message because you are subscribed to the Google > >> > Groups > >> > "sympy" group. > >> > To post to this group, send email to [email protected]. > >> > To unsubscribe from this group, send email to > >> > [email protected]. > >> > For more options, visit this group at > >> > http://groups.google.com/group/sympy?hl=en. > >> > > >> > >> -- > >> You received this message because you are subscribed to the Google > Groups > >> "sympy" group. > >> To post to this group, send email to [email protected]. > >> To unsubscribe from this group, send email to > >> [email protected]. > >> For more options, visit this group at > >> http://groups.google.com/group/sympy?hl=en. > >> > > > > -- > > You received this message because you are subscribed to the Google Groups > > "sympy" group. > > To post to this group, send email to [email protected]. > > To unsubscribe from this group, send email to > > [email protected]. > > For more options, visit this group at > > http://groups.google.com/group/sympy?hl=en. > > > > -- > You received this message because you are subscribed to the Google Groups > "sympy" group. > To post to this group, send email to [email protected]. > To unsubscribe from this group, send email to > [email protected]. > For more options, visit this group at > http://groups.google.com/group/sympy?hl=en. > > -- You received this message because you are subscribed to the Google Groups "sympy" group. To post to this group, send email to [email protected]. To unsubscribe from this group, send email to [email protected]. For more options, visit this group at http://groups.google.com/group/sympy?hl=en.
