Add multiplication operation support to MultiIndexSet instances
In order to support a more consistent multiplication between polynomials, MultiIndexSet
instances should support multiplication operation via the dunder method __mul__()
.
Below are the expected behaviors using some examples.
Same dimension, same lp-degree
>>> import minterpy as mp
>>> import numpy as np
>>> mi_1 = mp.MultiIndexSet.from_degree(2, 2, 1)
>>> mi_1
MultiIndexSet
[[0 0]
[1 0]
[2 0]
[0 1]
[1 1]
[0 2]]
>>> mi_2 = mp.MultiIndexSet.from_degree(2, 1, 1)
>>> mi_2
MultiIndexSet
[[0 0]
[1 0]
[0 1]]
>>> mi_3 = mi_1 * mi_2
>>> mi_3
MultiIndexSet
[[0 0]
[1 0]
[2 0]
[3 0]
[0 1]
[1 1]
[2 1]
[0 2]
[1 2]
[0 3]]
>>> mi_3.poly_degree
3
The resulting exponents are the cross-product of exponents between the two multi-index sets and then summed along the dimension. As a set, any repeated elements will be squashed in the resulting set.
If the l_p
-degree between the two multi-index sets are the same then the resulting polynomial degree is the sum of the two polynomial degrees as expected. Furthermore, if the two sets are complete (resp. downward-closed) then the product will also be complete (resp. downward-closed). If one of the sets is incomplete (resp. downward-open) then the product will also be incomplete (resp. downward-open).
Same dimension, different lp-degree
>>> import minterpy as mp
>>> import numpy as np
>>> mi_1 = mp.MultiIndexSet.from_degree(2, 1, np.inf)
>>> mi_1
MultiIndexSet
[[0 0]
[1 0]
[0 1]
[1 1]]
>>> mi_2 = mp.MultiIndexSet.from_degree(2, 1, 1)
>>> mi_2
MultiIndexSet
[[0 0]
[1 0]
[0 1]]
>>> mi_3 = mi_1 * mi_2
>>> mi_3
MultiIndexSet
[[0 0]
[1 0]
[2 0]
[0 1]
[1 1]
[2 1]
[0 2]
[1 2]]
>>> mi_3.lp_degree
inf
>>> mi_3.poly_degree
2
Here, the l_p
-degree of the resulting multi-index set, by convention, should be the larger between the l_p
-degrees of the two sets (not added otherwise repeated index sets multiplication will cause the l_p
-degree to grows quickly which is rather strange).
While the operands may be both complete with respect to the respective l_p
-degree, the product set may no longer be complete.
>>> mp.MultiIndexSet.from_degree(2, 2, np.inf)
MultiIndexSet
[[0 0]
[1 0]
[2 0]
[0 1]
[1 1]
[2 1]
[0 2]
[1 2]
[2 2]]
That is, there is no product that results in the element [2, 2]
.
On the other hand, if the operands are both downward-closed, the product will remain downward-closed.
Different dimension
>>> mi_1 = mp.MultiIndexSet.from_degree(1, 3, 1)
>>> mi_1
MultiIndexSet
[[0]
[1]
[2]
[3]]
>>> mi_2 = mp.MultiIndexSet.from_degree(2, 2, 1)
>>> mi_2
MultiIndexSet
[[0 0]
[1 0]
[2 0]
[0 1]
[1 1]
[0 2]]
>>> mi_3 = mi_1 * mi_2
MultiIndexSet
[[0 0]
[1 0]
[2 0]
[3 0]
[4 0]
[5 0]
[0 1]
[1 1]
[2 1]
[3 1]
[4 1]
[0 2]
[1 2]
[2 2]
[3 2]]
Here, the dimension of the first multi-index set (having the lower dimension) is expanded to the dimension of the second set (having the higher dimension). Then, the same procedure as the above follows to obtain the resulting set.
Notes on the current behavior
Direct multiplication between instances of MultiIndexSet
is not currently supported,
but the _lagrange_mul()
has an implementation of a multi-index set multiplication for the multiplication between two Lagrange polynomials.
However, in that implementation the resulting multi-index set of the product polynomial is always forced to be complete by creating a new multi-index set that has the sum of the degrees and the sum of the l_p
-degrees.
If the operands polynomials have incomplete set, there are many exponents that are not needed in the product.
Furthermore, by summing up l_p
-degree from multiplication the complete set may quickly become large.
By implementing the product of the set from the bottom up (from MultiIndexSet
) instead directly in the polynomial basis classes, we can have a more consistent and reusable behavior.
This issue is part of the larger issue of refactoring the multi-index set (Issue #99 (closed)) as well as supporting the computation of orthogonal polynomials in Minterpy (see Issue #116)