kauri 2.0.0__tar.gz → 2.1.0__tar.gz

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.
Files changed (37) hide show
  1. {kauri-2.0.0 → kauri-2.1.0}/PKG-INFO +16 -7
  2. {kauri-2.0.0 → kauri-2.1.0}/README.md +8 -0
  3. {kauri-2.0.0 → kauri-2.1.0}/kauri/__init__.py +8 -9
  4. {kauri-2.0.0 → kauri-2.1.0}/kauri/bseries.py +14 -2
  5. {kauri-2.0.0 → kauri-2.1.0}/kauri/gentrees.py +187 -2
  6. {kauri-2.0.0 → kauri-2.1.0}/kauri/rk.py +24 -8
  7. {kauri-2.0.0 → kauri-2.1.0}/kauri/trees.py +7 -2
  8. {kauri-2.0.0 → kauri-2.1.0}/kauri/utils.py +1 -1
  9. {kauri-2.0.0 → kauri-2.1.0}/kauri.egg-info/PKG-INFO +16 -7
  10. {kauri-2.0.0 → kauri-2.1.0}/kauri.egg-info/requires.txt +6 -4
  11. {kauri-2.0.0 → kauri-2.1.0}/pyproject.toml +11 -10
  12. {kauri-2.0.0 → kauri-2.1.0}/LICENSE +0 -0
  13. {kauri-2.0.0 → kauri-2.1.0}/kauri/_protocols.py +0 -0
  14. {kauri-2.0.0 → kauri-2.1.0}/kauri/bck/__init__.py +0 -0
  15. {kauri-2.0.0 → kauri-2.1.0}/kauri/bck/bck.py +0 -0
  16. {kauri-2.0.0 → kauri-2.1.0}/kauri/cem/__init__.py +0 -0
  17. {kauri-2.0.0 → kauri-2.1.0}/kauri/cem/cem.py +0 -0
  18. {kauri-2.0.0 → kauri-2.1.0}/kauri/cf.py +0 -0
  19. {kauri-2.0.0 → kauri-2.1.0}/kauri/display.py +0 -0
  20. {kauri-2.0.0 → kauri-2.1.0}/kauri/generic_algebra.py +0 -0
  21. {kauri-2.0.0 → kauri-2.1.0}/kauri/gl/__init__.py +0 -0
  22. {kauri-2.0.0 → kauri-2.1.0}/kauri/gl/gl.py +0 -0
  23. {kauri-2.0.0 → kauri-2.1.0}/kauri/manifold_ees.py +0 -0
  24. {kauri-2.0.0 → kauri-2.1.0}/kauri/maps.py +0 -0
  25. {kauri-2.0.0 → kauri-2.1.0}/kauri/mkw/__init__.py +0 -0
  26. {kauri-2.0.0 → kauri-2.1.0}/kauri/mkw/mkw.py +0 -0
  27. {kauri-2.0.0 → kauri-2.1.0}/kauri/nck/__init__.py +0 -0
  28. {kauri-2.0.0 → kauri-2.1.0}/kauri/nck/nck.py +0 -0
  29. {kauri-2.0.0 → kauri-2.1.0}/kauri/oddeven.py +0 -0
  30. {kauri-2.0.0 → kauri-2.1.0}/kauri/pgl/__init__.py +0 -0
  31. {kauri-2.0.0 → kauri-2.1.0}/kauri/pgl/pgl.py +0 -0
  32. {kauri-2.0.0 → kauri-2.1.0}/kauri/planar_oddeven.py +0 -0
  33. {kauri-2.0.0 → kauri-2.1.0}/kauri/rk_methods.py +0 -0
  34. {kauri-2.0.0 → kauri-2.1.0}/kauri.egg-info/SOURCES.txt +0 -0
  35. {kauri-2.0.0 → kauri-2.1.0}/kauri.egg-info/dependency_links.txt +0 -0
  36. {kauri-2.0.0 → kauri-2.1.0}/kauri.egg-info/top_level.txt +0 -0
  37. {kauri-2.0.0 → kauri-2.1.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kauri
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: Hopf algebras, B-series, and Runge-Kutta methods on rooted trees
5
5
  Author-email: Daniil Shmelev <daniil.shmelev23@imperial.ac.uk>
6
6
  License: Apache-2.0
@@ -16,14 +16,15 @@ Classifier: Operating System :: Microsoft :: Windows
16
16
  Classifier: Operating System :: Unix
17
17
  Classifier: Programming Language :: Python
18
18
  Classifier: Topic :: Scientific/Engineering :: Mathematics
19
- Requires-Python: >=3.10
19
+ Requires-Python: >=3.9
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
- Requires-Dist: matplotlib>=3.9.2
23
- Requires-Dist: numpy>=1.26.4
24
- Requires-Dist: sympy>=1.12
25
- Requires-Dist: scipy>=1.13
26
- Requires-Dist: tqdm
22
+ Provides-Extra: full
23
+ Requires-Dist: matplotlib>=3.9.2; extra == "full"
24
+ Requires-Dist: numpy>=1.26.4; extra == "full"
25
+ Requires-Dist: sympy>=1.12; extra == "full"
26
+ Requires-Dist: scipy>=1.13; extra == "full"
27
+ Requires-Dist: tqdm; extra == "full"
27
28
  Provides-Extra: dev
28
29
  Requires-Dist: pytest>=8.0; extra == "dev"
29
30
  Requires-Dist: ruff>=0.9.0; extra == "dev"
@@ -51,6 +52,14 @@ Kauri is a Python package for symbolic and algebraic manipulation of rooted tree
51
52
  pip install kauri
52
53
  ```
53
54
 
55
+ The base install is lightweight (pure Python, no external dependencies) and provides tree algebra, enumeration, indexing, and Hopf algebraic operations.
56
+
57
+ For visualization, Runge-Kutta analysis, and B-series (requires matplotlib, numpy, scipy, sympy, tqdm):
58
+
59
+ ```
60
+ pip install kauri[full]
61
+ ```
62
+
54
63
  ## Documentation
55
64
 
56
65
  Full documentation is available at [https://kauri.readthedocs.io](https://kauri.readthedocs.io)
@@ -20,6 +20,14 @@ Kauri is a Python package for symbolic and algebraic manipulation of rooted tree
20
20
  pip install kauri
21
21
  ```
22
22
 
23
+ The base install is lightweight (pure Python, no external dependencies) and provides tree algebra, enumeration, indexing, and Hopf algebraic operations.
24
+
25
+ For visualization, Runge-Kutta analysis, and B-series (requires matplotlib, numpy, scipy, sympy, tqdm):
26
+
27
+ ```
28
+ pip install kauri[full]
29
+ ```
30
+
23
31
  ## Documentation
24
32
 
25
33
  Full documentation is available at [https://kauri.readthedocs.io](https://kauri.readthedocs.io)
@@ -17,7 +17,7 @@
17
17
  Algebraic manipulation of rooted trees for the analysis of B-series and Runge-Kutta schemes.
18
18
  """
19
19
 
20
- __version__ = "2.0.0"
20
+ __version__ = "2.1.0"
21
21
 
22
22
  __all__ = [
23
23
  # Core types
@@ -30,6 +30,8 @@ __all__ = [
30
30
  # Tree generation
31
31
  "trees_of_order", "trees_up_to_order",
32
32
  "colored_trees_of_order", "colored_trees_up_to_order",
33
+ "colored_trees", "colored_tree_to_idx", "idx_to_colored_tree",
34
+ "canonical_to_recursive_permutation", "recursive_to_canonical_permutation",
33
35
  "planar_trees_of_order", "planar_trees_up_to_order",
34
36
  "colored_planar_trees_of_order", "colored_planar_trees_up_to_order",
35
37
  # Display
@@ -53,23 +55,23 @@ __all__ = [
53
55
 
54
56
  from .trees import (Tree, Forest, CommutativeForest, ForestSum, TensorProductSum,
55
57
  NoncommutativeForest, PlanarTree, OrderedForest, EMPTY_PLANAR_TREE)
58
+ from .trees import EMPTY_TREE, EMPTY_FOREST, EMPTY_ORDERED_FOREST, EMPTY_FOREST_SUM, ZERO_FOREST_SUM
56
59
  from .maps import Map, ident, sign, exact_weights, omega
57
60
  from .display import display
58
61
  from .gentrees import (trees_of_order, trees_up_to_order,
59
62
  colored_trees_of_order, colored_trees_up_to_order,
63
+ colored_trees, colored_tree_to_idx, idx_to_colored_tree,
64
+ canonical_to_recursive_permutation, recursive_to_canonical_permutation,
60
65
  planar_trees_of_order, planar_trees_up_to_order,
61
66
  colored_planar_trees_of_order, colored_planar_trees_up_to_order)
62
67
  from .rk import RK, rk_symbolic_weight, rk_order_cond
63
68
  from .cf import CFMethod
64
-
65
69
  from .rk_methods import (euler, heun_rk2, midpoint, kutta_rk3, heun_rk3,
66
70
  ralston_rk3, rk4, ralston_rk4, nystrom_rk5, backward_euler,
67
71
  implicit_midpoint, crank_nicolson, gauss6, radau_iia, lobatto6,
68
72
  EES25, EES27)
69
-
70
73
  from .bseries import BSeries, elementary_differential
71
-
72
- from .trees import EMPTY_TREE, EMPTY_FOREST, EMPTY_ORDERED_FOREST, EMPTY_FOREST_SUM, ZERO_FOREST_SUM
74
+ from .oddeven import id_sqrt, minus, plus
73
75
 
74
76
  import kauri.bck
75
77
  import kauri.cem
@@ -77,8 +79,5 @@ import kauri.gl
77
79
  import kauri.mkw
78
80
  import kauri.nck
79
81
  import kauri.pgl
80
-
81
- from .oddeven import id_sqrt, minus, plus
82
-
83
82
  import kauri.oddeven
84
- import kauri.planar_oddeven
83
+ import kauri.planar_oddeven
@@ -78,15 +78,25 @@ and the symmetric-adjoint method is given by
78
78
  where `kr.sign` is the :class:`Map` sending `t` to `(-1)^|t| * t`.
79
79
 
80
80
  """
81
+ from __future__ import annotations
81
82
  import itertools
82
83
  from functools import cache
83
84
  from typing import Union
84
85
 
85
- import sympy as sp
86
+ try:
87
+ import sympy as sp
88
+ except ImportError:
89
+ sp = None
86
90
 
87
91
  from kauri import Tree, trees_up_to_order, Map
88
92
  from kauri.trees import _is_scalar
89
93
 
94
+ def _require_sympy():
95
+ if sp is None:
96
+ raise ImportError(
97
+ "B-series requires sympy. Install with: pip install kauri[full]"
98
+ )
99
+
90
100
  def _check_f_y(f, y):
91
101
  # Checks that f and y are correctly specified
92
102
 
@@ -179,6 +189,7 @@ def elementary_differential(tree : Tree,
179
189
  t = Tree([[[]],[]])
180
190
  print(elementary_differential(t, f, y))
181
191
  """
192
+ _require_sympy()
182
193
  if not isinstance(tree, Tree):
183
194
  raise TypeError("The argument 'tree' must be of type Tree, not " + str(type(tree)))
184
195
  if tree.colors() > 1:
@@ -227,7 +238,8 @@ class BSeries:
227
238
  print(bs([1], 0.1)) # Evaluate the B-Series at y = [1], h = 0.1
228
239
  """
229
240
 
230
- def __init__(self, y : sp.Matrix, f : sp.Matrix, weights : Map, order : int):
241
+ def __init__(self, y, f, weights : Map, order : int):
242
+ _require_sympy()
231
243
  if not isinstance(weights, Map):
232
244
  raise TypeError("weights must be a Map, not " + str(type(weights)))
233
245
  if not isinstance(order, int):
@@ -17,10 +17,10 @@
17
17
  Functions for generating rooted trees in lexicographic order, based on the algorithms of :cite:`beyer1980constant`.
18
18
  """
19
19
  from typing import Generator
20
- from functools import lru_cache
20
+ from functools import lru_cache, cache
21
21
  from itertools import product
22
22
 
23
- from .trees import Tree
23
+ from .trees import Tree, EMPTY_TREE
24
24
  from .utils import _level_sequence_to_list_repr, _apply_color_sequence
25
25
 
26
26
  def trees_up_to_order(order : int) -> Generator[Tree, None, None]:
@@ -282,3 +282,188 @@ def colored_planar_trees_up_to_order(order: int, d: int):
282
282
  _validate_num_colors(d)
283
283
  for current_order in range(order + 1):
284
284
  yield from colored_planar_trees_of_order(current_order, d)
285
+
286
+
287
+ # ---------------------------------------------------------------------------
288
+ # Colored tree indexing
289
+ # ---------------------------------------------------------------------------
290
+
291
+ @cache
292
+ def _colored_tree_list_cached(max_order: int, d: int) -> tuple:
293
+ """Cached tuple of all colored trees up to max_order with d colors."""
294
+ return tuple(colored_trees_up_to_order(max_order, d))
295
+
296
+
297
+ @cache
298
+ def _colored_tree_lookup_cached(max_order: int, d: int) -> dict:
299
+ """Cached dict mapping Tree -> index."""
300
+ trees = _colored_tree_list_cached(max_order, d)
301
+ return {t: i for i, t in enumerate(trees)}
302
+
303
+
304
+ def colored_trees(d: int, max_order: int) -> list[Tree]:
305
+ """
306
+ Returns all distinct colored rooted trees up to a given order with *d* colors,
307
+ starting with the empty tree.
308
+
309
+ :param d: Number of colors (path dimension).
310
+ :type d: int
311
+ :param max_order: Maximum number of nodes.
312
+ :type max_order: int
313
+ :return: List of colored trees.
314
+ :rtype: list[Tree]
315
+ """
316
+ _validate_num_colors(d)
317
+ return list(_colored_tree_list_cached(max_order, d))
318
+
319
+
320
+ def colored_tree_to_idx(tree: Tree, d: int, max_order: int) -> int:
321
+ """
322
+ Returns the index of a colored tree in the canonical enumeration.
323
+
324
+ Index 0 is the empty tree. Non-empty trees are enumerated by order,
325
+ then by shape, then by coloring.
326
+
327
+ :param tree: A colored rooted tree.
328
+ :type tree: Tree
329
+ :param d: Number of colors (path dimension).
330
+ :type d: int
331
+ :param max_order: Maximum number of nodes.
332
+ :type max_order: int
333
+ :return: Index in the enumeration.
334
+ :rtype: int
335
+ """
336
+ _validate_num_colors(d)
337
+ lookup = _colored_tree_lookup_cached(max_order, d)
338
+ if tree not in lookup:
339
+ raise ValueError(f"Tree {tree} not found in enumeration for d={d}, max_order={max_order}")
340
+ return lookup[tree]
341
+
342
+
343
+ def idx_to_colored_tree(idx: int, d: int, max_order: int) -> Tree:
344
+ """
345
+ Returns the colored tree at a given index in the canonical enumeration.
346
+
347
+ :param idx: Index (0 = empty tree).
348
+ :type idx: int
349
+ :param d: Number of colors (path dimension).
350
+ :type d: int
351
+ :param max_order: Maximum number of nodes.
352
+ :type max_order: int
353
+ :return: The colored tree at the given index.
354
+ :rtype: Tree
355
+ """
356
+ _validate_num_colors(d)
357
+ trees = _colored_tree_list_cached(max_order, d)
358
+ if idx < 0 or idx >= len(trees):
359
+ raise ValueError(f"idx {idx} out of range [0, {len(trees)}) for d={d}, max_order={max_order}")
360
+ return trees[idx]
361
+
362
+
363
+ # ---------------------------------------------------------------------------
364
+ # Recursive tree ordering and canonical-recursive permutation
365
+ #
366
+ # The "recursive" ordering enumerates decorated trees by building them
367
+ # bottom-up from child multisets, cycling root labels innermost. This
368
+ # matches the C++ enumeration in pySigLib's cp_branched_trees.h.
369
+ #
370
+ # The "canonical" ordering (used by colored_trees_of_order etc.) enumerates
371
+ # by shape first, then colorings.
372
+ # ---------------------------------------------------------------------------
373
+
374
+ def _enumerate_child_multisets(target_nodes, min_idx, tree_nodes, total_count):
375
+ """Enumerate multisets of tree indices whose total node count equals target_nodes."""
376
+ if target_nodes == 0:
377
+ yield ()
378
+ return
379
+ for idx in range(min_idx, total_count):
380
+ n = tree_nodes[idx]
381
+ if n > target_nodes:
382
+ break
383
+ for rest in _enumerate_child_multisets(target_nodes - n, idx, tree_nodes, total_count):
384
+ yield (idx,) + rest
385
+
386
+
387
+ @cache
388
+ def _enumerate_trees_recursive(d: int, max_order: int) -> tuple:
389
+ """Enumerate decorated trees in recursive ordering (child-multiset first, root label innermost)."""
390
+ trees = []
391
+ tree_nodes = []
392
+ for order in range(1, max_order + 1):
393
+ if order == 1:
394
+ for label in range(d):
395
+ trees.append((1, label, ()))
396
+ tree_nodes.append(1)
397
+ else:
398
+ current_count = len(trees)
399
+ for children in _enumerate_child_multisets(order - 1, 0, tree_nodes, current_count):
400
+ if not children:
401
+ continue
402
+ for label in range(d):
403
+ trees.append((order, label, children))
404
+ tree_nodes.append(order)
405
+ return tuple(trees)
406
+
407
+
408
+ def _recursive_tree_to_kauri(tree_idx, all_trees):
409
+ """Convert a recursive-order internal tree to a kauri Tree object."""
410
+ from .trees import Forest
411
+ _num_nodes, label, child_ids = all_trees[tree_idx]
412
+ if not child_ids:
413
+ return Tree([label])
414
+ children = [_recursive_tree_to_kauri(c, all_trees) for c in child_ids]
415
+ return Forest(children).join(root_color=label)
416
+
417
+
418
+ @cache
419
+ def canonical_to_recursive_permutation(d: int, max_order: int):
420
+ """
421
+ Compute the permutation mapping canonical tree indices to recursive tree indices.
422
+
423
+ ``perm[i] = j`` means the tree at canonical position ``i`` is at recursive
424
+ position ``j``. Both are 0-indexed and exclude the empty tree.
425
+
426
+ :param d: Number of colors (path dimension).
427
+ :type d: int
428
+ :param max_order: Maximum number of nodes.
429
+ :type max_order: int
430
+ :return: Permutation array of shape ``(num_trees,)``.
431
+ :rtype: numpy.ndarray
432
+ """
433
+ try:
434
+ import numpy as np
435
+ except ImportError:
436
+ raise ImportError("Permutation functions require numpy. Install with: pip install kauri[full]")
437
+ _validate_num_colors(d)
438
+ rec_trees = _enumerate_trees_recursive(d, max_order)
439
+ rec_kauri = [_recursive_tree_to_kauri(i, rec_trees) for i in range(len(rec_trees))]
440
+ rec_lookup = {t: i for i, t in enumerate(rec_kauri)}
441
+
442
+ canonical = _colored_tree_list_cached(max_order, d)
443
+ perm = [rec_lookup[kt] for kt in canonical[1:]]
444
+ return np.array(perm, dtype=np.int64)
445
+
446
+
447
+ @cache
448
+ def recursive_to_canonical_permutation(d: int, max_order: int):
449
+ """
450
+ Compute the permutation mapping recursive tree indices to canonical tree indices.
451
+
452
+ Inverse of :func:`canonical_to_recursive_permutation`.
453
+
454
+ :param d: Number of colors (path dimension).
455
+ :type d: int
456
+ :param max_order: Maximum number of nodes.
457
+ :type max_order: int
458
+ :return: Inverse permutation array of shape ``(num_trees,)``.
459
+ :rtype: numpy.ndarray
460
+ """
461
+ try:
462
+ import numpy as np
463
+ except ImportError:
464
+ raise ImportError("Permutation functions require numpy. Install with: pip install kauri[full]")
465
+ _validate_num_colors(d)
466
+ perm = canonical_to_recursive_permutation(d, max_order)
467
+ inv = np.empty_like(perm)
468
+ inv[perm] = np.arange(len(perm))
469
+ return inv
@@ -16,15 +16,26 @@
16
16
  """
17
17
  Runge-Kutta Schemes
18
18
  """
19
+ from __future__ import annotations
19
20
  import copy
20
21
  from typing import Union, Callable, Tuple
21
22
  import warnings
22
23
 
23
- import numpy as np
24
- import sympy
25
- from scipy.optimize import root
26
- import matplotlib.pyplot as plt
27
- from tqdm import tqdm
24
+ try:
25
+ import numpy as np
26
+ import sympy
27
+ from scipy.optimize import root as _scipy_root
28
+ import matplotlib.pyplot as plt
29
+ from tqdm import tqdm
30
+ except ImportError:
31
+ np = sympy = _scipy_root = plt = tqdm = None
32
+
33
+ def _require_full():
34
+ if np is None:
35
+ raise ImportError(
36
+ "This feature requires additional dependencies. "
37
+ "Install with: pip install kauri[full]"
38
+ )
28
39
 
29
40
  from .gentrees import trees_of_order, planar_trees_of_order
30
41
  from .trees import Tree, Forest, ForestSum, PlanarTree, _is_scalar
@@ -158,6 +169,7 @@ def rk_symbolic_weight(
158
169
  if not isinstance(rationalise, bool):
159
170
  raise TypeError("rationalise must be a bool, not " + str(type(rationalise)))
160
171
 
172
+ _require_full()
161
173
  t_ = t
162
174
  if _is_scalar(t):
163
175
  t_ = t * Tree(None).as_forest_sum()
@@ -214,6 +226,7 @@ def rk_order_cond(
214
226
  print(rk_order_cond(t, 2, a_mask = a_mask, b_mask = b_mask))
215
227
 
216
228
  """
229
+ _require_full()
217
230
  if not isinstance(t, (int, float, TreeLike, ForestLike, ForestSumLike)):
218
231
  raise TypeError("t must be a Tree, Forest, ForestSum (or planar equivalent), int or float, not " + str(type(t)))
219
232
 
@@ -237,9 +250,10 @@ class RK:
237
250
  :param b: The Runge--Kutta parameter vector :math:`b`.
238
251
  """
239
252
  def __init__(self, a, b, name = None):
240
- if not isinstance(a, (list, np.ndarray)):
253
+ _valid_types = (list, np.ndarray) if np is not None else (list,)
254
+ if not isinstance(a, _valid_types):
241
255
  raise TypeError("a must be a list or array, not " + str(type(a)))
242
- if not isinstance(b, (list, np.ndarray)):
256
+ if not isinstance(b, _valid_types):
243
257
  raise TypeError("b must be a list or array, not " + str(type(b)))
244
258
 
245
259
  self.name = name
@@ -343,7 +357,7 @@ class RK:
343
357
 
344
358
  return np.concatenate(G_vec)
345
359
 
346
- sol = root(G, k0, method='hybr', tol=tol, options={'maxfev': max_iter})
360
+ sol = _scipy_root(G, k0, method='hybr', tol=tol, options={'maxfev': max_iter})
347
361
 
348
362
  if not sol.success:
349
363
  warnings.warn(f"Implicit RK solver failed: {sol.message}")
@@ -379,6 +393,7 @@ class RK:
379
393
  :rtype: list | array
380
394
  """
381
395
 
396
+ _require_full()
382
397
  if not isinstance(y0, (list, np.ndarray)):
383
398
  raise TypeError("y0 must be a list or array, not " + str(type(y0)))
384
399
  if not isinstance(t0, float):
@@ -440,6 +455,7 @@ class RK:
440
455
  :return: t_vals, y_vals - the lists of values of t and y respectively
441
456
  :rtype: tuple[list, list]
442
457
  """
458
+ _require_full()
443
459
 
444
460
  if not isinstance(y0, (list, np.ndarray)):
445
461
  raise TypeError("y0 must be a list or array, not " + str(type(y0)))
@@ -37,7 +37,6 @@ from functools import total_ordering
37
37
  from typing import Union
38
38
  import warnings
39
39
 
40
- import sympy
41
40
 
42
41
  from .utils import (_nodes, _height, _factorial, _sigma,
43
42
  _sorted_list_repr, _list_repr_to_level_sequence,
@@ -1431,8 +1430,14 @@ class ForestSum:
1431
1430
  ##############################################
1432
1431
  ##############################################
1433
1432
 
1433
+ try:
1434
+ import sympy as _sympy
1435
+ _SCALAR_TYPES = (numbers.Real, _sympy.Expr)
1436
+ except ImportError:
1437
+ _SCALAR_TYPES = (numbers.Real,)
1438
+
1434
1439
  def _is_scalar(obj):
1435
- return isinstance(obj, (numbers.Real, sympy.Expr))
1440
+ return isinstance(obj, _SCALAR_TYPES)
1436
1441
 
1437
1442
  def _is_tree_or_forest(obj):
1438
1443
  return isinstance(obj, (TreeLike, ForestLike))
@@ -18,7 +18,6 @@ Back-end utility functions
18
18
  """
19
19
  import math
20
20
  from functools import cache
21
- import sympy as sp
22
21
 
23
22
  def _to_list(obj):
24
23
  # Convert a tuple representation to a list representation
@@ -241,6 +240,7 @@ def _next_planar_layout(layout):
241
240
 
242
241
  def _rationalise(c, tol = 1e-10):
243
242
  # rationalised float
243
+ import sympy as sp
244
244
  return str(sp.nsimplify(c, tolerance=tol, rational = True))
245
245
 
246
246
  def _str(c, rationalise = False, tol = 1e-10):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kauri
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: Hopf algebras, B-series, and Runge-Kutta methods on rooted trees
5
5
  Author-email: Daniil Shmelev <daniil.shmelev23@imperial.ac.uk>
6
6
  License: Apache-2.0
@@ -16,14 +16,15 @@ Classifier: Operating System :: Microsoft :: Windows
16
16
  Classifier: Operating System :: Unix
17
17
  Classifier: Programming Language :: Python
18
18
  Classifier: Topic :: Scientific/Engineering :: Mathematics
19
- Requires-Python: >=3.10
19
+ Requires-Python: >=3.9
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
- Requires-Dist: matplotlib>=3.9.2
23
- Requires-Dist: numpy>=1.26.4
24
- Requires-Dist: sympy>=1.12
25
- Requires-Dist: scipy>=1.13
26
- Requires-Dist: tqdm
22
+ Provides-Extra: full
23
+ Requires-Dist: matplotlib>=3.9.2; extra == "full"
24
+ Requires-Dist: numpy>=1.26.4; extra == "full"
25
+ Requires-Dist: sympy>=1.12; extra == "full"
26
+ Requires-Dist: scipy>=1.13; extra == "full"
27
+ Requires-Dist: tqdm; extra == "full"
27
28
  Provides-Extra: dev
28
29
  Requires-Dist: pytest>=8.0; extra == "dev"
29
30
  Requires-Dist: ruff>=0.9.0; extra == "dev"
@@ -51,6 +52,14 @@ Kauri is a Python package for symbolic and algebraic manipulation of rooted tree
51
52
  pip install kauri
52
53
  ```
53
54
 
55
+ The base install is lightweight (pure Python, no external dependencies) and provides tree algebra, enumeration, indexing, and Hopf algebraic operations.
56
+
57
+ For visualization, Runge-Kutta analysis, and B-series (requires matplotlib, numpy, scipy, sympy, tqdm):
58
+
59
+ ```
60
+ pip install kauri[full]
61
+ ```
62
+
54
63
  ## Documentation
55
64
 
56
65
  Full documentation is available at [https://kauri.readthedocs.io](https://kauri.readthedocs.io)
@@ -1,9 +1,11 @@
1
+
2
+ [dev]
3
+ pytest>=8.0
4
+ ruff>=0.9.0
5
+
6
+ [full]
1
7
  matplotlib>=3.9.2
2
8
  numpy>=1.26.4
3
9
  sympy>=1.12
4
10
  scipy>=1.13
5
11
  tqdm
6
-
7
- [dev]
8
- pytest>=8.0
9
- ruff>=0.9.0
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "kauri"
7
- version = "2.0.0"
7
+ version = "2.1.0"
8
8
  description = "Hopf algebras, B-series, and Runge-Kutta methods on rooted trees"
9
9
  readme = "README.md"
10
- requires-python = ">=3.10"
10
+ requires-python = ">=3.9"
11
11
  license = { text = "Apache-2.0" }
12
12
  authors = [
13
13
  { name = "Daniil Shmelev", email = "daniil.shmelev23@imperial.ac.uk" },
@@ -24,19 +24,20 @@ classifiers = [
24
24
  "Programming Language :: Python",
25
25
  "Topic :: Scientific/Engineering :: Mathematics",
26
26
  ]
27
- dependencies = [
28
- "matplotlib>=3.9.2",
29
- "numpy>=1.26.4",
30
- "sympy>=1.12",
31
- "scipy>=1.13",
32
- "tqdm",
33
- ]
27
+ dependencies = []
34
28
 
35
29
  [project.urls]
36
30
  Homepage = "https://github.com/daniil-shmelev/kauri"
37
31
  Documentation = "https://kauri.readthedocs.io"
38
32
 
39
33
  [project.optional-dependencies]
34
+ full = [
35
+ "matplotlib>=3.9.2",
36
+ "numpy>=1.26.4",
37
+ "sympy>=1.12",
38
+ "scipy>=1.13",
39
+ "tqdm",
40
+ ]
40
41
  dev = [
41
42
  "pytest>=8.0",
42
43
  "ruff>=0.9.0",
@@ -46,7 +47,7 @@ dev = [
46
47
  include = ["kauri*"]
47
48
 
48
49
  [tool.ruff]
49
- target-version = "py310"
50
+ target-version = "py39"
50
51
  line-length = 100
51
52
  src = ["kauri", "unit_tests"]
52
53
  exclude = ["build", "docs/_build", ".venv"]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes