Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
31 changes: 14 additions & 17 deletions analysis/BENCHMARK_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,24 @@ single source of truth; the sweep script and the per-flap configs must agree.

## Hydrodynamics
- **Physical plant coordinate:** the time-domain solver measures `P_capture` in the **hinge DOF**.
- **Current post-processing basis:** the committed benchmark still tabulates
`F_exc_Nm` / `B55_Nmsrad` from the CG-referenced H5 files
(`hydroData/vgoswec_{0,10,20,45,90}.h5`).
- **Implication:** `P_opt = |F_exc|^2 / (8 * B55)` is invariant under the CG↔hinge
referral, so the efficiency denominator is only mildly affected, but the
**masking** rule is basis-sensitive. A hinge-basis re-tabulation remains an open
post-processing task.
- **Post-processing basis:** the committed capture-efficiency CSVs now tabulate
`F_exc_Nm` / `B55_Nmsrad` from the **hinge-referenced** H5 files
(`hydroData/hinged_vgoswec_{0,10,20,45,90}.h5`), matching the hinge DOF used by
the controller and `P_capture`.
- **Implication:** `P_opt = |F_exc|^2 / (8 * B55)` remains basis-invariant in exact
referral, and the `masked` rule is now applied on the same hinge basis as the
time-domain capture curves.
- **Hinge-referenced impedance files now matter elsewhere:** controller gain
computation (`opt_passive`, CC) now explicitly uses
`hydro.impedance_h5_file = hydroData/hinged_vgoswec_*.h5`. See
[`../docs/IMPEDANCE_BASIS.md`](../docs/IMPEDANCE_BASIS.md).
- **P_opt (Budal / Falnes bound):** `P_opt = |F_exc|^2 / (8 * B55)`, computed from
body1 pitch hydro (`radiation_damping/components/5_5` and
`excitation/mag[dof=5,dir=0]`), at H = 0.05 m.
- **De-normalization (WEC-Sim / BEMIO convention, rho and g read from each H5,
rho = 1000, g = 9.80665):**
- `B55 = B55_norm * rho * omega` [N*m/(rad/s)] (peak ~3, matches BEMRosetta)
- **De-normalization (WEC-Sim / BEMIO convention, `rho` and `g` read from each H5;
hinged files currently store `rho = 1025`, whereas the earlier CG-basis notes
assumed `rho = 1000`):**
- `B55 = max(0, B55_norm * rho * omega)` [N*m/(rad/s)] (peak ~3, matches BEMRosetta)
- `F_exc = F_exc_norm * rho * g * a` [N*m] (per-amplitude ~174 N*m/m, matches BEMRosetta)
- **Mask rule:** points with `B55 <= 1e-04 N*m*s/rad` are treated as
reactive-limited and omitted. On the 4-12 band all five flaps clear this
Expand All @@ -69,15 +70,11 @@ single source of truth; the sweep script and the per-flap configs must agree.
omega = 7.0 and 7.5). At the 0.5 rad/s grid spacing this edge is under-resolved,
so the exact peak height (~34 %) is grid-sensitive; the shape and location are
robust. Refining the grid near omega ~ 7 would pin the peak value.
- **CG- vs hinge-referenced:** P_opt/eta here use CG-referenced free-flap pitch
hydro, not the hinge-referenced flap. `F_exc` is the raw un-hinge-referred pitch
moment and `alpha` is signed positive to paper over the resulting phase/sign
mismatch.
- **Hinge-referenced tabulation:** P_opt/eta and the `masked` rule are now tabulated
from hinge-referenced flap hydro, so the benchmark notes no longer rely on a
CG-basis workaround or sign-convention patch-up in post-processing.

## Follow-up (next milestone)
- Re-tabulate `F_exc`, `B55`, and the `masked` column from the
**hinge-referenced** coefficients (`hinged_vgoswec_*.h5`) so benchmark masking is
consistent with the hinge DOF used by the plant.
- Implement true complex-conjugate control (theoretical eta_max reference) with
hinge-referenced K_r / B_r, then causal approximations (Korde) and constrained
MPC (Ringwood). Resonance markers return once resonance is defined consistently
Expand Down
13 changes: 9 additions & 4 deletions analysis/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@
| `omega_rads` | Angular frequency [rad/s], `2π/T` |
| `P_capture_W` | Steady-state mean absorbed power from tuned `exc_ff_pid` [W] |
| `P_opt_W` | Theoretical optimum power [W], blank when masked |
| `B55_Nmsrad` | De-normalized pitch radiation damping `B55` [N·m·s/rad] |
| `F_exc_Nm` | De-normalized pitch excitation moment magnitude `|F_exc|` for `A=0.025 m` [N·m] |
| `B55_Nmsrad` | Hinge-basis de-normalized pitch radiation damping `B55` [N·m·s/rad] from `hydroData/hinged_vgoswec_*.h5` |
| `F_exc_Nm` | Hinge-basis de-normalized pitch excitation moment magnitude `|F_exc|` for `A=0.025 m` [N·m] from `hydroData/hinged_vgoswec_*.h5` |
| `eta` | Capture efficiency `η = P_capture / P_opt`; only blank when `masked=true` (`B55 <= 1e-4`) |
| `masked` | `true` where `B55 <= 1e-4` (reactive-limited / undefined `P_opt`, including non-positive `B55`) |
| `masked` | `true` where hinge-basis `B55 <= 1e-4` (reactive-limited / undefined `P_opt`, including non-positive `B55`) |

## Capture-efficiency method (tuned `exc_ff_pid`)

Expand All @@ -75,7 +75,9 @@
- Body: `body1` (flap), ignore `body2`.
- Pitch term: `body1/hydro_coeffs/radiation_damping/components/5_5`.
- Excitation: `body1/hydro_coeffs/excitation/mag` at DOF5 (index 4), direction 0.
- De-normalization: `B55 = B55_norm * rho * omega`, `|F_exc| = mag * rho * g * A`.
- Basis: hinge-referenced `hydroData/hinged_vgoswec_*.h5`, matching the hinge DOF used by `P_capture_W`.
- De-normalization: `B55 = max(0, B55_norm * rho * omega)`, `|F_exc| = mag * rho * g * A`.
- `rho` and `g` are read from each H5 file; the hinged files store `rho = 1025`.
- Wave amplitude fixed to `A = 0.025 m` (`H = 0.05 m`) for both sim and `P_opt`.
- Masking/flagging (essential):
- `B55 <= 1e-4` => `P_opt` undefined (reactive-limited notch), so `η` is not reported/plotted (`masked=true`).
Expand Down Expand Up @@ -112,7 +114,10 @@ python3 scripts/plot_kpkd_surface.py
python3 scripts/capture_efficiency_sweep.py

# 4. Re-plot only, from committed capture-efficiency CSVs
python3 scripts/retabulate_hydro_columns.py --dry-run
python3 scripts/retabulate_hydro_columns.py
python3 scripts/capture_efficiency_sweep.py --plot-only
python3 scripts/cc_capture_efficiency_sweep.py --plot-only
python3 scripts/cc_vs_ffpid_comparison.py --plot-only
python3 scripts/plot_opt_passive_stage.py
```
8 changes: 7 additions & 1 deletion docs/EOD_SUMMARY_2026-09-19.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,16 @@ The owner should be able to `git pull` and immediately re-run the `opt_passive`

1. **Highest priority:** ff+PID guard-fire fraction was never measured. Instrument `ExcitationVelocityController::ComputeForce` to count `tau*vel > 0` events and run VGM-0 at `T = 4.50 s`. If the guard fires near 100% of the time, the long-period band is the passive-safety floor rather than feedforward control, and the claim *"ff+PID carries the long-period tail"* must be rewritten.
2. The `2.44488972 W` headline is still unconfirmed as settled (`430 s` vs `760 s` check still needed).
3. Sweep scripts still tabulate `F_exc_Nm` / `B55_Nmsrad` from **CG** H5 files while `P_capture` is measured in the hinge DOF. `P_opt = F^2/(8B)` is basis-invariant so `eta` is only mildly affected, but `masked` is not. Hinge-basis re-tabulation is still needed as post-processing.
3. **Resolved in follow-up PR:** sweep post-processing now reads hinge-referenced
`hydroData/hinged_vgoswec_*.h5`, clamps de-normalized `B55 >= 0` in Python to
match `src/impedance.cpp`, and can re-tabulate existing `analysis/{passive,opt_passive}`
CSVs in place without re-running simulations (`scripts/retabulate_hydro_columns.py`).
4. `analysis/FINDINGS_3REGIME.md` hand-written tables are still stale.
5. There remains a uniform `~1.4%` residual between corrected analytic `omega_n` and free-decay (`single-DOF analytic model` vs `coupled plant`).
6. `[impedance] INFO: legacy A55-match rho=...` still prints more often than intended and should become truly once-per-process.
7. The isolated single-point `P_capture` dips near resonance (notably VGM-0 `T=6.00 s`,
VGM-45 `T=3.50 s`, VGM-90 `T=3.00 s`) remain an open question; no controller or
hydro post-processing change in this PR attempts to explain or alter them.

---

Expand Down
Binary file added docs/img/opt_passive_stage_all_flaps.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/opt_passive_stage_gain.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/img/opt_passive_stage_per_flap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 6 additions & 6 deletions scripts/capture_efficiency_sweep.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,27 +65,27 @@
0: {
"label": "VGM-0",
"config": "config/vgoswec_0_exc_ff_pid.yaml",
"h5": "hydroData/vgoswec_0.h5",
"h5": "hydroData/hinged_vgoswec_0.h5",
},
10: {
"label": "VGM-10",
"config": "config/vgoswec_10_exc_ff_pid.yaml",
"h5": "hydroData/vgoswec_10.h5",
"h5": "hydroData/hinged_vgoswec_10.h5",
},
20: {
"label": "VGM-20",
"config": "config/vgoswec_20_exc_ff_pid.yaml",
"h5": "hydroData/vgoswec_20.h5",
"h5": "hydroData/hinged_vgoswec_20.h5",
},
45: {
"label": "VGM-45",
"config": "config/vgoswec_45_exc_ff_pid.yaml",
"h5": "hydroData/vgoswec_45.h5",
"h5": "hydroData/hinged_vgoswec_45.h5",
},
90: {
"label": "VGM-90",
"config": "config/vgoswec_90_exc_ff_pid.yaml",
"h5": "hydroData/vgoswec_90.h5",
"h5": "hydroData/hinged_vgoswec_90.h5",
},
}

Expand Down Expand Up @@ -208,7 +208,7 @@ def popt_curve_from_h5(h5_path: Path, periods_s: np.ndarray) -> tuple[np.ndarray
b55_norm = b55_norm[order]
fexc_norm = fexc_norm[order]

b55 = b55_norm * rho * w
b55 = np.maximum(0.0, b55_norm * rho * w)
fexc = fexc_norm * rho * g * WAVE_AMPLITUDE_M

omega_targets = (2.0 * math.pi) / periods_s
Expand Down
12 changes: 6 additions & 6 deletions scripts/cc_capture_efficiency_sweep.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@
REACTIVE_NOTE = f"|P_capture| / P_converted < {REACTIVE_RESIDUAL_TOL:.0e}"

FLAPS = {
0: {"label": "VGM-0", "config": "config/vgoswec_0_cc.yaml", "h5": "hydroData/vgoswec_0.h5"},
10: {"label": "VGM-10", "config": "config/vgoswec_10_cc.yaml", "h5": "hydroData/vgoswec_10.h5"},
20: {"label": "VGM-20", "config": "config/vgoswec_20_cc.yaml", "h5": "hydroData/vgoswec_20.h5"},
45: {"label": "VGM-45", "config": "config/vgoswec_45_cc.yaml", "h5": "hydroData/vgoswec_45.h5"},
90: {"label": "VGM-90", "config": "config/vgoswec_90_cc.yaml", "h5": "hydroData/vgoswec_90.h5"},
0: {"label": "VGM-0", "config": "config/vgoswec_0_cc.yaml", "h5": "hydroData/hinged_vgoswec_0.h5"},
10: {"label": "VGM-10", "config": "config/vgoswec_10_cc.yaml", "h5": "hydroData/hinged_vgoswec_10.h5"},
20: {"label": "VGM-20", "config": "config/vgoswec_20_cc.yaml", "h5": "hydroData/hinged_vgoswec_20.h5"},
45: {"label": "VGM-45", "config": "config/vgoswec_45_cc.yaml", "h5": "hydroData/hinged_vgoswec_45.h5"},
90: {"label": "VGM-90", "config": "config/vgoswec_90_cc.yaml", "h5": "hydroData/hinged_vgoswec_90.h5"},
}

JOURNAL_STYLE = {
Expand Down Expand Up @@ -199,7 +199,7 @@ def popt_curve_from_h5(h5_path: Path, periods_s: np.ndarray) -> tuple[np.ndarray
omega_rads = omega_rads[order]
b55_norm = b55_norm[order]
fexc_norm = fexc_norm[order]
b55 = b55_norm * rho * omega_rads
b55 = np.maximum(0.0, b55_norm * rho * omega_rads)
fexc = fexc_norm * rho * g * WAVE_AMPLITUDE_M

omega_targets = (2.0 * math.pi) / periods_s
Expand Down
12 changes: 6 additions & 6 deletions scripts/passive_vs_optpassive_sweep.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,35 +83,35 @@
"label": "VGM-0",
"passive_config": "config/vgoswec_0_passive.yaml",
"opt_passive_config": "config/vgoswec_0_opt_passive.yaml",
"h5": "hydroData/vgoswec_0.h5",
"h5": "hydroData/hinged_vgoswec_0.h5",
"omega0": 1.07,
},
10: {
"label": "VGM-10",
"passive_config": "config/vgoswec_10_passive.yaml",
"opt_passive_config": "config/vgoswec_10_opt_passive.yaml",
"h5": "hydroData/vgoswec_10.h5",
"h5": "hydroData/hinged_vgoswec_10.h5",
"omega0": 1.468,
},
20: {
"label": "VGM-20",
"passive_config": "config/vgoswec_20_passive.yaml",
"opt_passive_config": "config/vgoswec_20_opt_passive.yaml",
"h5": "hydroData/vgoswec_20.h5",
"h5": "hydroData/hinged_vgoswec_20.h5",
"omega0": 1.568,
},
45: {
"label": "VGM-45",
"passive_config": "config/vgoswec_45_passive.yaml",
"opt_passive_config": "config/vgoswec_45_opt_passive.yaml",
"h5": "hydroData/vgoswec_45.h5",
"h5": "hydroData/hinged_vgoswec_45.h5",
"omega0": 1.84,
},
90: {
"label": "VGM-90",
"passive_config": "config/vgoswec_90_passive.yaml",
"opt_passive_config": "config/vgoswec_90_opt_passive.yaml",
"h5": "hydroData/vgoswec_90.h5",
"h5": "hydroData/hinged_vgoswec_90.h5",
"omega0": 2.094,
},
}
Expand Down Expand Up @@ -264,7 +264,7 @@ def popt_curve_from_h5(
b55_norm = b55_norm[order]
fexc_norm = fexc_norm[order]

b55 = b55_norm * rho * w
b55 = np.maximum(0.0, b55_norm * rho * w)
fexc = fexc_norm * rho * g * WAVE_AMPLITUDE_M

omega_targets = (2.0 * math.pi) / periods_s
Expand Down
Loading