Skip to content

Support tuple and unordered node states - #19

Merged
wsdewitt merged 2 commits into
mainfrom
tuple-states
Oct 18, 2026
Merged

wsdewitt merged 2 commits into
mainfrom
tuple-states

Conversation

@wsdewitt

Copy link
Copy Markdown
Collaborator

Closes #18.

Summary

Tuple-valued node states, and state types that can't be ordered, now work in Tree.evolve.

  • HomogeneousProcess.Λ_inv returns a single infinity for a zero rate. It used to return np.full_like(x, np.inf), which for a tuple state gives one infinity per tuple element, so evolve failed when comparing waiting times. Because the default death and mutation processes have rate zero, every tuple-state simulation hit this. For scalar states, the return value is the same as before, array(inf).
  • evolve compares only waiting times when choosing the next event (min(..., key=...)). It used to compare (waiting_time, event, state) tuples, so two infinite waiting times led to comparing states, which raised TypeError for states without an ordering, such as a frozen dataclass without order=True.
  • ConstantProcess.λ_homogeneous returns one rate per state for a sequence of states, matching DiscreteProcess. For a list of three tuple states it used to return shape (3, 2). Numpy arrays keep their shape, as before.

Seeded results don't change

The event chosen for an infinite waiting time is never used, because the simulation reaches its end time first. Two finite waiting times tie with probability zero, so comparing only waiting times picks the same event as before. As a check, the outputs of about 140 seeded simulations, covering every capacity_method, time-inhomogeneous processes, a Gaussian mutator, and sampling and pruning, are identical to 1.0.0.

Tests

Two new tests fail on 1.0.0 and pass with this change:

  • test_tuple_states runs the reproducer from Tuple-valued states make evolve fail (zero-rate waiting time has the wrong shape) #18, which uses tuple states and the default processes. It checks that the resulting tree has the same node times as the same model with a scalar state, and that ConstantProcess returns one rate per tuple state.
  • test_unordered_states simulates states that can't be ordered, two of which have zero birth rate, so their infinite birth waiting times tie.

On Python 3.15 locally, all unit tests, doctests, and both notebooks pass, along with ruff, docformatter, and pyright. On Python 3.12, simulation speed is unchanged or slightly faster.

🤖 Generated with Claude Code

wsdewitt and others added 2 commits October 17, 2026 20:52
- HomogeneousProcess.Λ_inv returns a single infinity for a zero rate,
  rather than np.full_like(x, np.inf), which had one element per tuple
  element when the state was a tuple. With the default zero-rate death
  and mutation processes, this made evolve fail for any tuple state.
- evolve compares only waiting times when choosing the next event, so
  states need not be orderable. Previously, ties between infinite
  waiting times compared the states themselves. Seeded results are
  unchanged, since the event chosen for an infinite waiting time is
  never used.
- ConstantProcess returns one rate per state for a sequence of states,
  instead of one per tuple element.

Closes #18

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@wsdewitt
wsdewitt merged commit e9f337e into main Oct 18, 2026
5 checks passed
@wsdewitt
wsdewitt deleted the tuple-states branch October 18, 2026 05:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tuple-valued states make evolve fail (zero-rate waiting time has the wrong shape)

1 participant