Skip to content

Commit 38faeb0

Browse files
authored
Merge pull request #76 from symbiotic-engineering/radial-eigenfunction-fix
R_1n radial eigenfunction fix
2 parents fb0bb34 + 431c74a commit 38faeb0

4 files changed

Lines changed: 47 additions & 73 deletions

File tree

.gitignore

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,4 +206,12 @@ html/
206206
*.mat
207207

208208
# vscode configuration files
209-
.vscode/
209+
.vscode/
210+
211+
.calkit/overleaf
212+
213+
# Calkit build artifacts
214+
.calkit/env-locks/
215+
.calkit/logs/
216+
.calkit/notebooks/executed/
217+
.calkit/notebooks/cleaned/

package/src/openflash/multi_equations.py

Lines changed: 35 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -195,18 +195,11 @@ def R_1n_vectorized(n, r, i, h, d, a):
195195
# ---------------------------------------------------------------------------------
196196

197197
cond_n_is_zero = (n == 0)
198-
cond_r_at_boundary = (r == scale(a)[i])
198+
cond_r_at_scale = (r == scale(a)[i])
199199

200200
# --- Define the outcomes for each condition ---
201-
# FIX: n=0 logic must match scalar version
202-
if i == 0:
203-
outcome_for_n_zero = np.full_like(r, 0.5)
204-
else:
205-
# Annulus: 1.0 + 0.5 * log(r / outer)
206-
# Use np.divide to handle r=0 gracefully just in case
207-
with np.errstate(divide='ignore', invalid='ignore'):
208-
val = 1.0 + 0.5 * np.log(np.divide(r, scale(a)[i]))
209-
outcome_for_n_zero = val
201+
# Outcome 1: n=0 eigenfunction (constant)
202+
outcome_for_n_zero = np.full_like(r, 0.5)
210203

211204
# Outcome 2: If n>=1 and r is at the boundary, the value is 1.0.
212205
outcome_for_r_boundary = 1.0
@@ -221,7 +214,7 @@ def R_1n_vectorized(n, r, i, h, d, a):
221214
exp(safe_lambda * (r - scale(a)[i]))
222215

223216
# --- Apply the logic using nested np.where ---
224-
result_if_n_not_zero = np.where(cond_r_at_boundary, outcome_for_r_boundary, bessel_term)
217+
result_if_n_not_zero = np.where(cond_r_at_scale, outcome_for_r_boundary, bessel_term)
225218

226219
return np.where(cond_n_is_zero, outcome_for_n_zero, result_if_n_not_zero)
227220

@@ -237,11 +230,8 @@ def diff_R_1n_vectorized(n, r, i, h, d, a):
237230

238231
condition = (n == 0)
239232

240-
if i == 0:
241-
value_if_true = np.zeros_like(r)
242-
else:
243-
# Derivative is 1/(2r)
244-
value_if_true = np.divide(1.0, 2 * r, out=np.full_like(r, np.inf), where=(r!=0))
233+
# n=0 eigenfunction is constant -> derivative is zero
234+
value_if_true = np.zeros_like(r)
245235

246236
# --- Calculation for when n > 0 ---
247237
lambda_val = lambda_ni(n, i, h, d)
@@ -276,31 +266,31 @@ def R_2n_vectorized(n, r, i, a, h, d):
276266
# -------------------------------------------
277267

278268
# LEGACY: Use Outer Radius
279-
outer_r = scale(a)[i]
269+
scale_r = scale(a)[i]
280270

281271
cond_n_is_zero = (n == 0)
282-
cond_r_at_boundary = (r == outer_r)
272+
cond_r_at_scale = (r == scale_r)
283273

284274
# Case 1: n = 0
285275
with np.errstate(divide='ignore', invalid='ignore'):
286-
outcome_for_n_zero = 0.5 * np.log(np.divide(r, outer_r))
276+
outcome_for_n_zero = 0.5 * np.log(np.divide(r, scale_r))
287277

288278
# Case 2: n > 0 and r is at the boundary
289-
outcome_for_r_boundary = 1.0
279+
outcome_for_r_scale = 1.0
290280

291281
# Case 3: n > 0 and r is not at the boundary
292282
lambda_val = lambda_ni(n, i, h, d)
293283

294284
# Mask input where n=0 to prevent 'inf' errors
295285
lambda_safe = np.where(cond_n_is_zero, 1.0, lambda_val)
296286

297-
# LEGACY: Denom uses OUTER radius
298-
denom = besselke(0, lambda_safe * outer_r)
287+
# LEGACY: Denom uses scaled radius
288+
denom = besselke(0, lambda_safe * scale_r)
299289
denom = np.where(np.abs(denom) < 1e-12, np.nan, denom)
300290

301-
bessel_term = (besselke(0, lambda_safe * r) / denom) * exp(lambda_safe * (outer_r - r))
291+
bessel_term = (besselke(0, lambda_safe * r) / denom) * exp(lambda_safe * (scale_r - r))
302292

303-
result_if_n_not_zero = np.where(cond_r_at_boundary, outcome_for_r_boundary, bessel_term)
293+
result_if_n_not_zero = np.where(cond_r_at_scale, outcome_for_r_scale, bessel_term)
304294

305295
return np.where(cond_n_is_zero, outcome_for_n_zero, result_if_n_not_zero)
306296

@@ -316,19 +306,19 @@ def diff_R_2n_vectorized(n, r, i, h, d, a):
316306

317307
# Case n > 0
318308
lambda_val = lambda_ni(n, i, h, d)
319-
outer_r = scale(a)[i] # LEGACY ANCHOR
309+
scale_r = scale(a)[i] # LEGACY ANCHOR
320310

321311
lambda_safe = np.where(n == 0, 1.0, lambda_val)
322312

323-
# LEGACY: Denom uses OUTER radius
324-
denom = besselke(0, lambda_safe * outer_r)
313+
# LEGACY: Denom uses scale radius
314+
denom = besselke(0, lambda_safe * scale_r)
325315
safe_denom = np.where(np.abs(denom) < 1e-10, 1e-10, denom)
326316

327317
with np.errstate(divide='ignore', invalid='ignore'):
328318
numerator = -lambda_safe * besselke(1, lambda_safe * r)
329319
ratio = numerator / safe_denom
330320
# LEGACY: Exponential decay from OUTER radius
331-
exp_term = exp(lambda_safe * (outer_r - r))
321+
exp_term = exp(lambda_safe * (scale_r - r))
332322
value_if_false = ratio * exp_term
333323

334324
return np.where(n == 0, value_if_true, value_if_false)
@@ -386,9 +376,9 @@ def Lambda_k_vectorized(k, r, m0, a, m_k_arr):
386376
# -------------------------------------------
387377

388378
cond_k_is_zero = (k == 0)
389-
cond_r_at_boundary = (r == scale(a)[-1])
379+
cond_r_at_scale = (r == scale(a)[-1])
390380

391-
outcome_boundary = 1.0
381+
outcome_scale = 1.0
392382

393383
# --- Case 2: k = 0 ---
394384
if m0 == inf:
@@ -426,13 +416,13 @@ def Lambda_k_vectorized(k, r, m0, a, m_k_arr):
426416
where=np.isfinite(denom_k_nonzero) & (denom_k_nonzero != 0))
427417
outcome_k_nonzero = bessel_ratio * exp(safe_m_k * (scale(a)[-1] - r))
428418

429-
result_if_not_boundary = np.where(cond_k_is_zero,
419+
result_if_not_scale = np.where(cond_k_is_zero,
430420
outcome_k_zero,
431421
outcome_k_nonzero)
432422

433-
return np.where(cond_r_at_boundary,
434-
outcome_boundary,
435-
result_if_not_boundary)
423+
return np.where(cond_r_at_scale,
424+
outcome_scale,
425+
result_if_not_scale)
436426

437427
# Differentiate wrt r
438428
def diff_Lambda_k_vectorized(k, r, m0, a, m_k_arr):
@@ -567,27 +557,11 @@ def diff_Z_k_e_vectorized(k, z, m0, h, m_k_arr, N_k_arr):
567557
#integrating R_1n * r
568558
# Integration
569559
def int_R_1n(i, n, a, h, d):
570-
if n == 0:
560+
if n == 0: # Integral of 0.5 * r
571561
if i == 0:
572-
# Central cylinder: Integral of 0.5 * r
573562
return a[i]**2/4
574563
else:
575-
# Annulus: Integral of r * (1.0 + 0.5 * ln(r/outer_r))
576-
outer_r = scale(a)[i]
577-
inner_r = a[i-1]
578-
579-
cyl_term = (outer_r**2 - inner_r**2) / 2.0
580-
581-
def log_indefinite_int(r):
582-
# Integral of 0.5 * r * ln(r/outer)
583-
# = 0.5 * [ (r^2/2)*ln(r/outer) - r^2/4 ]
584-
log_val = np.log(r/outer_r) if r > 0 else 0
585-
return 0.5 * ((r**2 / 2.0) * log_val - (r**2 / 4.0))
586-
587-
val_outer = log_indefinite_int(outer_r) # log(1)=0, so just -r^2/4 term
588-
val_inner = log_indefinite_int(inner_r)
589-
590-
return cyl_term + (val_outer - val_inner)
564+
return (a[i]**2 - a[i-1]**2)/4
591565
else:
592566
# Standard Bessel I integral (Unchanged)
593567
lambda0 = lambda_ni(n, i, h, d)
@@ -608,8 +582,9 @@ def int_R_2n(i, n, a, h, d):
608582
raise ValueError("i cannot be 0")
609583

610584
# LEGACY: Use Outer Radius
611-
outer_r = scale(a)[i]
585+
outer_r =a[i]
612586
inner_r = a[i-1] # Previous radius is inner
587+
scale_r = scale(a)[i]
613588

614589
if n == 0:
615590
# Integral of r * (0.5 * ln(r/outer_r))
@@ -618,7 +593,7 @@ def int_R_2n(i, n, a, h, d):
618593
def indefinite(r):
619594
if r <= 0: return 0
620595
# ln(r/outer_r) is 0 when r=outer_r
621-
term_log = np.log(r / outer_r)
596+
term_log = np.log(r / scale_r)
622597
return 0.5 * ((r**2 / 2) * term_log - (r**2 / 4))
623598

624599
val_outer = indefinite(outer_r)
@@ -629,13 +604,14 @@ def indefinite(r):
629604
lambda0 = lambda_ni(n, i, h, d)
630605

631606
term_outer = (outer_r * besselke(1, lambda0 * outer_r))
632-
# exp(la - la) = 1, so no exp factor needed for outer term.
607+
# exp(la - la) = 1, for scale = outer_r, so no exp factor needed for outer term in that case.
608+
term_outer *= np.exp(lambda0 * (scale_r - outer_r))
633609

634610
term_inner = (inner_r * besselke(1, lambda0 * inner_r))
635611
# exp(la - li). We need to multiply by this.
636-
term_inner *= np.exp(lambda0 * (outer_r - inner_r))
612+
term_inner *= np.exp(lambda0 * (scale_r - inner_r))
637613

638-
denom = lambda0 * besselke(0, lambda0 * outer_r)
614+
denom = lambda0 * besselke(0, lambda0 * scale_r)
639615

640616
return (term_inner - term_outer) / denom
641617
#integrating phi_p_i * d_phi_p_i/dz * r *d_r at z=d[i]
@@ -657,8 +633,8 @@ def z_n_d(n):
657633
#############################################
658634
def excitation_phase(x, NMK, m0, a): # x-vector of unknown coefficients
659635
coeff = x[-NMK[-1]] # first coefficient of e-region expansion
660-
local_scale = scale(a)
661-
return -(pi/2) + np.angle(coeff) - np.angle(besselh(0, m0 * local_scale[-1]))
636+
exterior_scale = scale(a)[-1]
637+
return -(pi/2) + np.angle(coeff) - np.angle(besselh(0, m0 * exterior_scale))
662638

663639
def excitation_force(damping, m0, h):
664640
# --- FIX: Handle infinite m0 ---

package/test/test_multi_equations.py

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -301,23 +301,12 @@ def test_int_R_1n_n0_i0(a, h, d):
301301
assert np.isclose(int_R_1n(0, 0, a, h, d), expected)
302302

303303
def test_int_R_1n_n0_i_positive(a, h, d):
304-
# Fixed expected value: Integral of r * (1.0 + 0.5 * log(r/outer))
304+
# Fixed expected value: Integral of r * 0.5
305305
i = 1
306306
outer_r = a[i]
307307
inner_r = a[i-1]
308308

309-
# 1. Cylinder term (integral of r*1.0)
310-
cyl_term = (outer_r**2 - inner_r**2) / 2.0
311-
312-
# 2. Log term helper
313-
def log_indefinite_int(r):
314-
log_val = np.log(r/outer_r) if r > 0 else 0
315-
return 0.5 * ((r**2 / 2.0) * log_val - (r**2 / 4.0))
316-
317-
val_outer = log_indefinite_int(outer_r)
318-
val_inner = log_indefinite_int(inner_r)
319-
320-
expected = cyl_term + (val_outer - val_inner)
309+
expected = (outer_r**2 - inner_r**2) / 4
321310
assert np.isclose(int_R_1n(1, 0, a, h, d), expected)
322311

323312
def test_int_R_1n_n_positive(test_n, test_i, a, h, d):

sea-lab-utils

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit f9d69427a24304292c4a140b272e63122df9b6cf

0 commit comments

Comments
 (0)