Skip to content

Commit a4634ac

Browse files
feat(simint-alpha): dev experience of simulation run data (#2158)
Co-authored-by: Ivan Polomani <ivan.polomanyi@cognite.com>
1 parent 0c3fc37 commit a4634ac

3 files changed

Lines changed: 169 additions & 0 deletions

File tree

cognite/client/data_classes/simulators/runs.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@
1717
)
1818
from cognite.client.data_classes.simulators.logs import SimulatorLog
1919
from cognite.client.utils._experimental import FeaturePreviewWarning
20+
from cognite.client.utils._importing import local_import
2021
from cognite.client.utils._retry import Backoff
22+
from cognite.client.utils._text import to_snake_case
2123

2224
if TYPE_CHECKING:
25+
import pandas
26+
2327
from cognite.client import CogniteClient
2428

2529
_WARNING = FeaturePreviewWarning(api_maturity="General Availability", sdk_maturity="alpha", feature_name="Simulators")
@@ -236,6 +240,18 @@ def get_logs(self) -> SimulatorLog | None:
236240
"""
237241
return self._cognite_client.simulators.logs.retrieve(id=self.log_id)
238242

243+
def get_data(self) -> SimulationRunDataItem | None:
244+
"""`Retrieve data associated with this simulation run. <https://developer.cognite.com/api#tag/Simulation-Runs/operation/simulation_data_by_run_id_simulators_runs_data_list_post>`_
245+
246+
Returns:
247+
SimulationRunDataItem | None: Data for the simulation run.
248+
"""
249+
data = self._cognite_client.simulators.runs.list_run_data(run_id=self.id)
250+
if data:
251+
return data[0]
252+
253+
return None
254+
239255
def update(self) -> None:
240256
"""Update the simulation run object to the latest state. Useful if the run was created with wait=False."""
241257
# same logic as Cognite Functions
@@ -424,10 +440,56 @@ def dump(self, camel_case: bool = True) -> dict[str, Any]:
424440
output["outputs"] = [output_.dump(camel_case=camel_case) for output_ in self.outputs]
425441
return output
426442

443+
def to_pandas( # type: ignore [override]
444+
self,
445+
) -> pandas.DataFrame:
446+
"""Convert the simulation run data to a pandas DataFrame.
447+
448+
Returns:
449+
pandas.DataFrame: The dataframe.
450+
"""
451+
pd = local_import("pandas")
452+
453+
def _create_row(item: SimulationInput | SimulationOutput) -> dict:
454+
"""Create a row dictionary for an input or output item."""
455+
item_type = (isinstance(item, SimulationInput) and "Input") or "Output"
456+
row = {
457+
"run_id": self.run_id,
458+
"type": item_type,
459+
"reference_id": item.reference_id,
460+
"value": item.value,
461+
"unit_name": item.unit.name if item.unit else None,
462+
"value_type": item.value_type,
463+
"overridden": getattr(item, "overridden", None),
464+
"timeseries_external_id": item.timeseries_external_id,
465+
}
466+
467+
if item.simulator_object_reference:
468+
for reference_key, reference_value in item.simulator_object_reference.items():
469+
snake_key = to_snake_case(reference_key)
470+
row[snake_key] = reference_value
471+
472+
return row
473+
474+
rows = [_create_row(item) for item in self.inputs + self.outputs]
475+
476+
return pd.DataFrame(rows)
477+
427478

428479
class SimulatorRunDataList(CogniteResourceList[SimulationRunDataItem], IdTransformerMixin):
429480
_RESOURCE = SimulationRunDataItem
430481

482+
def to_pandas( # type: ignore [override]
483+
self,
484+
) -> pandas.DataFrame:
485+
"""Convert the simulation run data list to a pandas DataFrame.
486+
487+
Returns:
488+
pandas.DataFrame: The dataframe.
489+
"""
490+
pd = local_import("pandas")
491+
return pd.concat([item.to_pandas() for item in self.data], ignore_index=True)
492+
431493

432494
class SimulationRunWriteList(CogniteResourceList[SimulationRunWrite], ExternalIDTransformerMixin):
433495
_RESOURCE = SimulationRunWrite

tests/tests_integration/test_api/test_simulators/test_runs.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ async def test_run_with_wait_and_retrieve(self, cognite_client: CogniteClient, s
9999
assert logs_res is not None
100100
assert logs_res.dump() == logs_res2.dump()
101101

102+
data_res = retrieved_run.get_data()
103+
data_res2 = cognite_client.simulators.runs.list_run_data(run_id=created_run.id)[0]
104+
assert data_res is not None
105+
assert data_res.dump() == data_res2.dump()
106+
102107
def test_create_run(
103108
self, cognite_client: CogniteClient, seed_simulator_routine_revisions, seed_resource_names
104109
) -> None:
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import pytest
2+
3+
from cognite.client.data_classes.simulators.runs import (
4+
SimulationInput,
5+
SimulationOutput,
6+
SimulationRunDataItem,
7+
SimulationValueUnitName,
8+
)
9+
10+
11+
@pytest.mark.dsl
12+
class TestSimulationRunDataItemPandasIntegration:
13+
def test_to_pandas(self):
14+
import pandas as pd
15+
16+
# Create sample data
17+
unit = SimulationValueUnitName(name="C")
18+
inputs = [
19+
SimulationInput(
20+
reference_id="CWT",
21+
value=11.0,
22+
value_type="DOUBLE",
23+
overridden=True,
24+
unit=unit,
25+
),
26+
SimulationInput(
27+
reference_id="CWP",
28+
value=[5.0],
29+
value_type="DOUBLE_ARRAY",
30+
overridden=True,
31+
unit=SimulationValueUnitName(name="bar"),
32+
),
33+
]
34+
outputs = [
35+
SimulationOutput(
36+
reference_id="ST",
37+
simulator_object_reference={"address": "test_out"},
38+
value=18.5,
39+
value_type="DOUBLE",
40+
unit=unit,
41+
)
42+
]
43+
44+
# Create the SimulationRunDataItem
45+
data_item = SimulationRunDataItem(run_id=123, inputs=inputs, outputs=outputs)
46+
47+
# Convert to pandas DataFrame
48+
df = data_item.to_pandas()
49+
50+
# Assertions
51+
assert isinstance(df, pd.DataFrame)
52+
assert df.shape == (3, 9) # 3 rows (2 inputs + 1 output), 9 columns
53+
54+
# Check that the data is correctly formatted
55+
assert list(df.columns) == [
56+
"run_id",
57+
"type",
58+
"reference_id",
59+
"value",
60+
"unit_name",
61+
"value_type",
62+
"overridden",
63+
"timeseries_external_id",
64+
"address",
65+
]
66+
67+
expected_data_rows = [
68+
{
69+
"run_id": 123,
70+
"type": "Input",
71+
"reference_id": "CWT",
72+
"value": 11.0,
73+
"unit_name": "C",
74+
"value_type": "DOUBLE",
75+
"overridden": True,
76+
"timeseries_external_id": None,
77+
},
78+
{
79+
"run_id": 123,
80+
"type": "Input",
81+
"reference_id": "CWP",
82+
"value": [5.0],
83+
"unit_name": "bar",
84+
"value_type": "DOUBLE_ARRAY",
85+
"overridden": True,
86+
"timeseries_external_id": None,
87+
},
88+
{
89+
"run_id": 123,
90+
"type": "Output",
91+
"reference_id": "ST",
92+
"value": 18.5,
93+
"unit_name": "C",
94+
"value_type": "DOUBLE",
95+
"overridden": None,
96+
"timeseries_external_id": None,
97+
"address": "test_out",
98+
},
99+
]
100+
101+
expected_df = pd.DataFrame(expected_data_rows)
102+
pd.testing.assert_frame_equal(df, expected_df)

0 commit comments

Comments
 (0)