Skip to content
Draft
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
e408db5
get the timeline of a port from the yMMSL config and define an iterat…
IrisvdWerf Jun 22, 2026
57373fc
pass the timeline when constructing the muscle3 Port
IrisvdWerf Jun 22, 2026
6b4f7ec
update tests
IrisvdWerf Jun 22, 2026
5b96e98
test the iteration property
IrisvdWerf Jun 22, 2026
0f00d7a
Create the timeline manager
IrisvdWerf Jun 22, 2026
6828544
Fix the timeline manager and add setup test
IrisvdWerf Jun 23, 2026
21f1f65
fix ruff
IrisvdWerf Jun 23, 2026
d40820a
Add a SubTimelineManager
IrisvdWerf Jun 23, 2026
db83a19
Initialize the TimelineManager
IrisvdWerf Jun 23, 2026
0805fdb
Make the TimelineManager available for the Communicator
IrisvdWerf Jun 23, 2026
7eccccc
Initialize the timelinemanager within the communicator
IrisvdWerf Jun 23, 2026
cb05529
add check_send_message
IrisvdWerf Jun 24, 2026
95ecdf8
Add iteration to MPPMessage
IrisvdWerf Jun 24, 2026
60c7942
add test for the iteration roundtrip?
IrisvdWerf Jun 24, 2026
7851b5a
Add check_received_message
IrisvdWerf Jun 25, 2026
0464c5f
Fix iteration type
IrisvdWerf Jun 25, 2026
365ea81
Define some common functionality
IrisvdWerf Jun 25, 2026
6fb10c4
add temporary return type
IrisvdWerf Jun 25, 2026
7d97ab8
Add timeline filter for list_ports
IrisvdWerf Jun 29, 2026
1d80efb
Add reset function to the timeline managers
IrisvdWerf Jun 30, 2026
77d9f11
Transport the Timeline information
IrisvdWerf Jun 30, 2026
d4ccb13
Add two comments for subtimelines
IrisvdWerf Jun 30, 2026
f50f5b7
Move SEL order checks to the Timeline Manager
IrisvdWerf Jul 1, 2026
8eaf5fb
Handle bridge and skip checks
IrisvdWerf Jul 3, 2026
1e23176
remove a check
IrisvdWerf Jul 6, 2026
196e07d
Update the MMSFValidator docstring
IrisvdWerf Jul 6, 2026
f11bc85
update check send
IrisvdWerf Jul 7, 2026
3b42b2f
Splits check_received into check_receive and check_received
IrisvdWerf Jul 7, 2026
ecda1fe
Merge branch 'develop' into timelines
IrisvdWerf Jul 14, 2026
7ba205e
Fix tests
IrisvdWerf Jul 14, 2026
9a41cc0
Two phase initialization in the communicator, and remove iteration fr…
IrisvdWerf Jul 15, 2026
978c63e
check send message and reset
IrisvdWerf Jul 15, 2026
8c56a21
add check_receive_message (O_I-S or S-O_I)
IrisvdWerf Jul 15, 2026
bec712d
check_received_message
IrisvdWerf Jul 15, 2026
bc3167c
skip the check when sending a closeport message + ruff format
IrisvdWerf Jul 15, 2026
f101c3c
include exception for closeport, vectorports (slots),
IrisvdWerf Jul 15, 2026
1103336
fix parameter overlay test
IrisvdWerf Jul 16, 2026
40520af
include skip_f_init
IrisvdWerf Jul 17, 2026
1729261
save snapshot for a root component where the iteration was not set
IrisvdWerf Jul 17, 2026
ea87146
Make the timeline manager work with coupling schemes
IrisvdWerf Jul 17, 2026
1a99220
Remove the MMSF Validator
IrisvdWerf Jul 17, 2026
de3876b
simplify test_communicator
IrisvdWerf Jul 17, 2026
9170e9a
Simplify timeline manager and create TimelineState
IrisvdWerf Jul 17, 2026
d12bc62
simplify timelines
IrisvdWerf Jul 17, 2026
a00516a
update timeline manager
IrisvdWerf Jul 20, 2026
28c6c52
clean up and add capture_timeline_state
IrisvdWerf Jul 20, 2026
d0edb55
'Fix ruff
IrisvdWerf Jul 20, 2026
4f4cf6b
update timeline syntax
IrisvdWerf Jul 20, 2026
6308361
always save the snapshot state
IrisvdWerf Jul 21, 2026
218ccf8
reset should run before decide_reuse_instance
IrisvdWerf Jul 21, 2026
ecac490
Fix cache O_I/S
IrisvdWerf Jul 21, 2026
82819cd
Fix snapshot interact test
IrisvdWerf Jul 21, 2026
2307393
Include comments part 1
IrisvdWerf Jul 21, 2026
82a8b69
Include comments part 2
IrisvdWerf Jul 21, 2026
7958461
Include comments part 3
IrisvdWerf Jul 21, 2026
7a62fdb
Update test
IrisvdWerf Jul 21, 2026
72e3628
ruff format
IrisvdWerf Jul 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions docs/source/examples/python/interact_coupling.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,7 @@ def temporal_coupler() -> None:
"""
instance = Instance({
Operator.O_I: ['a_out', 'b_out'],
Operator.S: ['a_in', 'b_in']},
InstanceFlags.SKIP_MMSF_SEQUENCE_CHECKS)
Operator.S: ['a_in', 'b_in']})

while instance.reuse_instance():
# Receive initial messages and initialise state
Expand Down Expand Up @@ -277,7 +276,7 @@ def checkpointing_temporal_coupler() -> None:
instance = Instance({
Operator.O_I: ['a_out', 'b_out'],
Operator.S: ['a_in', 'b_in']},
InstanceFlags.USES_CHECKPOINT_API | InstanceFlags.SKIP_MMSF_SEQUENCE_CHECKS)
InstanceFlags.USES_CHECKPOINT_API)

while instance.reuse_instance():
if instance.resuming():
Expand Down
3 changes: 1 addition & 2 deletions integration_test/test_snapshot_complex_coupling.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

from libmuscle import (
KEEPS_NO_STATE_FOR_NEXT_USE,
SKIP_MMSF_SEQUENCE_CHECKS,
USES_CHECKPOINT_API,
Instance,
Message,
Expand Down Expand Up @@ -71,7 +70,7 @@ def echo_component(max_channels=2):
Operator.F_INIT: [f"in{i + 1}" for i in range(max_channels)],
Operator.O_F: [f"out{i + 1}" for i in range(max_channels)],
}
instance = Instance(ports, KEEPS_NO_STATE_FOR_NEXT_USE | SKIP_MMSF_SEQUENCE_CHECKS)
instance = Instance(ports, KEEPS_NO_STATE_FOR_NEXT_USE)

while instance.reuse_instance():
for p_in, p_out in zip(ports[Operator.F_INIT], ports[Operator.O_F]):
Expand Down
4 changes: 2 additions & 2 deletions integration_test/test_snapshot_interact.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,10 @@ def test_snapshot_interact_varstep(tmp_path, scale):
implementation: component
coupler:
ports:
+timeline_a:
timeline timeline_a:
o_i: a_in
s: a_out
+timeline_b:
timeline timeline_b:
o_i: b_in
s: b_out
description: >
Expand Down
25 changes: 20 additions & 5 deletions libmuscle/python/libmuscle/communicator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from libmuscle.profiler import Profiler
from libmuscle.profiling import ProfileEvent, ProfileEventType, ProfileTimestamp
from libmuscle.receive_timeout_handler import Deadlock, ReceiveTimeoutHandler
from libmuscle.timeline_manager import TimelineManager

_logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -108,6 +109,8 @@ def __init__(
# indexed by remote instance id
self._clients: dict[Reference, MPPClient] = {}

self._timeline_manager = TimelineManager(port_manager)

def get_locations(self) -> list[str]:
"""Returns a list of locations that we can be reached at.

Expand All @@ -124,12 +127,14 @@ def set_peer_info(self, peer_info: PeerInfo) -> None:
"""Inform this Communicator about its peers.

This tells the Communicator about its peers, so that it can route
messages accordingly.
messages accordingly. This also completes the TimelineManager's
initialization.

Args:
peer_info: Information about the peers.
"""
self._peer_info = peer_info
self._timeline_manager.connect_sub_timelines()
Comment thread
IrisvdWerf marked this conversation as resolved.
Outdated

def set_receive_timeout(self, receive_timeout: float) -> None:
"""Update the timeout after which the manager is notified that we are waiting
Expand Down Expand Up @@ -172,6 +177,11 @@ def send_message(
# log sending on disconnected port
return

if isinstance(message.data, ClosePort):
iteration = None
else:
iteration = self._timeline_manager.check_send_message(port_name, slot)
Comment thread
IrisvdWerf marked this conversation as resolved.

port = self._port_manager.get_port(port_name)
profile_event = ProfileEvent(
ProfileEventType.SEND,
Expand Down Expand Up @@ -204,6 +214,7 @@ def send_message(
port.get_num_messages(slot),
checkpoints_considered_until,
message.data,
iteration,
)
encoded_message = mpp_message.encoded()
self._server.deposit(recv_endpoint.ref(), encoded_message)
Expand Down Expand Up @@ -245,10 +256,7 @@ def receive_message(
RuntimeError: If the network connection had an error, or the
message number was incorrect.
"""
if port_name == "muscle_settings_in":
port = self._port_manager._muscle_settings_in
else:
port = self._port_manager.get_port(port_name)
port = self._port_manager.get_port(port_name)

if slot is None:
port_and_slot = port_name
Expand Down Expand Up @@ -321,6 +329,8 @@ def receive_message(

if isinstance(mpp_message.data, ClosePort):
port.set_closed(slot)
else:
self._timeline_manager.check_receive(port_name, slot)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this check the first thing you should do when the user calls receive(...)?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can call this after determining whether the incoming message is a ClosePort. We only know that after the client.receive() and decode step. This could potentially be moved earlier in the future if we switch from ClosePort messages to milestones.


message = Message(
mpp_message.timestamp,
Expand Down Expand Up @@ -395,6 +405,11 @@ def receive_message(
if isinstance(mpp_message.data, ClosePort):
_logger.debug(f"Port {port_and_slot} is now closed")

if not isinstance(mpp_message.data, ClosePort):
self._timeline_manager.check_received_message(
port_name, mpp_message.iteration, slot
)

return message, mpp_message.saved_until

def shutdown(self) -> None:
Expand Down
36 changes: 13 additions & 23 deletions libmuscle/python/libmuscle/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from libmuscle.logging import LogLevel
from libmuscle.logging_handler import MuscleManagerHandler
from libmuscle.mmp_client import MMPClient
from libmuscle.mmsf_validator import MMSFValidator
from libmuscle.mpp_message import ClosePort
from libmuscle.port_manager import PortManager
from libmuscle.profiler import Profiler
Expand Down Expand Up @@ -84,14 +83,6 @@ class InstanceFlags(Flag):
:external:py:attr:`ymmsl.KeepsStateForNextUse.NECESSARY`).
"""

SKIP_MMSF_SEQUENCE_CHECKS = auto()
"""Disable the checks whether the MMSF is strictly followed when sending/receiving
messages.

See :class:`~libmuscle.mmsf_validator.MMSFValidator` for a detailed description of
the checks.
"""

Comment on lines -87 to -94

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To discuss: do we want to remove this InstanceFlag and force users to update their codes if they use this?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this because if I understood you and Lourens correctly we would like to remove the possibility to skip the checks such that simulations can always work with well-defined timelines. However, now I removed this whole instance check but I can also re-add this and then give an error that you cannot skip the checks? Or would you like to give this possibility?


_CHECKPOINT_SUPPORT_MASK = (
InstanceFlags.USES_CHECKPOINT_API
Expand Down Expand Up @@ -205,12 +196,6 @@ def __init__(
self._set_remote_log_level()
self._setup_profiling()
self._setup_receive_timeout()
# MMSFValidator needs a connected port manager, and does some logging
self._mmsf_validator = (
None
if InstanceFlags.SKIP_MMSF_SEQUENCE_CHECKS in self._flags
else MMSFValidator(self._port_manager)
)

def reuse_instance(self) -> bool:
"""Decide whether to run this instance again.
Expand Down Expand Up @@ -247,8 +232,6 @@ def reuse_instance(self) -> bool:
:meth:`save_final_snapshot`, or the checkpointing tutorial.
"""
self._api_guard.verify_reuse_instance()
if self._mmsf_validator:
self._mmsf_validator.reuse_instance()

if self._do_reuse is not None:
# thank you, should_save_final_snapshot, for running this already
Expand All @@ -257,8 +240,11 @@ def reuse_instance(self) -> bool:
else:
do_reuse = self._decide_reuse_instance()

if self._do_resume and not self._do_init and self._mmsf_validator:
self._mmsf_validator.skip_f_init()
restored_from_intermediate = self._do_resume and not self._do_init
if restored_from_intermediate:
self._communicator._timeline_manager.restore_state(
self._snapshot_manager.resume_state()
)

# now _first_run, _do_resume and _do_init are also set correctly

Expand All @@ -278,6 +264,12 @@ def reuse_instance(self) -> bool:
# store a None instead of a Message
self._save_snapshot(None, True, self.__f_init_max_timestamp)

if (
not restored_from_intermediate
and self._communicator._timeline_manager.cycle_complete()
):
self._communicator._timeline_manager.reset()

Comment thread
IrisvdWerf marked this conversation as resolved.
Outdated
if not do_reuse:
self.__shutdown()

Expand Down Expand Up @@ -500,8 +492,6 @@ def send(
slot: The slot to send the message on, if any.
"""
self.__check_port(port_name, slot, True)
if self._mmsf_validator:
self._mmsf_validator.check_send(port_name, slot)
if message.settings is None:
message = copy(message)
message.settings = self._settings_manager.overlay
Expand Down Expand Up @@ -961,13 +951,15 @@ def _save_snapshot(
"""
triggers = self._trigger_manager.get_triggers()
walltime = self._trigger_manager.elapsed_walltime()
timeline_state = self._communicator._timeline_manager.get_state()
Comment thread
IrisvdWerf marked this conversation as resolved.
Outdated
timestamp = self._snapshot_manager.save_snapshot(
message,
final,
triggers,
walltime,
f_init_max_timestamp,
self._settings_manager.overlay,
timeline_state,
)
self._trigger_manager.update_checkpoints(timestamp)

Expand All @@ -984,8 +976,6 @@ def __receive_message(
description of those.
"""
self.__check_port(port_name, slot, False, True)
if self._mmsf_validator:
self._mmsf_validator.check_receive(port_name, slot)

port = self._port_manager.get_port(port_name)
if port.operator == Operator.F_INIT:
Expand Down
5 changes: 4 additions & 1 deletion libmuscle/python/libmuscle/manager/mmp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ def encode_conduit(conduit: Conduit) -> list[str]:

def encode_ports(ports: Ports) -> list[list[str]]:
"""Convert a Ports to a MsgPack-compatible value."""
return [[str(port_name), ports[port_name].operator.name] for port_name in ports]
return [
[str(port_name), ports[port_name].operator.name, str(ports[port_name].timeline)]
for port_name in ports
]


def encode_checkpoint_rule(rule: CheckpointRule) -> dict[str, Any]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ def test_request_peers_fanout(registered_mmp_request_handler):
assert name == f"micro[{i // 10}][{i % 10}]"
assert locs == [f"direct:{name}"]

assert ports == [["out", "O_I"], ["in", "S"]]
assert ports == [["out", "O_I", ""], ["in", "S", ""]]


def test_request_peers_fanin(registered_mmp_request_handler):
Expand All @@ -288,7 +288,7 @@ def test_request_peers_fanin(registered_mmp_request_handler):

assert locations["macro"] == ["direct:macro"]

assert ports == [["in", "F_INIT"], ["out", "O_F"]]
assert ports == [["in", "F_INIT", ""], ["out", "O_F", ""]]


def test_request_peers_bidir(registered_mmp_request_handler2):
Expand Down Expand Up @@ -319,7 +319,12 @@ def test_request_peers_bidir(registered_mmp_request_handler2):
for i in range(10):
assert locations[f"micro[2][{i}]"] == [f"direct:micro[2][{i}]"]

assert ports == [["init", "F_INIT"], ["out", "O_I"], ["in", "S"], ["final", "O_F"]]
assert ports == [
["init", "F_INIT", ""],
["out", "O_I", ""],
["in", "S", ""],
["final", "O_F", ""],
]


def test_request_peers_own_conduits(registered_mmp_request_handler2):
Expand All @@ -337,7 +342,7 @@ def test_request_peers_own_conduits(registered_mmp_request_handler2):
assert conduits[1][0] == "meso.final"
assert conduits[1][1] == "macro.in"

assert ports == [["out", "O_I"], ["in", "S"]]
assert ports == [["out", "O_I", ""], ["in", "S", ""]]


def test_request_peers_unknown(registered_mmp_request_handler2):
Expand Down
6 changes: 5 additions & 1 deletion libmuscle/python/libmuscle/mmp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
Port,
Reference,
Settings,
Timeline,
)

import libmuscle
Expand Down Expand Up @@ -311,7 +312,10 @@ def request_peers(self) -> PeerInfo:
Reference(instance): locs for instance, locs in response[3].items()
}

ports = [Port(Identifier(name), Operator[op]) for name, op in response[4]]
ports = [
Port(Identifier(name), Operator[op], Timeline(tl))
for name, op, tl in response[4]
]
name = instance_to_kernel(self._instance_id)
index = instance_indices(self._instance_id)
return PeerInfo(name, index, conduits, peer_dimensions, peer_locations, ports)
Expand Down
Loading
Loading