Skip to content

Commit 894de90

Browse files
authored
Merge pull request #73 from multiscale/flatten_timeline_support
Flatten timeline support
2 parents dae1f8d + 5022bf5 commit 894de90

4 files changed

Lines changed: 90 additions & 5 deletions

File tree

‎ymmsl/v0_2/model.py‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from typing import Any, List, Sequence, TypeAlias, cast
55

66
import yatiml
7+
from typing_extensions import Self
78

89
from ymmsl.v0_2.component import Component
910
from ymmsl.v0_2.identity import Identifier, Reference
@@ -331,6 +332,35 @@ def __init__(
331332
def __contains__(self, timeline: Timeline) -> bool:
332333
return timeline in self.matches
333334

335+
def __copy__(self) -> "MatchingTimelines":
336+
"""Shallow-copy the object.
337+
338+
Since this class effectively models a container, a shallow copy should create a
339+
new container with the same items in it, to which items can be added or removed
340+
without affecting the original. By default, copy.copy() won't do that however,
341+
as it will return a new MatchingTimelines containing the same self.matches. So
342+
we override it to give more natural semantics.
343+
"""
344+
return MatchingTimelines(self.head, list(self.matches))
345+
346+
def __ior__(self, other: "MatchingTimelines") -> Self:
347+
"""Merge another MatchingTimelines into this one.
348+
349+
This takes all of the matching timelines from other and adds them to this
350+
object. If the head timeline of other is nested less deeply than the current
351+
head, then it will replace the current head, otherwise the head remains
352+
unchanged.
353+
"""
354+
355+
self.matches |= other.matches
356+
357+
this_depth = max([len(ref) for ref in self.head])
358+
other_depth = max([len(ref) for ref in other.head])
359+
if other_depth < this_depth:
360+
self.head = other.head
361+
362+
return self
363+
334364
def _yatiml_attributes(self) -> OrderedDict:
335365
matches: str | list[str] = list(map(str, sorted(self.matches - {self.head})))
336366
if len(matches) < 6 and sum(len(m) for m in matches) < 60:

‎ymmsl/v0_2/ports.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,15 @@ def __init__(
180180
self.operator = operator
181181
if timeline is None:
182182
timeline = Timeline("")
183+
184+
lengthy = [ref for ref in timeline if len(ref) > 1]
185+
if lengthy:
186+
raise ValueError(
187+
"To avoid confusion, timeline annotations must have a single"
188+
" identifier between colons, so {timeline} is not valid. Please"
189+
" use names without periods instead of e.g. {lengthy[0]}."
190+
)
191+
183192
self.timeline = timeline
184193

185194
def __eq__(self, other: Any) -> bool:

‎ymmsl/v0_2/tests/test_model.py‎

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from copy import copy
12
from typing import Callable
23

34
import pytest
@@ -194,7 +195,7 @@ def test_dump_multicast_conduits() -> None:
194195
assert text == ("sender: init.out\nreceiver:\n- c1.in\n- repeat pad c2.in\n")
195196

196197

197-
def test_create_matching_timeline() -> None:
198+
def test_create_matching_timelines() -> None:
198199
mt = MatchingTimelines(Timeline("tl1"), "tl2")
199200
assert isinstance(mt.head, Timeline)
200201
assert mt.head == Timeline("tl1")
@@ -225,6 +226,49 @@ def test_create_matching_timeline() -> None:
225226
}
226227

227228

229+
def test_copy_matching_timelines() -> None:
230+
tl1 = Timeline("tl1")
231+
tl2 = Timeline("tl2")
232+
mt1 = MatchingTimelines(tl1, [tl2])
233+
234+
mt2 = copy(mt1)
235+
236+
assert mt2.head is mt1.head
237+
assert mt2.matches is not mt1.matches
238+
for m2 in mt2.matches:
239+
assert len([m1 for m1 in mt1.matches if m1 is m2]) > 0
240+
241+
242+
def test_merge_matching_timelines() -> None:
243+
mt1 = MatchingTimelines(Timeline("tl1"), "tl2")
244+
mt2 = MatchingTimelines(Timeline("tl2"), "tl3")
245+
246+
mt1 |= mt2
247+
assert mt1.head == "tl1"
248+
assert mt1.matches == {Timeline("tl1"), Timeline("tl2"), Timeline("tl3")}
249+
250+
mt3 = MatchingTimelines(Timeline("tl4"), "tl5")
251+
mt1 |= mt3
252+
assert mt1.head == "tl1"
253+
assert mt1.matches == {
254+
Timeline("tl1"),
255+
Timeline("tl2"),
256+
Timeline("tl3"),
257+
Timeline("tl4"),
258+
Timeline("tl5"),
259+
}
260+
261+
mt4 = MatchingTimelines(Timeline("c1.c2.tl1"), "c1.c3.tl1")
262+
mt4 |= mt3
263+
assert mt4.head == "tl4"
264+
assert mt4.matches == {
265+
Timeline("tl4"),
266+
Timeline("tl5"),
267+
Timeline("c1.c2.tl1"),
268+
Timeline("c1.c3.tl1"),
269+
}
270+
271+
228272
def test_load_matching_timelines() -> None:
229273
load = yatiml.load_function(MatchingTimelines, Timeline)
230274

‎ymmsl/v0_2/timeline_resolver.py‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,8 +273,8 @@ def timeline_for_port(
273273
This returns the name of the timeline the given port communicates on. These are
274274
as follows:
275275
276-
- for a model port without timeline annotation, empty
277-
- for a model port with a timeline annotation, that annotation
276+
- for a model port without timeline annotation, <model>
277+
- for a model port with a timeline annotation tl1:tl2, <model>.tl1:<model>.tl2
278278
- for a component port without timeline annotation, <parent_tl>:<component>
279279
- for a component O_I or S port with timeline annotation "subtl1",
280280
<parent_tl>:<component>.subtl1
@@ -296,9 +296,9 @@ def timeline_for_port(
296296
assert isinstance(model_port, Identifier)
297297
port = self._model.ports[model_port]
298298
if port.timeline:
299-
result = port.timeline
299+
result = Timeline([self._model.name + tl for tl in port.timeline])
300300
else:
301-
result = ROOT_TIMELINE
301+
result = Timeline([self._model.name])
302302

303303
else:
304304
# Component port
@@ -377,6 +377,8 @@ def format_timelines(self) -> str:
377377
"""Create a formatted list of determined timelines per component."""
378378
return "\n".join(
379379
f"- Component '{comp}' has timeline '{tl}:{comp}'"
380+
if tl
381+
else f"- Component '{comp}' has timeline '{comp}'"
380382
for comp, tl in self._parent_timeline.items()
381383
if len(comp) > 0 # Ony print actual components
382384
)

0 commit comments

Comments
 (0)