Mathematical types
------------------
While the concept of type doesn't usually appear explicitly in
elementary presentations of mathematics, it's very important for
mathematical intuition, allowing for instance to recognize immediately
that it's absurd to add a scalar and a vector.
So, every sympy object should have a 'type' attribute (NB: this is
independent from the class of the object returned by 'type(obj)'). These
types should be sympy objects themselves, forming a hierarchy - a
directed acyclic graph - similar to a class hierarchy, including
equivalents of isinstance and issubclass. Also, each type defines a
Python-level interface (implemented as a mixin or abstract base class)
which objects belonging to that type must implement (e.g. by subclassing
the interface). This allows the decoupling of the mathematical
properties from the implementation and, provided that algorithms use
only the interface instead of details of the actual class, should allow
different implementations of the same type to coexist without troubles.
Now, what prevents these types from simply being implemented as ABCs is
that we need parametric types (like Vector(Real, 3)) and that we want
operation objects like Add to have different types depending on their
arguments so that Int + Real -> Real, Ket + Ket -> Ket and Ket + Real ->
TypeError(?). Implementing these objects with covariant type is, of
course, is the tricky bit, but it would allow to extend sympy without
having to reimplement half the core's classes.
Another benefit of this is that we could implement a Variable class so
that implementing a type would automatically allow us to create symbolic
instantiations of it. Which brings me to my second point...
Symbols vs variables
--------------------
Currently in sympy, there is no distinction between the concepts of
symbol and variable. Yet these are different. A symbol is just a sign -
blobs of ink on the page - without any intrinsic meaning. A variable is
basically a placeholder that can be replaced by any value in some range.
The properties of the variable are the properties that are common to all
the elements of the range. Most of what we do in sympy should use
variables, symbols should only matter for parsing and printing.
Symbol() objects are actually almost variables already. Besides the
name, they only lack a more explicit range and shouldn't require a name.
Intermediate representation for printing
----------------------------------------
At the moment, we have many different printers (str, repr, pretty,
latex, mathml, ...) and printing logic specific to an object needs to be
implemented for each of them even though we want the results to look
similar between most printers. It would be much easier if we could
specify once how the object should be printed (e.g "put the contents of
self.label between a vertical bar and an angled bracket") and let sympy
take care of outputting actual LaTeX/MathML/pretty-printer stuff/...
So, the idea would be to have a system of objects giving a high-level
description of printed mathematical expressions - this system should
probably look like a straightforward translation of Content MathML into
an object hierarchy. Objects should have a single method returning their
representation in this system while the Printers have all the logic
needed to create the print_* output. This would replace the _latex(),
_mathml() methods as well as all the _print_Stuff() mess in printer
classes and turn this (N object classes) * (M printers) problem into an
(N+M) one.
I don't know whether this representation should use sympy objects (=
instances of Basic): avoiding the dependence on the rest of sympy could
allow as a long-term goal to get other projects (matplotlib,
ipython, ...) to use it, so that it becomes possible to exchange
mathematical expressions with them without serialising to LaTeX or
MathML.
I don't know whether any of this can realistically be implemented any
time soon but I think these are important medium-term goals that would
make sympy more of a generic framework.
> Considering the merge of the quantum branch and some of the recent
> discussions, I have a few ideas to make sympy more modular and to make
> it easier to extend it.
Yes, these issues came up for us a lot in the writing of the quantum
stuff. For examples, we currently make the implicit (and possibly
false) assumption that commutative=False means that something is an
Operator. But, of course, you can easily have non-commutative things
other than Operators.
> Mathematical types
> ------------------
>
> While the concept of type doesn't usually appear explicitly in
> elementary presentations of mathematics, it's very important for
> mathematical intuition, allowing for instance to recognize immediately
> that it's absurd to add a scalar and a vector.
Yes, absolutely. Having a proper mathematical type system would allow
us to easily do validation of various operations. We definitely need
this in quantum, where, for example, we can do lots of mathematically
insane things like 2 + Ket('psi').
> So, every sympy object should have a 'type' attribute (NB: this is
> independent from the class of the object returned by 'type(obj)'). These
> types should be sympy objects themselves, forming a hierarchy - a
> directed acyclic graph - similar to a class hierarchy, including
> equivalents of isinstance and issubclass. Also, each type defines a
> Python-level interface (implemented as a mixin or abstract base class)
> which objects belonging to that type must implement (e.g. by subclassing
> the interface). This allows the decoupling of the mathematical
> properties from the implementation and, provided that algorithms use
> only the interface instead of details of the actual class, should allow
> different implementations of the same type to coexist without troubles.
I think the basics of this idea are great. Some questions though...
* For mathematical objects, it seems like there are two broad types of
things: spaces and things that live in those spaces (elements). There
are vectors and vector spaces, Kets and hilbert spaces, the ring of
integers, etc. Do we also need to think about objects that encode the
spaces as well as the elements they contain?
* Coercion. Sage has thought through this type of thing in
considerable depth and one conecpt they have is coercion. I don't
quite understand the difference (in Sage) between casting and
coercion, but the general idea is what do you do when you attempt an
operation between two different types - how do you coerse one type
into another so that the operation can happen.
* Are the interfaces you envision on the type objects or the objects
that have the type attributes? Does that make sense?
> Now, what prevents these types from simply being implemented as ABCs is
> that we need parametric types (like Vector(Real, 3)) and that we want
> operation objects like Add to have different types depending on their
> arguments so that Int + Real -> Real, Ket + Ket -> Ket and Ket + Real ->
> TypeError(?). Implementing these objects with covariant type is, of
> course, is the tricky bit, but it would allow to extend sympy without
> having to reimplement half the core's classes.
I think the extensibilty of sympy is super important - it is one of
the most attractive parts of it. The quantum stuff we did would have
been very difficult in many other environments, but having a proper
type system would make it even easier and more robust.
> Another benefit of this is that we could implement a Variable class so
> that implementing a type would automatically allow us to create symbolic
> instantiations of it. Which brings me to my second point...
>
> Symbols vs variables
> --------------------
>
> Currently in sympy, there is no distinction between the concepts of
> symbol and variable. Yet these are different. A symbol is just a sign -
> blobs of ink on the page - without any intrinsic meaning. A variable is
> basically a placeholder that can be replaced by any value in some range.
> The properties of the variable are the properties that are common to all
> the elements of the range. Most of what we do in sympy should use
> variables, symbols should only matter for parsing and printing.
So are you thinking that a variable is a symbol that has a definite type?
> Symbol() objects are actually almost variables already. Besides the
> name, they only lack a more explicit range and shouldn't require a name.
>
> Intermediate representation for printing
> ----------------------------------------
>
> At the moment, we have many different printers (str, repr, pretty,
> latex, mathml, ...) and printing logic specific to an object needs to be
> implemented for each of them even though we want the results to look
> similar between most printers. It would be much easier if we could
> specify once how the object should be printed (e.g "put the contents of
> self.label between a vertical bar and an angled bracket") and let sympy
> take care of outputting actual LaTeX/MathML/pretty-printer stuff/...
The only issue with this is that in many cases the abstract notion of
how something is printed is very dependent on the features of the
printer. For example, in the quantum stuff for printers that have
subscripts (pretty, latex, etc.) we do one thing, but for printers
with no good looking subscripts, something completely different. Thus
is may be difficult to abstract this in a clean way.
> So, the idea would be to have a system of objects giving a high-level
> description of printed mathematical expressions - this system should
> probably look like a straightforward translation of Content MathML into
> an object hierarchy. Objects should have a single method returning their
> representation in this system while the Printers have all the logic
> needed to create the print_* output. This would replace the _latex(),
> _mathml() methods as well as all the _print_Stuff() mess in printer
> classes and turn this (N object classes) * (M printers) problem into an
> (N+M) one.
Also, our notion of printers is much more general than mathematica
notation. Some of our printers output code, which is *very* different
from mathml or latex as a base.
> I don't know whether this representation should use sympy objects (=
> instances of Basic): avoiding the dependence on the rest of sympy could
> allow as a long-term goal to get other projects (matplotlib,
> ipython, ...) to use it, so that it becomes possible to exchange
> mathematical expressions with them without serialising to LaTeX or
> MathML.
>
>
>
> I don't know whether any of this can realistically be implemented any
> time soon but I think these are important medium-term goals that would
> make sympy more of a generic framework.
I think the first two of these proposals are quite interesting. The
third, possibly, but it is not clear how it would all work.
Cheers,
Brian
>
> --
> You received this message because you are subscribed to the Google Groups "sympy" group.
> To post to this group, send email to sy...@googlegroups.com.
> To unsubscribe from this group, send email to sympy+un...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/sympy?hl=en.
>
>
--
Brian E. Granger, Ph.D.
Assistant Professor of Physics
Cal Poly State University, San Luis Obispo
bgra...@calpoly.edu
elli...@gmail.com
I think having objects for all the sets (or sets with structures) is another common thing that other CASs do. Again, we should look at how the better, more mature ones out there do it.
Also, this makes me realize that this is related to assumptions. How would you recommend having the assumptions with respect to this system?
> * Coercion. Sage has thought through this type of thing in
> considerable depth and one conecpt they have is coercion. I don't
> quite understand the difference (in Sage) between casting and
> coercion, but the general idea is what do you do when you attempt an
> operation between two different types - how do you coerse one type
> into another so that the operation can happen.
Isn't coercion implicit while casting is explicit? At least that's what the names imply to me. Anyway, if it is, we might remember that "explicit is better than implicit."
> * Are the interfaces you envision on the type objects or the objects
> that have the type attributes? Does that make sense?
>
>> Now, what prevents these types from simply being implemented as ABCs is
>> that we need parametric types (like Vector(Real, 3)) and that we want
>> operation objects like Add to have different types depending on their
>> arguments so that Int + Real -> Real, Ket + Ket -> Ket and Ket + Real ->
>> TypeError(?). Implementing these objects with covariant type is, of
>> course, is the tricky bit, but it would allow to extend sympy without
>> having to reimplement half the core's classes.
>
> I think the extensibilty of sympy is super important - it is one of
> the most attractive parts of it. The quantum stuff we did would have
> been very difficult in many other environments, but having a proper
> type system would make it even easier and more robust.
>
>> Another benefit of this is that we could implement a Variable class so
>> that implementing a type would automatically allow us to create symbolic
>> instantiations of it. Which brings me to my second point…
This all sounds good. We should look at what other, more mature computer algebra systems do with this.
It sounds to me like it might require a pretty significant rewrite of the core. I think that if we ever do decide to make such a change, that we should try to make the core as modular as possible, so that we can avoid future rewrites.
>>
>> Symbols vs variables
>> ——————————
>>
>> Currently in sympy, there is no distinction between the concepts of
>> symbol and variable. Yet these are different. A symbol is just a sign -
>> blobs of ink on the page - without any intrinsic meaning. A variable is
>> basically a placeholder that can be replaced by any value in some range.
>> The properties of the variable are the properties that are common to all
>> the elements of the range. Most of what we do in sympy should use
>> variables, symbols should only matter for parsing and printing.
>
> So are you thinking that a variable is a symbol that has a definite type?
>
>> Symbol() objects are actually almost variables already. Besides the
>> name, they only lack a more explicit range and shouldn't require a name.
I understand mathematically the difference between the two, but I don't see from your explanation how you think they should be different in SymPy.
Anyway, the name "symbol" is pretty engrained in SymPy by now, so if you are suggesting a name change, I don't think that would be possible.
>>
>> Intermediate representation for printing
>> ----------------------------------------
>>
>> At the moment, we have many different printers (str, repr, pretty,
>> latex, mathml, ...) and printing logic specific to an object needs to be
>> implemented for each of them even though we want the results to look
>> similar between most printers. It would be much easier if we could
>> specify once how the object should be printed (e.g "put the contents of
>> self.label between a vertical bar and an angled bracket") and let sympy
>> take care of outputting actual LaTeX/MathML/pretty-printer stuff/...
>
> The only issue with this is that in many cases the abstract notion of
> how something is printed is very dependent on the features of the
> printer. For example, in the quantum stuff for printers that have
> subscripts (pretty, latex, etc.) we do one thing, but for printers
> with no good looking subscripts, something completely different. Thus
> is may be difficult to abstract this in a clean way.
Brian makes a good point. Why do we need what would essentially be a separate implementation of the core to do printing. The representation for an object can just be the object itself. This wouldn't, as you say, make it very easy to adapt to other systems, but I think it would be the easiest to work with from within SymPy itself.
>
>> So, the idea would be to have a system of objects giving a high-level
>> description of printed mathematical expressions - this system should
>> probably look like a straightforward translation of Content MathML into
>> an object hierarchy. Objects should have a single method returning their
>> representation in this system while the Printers have all the logic
>> needed to create the print_* output. This would replace the _latex(),
>> _mathml() methods as well as all the _print_Stuff() mess in printer
>> classes and turn this (N object classes) * (M printers) problem into an
>> (N+M) one.
>
> Also, our notion of printers is much more general than mathematica
> notation. Some of our printers output code, which is *very* different
> from mathml or latex as a base.
This is another thing. For an arbitrary printer, you might need any amount of information contained in the original object. Or maybe you will say that the code printer shouldn't really be implemented as a printer.
Aaron Meurer
Yes, objects representing spaces are important, but this kind of things
is still underdeveloped in sympy, so I haven't thought about it much.
Quantum Hilbert spaces are brand new and though polynomials have Domain
objects, they're mostly meant for internal use.
> I think having objects for all the sets (or sets with structures) is
> another common thing that other CASs do. Again, we should look at how
> the better, more mature ones out there do it.
Yes, sets and structures are important as well. We need more of them,
independently of this proposal.
> Also, this makes me realize that this is related to assumptions. How
> would you recommend having the assumptions with respect to this
> system?
Assumptions need to be aware of types so that checking that something
with integer type is an integer quickly resolves to True, but I don't
think major changes are required.
> > * Coercion. Sage has thought through this type of thing in
> > considerable depth and one conecpt they have is coercion. I don't
> > quite understand the difference (in Sage) between casting and
> > coercion, but the general idea is what do you do when you attempt an
> > operation between two different types - how do you coerse one type
> > into another so that the operation can happen.
I've looked into Sage's coercion model (starting from William Stein's
blog post:
http://sagemath.blogspot.com/2010/11/brief-history-and-motivation-behind.html) and while the Types I'm defining here and Sage's Parents are quite similar, there's a major difference in that Sage conflates types and algebraic structures. Therefore, in Sage ZZ(1) and QQ(1) aren't the same object, but in sympy there's a single Integer(1) object which can be considered as an element of the ring of integers or of the field of rationals. Also, we don't need to coerce between different types to allow the operation to happen but we do need to assign the correct type to the result (and possibly to take some action based on the type).
> Isn't coercion implicit while casting is explicit? At least that's
> what the names imply to me. Anyway, if it is, we might remember that
> "explicit is better than implicit."
Yes, that's one more reason to avoid coercion as much as possible. It
should mostly be limited to Python builtins and interconversion between
different implementations of the same type.
>
> > * Are the interfaces you envision on the type objects or the objects
> > that have the type attributes? Does that make sense?
I'm not sure I understand your question. Concretely, we should have:
>>> n = Integer(12)
>>> n.type
IntegerType
>>> IntegerType.interface
IntegerInterface
>>> isinstance(n, IntegerInterface)
True
>>> n.type in RationalType # or n.type.issubtype(RationalType)
True
>>> isinstance(n , RationalInterface)
True
where IntegerType and RationalType are instances of Basic, and
IntegerInterface and RationalInterface are mixin classes and possibly
ABCs.
> >> Now, what prevents these types from simply being implemented as ABCs is
> >> that we need parametric types (like Vector(Real, 3)) and that we want
> >> operation objects like Add to have different types depending on their
> >> arguments so that Int + Real -> Real, Ket + Ket -> Ket and Ket + Real ->
> >> TypeError(?). Implementing these objects with covariant type is, of
> >> course, is the tricky bit, but it would allow to extend sympy without
> >> having to reimplement half the core's classes.
> >
> > I think the extensibilty of sympy is super important - it is one of
> > the most attractive parts of it. The quantum stuff we did would have
> > been very difficult in many other environments, but having a proper
> > type system would make it even easier and more robust.
> >
> >> Another benefit of this is that we could implement a Variable class so
> >> that implementing a type would automatically allow us to create symbolic
> >> instantiations of it. Which brings me to my second point…
>
> This all sounds good. We should look at what other, more mature
> computer algebra systems do with this.
>
> It sounds to me like it might require a pretty significant rewrite of
> the core. I think that if we ever do decide to make such a change,
> that we should try to make the core as modular as possible, so that we
> can avoid future rewrites.
Yes, but I think it can be done incrementally. And the objective is
certainly to allow more things to be done without changing a single line
in the core.
>
> >>
> >> Symbols vs variables
> >> ——————————
>
> >>
> >> Currently in sympy, there is no distinction between the concepts of
> >> symbol and variable. Yet these are different. A symbol is just a sign -
> >> blobs of ink on the page - without any intrinsic meaning. A variable is
> >> basically a placeholder that can be replaced by any value in some range.
> >> The properties of the variable are the properties that are common to all
> >> the elements of the range. Most of what we do in sympy should use
> >> variables, symbols should only matter for parsing and printing.
> >
> > So are you thinking that a variable is a symbol that has a definite type?
>
> >
> >> Symbol() objects are actually almost variables already. Besides the
> >> name, they only lack a more explicit range and shouldn't require a name.
>
> I understand mathematically the difference between the two, but I don't
> see from your explanation how you think they should be different in
> SymPy.
>
> Anyway, the name "symbol" is pretty engrained in SymPy by now, so if
> you are suggesting a name change, I don't think that would be
> possible.
Actually, Variables would be used much like Symbols (or Dummies) are
now, but they are completely different internally. They must have a
type, but the name is optional. Also, variables have a concept of
instantiation and the instance must belong to the range of the variable.
Using instantiation of variables instead of substitution of symbols
should give better guarantees on the correctness of calculations.
> >>
> >> Intermediate representation for printing
> >> ----------------------------------------
> >>
> >> At the moment, we have many different printers (str, repr, pretty,
> >> latex, mathml, ...) and printing logic specific to an object needs to be
> >> implemented for each of them even though we want the results to look
> >> similar between most printers. It would be much easier if we could
> >> specify once how the object should be printed (e.g "put the contents of
> >> self.label between a vertical bar and an angled bracket") and let sympy
> >> take care of outputting actual LaTeX/MathML/pretty-printer stuff/...
> >
> > The only issue with this is that in many cases the abstract notion of
> > how something is printed is very dependent on the features of the
> > printer. For example, in the quantum stuff for printers that have
> > subscripts (pretty, latex, etc.) we do one thing, but for printers
> > with no good looking subscripts, something completely different. Thus
> > is may be difficult to abstract this in a clean way.
>
> Brian makes a good point. Why do we need what would essentially be a
> separate implementation of the core to do printing. The representation
> for an object can just be the object itself. This wouldn't, as you
> say, make it very easy to adapt to other systems, but I think it would
> be the easiest to work with from within SymPy itself.
It wouldn't be a separate implementation of the core. The things that
need to be implemented are subscripts, superscripts, matching
delimiters, ... But perhaps this is only applicable to pretty-printers
(defined as targets with a 2-D output and a wide range of symbols
available). Anyway, the preferred representation of an object is an
intrinsic property that can't be inferred from the structure of the
expression tree.
Having this information in the object would remove a lot of code
duplication. Compare for instance LatexPrinter._print_Add,
MathMLPrinter._print_Add and PrettyPrinter._print_Add. These are 3
different implementations of the same problem: sorting the arguments in
a sensible order and converting 'x + -1' into 'x - 1'.
Also, it would allow to create symbols named (in LateX) 'x_+' or
'\gamma^5' without dirty hacks.
>
> >
> >> So, the idea would be to have a system of objects giving a high-level
> >> description of printed mathematical expressions - this system should
> >> probably look like a straightforward translation of Content MathML into
> >> an object hierarchy. Objects should have a single method returning their
> >> representation in this system while the Printers have all the logic
> >> needed to create the print_* output. This would replace the _latex(),
> >> _mathml() methods as well as all the _print_Stuff() mess in printer
> >> classes and turn this (N object classes) * (M printers) problem into an
> >> (N+M) one.
> >
> > Also, our notion of printers is much more general than mathematica
> > notation. Some of our printers output code, which is *very* different
> > from mathml or latex as a base.
>
> This is another thing. For an arbitrary printer, you might need any
> amount of information contained in the original object. Or maybe you
> will say that the code printer shouldn't really be implemented as a
> printer.
Indeed, I'm not sure that code generation should use the printer model.
It works for expressions, but there are difficulties when you need to
output statements.
So which way is better in your opinion?
I still don't quite understand what you are saying here. Can you give some kind of example?
OK, that makes sense then.
>
>>
>>>
>>>> So, the idea would be to have a system of objects giving a high-level
>>>> description of printed mathematical expressions - this system should
>>>> probably look like a straightforward translation of Content MathML into
>>>> an object hierarchy. Objects should have a single method returning their
>>>> representation in this system while the Printers have all the logic
>>>> needed to create the print_* output. This would replace the _latex(),
>>>> _mathml() methods as well as all the _print_Stuff() mess in printer
>>>> classes and turn this (N object classes) * (M printers) problem into an
>>>> (N+M) one.
>>>
>>> Also, our notion of printers is much more general than mathematica
>>> notation. Some of our printers output code, which is *very* different
>>> from mathml or latex as a base.
>>
>> This is another thing. For an arbitrary printer, you might need any
>> amount of information contained in the original object. Or maybe you
>> will say that the code printer shouldn't really be implemented as a
>> printer.
>
> Indeed, I'm not sure that code generation should use the printer model.
> It works for expressions, but there are difficulties when you need to
> output statements.
Maybe there should be a separate class (subclassing from Printer) for 2-D output, which uses the model above, and the other "printers" like this one would just be regular Printers.
Aaron Meurer
I agree that we need something like this, but I think it need some
further thought. I have added a note about this to the Roadmap:
https://github.com/sympy/sympy/wiki/Roadmap
Cheers,
Brian