Skip to content

Commit cdf5f9c

Browse files
committed
Remove absolute/relative Timelines distinction
1 parent 015bfc8 commit cdf5f9c

4 files changed

Lines changed: 75 additions & 130 deletions

File tree

‎ymmsl/v0_2/ports.py‎

Lines changed: 39 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,31 @@
88
class Timeline:
99
"""Identify a timeline on which a port sends or receives.
1010
11-
Timeline objects describe when a port sends or receives, either relative to a parent
12-
timeline that calls the component they're a part of, or by describing the whole list
13-
of components calling each other from the root timeline down.
14-
15-
A component c1 that is not called by any other component will have its F_INIT and
16-
O_F ports (if any) on the root timeline, which is represented by ':'. If c1's
17-
implementation has a loop in which it sends on an O_I port and receives on an S
18-
port, then those ports are on a subtimeline, which is named after the component by
19-
default, ':c1'.
20-
21-
If we add a component c2 and connect its F_INIT and O_F to those ports, then we
22-
create a macro-micro type coupling. The F_INIT and O_F ports of c2 will then be on
23-
timeline ':c1', because they'll receive and send at the exact points in simulated
24-
time that c1's O_I and S ports send and receive.
25-
26-
If c2 has its own O_I and S ports, then those will be on timeline ':c1:c2', this
27-
being the concatenation of the parent timeline and the relative timeline within c2.
28-
29-
Some implementations may have more than one set of O_I/S ports, on which they
30-
communicate at different rates. In that case, each port should be given a local
31-
relative timeline explicitly. In the above example, if c1's O_I and S ports were
32-
specified to be on local relative timeline 'tl1', then their full relative timeline
33-
is 'c1.tl' and their absolute timeline is ':c1.tl`, putting c2's O_I and S ports on
34-
':c1.tl:c2' unless they too have an explicit timeline designation.
11+
A timeline is a stretch of time with a beginning and an end that is subdivided into
12+
zero or more time steps that step from one intermediate time point to the next.
13+
14+
F_INIT ports receive at the beginning of the timeline, O_F ports send at the end of
15+
the timeline, O_I ports send at the first and intermediate points on the timeline,
16+
and S ports receive at intermediate and the last time point of the timeline.
17+
18+
Timeline objects are used to annotate components and ports, to describe which
19+
timeline they're on.
20+
21+
For components, the timeline depends on where in a call hierarchy the component
22+
sits. A component ``c1`` that is not called (via an O_I to F_INIT conduit) by any
23+
other model has timeline ``c1``, relative to the model it's in. If ``c2`` is called
24+
by ``c1``, then it is in timeline ``c2`` relative to its *parent timeline* ``c1``,
25+
and in ``c1:c2`` relative to the model. If ``c1`` dispatches (O_F to F_INIT) to
26+
``c3``, then ``c3``'s parent timeline is the same as ``c1``'s parent timeline, which
27+
is the empty model timeline, putting component ``c3`` into timeline ``c3``.
28+
29+
Ports are on the timeline of the component they're associated with. Some components
30+
however have multiple sets of O_I/S ports, on which they communicate at different
31+
rates. Each group of such ports is annotated, using this class, with a timeline
32+
annotation naming the sub-timeline that port is on. If an O_I port on component
33+
``c1`` is annotated with ``tl1``, then that port will be on timeline ``c1.tl1``
34+
rather than on ``c1``, and if it were on ``c2`` then the full timeline would be
35+
``c1:c2.tl1``.
3536
3637
Timelines have a technical representation as a list of References, and a string
3738
representation in which those References are joined using colons.
@@ -41,14 +42,11 @@ class Timeline:
4142
def __init__(self, timeline: str) -> None: ...
4243

4344
@overload
44-
def __init__(
45-
self, timeline: Sequence[str | Reference], absolute: bool = True
46-
) -> None: ...
45+
def __init__(self, timeline: Sequence[str | Reference]) -> None: ...
4746

4847
def __init__(
4948
self,
5049
timeline: str | Sequence[str | Reference],
51-
absolute: bool = True,
5250
) -> None:
5351
"""Create a Timeline.
5452
@@ -60,32 +58,21 @@ def make_new_reference(x: str | Reference) -> Reference:
6058
return Reference(str(x))
6159

6260
if isinstance(timeline, str):
61+
timeline = timeline.strip(":")
6362
if timeline == "":
64-
self.absolute = False
65-
parts: Sequence[str | Reference] = []
66-
elif timeline == ":":
67-
self.absolute = True
68-
parts = []
69-
else:
70-
self.absolute = False
71-
if timeline[0] == ":":
72-
self.absolute = True
73-
timeline = timeline[1:]
74-
75-
parts = timeline.split(":")
63+
self._parts = []
64+
return
7665

77-
self._parts = list(map(make_new_reference, parts))
66+
timeline = timeline.split(":")
7867

79-
else:
80-
self.absolute = absolute
81-
self._parts = list(map(make_new_reference, timeline))
68+
self._parts = list(map(make_new_reference, timeline))
8269

8370
def __eq__(self, other: Any) -> bool:
8471
"""Compare with another Timeline or a string for equality."""
8572
if isinstance(other, str):
8673
return str(self) == other
8774
elif isinstance(other, Timeline):
88-
return self.absolute == other.absolute and self._parts == other._parts
75+
return self._parts == other._parts
8976
return NotImplemented
9077

9178
def __hash__(self) -> int:
@@ -103,8 +90,7 @@ def __lt__(self, other: Any) -> bool:
10390

10491
def __str__(self) -> str:
10592
"""Return the string representation of this Timeline."""
106-
anchor = ":" if self.absolute else ""
107-
return anchor + ":".join(map(str, self._parts))
93+
return ":".join(map(str, self._parts))
10894

10995
def __repr__(self) -> str:
11096
"""Return a representation of the object."""
@@ -138,16 +124,12 @@ def __iter__(self) -> Iterator[Reference]:
138124
yield from self._parts
139125

140126
def __add__(self, other: Any) -> "Timeline":
141-
"""Concatenate this timeline with another (relative!) Timeline."""
127+
"""Concatenate this timeline with another Timeline."""
142128
if isinstance(other, Timeline):
143-
if other.absolute:
144-
raise ValueError(
145-
"Cannot concatenate an absolute Timeline onto another one"
146-
)
147-
return Timeline(self._parts + other._parts, self.absolute)
129+
return Timeline(self._parts + other._parts)
148130

149131
if isinstance(other, Reference):
150-
return Timeline(self._parts + [other], self.absolute)
132+
return Timeline(self._parts + [other])
151133

152134
return NotImplemented
153135

@@ -159,19 +141,16 @@ def parent(self) -> "Timeline":
159141
RuntimeError if this is the root and there is no parent."""
160142
if not self._parts:
161143
raise RuntimeError("The root timeline does not have a parent")
162-
return Timeline(self._parts[:-1], self.absolute)
144+
return Timeline(self._parts[:-1])
163145

164146
def relative_to(self, other: "Timeline") -> "Timeline":
165147
"""Compute a version of this timeline relative to `other`.
166148
167-
Both timelines must be absolute, and the other timeline must be a parent
168-
timeline of this one.
149+
The other timeline must be a parent timeline of this one.
169150
"""
170-
if not self.absolute or not other.absolute:
171-
raise ValueError("Both timelines must be absolute")
172151
if self._parts[: len(other)] != other._parts:
173152
raise ValueError(f"{self} is not a subtimeline of {other}")
174-
return Timeline(self._parts[len(other) :], False)
153+
return Timeline(self._parts[len(other) :])
175154

176155

177156
class Port:

‎ymmsl/v0_2/tests/test_ports.py‎

Lines changed: 16 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,54 +9,41 @@
99

1010
def test_create_empty_timeline() -> None:
1111
tl = Timeline("")
12-
assert tl.absolute is False
1312
assert len(tl._parts) == 0
1413

1514

1615
def test_create_root_timeline() -> None:
1716
tl = Timeline(":")
18-
assert tl.absolute is True
1917
assert len(tl._parts) == 0
2018

2119

22-
def test_create_absolute_timeline() -> None:
23-
tl = Timeline(":timeline")
24-
assert tl.absolute is True
25-
assert len(tl._parts) == 1
26-
assert tl._parts[0] == "timeline"
27-
28-
29-
def test_create_relative_timeline() -> None:
20+
def test_create_timeline() -> None:
3021
tl = Timeline("timeline")
31-
assert tl.absolute is False
3222
assert len(tl._parts) == 1
3323
assert tl._parts[0] == "timeline"
3424

3525

3626
def test_create_full_timeline() -> None:
3727
tl = Timeline("c1.a:c2.b:c3:c4")
38-
assert tl.absolute is False
3928
assert len(tl._parts) == 4
4029
assert tl._parts == ["c1.a", "c2.b", "c3", "c4"]
4130

4231

43-
def test_create_absolute_from_list_of_str() -> None:
44-
tl = Timeline(["c1.a", "c2.b", "c3", "c4"], True)
45-
assert tl.absolute is True
32+
def test_create_from_list_of_str() -> None:
33+
tl = Timeline(["c1.a", "c2.b", "c3", "c4"])
4634
assert len(tl._parts) == 4
4735
assert tl._parts == ["c1.a", "c2.b", "c3", "c4"]
4836

4937

50-
def test_create_relative_from_list_of_ref() -> None:
51-
tl = Timeline([Ref("c1"), Ref("c2.a")], False)
52-
assert tl.absolute is False
38+
def test_create_from_list_of_ref() -> None:
39+
tl = Timeline([Ref("c1"), Ref("c2.a")])
5340
assert len(tl._parts) == 2
5441
assert tl._parts == ["c1", "c2.a"]
5542

5643

5744
def test_timeline_equality() -> None:
5845
tl1 = Timeline(":c0:c1:c2.a")
59-
tl2 = Timeline(["c0", "c1", "c2.a"], True)
46+
tl2 = Timeline(["c0", "c1", "c2.a"])
6047
assert tl1 == tl2
6148

6249
tl2._parts[1] = Reference("c1.x")
@@ -65,26 +52,14 @@ def test_timeline_equality() -> None:
6552
tl1._parts[1] = Reference("c1.x")
6653
assert tl1 == tl2
6754

68-
tl2.absolute = False
69-
assert tl1 != tl2
70-
71-
tl1.absolute = False
72-
assert tl1 == tl2
73-
74-
tl2.absolute = True
75-
assert tl1 != tl2
76-
7755

7856
def test_timeline_to_str() -> None:
79-
tl = Timeline([Ref("c1"), Ref("c2.a"), Ref("c3")], False)
57+
tl = Timeline([Ref("c1"), Ref("c2.a"), Ref("c3")])
8058
assert str(tl) == "c1:c2.a:c3"
8159

82-
tl.absolute = True
83-
assert str(tl) == ":c1:c2.a:c3"
84-
8560

8661
def test_timeline_indexing() -> None:
87-
tl = Timeline(":c0:c1:c2.p:c3.q")
62+
tl = Timeline("c0:c1:c2.p:c3.q")
8863
assert len(tl) == 4
8964
assert isinstance(tl[0], Reference)
9065
assert tl[0] == "c0"
@@ -98,20 +73,17 @@ def test_timeline_indexing() -> None:
9873
with pytest.raises(IndexError):
9974
tl[4]
10075

76+
assert tl[0:2] == "c0:c1"
77+
assert tl[1:3] == "c1:c2.p"
78+
10179

10280
def test_timeline_concatenation() -> None:
103-
tl1 = Timeline(":c1:c2.a")
81+
tl1 = Timeline("c1:c2.a")
10482
tl2 = Timeline("c3.b:c4")
10583
tl3 = tl1 + tl2
10684

107-
assert tl3.absolute is True
10885
assert len(tl3._parts) == 4
109-
assert tl3 == Timeline(":c1:c2.a:c3.b:c4")
110-
111-
tl2.absolute = True
112-
113-
with pytest.raises(ValueError):
114-
tl1 + tl2
86+
assert tl3 == Timeline("c1:c2.a:c3.b:c4")
11587

11688

11789
def test_timeline_concatenate_empty() -> None:
@@ -133,20 +105,14 @@ def test_timeline_parent() -> None:
133105
_ = Timeline(":").parent
134106

135107
assert Timeline("a:b").parent == Timeline("a")
136-
assert Timeline(":a:b").parent == Timeline(":a")
137108

138109

139110
def test_timeline_relative_to() -> None:
140-
assert Timeline(":a:b:c").relative_to(Timeline(":a:b")) == Timeline("c")
141-
assert Timeline(":a:b:c").relative_to(Timeline(":a")) == Timeline("b:c")
142-
143-
with pytest.raises(ValueError, match="absolute"):
144-
Timeline("a:b").relative_to(Timeline(":a"))
145-
with pytest.raises(ValueError, match="absolute"):
146-
Timeline(":a:b").relative_to(Timeline("a"))
111+
assert Timeline("a:b:c").relative_to(Timeline("a:b")) == Timeline("c")
112+
assert Timeline("a:b:c").relative_to(Timeline("a")) == Timeline("b:c")
147113

148114
with pytest.raises(ValueError, match="subtimeline"):
149-
Timeline(":a:b").relative_to(Timeline(":b"))
115+
Timeline("a:b").relative_to(Timeline("b"))
150116

151117

152118
def test_create_empty_ports() -> None:

‎ymmsl/v0_2/tests/test_timeline_resolver.py‎

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +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 == ":first"
33-
assert model.components[Ref("second")].timeline == ":second"
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 == Timeline(":macro")
40-
assert model.components[Ref("micro")].timeline == Timeline(":macro:micro")
39+
assert model.components[Ref("macro")].timeline == Timeline("macro")
40+
assert model.components[Ref("micro")].timeline == Timeline("macro:micro")
4141

4242

4343
def test_cycle(timelines_configuration: Configuration) -> None:
@@ -48,8 +48,8 @@ def test_cycle(timelines_configuration: Configuration) -> None:
4848
def test_reducer(timelines_configuration: Configuration) -> None:
4949
model = timelines_configuration.models[Ref("reducer")]
5050
resolve_timelines(model)
51-
assert model.components[Ref("first")].timeline == Timeline(":first")
52-
assert model.components[Ref("second")].timeline == Timeline(":second")
51+
assert model.components[Ref("first")].timeline == Timeline("first")
52+
assert model.components[Ref("second")].timeline == Timeline("second")
5353

5454

5555
def test_only_reducer(timelines_configuration: Configuration) -> None:
@@ -58,8 +58,8 @@ def test_only_reducer(timelines_configuration: Configuration) -> None:
5858
del model.conduits[0]
5959
assert model.conduits[0].filters == [ConduitFilter("last")]
6060
resolve_timelines(model)
61-
assert model.components[Ref("first")].timeline == Timeline(":first")
62-
assert model.components[Ref("second")].timeline == Timeline(":second")
61+
assert model.components[Ref("first")].timeline == Timeline("first")
62+
assert model.components[Ref("second")].timeline == Timeline("second")
6363

6464

6565
def test_too_many_reducers(timelines_configuration: Configuration) -> None:
@@ -77,9 +77,9 @@ def test_inconsistent_timelines(timelines_configuration: Configuration) -> None:
7777
def test_repeaters(timelines_configuration: Configuration) -> None:
7878
model = timelines_configuration.models[Ref("repeaters")]
7979
resolve_timelines(model)
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")
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")
8383

8484

8585
def test_too_many_repeaters(timelines_configuration: Configuration) -> None:
@@ -106,10 +106,10 @@ def test_repeater_and_too_many_reducers(timelines_configuration: Configuration)
106106
def test_repeater_after_reducer(timelines_configuration: Configuration) -> None:
107107
model = timelines_configuration.models[Ref("repeater_reducer")]
108108
resolve_timelines(model)
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")
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")
113113

114114
# Remove filters on the last conduit to make the incoming timelines inconsistent
115115
model.conduits[-1].filters = []
@@ -123,9 +123,9 @@ def test_repeater_after_reducer_error(timelines_configuration: Configuration) ->
123123
resolve_timelines(model)
124124
model.conduits[-1].filters = []
125125
resolve_timelines(model)
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")
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")
129129

130130

131131
def test_inconsistent_interact(timelines_configuration: Configuration) -> None:

‎ymmsl/v0_2/timeline_resolver.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -306,10 +306,10 @@ def timeline_for_port(
306306
port = self._all_ports[port_name]
307307
if port.timeline:
308308
subtimeline = Timeline(
309-
[f"{component}.{name}" for name in port.timeline], False
309+
[f"{component}.{name}" for name in port.timeline]
310310
)
311311
else:
312-
subtimeline = Timeline([component], False)
312+
subtimeline = Timeline([component])
313313

314314
result = parent_tl + subtimeline
315315

0 commit comments

Comments
 (0)