diff --git a/Passive_Yaw/PassiveYawRegression/IrrYaw_org.mat b/Passive_Yaw/PassiveYawRegression/IrrYaw_org.mat index dc5e3430..ed2ba868 100644 Binary files a/Passive_Yaw/PassiveYawRegression/IrrYaw_org.mat and b/Passive_Yaw/PassiveYawRegression/IrrYaw_org.mat differ diff --git a/Passive_Yaw/PassiveYawRegression/README.md b/Passive_Yaw/PassiveYawRegression/README.md new file mode 100644 index 00000000..c2fef0d4 --- /dev/null +++ b/Passive_Yaw/PassiveYawRegression/README.md @@ -0,0 +1,37 @@ +# Passive-yaw regression baseline + +The OSWEC irregular-wave regression runs for 250 s with phase seed 1 and +fixed-step `ode4` at 0.01 s. Both bodies use `yaw.threshold = 0`, which +interpolates heading-dependent hydrodynamic coefficients every step. The +previous one-degree coefficient hold produced strongly step-dependent +heading events and PTO work in all three published phase realizations. + +`IrrYaw_org.mat` contains only the archived time, flap yaw, total yaw +torque, wave elevation, and spectrum needed by the regression. Its source +is the complete MATLAB R2025b run of WEC-Sim commit +`0753b2e47f2457c078751dcfe5d251d1767b80ab` and WEC-Sim_Applications +commit `d53d4d4c9eda2581f04204f5d394a6ef84bb099e`, with the two yaw +thresholds changed from 1 degree to zero. The archived source output is +from [this workflow run](https://github.com/cmudrc/wec-sim-python/actions/runs/37720514831) +(`matlab-reference-model-output`, `OSWEC_PASSIVE_YAW_IRR_CONT`). Its CSVs +provide `body1` column 1 for time, column 7 for yaw, and column 19 for +total yaw torque (one-based), plus the wave and component files. The +component amplitude divided by two is `waves.spectrum` in this WEC-Sim +version. The archived body, wave, and component CSV SHA-256 values are: + +| CSV | SHA-256 | +| --- | --- | +| `body1` | `b8530d3f72ff6c1f842411b716e126c0abc177a39e604214fcbb1178a9e2238e` | +| wave | `be5906bf1e1a0dfc8d5ec5075473202fe72239b1727cd849869847863163a507` | +| components | `053265841734b875d53129e3dd5de92961a43f890a6004f3c68401ae5dcdb384` | + +The archived artifact also contains the BEMIO-generated `oswec.h5` used by +that source run. Its byte hash differs across archived regenerations; +the CSV hashes above identify the stored trajectory and wave reference. + +The baseline was assembled from those archived numeric outputs rather +than from source solver states during a comparison. To regenerate after an +intentional source change, run `regeneratePassiveYawBaseline` with that +WEC-Sim checkout on the MATLAB path and review the resulting baseline and +regression diff. The test compares the full yaw and torque trajectories, +both spectral moments, and the full wave time history. diff --git a/Passive_Yaw/PassiveYawRegression/regeneratePassiveYawBaseline.m b/Passive_Yaw/PassiveYawRegression/regeneratePassiveYawBaseline.m new file mode 100644 index 00000000..10db7115 --- /dev/null +++ b/Passive_Yaw/PassiveYawRegression/regeneratePassiveYawBaseline.m @@ -0,0 +1,30 @@ +function regeneratePassiveYawBaseline() +% Rebuild the compact phase-seed-1 reference after an intentional rebaseline. +% Run with the pinned WEC-Sim source on the MATLAB path. This overwrites the +% checked-in IrrYaw_org.mat, so inspect the resulting regression diff. +caseDir = fileparts(mfilename('fullpath')); +originalDir = pwd; +cleanup = onCleanup(@() cd(originalDir)); +cd(caseDir); +wecSim; +assert(strcmp(simu.solver, 'ode4') && simu.dt == 0.01 && ... + simu.startTime == 0 && simu.endTime == 250 && ... + waves.phaseSeed == 1 && body(1).yaw.threshold == 0 && ... + body(2).yaw.threshold == 0, ... + 'The input does not match the continuous-heading regression'); +referenceTime = output.bodies(1).time; +referenceYaw = output.bodies(1).position(:,6); +referenceForce = output.bodies(1).forceTotal(:,6); +referenceOmega = waves.omega(:); +referenceSpectrum = waves.spectrum(:); +referenceWave = waves.waveAmpTime(:,2); +assert(numel(referenceTime) == 25001 && ... + isequal(size(referenceYaw),size(referenceTime), ... + size(referenceForce),size(referenceWave)) && ... + all(isfinite([referenceTime,referenceYaw,referenceForce, ... + referenceWave]),'all'), 'Incomplete passive-yaw reference'); +save('IrrYaw_org.mat','referenceTime','referenceYaw', ... + 'referenceForce','referenceOmega','referenceSpectrum', ... + 'referenceWave','-v7'); +close_system('OSWEC',0); +end diff --git a/Passive_Yaw/PassiveYawRegression/runLoadPassiveYawIrr.m b/Passive_Yaw/PassiveYawRegression/runLoadPassiveYawIrr.m index 45b446c6..380d0f80 100644 --- a/Passive_Yaw/PassiveYawRegression/runLoadPassiveYawIrr.m +++ b/Passive_Yaw/PassiveYawRegression/runLoadPassiveYawIrr.m @@ -1,40 +1,43 @@ -%% Run the WEC-Sim RM3 Test Case for 1DOF w/PTO -% This script will run the RM3 in WEC-Sim for irregular waves with Hs=2.5[m] -% and Tp=8[s]. The RM3 WEC models has 2 bodies, restricted to heave motion -% only, and has PTO damping=1200[kN-s/m]. - -%% Run Simulation -wecSim; % Run Simulation - -%% Post-Process Data -% Flap position and force -IrrYaw.time_new=output.bodies(1).time; -IrrYaw.Pos_new=output.bodies(1).position(:,6); -IrrYaw.Force_new=output.bodies(1).forceTotal(:,6); -% Spectrum +%% Run the continuous-heading OSWEC passive-yaw regression +wecSim; + +%% Compare the current run with the archived 250 s phase-seed-1 reference +reference = load('IrrYaw_org.mat'); +assert(all(isfield(reference, {'referenceTime', 'referenceYaw', ... + 'referenceForce', 'referenceOmega', 'referenceSpectrum', ... + 'referenceWave'})), 'Passive-yaw reference is incomplete'); +assert(body(1).yaw.threshold == 0 && body(2).yaw.threshold == 0 && ... + waves.phaseSeed == 1 && simu.endTime == 250 && simu.dt == 0.01, ... + 'Passive-yaw input does not match the continuous-heading reference'); + +IrrYaw.time_new = output.bodies(1).time; +IrrYaw.Pos_new = output.bodies(1).position(:,6); +IrrYaw.Force_new = output.bodies(1).forceTotal(:,6); +IrrYaw.time_org = reference.referenceTime; +IrrYaw.Pos_org = reference.referenceYaw; +IrrYaw.Force_org = reference.referenceForce; +assert(isequal(size(IrrYaw.time_new), size(IrrYaw.time_org)) && ... + max(abs(IrrYaw.time_new - IrrYaw.time_org)) < 1e-10, ... + 'Passive-yaw reference time grid differs'); + IrrYaw.Sp.WEC_Sim_new.m0 = calcSpectralMoment(waves.omega,waves.spectrum,0); IrrYaw.Sp.WEC_Sim_new.m2 = calcSpectralMoment(waves.omega,waves.spectrum,2); - -%% Load Data -irrCICouput_new=output; % Keeps the new run in the workspace -load('IrrYaw_org.mat') % Load Previous WEC-Sim Data - -%% Post-Process Data -% Flap position and force -IrrYaw.time_org=output.bodies(1).time; -IrrYaw.Pos_org=output.bodies(1).position(:,6); -IrrYaw.Force_org=output.bodies(1).forceTotal(:,6); -% Spectrum -IrrYaw.Sp.WEC_Sim_org.m0 = calcSpectralMoment(waves.omega,waves.spectrum,0); -IrrYaw.Sp.WEC_Sim_org.m2 = calcSpectralMoment(waves.omega,waves.spectrum,2); - -%% Quantify Maximum Difference Between Saved and Current WEC-Sim RunsReg -[IrrYaw.Pos_diff ,IrrYaw.Pos_i]=max(abs(IrrYaw.Pos_new-IrrYaw.Pos_org)); -[IrrYaw.Force_diff ,IrrYaw.Force_i]=max(abs(IrrYaw.Force_new-IrrYaw.Force_org)); +IrrYaw.Sp.WEC_Sim_org.m0 = calcSpectralMoment( ... + reference.referenceOmega,reference.referenceSpectrum,0); +IrrYaw.Sp.WEC_Sim_org.m2 = calcSpectralMoment( ... + reference.referenceOmega,reference.referenceSpectrum,2); +assert(isequal(size(waves.waveAmpTime(:,2)), size(reference.referenceWave)) && ... + max(abs(waves.waveAmpTime(:,1) - IrrYaw.time_org)) < 1e-10, ... + 'Passive-yaw reference wave grid differs'); +IrrYaw.Wave_diff = max(abs(waves.waveAmpTime(:,2) - ... + reference.referenceWave)); + +[IrrYaw.Pos_diff,IrrYaw.Pos_i] = max(abs(IrrYaw.Pos_new-IrrYaw.Pos_org)); +[IrrYaw.Force_diff,IrrYaw.Force_i] = max(abs(IrrYaw.Force_new-IrrYaw.Force_org)); save('IrrYaw','IrrYaw') -%% Plot Old vs. New Comparison -figure(); % plots of yaw position +%% Plot archived and current trajectories +figure(); subplot(1,2,1) plot(IrrYaw.time_new,IrrYaw.Pos_new,':k','LineWidth',1.4) hold on; grid on; @@ -42,19 +45,18 @@ xlabel('Time(s)'); ylabel('Yaw position (rad)') title('Yaw position comparison') -subplot(1,2,2) % plots of yaw position +subplot(1,2,2) plot(IrrYaw.time_new,IrrYaw.Force_new,':k','LineWidth',1.4) hold on; grid on; plot(IrrYaw.time_org,IrrYaw.Force_org,'-k'); xlabel('Time(s)'); -ylabel('Total Yaw Force (N)'); +ylabel('Total Yaw Force (N m)'); legend('New','Original'); title('Yaw force comparison'); sgtitle('Passive yaw regression, irregular wave') -savefig('figYawIrr'); +savefig('figYawIrr'); -%% Clear output and .slx directory -try - delete('OSWEC.slx.autosave') +if isfile('OSWEC.slx.autosave') + delete('OSWEC.slx.autosave') end diff --git a/Passive_Yaw/PassiveYawRegression/wecSimInputFile.m b/Passive_Yaw/PassiveYawRegression/wecSimInputFile.m index 10694756..6bce7c8d 100644 --- a/Passive_Yaw/PassiveYawRegression/wecSimInputFile.m +++ b/Passive_Yaw/PassiveYawRegression/wecSimInputFile.m @@ -27,7 +27,7 @@ body(1).mass = 12700; % User-Defined mass [kg] body(1).inertia = [1.85e6 1.85e6 1.85e6]; % Moment of Inertia [kg-m^2] body(1).yaw.option = 1; % Turn passive yaw ON -body(1).yaw.threshold = 1; % Set passive yaw threshold +body(1).yaw.threshold = 0; % Interpolate heading coefficients every step % Base body(2) = bodyClass('../../_Common_Input_Files/OSWEC/hydroData/oswec.h5'); % Initialize bodyClass for Base @@ -35,7 +35,7 @@ body(2).mass = 999; % Placeholder mass for fixed body body(2).inertia = [999 999 999]; % Placeholder inertia for fixed body body(2).yaw.option = 1; % Turn passive yaw ON -body(2).yaw.threshold = 1; % Set passive yaw threshold +body(2).yaw.threshold = 0; % Interpolate heading coefficients every step %% PTO and Constraint Parameters % Fixed diff --git a/Passive_Yaw/TestPassiveYawRegression.m b/Passive_Yaw/TestPassiveYawRegression.m index 51d10013..3fe56093 100644 --- a/Passive_Yaw/TestPassiveYawRegression.m +++ b/Passive_Yaw/TestPassiveYawRegression.m @@ -93,5 +93,9 @@ function irregYaw_2nd_Spectral_Moment(testCase) fprintf(['2nd Order Spectral Moment, Diff = ' ... num2str(max(abs(org-new))) '\n']); end + function irregYaw_wave_elevation(testCase) + % Match the actual wave time history, not just its spectrum. + testCase.verifyEqual(testCase.IrrYaw.Wave_diff,0,'AbsTol',1e-10); + end end end