Skip to content

Commit 7076bbc

Browse files
authored
Merge pull request #70 from multiscale/update_port_timelines
Implement improved ideas regarding timelines for components and ports
2 parents ec4d514 + 4901688 commit 7076bbc

5 files changed

Lines changed: 163 additions & 76 deletions

File tree

‎ymmsl/v0_2/ports.py‎

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,24 @@ def __len__(self) -> int:
105105
"""Return the number of parts in the Timeline."""
106106
return len(self._parts)
107107

108-
def __getitem__(self, index: int) -> Reference:
109-
"""Return the index'th item in the timeline."""
110-
return self._parts[index]
108+
@overload
109+
def __getitem__(self, index: int) -> Reference: ...
110+
111+
@overload
112+
def __getitem__(self, index: slice) -> "Timeline": ...
113+
114+
def __getitem__(self, index: int | slice) -> "Reference | Timeline":
115+
"""Return an item or a subtimeline.
116+
117+
Args
118+
key: Either an int, to retrieve the given item, or a slice, to return a new
119+
Timeline object with the given items.
120+
"""
121+
if isinstance(index, int):
122+
return self._parts[index]
123+
if isinstance(index, slice):
124+
return Timeline(self._parts[index])
125+
raise TypeError("Subscript must be either an int or a slice")
111126

112127
def __iter__(self) -> Iterator[Reference]:
113128
"""Iterate over the timeline parts."""
@@ -121,13 +136,20 @@ def __add__(self, other: Any) -> "Timeline":
121136
"Cannot concatenate an absolute Timeline onto another one"
122137
)
123138
return Timeline(self._parts + other._parts, self.absolute)
139+
140+
if isinstance(other, Reference):
141+
return Timeline(self._parts + [other], self.absolute)
142+
124143
return NotImplemented
125144

126145
@property
127-
def parent(self) -> "Timeline | None":
128-
"""Get the parent of this timeline. Returns None when there is no parent."""
146+
def parent(self) -> "Timeline":
147+
"""Get the parent of this timeline.
148+
149+
Raises:
150+
RuntimeError if this is the root and there is no parent."""
129151
if not self._parts:
130-
return None
152+
raise RuntimeError("The root timeline does not have a parent")
131153
return Timeline(self._parts[:-1], self.absolute)
132154

133155
def relative_to(self, other: "Timeline") -> "Timeline":

‎ymmsl/v0_2/tests/test_ports.py‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,11 @@ def test_timeline_concatenate_empty() -> None:
126126

127127

128128
def test_timeline_parent() -> None:
129-
assert Timeline("").parent is None
130-
assert Timeline(":").parent is None
129+
with pytest.raises(RuntimeError, match="root timeline"):
130+
_ = Timeline("").parent
131+
132+
with pytest.raises(RuntimeError, match="root timeline"):
133+
_ = Timeline(":").parent
131134

132135
assert Timeline("a:b").parent == Timeline("a")
133136
assert Timeline(":a:b").parent == Timeline(":a")

‎ymmsl/v0_2/tests/test_timeline_resolver.py‎

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
from ymmsl.v0_2 import ConduitFilter, Configuration, Timeline
77
from ymmsl.v0_2 import Reference as Ref
88
from ymmsl.v0_2.timeline_resolver import (
9-
ROOT_TIMELINE,
109
ConduitTimelineError,
1110
CyclicDependency,
1211
InconsistentTimelines,
1312
TooManyReducerFilters,
13+
check_timelines,
1414
resolve_timelines,
1515
)
1616

@@ -29,24 +29,15 @@ def test_consistent_configuration(timelines_configuration: Configuration) -> Non
2929
def test_dispatch(timelines_configuration: Configuration) -> None:
3030
model = timelines_configuration.models[Ref("dispatch")]
3131
resolve_timelines(model)
32-
assert model.components[Ref("first")].timeline == ROOT_TIMELINE
33-
assert model.components[Ref("second")].timeline == ROOT_TIMELINE
32+
assert model.components[Ref("first")].timeline == ":first"
33+
assert model.components[Ref("second")].timeline == ":second"
3434

3535

3636
def test_macromicro(timelines_configuration: Configuration) -> None:
3737
model = timelines_configuration.models[Ref("macromicro")]
3838
resolve_timelines(model)
39-
assert model.components[Ref("macro")].timeline == ROOT_TIMELINE
40-
assert model.components[Ref("micro")].timeline == Timeline(":macro")
41-
42-
# Check that ports have the correct relative timelines:
43-
macro = model.components[Ref("macro")]
44-
assert macro.ports["init"].timeline == Timeline("")
45-
assert macro.ports["out"].timeline == Timeline("macro")
46-
assert macro.ports["in"].timeline == Timeline("macro")
47-
48-
for micro_port in model.components[Ref("micro")].ports.values():
49-
assert micro_port.timeline == Timeline("")
39+
assert model.components[Ref("macro")].timeline == Timeline(":macro")
40+
assert model.components[Ref("micro")].timeline == Timeline(":macro:micro")
5041

5142

5243
def test_cycle(timelines_configuration: Configuration) -> None:
@@ -57,8 +48,8 @@ def test_cycle(timelines_configuration: Configuration) -> None:
5748
def test_reducer(timelines_configuration: Configuration) -> None:
5849
model = timelines_configuration.models[Ref("reducer")]
5950
resolve_timelines(model)
60-
assert model.components[Ref("first")].timeline == ROOT_TIMELINE
61-
assert model.components[Ref("second")].timeline == ROOT_TIMELINE
51+
assert model.components[Ref("first")].timeline == Timeline(":first")
52+
assert model.components[Ref("second")].timeline == Timeline(":second")
6253

6354

6455
def test_only_reducer(timelines_configuration: Configuration) -> None:
@@ -67,8 +58,8 @@ def test_only_reducer(timelines_configuration: Configuration) -> None:
6758
del model.conduits[0]
6859
assert model.conduits[0].filters == [ConduitFilter("last")]
6960
resolve_timelines(model)
70-
assert model.components[Ref("first")].timeline == ROOT_TIMELINE
71-
assert model.components[Ref("second")].timeline == ROOT_TIMELINE
61+
assert model.components[Ref("first")].timeline == Timeline(":first")
62+
assert model.components[Ref("second")].timeline == Timeline(":second")
7263

7364

7465
def test_too_many_reducers(timelines_configuration: Configuration) -> None:
@@ -86,9 +77,9 @@ def test_inconsistent_timelines(timelines_configuration: Configuration) -> None:
8677
def test_repeaters(timelines_configuration: Configuration) -> None:
8778
model = timelines_configuration.models[Ref("repeaters")]
8879
resolve_timelines(model)
89-
assert model.components[Ref("macro")].timeline == ROOT_TIMELINE
90-
assert model.components[Ref("meso")].timeline == Timeline(":macro")
91-
assert model.components[Ref("micro")].timeline == Timeline(":macro:meso")
80+
assert model.components[Ref("macro")].timeline == Timeline(":macro")
81+
assert model.components[Ref("meso")].timeline == Timeline(":macro:meso")
82+
assert model.components[Ref("micro")].timeline == Timeline(":macro:meso:micro")
9283

9384

9485
def test_too_many_repeaters(timelines_configuration: Configuration) -> None:
@@ -115,10 +106,10 @@ def test_repeater_and_too_many_reducers(timelines_configuration: Configuration)
115106
def test_repeater_after_reducer(timelines_configuration: Configuration) -> None:
116107
model = timelines_configuration.models[Ref("repeater_reducer")]
117108
resolve_timelines(model)
118-
assert model.components[Ref("macro1")].timeline == ROOT_TIMELINE
119-
assert model.components[Ref("macro2")].timeline == ROOT_TIMELINE
120-
assert model.components[Ref("micro1")].timeline == Timeline(":macro1")
121-
assert model.components[Ref("micro2")].timeline == Timeline(":macro2")
109+
assert model.components[Ref("macro1")].timeline == Timeline(":macro1")
110+
assert model.components[Ref("macro2")].timeline == Timeline(":macro2")
111+
assert model.components[Ref("micro1")].timeline == Timeline(":macro1:micro1")
112+
assert model.components[Ref("micro2")].timeline == Timeline(":macro2:micro2")
122113

123114
# Remove filters on the last conduit to make the incoming timelines inconsistent
124115
model.conduits[-1].filters = []
@@ -132,18 +123,26 @@ def test_repeater_after_reducer_error(timelines_configuration: Configuration) ->
132123
resolve_timelines(model)
133124
model.conduits[-1].filters = []
134125
resolve_timelines(model)
135-
assert model.components[Ref("macro")].timeline == ROOT_TIMELINE
136-
assert model.components[Ref("micro1")].timeline == Timeline(":macro")
137-
assert model.components[Ref("micro2")].timeline == Timeline(":macro")
126+
assert model.components[Ref("macro")].timeline == Timeline(":macro")
127+
assert model.components[Ref("micro1")].timeline == Timeline(":macro:micro1")
128+
assert model.components[Ref("micro2")].timeline == Timeline(":macro:micro2")
138129

139130

140131
def test_inconsistent_interact(timelines_configuration: Configuration) -> None:
141132
model = timelines_configuration.models[Ref("inconsistent_interact")]
142133
with pytest.raises(ConduitTimelineError, match="missing timeline annotations"):
143134
resolve_timelines(model)
144-
model.components[Ref("B")].ports["out"].timeline = Timeline("A")
145-
model.components[Ref("B")].ports["in"].timeline = Timeline("A")
146-
resolve_timelines(model)
135+
# TODO: add matching_timelines and try again successfully
136+
137+
138+
def test_subtimelines(timelines_configuration: Configuration) -> None:
139+
model = timelines_configuration.models[Ref("different_subtimelines")]
140+
check_timelines(model)
141+
142+
model.conduits[0].receiver = Ref("micro2.init")
143+
model.conduits[2].receiver = Ref("micro1.init")
144+
with pytest.raises(ConduitTimelineError, match="these do not match"):
145+
resolve_timelines(model)
147146

148147

149148
def test_model_ports(timelines_configuration: Configuration) -> None:

‎ymmsl/v0_2/tests/ymmsl1/timelines.ymmsl‎

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,12 @@ models:
195195
B.out: A.in
196196

197197
model_ports:
198+
ports:
199+
f_init: init
200+
timeline tl1:tl2:
201+
o_i: out
202+
s: in
203+
o_f: final
198204
components:
199205
macro:
200206
description: macro
@@ -210,12 +216,6 @@ models:
210216
o_i: out
211217
s: in
212218
o_f: final
213-
ports:
214-
f_init: init
215-
timeline macro:meso:
216-
o_i: out
217-
s: in
218-
o_f: final
219219
conduits:
220220
init: macro.init
221221
macro.out: meso.init
@@ -238,3 +238,30 @@ models:
238238
conduits:
239239
mc.parameters_out: sim.muscle_settings_in
240240
sim.state_out: mc.states_in
241+
242+
different_subtimelines:
243+
components:
244+
macro:
245+
description: macro with two subtimelines
246+
ports:
247+
timeline tl1:
248+
o_i: out1
249+
s: in1
250+
timeline tl2:
251+
o_i: out2
252+
s: in2
253+
micro1:
254+
description: micro1
255+
ports:
256+
f_init: init
257+
o_f: final
258+
micro2:
259+
description: micro2
260+
ports:
261+
f_init: init
262+
o_f: final
263+
conduits:
264+
macro.out1: micro1.init
265+
micro1.final: macro.in1
266+
macro.out2: micro2.init
267+
micro2.final: macro.in2

0 commit comments

Comments
 (0)