Skip to content

Commit fb0bb34

Browse files
Merge pull request #75 from symbiotic-engineering/c-vector
feat: extract c_vector assembly and add to NetCDF export
2 parents 3341c60 + aa5db42 commit fb0bb34

2 files changed

Lines changed: 54 additions & 39 deletions

File tree

package/src/openflash/meem_engine.py

Lines changed: 48 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,37 @@ def assemble_b_multi(self, problem: 'MEEMProblem', m0) -> np.ndarray:
6767
for row, calc_func in cache.m0_dependent_b_indices:
6868
b[row] = calc_func(problem, m0, cache.m_k_arr, cache.N_k_arr, I_mk_vals)
6969
return b
70+
71+
def assemble_c_vector(self, problem: 'MEEMProblem', heaving: List[int]) -> np.ndarray:
72+
"""
73+
Assembles the c_vector based on the heaving state of the domains.
74+
"""
75+
cache = self.cache_list[problem]
76+
int_R1_store, int_R2_store, _ = cache._get_integration_constants()
77+
78+
geometry = problem.geometry
79+
domain_keys = list(geometry.domain_list.keys())
80+
NMK = [geometry.domain_list[idx].number_harmonics for idx in domain_keys]
81+
boundary_count = len(NMK) - 1
82+
size = NMK[0] + NMK[-1] + 2 * sum(NMK[1:len(NMK) - 1])
83+
84+
c_vector = np.zeros((size - NMK[-1]), dtype=complex)
85+
col = 0
86+
87+
# 1. Inner Region (Index 0)
88+
for n in range(NMK[0]):
89+
c_vector[n] = heaving[0] * int_R1_store[(0, n)] * z_n_d(n)
90+
col += NMK[0]
91+
92+
# 2. Outer Regions
93+
for i in range(1, boundary_count):
94+
M = NMK[i]
95+
for m in range(M):
96+
c_vector[col + m] = heaving[i] * int_R1_store[(i, m)] * z_n_d(m)
97+
c_vector[col + M + m] = heaving[i] * int_R2_store[(i, m)] * z_n_d(m)
98+
col += 2 * M
99+
100+
return c_vector
70101

71102
def build_problem_cache(self, problem: 'MEEMProblem') -> ProblemCache:
72103
"""
@@ -319,29 +350,19 @@ def reformat_coeffs(self, x: np.ndarray, NMK, boundary_count) -> list[np.ndarray
319350
return cs
320351

321352
def compute_hydrodynamic_coefficients(self, problem, X, m0, modes_to_calculate: Optional[np.ndarray] = None, rho: Optional[float] = None):
322-
"""
323-
Computes the hydrodynamic coefficients (Added Mass and Damping) from the solution vector X.
324-
"""
325353
if rho is None:
326354
rho = default_rho
327355

328356
geometry = problem.geometry
329357
domain_keys = list(geometry.domain_list.keys())
330358
a = [geometry.domain_list[idx].a for idx in domain_keys]
331-
d = [
332-
domain.di[0] if isinstance(domain.di, list) else domain.di
333-
for domain in geometry.domain_list.values()
334-
]
335-
h = geometry.domain_list[0].h
359+
h = geometry.h
336360
NMK = [geometry.domain_list[idx].number_harmonics for idx in domain_keys]
337361
boundary_count = len(NMK) - 1
338362

339-
size = NMK[0] + NMK[-1] + 2 * sum(NMK[1:len(NMK) - 1])
340-
341363
results_per_mode = []
342-
343364
cache = self.cache_list[problem]
344-
int_R1_store, int_R2_store, int_phi_store = cache._get_integration_constants()
365+
_, _, int_phi_store = cache._get_integration_constants()
345366

346367
if modes_to_calculate is None:
347368
num_bodies = len(geometry.body_arrangement.bodies)
@@ -366,25 +387,8 @@ def compute_hydrodynamic_coefficients(self, problem, X, m0, modes_to_calculate:
366387
if r_idx < len(heaving):
367388
heaving[r_idx] = 1
368389

369-
c_vector = np.zeros((size - NMK[-1]), dtype=complex)
370-
col = 0
371-
372-
# 1. Inner Region (Index 0)
373-
for n in range(NMK[0]):
374-
val = int_R1_store[(0, n)]
375-
c_vector[n] = heaving[0] * val * z_n_d(n)
376-
col += NMK[0]
377-
378-
# 2. Outer Regions
379-
for i in range(1, boundary_count):
380-
M = NMK[i]
381-
for m in range(M):
382-
r1_val = int_R1_store[(i, m)]
383-
c_vector[col + m] = heaving[i] * r1_val * z_n_d(m)
384-
385-
r2_val = int_R2_store[(i, m)]
386-
c_vector[col + M + m] = heaving[i] * r2_val * z_n_d(m)
387-
col += 2 * M
390+
# --- USE YOUR NEW HELPER METHOD ---
391+
c_vector = self.assemble_c_vector(problem, heaving)
388392

389393
hydro_p_term_sum = np.zeros(boundary_count, dtype=complex)
390394
for i in range(boundary_count):
@@ -403,7 +407,8 @@ def compute_hydrodynamic_coefficients(self, problem, X, m0, modes_to_calculate:
403407
"real": hydro_coef_real,
404408
"imag": hydro_coef_imag,
405409
"excitation_phase": excitation_phase(X, NMK, m0, a),
406-
"excitation_force": excitation_force(hydro_coef_imag, m0, h)
410+
"excitation_force": excitation_force(hydro_coef_imag, m0, h),
411+
"c_vector": c_vector # --- APPEND C_VECTOR TO OUTPUT ---
407412
})
408413

409414
return results_per_mode
@@ -591,6 +596,10 @@ def run_and_store_results(self, problem_index: int) -> Results:
591596
full_excitation_force = np.full((num_freqs, num_modes), np.nan)
592597
full_excitation_phase = np.full((num_freqs, num_modes), np.nan)
593598

599+
# --- NEW: Initialize full_c_vector ---
600+
c_vector_len = NMK_list[0] + 2 * sum(NMK_list[1:-1])
601+
full_c_vector = np.full((num_freqs, num_modes, c_vector_len), np.nan + 1j * np.nan, dtype=complex)
602+
594603
all_potentials_batch_data = []
595604

596605
temp_bodies = []
@@ -659,6 +668,10 @@ def run_and_store_results(self, problem_index: int) -> Results:
659668
if i_idx == j_idx:
660669
full_excitation_force[freq_idx, j_idx] = coeff_dict.get('excitation_force', np.nan)
661670
full_excitation_phase[freq_idx, j_idx] = coeff_dict.get('excitation_phase', np.nan)
671+
672+
# --- NEW: Catch the c_vector from your new dict output ---
673+
if 'c_vector' in coeff_dict:
674+
full_c_vector[freq_idx, j_idx, :] = coeff_dict['c_vector']
662675

663676
Cs = temp_engine.reformat_coeffs(X_i, NMK_list, len(NMK_list) - 1)
664677

@@ -685,12 +698,14 @@ def run_and_store_results(self, problem_index: int) -> Results:
685698
full_damping_matrix[freq_idx, :, i_idx] = np.nan
686699
continue
687700

701+
# --- NEW: Pass the full_c_vector array to the storage function ---
688702
results.store_hydrodynamic_coefficients(
689703
frequencies=omegas_to_run,
690704
added_mass_matrix=full_added_mass_matrix,
691705
damping_matrix=full_damping_matrix,
692706
excitation_force=full_excitation_force,
693-
excitation_phase=full_excitation_phase
707+
excitation_phase=full_excitation_phase,
708+
c_vector=full_c_vector
694709
)
695710

696711
if all_potentials_batch_data:

package/src/openflash/results.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ def store_all_potentials(self, all_potentials_batch: list[dict]):
204204

205205

206206
def store_hydrodynamic_coefficients(self, frequencies: np.ndarray,
207-
added_mass_matrix: np.ndarray, damping_matrix: np.ndarray, excitation_force: Optional[np.ndarray] = None, excitation_phase: Optional[np.ndarray] = None):
207+
added_mass_matrix: np.ndarray, damping_matrix: np.ndarray, excitation_force: Optional[np.ndarray] = None, excitation_phase: Optional[np.ndarray] = None, c_vector: Optional[np.ndarray] = None):
208208
"""
209209
Store hydrodynamic coefficients (added mass, damping, excitation).
210210
"""
@@ -220,16 +220,16 @@ def store_hydrodynamic_coefficients(self, frequencies: np.ndarray,
220220
self.dataset['added_mass'] = (('frequency', 'mode_i', 'mode_j'), added_mass_matrix)
221221
self.dataset['damping'] = (('frequency', 'mode_i', 'mode_j'), damping_matrix)
222222

223-
# --- NEW: Store Excitation Data ---
224223
if excitation_force is not None:
225-
# Shape is (frequency, mode_i) - Force on body i due to its own motion/wave
226-
# Note: For diffraction problem strictly, this might differ, but for radiation
227-
# we align it with mode_i.
228224
self.dataset['excitation_force'] = (('frequency', 'mode_i'), excitation_force)
229225

230226
if excitation_phase is not None:
231227
self.dataset['excitation_phase'] = (('frequency', 'mode_i'), excitation_phase)
232-
# ----------------------------------
228+
229+
# --- NEW: Store c_vector ---
230+
if c_vector is not None:
231+
self.dataset['c_vector'] = (('frequency', 'mode_i', 'c_vector_index'), c_vector)
232+
# ---------------------------
233233

234234
print("Hydrodynamic coefficients stored in xarray dataset.")
235235

0 commit comments

Comments
 (0)