@@ -188,30 +188,34 @@ def diff_z_phi_p_i(d, z, h):
188188def 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):
220224def 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)
289301def 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
316331def 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
332351def 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]
640619def int_phi_p_i (i , h , d , a ):
0 commit comments