StateIndexer

phasic.StateIndexer(
    *slot_names,
    property_sets=None,
    slots=None,
    **named_property_lists,
)

Manages multiple independent property sets and slots with attribute access.

StateIndexer allows organizing state variables into named PropertySets and Slots, where each PropertySet defines an independent combinatorial space and each Slot occupies a single index. This is useful for separating primary state features (e.g., lineage properties) from auxiliary metadata (e.g., timing, identifiers).

Parameters

*slot_names : str = ()

Positional slot names (each occupies exactly 1 index). Accessible as attributes returning int indices.

property_sets : list[PropertySet] | list[Property] | None = None

Either a list of PropertySet objects or a list of Property objects (which creates a single ‘default’ PropertySet for backward compatibility)

slots : list[str] | None = None

List of slot names or Slot objects (backward compatibility)

****named_property_lists** : list[Property] = {}

Keyword arguments mapping PropertySet names to lists of Property objects

Attributes

state_length : int

Total states across all PropertySets and slots (backward compatibility)

Examples

>>> # Positional slot arguments (NEW)
>>> indexer = StateIndexer(
...     'epoch', 'branch_id',
...     lineage=[Property('descendants', max_value=10)],  # indices 0-10
...     metadata=[Property('time_bin', max_value=100)]    # indices 11-111
... )
>>> indexer.lineage.state_length
11
>>> indexer.metadata.state_length
101
>>> indexer.epoch  # Slot index
112
>>> indexer.branch_id  # Slot index
113
>>> # Backward compatible with slots= parameter
>>> indexer = StateIndexer(
...     lineage=[Property('descendants', max_value=10)],
...     metadata=[Property('time_bin', max_value=100)],
...     slots=['epoch', 'branch_id']
... )
>>> # Access via attributes
>>> props = indexer.lineage.index_to_props(5)
>>> props
{'descendants': 5}
>>> # Backward compatible with single PropertySet
>>> indexer = StateIndexer([Property('descendants', max_value=10)])
>>> indexer.state_length  # Delegates to default PropertySet
11
>>> indexer.index_to_props(5)  # Delegates to default PropertySet
{'descendants': 5}

Methods

Name Description
append Create new StateIndexer by appending another’s PropertySets and Slots.
from_dict Reconstruct StateIndexer from serialized dict.
i2p Alias for :meth:index_to_props.
index_to_props Convert index to property values or slot indicator.
indices Get all indices in concatenated space as a numpy array.
items Get (name, PropertySet) pairs.
keys Get PropertySet names.
p2i Alias for :meth:props_to_index.
property_sets Return all PropertySets in insertion order.
props_to_index Convert property values to index in concatenated space.
slots Return all Slots in insertion order.
to_dict Serialize StateIndexer to JSON-compatible dict.
values Get PropertySet objects.

append

phasic.StateIndexer.append(other, prefix_other=None)

Create new StateIndexer by appending another’s PropertySets and Slots.

The resulting indexer has: - All PropertySets from self (indices 0 to self.state_length - 1) - All PropertySets from other (indices offset by self.state_length) - Slots from both indexers

Parameters

other : StateIndexer

StateIndexer to append

prefix_other : str = None

If provided, prefix all PropertySet and Slot names from other with this string. If None (default), name collisions raise ValueError.

Returns

: StateIndexer

New StateIndexer with combined PropertySets and Slots

Raises

: ValueError

If PropertySet or Slot names collide and prefix_other is None

Examples

>>> indexer1 = StateIndexer(
...     lineage=[Property('descendants', max_value=10)]
... )
>>> indexer2 = StateIndexer(
...     metadata=[Property('time_bin', max_value=100)]
... )
>>> combined = indexer1.append(indexer2)
>>> combined.state_length
112  # 11 + 101
>>> combined.lineage.state_length
11
>>> combined.metadata.state_length
101
>>> # With prefix to avoid name collisions
>>> indexer3 = StateIndexer(
...     lineage=[Property('descendants', max_value=5)]
... )
>>> combined = indexer1.append(indexer3, prefix_other='other_')
>>> combined.lineage.state_length  # Original
11
>>> combined.other_lineage.state_length  # Prefixed
6

from_dict

phasic.StateIndexer.from_dict(data)

Reconstruct StateIndexer from serialized dict.

Parameters

data : dict

Dictionary created by to_dict()

Returns

: StateIndexer

Reconstructed StateIndexer with identical structure

Examples

>>> indexer = StateIndexer(
...     'epoch',
...     lineage=[Property('descendants', max_value=10)]
... )
>>> data = indexer.to_dict()
>>> reconstructed = StateIndexer.from_dict(data)
>>> reconstructed.state_length == indexer.state_length
True

i2p

phasic.StateIndexer.i2p(*args, **kwargs)

Alias for :meth:index_to_props.

index_to_props

phasic.StateIndexer.index_to_props(
    index,
    as_dict=False,
    as_values=False,
    flatten=False,
)

Convert index to property values or slot indicator.

Returns dynamic dataclass with PropertySet/Slot names as attributes. Only the matching PropertySet/Slot is set, others are None.

Parameters

index : int or np.ndarray

Index or array of indices in concatenated space

as_dict : bool = False

If True, return dict instead of dataclass for property values

as_values : bool = False

If True, return array of property values

flatten : bool = False

If True, return properties directly without IndexResult wrapper. Only works when index maps to a single PropertySet (not a slot).

Returns

: IndexResult or list of IndexResult or properties

Default: Dataclass with one attribute per PropertySet/Slot name. If flatten=True: Properties directly (dataclass, dict, or array)

Examples

>>> indexer = StateIndexer(
...     lineage=[Property('descendants', max_value=10)],
...     metadata=[Property('time_bin', max_value=100)],
...     slots=['epoch']
... )
>>> # Index in lineage range
>>> result = indexer.index_to_props(5)
>>> result.lineage.descendants
5
>>> result.metadata
None
>>> result.epoch
None
>>> # Index in metadata range
>>> result = indexer.index_to_props(15)
>>> result.lineage
None
>>> result.metadata.time_bin
4
>>> # Index is epoch slot
>>> result = indexer.index_to_props(112)
>>> result.lineage
None
>>> result.metadata
None
>>> result.epoch
True

indices

phasic.StateIndexer.indices()

Get all indices in concatenated space as a numpy array.

Returns

: np.ndarray

Indices from 0 to state_length - 1

Examples

>>> indexer = StateIndexer(
...     lineage=[Property('descendants', max_value=2)]
... )
>>> indexer.indices()
array([0, 1, 2])

items

phasic.StateIndexer.items()

Get (name, PropertySet) pairs.

keys

phasic.StateIndexer.keys()

Get PropertySet names.

p2i

phasic.StateIndexer.p2i(*args, **kwargs)

Alias for :meth:props_to_index.

property_sets

phasic.StateIndexer.property_sets()

Return all PropertySets in insertion order.

Returns

: list[PropertySet]

PropertySet objects in the order they were added.

props_to_index

phasic.StateIndexer.props_to_index(pset_name=None, props=None, **kwargs)

Convert property values to index in concatenated space.

Note: This method is for PropertySets only. To get slot indices, use attribute access: indexer.slot_name

Supports two calling patterns: 1. Explicit PropertySet: props_to_index(‘pset_name’, props_dict) 2. Auto-detect (single PropertySet only): props_to_index(props_dict)

Parameters

pset_name : str or dict or np.ndarray or None = None
  • If str: Name of the PropertySet (explicit mode) - If dict/np.ndarray: Property values (auto-detect mode, pset_name=None) - If None: use props parameter
props : dict or np.ndarray or None = None

Property values to convert (used when pset_name is str). Can be: - dict: mapping from property names to values - np.ndarray: property values in same order as PropertySet.properties - None: use kwargs instead

****kwargs** : int = {}

Alternative to dict: pass properties as keyword arguments

Returns

: int

Index in concatenated space

Raises

: KeyError

If pset_name doesn’t exist

: ValueError

If auto-detect mode used with multiple PropertySets

Examples

>>> # Explicit PropertySet name
>>> indexer = StateIndexer(
...     lineage=[Property('descendants', max_value=10)],
...     metadata=[Property('time_bin', max_value=100)],
...     slots=['epoch']
... )
>>> indexer.props_to_index('lineage', {'descendants': 5})
5
>>> indexer.props_to_index('metadata', time_bin=4)  # kwargs
15
>>> # For slots, use attribute access directly:
>>> indexer.epoch  # Returns 112
>>> # Auto-detect (single PropertySet only)
>>> single = StateIndexer(lineage=[Property('descendants', max_value=10)])
>>> single.props_to_index({'descendants': 5})
5

slots

phasic.StateIndexer.slots()

Return all Slots in insertion order.

Returns

: list

Slot objects in the order they were added.

to_dict

phasic.StateIndexer.to_dict()

Serialize StateIndexer to JSON-compatible dict.

Returns

: dict

Dictionary containing all information needed to reconstruct the StateIndexer, including PropertySets, Properties, slots, and their ordering.

Examples

>>> indexer = StateIndexer(
...     'epoch',
...     lineage=[Property('descendants', max_value=10)],
...     metadata=[Property('time_bin', max_value=100)]
... )
>>> data = indexer.to_dict()
>>> reconstructed = StateIndexer.from_dict(data)
>>> reconstructed.state_length == indexer.state_length
True

values

phasic.StateIndexer.values()

Get PropertySet objects.