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
otherwith 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
6from_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
Truei2p
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 :intornp.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
Trueindices
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 :strordictornp.ndarrayor 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
- If str: Name of the PropertySet (explicit mode) - If dict/
props :dictornp.ndarrayor 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})
5slots
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
Truevalues
phasic.StateIndexer.values()Get PropertySet objects.