kauri 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kauri/__init__.py +19 -0
- kauri/bck/__init__.py +28 -0
- kauri/bck/bck.py +119 -0
- kauri/bck_impl/__init__.py +4 -0
- kauri/bck_impl/bck_impl.py +101 -0
- kauri/bseries.py +296 -0
- kauri/cem/__init__.py +43 -0
- kauri/cem/cem.py +126 -0
- kauri/cem_impl/__init__.py +5 -0
- kauri/cem_impl/cem_impl.py +109 -0
- kauri/display.py +445 -0
- kauri/generic_algebra.py +78 -0
- kauri/gentrees.py +62 -0
- kauri/maps.py +323 -0
- kauri/rk.py +660 -0
- kauri/rk_methods.py +284 -0
- kauri/trees.py +1467 -0
- kauri/utils.py +215 -0
- kauri-1.0.0.dist-info/METADATA +221 -0
- kauri-1.0.0.dist-info/RECORD +23 -0
- kauri-1.0.0.dist-info/WHEEL +5 -0
- kauri-1.0.0.dist-info/licenses/LICENSE +201 -0
- kauri-1.0.0.dist-info/top_level.txt +1 -0
kauri/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Algebraic manipulation of rooted trees for the analysis of B-series and Runge-Kutta schemes.
|
|
3
|
+
"""
|
|
4
|
+
from .trees import Tree, Forest, ForestSum, TensorProductSum
|
|
5
|
+
from .maps import Map, ident, sign, exact_weights, omega
|
|
6
|
+
from .display import display
|
|
7
|
+
from .gentrees import trees_of_order, trees_up_to_order
|
|
8
|
+
from .rk import RK, rk_symbolic_weight, rk_order_cond
|
|
9
|
+
|
|
10
|
+
from .rk_methods import (euler, heun_rk2, midpoint, kutta_rk3, heun_rk3,
|
|
11
|
+
ralston_rk3, rk4, ralston_rk4, nystrom_rk5, backward_euler,
|
|
12
|
+
implicit_midpoint, crank_nicolson, gauss6, radau_iia, lobatto6)
|
|
13
|
+
|
|
14
|
+
from .bseries import BSeries, elementary_differential
|
|
15
|
+
|
|
16
|
+
from .trees import EMPTY_TREE, EMPTY_FOREST, EMPTY_FOREST_SUM, ZERO_FOREST_SUM
|
|
17
|
+
|
|
18
|
+
import kauri.bck
|
|
19
|
+
import kauri.cem
|
kauri/bck/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The ``kauri.bck`` sub-package implements the Butcher-Connes-Kreimer (BCK) :cite:`connes1999hopf` Hopf algebra
|
|
3
|
+
:math:`(H, \\Delta_{BCK}, \\mu, \\varepsilon_{BCK}, \\emptyset, S_{BCK})`, defined as follows.
|
|
4
|
+
|
|
5
|
+
- :math:`H` is the set of all non-planar rooted trees.
|
|
6
|
+
- The unit :math:`\\emptyset` is the empty forest.
|
|
7
|
+
- The counit map is defined by :math:`\\varepsilon_{BCK}(\\emptyset) = 1`,
|
|
8
|
+
:math:`\\varepsilon_{BCK}(t) = 0` for all :math:`\\emptyset \\neq t \\in H`.
|
|
9
|
+
- Multiplication :math:`\\mu : H \\otimes H \\to H` is defined as the
|
|
10
|
+
commutative juxtaposition of two forests.
|
|
11
|
+
- Comultiplication :math:`\\Delta : H \\to H \\otimes H` is defined as
|
|
12
|
+
|
|
13
|
+
.. math::
|
|
14
|
+
|
|
15
|
+
\\Delta_{BCK}(t) = t \\otimes \\emptyset + \\emptyset \\otimes t + \\sum_{s \\subset t} [t \\setminus s] \\otimes s
|
|
16
|
+
|
|
17
|
+
where the sum runs over all proper rooted subtrees :math:`s` of :math:`t`, and :math:`[t \\setminus s]`
|
|
18
|
+
is the forest of all trees remaining after erasing :math:`s` from :math:`t`.
|
|
19
|
+
- The antipode :math:`S_{BCK}` is defined by :math:`S_{BCK}(\\bullet) = -\\bullet` and
|
|
20
|
+
|
|
21
|
+
.. math::
|
|
22
|
+
|
|
23
|
+
S_{BCK}(t) = -t - \\sum_{s \\subset t} (-1)^{n(t \\setminus s)} S_{BCK}([t \\setminus s]) s,
|
|
24
|
+
|
|
25
|
+
where :math:`n(t \\setminus s)` is the number of trees in the forest :math:`[t \\setminus s]`.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from .bck import antipode, counit, coproduct, map_power, map_product
|
kauri/bck/bck.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Front-end for the BCK module
|
|
3
|
+
"""
|
|
4
|
+
from ..bck_impl import _counit, _coproduct, _antipode
|
|
5
|
+
from ..maps import Map
|
|
6
|
+
from ..trees import Tree, TensorProductSum
|
|
7
|
+
|
|
8
|
+
counit = Map(_counit)
|
|
9
|
+
counit.__doc__ = """
|
|
10
|
+
The counit :math:`\\varepsilon_{BCK}` of the BCK Hopf algebra.
|
|
11
|
+
|
|
12
|
+
:type: Map
|
|
13
|
+
|
|
14
|
+
Example usage::
|
|
15
|
+
|
|
16
|
+
import kauri as kr
|
|
17
|
+
import kauri.bck as bck
|
|
18
|
+
|
|
19
|
+
bck.counit(kr.Tree(None)) # Returns 1
|
|
20
|
+
bck.counit(kr.Tree([])) # Returns 0
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
antipode = Map(_antipode)
|
|
24
|
+
antipode.__doc__ = """
|
|
25
|
+
The antipode :math:`S_{BCK}` of the BCK Hopf algebra.
|
|
26
|
+
|
|
27
|
+
:type: Map
|
|
28
|
+
|
|
29
|
+
Example usage::
|
|
30
|
+
|
|
31
|
+
import kauri as kr
|
|
32
|
+
import kauri.bck as bck
|
|
33
|
+
|
|
34
|
+
t = kr.Tree([[[]],[]])
|
|
35
|
+
bck.antipode(t)
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def coproduct(t : Tree) -> TensorProductSum:
|
|
39
|
+
"""
|
|
40
|
+
The coproduct :math:`\\Delta_{BCK}` of the BCK Hopf algebra.
|
|
41
|
+
|
|
42
|
+
:param t: tree
|
|
43
|
+
:type t: Tree
|
|
44
|
+
:rtype: TensorProductSum
|
|
45
|
+
|
|
46
|
+
Example usage::
|
|
47
|
+
|
|
48
|
+
import kauri as kr
|
|
49
|
+
import kauri.bck as bck
|
|
50
|
+
|
|
51
|
+
bck.coproduct(kr.Tree([])) # Returns 1 ∅ ⊗ []+1 [] ⊗ ∅
|
|
52
|
+
bck.coproduct(kr.Tree([[]])) # Returns 1 [[]] ⊗ ∅+1 ∅ ⊗ [[]]+1 [] ⊗ []
|
|
53
|
+
"""
|
|
54
|
+
if not isinstance(t, Tree):
|
|
55
|
+
raise TypeError("Argument to bck.coproduct must be a Tree, not " + str(type(t)))
|
|
56
|
+
return _coproduct(t)
|
|
57
|
+
|
|
58
|
+
def map_product(f : Map, g : Map) -> Map:
|
|
59
|
+
"""
|
|
60
|
+
Returns the product of maps in the BCK Hopf algebra, defined by
|
|
61
|
+
|
|
62
|
+
.. math::
|
|
63
|
+
|
|
64
|
+
(f \\cdot g)(t) := \\mu \\circ (f \\otimes g) \\circ \\Delta_{BCK} (t)
|
|
65
|
+
|
|
66
|
+
.. note::
|
|
67
|
+
`bck.map_product(f,g)` is equivalent to the Map operator `f * g`
|
|
68
|
+
|
|
69
|
+
:param f: f
|
|
70
|
+
:type f: Map
|
|
71
|
+
:param g: g
|
|
72
|
+
:type g: Map
|
|
73
|
+
:rtype: Map
|
|
74
|
+
|
|
75
|
+
Example usage::
|
|
76
|
+
|
|
77
|
+
import kauri as kr
|
|
78
|
+
import kauri.bck as bck
|
|
79
|
+
|
|
80
|
+
ident = kr.Map(lambda x : x)
|
|
81
|
+
counit = bck.map_product(ident, bck.antipode) # Equivalent to indent * bck.antipode
|
|
82
|
+
"""
|
|
83
|
+
if not (isinstance(f, Map) and isinstance(g, Map)):
|
|
84
|
+
raise TypeError("Arguments in bck.map_product must be of type Map, not " + str(type(f)) + " and " + str(type(g)))
|
|
85
|
+
return f * g
|
|
86
|
+
|
|
87
|
+
def map_power(f : Map, exponent : int) -> Map:
|
|
88
|
+
"""
|
|
89
|
+
Returns the power of a map in the BCK Hopf algebra, where the product of functions is defined by
|
|
90
|
+
|
|
91
|
+
.. math::
|
|
92
|
+
|
|
93
|
+
(f \\cdot g)(t) := \\mu \\circ (f \\otimes g) \\circ \\Delta_{BCK} (t)
|
|
94
|
+
|
|
95
|
+
and negative powers are defined as :math:`f^{-n} = f^n \\circ S_{BCK}`,
|
|
96
|
+
where :math:`S_{BCK}` is the BCK antipode.
|
|
97
|
+
|
|
98
|
+
.. note::
|
|
99
|
+
`bck.map_power(f, n)` is equivalent to the Map operator `f ** n`
|
|
100
|
+
|
|
101
|
+
:param f: f
|
|
102
|
+
:type f: Map
|
|
103
|
+
:param exponent: exponent
|
|
104
|
+
:type exponent: int
|
|
105
|
+
|
|
106
|
+
Example usage::
|
|
107
|
+
|
|
108
|
+
import kauri as kr
|
|
109
|
+
import kauri.bck as bck
|
|
110
|
+
|
|
111
|
+
ident = kr.Map(lambda x : x)
|
|
112
|
+
S = bck.map_power(ident, -1) # antipode, equivalent to ident ** (-1)
|
|
113
|
+
ident_sq = bck.map_power(ident, 2) # identity squared, equivalent to ident ** 2
|
|
114
|
+
"""
|
|
115
|
+
if not isinstance(f, Map):
|
|
116
|
+
raise TypeError("f must be a Map, not " + str(type(f)))
|
|
117
|
+
if not isinstance(exponent, int):
|
|
118
|
+
raise TypeError("exponent must be an int, not " + str(type(exponent)))
|
|
119
|
+
return f ** exponent
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Back-end for the BCK module
|
|
3
|
+
"""
|
|
4
|
+
from functools import cache
|
|
5
|
+
import itertools
|
|
6
|
+
from ..trees import (Tree, Forest, TensorProductSum,
|
|
7
|
+
EMPTY_TREE, EMPTY_FOREST, EMPTY_FOREST_SUM)
|
|
8
|
+
from ..generic_algebra import _forest_apply
|
|
9
|
+
|
|
10
|
+
def _counit(t):
|
|
11
|
+
# Return 1 if t is the empty tree, otherwise 0
|
|
12
|
+
return 1 if t.list_repr is None else 0
|
|
13
|
+
|
|
14
|
+
@cache
|
|
15
|
+
def _antipode(t):
|
|
16
|
+
if t.list_repr is None:
|
|
17
|
+
return EMPTY_FOREST_SUM # Antipode of empty tree is the empty tree
|
|
18
|
+
if t.list_repr == tuple():
|
|
19
|
+
return -t # Antipode of singleton is the negative singleton
|
|
20
|
+
|
|
21
|
+
cp = _coproduct(t)
|
|
22
|
+
out = -t.as_forest_sum() # First term, -t
|
|
23
|
+
for c, branches, subtree_ in cp: # Remaining terms
|
|
24
|
+
subtree = subtree_[0] # Convert from Forest to Tree
|
|
25
|
+
if subtree.equals(t) or subtree.equals(EMPTY_TREE):
|
|
26
|
+
continue # We've already included the -t term at the start, so move on
|
|
27
|
+
out = out - c * _forest_apply(branches, _antipode) * subtree
|
|
28
|
+
|
|
29
|
+
return out.simplify()
|
|
30
|
+
|
|
31
|
+
@cache
|
|
32
|
+
def _coproduct_helper_2(t):
|
|
33
|
+
# This returns the coproduct as a list of (Forest, Tree) tuples
|
|
34
|
+
# The function _coproduct then converts this to a tensor product sum
|
|
35
|
+
# and simplifies.
|
|
36
|
+
|
|
37
|
+
# We compute the coproduct for a tree t = [t_1, t_2, ..., t_k] recursively.
|
|
38
|
+
# As per https://www2.mathematik.hu-berlin.de/~kreimer/wp-content/uploads/Foissy.pdf,
|
|
39
|
+
# the coproduct can be written as a sum over admissible cuts, defined as cuts
|
|
40
|
+
# where every walk from the root to a leaf contains at most one cut edge.
|
|
41
|
+
# The coproduct is then a sum of tensor products, where each term is the
|
|
42
|
+
# product of the forest of branches resulting from a cut and the remaining tree
|
|
43
|
+
# connected to the root. Denote these by P_c(t) and R_c(t) respectively, for a
|
|
44
|
+
# cut c.
|
|
45
|
+
|
|
46
|
+
# The recursion works on the idea that P_c(t) is the union of P_c(t_i),
|
|
47
|
+
# and R_c(t) = [R_c(t_1), R_c(t_2), ..., R_c(t_k)]. This allows us to use the
|
|
48
|
+
# coproducts of t_i to compute the coproduct of t.
|
|
49
|
+
|
|
50
|
+
# Caching of this function makes it fairly efficient for large computations.
|
|
51
|
+
|
|
52
|
+
if t.list_repr is None: # Empty tree
|
|
53
|
+
return [(EMPTY_FOREST, EMPTY_TREE)]
|
|
54
|
+
if t.list_repr == tuple(): # Singleton tree
|
|
55
|
+
return [(EMPTY_FOREST, t), (t.as_forest(), EMPTY_TREE)]
|
|
56
|
+
|
|
57
|
+
# Compute the coproducts of t_1, t_2, ..., t_k
|
|
58
|
+
subtree_coproducts = []
|
|
59
|
+
for rep in t.list_repr[:-1]: # Recall last element is the root label, so take [:-1]
|
|
60
|
+
subtree = Tree(rep)
|
|
61
|
+
subtree_coproducts.append(_coproduct_helper_2(subtree))
|
|
62
|
+
|
|
63
|
+
t_coproduct = [(Forest((t,)), EMPTY_TREE)] # First term of coproduct, t x empty tree
|
|
64
|
+
|
|
65
|
+
# Remaining terms, compute these recursively
|
|
66
|
+
for p in itertools.product(*subtree_coproducts):
|
|
67
|
+
R_c_repr = []
|
|
68
|
+
P_c_tree_list = []
|
|
69
|
+
for f, s in p:
|
|
70
|
+
if s.list_repr is not None:
|
|
71
|
+
R_c_repr += [s.list_repr]
|
|
72
|
+
P_c_tree_list += f.tree_list
|
|
73
|
+
R_c_repr += [t.list_repr[-1]] #Add label of root
|
|
74
|
+
t_coproduct.append((Forest(P_c_tree_list), Tree(R_c_repr)))
|
|
75
|
+
|
|
76
|
+
return t_coproduct
|
|
77
|
+
|
|
78
|
+
def _coproduct_2(t):
|
|
79
|
+
cp = _coproduct_helper_2(t)
|
|
80
|
+
return TensorProductSum(tuple((1, x[0], x[1]) for x in cp)).simplify()
|
|
81
|
+
|
|
82
|
+
@cache
|
|
83
|
+
def _coproduct(t):
|
|
84
|
+
# This follows the recursive definition of https://arxiv.org/pdf/hep-th/9808042
|
|
85
|
+
# using B_- and B_+
|
|
86
|
+
if t == Tree(None):
|
|
87
|
+
return TensorProductSum(( (1, EMPTY_FOREST, EMPTY_FOREST), )) # Tree(None) @ Tree(None)
|
|
88
|
+
if len(t.list_repr) == 1:
|
|
89
|
+
return TensorProductSum(( (1, EMPTY_FOREST, t.as_forest()), (1, t.as_forest(), EMPTY_FOREST) )) # Tree(None) @ t + t @ Tree(None)
|
|
90
|
+
|
|
91
|
+
root_color = t.list_repr[-1]
|
|
92
|
+
branches = t.unjoin()
|
|
93
|
+
|
|
94
|
+
cp_prod = 1
|
|
95
|
+
for subtree in branches:
|
|
96
|
+
cp = _coproduct(subtree)
|
|
97
|
+
cp_prod = cp_prod * cp
|
|
98
|
+
|
|
99
|
+
# Return t \otimes \emptyset + (id \otimes B_+)[\Delta(B_-(t))]
|
|
100
|
+
out = t @ Tree(None) + TensorProductSum(tuple((c, f1, f2.join(root_color)) for c, f1, f2 in cp_prod))
|
|
101
|
+
return out.simplify()
|
kauri/bseries.py
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module allows for the symbolic manipulation and evaluation of truncated
|
|
3
|
+
B-Series on unlabelled trees, for an ODE of the form
|
|
4
|
+
|
|
5
|
+
.. math::
|
|
6
|
+
|
|
7
|
+
\\frac{dy}{ds} = f(y).
|
|
8
|
+
|
|
9
|
+
Given a weights function :math:`\\varphi`, the associated truncated B-Series is
|
|
10
|
+
|
|
11
|
+
.. math::
|
|
12
|
+
|
|
13
|
+
B_h(\\varphi, y_0) := \\sum_{|t| \\leq n} \\frac{h^{|t|}}{\\sigma(t)} \\varphi(t) F(t)(y_0),
|
|
14
|
+
|
|
15
|
+
where the sum runs over all trees of order at most :math:`n`, :math:`\\sigma(t)` is the symmetry factor
|
|
16
|
+
of a tree, and :math:`F(t)(y_0)` are the elementary differentials, defined recursively on trees by:
|
|
17
|
+
|
|
18
|
+
.. math::
|
|
19
|
+
|
|
20
|
+
F(\\emptyset) = y,
|
|
21
|
+
.. math::
|
|
22
|
+
|
|
23
|
+
F(\\bullet) = f(y),
|
|
24
|
+
.. math::
|
|
25
|
+
|
|
26
|
+
F([t_1, t_2, \\ldots, t_k])(y) = f^{(k)}(y)(F(t_1)(y), F(t_2)(y), \\ldots, F(t_k)(y)).
|
|
27
|
+
|
|
28
|
+
The main objective of this module is to evaluate existing B-series. For more complicated operations,
|
|
29
|
+
it is recommended to work with the underlying character (or elementary weights function) and then
|
|
30
|
+
construct a B-series from the result. For example, take the following B-series corresponding to
|
|
31
|
+
the Euler and RK4 schemes:
|
|
32
|
+
|
|
33
|
+
.. code-block:: python
|
|
34
|
+
|
|
35
|
+
import kauri as kr
|
|
36
|
+
|
|
37
|
+
y1 = sp.symbols('y1')
|
|
38
|
+
y = sp.Matrix([y1])
|
|
39
|
+
f = sp.Matrix([y1 ** 2])
|
|
40
|
+
|
|
41
|
+
bs1 = kr.BSeries(y, f, kr.euler.elementary_weights_map(), 5)
|
|
42
|
+
bs2 = kr.BSeries(y, f, kr.rk4.elementary_weights_map(), 5)
|
|
43
|
+
|
|
44
|
+
At the level of the underlying characters, the composition of two B-series is given by the BCK product
|
|
45
|
+
of the characters, so one can get the composition by doing
|
|
46
|
+
|
|
47
|
+
.. code-block:: python
|
|
48
|
+
|
|
49
|
+
bs_composition = kr.BSeries(y, f, bs2.weights * bs1.weights, 5)
|
|
50
|
+
|
|
51
|
+
Similarly the inverse B-series for the Euler scheme is given by
|
|
52
|
+
|
|
53
|
+
.. code-block:: python
|
|
54
|
+
|
|
55
|
+
bs_inverse = kr.BSeries(y, f, bs1.weights ** (-1), 5)
|
|
56
|
+
|
|
57
|
+
and the symmetric-adjoint method is given by
|
|
58
|
+
|
|
59
|
+
.. code-block:: python
|
|
60
|
+
|
|
61
|
+
bs_adjoint = kr.BSeries(y, f, bs1.weights ** (-1) & kr.sign, 5)
|
|
62
|
+
|
|
63
|
+
where `kr.sign` is the :class:`Map` sending `t` to `-t`.
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
import itertools
|
|
67
|
+
from functools import cache
|
|
68
|
+
from typing import Union
|
|
69
|
+
|
|
70
|
+
import sympy as sp
|
|
71
|
+
|
|
72
|
+
from kauri import Tree, trees_up_to_order, Map
|
|
73
|
+
|
|
74
|
+
def _check_f_y(f, y):
|
|
75
|
+
# Checks that f and y are correctly specified
|
|
76
|
+
|
|
77
|
+
if not isinstance(y, sp.Matrix):
|
|
78
|
+
raise TypeError("y must be of type sympy.Matrix, not " + str(type(y)))
|
|
79
|
+
if not isinstance(f, sp.Matrix):
|
|
80
|
+
raise TypeError("The vector field f must be of type sympy.Matrix, not " + str(type(f)))
|
|
81
|
+
|
|
82
|
+
# Make sure f, y are vectors of the same dimensions
|
|
83
|
+
if not (f.shape[1] == 1 and y.shape[1] == 1 and f.shape[0] == y.shape[0]):
|
|
84
|
+
raise ValueError("""f, y must be column vectors, both of shape (d, 1) for some d.
|
|
85
|
+
Instead, got f of shape """ + str(f.shape) + " and y of shape " + str(y.shape))
|
|
86
|
+
|
|
87
|
+
# Make sure f is in terms of y and nothing else
|
|
88
|
+
allowed_symbols = set(y)
|
|
89
|
+
f_symbols = set().union(*(expr.free_symbols for expr in f))
|
|
90
|
+
if not f_symbols <= allowed_symbols:
|
|
91
|
+
raise ValueError("""The vector field f contains unknown symbols which are not contained in y.
|
|
92
|
+
If these are constants, please add them to the ODE with a derivative of 0. If these represent
|
|
93
|
+
time, please add them to the ODE with a derivative of 1.""")
|
|
94
|
+
|
|
95
|
+
@cache
|
|
96
|
+
def _elementary_differential(tree : Tree,
|
|
97
|
+
f : sp.ImmutableDenseMatrix,
|
|
98
|
+
y_vars : sp.ImmutableDenseMatrix):
|
|
99
|
+
if tree.list_repr is None:
|
|
100
|
+
return y_vars # y
|
|
101
|
+
if len(tree.list_repr) == 1:
|
|
102
|
+
return f # f(y)
|
|
103
|
+
|
|
104
|
+
# tree = [t_1, ..., t_k], sub_diffs = [F(t_1), ..., F(t_k)]
|
|
105
|
+
sub_diffs = tuple(_elementary_differential(subtree, f, y_vars) for subtree in tree.unjoin())
|
|
106
|
+
|
|
107
|
+
# Now compute f^(k) (F(t_1), ..., F(t_k))
|
|
108
|
+
# which equals \sum_{i_j = 1,...,d} F(t_1)_{i_1} ... F(t_k)_{i_k} ( d^k f / dy_{i_1} ... dy_{i_k} )
|
|
109
|
+
result = sp.zeros(*sp.shape(y_vars))
|
|
110
|
+
dim = len(y_vars)
|
|
111
|
+
k = len(tree.list_repr) - 1
|
|
112
|
+
|
|
113
|
+
for idx in itertools.product(range(dim), repeat=k):
|
|
114
|
+
# Compute the derivative d^k f / dy_{i_1} ... dy_{i_k} first
|
|
115
|
+
term = f
|
|
116
|
+
for i in idx:
|
|
117
|
+
term = sp.diff(term, y_vars[i])
|
|
118
|
+
|
|
119
|
+
# Now multiply by F(t_1)_{i_1} ... F(t_k)_{i_k}
|
|
120
|
+
for j, i in enumerate(idx):
|
|
121
|
+
term *= sub_diffs[j][i]
|
|
122
|
+
result += term
|
|
123
|
+
|
|
124
|
+
return result
|
|
125
|
+
|
|
126
|
+
def elementary_differential(tree : Tree,
|
|
127
|
+
f : sp.Matrix,
|
|
128
|
+
y : sp.Matrix
|
|
129
|
+
) -> sp.Matrix:
|
|
130
|
+
"""
|
|
131
|
+
Returns the elementary differential of a vector field.
|
|
132
|
+
These are defined recursively on trees by:
|
|
133
|
+
|
|
134
|
+
.. math::
|
|
135
|
+
|
|
136
|
+
F(\\emptyset) = y,
|
|
137
|
+
.. math::
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
F(\\bullet) = f(y),
|
|
141
|
+
.. math::
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
F([t_1, t_2, \\ldots, t_k])(y) = f^{(k)}(y)(F(t_1)(y), F(t_2)(y), \\ldots, F(t_k)(y)).
|
|
145
|
+
|
|
146
|
+
:param tree: Unlabelled tree corresponding to the elementary differential
|
|
147
|
+
:type tree: Tree
|
|
148
|
+
:param f: Vector field
|
|
149
|
+
:type f: sympy.Matrix
|
|
150
|
+
:param y: Symbolic variables y
|
|
151
|
+
:type y: sympy.Matrix
|
|
152
|
+
|
|
153
|
+
Example usage::
|
|
154
|
+
|
|
155
|
+
import kauri as kr
|
|
156
|
+
import sympy as sp
|
|
157
|
+
|
|
158
|
+
y1, y2 = sp.symbols('y1 y2')
|
|
159
|
+
y = sp.Matrix([y1, y2])
|
|
160
|
+
f = sp.Matrix([y1 ** 2, y1 * y2])
|
|
161
|
+
|
|
162
|
+
t = kr.Tree([[[]],[]])
|
|
163
|
+
elementary_differential(t, f, y) # Returns sp.Matrix([[4 * y1**5 ], [ 4 * y1**4 * y2]])
|
|
164
|
+
"""
|
|
165
|
+
if not isinstance(tree, Tree):
|
|
166
|
+
raise TypeError("The argument 'tree' must be of type Tree, not " + str(type(tree)))
|
|
167
|
+
if tree.colors() > 1:
|
|
168
|
+
raise ValueError("Tree passed to elementary differential must be unlabelled.")
|
|
169
|
+
|
|
170
|
+
_check_f_y(f, y)
|
|
171
|
+
|
|
172
|
+
return _elementary_differential(tree, sp.ImmutableDenseMatrix(f), sp.ImmutableDenseMatrix(y))
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class BSeries:
|
|
176
|
+
"""
|
|
177
|
+
This class allows for the symbolic manipulation and evaluation of truncated
|
|
178
|
+
B-Series on unlabelled trees, for a given vector field f. Given a weights
|
|
179
|
+
function :math:`\\varphi`, the associated truncated B-Series is
|
|
180
|
+
|
|
181
|
+
.. math::
|
|
182
|
+
|
|
183
|
+
B_h(\\varphi, y_0) := \\sum_{|t| \\leq n} \\frac{h^{|t|}}{\\sigma(t)} \\varphi(t) F(t)(y_0),
|
|
184
|
+
|
|
185
|
+
where the sum runs over all trees of order at most :math:`n`.
|
|
186
|
+
|
|
187
|
+
:param y: Symbolic variables y
|
|
188
|
+
:type y: sympy.Matrix
|
|
189
|
+
:param f: Vector field
|
|
190
|
+
:type f: sympy.Matrix
|
|
191
|
+
:param weights: The weights function :math:`\\varphi` corresponding to the B-Series.
|
|
192
|
+
:type weights: kauri.Map
|
|
193
|
+
:param order: The truncation order of the B-Series
|
|
194
|
+
:type order: int
|
|
195
|
+
|
|
196
|
+
Example usage::
|
|
197
|
+
|
|
198
|
+
import kauri as kr
|
|
199
|
+
import sympy as sp
|
|
200
|
+
|
|
201
|
+
y1 = sp.symbols('y1')
|
|
202
|
+
y = sp.Matrix([y1])
|
|
203
|
+
f = sp.Matrix([y1 ** 2])
|
|
204
|
+
|
|
205
|
+
m = kr.rk4.elementary_weights_map()
|
|
206
|
+
bs = BSeries(y, f, m, 5)
|
|
207
|
+
|
|
208
|
+
print(bs.series()) # Print the B-Series as a sympy expression
|
|
209
|
+
print(bs(1, 0.1)) # Evaluate the B-Series at y = 1, h = 0.1
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
def __init__(self, y : sp.Matrix, f : sp.Matrix, weights : Map, order : int):
|
|
213
|
+
if not isinstance(weights, Map):
|
|
214
|
+
raise TypeError("weights must be a Map, not " + str(type(weights)))
|
|
215
|
+
if not isinstance(order, int):
|
|
216
|
+
raise TypeError("order must be an int, not " + str(type(order)))
|
|
217
|
+
if order < 0:
|
|
218
|
+
raise ValueError("order cannot be negative")
|
|
219
|
+
|
|
220
|
+
_check_f_y(f, y)
|
|
221
|
+
|
|
222
|
+
self.f = f
|
|
223
|
+
self.y = y
|
|
224
|
+
self.f_imm = sp.ImmutableDenseMatrix(f) #Immutable for cache in elementary_differential
|
|
225
|
+
self.y_imm = sp.ImmutableDenseMatrix(y) #Immutable for cache in elementary_differential
|
|
226
|
+
self.h = sp.symbols('h')
|
|
227
|
+
self.order = order
|
|
228
|
+
self.weights = weights
|
|
229
|
+
self.dim = len(y)
|
|
230
|
+
self.symbolic_expr = sp.zeros(*sp.shape(y))
|
|
231
|
+
for t in trees_up_to_order(order):
|
|
232
|
+
self.symbolic_expr = self.symbolic_expr + self.h ** t.nodes() * weights(t) * _elementary_differential(t, self.f_imm, self.y_imm) / t.sigma()
|
|
233
|
+
|
|
234
|
+
def __call__(self, y : list, h : Union[int, float]) -> list:
|
|
235
|
+
"""
|
|
236
|
+
Evalutes the B-series at the given values for y and h.
|
|
237
|
+
|
|
238
|
+
:param y: List of values to substitute for y
|
|
239
|
+
:type y: list
|
|
240
|
+
:param h: Value to substitute for the step size h
|
|
241
|
+
:type h: int | float
|
|
242
|
+
:return: Value of the B-series evaluated for the given values of y and h
|
|
243
|
+
:rtype: list
|
|
244
|
+
|
|
245
|
+
"""
|
|
246
|
+
if not isinstance(y, list):
|
|
247
|
+
raise ValueError("y must be a list, not " + str(type(y)))
|
|
248
|
+
if len(y) != self.dim:
|
|
249
|
+
raise ValueError("List of values for y is of incorrect length. Expected " + str(self.dim) + " got " + str(len(y)))
|
|
250
|
+
if not isinstance(h, (int, float)):
|
|
251
|
+
raise ValueError("h must be an int or float, not " + str(type(h)))
|
|
252
|
+
|
|
253
|
+
out = self.symbolic_expr.subs(self.h, h)
|
|
254
|
+
for i in range(self.dim):
|
|
255
|
+
out = out.subs(self.y[i], y[i])
|
|
256
|
+
return [float(x) for x in out]
|
|
257
|
+
|
|
258
|
+
def series(self) -> sp.Matrix:
|
|
259
|
+
"""
|
|
260
|
+
Returns the truncated B-series as a sympy Matrix.
|
|
261
|
+
|
|
262
|
+
:rtype: sympy.Matrix
|
|
263
|
+
"""
|
|
264
|
+
return self.symbolic_expr
|
|
265
|
+
|
|
266
|
+
def __repr__(self):
|
|
267
|
+
return repr(self.symbolic_expr)
|
|
268
|
+
|
|
269
|
+
# def __and__(self, other : 'BSeries') -> 'BSeries':
|
|
270
|
+
# """
|
|
271
|
+
# Returns the composition of two B-Series, assuming they are with respect
|
|
272
|
+
# to the same variables and vector field. That is, given two B-Series:
|
|
273
|
+
#
|
|
274
|
+
# .. math::
|
|
275
|
+
#
|
|
276
|
+
# B_h(\\varphi, y_0) := \\sum_{|t| \\leq n} \\frac{h^{|t|}}{\\sigma(t)} \\varphi(t) F(t)(y_0),
|
|
277
|
+
#
|
|
278
|
+
# .. math::
|
|
279
|
+
#
|
|
280
|
+
# B_h(\\psi, y_0) := \\sum_{|t| \\leq n} \\frac{h^{|t|}}{\\sigma(t)} \\psi(t) F(t)(y_0),
|
|
281
|
+
#
|
|
282
|
+
# returns their composition :math:`B_h(\\psi, B_h(\\varphi, y_0)) = B_h(\\varphi * \\psi, y_0)`,
|
|
283
|
+
# where the product is the BCK product of characters. The truncation order of the resulting B-series
|
|
284
|
+
# is the minimum of the truncation orders of the original two.
|
|
285
|
+
#
|
|
286
|
+
# :param other: other
|
|
287
|
+
# :type other: BSeries
|
|
288
|
+
# """
|
|
289
|
+
# if not isinstance(other, BSeries):
|
|
290
|
+
# raise TypeError("Cannot multiply BSeries and object of type " + str(type(other)))
|
|
291
|
+
# if self.y != other.y:
|
|
292
|
+
# raise ValueError("Cannot compose B-Series: symbolic variables y of the two series do not match")
|
|
293
|
+
# if self.f != other.f:
|
|
294
|
+
# raise ValueError("Cannot compose B-Series: vector fields of the two series do not match")
|
|
295
|
+
#
|
|
296
|
+
# return BSeries(self.y, self.f, self.weights * other.weights, min(self.order, other.order))
|
kauri/cem/__init__.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The ``kauri.cem`` sub-package implements the Calaque, Ebrahimi-Fard and Manchon (CEM) :cite:`calaque2011two` Hopf algebra
|
|
3
|
+
:math:`(\\widetilde{H}, \\Delta_{CEM}, \\mu, \\varepsilon_{CEM}, \\bullet, S_{CEM})`, defined as follows.
|
|
4
|
+
|
|
5
|
+
- :math:`\\widetilde{H}` is the space of non-empty trees, defined as
|
|
6
|
+
:math:`\\widetilde{H} = H / J` where :math:`J` is the ideal
|
|
7
|
+
generated by :math:`\\bullet - \\emptyset`.
|
|
8
|
+
- The unit is the single-node tree, :math:`\\bullet`.
|
|
9
|
+
- The counit map is defined by :math:`\\varepsilon_{CEM}(\\bullet) = 1`,
|
|
10
|
+
:math:`\\varepsilon_{CEM}(t) = 0` for all :math:`\\bullet \\neq t \\in \\widetilde{H}`.
|
|
11
|
+
- Multiplication :math:`\\mu : \\widetilde{H} \\otimes \\widetilde{H} \\to \\widetilde{H}` is defined as the
|
|
12
|
+
commutative juxtaposition of two forests.
|
|
13
|
+
- Comultiplication :math:`\\Delta : \\widetilde{H} \\to \\widetilde{H} \\otimes \\widetilde{H}` is defined as
|
|
14
|
+
|
|
15
|
+
.. math::
|
|
16
|
+
|
|
17
|
+
\\Delta_{CEM}(t) = \\sum_{s \\subset t} s \\otimes t / s
|
|
18
|
+
|
|
19
|
+
where the sum runs over all possible subforests :math:`s` of :math:`t`, and
|
|
20
|
+
:math:`t / s` is the tree obtained by contracting each connected component of
|
|
21
|
+
:math:`s` onto a vertex :cite:`calaque2011two`.
|
|
22
|
+
- The antipode :math:`S_{CEM}` is defined by :math:`S_{CEM}(\\bullet) = \\bullet` and
|
|
23
|
+
|
|
24
|
+
.. math::
|
|
25
|
+
|
|
26
|
+
S_{CEM}(t) = -t - \\sum_{t, \\bullet \\neq s \\subset t} S_{CEM}(s) \\, t / s.
|
|
27
|
+
|
|
28
|
+
.. note::
|
|
29
|
+
|
|
30
|
+
We adopt the singleton-reduced coproduct, where the singleton tree :math:`\\bullet`
|
|
31
|
+
appears in each forest at most once. This defines a Hopf algebra
|
|
32
|
+
on :math:`\\widetilde{H} = H / J`. There are alternative forms of the
|
|
33
|
+
coproduct defined directly on :math:`H`, but these do not define a Hopf
|
|
34
|
+
algebra.
|
|
35
|
+
|
|
36
|
+
.. note::
|
|
37
|
+
|
|
38
|
+
Since :math:`\\bullet` is the unit, characters :math:`\\phi` on the resulting
|
|
39
|
+
Hopf algebra must satisfy :math:`\\phi(\\bullet) = 1`.
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
from .cem import antipode, counit, coproduct, map_power, map_product
|