Skip to content

Commit 4517c95

Browse files
committed
minor changes
1 parent e6fa458 commit 4517c95

5 files changed

Lines changed: 184 additions & 19 deletions

File tree

docs/data/wec-models.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,19 @@ WEC-Grid includes two pre-configured wave energy converter models for simulation
2626

2727
## Wave-to-Wire Model Architecture
2828

29-
<div style="clear: both; text-align: center;">
30-
<img src="../../assets/lupa_simulink.png" alt="Wave-to-Wire" style="width: 100%; height: auto;">
31-
</div>
29+
### Wave-to-Wire Model Architecture
30+
31+
<!-- <div style="clear: both; text-align: center;">
32+
<img src="../../assets/lupa_simulink.png" alt="LUPA Wave-to-Wire Model Architecture" style="width: 100%; height: auto;">
33+
<p style="font-style: italic; margin-top: 8px; color: #666;">
34+
Figure 1: Complete wave-to-wire model architecture showing the integration of WEC-Sim hydrodynamic simulation with power take-off systems, electrical conversion, and grid interface components.
35+
</p>
36+
</div> -->
37+
38+
<figure markdown="span">
39+
![LUPA Wave-to-Wire Model Architecture](../../assets/lupa_simulink.png){ width="300" }
40+
<figcaption>Complete wave-to-wire model architecture showing the integration of WEC-Sim hydrodynamic simulation with power take-off systems, electrical conversion, and grid interface components.</figcaption>
41+
</figure>
3242

3343

3444
In the available models below we have a custom Wave-to-Wire model that captures the full energy conversion chain from ocean waves to grid-delivered electricity.

docs/wecgrid/plot.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,25 @@ fig, ax = engine.plot.gen(software="pypsa", parameter="p", gen=["WEC_Farm_1"])
6666
```python
6767
# Compare PSS®E vs PyPSA bus voltages
6868
plotter = WECGridPlot(engine)
69-
fig, ax, metrics = plotter.compare_modelers(
69+
fig, ax = plotter.compare_modelers(
7070
grid_component="bus",
7171
name=["Bus_1", "Bus_5"],
7272
parameter="v_mag",
73-
annotate=True, # overlay metrics on figure
73+
# annotate=False, # default: do not overlay metrics on figure
7474
print_metrics=True, # print metrics to console
7575
)
76+
```
77+
78+
To get only the metrics DataFrame (and skip showing the figure):
79+
80+
```python
81+
metrics = plotter.compare_modelers(
82+
grid_component="bus",
83+
name=["Bus_1", "Bus_5"],
84+
parameter="v_mag",
85+
dataframe=True, # return DataFrame, do not show plot
86+
print_metrics=False, # optional: silence console output
87+
)
7688
print(metrics)
7789
```
7890

docs/wecgrid/wecsim.md

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ MATLAB Engine → Model Directory → Set Parameters → w2gSim() → Results
6161
- `set_wec_sim_path()` — Configure WEC-Sim installation location
6262

6363
- **Simulation Control**
64-
- `__call__()` — Execute complete WEC device simulation with parameters
64+
- `simulate()` — Execute complete WEC device simulation with parameters
6565
- Wave parameters: height [m], period [s], spectrum type, random seed
6666
- Simulation parameters: duration [s], time step [s], model path
6767

@@ -225,3 +225,125 @@ wec_sim_id = wecsim(
225225
- Reduce simulation length for testing workflows
226226
- Increase time step for faster execution (trade-off with accuracy)
227227
- Use smaller wave heights to avoid numerical instabilities
228+
229+
### Example Run
230+
231+
elow is an example of running a full 24 wec simulation of the RM3 wave energy model.
232+
233+
# Run WEC-Sim simulation of the RM3 wave energy model
234+
235+
```python
236+
237+
runner.wecsim(
238+
model_path="./wec/RM3",
239+
sim_length=3600 * 24, # 24 hours in seconds
240+
delta_time=0.1,
241+
spectrum_type='PM',
242+
wave_class='irregular',
243+
wave_height=2.5,
244+
wave_period=8.0,
245+
wave_seed = random.randint(1, 100),
246+
)
247+
248+
```
249+
```bash
250+
251+
⠀ WEC-SIM⠀⠀⠀⠀ ⣠⣴⣶⠾⠿⠿⠯⣷⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
252+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⣾⠛⠁⠀⠀⠀⠀⠀⠀⠈⢻⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
253+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⠿⠁⠀⠀⠀⢀⣤⣾⣟⣛⣛⣶⣬⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
254+
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⠟⠃⠀⠀⠀⠀⠀⣾⣿⠟⠉⠉⠉⠉⠛⠿⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
255+
⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⡟⠋⠀⠀⠀⠀⠀⠀⠀⣿⡏⣤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
256+
⠀⠀⠀⠀⠀⠀⠀⣠⡿⠛⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣷⡍⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣤⣤⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
257+
⠀⠀⠀⠀⠀⣠⣼⡏⠀⠀ ⠈⠙⠷⣤⣤⣠⣤⣤⡤⡶⣶⢿⠟⠹⠿⠄⣿⣿⠏⠀⣀⣤⡦⠀⠀⠀⠀⣀⡄
258+
⢀⣄⣠⣶⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠓⠚⠋⠉⠀⠀⠀⠀⠀⠀⠈⠛⡛⡻⠿⠿⠙⠓⢒⣺⡿⠋⠁
259+
⠉⠉⠉⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠁⠀
260+
261+
Starting MATLAB Engine... MATLAB engine started.
262+
Adding WEC-SIM to path... WEC-SIM path added.
263+
Starting WEC-SIM simulation...
264+
Model: RM3
265+
Model Path: ./wec/RM3
266+
Simulation Length: 86400 seconds
267+
Time Step: 0.1 seconds
268+
Wave class: irregular
269+
Wave Height: 2.5 m
270+
Wave Period: 8.0 s
271+
272+
simulation complete... writing to database at
273+
c:\Users\alexb\research\WEC-GRID\examples\WECGrid.db
274+
WEC-SIM complete: model = RM3, wec_sim_id = 1, duration = 86400s
275+
MATLAB Output:
276+
==========
277+
WEC-Sim: An open-source code for simulating wave energy converters
278+
Version: 5.0.1
279+
280+
Initializing the Simulation Class...
281+
Case Dir: C:\Users\alexb\research\WEC-GRID\examples\wec\RM3
282+
283+
WEC-Sim Input From Standard wecSimInputFile.m Of Case Directory...
284+
WEC-Sim: An open-source code for simulating wave energy converters
285+
Version: 5.0.1
286+
287+
Initializing the Simulation Class...
288+
Case Dir: C:\Users\alexb\research\WEC-GRID\examples\wec\RM3
289+
Elapsed time is 0.399332 seconds.
290+
291+
WEC-Sim Pre-processing ...
292+
Infinite water depth specified in BEM and "waves.waterDepth" not specified in input file.
293+
Set water depth to 200m for visualization.
294+
Elapsed time is 24.918356 seconds.
295+
296+
WEC-Sim Simulation Settings:
297+
Start Time (sec) = 0
298+
End Time (sec) = 86400
299+
Time Step Size (sec) = 0.1
300+
Ramp Function Time (sec) = 0
301+
Convolution Integral Interval (sec) = 60
302+
Number of Time Steps = 864000
303+
304+
Wave Environment:
305+
Wave Type = Irregular Waves (Predefined Random Phase)
306+
Spectrum Type = Pierson-Moskowitz
307+
Significant Wave Height, Hs (m) = 2.5
308+
Peak Wave Period, Tp (sec) = 8
309+
310+
List of Body: Number of Bodies = 2
311+
312+
***** Body Number 1, Name: float *****
313+
Body CG (m) = [0,0,-0.72]
314+
Body Mass (kg) = 725834
315+
Body Diagonal MOI (kgm2)= [2.09073E+07,2.13061E+07,3.70855E+07]
316+
317+
***** Body Number 2, Name: spar *****
318+
Body CG (m) = [0,0,-21.29]
319+
Body Mass (kg) = 886691
320+
Body Diagonal MOI (kgm2)= [9.44196E+07,9.44071E+07,2.85422E+07]
321+
322+
List of PTO(s): Number of PTOs = 1
323+
324+
***** PTO Name: PTO1 *****
325+
PTO Stiffness (N/m;Nm/rad) = 0
326+
PTO Damping (Ns/m;Nsm/rad) = 0
327+
328+
List of Constraint(s): Number of Constraints = 1
329+
330+
***** Constraint Name: Constraint1 *****
331+
332+
333+
Simulating the WEC device defined in the SimMechanics model C:\Users\alexb\research\WEC-GRID\examples\wec\RM3\W2G_ss_RM3.slx...
334+
Elapsed time is 5.154023 seconds.
335+
Elapsed time is 802.377574 seconds.
336+
337+
Post-processing and saving...
338+
Elapsed time is 8.199315 seconds.
339+
Inserting simulation metadata...
340+
model_type: RM3 (class: char)
341+
wave_spectrum: PM (class: char)
342+
wave_class: irregular (class: char)
343+
sim_hash: RM3_2.5m_8.0s_94 (class: char)
344+
WEC-Sim data stored: wec_sim_id = 1, 864001 time points
345+
346+
==========
347+
MATLAB engine stopped.
348+
349+
```

src/wecgrid/modelers/wec_sim/runner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ def sim_results(self, df_power, model, wec_sim_id):
254254

255255
plt.show()
256256

257-
def __call__(
257+
def simulate(
258258
self,
259259
model_path: str,
260260
sim_length: int = 3600 * 24, # 24 hours

src/wecgrid/util/plot.py

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,8 @@ def compare_modelers(
671671
grid_component: str,
672672
name: List[str],
673673
parameter: str,
674-
annotate: bool = True,
674+
annotate: bool = False,
675+
dataframe: bool = False,
675676
print_metrics: bool = True,
676677
):
677678
"""Compare a component parameter across PSS®E and PyPSA.
@@ -681,13 +682,15 @@ def compare_modelers(
681682
name: Component name(s) to compare.
682683
parameter: Parameter to compare (e.g., "p", "v_mag").
683684
annotate: If True, overlay metrics text on the figure.
685+
dataframe: If True, return only a metrics DataFrame and do not show the plot.
684686
print_metrics: If True, print metrics to stdout.
685687
686688
Returns:
687-
(Figure, Axes, DataFrame): Matplotlib figure/axes and a DataFrame
688-
with columns ["component", "rmse", "mae", "max_abs_err",
689-
"mape_pct", "nrmse_mean", "nrmse_range", "r", "n"]. The
690-
DataFrame is None if metrics cannot be computed.
689+
- If `dataframe` is False (default): `(Figure, Axes)` for the comparison plot.
690+
- If `dataframe` is True: a pandas `DataFrame` with columns
691+
["component", "rmse", "mae", "max_abs_err", "mape_pct",
692+
"nrmse_mean", "nrmse_range", "r", "n"]. Returns `None` if
693+
metrics cannot be computed.
691694
"""
692695
# Check for available software data
693696
available_software = []
@@ -701,7 +704,7 @@ def compare_modelers(
701704
f"Available: {available_software}. Use add_grid() to add GridState objects "
702705
f"or ensure both 'psse' and 'pypsa' are loaded in the engine."
703706
)
704-
return None, None, None
707+
return None if dataframe else (None, None)
705708

706709
fig, ax = plt.subplots(figsize=(12, 6))
707710

@@ -890,8 +893,12 @@ def compare_modelers(
890893
s1 = df_psse[col]
891894
s2 = df_pypsa[col]
892895
mask = s1.notna() & s2.notna()
893-
s1v = s1[mask].values
894-
s2v = s2[mask].values
896+
# Coerce to numeric and drop any non-numeric remnants
897+
s1c = pd.to_numeric(s1[mask], errors="coerce")
898+
s2c = pd.to_numeric(s2[mask], errors="coerce")
899+
mask2 = s1c.notna() & s2c.notna()
900+
s1v = s1c[mask2].to_numpy(dtype=float).ravel()
901+
s2v = s2c[mask2].to_numpy(dtype=float).ravel()
895902
if len(s1v) == 0:
896903
rmse = np.nan
897904
mae = np.nan
@@ -924,10 +931,19 @@ def compare_modelers(
924931
mape_pct = float(100.0 * np.mean(np.abs(err[nonzero] / s1v[nonzero])))
925932
else:
926933
mape_pct = np.nan
927-
if len(s1v) < 2 or np.std(s1v) == 0 or np.std(s2v) == 0:
934+
# Pearson correlation (manual) with ddof=1
935+
if len(s1v) < 2:
928936
corr = np.nan
929937
else:
930-
corr = float(np.corrcoef(s1v, s2v)[0, 1])
938+
sx = float(np.std(s1v, ddof=1))
939+
sy = float(np.std(s2v, ddof=1))
940+
if sx <= eps or sy <= eps:
941+
corr = np.nan
942+
else:
943+
xm = float(np.mean(s1v))
944+
ym = float(np.mean(s2v))
945+
cov = float(np.sum((s1v - xm) * (s2v - ym)) / (len(s1v) - 1))
946+
corr = float(cov / (sx * sy))
931947
n = int(len(s1v))
932948

933949
# Prefer PSSE friendly name, then PYPSA, else the key
@@ -1008,5 +1024,10 @@ def compare_modelers(
10081024
pass # Fall back to default formatting if anything goes wrong
10091025

10101026
plt.tight_layout()
1011-
plt.show()
1012-
return fig, ax, metrics_df
1027+
# Return depending on 'dataframe' flag
1028+
if dataframe:
1029+
# Do not display the figure when only metrics are requested
1030+
return metrics_df
1031+
else:
1032+
plt.show()
1033+
return fig, ax

0 commit comments

Comments
 (0)