@@ -24,14 +24,27 @@ def nfreq():
2424 """Number of frequencies in the array"""
2525 return 50
2626
27+
2728@pytest .fixture (scope = 'module' )
2829def pto ():
2930 """Basic PTO: unstructured, 1 DOF, mechanical power."""
3031 ndof = 1
3132 kinematics = np .eye (ndof )
3233 controller = wot .controllers .unstructured_controller ()
33- pto = wot .pto .PTO (ndof = ndof ,
34- kinematics = kinematics ,
34+ pto = wot .pto .PTO (ndof = ndof ,
35+ kinematics = kinematics ,
36+ controller = controller )
37+ return pto
38+
39+
40+ @pytest .fixture (scope = 'module' )
41+ def pto_2d ():
42+ """Basic PTO: unstructured, 1 DOF, mechanical power."""
43+ ndof = 1
44+ kinematics = np .array ([[1 ,0 ],])
45+ controller = wot .controllers .unstructured_controller ()
46+ pto = wot .pto .PTO (ndof = ndof ,
47+ kinematics = kinematics ,
3548 controller = controller )
3649 return pto
3750
@@ -78,6 +91,28 @@ def fb():
7891 return fb
7992
8093
94+ @pytest .fixture (scope = "module" )
95+ def fb_2d ():
96+ """Capytaine FloatingBody object"""
97+ try :
98+ import wecopttool .geom as geom
99+ except ImportError :
100+ pytest .skip (
101+ 'Skipping integration tests due to missing optional geometry ' +
102+ 'dependencies. Run `pip install wecopttool[geometry]` to run ' +
103+ 'these tests.'
104+ )
105+ mesh_size_factor = 0.2
106+ wb = geom .WaveBot ()
107+ mesh = wb .mesh (mesh_size_factor )
108+ mesh_obj = load_from_meshio (mesh , 'WaveBot' )
109+ lid_mesh = mesh_obj .generate_lid (- 2e-2 )
110+ fb = cpy .FloatingBody (mesh = mesh_obj , lid_mesh = lid_mesh , name = "WaveBot" )
111+ fb .add_translation_dof (name = "Heave" )
112+ fb .add_translation_dof (name = "Surge" )
113+ return fb
114+
115+
81116@pytest .fixture (scope = "module" )
82117def hydro_data (f1 , nfreq , fb ):
83118 """Boundary element model (Capytaine) results (with friction added)"""
@@ -88,6 +123,16 @@ def hydro_data(f1, nfreq, fb):
88123 return hd
89124
90125
126+ @pytest .fixture (scope = "module" )
127+ def hydro_data_2d (f1 , nfreq , fb_2d ):
128+ """Boundary element model (Capytaine) results (with friction added)"""
129+ freq = wot .frequency (f1 , nfreq , False )
130+ hydro_data = wot .run_bem (fb_2d , freq )
131+ hd = wot .add_linear_friction (hydro_data )
132+ hd = wot .check_radiation_damping (hd , min_damping = min_damping )
133+ return hd
134+
135+
91136@pytest .fixture (scope = 'module' )
92137def regular_wave (f1 , nfreq ):
93138 """Single frequency wave"""
@@ -111,18 +156,35 @@ def long_crested_wave(f1, nfreq):
111156
112157
113158@pytest .fixture (scope = 'module' )
114- def wec_from_bem (f1 , nfreq , hydro_data , fb , pto ):
159+ def wec_from_bem (hydro_data , pto ):
115160 """Simple WEC: 1 DOF, no constraints."""
116161 f_add = {"PTO" : pto .force_on_wec }
117162 wec = wot .WEC .from_bem (hydro_data , f_add = f_add )
118163 return wec
119164
120165
166+ @pytest .fixture (scope = 'module' )
167+ def wec_from_bem_2d (hydro_data_2d , pto_2d ):
168+ """Simple WEC: 2 DOF, no constraints."""
169+ f_add = {"PTO" : pto_2d .force_on_wec }
170+ wec = wot .WEC .from_bem (hydro_data_2d , f_add = f_add )
171+ return wec
172+
173+
121174@pytest .fixture (scope = 'module' )
122175def wec_from_floatingbody (f1 , nfreq , fb , pto ):
123176 """Simple WEC: 1 DOF, no constraints."""
124177 f_add = {"PTO" : pto .force_on_wec }
125- wec = wot .WEC .from_floating_body (fb , f1 , nfreq , f_add = f_add ,
178+ wec = wot .WEC .from_floating_body (fb , f1 , nfreq , f_add = f_add ,
179+ min_damping = min_damping )
180+ return wec
181+
182+
183+ @pytest .fixture (scope = 'module' )
184+ def wec_from_floatingbody_2d (f1 , nfreq , fb_2d , pto_2d ):
185+ """Simple WEC: 2 DOF, no constraints."""
186+ f_add = {"PTO" : pto_2d .force_on_wec }
187+ wec = wot .WEC .from_floating_body (fb_2d , f1 , nfreq , f_add = f_add ,
126188 min_damping = min_damping )
127189 return wec
128190
@@ -140,14 +202,39 @@ def wec_from_impedance(hydro_data, pto, fb):
140202 fb = fb .immersed_part ()
141203 mass = bemc ['inertia_matrix' ].values
142204 hstiff = bemc ['hydrostatic_stiffness' ].values
143- K = np .expand_dims (hstiff , 2 )
144-
205+ K = np .expand_dims (hstiff , 0 )
206+
145207 freqs = omega / (2 * np .pi )
146208 impedance = (A + mass )* (1j * w ) + B + K / (1j * w )
147209 exc_coeff = hydro_data ['Froude_Krylov_force' ] + hydro_data ['diffraction_force' ]
148210 f_add = {"PTO" : pto .force_on_wec }
149211
150- wec = wot .WEC .from_impedance (freqs , impedance , exc_coeff , hstiff , f_add ,
212+ wec = wot .WEC .from_impedance (freqs , impedance , exc_coeff , hstiff , f_add ,
213+ min_damping = min_damping )
214+ return wec
215+
216+
217+ @pytest .fixture (scope = 'module' )
218+ def wec_from_impedance_2d (hydro_data_2d , pto_2d , fb_2d ):
219+ """Simple WEC: 2 DOF, no constraints."""
220+ bemc = hydro_data_2d .copy ()
221+ omega = bemc ['omega' ].values
222+ w = np .expand_dims (omega , [1 ,2 ])
223+ A = bemc ['added_mass' ].values
224+ B = bemc ['radiation_damping' ].values
225+ fb_2d .center_of_mass = [0 , 0 , 0 ]
226+ fb_2d .rotation_center = fb_2d .center_of_mass
227+ fb_2d = fb_2d .immersed_part ()
228+ mass = bemc ['inertia_matrix' ].values
229+ hstiff = bemc ['hydrostatic_stiffness' ].values
230+ K = np .expand_dims (hstiff , 0 )
231+
232+ freqs = omega / (2 * np .pi )
233+ impedance = (A + mass )* (1j * w ) + B + K / (1j * w )
234+ exc_coeff = hydro_data_2d ['Froude_Krylov_force' ] + hydro_data_2d ['diffraction_force' ]
235+ f_add = {"PTO" : pto_2d .force_on_wec }
236+
237+ wec = wot .WEC .from_impedance (freqs , impedance , exc_coeff , hstiff , f_add ,
151238 min_damping = min_damping )
152239 return wec
153240
@@ -222,7 +309,28 @@ def test_same_wec_init(wec_from_bem,
222309 bem_res = wec_from_bem .residual (x_wec_0 , x_opt_0 , waves .sel (realization = 0 ))
223310 fb_res = wec_from_floatingbody .residual (x_wec_0 , x_opt_0 , waves .sel (realization = 0 ))
224311 imp_res = wec_from_impedance .residual (x_wec_0 , x_opt_0 , waves .sel (realization = 0 ))
225-
312+
313+ assert fb_res == approx (bem_res , rel = 0.01 )
314+ assert imp_res == approx (bem_res , rel = 0.01 )
315+
316+
317+ def test_same_wec_init_2d (wec_from_bem_2d ,
318+ wec_from_floatingbody_2d ,
319+ wec_from_impedance_2d ,
320+ f1 ,
321+ nfreq ):
322+ """Test that different init methods for WEC class produce the same object
323+ """
324+
325+ waves = wot .waves .regular_wave (f1 , nfreq , 0.3 , 0.0625 )
326+ np .random .seed (0 )
327+ x_wec_0 = np .random .randn (wec_from_bem_2d .nstate_wec )
328+ np .random .seed (1 )
329+ x_opt_0 = np .random .randn (wec_from_bem_2d .nstate_wec )
330+ bem_res = wec_from_bem_2d .residual (x_wec_0 , x_opt_0 , waves .sel (realization = 0 ))
331+ fb_res = wec_from_floatingbody_2d .residual (x_wec_0 , x_opt_0 , waves .sel (realization = 0 ))
332+ imp_res = wec_from_impedance_2d .residual (x_wec_0 , x_opt_0 , waves .sel (realization = 0 ))
333+
226334 assert fb_res == approx (bem_res , rel = 0.01 )
227335 assert imp_res == approx (bem_res , rel = 0.01 )
228336
@@ -246,7 +354,7 @@ def hydro_impedance(self, hydro_data):
246354 """Intrinsic hydrodynamic impedance"""
247355 Zi = wot .hydrodynamic_impedance (hydro_data )
248356 return Zi
249-
357+
250358 @pytest .fixture (scope = 'class' )
251359 def unstruct_wec (self ,
252360 hydro_data ,
@@ -265,7 +373,7 @@ def long_crested_wave_unstruct_res(self,
265373 pto ,
266374 hydro_data ,
267375 nfreq ):
268- """Solution for an unstructured controller with multiple long crested
376+ """Solution for an unstructured controller with multiple long crested
269377 waves"""
270378
271379 f_add = {"PTO" : pto .force_on_wec }
@@ -289,7 +397,7 @@ def test_p_controller_resonant_wave(self,
289397 hydro_impedance ):
290398 """Proportional controller should match optimum for natural resonant
291399 wave"""
292-
400+
293401 f_add = {"PTO" : p_controller_pto .force_on_wec }
294402 wec = wot .WEC .from_bem (hydro_data , f_add = f_add )
295403
@@ -346,7 +454,7 @@ def test_pi_controller_regular_wave(self,
346454 ).squeeze ().sum ('omega' ).item ()
347455
348456 assert power_sol == approx (power_optimal , rel = 1e-4 )
349-
457+
350458 def test_unstructured_controller_long_crested_wave (self ,
351459 unstruct_wec ,
352460 long_crested_wave ,
@@ -358,8 +466,8 @@ def test_unstructured_controller_long_crested_wave(self,
358466
359467 power_sol = - 1 * long_crested_wave_unstruct_res [0 ]['fun' ]
360468
361- res_fd , _ = unstruct_wec .post_process (unstruct_wec , long_crested_wave_unstruct_res ,
362- long_crested_wave ,
469+ res_fd , _ = unstruct_wec .post_process (unstruct_wec , long_crested_wave_unstruct_res ,
470+ long_crested_wave ,
363471 nsubsteps = 1 )
364472 Fex = res_fd .force .sel (realization = 0 ,
365473 type = ['Froude_Krylov' , 'diffraction' ]).sum ('type' )
@@ -370,7 +478,7 @@ def test_unstructured_controller_long_crested_wave(self,
370478
371479 def test_unconstrained_solutions_multiple_phase_realizations (self ,
372480 long_crested_wave_unstruct_res ):
373- """Solutions for average power with an unstructured controller
481+ """Solutions for average power with an unstructured controller
374482 (no constraints) match for different phase realizations"""
375483
376484 pow = [res ['fun' ] for res in long_crested_wave_unstruct_res ]
@@ -390,27 +498,27 @@ def test_saturated_pi_controller(self,
390498 pto = {}
391499 wec = {}
392500 nstate_opt = {}
393-
501+
394502 # Constraint
395503 f_max = 2000.0
396504
397505 nstate_opt ['us' ] = 2 * nfreq
398506 pto ['us' ] = pto_tmp
399507 def const_f_pto (wec , x_wec , x_opt , wave ):
400- f = pto ['us' ].force_on_wec (wec , x_wec , x_opt , wave ,
508+ f = pto ['us' ].force_on_wec (wec , x_wec , x_opt , wave ,
401509 nsubsteps = 4 )
402510 return f_max - np .abs (f .flatten ())
403511 wec ['us' ] = wot .WEC .from_bem (hydro_data ,
404512 f_add = {"PTO" : pto ['us' ].force_on_wec },
405513 constraints = [{'type' : 'ineq' ,
406514 'fun' : const_f_pto , }])
407-
408-
515+
516+
409517 ndof = 1
410518 nstate_opt ['pi' ] = 2
411519 # def saturated_pi(pto, wec, x_wec, x_opt, waves=None, nsubsteps=1):
412- # return wot.pto.controller_pi(pto, wec, x_wec, x_opt, waves,
413- # nsubsteps,
520+ # return wot.pto.controller_pi(pto, wec, x_wec, x_opt, waves,
521+ # nsubsteps,
414522 # saturation=[-f_max, f_max])
415523 saturated_pi = wot .controllers .pid_controller (1 ,True ,True ,False ,
416524 saturation = [- f_max , f_max ])
@@ -420,7 +528,7 @@ def const_f_pto(wec, x_wec, x_opt, wave):
420528 wec ['pi' ] = wot .WEC .from_bem (hydro_data ,
421529 f_add = {"PTO" : pto ['pi' ].force_on_wec },
422530 constraints = [])
423-
531+
424532 x_opt_0 = {'us' : np .ones (nstate_opt ['us' ])* 0.1 ,
425533 'pi' : [- 1e3 , 1e4 ]}
426534 scale_x_wec = {'us' : 1e1 ,
@@ -431,7 +539,7 @@ def const_f_pto(wec, x_wec, x_opt, wave):
431539 'pi' : 1e-2 }
432540 bounds_opt = {'us' : None ,
433541 'pi' : ((- 1e4 , 0 ), (0 , 2e4 ),)}
434-
542+
435543 res = {}
436544 pto_fdom = {}
437545 pto_tdom = {}
@@ -447,13 +555,13 @@ def const_f_pto(wec, x_wec, x_opt, wave):
447555 optim_options = {'maxiter' : 200 },
448556 bounds_opt = bounds_opt [key ]
449557 )
450-
558+
451559 nsubstep_postprocess = 4
452- pto_fdom [key ], pto_tdom [key ] = pto [key ].post_process (wec [key ],
453- res [key ],
454- regular_wave ,
560+ pto_fdom [key ], pto_tdom [key ] = pto [key ].post_process (wec [key ],
561+ res [key ],
562+ regular_wave ,
455563 nsubstep_postprocess )
456-
564+
457565 xr .testing .assert_allclose (
458566 pto_tdom ['pi' ].sel (realization = 0 ).power .squeeze ().mean ('time' ),
459567 pto_tdom ['us' ].sel (realization = 0 ).power .squeeze ().mean ('time' ),
0 commit comments