Skip to content

Commit 1e682ca

Browse files
committed
applied the np.asarray() casting to all vectorized functions (R_1n, R_2n, Z_n, Lambda_k, Z_k, and their derivatives). This ensures that passing a Python list (as meem_engine.py does) will no longer crash the math operations
1 parent 0fa25b5 commit 1e682ca

File tree

1 file changed

+72
-93
lines changed

1 file changed

+72
-93
lines changed

package/src/openflash/multi_equations.py

Lines changed: 72 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -188,30 +188,34 @@ def diff_z_phi_p_i(d, z, h):
188188
def R_1n_vectorized(n, r, i, h, d, a):
189189
"""
190190
Vectorized version of the R_1n radial eigenfunction.
191-
FIXED: Handles i!=0 case for n=0 to match scalar R_1n.
192191
"""
193-
# --- Define the conditions for the nested logic ---
192+
# --- FIX: Ensure inputs are arrays ---
193+
n = np.asarray(n)
194+
r = np.asarray(r)
195+
# -------------------------------------
196+
194197
cond_n_is_zero = (n == 0)
195198
cond_r_at_boundary = (r == scale(a)[i])
196199

197200
# --- Define the outcomes for each condition ---
198-
# FIX: n=0 logic must match scalar version
199201
if i == 0:
200202
outcome_for_n_zero = np.full_like(r, 0.5)
201203
else:
202204
# Annulus: 1.0 + 0.5 * log(r / outer)
203-
# Handle r=0 if necessary, though in annulus r > 0.
204-
outcome_for_n_zero = 1.0 + 0.5 * np.log(r / scale(a)[i])
205+
# Use np.divide to handle r=0 gracefully just in case
206+
with np.errstate(divide='ignore', invalid='ignore'):
207+
val = 1.0 + 0.5 * np.log(np.divide(r, scale(a)[i]))
208+
outcome_for_n_zero = val
205209

206-
# Outcome 2: If n>=1 and r is at the boundary, the value is 1.0.
207210
outcome_for_r_boundary = 1.0
208211

209-
# Outcome 3: The general case for n>=1 inside the boundary.
212+
# Calculate lambda. Mask n=0 to prevent divide by zero in Bessel if lambda becomes 0
210213
lambda_val = lambda_ni(n, i, h, d)
211-
bessel_term = (besselie(0, lambda_val * r) / besselie(0, lambda_val * scale(a)[i])) * \
212-
exp(lambda_val * (r - scale(a)[i]))
214+
safe_lambda = np.where(cond_n_is_zero, 1.0, lambda_val)
215+
216+
bessel_term = (besselie(0, safe_lambda * r) / besselie(0, safe_lambda * scale(a)[i])) * \
217+
exp(safe_lambda * (r - scale(a)[i]))
213218

214-
# --- Apply the logic using nested np.where ---
215219
result_if_n_not_zero = np.where(cond_r_at_boundary, outcome_for_r_boundary, bessel_term)
216220

217221
return np.where(cond_n_is_zero, outcome_for_n_zero, result_if_n_not_zero)
@@ -220,28 +224,30 @@ def R_1n_vectorized(n, r, i, h, d, a):
220224
def diff_R_1n_vectorized(n, r, i, h, d, a):
221225
"""
222226
Vectorized derivative of the diff_R_1n radial function.
223-
FIXED: Handles i!=0 case for n=0 to match scalar version.
224227
"""
228+
# --- FIX: Ensure inputs are arrays ---
229+
n = np.asarray(n)
230+
r = np.asarray(r)
231+
# -------------------------------------
232+
225233
condition = (n == 0)
226234

227-
# FIX: n=0 derivative logic
228235
if i == 0:
229236
value_if_true = np.zeros_like(r)
230237
else:
231-
# Derivative is 1/(2r)
232238
value_if_true = np.divide(1.0, 2 * r, out=np.full_like(r, np.inf), where=(r!=0))
233239

234-
# --- Calculation for when n > 0 ---
235240
lambda_val = lambda_ni(n, i, h, d)
241+
safe_lambda = np.where(condition, 1.0, lambda_val)
236242

237-
numerator = lambda_val * besselie(1, lambda_val * r)
238-
denominator = besselie(0, lambda_val * scale(a)[i])
243+
numerator = safe_lambda * besselie(1, safe_lambda * r)
244+
denominator = besselie(0, safe_lambda * scale(a)[i])
239245

240246
bessel_ratio = np.divide(numerator, denominator,
241247
out=np.zeros_like(numerator, dtype=float),
242248
where=(denominator != 0))
243249

244-
value_if_false = bessel_ratio * exp(lambda_val * (r - scale(a)[i]))
250+
value_if_false = bessel_ratio * exp(safe_lambda * (r - scale(a)[i]))
245251

246252
return np.where(condition, value_if_true, value_if_false)
247253

@@ -256,6 +262,11 @@ def R_2n_vectorized(n, r, i, a, h, d):
256262
"""
257263
if i == 0:
258264
raise ValueError("R_2n function is not defined for the innermost region (i=0).")
265+
266+
# --- FIX: Ensure inputs are arrays ---
267+
n = np.asarray(n)
268+
r = np.asarray(r)
269+
# -------------------------------------
259270

260271
# LEGACY: Use Outer Radius
261272
outer_r = scale(a)[i]
@@ -264,7 +275,8 @@ def R_2n_vectorized(n, r, i, a, h, d):
264275
cond_r_at_boundary = (r == outer_r)
265276

266277
# Case 1: n = 0
267-
outcome_for_n_zero = 0.5 * np.log(r / outer_r)
278+
with np.errstate(divide='ignore', invalid='ignore'):
279+
outcome_for_n_zero = 0.5 * np.log(np.divide(r, outer_r))
268280

269281
# Case 2: n > 0 and r is at the boundary
270282
outcome_for_r_boundary = 1.0
@@ -287,8 +299,10 @@ def R_2n_vectorized(n, r, i, a, h, d):
287299

288300
# Differentiate wrt r (Unchanged, as d/dr(1.0) is 0)
289301
def diff_R_2n_vectorized(n, r, i, h, d, a):
302+
# --- FIX: Ensure inputs are arrays ---
290303
n = np.asarray(n)
291304
r = np.asarray(r)
305+
# -------------------------------------
292306

293307
# Case n == 0: Derivative is still 1/(2r)
294308
value_if_true = np.divide(1.0, 2 * r, out=np.full_like(r, np.inf), where=(r != 0))
@@ -311,40 +325,46 @@ def diff_R_2n_vectorized(n, r, i, h, d, a):
311325
value_if_false = ratio * exp_term
312326

313327
return np.where(n == 0, value_if_true, value_if_false)
328+
314329
#############################################
315330
# i-region vertical eigenfunctions
316331
def Z_n_i_vectorized(n, z, i, h, d):
317332
"""
318333
Vectorized version of the i-region vertical eigenfunction Z_n_i.
319334
"""
320-
# Define the condition to check for each element in the 'n' array
335+
# --- FIX: Ensure inputs are arrays ---
336+
n = np.asarray(n)
337+
z = np.asarray(z)
338+
# -------------------------------------
339+
321340
condition = (n == 0)
322341

323-
# Define the calculation for when n != 0
342+
lambda_val = lambda_ni(n, i, h, d)
343+
safe_lambda = np.where(condition, 0.0, lambda_val)
344+
324345
# This part is already vectorized thanks to NumPy
325-
value_if_false = np.sqrt(2) * np.cos(lambda_ni(n, i, h, d) * (z + h))
346+
value_if_false = np.sqrt(2) * np.cos(safe_lambda * (z + h))
326347

327348
# Use np.where to choose the output:
328-
# If condition is True (n==0), return 1.0.
329-
# Otherwise, return the result of the calculation.
330349
return np.where(condition, 1.0, value_if_false)
331350

332351
def diff_Z_n_i_vectorized(n, z, i, h, d):
333352
"""
334353
Vectorized derivative of the Z_n_i vertical function.
335354
"""
336-
# Define the condition to be applied element-wise.
337-
condition = (n == 0)
355+
# --- FIX: Ensure inputs are arrays ---
356+
n = np.asarray(n)
357+
z = np.asarray(z)
358+
# -------------------------------------
338359

339-
# Define the value if the condition is True (when n=0).
360+
condition = (n == 0)
340361
value_if_true = 0.0
341362

342-
# Define the calculation for when the condition is False (when n > 0).
343-
# This part is already vectorized.
344363
lambda_val = lambda_ni(n, i, h, d)
345-
value_if_false = -lambda_val * np.sqrt(2) * np.sin(lambda_val * (z + h))
364+
safe_lambda = np.where(condition, 0.0, lambda_val)
365+
366+
value_if_false = -safe_lambda * np.sqrt(2) * np.sin(safe_lambda * (z + h))
346367

347-
# Use np.where to select the output based on the condition.
348368
return np.where(condition, value_if_true, value_if_false)
349369

350370
#############################################
@@ -353,6 +373,11 @@ def Lambda_k_vectorized(k, r, m0, a, m_k_arr):
353373
"""
354374
Vectorized version of the exterior region radial eigenfunction Lambda_k.
355375
"""
376+
# --- FIX: Ensure inputs are arrays ---
377+
k = np.asarray(k)
378+
r = np.asarray(r)
379+
# -------------------------------------
380+
356381
if m0 == inf:
357382
return np.ones(np.broadcast(k, r).shape, dtype=float)
358383

@@ -361,15 +386,15 @@ def Lambda_k_vectorized(k, r, m0, a, m_k_arr):
361386

362387
outcome_boundary = 1.0
363388

364-
# --- Case 2: k = 0 (NEEDS FIX) ---
389+
# --- Case 2: k = 0 ---
365390
denom_k_zero = besselh(0, m0 * scale(a)[-1])
366391
numer_k_zero = besselh(0, m0 * r)
367392
with np.errstate(divide='ignore', invalid='ignore'):
368393
outcome_k_zero = np.divide(numer_k_zero, denom_k_zero,
369394
out=np.zeros_like(numer_k_zero, dtype=complex),
370395
where=np.isfinite(denom_k_zero) & (denom_k_zero != 0))
371396

372-
# --- Case 3: k > 0 (NEEDS FIX) ---
397+
# --- Case 3: k > 0 ---
373398
# Mask input where k=0 to prevent errors
374399
local_m_k_k = m_k_arr[k]
375400
safe_m_k = np.where(cond_k_is_zero, 1.0, local_m_k_k)
@@ -381,7 +406,6 @@ def Lambda_k_vectorized(k, r, m0, a, m_k_arr):
381406
out=np.zeros_like(numer_k_nonzero),
382407
where=np.isfinite(denom_k_nonzero) & (denom_k_nonzero != 0))
383408
outcome_k_nonzero = bessel_ratio * exp(safe_m_k * (scale(a)[-1] - r))
384-
# --- END FIXES ---
385409

386410
result_if_not_boundary = np.where(cond_k_is_zero,
387411
outcome_k_zero,
@@ -396,6 +420,11 @@ def diff_Lambda_k_vectorized(k, r, m0, a, m_k_arr):
396420
"""
397421
Vectorized derivative of the exterior region radial function Lambda_k.
398422
"""
423+
# --- FIX: Ensure inputs are arrays ---
424+
k = np.asarray(k)
425+
r = np.asarray(r)
426+
# -------------------------------------
427+
399428
# Handle the scalar case where m0 is infinite. The result is always 1.
400429
if m0 == inf:
401430
return np.ones(np.broadcast(k, r).shape, dtype=float)
@@ -451,6 +480,11 @@ def Z_k_e_vectorized(k, z, m0, h, m_k_arr, N_k_arr):
451480
Vectorized version of the e-region vertical eigenfunction Z_k_e.
452481
This version uses pre-calculated m_k_arr and N_k_arr for efficiency.
453482
"""
483+
# --- FIX: Ensure inputs are arrays ---
484+
k = np.asarray(k)
485+
z = np.asarray(z)
486+
# -------------------------------------
487+
454488
# This outer conditional is fine because it operates on scalar inputs.
455489
if m0 * h < M0_H_THRESH:
456490
# --- Logic for the standard case ---
@@ -477,6 +511,11 @@ def diff_Z_k_e_vectorized(k, z, m0, h, m_k_arr, N_k_arr):
477511
Vectorized derivative of the e-region vertical eigenfunction Z_k_e.
478512
This version uses pre-calculated m_k_arr and N_k_arr for efficiency.
479513
"""
514+
# --- FIX: Ensure inputs are arrays ---
515+
k = np.asarray(k)
516+
z = np.asarray(z)
517+
# -------------------------------------
518+
480519
# This outer conditional is fine because it operates on scalar inputs.
481520
if m0 * h < M0_H_THRESH:
482521
# --- Logic for the standard case ---
@@ -564,65 +603,8 @@ def indefinite(r):
564603
return val_outer - val_inner
565604

566605
else:
567-
# Integral of r * K0(lambda*r) / K0(lambda*outer) * exp(...)
568-
# The old code handled this via standard Bessel integrals.
569-
# We must replicate the specific normalization of old_assembly.
570-
571606
lambda0 = lambda_ni(n, i, h, d)
572607

573-
# In old_assembly, R_2n = K0(lr)/K0(la) * exp(...)
574-
# The integral of x*K0(x) is -x*K1(x).
575-
576-
# Numerator term (pure Bessel K integral part)
577-
# Int[ r * K0(lambda*r) ] = - (r/lambda) * K1(lambda*r)
578-
579-
# We need to evaluate: [ - (r/lambda)*K1(lambda*r) ] from inner to outer
580-
# Then multiply by the normalization constant: exp(lambda*outer) / K0(lambda*outer)
581-
# Wait, the exponential term in R_2n_old is: exp(lambda * (outer_r - r))
582-
# This exponential cancels out the exp inside the scaled Bessel K if we use K_scaled.
583-
# But old_assembly likely used raw K. Let's stick to the raw math.
584-
585-
# Let's look at the old implementation logic provided in previous turns:
586-
# It normalizes by K0(outer).
587-
588-
# Exact calculation matching old_assembly:
589-
k0_outer = besselke(0, lambda0 * outer_r) # scaled K0
590-
591-
# We need the integral of:
592-
# ( K0(lr) / K0(la) ) * exp( l(a-r) ) * r
593-
# = ( K0(lr)*exp(lr) / (K0(la)*exp(la)) ) * r <-- exp terms cancel strictly if using scaled K
594-
# effectively it is Integral( r * K0_scaled(lr) ) / K0_scaled(la)
595-
596-
# Analytic integral of x * K0(x) is -x * K1(x).
597-
# So Int( r * K0(lr) ) = - (r/lambda) * K1(lr)
598-
599-
# Converting to scaled Bessel K (kve):
600-
# K1(x) = kve(1, x) * exp(-x)
601-
# So - (r/l) * kve(1, lr) * exp(-lr)
602-
603-
# We want: [ - (r/l) * kve(1, lr) * exp(-lr) ] / [ kve(0, la) * exp(-la) ] * exp( l(a-r) )
604-
# Notice exp(-lr) * exp(l(a-r)) = exp(-lr + la - lr) ... wait.
605-
# R_2n definition: ( kve(0, lr) * exp(-lr) ) / ( kve(0, la) * exp(-la) ) * exp( l(a-r) )
606-
# = kve(0, lr) / kve(0, la) * exp( -lr + la + la - lr ) ... no.
607-
608-
# SIMPLER PATH:
609-
# R_2n_old(r) = K0(lr) / K0(la) (Unscaled)
610-
# Int(r * R_2n) = (1/K0(la)) * [ - (r/l)*K1(lr) ] bounds inner..outer
611-
612-
# Upper bound (r=outer):
613-
# - (outer/l) * K1(l*outer) / K0(l*outer)
614-
615-
# Lower bound (r=inner):
616-
# - (inner/l) * K1(l*inner) / K0(l*outer)
617-
618-
# Result = Upper - Lower
619-
# = (1/lambda0) * ( inner*K1(l*inner) - outer*K1(l*outer) ) / K0(l*outer)
620-
621-
# Using Scaled Bessel functions (kve) to match python 'besselke':
622-
# K1(x) = ke1(x) * exp(-x)
623-
# K0(x) = ke0(x) * exp(-x)
624-
# Ratio K1(x)/K0(y) = ke1(x)/ke0(y) * exp(y-x)
625-
626608
term_outer = (outer_r * besselke(1, lambda0 * outer_r))
627609
# exp(la - la) = 1, so no exp factor needed for outer term.
628610

@@ -632,9 +614,6 @@ def indefinite(r):
632614

633615
denom = lambda0 * besselke(0, lambda0 * outer_r)
634616

635-
# Result = (inner_term - outer_term) / (lambda * K0_scaled(outer))
636-
# Note the sign flip from the integration limits (Upper - Lower) vs (-x*K1).
637-
638617
return (term_inner - term_outer) / denom
639618
#integrating phi_p_i * d_phi_p_i/dz * r *d_r at z=d[i]
640619
def int_phi_p_i(i, h, d, a):

0 commit comments

Comments
 (0)