Skip to content

Commit a1b04ed

Browse files
authored
Merge pull request #3 from dnv-opensource/update-clean
Update clean
2 parents 130de24 + 67263e1 commit a1b04ed

5 files changed

Lines changed: 142 additions & 78 deletions

File tree

.github/workflows/publish_release.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,26 @@ on:
99
jobs:
1010
build_package:
1111
uses: ./.github/workflows/_build_package.yml
12+
create_release:
13+
name: Create GitHub Release
14+
needs:
15+
- build_package
16+
runs-on: ubuntu-latest
17+
permissions:
18+
contents: write
19+
steps:
20+
- name: Checkout code
21+
uses: actions/checkout@v5
22+
- name: Download build artifacts
23+
uses: actions/download-artifact@v5
24+
with:
25+
name: dist
26+
path: dist
27+
- name: Create GitHub Release
28+
uses: softprops/action-gh-release@v2
29+
with:
30+
generate_release_notes: true
31+
files: dist/*
1232
# publish_package:
1333
# name: Publish package
1434
# needs:

README.md

Lines changed: 54 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,15 @@ print(projects)
6767

6868
### Running a Simulation
6969

70-
The following example runs a non-interactive distributed simulation using a
71-
Spring-Mass-Damper project that has already been configured on the STC platform.
70+
The following example runs a **single non-interactive** distributed simulation
71+
using a Spring-Mass-Damper project that has already been configured on the STC
72+
platform. A simulation is non-interactive when the simulator runs to completion
73+
without user intervention (create → run → finish).
7274

7375
```py
7476
import time
7577

76-
from pystclient.clients import PyStclient, SimulationResults
78+
from pystclient.clients import PyStclient
7779
from pystclient.models import (
7880
LoggingConfiguration,
7981
ModelParameters,
@@ -119,80 +121,88 @@ client.project.update_log_config(
119121
project.id, LoggingConfiguration(post_plotting=True)
120122
)
121123

122-
# 5 — Create a simulator and wait until it is ready
123-
statuses, ready_future = client.simulator.create(
124+
# 5 — Create a non-interactive distributed simulator and wait until it is ready
125+
statuses, future = client.simulator.create(
124126
SimulationConfig(
125127
project_id=project.id,
126128
parameter_set_names=["Config 1"],
127129
type=SimulationType.DISTRIBUTED,
128130
)
129131
)
130132
simulator_id = statuses[0].id
131-
assert ready_future.result(600), "Simulator did not become ready in time!"
132-
133-
# 6 — Start the simulation with recording enabled
134-
client.simulator.start(simulator_id, record=True)
133+
assert future.result(600), "Simulator did not become ready in time!"
135134

136-
# 7 — Poll until the simulation finishes
135+
# 6 — Poll until the simulation finishes
137136
while not client.simulator.finished(simulator_id):
138137
s = client.simulator.status(simulator_id)
139138
print(f" simulation_time={s.simulation_time} end_time={s.end_time}")
140-
time.sleep(2)
139+
time.sleep(1)
141140

142-
# 8 — End the simulation and collect results
143-
results: SimulationResults | None = client.simulator.end_simulation(simulator_id).result(120)
144-
assert results is not None, "No results returned!"
145-
print(f"Results available — {results.measurement_size()} measurement(s).")
141+
print("Simulation finished.")
146142
```
147143

148144
### Fetching and Displaying Results
149145

150-
Once a simulation has completed and a `SimulationResults` object is available
151-
(see the previous example), you can iterate over the time-series data and
152-
plot it with [matplotlib](https://matplotlib.org/):
146+
Once a simulation has completed, you can retrieve it from the list of
147+
completed simulations and query time-series data for plotting with
148+
[matplotlib](https://matplotlib.org/):
153149

154150
```py
151+
import time
152+
155153
import matplotlib.pyplot as plt
156154

157-
from pystclient.models import QueryVariable
155+
from pystclient.models import MeasurementQuery, QueryVariable, SimulationInfo
156+
from pystclient.types import FmuCausalityType
158157
from pystclient.utils.time import convert_to_timestamp
159158

160-
# Reset the results iterator to start from the beginning
161-
results.reset()
159+
# Wait for the simulation to appear in the completed list
160+
completed_simulations: list[SimulationInfo] = []
162161

163-
# Iterate through all time windows and collect displacement data
162+
while len(completed_simulations) != 1:
163+
completed_simulations = client.project.completed_simulations(
164+
project_id=project.id,
165+
simulator_ids=[simulator_id],
166+
limit=10,
167+
)
168+
time.sleep(5)
169+
170+
sim = completed_simulations[0]
171+
print(f"Simulation {sim.id} name={sim.name} param_set={sim.parameter_set_name}")
172+
173+
# Retrieve measurements and query specific variable data
174+
sim_measurements = client.measurement.measurements(project.id, sim.id)
175+
assert sim_measurements, "No measurements found!"
176+
177+
measurement = sim_measurements[0]
178+
q = MeasurementQuery(
179+
variables=[
180+
QueryVariable(
181+
instance_name="Spring1",
182+
name="dis_yx",
183+
causality=FmuCausalityType.INPUT,
184+
)
185+
],
186+
time_from=0,
187+
time_to=10,
188+
)
189+
results = client.measurement.query(measurement.id, q)
190+
191+
# Plot the displacement data
164192
fig, ax = plt.subplots(figsize=(10, 5))
165193

166-
for query_result in results:
167-
for result in query_result:
168-
if result.signal == "dis_yx":
169-
x = convert_to_timestamp(result.x)
170-
ax.plot(x, result.y, label=f"{result.module}{result.signal}")
194+
for result in results:
195+
x = convert_to_timestamp(result.x)
196+
ax.plot(x, result.y, label=f"{sim.parameter_set_name}")
171197

172198
ax.set_xlabel("Time [s]")
173199
ax.set_ylabel("Displacement [m]")
174-
ax.set_title("Spring-Mass-Damper — Displacement (dis_yx)")
200+
ax.set_title("Spring-Mass-Damper — Spring Displacement (dis_yx)")
175201
ax.legend()
176202
plt.tight_layout()
177203
plt.show()
178204
```
179205

180-
You can also narrow the query to specific variables using `query_variables`:
181-
182-
```py
183-
results.reset(
184-
query_variables=[
185-
QueryVariable(instance_name="Mass1", name="dis_yx", causality="output"),
186-
]
187-
)
188-
189-
for query_result in results:
190-
for result in query_result:
191-
print(f"{result.module}.{result.signal}: {len(result.y)} data points")
192-
```
193-
194-
> **Tip**: Use `results.step(timedelta(minutes=5))` to change the size of
195-
> each time window when paging through results.
196206

197207
> **See also**: For complete, runnable notebooks check the
198208
> [`examples/`](https://github.com/dnv-opensource/pystclient/tree/main/examples) directory.

examples/create_simulation.ipynb

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@
9696
"source": [
9797
"import time\n",
9898
"\n",
99-
"from pystclient.clients import SimulationResults\n",
10099
"from pystclient.models import SimulationConfig\n",
101100
"from pystclient.types import SimulationType\n",
102101
"\n",
@@ -115,33 +114,53 @@
115114
"assert future.result(600), \"Simulator did not become ready in time!\"\n",
116115
"print(\"Simulator is ready.\")\n",
117116
"\n",
118-
"# Start the simulation with recording enabled\n",
119-
"assert client.simulator.start(simulator_id, record=True)\n",
120-
"print(\"Simulation started (recording).\")\n",
121-
"\n",
122117
"# Poll until the simulation finishes\n",
123118
"while not client.simulator.finished(simulator_id):\n",
124119
" s = client.simulator.status(simulator_id)\n",
125120
" print(f\" simulation_time={s.simulation_time} end_time={s.end_time}\")\n",
126-
" time.sleep(2)\n",
121+
" time.sleep(1)\n",
127122
"\n",
128-
"print(\"Simulation finished.\")\n",
123+
"print(\"Simulation finished.\")"
124+
],
125+
"id": "65af6192d52560c8",
126+
"outputs": [],
127+
"execution_count": null
128+
},
129+
{
130+
"metadata": {},
131+
"cell_type": "markdown",
132+
"source": "### 2.1 - Retrieve completed simulation\n",
133+
"id": "37af90877416c0df"
134+
},
135+
{
136+
"metadata": {},
137+
"cell_type": "code",
138+
"source": [
139+
"from pystclient.models import SimulationInfo\n",
129140
"\n",
130-
"# End the simulation and wait for measurement results\n",
131-
"results_future = client.simulator.end_simulation(simulator_id)\n",
132-
"single_results: SimulationResults | None = results_future.result(120)\n",
133-
"assert isinstance(single_results, SimulationResults), \"No results returned!\"\n",
134-
"print(f\"Results available - {single_results.measurement_size()} measurement(s).\")"
141+
"# Wait for the simulation to appear in the completed list\n",
142+
"completed_simulations: list[SimulationInfo] = []\n",
143+
"\n",
144+
"while len(completed_simulations) != 1:\n",
145+
" completed_simulations = client.project.completed_simulations(\n",
146+
" project_id=project_info.id,\n",
147+
" simulator_ids=[simulator_id],\n",
148+
" limit=10,\n",
149+
" )\n",
150+
" time.sleep(5)\n",
151+
"\n",
152+
"sim = completed_simulations[0]\n",
153+
"print(f\" Simulation {sim.id} name={sim.name} param_set={sim.parameter_set_name}\")"
135154
],
136-
"id": "e54c6b3ba6e91089",
155+
"id": "2b0fa396aa1c8ff0",
137156
"outputs": [],
138157
"execution_count": null
139158
},
140159
{
141160
"cell_type": "markdown",
142161
"id": "c44323e06877457f",
143162
"metadata": {},
144-
"source": "### 2.1 - Plot displacement from single simulation\n"
163+
"source": "### 2.2 - Plot displacement from single simulation\n"
145164
},
146165
{
147166
"cell_type": "code",
@@ -151,19 +170,33 @@
151170
"# pyright: reportUnknownMemberType=false\n",
152171
"import matplotlib.pyplot as plt\n",
153172
"\n",
154-
"from pystclient.models import QueryResult\n",
173+
"from pystclient.models import MeasurementQuery, QueryVariable\n",
174+
"from pystclient.types import FmuCausalityType\n",
155175
"from pystclient.utils.time import convert_to_timestamp\n",
156176
"\n",
157-
"single_results.reset()\n",
177+
"sim = completed_simulations[0]\n",
178+
"sim_measurements = client.measurement.measurements(project_info.id, sim.id)\n",
179+
"assert sim_measurements, \"No measurements found!\"\n",
180+
"\n",
181+
"measurement = sim_measurements[0]\n",
182+
"q = MeasurementQuery(\n",
183+
" variables=[\n",
184+
" QueryVariable(\n",
185+
" instance_name=\"Spring1\",\n",
186+
" name=\"dis_yx\",\n",
187+
" causality=FmuCausalityType.INPUT,\n",
188+
" )\n",
189+
" ],\n",
190+
" time_from=0,\n",
191+
" time_to=10,\n",
192+
")\n",
193+
"results = client.measurement.query(measurement.id, q)\n",
158194
"\n",
159195
"fig, ax = plt.subplots(figsize=(10, 5))\n",
160196
"\n",
161-
"for query_result in single_results:\n",
162-
" for result in query_result:\n",
163-
" result: QueryResult\n",
164-
" if result.signal == \"dis_yx\":\n",
165-
" x = convert_to_timestamp(result.x)\n",
166-
" ax.plot(x, result.y, label=f\"{result.module} - {result.signal}\")\n",
197+
"for result in results:\n",
198+
" x = convert_to_timestamp(result.x)\n",
199+
" ax.plot(x, result.y, label=f\"{sim.parameter_set_name}\")\n",
167200
"\n",
168201
"ax.set_xlabel(\"Time [s]\")\n",
169202
"ax.set_ylabel(\"Displacement [m]\")\n",

tests/stc/conftest.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -67,16 +67,16 @@ def test_models_by_version_id() -> list[FmuSelect]:
6767
FmuSelect(version=uuid.UUID(x))
6868
for x in (
6969
[
70-
"21B8F9DF-67F5-4A24-9325-45538E2E6EE0", # Spring
71-
"EE1EFF08-8958-495C-ADAF-1156E296524C", # Mass
72-
"830B4F41-BC6C-4A5F-B12D-8F81FB449626", # Damper
73-
]
74-
if any(x in API_ENDPOINT for x in ["dnvgl-osp-api-dev", "localhost", "127.0.0.1"])
75-
else [
7670
"592B1E80-79B3-4E7C-8304-D360B942176D", # Spring
7771
"54B95A5F-0936-4BBD-8011-F3CCF934A2B1", # Mass
7872
"373EA52B-3BF1-4190-AEE2-D28EC37871F2", # Damper
7973
]
74+
if "api.stc.dnv.com" in API_ENDPOINT
75+
else [
76+
"21B8F9DF-67F5-4A24-9325-45538E2E6EE0", # Spring
77+
"EE1EFF08-8958-495C-ADAF-1156E296524C", # Mass
78+
"830B4F41-BC6C-4A5F-B12D-8F81FB449626", # Damper
79+
]
8080
)
8181
]
8282
return fmus
@@ -88,16 +88,16 @@ def test_models_by_model_id() -> list[FmuSelect]:
8888
FmuSelect(id=uuid.UUID(x))
8989
for x in (
9090
[
91-
"95BD75BD-26DF-47DD-B353-49A460FCF83F", # Spring
92-
"CBEBAEE0-2C4D-4746-B26C-BDAFC6C02247", # Mass
93-
"88EAF9B4-BAAB-449C-97A1-FEE85CDFE38C", # Damper
94-
]
95-
if any(x in API_ENDPOINT for x in ["dnvgl-osp-api-dev", "localhost"])
96-
else [
9791
"869699A2-8945-4D48-B135-AB0AEBD292CA", # Spring
9892
"9D872C71-9589-4171-B2D0-3374CC947F70", # Mass
9993
"617EED59-21BF-438A-8524-0C09C7F8D5FF", # Damper
10094
]
95+
if "api.stc.dnv.com" in API_ENDPOINT
96+
else [
97+
"95BD75BD-26DF-47DD-B353-49A460FCF83F", # Spring
98+
"CBEBAEE0-2C4D-4746-B26C-BDAFC6C02247", # Mass
99+
"88EAF9B4-BAAB-449C-97A1-FEE85CDFE38C", # Damper
100+
]
101101
)
102102
]
103103
return fmus

tests/stc/test_simulation.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ def test_run_simulation(
6464
project_id=test_project.id,
6565
parameter_set_names=["Config 1"],
6666
type=SimulationType.DISTRIBUTED,
67+
is_interactive=True,
6768
)
6869
)
6970
simulator_id = status[0].id

0 commit comments

Comments
 (0)