From 7c7f4527be7010431a71c3f4819f729c56c178f3 Mon Sep 17 00:00:00 2001 From: Ryan Coe Date: Tue, 2 Apr 2024 15:08:46 -0600 Subject: [PATCH 1/5] use long_crested irregular wave had to fix issue with irreg. freq. from BEM --- tests/test_integration.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/tests/test_integration.py b/tests/test_integration.py index ea116fa40..bb0ce673a 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -91,13 +91,18 @@ def regular_wave(f1, nfreq): @pytest.fixture(scope='module') -def irregular_wave(f1, nfreq): +def long_crested_wave(f1, nfreq): """Idealized (Pierson-Moskowitz) spectrum wave""" freq = wot.frequency(f1, nfreq, False) fp = 0.3 hs = 0.0625*1.9 - spec = wot.waves.pierson_moskowitz_spectrum(freq, fp, hs) - waves = wot.waves.long_crested_wave(spec) + spec_fun = lambda f: wot.waves.pierson_moskowitz_spectrum(freq=f, + fp=fp, + hs=hs) + efth = wot.waves.omnidirectional_spectrum(f1=f1, nfreq=nfreq, + spectrum_func=spec_fun, + ) + waves = wot.waves.long_crested_wave(efth, nrealizations=1) return waves @@ -234,7 +239,9 @@ def hstiff(self, fb): def hydro_impedance(self, bem): """Intrinsic hydrodynamic impedance""" hd = wot.add_linear_friction(bem) - hd = wot.check_linear_damping(hd) + rad = bem['radiation_damping'] + min_damping = rad.where(rad>0)[-25:].mean() + hd = wot.check_linear_damping(hd, min_damping=min_damping) Zi = wot.hydrodynamic_impedance(hd) return Zi @@ -301,11 +308,10 @@ def test_pi_controller_regular_wave(self, power_optimal = (np.abs(Fex)**2/8 / np.real(hydro_impedance.squeeze()) ).squeeze().sum('omega').item() - assert power_sol == approx(power_optimal, rel=1e-4) - def test_unstructured_controller_irregular_wave(self, + def test_unstructured_controller_long_crested_wave(self, fb, bem, - regular_wave, + long_crested_wave, pto, nfreq, hydro_impedance): @@ -315,7 +321,7 @@ def test_unstructured_controller_irregular_wave(self, f_add = {"PTO": pto.force_on_wec} wec = wot.WEC.from_bem(bem, f_add=f_add) - res = wec.solve(waves=regular_wave, + res = wec.solve(waves=long_crested_wave, obj_fun=pto.average_power, nstate_opt=2*nfreq, x_wec_0=1e-1*np.ones(wec.nstate_wec), @@ -326,13 +332,15 @@ def test_unstructured_controller_irregular_wave(self, power_sol = -1*res[0]['fun'] - res_fd, _ = wec.post_process(res[0], regular_wave.sel(realization=0), nsubsteps=1) + res_fd, _ = wec.post_process(res[0], + long_crested_wave.sel(realization=0), + nsubsteps=1) Fex = res_fd.force.sel( type=['Froude_Krylov', 'diffraction']).sum('type') power_optimal = (np.abs(Fex)**2/8 / np.real(hydro_impedance.squeeze()) ).squeeze().sum('omega').item() - assert power_sol == approx(power_optimal, rel=1e-3) + assert power_sol == approx(power_optimal, rel=1e-1) def test_saturated_pi_controller(self, bem, From 4099c1f4a06d4a1dadd501735eea7f80cf186c8e Mon Sep 17 00:00:00 2001 From: Ryan Coe Date: Tue, 2 Apr 2024 15:09:20 -0600 Subject: [PATCH 2/5] loosen tolerance --- tests/test_integration.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_integration.py b/tests/test_integration.py index bb0ce673a..e4d309df0 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -308,6 +308,8 @@ def test_pi_controller_regular_wave(self, power_optimal = (np.abs(Fex)**2/8 / np.real(hydro_impedance.squeeze()) ).squeeze().sum('omega').item() + assert power_sol == approx(power_optimal, rel=1e-1) + def test_unstructured_controller_long_crested_wave(self, fb, bem, From 0b489f7c5e14ba329511c13c4c7ea484b085f5b9 Mon Sep 17 00:00:00 2001 From: Ryan Coe Date: Tue, 2 Apr 2024 15:30:24 -0600 Subject: [PATCH 3/5] update docstring --- tests/test_integration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_integration.py b/tests/test_integration.py index e4d309df0..59b89df69 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -318,7 +318,7 @@ def test_unstructured_controller_long_crested_wave(self, nfreq, hydro_impedance): """Unstructured (numerical optimal) controller matches optimal for any - irregular wave when unconstrained""" + irregular (long crested) wave when unconstrained""" f_add = {"PTO": pto.force_on_wec} wec = wot.WEC.from_bem(bem, f_add=f_add) From f462c68d54cc74725b48edb2adb91907af6d88d3 Mon Sep 17 00:00:00 2001 From: Ryan Coe Date: Tue, 2 Apr 2024 15:46:58 -0600 Subject: [PATCH 4/5] remove unused argument --- tests/test_integration.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_integration.py b/tests/test_integration.py index 59b89df69..35823c88d 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -311,7 +311,6 @@ def test_pi_controller_regular_wave(self, assert power_sol == approx(power_optimal, rel=1e-1) def test_unstructured_controller_long_crested_wave(self, - fb, bem, long_crested_wave, pto, From 014ca33b89f437fac38e58ea8723a93d2fa6094d Mon Sep 17 00:00:00 2001 From: Ryan Coe Date: Tue, 2 Apr 2024 16:43:38 -0600 Subject: [PATCH 5/5] use consistent damping shift --- tests/test_integration.py | 75 ++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/tests/test_integration.py b/tests/test_integration.py index 35823c88d..18a74c250 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -10,7 +10,7 @@ kplim = -1e1 - +min_damping = 45 @pytest.fixture(scope="module") def f1(): @@ -73,10 +73,13 @@ def fb(): @pytest.fixture(scope="module") -def bem(f1, nfreq, fb): - """Boundary elemement model (Capytaine) results""" +def hydro_data(f1, nfreq, fb): + """Boundary element model (Capytaine) results (with friction added)""" freq = wot.frequency(f1, nfreq, False) - return wot.run_bem(fb, freq) + hydro_data = wot.run_bem(fb, freq) + hd = wot.add_linear_friction(hydro_data) + hd = wot.check_radiation_damping(hd, min_damping=min_damping) + return hd @pytest.fixture(scope='module') @@ -107,10 +110,10 @@ def long_crested_wave(f1, nfreq): @pytest.fixture(scope='module') -def wec_from_bem(f1, nfreq, bem, fb, pto): +def wec_from_bem(f1, nfreq, hydro_data, fb, pto): """Simple WEC: 1 DOF, no constraints.""" f_add = {"PTO": pto.force_on_wec} - wec = wot.WEC.from_bem(bem, f_add=f_add) + wec = wot.WEC.from_bem(hydro_data, f_add=f_add) return wec @@ -118,14 +121,15 @@ def wec_from_bem(f1, nfreq, bem, fb, pto): def wec_from_floatingbody(f1, nfreq, fb, pto): """Simple WEC: 1 DOF, no constraints.""" f_add = {"PTO": pto.force_on_wec} - wec = wot.WEC.from_floating_body(fb, f1, nfreq, f_add=f_add) + wec = wot.WEC.from_floating_body(fb, f1, nfreq, f_add=f_add, + min_damping=min_damping) return wec @pytest.fixture(scope='module') -def wec_from_impedance(bem, pto, fb): +def wec_from_impedance(hydro_data, pto, fb): """Simple WEC: 1 DOF, no constraints.""" - bemc = bem.copy() + bemc = hydro_data.copy() omega = bemc['omega'].values w = np.expand_dims(omega, [1,2]) A = bemc['added_mass'].values @@ -139,17 +143,18 @@ def wec_from_impedance(bem, pto, fb): freqs = omega / (2 * np.pi) impedance = (A + mass)*(1j*w) + B + K/(1j*w) - exc_coeff = bem['Froude_Krylov_force'] + bem['diffraction_force'] + exc_coeff = hydro_data['Froude_Krylov_force'] + hydro_data['diffraction_force'] f_add = {"PTO": pto.force_on_wec} - wec = wot.WEC.from_impedance(freqs, impedance, exc_coeff, hstiff, f_add) + wec = wot.WEC.from_impedance(freqs, impedance, exc_coeff, hstiff, f_add, + min_damping=min_damping) return wec @pytest.fixture(scope='module') -def resonant_wave(f1, nfreq, fb, bem): +def resonant_wave(f1, nfreq, fb, hydro_data): """Regular wave at natural frequency of the WEC""" - hd = wot.add_linear_friction(bem) + hd = wot.add_linear_friction(hydro_data) Zi = wot.hydrodynamic_impedance(hd) wn = Zi['omega'][np.abs(Zi).argmin(dim='omega')].item() waves = wot.waves.regular_wave(f1, nfreq, freq=wn/2/np.pi, amplitude=0.1) @@ -236,17 +241,13 @@ def hstiff(self, fb): return fb.compute_hydrostatic_stiffness() @pytest.fixture(scope='class') - def hydro_impedance(self, bem): + def hydro_impedance(self, hydro_data): """Intrinsic hydrodynamic impedance""" - hd = wot.add_linear_friction(bem) - rad = bem['radiation_damping'] - min_damping = rad.where(rad>0)[-25:].mean() - hd = wot.check_linear_damping(hd, min_damping=min_damping) - Zi = wot.hydrodynamic_impedance(hd) + Zi = wot.hydrodynamic_impedance(hydro_data) return Zi def test_p_controller_resonant_wave(self, - bem, + hydro_data, resonant_wave, p_controller_pto, hydro_impedance): @@ -254,7 +255,7 @@ def test_p_controller_resonant_wave(self, wave""" f_add = {"PTO": p_controller_pto.force_on_wec} - wec = wot.WEC.from_bem(bem, f_add=f_add) + wec = wot.WEC.from_bem(hydro_data, f_add=f_add) res = wec.solve(waves=resonant_wave, obj_fun=p_controller_pto.average_power, @@ -279,14 +280,14 @@ def test_p_controller_resonant_wave(self, assert power_sol == approx(power_optimal, rel=0.03) def test_pi_controller_regular_wave(self, - bem, + hydro_data, regular_wave, pi_controller_pto, hydro_impedance): """PI controller matches optimal for any regular wave""" f_add = {"PTO": pi_controller_pto.force_on_wec} - wec = wot.WEC.from_bem(bem, f_add=f_add) + wec = wot.WEC.from_bem(hydro_data, f_add=f_add) res = wec.solve(waves=regular_wave, obj_fun=pi_controller_pto.average_power, @@ -308,19 +309,19 @@ def test_pi_controller_regular_wave(self, power_optimal = (np.abs(Fex)**2/8 / np.real(hydro_impedance.squeeze()) ).squeeze().sum('omega').item() - assert power_sol == approx(power_optimal, rel=1e-1) + assert power_sol == approx(power_optimal, rel=1e-4) def test_unstructured_controller_long_crested_wave(self, - bem, - long_crested_wave, - pto, - nfreq, - hydro_impedance): + hydro_data, + long_crested_wave, + pto, + nfreq, + hydro_impedance): """Unstructured (numerical optimal) controller matches optimal for any irregular (long crested) wave when unconstrained""" f_add = {"PTO": pto.force_on_wec} - wec = wot.WEC.from_bem(bem, f_add=f_add) + wec = wot.WEC.from_bem(hydro_data, f_add=f_add) res = wec.solve(waves=long_crested_wave, obj_fun=pto.average_power, @@ -334,17 +335,17 @@ def test_unstructured_controller_long_crested_wave(self, power_sol = -1*res[0]['fun'] res_fd, _ = wec.post_process(res[0], - long_crested_wave.sel(realization=0), - nsubsteps=1) + long_crested_wave.sel(realization=0), + nsubsteps=1) Fex = res_fd.force.sel( type=['Froude_Krylov', 'diffraction']).sum('type') power_optimal = (np.abs(Fex)**2/8 / np.real(hydro_impedance.squeeze()) - ).squeeze().sum('omega').item() + ).squeeze().sum('omega').item() - assert power_sol == approx(power_optimal, rel=1e-1) + assert power_sol == approx(power_optimal, rel=1e-2) def test_saturated_pi_controller(self, - bem, + hydro_data, regular_wave, pto, nfreq): @@ -366,7 +367,7 @@ def const_f_pto(wec, x_wec, x_opt, waves): f = pto['us'].force_on_wec(wec, x_wec, x_opt, waves, nsubsteps=4) return f_max - np.abs(f.flatten()) - wec['us'] = wot.WEC.from_bem(bem, + wec['us'] = wot.WEC.from_bem(hydro_data, f_add={"PTO": pto['us'].force_on_wec}, constraints=[{'type': 'ineq', 'fun': const_f_pto, }]) @@ -381,7 +382,7 @@ def saturated_pi(pto, wec, x_wec, x_opt, waves=None, nsubsteps=1): pto['pi'] = wot.pto.PTO(ndof=ndof, kinematics=np.eye(ndof), controller=saturated_pi,) - wec['pi'] = wot.WEC.from_bem(bem, + wec['pi'] = wot.WEC.from_bem(hydro_data, f_add={"PTO": pto['pi'].force_on_wec}, constraints=[])