From 2ea57b6342f3c148d863374c07202f737d1ba1ba Mon Sep 17 00:00:00 2001 From: Iris van der Werf Date: Tue, 8 Sep 2026 14:46:18 +0200 Subject: [PATCH 1/4] Splits check_timelines and resolve_timelines --- ymmsl/v0_2/__init__.py | 2 ++ ymmsl/v0_2/tests/test_timeline_resolver.py | 18 ++++++++++++ ymmsl/v0_2/timeline_resolver.py | 32 ++++++++++++++++++++-- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/ymmsl/v0_2/__init__.py b/ymmsl/v0_2/__init__.py index 3646590..6fb04b8 100644 --- a/ymmsl/v0_2/__init__.py +++ b/ymmsl/v0_2/__init__.py @@ -33,6 +33,7 @@ InconsistentTimelines, ResolveTimelineException, TooManyReducerFilters, + check_timelines, resolve_timelines, ) @@ -41,6 +42,7 @@ "CheckpointRule", "CheckpointRangeRule", "CheckpointAtRule", + "check_timelines", "Checkpoints", "Component", "Ports", diff --git a/ymmsl/v0_2/tests/test_timeline_resolver.py b/ymmsl/v0_2/tests/test_timeline_resolver.py index d81ea6f..8439004 100644 --- a/ymmsl/v0_2/tests/test_timeline_resolver.py +++ b/ymmsl/v0_2/tests/test_timeline_resolver.py @@ -11,6 +11,7 @@ CyclicDependency, InconsistentTimelines, TooManyReducerFilters, + check_timelines, resolve_timelines, ) @@ -154,3 +155,20 @@ def test_model_ports(timelines_configuration: Configuration) -> None: def test_muscle_settings_in(timelines_configuration: Configuration) -> None: model = timelines_configuration.models[Ref("qmc")] resolve_timelines(model) + + +def test_check_timelines_does_not_mutate( + timelines_configuration: Configuration, +) -> None: + model = timelines_configuration.models[Ref("macromicro")] + check_timelines(model) + assert model.components[Ref("macro")].timeline is None + assert model.components[Ref("micro")].timeline is None + for component in model.components.values(): + for port in component.ports.values(): + assert port.timeline == Timeline("") + + # The model can still be resolved afterwards + resolve_timelines(model) + assert model.components[Ref("macro")].timeline == ROOT_TIMELINE + assert model.components[Ref("micro")].timeline == Timeline(":macro") diff --git a/ymmsl/v0_2/timeline_resolver.py b/ymmsl/v0_2/timeline_resolver.py index efd4fa2..e1a4f22 100644 --- a/ymmsl/v0_2/timeline_resolver.py +++ b/ymmsl/v0_2/timeline_resolver.py @@ -13,6 +13,24 @@ MUSCLE_SETTINGS_IN = Identifier("muscle_settings_in") +def check_timelines(model: Model) -> None: + """Check that timelines for this model are consistent. + + This function checks that the timelines are consistent, and raises any of below + subclasses of :class:`ResolveTimelineException` if they are not. + + Raises: + CyclicDependency: When messages to an F_INIT port of a component depend in some + way on the output of that component. + TooManyReducerFilters: When a conduit filter is applied to messages in the root + timeline. + InconsistentTimelines: When a component's F_INIT ports are not all connected to + the same timeline. + ConduitTimelineError: When a conduit connects incompatible timelines. + """ + _check_timelines(model) + + def resolve_timelines(model: Model) -> None: """Determine timelines for each component and their O_I and S ports in this model. @@ -29,8 +47,7 @@ def resolve_timelines(model: Model) -> None: the same timeline. ConduitTimelineError: When a conduit connects incompatible timelines. """ - checker = TimelineChecker(model) - checker.check_consistent() + checker = _check_timelines(model) # Update timeline attributes for component in model.components.values(): @@ -41,6 +58,17 @@ def resolve_timelines(model: Model) -> None: port.timeline = timeline.relative_to(component.timeline) +def _check_timelines(model: Model) -> "TimelineChecker": + """Build a TimelineChecker for the model and check that it is consistent. + + Returns the checker so that callers that also need to resolve the model's + timelines don't have to build it a second time. + """ + checker = TimelineChecker(model) + checker.check_consistent() + return checker + + class ResolveTimelineException(RuntimeError): """Base class for exceptions raised while resolving timelines.""" From eec144c6c94f7057ad76ba87430c7170e031f10d Mon Sep 17 00:00:00 2001 From: Iris van der Werf Date: Tue, 8 Sep 2026 16:08:05 +0200 Subject: [PATCH 2/4] remove extra function and test --- ymmsl/v0_2/tests/test_timeline_resolver.py | 16 ---------------- ymmsl/v0_2/timeline_resolver.py | 17 ++++------------- 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/ymmsl/v0_2/tests/test_timeline_resolver.py b/ymmsl/v0_2/tests/test_timeline_resolver.py index 8439004..a9c5e2d 100644 --- a/ymmsl/v0_2/tests/test_timeline_resolver.py +++ b/ymmsl/v0_2/tests/test_timeline_resolver.py @@ -156,19 +156,3 @@ def test_muscle_settings_in(timelines_configuration: Configuration) -> None: model = timelines_configuration.models[Ref("qmc")] resolve_timelines(model) - -def test_check_timelines_does_not_mutate( - timelines_configuration: Configuration, -) -> None: - model = timelines_configuration.models[Ref("macromicro")] - check_timelines(model) - assert model.components[Ref("macro")].timeline is None - assert model.components[Ref("micro")].timeline is None - for component in model.components.values(): - for port in component.ports.values(): - assert port.timeline == Timeline("") - - # The model can still be resolved afterwards - resolve_timelines(model) - assert model.components[Ref("macro")].timeline == ROOT_TIMELINE - assert model.components[Ref("micro")].timeline == Timeline(":macro") diff --git a/ymmsl/v0_2/timeline_resolver.py b/ymmsl/v0_2/timeline_resolver.py index e1a4f22..9a8565a 100644 --- a/ymmsl/v0_2/timeline_resolver.py +++ b/ymmsl/v0_2/timeline_resolver.py @@ -28,7 +28,9 @@ def check_timelines(model: Model) -> None: the same timeline. ConduitTimelineError: When a conduit connects incompatible timelines. """ - _check_timelines(model) + checker = TimelineChecker(model) + checker.check_consistent() + return checker def resolve_timelines(model: Model) -> None: @@ -47,7 +49,7 @@ def resolve_timelines(model: Model) -> None: the same timeline. ConduitTimelineError: When a conduit connects incompatible timelines. """ - checker = _check_timelines(model) + checker = check_timelines(model) # Update timeline attributes for component in model.components.values(): @@ -58,17 +60,6 @@ def resolve_timelines(model: Model) -> None: port.timeline = timeline.relative_to(component.timeline) -def _check_timelines(model: Model) -> "TimelineChecker": - """Build a TimelineChecker for the model and check that it is consistent. - - Returns the checker so that callers that also need to resolve the model's - timelines don't have to build it a second time. - """ - checker = TimelineChecker(model) - checker.check_consistent() - return checker - - class ResolveTimelineException(RuntimeError): """Base class for exceptions raised while resolving timelines.""" From 2e2dd5bec66e337c3a77d6fed59032cb2109061a Mon Sep 17 00:00:00 2001 From: Iris van der Werf Date: Tue, 8 Sep 2026 16:12:32 +0200 Subject: [PATCH 3/4] fix return type --- ymmsl/v0_2/timeline_resolver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ymmsl/v0_2/timeline_resolver.py b/ymmsl/v0_2/timeline_resolver.py index 9a8565a..a97c1a4 100644 --- a/ymmsl/v0_2/timeline_resolver.py +++ b/ymmsl/v0_2/timeline_resolver.py @@ -13,7 +13,7 @@ MUSCLE_SETTINGS_IN = Identifier("muscle_settings_in") -def check_timelines(model: Model) -> None: +def check_timelines(model: Model) -> "TimelineChecker": """Check that timelines for this model are consistent. This function checks that the timelines are consistent, and raises any of below From f8cc6f8f5c41b820bca2cd56548e8bd98e45d655 Mon Sep 17 00:00:00 2001 From: Iris van der Werf Date: Tue, 8 Sep 2026 16:17:46 +0200 Subject: [PATCH 4/4] remove import --- ymmsl/v0_2/tests/test_timeline_resolver.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ymmsl/v0_2/tests/test_timeline_resolver.py b/ymmsl/v0_2/tests/test_timeline_resolver.py index a9c5e2d..d81ea6f 100644 --- a/ymmsl/v0_2/tests/test_timeline_resolver.py +++ b/ymmsl/v0_2/tests/test_timeline_resolver.py @@ -11,7 +11,6 @@ CyclicDependency, InconsistentTimelines, TooManyReducerFilters, - check_timelines, resolve_timelines, ) @@ -155,4 +154,3 @@ def test_model_ports(timelines_configuration: Configuration) -> None: def test_muscle_settings_in(timelines_configuration: Configuration) -> None: model = timelines_configuration.models[Ref("qmc")] resolve_timelines(model) -