utils
General utilities and helper functions that support the core Klotho functionality.
Algorithms
Costs
- klotho.utils.algorithms.costs.cost_matrix(items, cost_function, **kwargs)[source]
Generate a symmetric cost matrix for a collection of items.
Create a numpy array representing pairwise costs between items using a provided cost function. The resulting matrix is symmetric with indices corresponding to item positions in the input list.
- Parameters:
items (List[T]) – List of items to compute pairwise costs for. Items can be of any type that the cost function can handle.
cost_function (Callable[[T, T], float]) – Function that takes two items and returns a numeric cost value. Should be symmetric (cost(a, b) == cost(b, a)) for best results.
**kwargs (Any) – Additional keyword arguments to pass to the cost function.
- Returns:
A tuple containing:
Symmetric cost matrix as numpy array where entry (i, j) contains cost_function(items[i], items[j])
List of items in the same order as matrix indices
- Return type:
Tuple[numpy.ndarray, List[T]]
Examples
Create a distance matrix for 2D points:
>>> import math >>> points = [(0, 0), (1, 1), (2, 0)] >>> def euclidean_distance(p1, p2): ... return math.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2) >>> matrix, item_list = cost_matrix(points, euclidean_distance) >>> print(matrix[0, 1]) # Distance from (0,0) to (1,1) 1.4142135623730951
Factors
- klotho.utils.algorithms.factors.to_factors(value)[source]
Convert a numeric value to its prime factorization representation.
Decompose a rational number into its prime factors, returning a dictionary mapping prime numbers to their exponents. Negative exponents represent factors in the denominator.
- Parameters:
value (int, Fraction, or str) – The value to factorize. Can be an integer, Fraction object, or string representation of a fraction (e.g., ‘3/2’).
- Returns:
Dictionary mapping prime numbers to their exponents. Positive exponents represent factors in the numerator, negative exponents represent factors in the denominator.
- Return type:
- Raises:
TypeError – If input type is not supported.
Examples
Factor an integer:
>>> to_factors(12) {2: 2, 3: 1}
Factor a fraction:
>>> to_factors(Fraction(3, 2)) {2: -1, 3: 1}
Factor from string representation:
>>> to_factors('5/4') {2: -2, 5: 1}
- klotho.utils.algorithms.factors.from_factors(factors)[source]
Reconstruct a fraction from its prime factorization.
Convert a dictionary of prime factors back to a Fraction object. Positive exponents contribute to the numerator, negative exponents contribute to the denominator.
- Parameters:
factors (Dict[int, int]) – Dictionary mapping prime numbers to their exponents. Positive exponents represent factors in the numerator, negative exponents represent factors in the denominator.
- Returns:
The reconstructed fraction from the prime factorization.
- Return type:
Fraction
Examples
Reconstruct from prime factors:
>>> from_factors({2: 2, 3: 1}) Fraction(12, 1)
Handle negative exponents:
>>> from_factors({2: -1, 3: 1}) Fraction(3, 2)
Empty factorization returns 1:
>>> from_factors({}) Fraction(1, 1)
- klotho.utils.algorithms.factors.nth_prime(prime)[source]
Find the index (position) of a prime number in the sequence of primes.
Determine which position a given prime number occupies in the ordered sequence of all prime numbers (2 is 1st, 3 is 2nd, 5 is 3rd, etc.).
- Parameters:
prime (int) – The prime number to find the index for. Must be a valid prime number.
- Returns:
The 1-based index of the prime in the sequence of all primes.
- Return type:
- Raises:
ValueError – If the input number is not prime.
Examples
Find index of small primes:
>>> nth_prime(2) 1
>>> nth_prime(3) 2
>>> nth_prime(7) 4
>>> nth_prime(11) 5
- klotho.utils.algorithms.factors.factors_to_lattice_vector(factors, vector_size=None)[source]
Convert a prime-factor dictionary to a prime-coordinate vector.
Transform a dictionary of prime factors into a vector in prime basis space. This is efficient when factors are already available and factorization work can be skipped.
- Parameters:
- Returns:
Immutable vector of prime exponents with optional zero-padding. Position
icorresponds to the ``(i+1)``th prime.- Return type:
- Raises:
ValueError – If
vector_sizeis too small to represent the highest prime.
Examples
Convert factors to minimal vector:
>>> factors = {2: 1, 3: 1, 5: -1} >>> factors_to_lattice_vector(factors) array([ 1, 1, -1])
Convert with padding:
>>> factors_to_lattice_vector(factors, vector_size=5) array([ 1, 1, -1, 0, 0])
Notes
Returned arrays are immutable.
- klotho.utils.algorithms.factors.ratio_to_coordinate(ratio, vector_size=None, generators=None, basis_primes=None)[source]
Convert a ratio to a coordinate vector.
Behavior depends on
generators:If
generatorsisNone, return prime coordinates (monzo-style) in the canonical prime basis.If
generatorsis provided, solve for integer generator coordinates usingbasis_primesand a linear Diophantine system.
- Parameters:
vector_size (int, optional) – Output length. If larger than required, result is zero-padded. If smaller, raises
ValueError.generators (sequence[int | Fraction | str], optional) – Generator basis used for coordinates. Floats are not accepted.
basis_primes (sequence[int], optional) – Prime basis used to express generator monzos. If omitted, inferred from the generator factorizations.
- Returns:
Immutable integer coordinate vector.
- Return type:
- Raises:
TypeError – If generator values include floats.
ValueError – If the basis is invalid, if representation is not unique/integer, or if
vector_sizeis inconsistent.
- klotho.utils.algorithms.factors.ratios_to_coordinates(ratios, vector_size=None, generators=None, basis_primes=None)[source]
Convert multiple ratios to coordinate vectors.
This is the batch companion to
ratio_to_coordinateand preserves input order in the returned list.- Parameters:
ratios (sequence[int | Fraction | str]) – Ratios to convert.
vector_size (int, optional) – Target output length for each coordinate vector.
generators (sequence[int | Fraction | str], optional) – Generator basis for conversion. If omitted, prime coordinates are used.
basis_primes (sequence[int], optional) – Prime basis used with
generators.
- Returns:
Immutable coordinate vectors aligned with input order.
- Return type:
Graphs
- klotho.utils.algorithms.graphs.minimum_cost_path(G, traversal_func=None, **kwargs)[source]
Find a minimum cost path through a weighted graph using flexible traversal functions.
Delegates to the specified traversal function, providing sensible defaults for missing required parameters. The default traversal function is greedy_tsp which solves the traveling salesman problem.
- Parameters:
G (Graph) – Weighted graph with numeric edge weights representing costs.
traversal_func (Callable, optional) – Function to use for graph traversal. Receives the graph as first argument and all other parameters via kwargs. Defaults to greedy_tsp.
**kwargs – All parameters for the traversal function. If using the default greedy_tsp and ‘source’ is not provided, defaults to the first node.
- Returns:
Ordered list of node indices representing the path found by the traversal function.
- Return type:
List[int]
- Raises:
ValueError – If required nodes are not in the graph or if no valid path exists.
Examples
Use default greedy TSP with automatic source:
>>> from klotho.topos.graphs import Graph >>> G = Graph.digraph() >>> G.add_edge(0, 1, weight=1) >>> G.add_edge(1, 2, weight=2) >>> G.add_edge(0, 2, weight=4) >>> path = minimum_cost_path(G) # Uses first node as source
Use default greedy TSP with specified source:
>>> path = minimum_cost_path(G, source=0)
Use custom traversal function:
>>> def custom_dfs(graph, source, depth_limit=None): ... return list(graph.descendants(source))[:depth_limit] >>> path = minimum_cost_path(G, traversal_func=custom_dfs, source=0, depth_limit=2)
Notes
This function provides maximum flexibility by accepting any traversal function and passing all parameters via kwargs. When no traversal function is specified, it uses a greedy TSP algorithm which finds an approximate solution to the traveling salesman problem.
- klotho.utils.algorithms.graphs.greedy_random_walk(G, source, steps=10, weight='weight', target=None, **kwargs)[source]
Perform a greedy walk choosing minimum weight edges with random tie-breaking.
- Parameters:
G (Graph) – The graph to walk on
source (node) – Starting node
steps (int, optional) – Maximum number of steps to take (default: 10)
weight (str, optional) – Edge attribute to use for decision making (default: ‘weight’)
target (node, optional) – If provided, stop early when target is reached
**kwargs – Additional parameters (ignored)
- Returns:
Path as list of nodes visited
- Return type:
List[int]
- klotho.utils.algorithms.graphs.probabilistic_random_walk(G, source, steps=10, weight='weight', target=None, inverse_weights=True, **kwargs)[source]
Perform a probabilistic walk where lower weights have higher probability.
- Parameters:
G (Graph) – The graph to walk on
source (node) – Starting node
steps (int, optional) – Maximum number of steps to take (default: 10)
weight (str, optional) – Edge attribute to use for decision making (default: ‘weight’)
target (node, optional) – If provided, stop early when target is reached
inverse_weights (bool, optional) – If True, lower weights get higher probability (default: True)
**kwargs – Additional parameters (ignored)
- Returns:
Path as list of nodes visited
- Return type:
List[int]
- klotho.utils.algorithms.graphs.deterministic_greedy_walk(G, source, steps=10, weight='weight', target=None, **kwargs)[source]
Always choose the neighbor with minimum weight (deterministic, no randomness).
- Parameters:
G (Graph) – The graph to walk on
source (node) – Starting node
steps (int, optional) – Maximum number of steps to take (default: 10)
weight (str, optional) – Edge attribute to use for decision making (default: ‘weight’)
target (node, optional) – If provided, stop early when target is reached
**kwargs – Additional parameters (ignored)
- Returns:
Path as list of nodes visited
- Return type:
List[int]
- klotho.utils.algorithms.graphs.prim_order_traversal(G, source, weight='weight', **kwargs)[source]
Visit all nodes in the order they would be added by Prim’s MST algorithm.
This guarantees visiting every node while prioritizing edges with lower weights. Follows the minimum spanning tree construction order.
- klotho.utils.algorithms.graphs.greedy_nearest_unvisited(G, source, weight='weight', **kwargs)[source]
Visit all nodes by always moving to the nearest unvisited neighbor.
At each step, chooses the unvisited neighbor with the minimum edge weight. If no unvisited neighbors exist, backtracks or jumps to nearest unvisited node.
- klotho.utils.algorithms.graphs.dijkstra_order_traversal(G, source, weight='weight', **kwargs)[source]
Visit all nodes in order of their distance from source (Dijkstra-style).
Visits nodes in order of increasing shortest path distance from the source, guaranteeing that all reachable nodes are visited.
Lists
- klotho.utils.algorithms.lists.normalize_sum(data)[source]
Normalize values in a collection so their sum equals 1.
Scale all values proportionally so that their sum equals 1.0 while preserving their relative proportions and original data type.
- Parameters:
data (list, tuple, or numpy.ndarray) – Collection of numeric values to normalize. Can contain integers, floats, Fractions, Decimals, or other numeric types.
- Returns:
Collection of the same type as input with values scaled so that their sum equals 1.0. If input sum is zero, returns collection of zeros with same shape and type.
- Return type:
list, tuple, or numpy.ndarray
- Raises:
TypeError – If input is not a list, tuple, or numpy array.
Examples
Normalize a list of integers:
>>> normalize_sum([1, 2, 3, 4]) [0.1, 0.2, 0.3, 0.4]
Normalize a tuple of floats:
>>> normalize_sum((1.5, 2.5, 1.0)) (0.3, 0.5, 0.2)
Handle zero sum case:
>>> normalize_sum([0, 0, 0]) [0, 0, 0]
- klotho.utils.algorithms.lists.invert(data)[source]
Invert the proportional ordering of values in a collection.
Reorder values so that the largest becomes the smallest, the smallest becomes the largest, etc., while preserving positions and exact types. The ranking is inverted but all original values are preserved.
- Parameters:
data (list, tuple, or numpy.ndarray) – Collection of numeric values to invert. Values can be any comparable numeric type (int, float, Fraction, etc.).
- Returns:
Collection of the same type as input with values reordered so that proportional relationships are inverted. Original value types are preserved exactly.
- Return type:
list, tuple, or numpy.ndarray
- Raises:
TypeError – If input is not a list, tuple, or numpy array.
Examples
Basic inversion example:
>>> invert([5, 1, 3]) [1, 5, 3]
Four element example:
>>> invert([0, 5, 1, 4]) [5, 0, 4, 1]
Handle duplicate values:
>>> invert([1, 3, 1, 2, 3]) [3, 1, 3, 2, 1]
Single element remains unchanged:
>>> invert([42]) [42]
Random
- klotho.utils.algorithms.random.diverse_sample(elements, num_samples, subset_size, **kwargs)[source]
Generate diverse subsets from a master list using greedy algorithms.
Creates multiple subsets from a master list where each subset maximizes diversity relative to previously selected subsets. Uses diversipy’s greedy maximin algorithm for optimal distribution.
- Parameters:
elements (list) – Master list of elements to sample from.
num_samples (int) – Number of diverse subsets to generate.
subset_size (int or tuple of int) – Size of each subset. If tuple (min, max), randomly selects size within range for each subset.
**kwargs – Additional configuration parameters passed to subset generation.
- Returns:
Collection of diverse subsets, each containing elements from the master list.
- Return type:
- Raises:
ValueError – If num_samples or subset_size parameters are invalid.
ImportError – If diversipy library is not available.
Examples
Generate diverse subsets with fixed size:
>>> elements = ['A', 'B', 'C', 'D', 'E', 'F'] >>> subsets = diverse_sample(elements, 3, 2) >>> len(subsets) 3
Generate subsets with variable sizes:
>>> subsets = diverse_sample(elements, 2, (2, 4)) >>> all(2 <= len(subset) <= 4 for subset in subsets) True
Ratios
- klotho.utils.algorithms.ratios.is_superparticular(ratio)[source]
Check if a ratio is superparticular.
A superparticular ratio has the form (n+1)/n where n is a positive integer. Examples: 2/1, 3/2, 4/3, 5/4, 6/5, 9/8, 10/9, etc.
- Parameters:
ratio (int, float, Fraction, or str) – The ratio to check. Can be an integer, Fraction object, or string representation of a fraction (e.g., ‘3/2’).
- Returns:
True if the ratio is superparticular, False otherwise.
- Return type:
Examples
>>> is_superparticular('3/2') True
>>> is_superparticular(Fraction(5, 4)) True
>>> is_superparticular('5/3') False
>>> is_superparticular('8/9') # Subparticular (inverse) True
- klotho.utils.algorithms.ratios.superparticular_base(ratio)[source]
Get the base n of a superparticular ratio.
For a superparticular ratio (n+1)/n, returns n. For a subparticular ratio n/(n+1), also returns n.
- Parameters:
ratio (int, float, Fraction, or str) – The ratio to analyze. Should be superparticular.
- Returns:
The base n of the superparticular ratio.
- Return type:
Examples
>>> superparticular_base('3/2') 2
>>> superparticular_base('5/4') 4
>>> superparticular_base('9/8') 8
>>> superparticular_base('2/3') # Subparticular 2
Notes
This function does not validate that the input is superparticular. For non-superparticular ratios, returns the smaller of numerator and denominator, which may not be meaningful.
- klotho.utils.algorithms.ratios.validate_primes(primes)[source]
Validate and normalize a list of prime numbers.
Ensures all values in the list are unique prime integers.
- Parameters:
primes (List[int or float]) – List of values to validate as primes.
- Returns:
List of validated prime integers.
- Return type:
List[int]
- Raises:
ValueError – If any value is not prime or if there are duplicates.
Examples
>>> validate_primes([2, 3, 5]) [2, 3, 5]
>>> validate_primes([2.0, 3.0, 5.0]) [2, 3, 5]
>>> validate_primes([2, 3, 4]) # 4 is not prime Traceback (most recent call last): ... ValueError: all entries in primes must be prime
>>> validate_primes([2, 3, 3]) # duplicate Traceback (most recent call last): ... ValueError: primes must be unique
Basis
- klotho.utils.algorithms.basis.monzo_from_ratio(ratio, primes)[source]
Convert a ratio to its monzo (prime exponent vector) representation.
A monzo is a vector of prime exponents that represents a ratio. For primes [p1, p2, …, pn] and ratio r = p1^e1 * p2^e2 * … * pn^en, the monzo is [e1, e2, …, en].
- Parameters:
- Returns:
Column vector of prime exponents.
- Return type:
sympy.Matrix
Examples
>>> monzo_from_ratio('3/2', [2, 3, 5]) Matrix([ [-1], [ 1], [ 0]])
>>> monzo_from_ratio('5/4', [2, 3, 5]) Matrix([ [-2], [ 0], [ 1]])
- klotho.utils.algorithms.basis.ratio_from_monzo(monzo, primes)[source]
Convert a monzo (prime exponent vector) back to a ratio.
- Parameters:
- Returns:
The ratio represented by the monzo.
- Return type:
Fraction
Examples
>>> ratio_from_monzo([-1, 1, 0], [2, 3, 5]) Fraction(3, 2)
>>> ratio_from_monzo([-2, 0, 1], [2, 3, 5]) Fraction(5, 4)
- klotho.utils.algorithms.basis.basis_matrix(primes, generators)[source]
Construct the change-of-basis matrix from generator monzos.
The columns of the matrix are the monzos (prime exponent vectors) of the generators.
- Parameters:
primes (List[int]) – Ordered list of prime numbers defining the prime basis.
generators (List[Fraction-like]) – List of generator ratios. Length must equal len(primes).
- Returns:
Square matrix where column i is the monzo of generator i.
- Return type:
sympy.Matrix
Examples
>>> A = basis_matrix([2, 3, 5], ['2/1', '5/4', '6/5']) >>> A Matrix([ [ 1, -2, 1], [ 0, 0, 1], [ 0, 1, -1]])
- klotho.utils.algorithms.basis.is_unimodular(matrix)[source]
Check if an integer matrix is unimodular (det = +/-1).
A unimodular matrix has an integer inverse, meaning it represents a valid change of basis that spans the same lattice.
- Parameters:
matrix (sympy.Matrix) – Square integer matrix to check.
- Returns:
True if the matrix is unimodular (det = +/-1), False otherwise.
- Return type:
Examples
>>> A = basis_matrix([2, 3, 5], ['2/1', '5/4', '6/5']) >>> is_unimodular(A) True
>>> A = basis_matrix([2, 3, 5], ['2/1', '3/2', '5/4']) >>> is_unimodular(A) False
- klotho.utils.algorithms.basis.change_of_basis(primes, generators)[source]
Compute the change-of-basis matrices for a generator set.
Given a set of generators, returns both the forward transformation matrix A and its inverse A^-1 for converting between prime coordinates and generator coordinates.
- Parameters:
primes (List[int]) – Ordered list of prime numbers defining the prime basis.
generators (List[Fraction-like]) – List of generator ratios. Must form a unimodular basis.
- Returns:
(A, A_inv) where: - A converts generator coords to prime coords: x = A @ y - A_inv converts prime coords to generator coords: y = A_inv @ x
- Return type:
Tuple[sympy.Matrix, sympy.Matrix]
- Raises:
ValueError – If the generators do not form a unimodular basis.
Examples
>>> primes = [2, 3, 5] >>> generators = ['2/1', '5/4', '6/5'] >>> A, A_inv = change_of_basis(primes, generators) >>> is_unimodular(A) True
- klotho.utils.algorithms.basis.prime_to_generator_coords(prime_coords, A_inv)[source]
Convert prime coordinates to generator coordinates.
Given a ratio expressed in prime coordinates (monzo), compute its representation in the generator basis.
- Parameters:
prime_coords (List[int] or sympy.Matrix) – Prime exponent vector (monzo).
A_inv (sympy.Matrix) – Inverse of the basis matrix (from change_of_basis).
- Returns:
Generator exponent vector.
- Return type:
List[int]
- Raises:
ValueError – If the result contains non-integer values (shouldn’t happen for valid unimodular bases).
Examples
>>> A, A_inv = change_of_basis([2, 3, 5], ['2/1', '5/4', '6/5']) >>> prime_to_generator_coords([-2, 0, 1], A_inv) # monzo of 5/4 [0, 1, 0]
- klotho.utils.algorithms.basis.generator_to_prime_coords(gen_coords, A)[source]
Convert generator coordinates to prime coordinates.
Given a ratio expressed in the generator basis, compute its representation in prime coordinates (monzo).
- Parameters:
gen_coords (List[int] or sympy.Matrix) – Generator exponent vector.
A (sympy.Matrix) – The basis matrix (from change_of_basis).
- Returns:
Prime exponent vector (monzo).
- Return type:
List[int]
- Raises:
ValueError – If the result contains non-integer values (shouldn’t happen for integer inputs).
Examples
>>> A, A_inv = change_of_basis([2, 3, 5], ['2/1', '5/4', '6/5']) >>> generator_to_prime_coords([0, 1, 0], A) # 5/4 in generator coords [-2, 0, 1]
- klotho.utils.algorithms.basis.ratio_from_prime_coords(primes, prime_coords)[source]
Compute a ratio from its prime coordinates (monzo).
This is an alias for ratio_from_monzo for clarity in coordinate conversion contexts.
- Parameters:
- Returns:
The ratio represented by the coordinates.
- Return type:
Fraction
Examples
>>> ratio_from_prime_coords([2, 3, 5], [-2, 0, 1]) Fraction(5, 4)
- klotho.utils.algorithms.basis.ratio_from_generator_coords(generators, gen_coords)[source]
Compute a ratio from its generator coordinates.
- Parameters:
generators (List[Fraction-like]) – The generator ratios defining the basis.
gen_coords (List[int] or sympy.Matrix) – Generator exponent vector.
- Returns:
The ratio represented by the coordinates.
- Return type:
Fraction
Examples
>>> generators = ['2/1', '5/4', '6/5'] >>> ratio_from_generator_coords(generators, [0, 1, 1]) Fraction(3, 2)
Data Structures
Dictionaries
- class klotho.utils.data_structures.dictionaries.SafeDict(*args, aliases=None, **kwargs)[source]
Bases:
dictA dictionary with a fixed set of keys that cannot be added to or removed from.
Values for existing keys can be updated, but new keys cannot be inserted and existing keys cannot be deleted. Supports key aliases that resolve to canonical keys.
- Parameters:
*args – Positional arguments passed to
dict.aliases (dict, optional) – Mapping of alias keys to canonical keys. Each alias must resolve to a key present in the initial data.
**kwargs – Keyword arguments passed to
dict.
- Raises:
KeyError – If any alias target is not a key in the initial data.
Examples
>>> d = SafeDict({'a': 1, 'b': 2}, aliases={'x': 'a'}) >>> d['x'] 1 >>> d['a'] = 10 >>> d['a'] 10
- __getitem__(key)[source]
Retrieve the value for key, resolving aliases.
- Parameters:
key (hashable) – The key or alias to look up.
- Returns:
The associated value, or
Noneif the resolved key is not present.- Return type:
object or None
- __setitem__(key, value)[source]
Set value for key only if the resolved key is in the allowed set.
- Parameters:
key (hashable) – The key or alias to set.
value (object) – The value to assign.
- __delitem__(key)[source]
Prevent deletion of keys.
- Parameters:
key (hashable) – The key to delete.
- Raises:
KeyError – Always raised; keys cannot be removed from a
SafeDict.
- clear()[source]
Prevent clearing all entries.
- Raises:
KeyError – Always raised when the dictionary has allowed keys.
- popitem()[source]
Prevent popping an arbitrary item.
- Raises:
KeyError – Always raised; keys are fixed.
Enums
- class klotho.utils.data_structures.enums.DirectValueEnumMeta(cls, bases, classdict, *, boundary=None, _simple=False, **kwds)[source]
Bases:
EnumTypeMetaclass that returns member values directly on attribute access.
When accessing a member through the class, the raw value is returned instead of the enum member instance.
- class klotho.utils.data_structures.enums.MinMaxEnum(value)[source]
Bases:
EnumEnum whose members store
(min, max)tuples and support arithmetic.Provides
minandmaxproperties for convenient access, scalar multiplication, and callable random sampling from the uniform distribution over the range.Examples
>>> class Dynamics(MinMaxEnum): ... pp = (0.1, 0.3) ... ff = (0.7, 0.9) >>> Dynamics.pp.min 0.1 >>> Dynamics.pp.max 0.3
Node Mapping
Node Identity Mapping System for NetworkX to RustworkX Migration
This module provides a bidirectional mapping system between arbitrary Python objects and integer indices required by RustworkX. It maintains object identity while providing O(1) lookup performance in both directions.
- class klotho.utils.data_structures.node_mapping.NodeIdentityMapper[source]
Bases:
objectBidirectional mapping system between arbitrary node objects and integer indices.
This class maintains the relationship between user-provided node objects and RustworkX’s integer indices, ensuring consistent mapping across all graph operations.
Features:
O(1) lookup performance in both directions
Handles non-hashable objects using object identity
Automatic cleanup of removed nodes
Thread-safe operations
Memory-efficient using weak references where possible
- add_node(node_obj)[source]
Add a node object and return its index.
If the node already exists, returns its existing index. Otherwise, creates a new mapping and returns the new index.
- Parameters:
node_obj (object) – The node object to add (can be any Python object).
- Returns:
The integer index assigned to this node.
- Return type:
- Raises:
ValueError – If node_obj is
None(reserved value).
- get_all_objects()[source]
Get all mapped node objects.
- Returns:
All node objects currently mapped.
- Return type:
- num_nodes()[source]
Get the number of mapped nodes.
- Returns:
Number of nodes currently mapped.
- Return type:
Playback
Player
- klotho.utils.playback.player.play(obj, engine=None, custom_js_path=None, custom_js=None, **kwargs)[source]
Play a musical object in a Jupyter notebook.
Converts the given object to audio events and renders an interactive playback widget via the selected audio engine.
If obj is a
KlothoPlot(returned byplot()), delegates to its.play()method to display an animated figure with audio.If obj is a
Score, the universal dispatcher lowers its items viaklotho.utils.playback.supersonic.converters.convert_score_to_sc_events()and renders a SuperSonic widget with track / FX metadata and control-envelope buses.- Parameters:
obj (object) – A Klotho musical object (e.g.
Pitch,Chord,Scale,RhythmTree,TemporalUnit,CompositionalUnit), aScore, or aKlothoPlotreturned byplot().engine (str or None, optional) – Audio engine to use:
'tone'(Tone.js) or'supersonic'(SuperSonic / browser scsynth). WhenNone, uses the global default set byset_audio_engine()(initially'supersonic').custom_js_path (str or Path, optional) – Path to a custom JavaScript file to load in the widget (Tone.js engine only).
custom_js (str, optional) – Inline custom JavaScript source to embed in the widget (Tone.js engine only).
**kwargs – Forwarded to the converter (e.g.
dur,arp,strum,mode). ForScore,ring_timeis supported.
- Returns:
The displayed HTML widget handle, or the KlothoPlot after triggering its animation.
- Return type:
IPython.display.DisplayHandle or KlothoPlot
MIDI Player
- klotho.utils.playback.midi_player.play_midi(obj, dur=None, arp=False, prgm=0, max_channels=128, max_polyphony=None, soundfont_path=None, bend_sensitivity_semitones=12, debug=False, **kwargs)[source]
Play a musical object as MIDI audio in Jupyter/Colab notebooks.
Automatically detects the environment and uses appropriate MIDI synthesis: - Google Colab: Uses FluidSynth CLI with multi-channel support - Local Jupyter: Uses FluidSynth if available - Fallback: Returns MIDI file for download
- Parameters:
obj (RhythmTree, TemporalUnit, CompositionalUnit, TemporalUnitSequence, TemporalBlock,) – PitchCollection, Scale, Chord, Voicing, or ChordSequence The musical object to play. Different object types have different playback behaviors: - RhythmTree/TemporalUnit: Rhythmic playback with default pitch - PitchCollection: Sequential pitch playback - Scale: Ascending then descending playback - Chord/Voicing: Block chord or arpeggiated playback - ChordSequence: Sequential playback of chords
dur (float, optional) – Duration in seconds. Defaults depend on object type: - PitchCollection/Scale: 0.5 seconds per note - Chord/Voicing: 3.0 seconds total (or per note if arpeggiated) - ChordSequence: 3.0 seconds per chord
arp (bool, optional) – For chords only: if True, arpeggiate the chord (default False)
prgm (int, optional) – MIDI program number (0-127) for instrument sound (default 0 = Acoustic Grand Piano)
max_channels (int, optional) – Maximum number of MIDI channels to allocate across all ports (default 128) Supports up to 256 channels across 16 ports
max_polyphony (int, optional) – Maximum polyphony for synthesis (default equals max_channels)
soundfont_path (str, optional) – Path to custom soundfont file (uses system default if None)
bend_sensitivity_semitones (int, optional) – Pitch bend range in semitones (default 12, supports 12 or 24)
debug (bool, optional) – Enable debug logging for channel allocation (default False)
**kwargs – Additional arguments passed to MIDI creation functions
- Returns:
Audio widget for playback in Jupyter notebooks, or file link if audio synthesis is unavailable
- Return type:
IPython.display.Audio or IPython.display.FileLink
Notes
For Google Colab, FluidSynth is automatically installed if needed. For local environments, install FluidSynth for best results. The new backend supports true independence for up to 256 simultaneous voices with proper microtonal pitch bend and dynamic drum channel allocation.
- klotho.utils.playback.midi_player.create_midi(obj, dur=None, arp=False, prgm=0, max_channels=128, max_polyphony=None, bend_sensitivity_semitones=12, debug=False, **kwargs)[source]
Create a MIDI file from a musical object without audio synthesis.
This function creates the exact same MIDI file as play_midi() but returns the MidiFile object directly instead of converting to audio.
- Parameters:
obj (RhythmTree, TemporalUnit, CompositionalUnit, TemporalUnitSequence, TemporalBlock,) – PitchCollection, Scale, Chord, Voicing, or ChordSequence The musical object to convert to MIDI. Same as play_midi().
dur (float, optional) – Duration in seconds. Same as play_midi().
arp (bool, optional) – For chords only: if True, arpeggiate the chord. Same as play_midi().
prgm (int, optional) – MIDI program number (0-127) for instrument sound. Same as play_midi().
max_channels (int, optional) – Maximum number of MIDI channels to allocate. Same as play_midi().
max_polyphony (int, optional) – Unused in MIDI creation, kept for API compatibility.
bend_sensitivity_semitones (int, optional) – Pitch bend range in semitones. Same as play_midi().
debug (bool, optional) – Enable debug logging. Same as play_midi().
**kwargs – Additional arguments. Same as play_midi().
- Returns:
The MIDI file object that can be saved with midi_file.save(‘filename.mid’)
- Return type:
MidiFile
Examples
>>> from klotho.chronos.rhythm_trees.rhythm_tree import RhythmTree >>> rt = RhythmTree([1, [1, 1], 1]) >>> midi_file = create_midi(rt) >>> midi_file.save('my_rhythm.mid')
- class klotho.utils.playback.midi_player.MultiPortMidiWriter(max_voices=128, ticks_per_beat=480)[source]
Bases:
objectMulti-port MIDI file writer that supports up to 256 channels across 16 ports.
Creates Type 1 MIDI files with: - Track 0: Global meta events (tempo, time signature) - Track 1-N: One track per port with midi_port meta message
- add_event(port, event_time, message)[source]
Add a MIDI event to the specified port’s event bucket.
- class klotho.utils.playback.midi_player.ChannelAllocator(num_ports, bend_sensitivity_semitones=12)[source]
Bases:
objectPer-note channel allocator that manages (port, channel) assignment for voices.
Maintains separate pools for melodic and drum channels per port. Ensures no channel conflicts during concurrent note playback.
- allocate_voice(voice_id, is_drum=False, program=0)[source]
Allocate a (port, channel) for a new voice.
- klotho.utils.playback.midi_player.debug_voice_allocation()[source]
Debug voice allocation to understand piano fallback issue.
- klotho.utils.playback.midi_player.test_midi_backend()[source]
Run all MIDI backend tests.
- Returns:
True if all tests pass
- Return type:
- klotho.utils.playback.midi_player.debug_play_midi(obj, debug=True, **kwargs)[source]
Debug version of play_midi that shows detailed channel allocation and MIDI events.
- klotho.utils.playback.midi_player.compare_midi_files(obj, **kwargs)[source]
Compare MIDI files generated by play_midi() vs create_midi() to debug differences.
This function creates MIDI files using both methods and compares their contents to help identify any discrepancies.
- Parameters:
obj (musical object) – The object to test with both functions
**kwargs – Arguments passed to both functions
- Returns:
Comparison results with detailed information
- Return type:
- klotho.utils.playback.midi_player.debug_temporal_unit_chronons(temporal_unit)[source]
Debug function to examine chronon timing values in a TemporalUnit.
This helps identify why some notes might be very short in MIDI files.
- Parameters:
temporal_unit (TemporalUnit) – The temporal unit to examine
- Returns:
Information about each chronon
- Return type:
- klotho.utils.playback.midi_player.test_channel_allocation_system()[source]
Test the channel allocation system to verify it meets the requirements.
Requirements: 1. Exhaust ALL channels on a port before moving to the next port 2. Support up to 256 channels (16 ports × 16 channels) 3. Melodic instruments skip channel 9 on any port 4. Drum instruments use ONLY channel 9 on any port 5. Only loop back after exhausting all 256 channels
Tone.js Engine
- class klotho.utils.playback.tonejs.engine.ToneEngine(events, custom_js_path=None, custom_js=None)[source]
Bases:
objectJupyter widget that renders Tone.js-based audio playback controls.
Generates an HTML/JS snippet with play/stop and loop buttons, wiring the provided event list through Tone.js instruments.
- Parameters:
Tone.js Converters
- klotho.utils.playback.tonejs.converters.compositional_unit_to_events(obj, extra_pfields=None, animation=False)[source]
- klotho.utils.playback.tonejs.converters.compositional_unit_to_animation_events(obj, extra_pfields=None)[source]
- klotho.utils.playback.tonejs.converters.pitch_to_events(pitch, duration=None, amp=None, extra_pfields=None)[source]
- klotho.utils.playback.tonejs.converters.pitch_collection_to_events(obj, duration=None, mode='seq', arp=False, strum=0, direction='u', amp=None, pause=0.0, extra_pfields=None)[source]
- klotho.utils.playback.tonejs.converters.scale_to_events(obj, duration=None, equaves=1, amp=None, pause=0.0, extra_pfields=None)[source]
- klotho.utils.playback.tonejs.converters.chord_to_events(obj, duration=None, arp=False, strum=0, direction='u', amp=None, extra_pfields=None)[source]
- klotho.utils.playback.tonejs.converters.chord_sequence_to_events(obj, duration=None, arp=False, strum=0, direction='u', amp=None, pause=0.25, extra_pfields=None)[source]
- klotho.utils.playback.tonejs.converters.spectrum_to_events(obj, duration=None, arp=False, strum=0, direction='u', amp=None, extra_pfields=None)[source]
- klotho.utils.playback.tonejs.converters.temporal_unit_to_events(obj, use_absolute_time=False, amp=None, extra_pfields=None, animation=False)[source]
- klotho.utils.playback.tonejs.converters.temporal_unit_to_animation_events(obj, use_absolute_time=False, amp=None, extra_pfields=None)[source]
- klotho.utils.playback.tonejs.converters.rhythm_tree_to_events(obj, beat=None, bpm=None, amp=None, extra_pfields=None)[source]
- klotho.utils.playback.tonejs.converters.rhythm_tree_to_animation_events(obj, beat=None, bpm=None, amp=None, extra_pfields=None)[source]
- klotho.utils.playback.tonejs.converters.temporal_sequence_to_events(obj, extra_pfields=None, rebase_to_zero=True)[source]