Testing¶
mujou.testing ¶
Deterministic tests of nodes and networks.
Run the networks with run, which puts them on a new VirtualTimer, and
step the nodes by hand by a fixed dt in a fixed order: every update then
sees exactly that dt, and the result can be compared with the discrete
algorithm. Mujou tests its own nodes with the same API.
Example::
from typing import override
import torch
import mujou
import mujou.testing
from mujou import Node, Port, TensorSpec
class Double(Node):
def __init__(self) -> None:
super().__init__()
self.prev = Port(recv=TensorSpec((2,)))
self.next = Port(send=TensorSpec((2,)))
@override
def on_update(self, dt: float) -> None:
super().on_update(dt)
self.next.send(2 * self.prev.recv())
class Chain(mujou.Network):
def __init__(self) -> None:
super().__init__()
self.a, self.b = Double(), Double()
self.a.next.connect(self.b.prev)
self.prev, self.next = self.a.prev, self.b.next
net = Chain()
x = torch.ones(2)
with mujou.testing.run(net, inputs={net.prev: x}) as run:
run.step(0.001, net.a, net.b) # 1 ms later, a, then b reads a's new value
y, t = run.read_with_time(net.next)
assert torch.equal(y, 4 * x) and t is not None and t.ns == 1_000_000
ManualRun ¶
Bases: Run
A run whose nodes update only when step lets them, for tests.
mujou.testing.run returns it. The nodes start and stop as in any run, but
between updates each waits for step, which advances the run's
VirtualTimer first: every dt is exactly the time the test stepped,
and the result follows the discrete algorithm of the chosen order.
timer
property
¶
The run's timer: the VirtualTimer that mujou.testing.run made,
or the one given to it.
step ¶
Advance the timer by dt seconds, then update each of nodes
once, one after another in the given order, and return when done.
Without nodes, every node updates in the order of the tree (each
sees what the earlier ones sent in the same step, as Gauss-Seidel does;
give them from the output side back for the order of Jacobi). The
period of the nodes does not apply. Each dt a node sees is the time
since its last update, the first one from the start of the run (or the
last update restored). A node given twice in one step updates once:
the second time, its time has not moved. A timer shared with other runs
moves for them too.
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
A node is not in this run, or |
RuntimeError
|
The run has stopped normally, or the timer is paused. |
NetworkError
|
A node raised; the run has stopped. |
run ¶
run(
root: Node | Network,
*,
timer: VirtualTimer | None = None,
inputs: Mapping[P, Message] | None = None,
state: Mapping[str, Any] | None = None,
) -> ManualRun
Run root as mujou.run does, with the updates left to
ManualRun.step.
The default timer is a new mujou.time.VirtualTimer at time 0 (with
state, at the time of the state), which ManualRun.timer returns and
ManualRun.step advances. Pass a VirtualTimer of your own to share it
between runs: a step of one then moves the time of the others too.
inputs and state are those of mujou.run, and so are the errors.
There is no workers: the nodes update one at a time, in the order that
ManualRun.step gives.