Now we fill in the parts of
KroneckerProduct other than the class
definition line and its docstring.
None of the other built-in Mathics Functions serve as good model to
copy from, so we’ll use a combination
PauliMatrix. The former function is selected because it takes a
Matrix parameter, and PauliMatrix function because it is a SymPy
function which was recently created at the time of this writing.
Here is the first cut for
class KroneckerProduct(SympyFunction): """ ... as before """ attributes = A_PROTECTED | A_READ_PROTECTED summary_text = "Kronecker product" sympy_name = "physics.quantum.TensorProduct" def eval(self, mi: ListExpression, evaluation: Evaluation): "KroneckerProduct[mi__]" sympy_mi = [to_sympy_matrix(m) for m in mi.elements] return from_sympy(TensorProduct(*sympy_mi))
In the above, I needed to add imports for
Evaluation and other places. Examples of these
of of course can be adapted from the place where you copied the code
Now let’s go over the new parts…
Undefined, we set the attributes of function
KroneckerProduct by setting the class variable
Here, in addition to being “Protected”, it is also “ReadProtected” and
the way we indicate both attributes is to bit-or,
|, the to attribute
The purpose of the class value
summary_text is described when we
Undefined so we won’t go over it here. Again,
there is a unit test that checks that it exists.
However if you go into Django and type “Vector Space Operations” in
the documentation section and you should see
along with the summary text we just added.
There is a class variable
sympy_name which we set, which here is
not necessary. If there were no evaluation method, this is the
function name that would be used by the generic evaluation method of
SympyFunction class. I added it here, because in the future we
may want to have a way to list correspondences between Mathics builtin
function names and SymPy function names.
Here we will focus on the evaluation method. Confusingly, the name
needs to start with
def eval(self, mi: ListExpression, evaluation: Evaluation): "KroneckerProduct[mi__List]" sympy_mi = [to_sympy_matrix(m) for m in mi.elements] return from_sympy(TensorProduct(*sympy_mi))
There is only one form that needs to be implemented so we can do this with one method.
The docstring for the method
in fact is a Mathics pattern that is matched in the apply phase of
Evaluation. It is used to determine which, if any, evaluation method
KroneckerProduct class should be called.
By convention and to make things simple, we use the name name in the
pattern, mi as we did in the class docstring description. However
the name(s) that are used in the pattern must also appear in the
same order in Python definition. Since we used
mi__ in the pattern in the
definition docstring, we must use
mi as the first parameter in the
evaluation method. In Mathics
__ indicates a function definition
with a nonzero number of arguments. See the BlankSequence for
When we have such a pattern object, the Mathics object when
represented in Python will be of type
Evaluation methods also have an parameter of type
the parameters that are listed the definition docstring. By convention
we always name this parameter
Inside the function the first thing we do is to convert the elements of the ListExpression into a Python list of SymPy Matrix values.
In this simple, first-draft function we don’t do any validation of the arguments. We should come back later and fill this in.
After we have something that we can pass on to SymPy we call the SymPy function that corresponds to the Mathics function. And the return value is converted back into Mathics for return.
Again we don’t do checking on the return value from SymPy or from the conversion back to Mathics’ internal Element-based object. In a future version we should do this.
We have a number of Mathics builtin functions that work like this. One thing to notice is that evaluation this way causes a great deal of conversions from Mathics into SymPy followed by conversions out of Sympy into Mathics. Without other mechanisms like caching computed values this can be slow. Even with caching, this can be slow.
Instead of using pattern matching to ensure we have a list of lists, we can generalize the pattern to match anything and then do the check in Python.
def eval(self, mi: ListExpression, evaluation: Evaluation): "KroneckerProduct[mi__]" # Code in Python mi__List matching if not all(m.head is for m in mi.elements): return None sympy_mi = [to_sympy_matrix(m) for m in mi.elements]
This might be a little bit faster at the expense, code that might be harder to maintain. Also, while this is faster in the current implementation, over time we may improve the interpreter so that pattern matching performs the same kind of steps that the Python code uses. If or when that is done, the difference would be negligible.
Keep in mind that while these kinds of tricks may add a small boost in performance, there are also some downsides; in some cases this kind of thing might even have to be undone in the future.
A full version of the basic implementation can be found in this commit.