Skip to content

Commit 9397ea1

Browse files
authored
Merge pull request #656 from wrieg123/wrr/in-realtime
add in_realtime builtin for sim to realtime graph execution
2 parents 73e9065 + fd551cc commit 9397ea1

File tree

4 files changed

+97
-0
lines changed

4 files changed

+97
-0
lines changed

cpp/csp/python/cspimpl.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,16 @@ static PyObject *_set_capture_cpp_backtrace( PyObject *, PyObject *args )
116116
CSP_RETURN_NONE;
117117
}
118118

119+
static PyObject * _csp_in_realtime( PyObject*, PyObject * nodeptr )
120+
{
121+
CSP_BEGIN_METHOD;
122+
123+
csp::Node * node = reinterpret_cast<csp::Node *>( fromPython<uint64_t>( nodeptr ) );
124+
return toPython( node -> rootEngine() -> inRealtime() );
125+
126+
CSP_RETURN_NULL;
127+
}
128+
119129

120130
static PyMethodDef _cspimpl_methods[] = {
121131
{"_csp_now", (PyCFunction) _csp_now, METH_O, "current engine time"},
@@ -125,6 +135,7 @@ static PyMethodDef _cspimpl_methods[] = {
125135
{"create_traceback", (PyCFunction) _create_traceback, METH_VARARGS, "internal"},
126136
{"_csp_engine_stats", (PyCFunction) _engine_stats, METH_O, "engine statistics"},
127137
{"set_capture_cpp_backtrace", (PyCFunction) _set_capture_cpp_backtrace, METH_VARARGS, "internal"},
138+
{"_csp_in_realtime", (PyCFunction) _csp_in_realtime, METH_O, "is running engine in realtime mode"},
128139
{nullptr}
129140
};
130141

csp/impl/builtin_functions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,12 @@ def remove_dynamic_key(basket: GenericTSTypes["T"].TS_TYPE, key: Any):
318318
)
319319

320320

321+
@csp_builtin
322+
def in_realtime() -> bool:
323+
"""Returns whether the running engine is in realtime or sim mode"""
324+
raise RuntimeError("Unexpected use of csp.in_realtime, csp.in_realtime can only be used inside a node")
325+
326+
321327
@csp_builtin
322328
def engine_start_time() -> datetime:
323329
"""Returns the engine run start time (can be used both in nodes and graphs)"""

csp/impl/wiring/node_parser.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,14 @@ class NodeParser(BaseParser):
7575
_CSP_ENGINE_STATS_FUNC = "_csp_engine_stats"
7676

7777
_CSP_STOP_ENGINE_FUNC = "_csp_stop_engine"
78+
_CSP_IN_REALTIME_FUNC = "_csp_in_realtime"
7879
_LOCAL_METHODS = {
7980
_CSP_NOW_FUNC: _cspimpl._csp_now,
8081
_CSP_ENGINE_START_TIME_FUNC: _cspimpl._csp_engine_start_time,
8182
_CSP_ENGINE_END_TIME_FUNC: _cspimpl._csp_engine_end_time,
8283
_CSP_STOP_ENGINE_FUNC: _cspimpl._csp_stop_engine,
8384
_CSP_ENGINE_STATS_FUNC: _cspimpl._csp_engine_stats,
85+
_CSP_IN_REALTIME_FUNC: _cspimpl._csp_in_realtime,
8486
}
8587

8688
_SPECIAL_BLOCKS_METH = {"alarms", "state", "start", "stop", "outputs"}
@@ -586,6 +588,13 @@ def _parse_now(self, node):
586588
func=ast.Name(id=self._CSP_NOW_FUNC, ctx=ast.Load()), args=[self._node_proxy_expr()], keywords=[]
587589
)
588590

591+
def _parse_in_realtime(self, node):
592+
if len(node.args) or len(node.keywords):
593+
raise CspParseError("csp.in_realtime takes no arguments", node.lineno)
594+
return ast.Call(
595+
func=ast.Name(id=self._CSP_IN_REALTIME_FUNC, ctx=ast.Load()), args=[self._node_proxy_expr()], keywords=[]
596+
)
597+
589598
def _parse_stop_engine(self, node):
590599
args = [self._node_proxy_expr()] + node.args
591600
return ast.Call(func=ast.Name(id=self._CSP_STOP_ENGINE_FUNC, ctx=ast.Load()), args=args, keywords=node.keywords)
@@ -888,6 +897,7 @@ def _init_internal_maps(cls):
888897
"csp.make_passive": cls._make_single_proxy_arg_func_resolver(builtin_functions.make_passive),
889898
"csp.make_active": cls._make_single_proxy_arg_func_resolver(builtin_functions.make_active),
890899
"csp.remove_dynamic_key": cls._parse_remove_dynamic_key,
900+
"csp.in_realtime": cls._parse_in_realtime,
891901
# omit this as its handled in a special case
892902
# 'csp.alarm': cls._parse_alarm,
893903
"csp.schedule_alarm": cls._parse_schedule_alarm,

csp/tests/test_engine.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import numpy as np
1616
import psutil
1717
import pytest
18+
from pytz import UTC
1819

1920
import csp
2021
from csp import PushMode, ts
@@ -415,6 +416,75 @@ def times(x: ts[bool]) -> ts[datetime]:
415416
result = csp.run(times(x), starttime=datetime(2020, 2, 7, 9), endtime=timedelta(seconds=10))[0]
416417
self.assertEqual([v[0] for v in result], [v[1] for v in result])
417418

419+
def test_csp_in_realtime__historical(self):
420+
@csp.node
421+
def is_in_realtime(x: ts[bool]) -> ts[bool]:
422+
if csp.ticked(x):
423+
return csp.in_realtime()
424+
425+
@csp.graph
426+
def g() -> ts[bool]:
427+
# Symbolic curve of expected booleans
428+
times = csp.curve(
429+
bool,
430+
[(datetime(2025, 12, 24), False)],
431+
)
432+
csp.add_graph_output("in_realtime", is_in_realtime(times))
433+
434+
outputs = csp.run(g, starttime=datetime(2025, 12, 24), endtime=datetime(2025, 12, 25), realtime=False)
435+
assert outputs
436+
assert outputs["in_realtime"][0][1] is False
437+
438+
def test_csp_in_realtime__realtime(self):
439+
def utc_now() -> datetime:
440+
return datetime.now(UTC)
441+
442+
@csp.node
443+
def is_in_realtime(x: ts[bool]) -> ts[bool]:
444+
if csp.ticked(x):
445+
return csp.in_realtime()
446+
447+
@csp.graph
448+
def g() -> ts[bool]:
449+
# Symbolic curve of expected booleans
450+
times = csp.curve(
451+
bool,
452+
[(timedelta(seconds=10), True)],
453+
)
454+
csp.add_graph_output("in_realtime", is_in_realtime(times))
455+
csp.stop_engine(times)
456+
457+
outputs = csp.run(g, starttime=utc_now(), endtime=utc_now() + timedelta(seconds=30), realtime=True)
458+
assert outputs
459+
assert outputs["in_realtime"][0][1] is True
460+
461+
@pytest.mark.skipif(not os.environ.get("CSP_ENGINE_FLAKY_TESTS"), reason="potentially flaky test")
462+
def test_csp_in_realtime(self):
463+
def utc_now() -> datetime:
464+
return datetime.now(UTC)
465+
466+
@csp.node
467+
def is_in_realtime(x: ts[bool]) -> ts[bool]:
468+
if csp.ticked(x):
469+
assert csp.in_realtime() == x
470+
return csp.in_realtime()
471+
472+
@csp.graph
473+
def g() -> ts[bool]:
474+
# Symbolic curve of expected booleans
475+
times = csp.curve(
476+
bool,
477+
[(utc_now() - timedelta(seconds=3), False), (utc_now() + timedelta(seconds=3), True)],
478+
)
479+
csp.add_graph_output("in_realtime", is_in_realtime(times))
480+
481+
outputs = csp.run(
482+
g, starttime=utc_now() - timedelta(seconds=5), endtime=utc_now() + timedelta(seconds=10), realtime=True
483+
)
484+
assert outputs
485+
assert outputs["in_realtime"][0][1] is False
486+
assert outputs["in_realtime"][1][1] is True
487+
418488
def test_stop_engine(self):
419489
@csp.node
420490
def stop(x: ts[bool]) -> ts[bool]:

0 commit comments

Comments
 (0)