1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
//! Region 1 uniform asymptotic expansion for the I function.
//!
//! Translation of Fortran ZUNI1 from TOMS 644 / SLATEC (zbsubs.f lines 6840-7044).
//! Computes I(fnu,z) in the region |arg(z)| <= pi/3 using the uniform
//! asymptotic expansion.
#![allow(clippy::too_many_arguments)]
use num_complex::Complex;
use crate::algo::uchk::zuchk;
use crate::algo::unik::zunik;
use crate::algo::uoik::zuoik;
use crate::machine::BesselFloat;
use crate::types::{IkFlag, Scaling, SumOption};
use crate::utils::{reciprocal_z, zabs};
/// Output of ZUNI1.
#[derive(Debug, Clone, Copy)]
pub(crate) struct Uni1Output {
/// Underflow count (number of zeroed trailing members).
/// -1 indicates overflow.
pub nz: i32,
/// If nonzero, remaining items (orders fnu to fnu+nlast-1) need a different method
/// because fnu+nlast-1 < fnul.
pub nlast: i32,
}
/// Compute I(fnu,z) via Region 1 uniform asymptotic expansion.
///
/// Equivalent to Fortran ZUNI1 in TOMS 644 (zbsubs.f lines 6840-7044).
///
/// # Parameters
/// - `z`: complex argument
/// - `fnu`: starting order ν >= 0
/// - `kode`: scaling mode
/// - `y`: output slice for sequence members (length determines n)
/// - `fnul`: large order threshold
/// - `tol`, `elim`, `alim`: machine-derived thresholds
pub(crate) fn zuni1<T: BesselFloat>(
z: Complex<T>,
fnu: T,
kode: Scaling,
y: &mut [Complex<T>],
fnul: T,
tol: T,
elim: T,
alim: T,
) -> Uni1Output {
let n = y.len();
let zero = T::zero();
let one = T::one();
let czero = Complex::new(zero, zero);
let mut nz: i32 = 0;
let mut nd = n;
let mut nlast: i32 = 0;
// Initialize output slice to zero
y.fill(czero);
// ── 3-level scaling (Fortran lines 6877-6885) ──
let cscl = one / tol;
let crsc = tol;
let cssr = [cscl, one, crsc];
let csrr = [crsc, one, cscl];
let bry0 = T::from_f64(1.0e3) * T::MACH_TINY / tol;
// ── Check first member for underflow/overflow (Fortran lines 6889-6907) ──
let fn_val = fnu.max(one);
let result0 = zunik(z, fn_val, IkFlag::I, SumOption::SkipSum, tol, None);
let s1_check = if kode == Scaling::Exponential {
// KODE=2 (Fortran lines 6894-6900)
let st = z + result0.zeta2;
let rast = fn_val / zabs(st);
st.conj() * (rast * rast) - result0.zeta1
} else {
// KODE=1 (Fortran lines 6903-6904)
result0.zeta2 - result0.zeta1
};
let rs1 = s1_check.re;
if rs1.abs() > elim {
if rs1 > zero {
// Overflow (label 120 → NZ=-1)
return Uni1Output { nz: -1, nlast: 0 };
}
// All underflow (label 130 → set all to zero)
nz = n as i32;
y.fill(czero);
return Uni1Output { nz, nlast: 0 };
}
// ── Label 30: compute first min(2, nd) terms directly (Fortran lines 6908-6965) ──
'label30: loop {
let nn = nd.min(2);
let mut iflag: usize = 2; // default scaling level (1-based for cssr/csrr indexing)
let mut cy = [czero; 2]; // CYR, CYI workspace
let mut computed_ok = true;
#[allow(clippy::needless_range_loop)]
for i in 0..nn {
// fn = fnu + (nd - 1 - i) (Fortran: FN = FNU + FLOAT(ND-I), with Fortran I=1..NN)
let fn_val = fnu + T::from_f64((nd - 1 - i) as f64);
let result = zunik(z, fn_val, IkFlag::I, SumOption::Full, tol, None);
let s1_exp = if kode == Scaling::Exponential {
// KODE=2 (Fortran lines 6916-6922)
let st = z + result.zeta2;
let rast = fn_val / zabs(st);
// Note: Fortran line 6922 has +ZI for S1I (not present in KODE=1 path)
let mut s = st.conj() * (rast * rast) - result.zeta1;
s.im = s.im + z.im;
s
} else {
// KODE=1 (Fortran lines 6925-6926)
result.zeta2 - result.zeta1
};
// ── Test for underflow/overflow (Fortran lines 6931-6958) ──
let rs1 = s1_exp.re;
if rs1.abs() > elim {
// label 110: underflow or overflow
if rs1 > zero {
// Overflow (label 120)
return Uni1Output { nz: -1, nlast: 0 };
}
// Underflow: set y[nd-1] = 0, adjust nd (Fortran lines 7017-7032)
y[nd - 1] = czero;
nz += 1;
nd -= 1;
if nd == 0 {
return Uni1Output { nz, nlast: 0 };
}
// ZUOIK additional check
let nuf = zuoik(z, fnu, kode, IkFlag::I, &mut y[..nd], tol, elim, alim);
if nuf < 0 {
return Uni1Output { nz: -1, nlast: 0 };
}
nd -= nuf as usize;
nz += nuf;
if nd == 0 {
return Uni1Output { nz, nlast: 0 };
}
let fn_check = fnu + T::from_f64((nd - 1) as f64);
if fn_check >= fnul {
continue 'label30; // retry with reduced nd
}
nlast = nd as i32;
return Uni1Output { nz, nlast };
}
if i == 0 {
iflag = 2;
}
if rs1.abs() >= alim {
// Refine test (Fortran lines 6938-6943)
let aphi = zabs(result.phi);
let rs1_refined = rs1 + aphi.ln();
if rs1_refined.abs() > elim {
// label 110
if rs1_refined > zero {
return Uni1Output { nz: -1, nlast: 0 };
}
y[nd - 1] = czero;
nz += 1;
nd -= 1;
if nd == 0 {
return Uni1Output { nz, nlast: 0 };
}
let nuf = zuoik(z, fnu, kode, IkFlag::I, &mut y[..nd], tol, elim, alim);
if nuf < 0 {
return Uni1Output { nz: -1, nlast: 0 };
}
nd -= nuf as usize;
nz += nuf;
if nd == 0 {
return Uni1Output { nz, nlast: 0 };
}
let fn_check = fnu + T::from_f64((nd - 1) as f64);
if fn_check >= fnul {
continue 'label30;
}
nlast = nd as i32;
return Uni1Output { nz, nlast };
}
if i == 0 {
iflag = 1;
}
if rs1 < zero {
// iflag stays at 1 if i==0 already set it
} else if i == 0 {
iflag = 3;
}
}
// ── Scale S1 and compute S2 = PHI * SUM (Fortran lines 6948-6964) ──
let s2 = result.phi * result.sum;
let s1_scaled = s1_exp.exp() * cssr[iflag - 1];
let s2_final = s2 * s1_scaled;
if iflag == 1 && zuchk(s2_final, bry0, tol) {
// Underflow (label 110)
if rs1 > zero {
return Uni1Output { nz: -1, nlast: 0 };
}
y[nd - 1] = czero;
nz += 1;
nd -= 1;
if nd == 0 {
return Uni1Output { nz, nlast: 0 };
}
let nuf = zuoik(z, fnu, kode, IkFlag::I, &mut y[..nd], tol, elim, alim);
if nuf < 0 {
return Uni1Output { nz: -1, nlast: 0 };
}
nd -= nuf as usize;
nz += nuf;
if nd == 0 {
return Uni1Output { nz, nlast: 0 };
}
let fn_check = fnu + T::from_f64((nd - 1) as f64);
if fn_check >= fnul {
computed_ok = false;
break;
}
nlast = nd as i32;
return Uni1Output { nz, nlast };
}
// Store results (Fortran lines 6960-6964)
cy[i] = s2_final;
// m = nd - i (Fortran: M = ND - I + 1, 1-based)
let m = nd - 1 - i;
y[m] = s2_final * csrr[iflag - 1];
}
if !computed_ok {
continue 'label30;
}
// ── Forward recurrence for remaining terms (Fortran lines 6966-7011) ──
if nd <= 2 {
return Uni1Output { nz, nlast };
}
let rz = reciprocal_z(z);
let bry1 = one / bry0;
let bry2 = T::MACH_HUGE;
let mut s1 = cy[0]; // CYR(1), CYI(1)
let mut s2 = cy[1]; // CYR(2), CYI(2)
let mut c1r = csrr[iflag - 1];
let mut ascle = if iflag == 1 {
bry0
} else if iflag == 2 {
bry1
} else {
bry2
};
let mut k = nd - 2; // 0-based index into y (Fortran K = ND - 2, 1-based)
let mut fn_rec = T::from_f64(k as f64); // Fortran: FN = FLOAT(K), where K starts at ND-2
// Fortran DO 90 I=3,ND
for _i in 2..nd {
let c2 = s2;
let cfn = fnu + fn_rec;
s2 = s1 + rz * c2 * cfn;
s1 = c2;
let c2_scaled = s2 * c1r;
y[k] = c2_scaled;
k -= 1;
fn_rec = fn_rec - one;
if iflag >= 3 {
continue;
}
let c2m = c2_scaled.re.abs().max(c2_scaled.im.abs());
if c2m <= ascle {
continue;
}
iflag += 1;
ascle = if iflag == 2 { bry1 } else { bry2 };
s1 = s1 * c1r;
s2 = c2_scaled;
s1 = s1 * cssr[iflag - 1];
s2 = s2 * cssr[iflag - 1];
c1r = csrr[iflag - 1];
}
return Uni1Output { nz, nlast };
}
}