Skip to main content

fcmaes_core/
biteopt.rs

1// Numeric kernels index several parallel arrays by a shared loop counter, where
2// range loops read more clearly than zipped iterators.
3#![allow(clippy::needless_range_loop, clippy::manual_memcpy)]
4
5//! BiteOpt — Rust port of Aleksey Vaneev's BiteOpt
6//! (<https://github.com/avaneev/biteopt>), from the C++ `biteopt.h`/`biteaux.h`.
7//!
8//! The implementation includes the LCG-hash `BiteRnd`, 58-bit integer-mantissa
9//! parameters, adaptive sparse selectors, dynamic and diverging populations,
10//! all primary generators, CSpherOpt, sequential Nelder-Mead, deep
11//! multi-population solution exchange, and delayed-feedback batch ask/tell.
12//! Parity is validated by convergence because no independent Python twin
13//! exists.
14
15use std::collections::VecDeque;
16
17use crate::fitness::Objective;
18
19const INT_MANT_BITS: u32 = 58;
20const INT_MANT_MULT: i64 = 1i64 << INT_MANT_BITS;
21const INT_MANT_MASK: i64 = INT_MANT_MULT - 1;
22const BAD_COST: f64 = 1e300;
23const MAX_DEEP_OPTIMIZERS: i32 = 36;
24
25#[inline]
26fn sanitize_cost(cost: f64) -> f64 {
27    if cost.is_finite() { cost } else { BAD_COST }
28}
29
30#[inline]
31fn objective_cost(objective: &dyn Objective, values: &[f64]) -> f64 {
32    sanitize_cost(objective.eval_scalar(values))
33}
34
35/// Validate the dimensions and numeric domain required by BiteOpt.
36pub fn validate_bite_inputs(
37    lower: &[f64],
38    upper: &[f64],
39    init: Option<&[f64]>,
40    params: &BiteParams,
41    depth: i32,
42) -> Result<(), String> {
43    if lower.is_empty() || lower.len() != upper.len() {
44        return Err("bounds must be non-empty and have equal lengths".into());
45    }
46    for (&lo, &hi) in lower.iter().zip(upper) {
47        if !lo.is_finite() || !hi.is_finite() || lo >= hi || !(hi - lo).is_finite() {
48            return Err("bounds must contain finite intervals with lower < upper".into());
49        }
50    }
51    if let Some(values) = init {
52        if values.len() != lower.len() {
53            return Err("initial guess must match the bounds dimension".into());
54        }
55        if values.iter().any(|value| !value.is_finite()) {
56            return Err("initial guess must contain only finite values".into());
57        }
58    }
59    if params.stop_fitness.is_nan() {
60        return Err("stop_fitness must not be NaN".into());
61    }
62    if (1..4).contains(&params.popsize) {
63        return Err("popsize must be non-positive (automatic) or at least 4".into());
64    }
65    if depth > MAX_DEEP_OPTIMIZERS {
66        return Err(format!(
67            "M must not exceed {MAX_DEEP_OPTIMIZERS} deep optimizers"
68        ));
69    }
70    Ok(())
71}
72
73/// A generated-but-not-yet-applied candidate (frozen population state).
74struct Candidate {
75    enc: Vec<i64>,
76    real: Vec<f64>,
77    sels: Vec<SelUse>,
78    is_init: bool,
79    /// Set by `generateSolPar`: the parallel optimizer already evaluated it.
80    precomputed_cost: Option<f64>,
81}
82
83/// Which population a generator draws from.
84#[derive(Clone, Copy)]
85enum PopSel {
86    Main,
87    Par(usize),
88    ParOpt,
89    ParOpt2,
90}
91
92/// Outcome of a BiteOpt run.
93#[derive(Clone, Debug)]
94pub struct BiteResult {
95    pub x: Vec<f64>,
96    pub y: f64,
97    pub evaluations: u64,
98    pub iterations: i32,
99    pub stop: i32,
100}
101
102/// Tunable inputs for [`optimize_bite`].
103#[derive(Clone, Debug)]
104pub struct BiteParams {
105    pub popsize: i32,
106    pub max_evaluations: u64,
107    pub stop_fitness: f64,
108    pub stall_criterion: i32,
109    pub seed: u64,
110    pub runid: i64,
111}
112
113impl Default for BiteParams {
114    fn default() -> Self {
115        Self {
116            popsize: 0,
117            max_evaluations: 100_000,
118            stop_fitness: f64::NEG_INFINITY,
119            stall_criterion: 0,
120            seed: 0,
121            runid: 0,
122        }
123    }
124}
125
126// ---------------------------------------------------------------------------
127// PRNG (faithful port of CBiteRnd)
128// ---------------------------------------------------------------------------
129
130pub struct BiteRnd {
131    seed: u64,
132    lcg: u64,
133    hash: u64,
134    bit_pool: u64,
135    bits_left: i32,
136}
137
138impl BiteRnd {
139    pub fn new(seed: u64) -> Self {
140        let mut r = BiteRnd {
141            seed,
142            lcg: 0,
143            hash: 0,
144            bit_pool: 0,
145            bits_left: 0,
146        };
147        for _ in 0..5 {
148            r.advance();
149        }
150        r
151    }
152
153    #[inline]
154    fn advance(&mut self) -> u64 {
155        self.seed = self
156            .seed
157            .wrapping_mul(self.lcg.wrapping_mul(2).wrapping_add(1));
158        let rs = self.seed.rotate_left(32);
159        self.hash = self.hash.wrapping_add(rs).wrapping_add(0xAAAAAAAAAAAAAAAA);
160        self.lcg = self
161            .lcg
162            .wrapping_add(self.seed)
163            .wrapping_add(0x5555555555555555);
164        self.seed ^= self.hash;
165        self.lcg ^ rs
166    }
167
168    #[inline]
169    pub fn get(&mut self) -> f64 {
170        (self.advance() >> (64 - 53)) as f64 * (-53f64).exp2()
171    }
172    #[inline]
173    pub fn get_int(&mut self, n: i32) -> i32 {
174        (self.get() * n as f64) as i32
175    }
176    #[inline]
177    fn get_sqr(&mut self) -> f64 {
178        let v = self.get();
179        v * v
180    }
181    #[inline]
182    pub fn get_sqr_int(&mut self, n: i32) -> i32 {
183        (self.get_sqr() * n as f64) as i32
184    }
185    fn get_pow(&mut self, p: f64) -> f64 {
186        let v = self.get();
187        // These are the powers used by BiteOpt's hot paths. Matching the
188        // upstream algebra avoids a comparatively expensive generic pow().
189        match p {
190            0.25 => v.sqrt().sqrt(),
191            0.5 => v.sqrt(),
192            1.0 => v,
193            1.5 => v * v.sqrt(),
194            1.75 => {
195                let sv = v.sqrt();
196                v * sv * sv.sqrt()
197            }
198            2.0 => v * v,
199            3.0 => v * v * v,
200            4.0 => {
201                let v2 = v * v;
202                v2 * v2
203            }
204            _ => v.powf(p),
205        }
206    }
207    #[inline]
208    pub fn get_pow_int(&mut self, p: f64, n: i32) -> i32 {
209        (self.get_pow(p) * n as f64) as i32
210    }
211    #[inline]
212    pub fn get_raw(&mut self) -> u64 {
213        self.advance()
214    }
215    #[inline]
216    pub fn get_tpdf(&mut self) -> f64 {
217        let v1 = (self.advance() >> (64 - 53)) as i64;
218        let v2 = (self.advance() >> (64 - 53)) as i64;
219        (v1 - v2) as f64 * (-53f64).exp2()
220    }
221    #[inline]
222    pub fn get_bit(&mut self) -> i32 {
223        if self.bits_left == 0 {
224            self.bit_pool = self.advance();
225            let b = (self.bit_pool & 1) as i32;
226            self.bits_left = 63;
227            self.bit_pool >>= 1;
228            return b;
229        }
230        let b = (self.bit_pool & 1) as i32;
231        self.bits_left -= 1;
232        self.bit_pool >>= 1;
233        b
234    }
235    /// Leva's fast normal generator (as in CBiteRnd::getGaussian).
236    fn get_gaussian(&mut self) -> f64 {
237        loop {
238            let mut u = self.get();
239            let mut v = self.get();
240            if u == 0.0 || v == 0.0 {
241                u = 1.0;
242                v = 1.0;
243            }
244            v = 1.7156 * (v - 0.5);
245            let x = u - 0.449871;
246            let y = v.abs() + 0.386595;
247            let q = x * x + y * (0.19600 * y - 0.25472 * x);
248            if q < 0.27597 {
249                return v / u;
250            }
251            if q <= 0.27846 && v * v <= -4.0 * u.ln() * u * u {
252                return v / u;
253            }
254        }
255    }
256}
257
258fn wrap_param(rnd: &mut BiteRnd, v: i64) -> i64 {
259    if v < 0 {
260        if v > -INT_MANT_MULT {
261            (rnd.get() * (-v) as f64) as i64
262        } else {
263            (rnd.get_raw() as i64) & INT_MANT_MASK
264        }
265    } else if v > INT_MANT_MULT {
266        if v < INT_MANT_MULT * 2 {
267            (INT_MANT_MULT as f64 - rnd.get() * (v - INT_MANT_MULT) as f64) as i64
268        } else {
269            (rnd.get_raw() as i64) & INT_MANT_MASK
270        }
271    } else {
272        v
273    }
274}
275
276fn gaussian_int(rnd: &mut BiteRnd, sd: f64, mean: i64) -> i64 {
277    loop {
278        let r = rnd.get_gaussian() * sd;
279        if r > -8.0 && r < 8.0 {
280            return (r * INT_MANT_MULT as f64) as i64 + mean;
281        }
282    }
283}
284
285// ---------------------------------------------------------------------------
286// Adaptive selector (faithful port of CBiteSelBase)
287// ---------------------------------------------------------------------------
288
289const SLOT_COUNT: usize = 5;
290
291/// Selector state captured when a candidate is generated. Delayed batch
292/// feedback must restore this state instead of updating the last selection
293/// subsequently made for another candidate.
294#[derive(Clone, Copy)]
295struct SelUse {
296    index: usize,
297    value: i32,
298    position: usize,
299    slot_id: u8,
300    entry_id: u8,
301}
302
303struct BiteSel {
304    count: usize,
305    count_sp: usize,
306    count_sp1: usize,
307    accum_coeff: f64,
308    slot_accums: [f64; SLOT_COUNT],
309    slot_ids: [u8; SLOT_COUNT],
310    sels: [Vec<i32>; SLOT_COUNT],
311    entry_ids: [Vec<u8>; SLOT_COUNT],
312    sel: i32,
313    sel_id: u8,
314    selp: usize,
315    slot: usize,
316}
317
318impl BiteSel {
319    fn new(count: usize) -> Self {
320        BiteSel {
321            count,
322            count_sp: 0,
323            count_sp1: 0,
324            accum_coeff: 0.0,
325            slot_accums: [0.0; SLOT_COUNT],
326            slot_ids: [0, 1, 2, 3, 4],
327            sels: Default::default(),
328            entry_ids: Default::default(),
329            sel: 0,
330            sel_id: 0,
331            selp: 0,
332            slot: 0,
333        }
334    }
335
336    fn reset(&mut self, rnd: &mut BiteRnd, param_count: usize) {
337        let sparse_mul = 5usize;
338        self.count_sp = self.count * sparse_mul;
339        self.count_sp1 = self.count_sp - 1;
340        self.accum_coeff = 1.0 / (param_count as f64).sqrt();
341        for j in 0..SLOT_COUNT {
342            let mut sp = vec![0i32; self.count_sp];
343            let mut ids: Vec<u8> = (0..self.count_sp as u8).collect();
344            for i in 0..self.count {
345                for k in 0..sparse_mul {
346                    sp[i * sparse_mul + k] = i as i32;
347                }
348            }
349            for _ in 0..self.count_sp * 5 {
350                let i1 = rnd.get_int(self.count_sp as i32) as usize;
351                let i2 = rnd.get_int(self.count_sp as i32) as usize;
352                sp.swap(i1, i2);
353                ids.swap(i1, i2);
354            }
355            self.sels[j] = sp;
356            self.entry_ids[j] = ids;
357            self.slot_accums[j] = 0.0;
358            self.slot_ids[j] = j as u8;
359        }
360        self.slot = 0;
361        self.select(rnd);
362    }
363
364    fn select(&mut self, rnd: &mut BiteRnd) -> i32 {
365        self.slot = rnd.get_pow_int(1.5, SLOT_COUNT as i32) as usize;
366        self.selp = rnd.get_pow_int(1.5, self.count_sp as i32) as usize;
367        self.sel = self.sels[self.slot][self.selp];
368        self.sel_id = self.entry_ids[self.slot][self.selp];
369        self.sel
370    }
371
372    fn incr(&mut self, v: f64) {
373        let dp = (-(self.selp as f64) * v * v) as i64;
374        if dp < 0 {
375            if dp == -1 {
376                self.sels[self.slot].swap(self.selp, self.selp - 1);
377                self.entry_ids[self.slot].swap(self.selp, self.selp - 1);
378            } else {
379                let np = (self.selp as i64 + dp) as usize;
380                self.sels[self.slot].copy_within(np..self.selp, np + 1);
381                self.entry_ids[self.slot].copy_within(np..self.selp, np + 1);
382                self.sels[self.slot][np] = self.sel;
383                self.entry_ids[self.slot][np] = self.sel_id;
384            }
385        }
386        self.slot_accums[self.slot] += self.accum_coeff;
387        if self.slot_accums[self.slot] >= 1.0 {
388            let a = self.slot_accums[self.slot] - 1.0;
389            if self.slot > 0 {
390                self.sels.swap(self.slot, self.slot - 1);
391                self.entry_ids.swap(self.slot, self.slot - 1);
392                self.slot_ids.swap(self.slot, self.slot - 1);
393                self.slot_accums[self.slot] = self.slot_accums[self.slot - 1];
394                self.slot_accums[self.slot - 1] = a;
395            } else {
396                self.slot_accums[self.slot] = a;
397            }
398        }
399    }
400
401    fn decr(&mut self) {
402        if self.selp < self.count_sp1 {
403            self.sels[self.slot].swap(self.selp, self.selp + 1);
404            self.entry_ids[self.slot].swap(self.selp, self.selp + 1);
405        }
406        self.slot_accums[self.slot] -= self.accum_coeff;
407        if self.slot_accums[self.slot] <= -1.0 {
408            let a = self.slot_accums[self.slot] + 1.0;
409            if self.slot < SLOT_COUNT - 1 {
410                self.sels.swap(self.slot, self.slot + 1);
411                self.entry_ids.swap(self.slot, self.slot + 1);
412                self.slot_ids.swap(self.slot, self.slot + 1);
413                self.slot_accums[self.slot] = self.slot_accums[self.slot + 1];
414                self.slot_accums[self.slot + 1] = a;
415            } else {
416                self.slot_accums[self.slot] = a;
417            }
418        }
419    }
420
421    fn captured(&self, index: usize) -> SelUse {
422        SelUse {
423            index,
424            value: self.sel,
425            position: self.selp,
426            slot_id: self.slot_ids[self.slot],
427            entry_id: self.sel_id,
428        }
429    }
430
431    fn restore(&mut self, selection: SelUse) {
432        self.slot = self
433            .slot_ids
434            .iter()
435            .position(|&id| id == selection.slot_id)
436            .unwrap_or(0);
437        self.sel = selection.value;
438        self.sel_id = selection.entry_id;
439        self.selp = self.entry_ids[self.slot]
440            .iter()
441            .position(|&id| id == selection.entry_id)
442            .or_else(|| {
443                self.sels[self.slot]
444                    .iter()
445                    .enumerate()
446                    .filter(|(_, value)| **value == selection.value)
447                    .min_by_key(|(position, _)| position.abs_diff(selection.position))
448                    .map(|(position, _)| position)
449            })
450            .unwrap_or(selection.position.min(self.count_sp1));
451    }
452
453    fn incr_captured(&mut self, selection: SelUse, value: f64) {
454        self.restore(selection);
455        self.incr(value);
456    }
457
458    fn decr_captured(&mut self, selection: SelUse) {
459        self.restore(selection);
460        self.decr();
461    }
462}
463
464// Selector indices (full CBiteOpt roster).
465mod sel {
466    pub const METHOD: usize = 0;
467    pub const M1: usize = 1;
468    pub const M1A: usize = 2;
469    pub const M1B: usize = 3;
470    pub const M1C: usize = 4;
471    pub const M2: usize = 5;
472    pub const M2B: usize = 6;
473    pub const POP_CHANGE_INCR: usize = 7;
474    pub const POP_CHANGE_DECR: usize = 8;
475    pub const PAR_OPT2: usize = 9;
476    pub const PAR_POP_P: usize = 10; // [0..4] = 10..14
477    pub const ALT_POP_P: usize = 14;
478    pub const ALT_POP: usize = 15; // [0..4] = 15..19
479    pub const MIN_SOL_PWR: usize = 19; // [0..4] = 19..23
480    pub const MIN_SOL_MUL: usize = 23; // [0..4] = 23..27
481    pub const GEN1_ALLP: usize = 27;
482    pub const GEN1_MOVE_ASYNC: usize = 28;
483    pub const GEN1_MOVE_SPAN: usize = 29;
484    pub const GEN2_MODE: usize = 30;
485    pub const GEN2B_MODE: usize = 31;
486    pub const GEN2C_MODE: usize = 32;
487    pub const GEN2D_MODE: usize = 33;
488    pub const GEN3_MODE: usize = 34;
489    pub const GEN4_MIX_FAC: usize = 35;
490    pub const GEN5B_MODE: usize = 36;
491    pub const GEN7_POW_FAC: usize = 37;
492    pub const GEN8_MODE: usize = 38;
493    pub const GEN8_NUM: usize = 39;
494    pub const GEN8_SPAN: usize = 40; // [0..2] = 40..42
495    pub const COUNT: usize = 42;
496}
497
498fn build_selectors() -> Vec<BiteSel> {
499    let mut s = Vec::with_capacity(sel::COUNT);
500    s.push(BiteSel::new(4)); // METHOD
501    s.push(BiteSel::new(4)); // M1
502    s.push(BiteSel::new(3)); // M1A
503    s.push(BiteSel::new(2)); // M1B
504    s.push(BiteSel::new(2)); // M1C
505    s.push(BiteSel::new(2)); // M2
506    s.push(BiteSel::new(4)); // M2B
507    s.push(BiteSel::new(2)); // POP_CHANGE_INCR
508    s.push(BiteSel::new(2)); // POP_CHANGE_DECR
509    s.push(BiteSel::new(2)); // PAR_OPT2
510    for _ in 0..4 {
511        s.push(BiteSel::new(2)); // PAR_POP_P[gi]
512    }
513    s.push(BiteSel::new(2)); // ALT_POP_P
514    for _ in 0..4 {
515        s.push(BiteSel::new(2)); // ALT_POP[gi]
516    }
517    for _ in 0..4 {
518        s.push(BiteSel::new(4)); // MIN_SOL_PWR[gi]
519    }
520    for _ in 0..4 {
521        s.push(BiteSel::new(4)); // MIN_SOL_MUL[gi]
522    }
523    s.push(BiteSel::new(2)); // GEN1_ALLP
524    s.push(BiteSel::new(2)); // GEN1_MOVE_ASYNC
525    s.push(BiteSel::new(4)); // GEN1_MOVE_SPAN
526    s.push(BiteSel::new(2)); // GEN2_MODE
527    s.push(BiteSel::new(2)); // GEN2B_MODE
528    s.push(BiteSel::new(2)); // GEN2C_MODE
529    s.push(BiteSel::new(2)); // GEN2D_MODE
530    s.push(BiteSel::new(4)); // GEN3_MODE
531    s.push(BiteSel::new(4)); // GEN4_MIX_FAC
532    s.push(BiteSel::new(2)); // GEN5B_MODE
533    s.push(BiteSel::new(4)); // GEN7_POW_FAC
534    s.push(BiteSel::new(2)); // GEN8_MODE
535    s.push(BiteSel::new(4)); // GEN8_NUM
536    for _ in 0..2 {
537        s.push(BiteSel::new(4)); // GEN8_SPAN[i]
538    }
539    s
540}
541
542// ---------------------------------------------------------------------------
543// Population (faithful port of CBitePop, fixed capacity and dynamic active size)
544// ---------------------------------------------------------------------------
545
546#[derive(Clone)]
547struct BitePop {
548    param_count: usize,
549    pop_size: usize,
550    params: Vec<Vec<i64>>, // ordered by cost ascending
551    costs: Vec<f64>,
552    cent: Vec<i64>,
553    cur_pop_pos: usize,
554    // Dynamic "active window" size (the C++ CurPopSize); <= pop_size.
555    cur_pop_size: usize,
556    cur_pop_size1: usize,
557    cur_pop_size_i: f64,
558    need_cent: bool,
559    cent_lpc: f64,
560}
561
562fn calc_lp1_coeff(count: f64) -> f64 {
563    let theta = 2.8 / count;
564    let costheta2 = 2.0 - theta.cos();
565    1.0 - (costheta2 - (costheta2 * costheta2 - 1.0).sqrt())
566}
567
568impl BitePop {
569    fn new(param_count: usize, pop_size: usize) -> Self {
570        BitePop {
571            param_count,
572            pop_size,
573            params: vec![vec![0i64; param_count]; pop_size],
574            costs: vec![1e300; pop_size],
575            cent: vec![0i64; param_count],
576            cur_pop_pos: 0,
577            cur_pop_size: pop_size,
578            cur_pop_size1: pop_size - 1,
579            cur_pop_size_i: 1.0 / pop_size as f64,
580            need_cent: false,
581            cent_lpc: calc_lp1_coeff(pop_size as f64),
582        }
583    }
584
585    fn reset_cur_pop_pos(&mut self) {
586        self.cur_pop_pos = 0;
587        self.cur_pop_size = self.pop_size;
588        self.cur_pop_size1 = self.pop_size - 1;
589        self.cur_pop_size_i = 1.0 / self.pop_size as f64;
590        self.need_cent = false;
591        self.cent_lpc = calc_lp1_coeff(self.pop_size as f64);
592    }
593
594    fn incr_cur_pop_size(&mut self) {
595        self.cur_pop_size += 1;
596        self.cur_pop_size1 += 1;
597        self.cur_pop_size_i = 1.0 / self.cur_pop_size as f64;
598        self.need_cent = true;
599        self.cent_lpc = calc_lp1_coeff(self.cur_pop_size as f64);
600    }
601
602    fn decr_cur_pop_size(&mut self) {
603        self.cur_pop_size -= 1;
604        self.cur_pop_size1 -= 1;
605        self.cur_pop_size_i = 1.0 / self.cur_pop_size as f64;
606        self.need_cent = true;
607        self.cent_lpc = calc_lp1_coeff(self.cur_pop_size as f64);
608    }
609
610    /// Insert a solution keeping the population cost-sorted; returns the
611    /// insertion index, or `pop_size` if the solution was rejected.
612    fn update_pop(&mut self, mut cost: f64, up: &[i64], do_update_centroid: bool) -> usize {
613        let ri;
614        if self.cur_pop_pos < self.pop_size {
615            ri = self.cur_pop_pos;
616            if cost.is_nan() {
617                cost = 1e300;
618            }
619        } else {
620            ri = self.pop_size - 1;
621            if cost.is_nan() || cost >= self.costs[ri] {
622                return self.pop_size;
623            }
624        }
625        // binary search for insertion position
626        let mut p = 0usize;
627        let mut i = ri;
628        while p < i {
629            let mid = (p + i) >> 1;
630            if self.costs[mid] >= cost {
631                i = mid;
632            } else {
633                p = mid + 1;
634            }
635        }
636        if self.cur_pop_pos < self.pop_size {
637            self.cur_pop_pos += 1;
638        }
639        // shift [p..ri) right by one, insert at p
640        for k in (p + 1..=ri).rev() {
641            self.params.swap(k, k - 1);
642            self.costs[k] = self.costs[k - 1];
643        }
644        self.costs[p] = cost;
645        if self.params[p] != up {
646            if do_update_centroid {
647                for (c, &u) in self.cent.iter_mut().zip(up) {
648                    *c += ((u - *c) as f64 * self.cent_lpc) as i64;
649                }
650                self.params[p].copy_from_slice(up);
651            } else {
652                self.params[p].copy_from_slice(up);
653                self.need_cent = true;
654            }
655        } else {
656            self.need_cent = true;
657        }
658        p
659    }
660
661    fn update_centroid(&mut self) {
662        self.need_cent = false;
663        let cm = 1.0 / self.pop_size as f64;
664        for j in 0..self.param_count {
665            let mut sum = 0i128;
666            for row in &self.params {
667                sum += row[j] as i128;
668            }
669            self.cent[j] = (sum as f64 * cm) as i64;
670        }
671    }
672
673    #[inline]
674    fn ordered(&self, i: usize) -> &[i64] {
675        &self.params[i]
676    }
677
678    #[inline]
679    fn cur_pop_size(&self) -> usize {
680        self.cur_pop_size
681    }
682
683    fn get_centroid(&mut self) -> &[i64] {
684        if self.need_cent {
685            self.update_centroid();
686        }
687        &self.cent
688    }
689
690    /// Copy another (same-size) population wholesale (the C++ `CBitePop::copy`).
691    fn copy_from(&mut self, src: &BitePop) {
692        for (d, s) in self.params.iter_mut().zip(&src.params) {
693            d.copy_from_slice(s);
694        }
695        self.costs.copy_from_slice(&src.costs);
696        self.cent.copy_from_slice(&src.cent);
697        self.cur_pop_pos = src.cur_pop_pos;
698        self.cur_pop_size = src.cur_pop_size;
699        self.cur_pop_size1 = src.cur_pop_size1;
700        self.cur_pop_size_i = src.cur_pop_size_i;
701        self.need_cent = src.need_cent;
702        self.cent_lpc = src.cent_lpc;
703    }
704}
705
706// ---------------------------------------------------------------------------
707// Secondary ("parallel") optimizers (ports of CSpherOpt and CNMSeqOpt)
708// ---------------------------------------------------------------------------
709
710/// Wrap a normalized value into `[0, 1]` (the C++ double-branch `wrapParam`).
711fn wrap01(rnd: &mut BiteRnd, v: f64) -> f64 {
712    if v < 0.0 {
713        if v > -1.0 { rnd.get() * -v } else { rnd.get() }
714    } else if v > 1.0 {
715        if v < 2.0 {
716            1.0 - rnd.get() * (v - 1.0)
717        } else {
718            rnd.get()
719        }
720    } else {
721        v
722    }
723}
724
725/// Reflect a real value back into `[minv, minv+diffv]` (the C++ `wrapParamReal`).
726fn wrap_param_real(rnd: &mut BiteRnd, v: f64, minv: f64, diffv: f64) -> f64 {
727    if v < minv {
728        if v > minv - diffv {
729            minv + rnd.get() * (minv - v)
730        } else {
731            minv + rnd.get() * diffv
732        }
733    } else {
734        let maxv = minv + diffv;
735        if v > maxv {
736            if v < maxv + diffv {
737                maxv - rnd.get() * (v - maxv)
738            } else {
739                maxv - rnd.get() * diffv
740            }
741        } else {
742            v
743        }
744    }
745}
746
747/// One evaluated step from a secondary optimizer.
748struct ParStep {
749    stall: i64,
750    cost: f64,
751    values: Vec<f64>,
752}
753
754/// CSpherOpt — converging hyper-spheroid optimizer (normalized `[0,1]` space).
755struct SpherOpt {
756    dim: usize,
757    pop_size: usize,
758    params: Vec<Vec<f64>>, // sorted ascending by cost
759    costs: Vec<f64>,
760    cur_pop_pos: usize,
761    cent: Vec<f64>,
762    min_values: Vec<f64>,
763    diff_values: Vec<f64>,
764    sels: [BiteSel; 3], // CentPow(4), RadPow(4), EvalFac(3)
765    apply: Vec<usize>,
766    radius: f64,
767    eval_fac: f64,
768    cure: i32,
769    curem: i32,
770    do_cent_eval: bool,
771    jit_mult: f64,
772    jit_offs: f64,
773    avg_cost: f64,
774    hi_bound: f64,
775    stall_count: i64,
776    best_cost: f64,
777    best_values: Vec<f64>,
778}
779
780impl SpherOpt {
781    fn new(dim: usize, min_values: Vec<f64>, diff_values: Vec<f64>, pop_size: usize) -> Self {
782        let dim_i = 1.0 / dim as f64;
783        SpherOpt {
784            dim,
785            pop_size,
786            params: vec![vec![0.0; dim]; pop_size],
787            costs: vec![1e300; pop_size],
788            cur_pop_pos: 0,
789            cent: vec![0.5; dim],
790            min_values,
791            diff_values,
792            sels: [BiteSel::new(4), BiteSel::new(4), BiteSel::new(3)],
793            apply: Vec::new(),
794            radius: 0.5,
795            eval_fac: 2.0,
796            cure: 0,
797            curem: 0,
798            do_cent_eval: false,
799            jit_mult: 5.0 * dim_i,
800            jit_offs: 1.0 - 5.0 * dim_i * 0.5,
801            avg_cost: 0.0,
802            hi_bound: 1e300,
803            stall_count: 0,
804            best_cost: 1e300,
805            best_values: vec![0.0; dim],
806        }
807    }
808
809    fn real_value(&self, norm: &[f64], i: usize) -> f64 {
810        self.min_values[i] + self.diff_values[i] * norm[i]
811    }
812
813    fn init(&mut self, rnd: &mut BiteRnd, init_params: Option<&[f64]>, radius: f64) {
814        self.best_cost = 1e300;
815        self.stall_count = 0;
816        self.hi_bound = 1e300;
817        self.avg_cost = 0.0;
818        for sel in self.sels.iter_mut() {
819            sel.reset(rnd, self.dim);
820        }
821        self.cur_pop_pos = 0;
822        self.radius = 0.5 * radius;
823        self.eval_fac = 2.0;
824        self.cure = 0;
825        self.curem = (self.pop_size as f64 * self.eval_fac).ceil() as i32;
826        match init_params {
827            None => {
828                self.cent = vec![0.5; self.dim];
829                self.do_cent_eval = false;
830            }
831            Some(ip) => {
832                for i in 0..self.dim {
833                    self.cent[i] = wrap01(rnd, (ip[i] - self.min_values[i]) / self.diff_values[i]);
834                }
835                self.do_cent_eval = true;
836            }
837        }
838    }
839
840    fn update_pop(&mut self, cost: f64, params: &[f64]) {
841        let ri;
842        if self.cur_pop_pos < self.pop_size {
843            ri = self.cur_pop_pos;
844        } else {
845            ri = self.pop_size - 1;
846            if cost >= self.costs[ri] {
847                return;
848            }
849        }
850        let mut p = 0usize;
851        let mut i = ri;
852        while p < i {
853            let mid = (p + i) >> 1;
854            if self.costs[mid] >= cost {
855                i = mid;
856            } else {
857                p = mid + 1;
858            }
859        }
860        if self.cur_pop_pos < self.pop_size {
861            self.cur_pop_pos += 1;
862        }
863        for k in (p + 1..=ri).rev() {
864            self.params.swap(k, k - 1);
865            self.costs[k] = self.costs[k - 1];
866        }
867        self.params[p].copy_from_slice(params);
868        self.costs[p] = cost;
869    }
870
871    fn optimize(&mut self, rnd: &mut BiteRnd, obj: &dyn Objective) -> ParStep {
872        let mut params = vec![0.0; self.dim];
873        let mut new_values = vec![0.0; self.dim];
874        if self.do_cent_eval {
875            self.do_cent_eval = false;
876            for i in 0..self.dim {
877                params[i] = self.cent[i];
878                new_values[i] = self.real_value(&self.cent, i);
879            }
880        } else {
881            let mut s2 = 1e-300;
882            for pi in params.iter_mut() {
883                *pi = rnd.get() - 0.5;
884                s2 += *pi * *pi;
885            }
886            let d = self.radius / s2.sqrt();
887            if self.dim > 4 {
888                for i in 0..self.dim {
889                    params[i] = wrap01(rnd, self.cent[i] + params[i] * d);
890                    new_values[i] = self.real_value(&params, i);
891                }
892            } else {
893                for i in 0..self.dim {
894                    let m = self.jit_offs + rnd.get() * self.jit_mult;
895                    params[i] = wrap01(rnd, self.cent[i] + params[i] * d * m);
896                    new_values[i] = self.real_value(&params, i);
897                }
898            }
899        }
900        let cost = objective_cost(obj, &new_values);
901        self.update_pop(cost, &params);
902        if cost <= self.best_cost {
903            self.best_cost = cost;
904            self.best_values.copy_from_slice(&new_values);
905        }
906        self.avg_cost += cost;
907        self.cure += 1;
908        if self.cure >= self.curem {
909            self.avg_cost /= self.cure as f64;
910            if self.avg_cost < self.hi_bound {
911                self.hi_bound = self.avg_cost;
912                self.stall_count = 0;
913                for &s in &self.apply {
914                    self.sels[s].incr(1.0);
915                }
916            } else {
917                self.stall_count += self.cure as i64;
918                for &s in &self.apply {
919                    self.sels[s].decr();
920                }
921            }
922            self.apply.clear();
923            self.cur_pop_pos = 0;
924            self.avg_cost = 0.0;
925            self.cure = 0;
926            self.update(rnd);
927            self.curem = (self.pop_size as f64 * self.eval_fac).ceil() as i32;
928        }
929        ParStep {
930            stall: self.stall_count,
931            cost,
932            values: new_values,
933        }
934    }
935
936    fn sel(&mut self, idx: usize, rnd: &mut BiteRnd) -> i32 {
937        self.apply.push(idx);
938        self.sels[idx].select(rnd)
939    }
940
941    fn update(&mut self, rnd: &mut BiteRnd) {
942        const WCENT: [f64; 4] = [4.5, 6.0, 7.5, 10.0];
943        const WRAD: [f64; 4] = [14.0, 16.0, 18.0, 20.0];
944        const EVAL_FACS: [f64; 3] = [2.1, 2.0, 1.9];
945        let cent_fac = WCENT[self.sel(0, rnd) as usize];
946        let rad_fac = WRAD[self.sel(1, rnd) as usize];
947        self.eval_fac = EVAL_FACS[self.sel(2, rnd) as usize];
948
949        let lm = 1.0 / self.curem as f64;
950        let mut wc = vec![0.0; self.pop_size];
951        let mut wr = vec![0.0; self.pop_size];
952        let mut s1 = 0.0;
953        let mut s2 = 0.0;
954        for i in 0..self.pop_size {
955            let l = 1.0 - i as f64 * lm;
956            let v1 = l.powf(cent_fac);
957            wc[i] = v1;
958            s1 += v1;
959            let v2 = l.powf(rad_fac);
960            wr[i] = v2;
961            s2 += v2;
962        }
963        s1 = 1.0 / s1;
964        s2 = 1.0 / s2;
965        for j in 0..self.dim {
966            let mut acc = 0.0;
967            for i in 0..self.pop_size {
968                acc += self.params[i][j] * wc[i] * s1;
969            }
970            self.cent[j] = acc;
971        }
972        let mut radius = 0.0;
973        for i in 0..self.pop_size {
974            let mut s = 0.0;
975            for j in 0..self.dim {
976                let d = self.params[i][j] - self.cent[j];
977                s += d * d;
978            }
979            radius += s * wr[i];
980        }
981        self.radius = (radius * s2).sqrt();
982    }
983}
984
985/// CNMSeqOpt — sequential Nelder-Mead simplex (real-value space).
986struct NMSeqOpt {
987    n: usize,
988    m: usize,
989    m1: usize,
990    m1i: f64,
991    param_count_i: f64,
992    x: Vec<Vec<f64>>, // simplex points (real)
993    y: Vec<f64>,      // costs
994    x0: Vec<f64>,     // centroid
995    x1: Vec<f64>,
996    x2: Vec<f64>,
997    y1: f64,
998    xlo: usize,
999    xhi: usize,
1000    xhi2: usize,
1001    rx: usize, // index of lowest-cost vector during reduction
1002    rj: usize,
1003    do_init_evals: bool,
1004    cur_pop_pos: usize,
1005    state: NmState,
1006    stall_count: i64,
1007    min_values: Vec<f64>,
1008    diff_values: Vec<f64>,
1009    best_cost: f64,
1010    best_values: Vec<f64>,
1011}
1012
1013#[derive(Clone, Copy, PartialEq)]
1014enum NmState {
1015    Reflection,
1016    Expansion,
1017    Contraction,
1018    Reduction,
1019}
1020
1021impl NMSeqOpt {
1022    fn new(dim: usize, min_values: Vec<f64>, diff_values: Vec<f64>) -> Self {
1023        let m = (dim + 1) * 4;
1024        NMSeqOpt {
1025            n: dim,
1026            m,
1027            m1: m - 1,
1028            m1i: 1.0 / (m - 1) as f64,
1029            param_count_i: 1.0 / dim as f64,
1030            x: vec![vec![0.0; dim]; m],
1031            y: vec![1e300; m],
1032            x0: vec![0.0; dim],
1033            x1: vec![0.0; dim],
1034            x2: vec![0.0; dim],
1035            y1: 0.0,
1036            xlo: 0,
1037            xhi: 0,
1038            xhi2: 0,
1039            rx: 0,
1040            rj: 0,
1041            do_init_evals: true,
1042            cur_pop_pos: 0,
1043            state: NmState::Reflection,
1044            stall_count: 0,
1045            min_values,
1046            diff_values,
1047            best_cost: 1e300,
1048            best_values: vec![0.0; dim],
1049        }
1050    }
1051
1052    fn init(&mut self, rnd: &mut BiteRnd, init_params: Option<&[f64]>, radius: f64) {
1053        self.best_cost = 1e300;
1054        self.stall_count = 0;
1055        match init_params {
1056            Some(ip) => self.x[0].copy_from_slice(ip),
1057            None => {
1058                for i in 0..self.n {
1059                    self.x[0][i] = self.min_values[i] + self.diff_values[i] * 0.5;
1060                }
1061            }
1062        }
1063        self.xlo = 0;
1064        let base = self.x[0].clone();
1065        if radius <= 0.0 {
1066            for j in 1..self.m {
1067                for i in 0..self.n {
1068                    self.x[j][i] = self.min_values[i] + self.diff_values[i] * rnd.get();
1069                }
1070            }
1071        } else {
1072            let sd = 0.25 * radius;
1073            for j in 1..self.m {
1074                for i in 0..self.n {
1075                    self.x[j][i] = base[i] + self.diff_values[i] * rnd.get_gaussian() * sd;
1076                }
1077            }
1078        }
1079        self.state = NmState::Reflection;
1080        self.do_init_evals = true;
1081        self.cur_pop_pos = 0;
1082    }
1083
1084    fn eval(&mut self, rnd: &mut BiteRnd, params: &[f64], obj: &dyn Objective) -> (f64, Vec<f64>) {
1085        let mut nv = vec![0.0; self.n];
1086        for i in 0..self.n {
1087            nv[i] = wrap_param_real(rnd, params[i], self.min_values[i], self.diff_values[i]);
1088        }
1089        let cost = objective_cost(obj, &nv);
1090        if cost <= self.best_cost {
1091            self.best_cost = cost;
1092            self.best_values.copy_from_slice(&nv);
1093        }
1094        (cost, nv)
1095    }
1096
1097    fn find_hi(&mut self) {
1098        self.xhi2 = if self.y[0] > self.y[1] { 0 } else { 1 };
1099        self.xhi = 1 - self.xhi2;
1100        for j in 2..self.m {
1101            if self.y[j] > self.y[self.xhi] {
1102                self.xhi2 = self.xhi;
1103                self.xhi = j;
1104            } else if self.y[j] > self.y[self.xhi2] {
1105                self.xhi2 = j;
1106            }
1107        }
1108    }
1109
1110    fn calc_cent(&mut self) {
1111        self.find_hi();
1112        let mut xc = vec![0.0; self.n];
1113        for (j, xj) in self.x.iter().enumerate() {
1114            if j == self.xhi {
1115                continue;
1116            }
1117            for i in 0..self.n {
1118                xc[i] += xj[i];
1119            }
1120        }
1121        for c in xc.iter_mut() {
1122            *c *= self.m1i;
1123        }
1124        self.x0 = xc;
1125    }
1126
1127    fn copy(&mut self, ip: &[f64], cost: f64) {
1128        let replaced_index = self.xhi;
1129        self.y[replaced_index] = cost;
1130        self.x[replaced_index].copy_from_slice(ip);
1131        let replacement = self.x[replaced_index].clone();
1132        self.find_hi();
1133        if replaced_index != self.xhi {
1134            for i in 0..self.n {
1135                self.x0[i] += (replacement[i] - self.x[self.xhi][i]) * self.m1i;
1136            }
1137        }
1138        self.stall_count = 0;
1139    }
1140
1141    fn optimize(&mut self, rnd: &mut BiteRnd, obj: &dyn Objective) -> ParStep {
1142        if self.do_init_evals {
1143            let xp = self.x[self.cur_pop_pos].clone();
1144            let (out_cost, out_values) = self.eval(rnd, &xp, obj);
1145            self.y[self.cur_pop_pos] = out_cost;
1146            if self.y[self.cur_pop_pos] < self.y[self.xlo] {
1147                self.xlo = self.cur_pop_pos;
1148            }
1149            self.cur_pop_pos += 1;
1150            if self.cur_pop_pos == self.m {
1151                self.do_init_evals = false;
1152                self.calc_cent();
1153            }
1154            return ParStep {
1155                stall: 0,
1156                cost: out_cost,
1157                values: out_values,
1158            };
1159        }
1160
1161        let out_cost;
1162        let out_values;
1163        self.stall_count += 1;
1164        let sn = 0.5 * self.param_count_i.sqrt();
1165        let alpha = 1.0;
1166        let gamma = 1.5 + sn;
1167        let rho = -0.75 + sn;
1168        let sigma = 1.0 - sn;
1169        let xh = self.x[self.xhi].clone();
1170
1171        match self.state {
1172            NmState::Reflection => {
1173                for i in 0..self.n {
1174                    self.x1[i] = self.x0[i] + alpha * (self.x0[i] - xh[i]);
1175                }
1176                let x1 = self.x1.clone();
1177                let (c, v) = self.eval(rnd, &x1, obj);
1178                self.y1 = c;
1179                out_cost = c;
1180                out_values = v;
1181                if self.y1 > self.y[self.xlo] && self.y1 < self.y[self.xhi2] {
1182                    let x1c = self.x1.clone();
1183                    self.copy(&x1c, self.y1);
1184                } else if self.y1 < self.y[self.xlo] {
1185                    self.state = NmState::Expansion;
1186                    self.stall_count -= 1;
1187                } else {
1188                    self.state = NmState::Contraction;
1189                }
1190            }
1191            NmState::Expansion => {
1192                for i in 0..self.n {
1193                    self.x2[i] = self.x0[i] + gamma * (self.x0[i] - xh[i]);
1194                }
1195                let x2 = self.x2.clone();
1196                let (y2, v) = self.eval(rnd, &x2, obj);
1197                out_cost = y2;
1198                out_values = v;
1199                self.xlo = self.xhi;
1200                if y2 < self.y1 {
1201                    let x2c = self.x2.clone();
1202                    self.copy(&x2c, y2);
1203                } else {
1204                    let x1c = self.x1.clone();
1205                    self.copy(&x1c, self.y1);
1206                }
1207                self.state = NmState::Reflection;
1208            }
1209            NmState::Contraction => {
1210                for i in 0..self.n {
1211                    self.x2[i] = self.x0[i] + rho * (self.x0[i] - xh[i]);
1212                }
1213                let x2 = self.x2.clone();
1214                let (y2, v) = self.eval(rnd, &x2, obj);
1215                out_cost = y2;
1216                out_values = v;
1217                if y2 < self.y[self.xhi] {
1218                    if y2 < self.y[self.xlo] {
1219                        self.xlo = self.xhi;
1220                    }
1221                    let x2c = self.x2.clone();
1222                    self.copy(&x2c, y2);
1223                    self.state = NmState::Reflection;
1224                } else {
1225                    self.rx = self.xlo;
1226                    self.rj = 0;
1227                    self.state = NmState::Reduction;
1228                }
1229            }
1230            NmState::Reduction => {
1231                if self.rj == self.rx {
1232                    self.rj += 1;
1233                }
1234                let rxv = self.x[self.rx].clone();
1235                for i in 0..self.n {
1236                    self.x[self.rj][i] = rxv[i] + sigma * (self.x[self.rj][i] - rxv[i]);
1237                }
1238                let xx = self.x[self.rj].clone();
1239                let (c, v) = self.eval(rnd, &xx, obj);
1240                self.y[self.rj] = c;
1241                out_cost = c;
1242                out_values = v;
1243                if self.y[self.rj] < self.y[self.xlo] {
1244                    self.xlo = self.rj;
1245                    self.stall_count = 0;
1246                }
1247                self.rj += 1;
1248                if self.rj == self.m || (self.rj == self.m1 && self.rj == self.rx) {
1249                    self.calc_cent();
1250                    self.state = NmState::Reflection;
1251                }
1252            }
1253        }
1254
1255        ParStep {
1256            stall: self.stall_count,
1257            cost: out_cost,
1258            values: out_values,
1259        }
1260    }
1261}
1262
1263// ---------------------------------------------------------------------------
1264// BiteOpt optimizer core (port of CBiteOpt)
1265// ---------------------------------------------------------------------------
1266
1267pub struct BiteOpt {
1268    param_count: usize,
1269    param_count_i: f64,
1270    pop_size: usize,
1271    min_values: Vec<f64>,
1272    diff_values: Vec<f64>,
1273    diff_values_i: Vec<f64>,
1274
1275    pop: BitePop,
1276    old_pop: BitePop,
1277    par_pop_count: usize,
1278    par_pops: Vec<BitePop>,
1279    par_opt_pop: BitePop,
1280    par_opt2_pop: BitePop,
1281    spher: SpherOpt,
1282    nmseq: NMSeqOpt,
1283    use_par_opt: i32,
1284
1285    sels: Vec<BiteSel>,
1286    apply_sels: Vec<SelUse>,
1287    deferred_sels: VecDeque<SelUse>,
1288    rnd: BiteRnd,
1289
1290    tmp: Vec<i64>,
1291    real_tmp: Vec<f64>,
1292    best_cost: f64,
1293    best_values: Vec<f64>,
1294    stall_count: i64,
1295
1296    // ask/tell (delayed-feedback batching)
1297    init_queue: VecDeque<Vec<i64>>,
1298    asked: Vec<Candidate>,
1299
1300    max_evaluations: u64,
1301    stopfitness: f64,
1302    stall_criterion: i32,
1303    evaluations: u64,
1304    iterations: i32,
1305    stop: i32,
1306}
1307
1308impl BiteOpt {
1309    pub fn new(lower: &[f64], upper: &[f64], init: Option<&[f64]>, p: &BiteParams) -> Self {
1310        validate_bite_inputs(lower, upper, init, p, 1).expect("invalid BiteOpt configuration");
1311        let param_count = lower.len();
1312        let pop_size = if p.popsize > 0 {
1313            p.popsize as usize
1314        } else {
1315            9 + param_count * 3
1316        };
1317        let min_values = lower.to_vec();
1318        let diff_values: Vec<f64> = upper
1319            .iter()
1320            .zip(lower)
1321            .map(|(u, l)| (u - l) / INT_MANT_MULT as f64)
1322            .collect();
1323        let diff_values_i: Vec<f64> = diff_values.iter().map(|d| 1.0 / d).collect();
1324        let real_diff: Vec<f64> = upper.iter().zip(lower).map(|(u, l)| u - l).collect();
1325        let par_pop_count = 4;
1326        let mut b = BiteOpt {
1327            param_count,
1328            param_count_i: 1.0 / param_count as f64,
1329            pop_size,
1330            min_values: min_values.clone(),
1331            diff_values,
1332            diff_values_i,
1333            pop: BitePop::new(param_count, pop_size),
1334            old_pop: BitePop::new(param_count, pop_size),
1335            par_pop_count,
1336            par_pops: (0..par_pop_count)
1337                .map(|_| BitePop::new(param_count, pop_size))
1338                .collect(),
1339            par_opt_pop: BitePop::new(param_count, pop_size),
1340            par_opt2_pop: BitePop::new(param_count, pop_size),
1341            spher: SpherOpt::new(
1342                param_count,
1343                min_values.clone(),
1344                real_diff.clone(),
1345                14 + param_count,
1346            ),
1347            nmseq: NMSeqOpt::new(param_count, min_values, real_diff),
1348            use_par_opt: 0,
1349            sels: build_selectors(),
1350            apply_sels: Vec::with_capacity(32),
1351            deferred_sels: VecDeque::with_capacity(2),
1352            rnd: BiteRnd::new(p.seed.wrapping_add(p.runid as u64)),
1353            tmp: vec![0; param_count],
1354            real_tmp: vec![0.0; param_count],
1355            best_cost: 1e300,
1356            best_values: vec![0.0; param_count],
1357            stall_count: 0,
1358            init_queue: VecDeque::new(),
1359            asked: Vec::new(),
1360            max_evaluations: if p.max_evaluations > 0 {
1361                p.max_evaluations
1362            } else {
1363                50_000
1364            },
1365            stopfitness: p.stop_fitness,
1366            stall_criterion: p.stall_criterion.max(0),
1367            evaluations: 0,
1368            iterations: 0,
1369            stop: 0,
1370        };
1371        b.init(init);
1372        b
1373    }
1374
1375    fn init(&mut self, init: Option<&[f64]>) {
1376        let seed_reset: Vec<usize> = (0..self.sels.len()).collect();
1377        for i in seed_reset {
1378            self.sels[i].reset(&mut self.rnd, self.param_count);
1379        }
1380        self.pop.reset_cur_pop_pos();
1381        self.old_pop.reset_cur_pop_pos();
1382        self.par_opt_pop.reset_cur_pop_pos();
1383        self.par_opt2_pop.reset_cur_pop_pos();
1384        let init_slice = init.map(|x| x.to_vec());
1385        self.spher.init(&mut self.rnd, init_slice.as_deref(), 1.0);
1386        self.nmseq.init(&mut self.rnd, init_slice.as_deref(), 1.0);
1387        self.use_par_opt = 0;
1388        self.init_queue.clear();
1389        self.asked.clear();
1390        self.deferred_sels.clear();
1391        let sd = 0.25;
1392        let mut members: Vec<Vec<i64>> = vec![vec![0i64; self.param_count]; self.pop_size];
1393        match init {
1394            None => {
1395                for member in members.iter_mut() {
1396                    for slot in member.iter_mut() {
1397                        let g = gaussian_int(&mut self.rnd, sd, INT_MANT_MULT >> 1);
1398                        *slot = wrap_param(&mut self.rnd, g);
1399                    }
1400                }
1401            }
1402            Some(x0) => {
1403                for i in 0..self.param_count {
1404                    let v = ((x0[i] - self.min_values[i]) / self.diff_values[i]) as i64;
1405                    members[0][i] = wrap_param(&mut self.rnd, v);
1406                }
1407                for j in 1..self.pop_size {
1408                    #[allow(clippy::needless_range_loop)]
1409                    for i in 0..self.param_count {
1410                        let mean = members[0][i];
1411                        let g = gaussian_int(&mut self.rnd, sd, mean);
1412                        members[j][i] = wrap_param(&mut self.rnd, g);
1413                    }
1414                }
1415            }
1416        }
1417        self.init_queue = members.into_iter().collect();
1418        self.best_cost = 1e300;
1419        self.stall_count = 0;
1420    }
1421
1422    #[inline]
1423    fn real_value(&self, params: &[i64], i: usize) -> f64 {
1424        self.min_values[i] + self.diff_values[i] * params[i] as f64
1425    }
1426
1427    #[inline]
1428    fn take_tmp(&mut self) -> Vec<i64> {
1429        let mut params = std::mem::take(&mut self.tmp);
1430        params.resize(self.param_count, 0);
1431        params.fill(0);
1432        params
1433    }
1434
1435    fn recycle_candidate(&mut self, mut candidate: Candidate) {
1436        if candidate.enc.capacity() >= self.tmp.capacity() {
1437            candidate.enc.resize(self.param_count, 0);
1438            self.tmp = candidate.enc;
1439        }
1440        if candidate.real.capacity() >= self.real_tmp.capacity() {
1441            candidate.real.resize(self.param_count, 0.0);
1442            self.real_tmp = candidate.real;
1443        }
1444        if candidate.sels.capacity() >= self.apply_sels.capacity() {
1445            candidate.sels.clear();
1446            self.apply_sels = candidate.sels;
1447        }
1448    }
1449
1450    fn select(&mut self, sel_idx: usize) -> i32 {
1451        let value = self.sels[sel_idx].select(&mut self.rnd);
1452        self.apply_sels.push(self.sels[sel_idx].captured(sel_idx));
1453        value
1454    }
1455
1456    fn get_min_sol_index(&mut self, gi: usize, ps: usize) -> usize {
1457        const PP: [f64; 4] = [0.05, 0.125, 0.25, 0.5];
1458        const RM: [f64; 4] = [0.0, 0.125, 0.25, 0.5];
1459        let pwr = self.select(sel::MIN_SOL_PWR + gi) as usize;
1460        let r = ps as f64 * self.rnd.get_pow(ps as f64 * PP[pwr]);
1461        let mul = self.select(sel::MIN_SOL_MUL + gi) as usize;
1462        (r * RM[mul]) as usize
1463    }
1464
1465    fn update_best_cost(&mut self, cost: f64, values: &[f64], p: i64) {
1466        if cost.is_nan() {
1467            return;
1468        }
1469        if p == 0 || (p < 0 && cost <= self.best_cost) {
1470            self.best_cost = cost;
1471            self.best_values.copy_from_slice(values);
1472        }
1473    }
1474
1475    // ---- population selection ----
1476
1477    fn pop_ref(&self, s: PopSel) -> &BitePop {
1478        match s {
1479            PopSel::Main => &self.pop,
1480            PopSel::Par(i) => &self.par_pops[i],
1481            PopSel::ParOpt => &self.par_opt_pop,
1482            PopSel::ParOpt2 => &self.par_opt2_pop,
1483        }
1484    }
1485
1486    fn ordered_of(&self, s: PopSel, i: usize) -> Vec<i64> {
1487        self.pop_ref(s).ordered(i).to_vec()
1488    }
1489
1490    fn cur_pop_size_of(&self, s: PopSel) -> usize {
1491        self.pop_ref(s).cur_pop_size()
1492    }
1493
1494    fn select_par_pop(&mut self, gi: usize) -> PopSel {
1495        if self.select(sel::PAR_POP_P + gi) != 0 {
1496            PopSel::Par(self.rnd.get_int(self.par_pop_count as i32) as usize)
1497        } else {
1498            PopSel::Main
1499        }
1500    }
1501
1502    fn select_alt_pop(&mut self, gi: usize) -> PopSel {
1503        if self.select(sel::ALT_POP_P) != 0 {
1504            if self.select(sel::ALT_POP + gi) != 0 {
1505                if self.par_opt_pop.cur_pop_pos >= self.pop.cur_pop_size() {
1506                    return PopSel::ParOpt;
1507                }
1508            } else if self.par_opt2_pop.cur_pop_pos >= self.pop.cur_pop_size() {
1509                return PopSel::ParOpt2;
1510            }
1511        }
1512        PopSel::Main
1513    }
1514
1515    fn update_par_pop(&mut self, cost: f64, params: &[i64]) {
1516        let p = self.get_min_dist_par_pop(params);
1517        self.par_pops[p].update_pop(cost, params, true);
1518    }
1519
1520    fn get_min_dist_par_pop(&mut self, params: &[i64]) -> usize {
1521        let mut best = 0usize;
1522        let mut best_d = f64::MAX;
1523        for pi in 0..self.par_pop_count {
1524            let c = self.par_pops[pi].get_centroid();
1525            let mut s = 0.0;
1526            for i in 0..self.param_count {
1527                let d = (c[i] - params[i]) as f64;
1528                s += d * d;
1529            }
1530            if s <= best_d {
1531                best_d = s;
1532                best = pi;
1533            }
1534        }
1535        best
1536    }
1537
1538    // ---- solution generators ----
1539
1540    fn generate_sol1(&mut self) {
1541        let par = self.select_par_pop(0);
1542        let par_ps = self.cur_pop_size_of(par);
1543        let si = self.get_min_sol_index(0, par_ps);
1544        let mut params = self.take_tmp();
1545        params.copy_from_slice(self.pop_ref(par).ordered(si));
1546
1547        let mut a;
1548        let mut b;
1549        let mut do_allp = false;
1550        if self.rnd.get() < 1.8 * self.param_count_i && self.select(sel::GEN1_ALLP) != 0 {
1551            do_allp = true;
1552        }
1553        if do_allp {
1554            a = 0;
1555            b = self.param_count;
1556        } else {
1557            a = self.rnd.get_int(self.param_count as i32) as usize;
1558            b = a + 1;
1559        }
1560
1561        let r1 = self.rnd.get();
1562        let r12 = r1 * r1;
1563        let ims = (r12 * r12 * 48.0) as u32;
1564        let imask = INT_MANT_MASK >> ims;
1565        let im2s = self.rnd.get_sqr_int(96);
1566        let imask2 = if im2s > 63 { 0 } else { INT_MANT_MASK >> im2s };
1567        let si1 = (r1 * r12 * par_ps as f64) as usize;
1568        {
1569            let rp1 = self.pop_ref(par).ordered(si1);
1570            for i in a..b {
1571                params[i] = ((params[i] ^ imask) + (rp1[i] ^ imask2)) >> 1;
1572            }
1573        }
1574        if self.rnd.get() < 1.0 - self.param_count_i {
1575            let ri = self.rnd.get_sqr_int(self.pop.cur_pop_size as i32) as usize;
1576            if self.rnd.get() < self.param_count_i.sqrt() && self.select(sel::GEN1_MOVE_ASYNC) != 0
1577            {
1578                a = 0;
1579                b = self.param_count;
1580            }
1581            const SPAN_MULTS: [f64; 4] = [0.5, 1.5, 2.0, 2.5];
1582            let m = SPAN_MULTS[self.select(sel::GEN1_MOVE_SPAN) as usize];
1583            let m1 = self.rnd.get_tpdf() * m;
1584            let m2 = self.rnd.get_tpdf() * m;
1585            let rp2 = self.pop.ordered(ri);
1586            for i in a..b {
1587                params[i] += ((rp2[i] - params[i]) as f64 * m1) as i64;
1588                params[i] += ((rp2[i] - params[i]) as f64 * m2) as i64;
1589            }
1590        }
1591        self.tmp = params;
1592    }
1593
1594    fn generate_sol2(&mut self) {
1595        let ps = self.pop.cur_pop_size;
1596        let ps1 = self.pop.cur_pop_size1;
1597        let si1 = self.get_min_sol_index(1, ps);
1598        let si2 = 1 + self.rnd.get_int(ps1 as i32) as usize;
1599        let si4 = self.rnd.get_sqr_int(ps as i32) as usize;
1600        let mode = self.select(sel::GEN2_MODE);
1601        let si1b = (mode != 0).then(|| self.rnd.get_sqr_int(ps as i32) as usize);
1602        let mut params = self.take_tmp();
1603        let rp1 = self.pop.ordered(si1);
1604        let rp2 = self.pop.ordered(si2);
1605        let rp3 = self.pop.ordered(ps1 - si1);
1606        let rp4 = self.pop.ordered(si4);
1607        let rp5 = self.pop.ordered(ps1 - si4);
1608        if mode == 0 {
1609            for i in 0..self.param_count {
1610                params[i] = rp1[i] + (((rp2[i] - rp3[i]) + (rp4[i] - rp5[i])) >> 1);
1611            }
1612        } else {
1613            let rp1b = self.pop.ordered(si1b.unwrap());
1614            for i in 0..self.param_count {
1615                params[i] = ((rp1[i] + rp1b[i]) + (rp2[i] - rp3[i]) + (rp4[i] - rp5[i])) >> 1;
1616            }
1617        }
1618        self.tmp = params;
1619    }
1620
1621    fn generate_sol2b(&mut self) {
1622        let ps = self.pop.cur_pop_size;
1623        let ps1 = self.pop.cur_pop_size1;
1624        let si1 = self.get_min_sol_index(2, ps);
1625        let si2 = self.rnd.get_int(ps as i32) as usize;
1626        let alt = self.select_alt_pop(0);
1627        let si4 = self.rnd.get_int(ps as i32) as usize;
1628        let mode = self.select(sel::GEN2B_MODE);
1629        let si1b = (mode != 0).then(|| self.rnd.get_sqr_int(ps as i32) as usize);
1630        let mut params = self.take_tmp();
1631        let rp1 = self.pop.ordered(si1);
1632        let rp2 = self.pop.ordered(si2);
1633        let rp3 = self.pop.ordered(ps1 - si2);
1634        let rp4 = self.pop_ref(alt).ordered(si4);
1635        let rp5 = self.pop_ref(alt).ordered(ps1 - si4);
1636        if mode == 0 {
1637            for i in 0..self.param_count {
1638                params[i] = rp1[i] + ((rp2[i] - rp3[i]) + (rp4[i] - rp5[i]));
1639            }
1640        } else {
1641            let rp1b = self.pop.ordered(si1b.unwrap());
1642            for i in 0..self.param_count {
1643                params[i] = ((rp1[i] + rp1b[i]) >> 1) + (rp2[i] - rp3[i]) + (rp4[i] - rp5[i]);
1644            }
1645        }
1646        self.tmp = params;
1647    }
1648
1649    fn generate_sol2c(&mut self) {
1650        let ps = self.pop.cur_pop_size;
1651        let mut params = self.take_tmp();
1652        let si1 = self.rnd.get_pow_int(4.0, (ps / 2) as i32) as usize;
1653        let pc = 7usize; // 1 + 2*PairCount, PairCount=3
1654        let mut pop_idx = [0usize; 7];
1655        pop_idx[0] = si1;
1656        let mut pp = 1;
1657        if self.pop.cur_pop_size1 <= pc {
1658            while pp < pc {
1659                pop_idx[pp] = self.rnd.get_int(ps as i32) as usize;
1660                pp += 1;
1661            }
1662        } else {
1663            while pp < pc {
1664                let sii = self.rnd.get_int(ps as i32) as usize;
1665                if !pop_idx[..pp].contains(&sii) {
1666                    pop_idx[pp] = sii;
1667                    pp += 1;
1668                }
1669            }
1670        }
1671        for i in 0..self.param_count {
1672            params[i] = (self.pop.ordered(pop_idx[1])[i] - self.pop.ordered(pop_idx[2])[i])
1673                + (self.pop.ordered(pop_idx[3])[i] - self.pop.ordered(pop_idx[4])[i])
1674                + (self.pop.ordered(pop_idx[5])[i] - self.pop.ordered(pop_idx[6])[i]);
1675        }
1676        if self.rnd.get_bit() != 0 && self.rnd.get_bit() != 0 {
1677            let k = self.rnd.get_int(self.param_count as i32) as usize;
1678            let v1 = (self.rnd.get_raw()
1679                & self.rnd.get_raw()
1680                & self.rnd.get_raw()
1681                & self.rnd.get_raw()
1682                & self.rnd.get_raw()) as i64
1683                & INT_MANT_MASK;
1684            let v2 = (self.rnd.get_raw()
1685                & self.rnd.get_raw()
1686                & self.rnd.get_raw()
1687                & self.rnd.get_raw()
1688                & self.rnd.get_raw()) as i64
1689                & INT_MANT_MASK;
1690            params[k] += v1 - v2;
1691        }
1692        let mode = self.select(sel::GEN2C_MODE);
1693        if mode == 0 {
1694            let mut si2 = si1 as i64 + self.rnd.get_bit() as i64 * 2 - 1;
1695            if si2 < 0 {
1696                si2 = 1;
1697            }
1698            for i in 0..self.param_count {
1699                params[i] =
1700                    (self.pop.ordered(si1)[i] + self.pop.ordered(si2 as usize)[i] + params[i]) >> 1;
1701            }
1702        } else {
1703            for i in 0..self.param_count {
1704                params[i] = self.pop.ordered(si1)[i] + (params[i] >> 1);
1705            }
1706        }
1707        self.tmp = params;
1708    }
1709
1710    fn generate_sol2d(&mut self) {
1711        if self.old_pop.cur_pop_pos < 3 {
1712            self.generate_sol2c();
1713            return;
1714        }
1715        let ps = self.pop.cur_pop_size;
1716        let i1 = self.rnd.get_sqr_int(ps as i32) as usize;
1717        let i2 = self.rnd.get_int(ps as i32) as usize;
1718        let old_pos = self.old_pop.cur_pop_pos;
1719        let i3 = self.rnd.get_int(old_pos as i32) as usize;
1720        let mode = self.select(sel::GEN2D_MODE);
1721        let i1b = (mode != 0).then(|| self.rnd.get_sqr_int(ps as i32) as usize);
1722        let mut params = self.take_tmp();
1723        let rp1 = self.pop.ordered(i1);
1724        let rp2 = self.pop.ordered(i2);
1725        let rp3 = self.old_pop.ordered(i3);
1726        if mode == 0 {
1727            for i in 0..self.param_count {
1728                params[i] = rp1[i] + ((rp2[i] - rp3[i]) >> 1);
1729            }
1730        } else {
1731            let rp1b = self.pop.ordered(i1b.unwrap());
1732            for i in 0..self.param_count {
1733                params[i] = ((rp1[i] + rp1b[i]) + (rp2[i] - rp3[i])) >> 1;
1734            }
1735        }
1736        self.tmp = params;
1737    }
1738
1739    fn generate_sol4(&mut self) {
1740        let alt = self.select_alt_pop(1);
1741        let par = self.select_par_pop(1);
1742        let use_size = [self.pop.cur_pop_size, self.cur_pop_size_of(par)];
1743        let km = 5 + (self.select(sel::GEN4_MIX_FAC) << 1);
1744        let mut p = self.rnd.get_bit() as usize;
1745        let idx = self.rnd.get_sqr_int(use_size[p] as i32) as usize;
1746        let mut params = self.take_tmp();
1747        params.copy_from_slice(self.pop_ref(if p == 0 { alt } else { par }).ordered(idx));
1748        for _ in 1..km {
1749            p = self.rnd.get_bit() as usize;
1750            let idx = self.rnd.get_sqr_int(use_size[p] as i32) as usize;
1751            let rp = self.pop_ref(if p == 0 { alt } else { par }).ordered(idx);
1752            for i in 0..self.param_count {
1753                params[i] ^= rp[i];
1754            }
1755        }
1756        self.tmp = params;
1757    }
1758
1759    fn generate_sol5(&mut self) {
1760        let par = self.select_par_pop(2);
1761        let par_ps = self.cur_pop_size_of(par);
1762        let si1 = self.rnd.get_sqr_int(par_ps as i32) as usize;
1763        let cp1 = self.ordered_of(par, si1);
1764        let alt = self.select_alt_pop(2);
1765        let si2 = self.rnd.get_sqr_int(self.pop.cur_pop_size as i32) as usize;
1766        let cp2 = self.ordered_of(alt, si2);
1767        let mut params = self.take_tmp();
1768        for i in 0..self.param_count {
1769            let crpl = (self.rnd.get_raw() as i64) & INT_MANT_MASK;
1770            params[i] = (cp1[i] & crpl) | (cp2[i] & !crpl);
1771            let bshift = self.rnd.get_int(INT_MANT_BITS as i32);
1772            params[i] +=
1773                ((self.rnd.get_bit() as i64) << bshift) - ((self.rnd.get_bit() as i64) << bshift);
1774        }
1775        self.tmp = params;
1776    }
1777
1778    fn generate_sol5b(&mut self) {
1779        let par = self.select_par_pop(3);
1780        let par_ps = self.cur_pop_size_of(par);
1781        let i0 = self.rnd.get_sqr_int(par_ps as i32) as usize;
1782        let cp0 = self.ordered_of(par, i0);
1783        let alt = self.select_alt_pop(3);
1784        let ps = self.pop.cur_pop_size;
1785        let ps1 = self.pop.cur_pop_size1;
1786        let cp1 = if self.rnd.get_bit() != 0 {
1787            let i1 = ps1 - self.rnd.get_sqr_int(ps as i32) as usize;
1788            self.ordered_of(alt, i1)
1789        } else {
1790            let i1 = self.rnd.get_sqr_int(ps as i32) as usize;
1791            self.ordered_of(alt, i1)
1792        };
1793        let mode = self.select(sel::GEN5B_MODE);
1794        let mut params = self.take_tmp();
1795        if mode == 0 {
1796            for i in 0..self.param_count {
1797                params[i] = if self.rnd.get_bit() != 0 {
1798                    cp1[i]
1799                } else {
1800                    cp0[i]
1801                };
1802            }
1803        } else {
1804            let i2 = self.rnd.get_sqr_int(par_ps as i32) as usize;
1805            let cp2 = self.ordered_of(par, i2);
1806            let i3 = self.rnd.get_sqr_int(ps as i32) as usize;
1807            let cp3 = self.ordered_of(alt, i3);
1808            let cps = [cp0, cp1, cp2, cp3];
1809            for i in 0..self.param_count {
1810                let sel = ((self.rnd.get_bit() << 1) | self.rnd.get_bit()) as usize;
1811                params[i] = cps[sel][i];
1812            }
1813        }
1814        self.tmp = params;
1815    }
1816
1817    fn generate_sol6(&mut self) {
1818        let ps = self.pop.cur_pop_size;
1819        let r = self.rnd.get_pow(4.0);
1820        let si = (r * ps as f64) as usize;
1821        let mut v = [0.0f64; 2];
1822        let k0 = self.rnd.get_int(self.param_count as i32) as usize;
1823        let use_second = self.rnd.get_bit() != 0;
1824        let k1 = use_second.then(|| self.rnd.get_int(self.param_count as i32) as usize);
1825        let row = self.pop.ordered(si);
1826        v[0] = self.real_value(row, k0);
1827        if let Some(k1) = k1 {
1828            v[1] = self.real_value(row, k1);
1829        } else {
1830            v[1] = v[0];
1831        }
1832        let m = 1.0 - r * r;
1833        v[0] *= m;
1834        v[1] *= m;
1835        let mut params = self.take_tmp();
1836        for i in 0..self.param_count {
1837            let pick = v[self.rnd.get_bit() as usize];
1838            params[i] = ((pick - self.min_values[i]) * self.diff_values_i[i]) as i64;
1839        }
1840        self.tmp = params;
1841    }
1842
1843    fn generate_sol7(&mut self) {
1844        let ps = self.pop.cur_pop_size;
1845        let use_old = self.old_pop.cur_pop_pos > 2;
1846        const P: [f64; 4] = [1.5, 1.75, 2.0, 2.25];
1847        let pwr = P[self.select(sel::GEN7_POW_FAC) as usize];
1848        let mut params = self.take_tmp();
1849        for i in 0..self.param_count {
1850            let rv = self.rnd.get_pow(pwr);
1851            if use_old && self.rnd.get_bit() != 0 && self.rnd.get_bit() != 0 {
1852                let idx = (rv * self.old_pop.cur_pop_pos as f64) as usize;
1853                params[i] = self.old_pop.ordered(idx)[i];
1854            } else {
1855                let idx = (rv * ps as f64) as usize;
1856                params[i] = self.pop.ordered(idx)[i];
1857            }
1858        }
1859        self.tmp = params;
1860    }
1861
1862    fn generate_sol8(&mut self) {
1863        let ps = self.pop.cur_pop_size;
1864        let mode = self.select(sel::GEN8_MODE);
1865        let num_sols = 5 + self.select(sel::GEN8_NUM) as usize;
1866        let mut rp = [0usize; 8];
1867        let first = self.rnd.get_sqr_int(ps as i32) as usize;
1868        rp[0] = first;
1869        let mut params = self.take_tmp();
1870        params.copy_from_slice(self.pop.ordered(first));
1871        for slot in rp.iter_mut().take(num_sols).skip(1) {
1872            let idx = self.rnd.get_sqr_int(ps as i32) as usize;
1873            *slot = idx;
1874            let r0 = self.pop.ordered(idx);
1875            for i in 0..self.param_count {
1876                params[i] = params[i].wrapping_add(r0[i]);
1877            }
1878        }
1879        let m = 1.0 / num_sols as f64;
1880        let mut cent = std::mem::take(&mut self.real_tmp);
1881        cent.resize(self.param_count, 0.0);
1882        for i in 0..self.param_count {
1883            cent[i] = params[i] as f64 * m;
1884            params[i] = cent[i] as i64;
1885        }
1886        if mode == 0 {
1887            const SPANS: [f64; 4] = [1.5, 2.5, 3.5, 4.5];
1888            let gm = SPANS[self.select(sel::GEN8_SPAN) as usize] * m.sqrt();
1889            for &index in &rp[..num_sols] {
1890                let r = self.rnd.get_gaussian() * gm;
1891                let rj = self.pop.ordered(index);
1892                for i in 0..self.param_count {
1893                    params[i] = params[i].wrapping_add(((cent[i] - rj[i] as f64) * r) as i64);
1894                }
1895            }
1896        } else {
1897            const SPANS: [f64; 4] = [0.5, 1.5, 2.5, 3.5];
1898            let gm = SPANS[self.select(sel::GEN8_SPAN + 1) as usize];
1899            for &index in &rp[..num_sols] {
1900                let r = self.rnd.get_gaussian() * gm;
1901                let rj = self.pop.ordered(index);
1902                for i in 0..self.param_count {
1903                    let delta = (params[i].wrapping_sub(rj[i]) as f64 * r) as i64;
1904                    params[i] = params[i].wrapping_add(delta);
1905                }
1906            }
1907        }
1908        self.real_tmp = cent;
1909        self.tmp = params;
1910    }
1911
1912    fn generate_sol9(&mut self) {
1913        let ps = self.pop.cur_pop_size;
1914        let ps1 = self.pop.cur_pop_size1;
1915        let si1 = self.rnd.get_int(ps as i32) as usize;
1916        let si2 = self.rnd.get_sqr_int(ps as i32) as usize;
1917        let subtract = self.rnd.get_bit() != 0;
1918        let mut params = self.take_tmp();
1919        let rp1 = self.pop.ordered(si1);
1920        let rp2 = self.pop.ordered(ps1 - si2);
1921        if subtract {
1922            for i in 0..self.param_count {
1923                params[i] = rp1[i] - ((rp2[i] - rp1[i]) >> 1) * (1 - 2 * self.rnd.get_bit() as i64);
1924            }
1925        } else {
1926            for i in 0..self.param_count {
1927                params[i] = rp1[i] + ((rp2[i] - rp1[i]) >> 1) * (1 - 2 * self.rnd.get_bit() as i64);
1928            }
1929        }
1930        self.tmp = params;
1931    }
1932
1933    fn generate_sol10(&mut self) {
1934        let ps = self.pop.cur_pop_size;
1935        let ps1 = self.pop.cur_pop_size1;
1936        let si1 = self.rnd.get_sqr_int(ps as i32) as usize;
1937        let si2 = self.rnd.get_sqr_int(ps as i32) as usize;
1938        let mut params = self.take_tmp();
1939        {
1940            let rp1 = self.pop.ordered(si1);
1941            let rp2 = self.pop.ordered(ps1 - si2);
1942            for i in 0..self.param_count {
1943                params[i] = (rp1[i] + rp2[i]) >> 1;
1944            }
1945        }
1946        let mut radius = 0.0;
1947        {
1948            let rp1 = self.pop.ordered(si1);
1949            let rp2 = self.pop.ordered(ps1 - si2);
1950            for i in 0..self.param_count {
1951                let v1 = (rp1[i] - params[i]) as f64;
1952                let v2 = (rp2[i] - params[i]) as f64;
1953                radius += v1 * v1 + 0.45 * v2 * v2;
1954            }
1955        }
1956        let mut s2 = 1e-300;
1957        let mut nv = std::mem::take(&mut self.real_tmp);
1958        nv.resize(self.param_count, 0.0);
1959        for n in nv.iter_mut() {
1960            *n = self.rnd.get() - 0.5;
1961            s2 += *n * *n;
1962        }
1963        let d = (radius / s2).sqrt();
1964        for i in 0..self.param_count {
1965            params[i] += (nv[i] * d) as i64;
1966        }
1967        self.real_tmp = nv;
1968        self.tmp = params;
1969    }
1970
1971    fn generate_sol3(&mut self) {
1972        let ps = self.pop.cur_pop_size;
1973        let si1 = self.get_min_sol_index(3, ps);
1974        let si2 = self.rnd.get_sqr_int(ps as i32) as usize;
1975        let mode = self.select(sel::GEN3_MODE);
1976        if mode != 0 && self.pop.need_cent {
1977            self.pop.update_centroid();
1978        }
1979        let mut params = self.take_tmp();
1980        let rp1 = self.pop.ordered(si1);
1981        let rp2 = self.pop.ordered(si2);
1982        if mode == 0 {
1983            for i in 0..self.param_count {
1984                params[i] = rp1[i] + (rp1[i] - rp2[i]);
1985            }
1986        } else {
1987            const CENT_PROB: [f64; 4] = [0.0, 0.25, 0.5, 0.75];
1988            let prob = CENT_PROB[mode as usize];
1989            for i in 0..self.param_count {
1990                params[i] = if self.rnd.get() < prob {
1991                    self.pop.cent[i]
1992                } else {
1993                    rp1[i] + (rp1[i] - rp2[i])
1994                };
1995            }
1996        }
1997        self.tmp = params;
1998    }
1999
2000    /// Generator that draws from an independently-running parallel optimizer.
2001    /// Returns the already-evaluated `(cost, real_values)`.
2002    fn generate_sol_par(&mut self, obj: &dyn Objective) -> (f64, Vec<f64>) {
2003        if self.use_par_opt == 1 {
2004            self.use_par_opt = self.select(sel::PAR_OPT2);
2005        }
2006        let step;
2007        let which_pop;
2008        if self.use_par_opt == 0 {
2009            step = self.spher.optimize(&mut self.rnd, obj);
2010            if step.stall > 0 {
2011                self.use_par_opt = 1;
2012            }
2013            if step.stall > self.param_count as i64 * 64 {
2014                let best = self.best_values.clone();
2015                self.spher.init(&mut self.rnd, Some(&best), 0.5);
2016                self.par_opt_pop.reset_cur_pop_pos();
2017            }
2018            which_pop = 0;
2019        } else {
2020            step = self.nmseq.optimize(&mut self.rnd, obj);
2021            if step.stall > 0 {
2022                self.use_par_opt = 0;
2023            }
2024            if step.stall > self.param_count as i64 * 16 {
2025                let best = self.best_values.clone();
2026                self.nmseq.init(&mut self.rnd, Some(&best), 1.0);
2027                self.par_opt2_pop.reset_cur_pop_pos();
2028            }
2029            which_pop = 1;
2030        }
2031        let mut tmp = self.take_tmp();
2032        for i in 0..self.param_count {
2033            tmp[i] = ((step.values[i] - self.min_values[i]) * self.diff_values_i[i]) as i64;
2034        }
2035        if which_pop == 0 {
2036            self.par_opt_pop.update_pop(step.cost, &tmp, false);
2037        } else {
2038            self.par_opt2_pop.update_pop(step.cost, &tmp, false);
2039        }
2040        self.tmp = tmp;
2041        (step.cost, step.values)
2042    }
2043
2044    /// Route the method-selection tree to a generator. Returns a precomputed
2045    /// cost and exact values when the parallel-optimizer generator (SolPar)
2046    /// was used; `None`
2047    /// otherwise (the caller then evaluates). `obj` is `None` in ask/tell mode,
2048    /// where the method selector is resampled to an ask/tell-compatible
2049    /// generator without registering a second selector use.
2050    fn generate(&mut self, obj: Option<&dyn Objective>) -> Option<(f64, Vec<f64>)> {
2051        let mut method = self.select(sel::METHOD);
2052        while method == 3 && obj.is_none() {
2053            method = self.sels[sel::METHOD].select(&mut self.rnd);
2054        }
2055        if obj.is_none() {
2056            let final_method = self.sels[sel::METHOD].captured(sel::METHOD);
2057            if let Some(selection) = self
2058                .apply_sels
2059                .iter_mut()
2060                .find(|selection| selection.index == sel::METHOD)
2061            {
2062                *selection = final_method;
2063            }
2064        }
2065        match method {
2066            0 => self.generate_sol2(),
2067            1 => {
2068                let m1 = self.select(sel::M1);
2069                match m1 {
2070                    0 => {
2071                        let m1a = self.select(sel::M1A);
2072                        match m1a {
2073                            0 => self.generate_sol2b(),
2074                            1 => self.generate_sol2c(),
2075                            _ => self.generate_sol2d(),
2076                        }
2077                    }
2078                    1 => {
2079                        if self.select(sel::M1B) != 0 {
2080                            self.generate_sol4();
2081                        } else {
2082                            self.generate_sol5b();
2083                        }
2084                    }
2085                    2 => {
2086                        if self.select(sel::M1C) != 0 {
2087                            self.generate_sol5();
2088                        } else {
2089                            self.generate_sol10();
2090                        }
2091                    }
2092                    _ => self.generate_sol6(),
2093                }
2094            }
2095            2 => {
2096                if self.select(sel::M2) != 0 {
2097                    self.generate_sol1();
2098                } else {
2099                    let m2b = self.select(sel::M2B);
2100                    match m2b {
2101                        0 => self.generate_sol3(),
2102                        1 => self.generate_sol7(),
2103                        2 => self.generate_sol8(),
2104                        _ => self.generate_sol9(),
2105                    }
2106                }
2107            }
2108            _ => {
2109                let (cost, real) =
2110                    self.generate_sol_par(obj.expect("parallel generator requires an objective"));
2111                return Some((cost, real));
2112            }
2113        }
2114        None
2115    }
2116
2117    fn in_init(&self) -> bool {
2118        !self.init_queue.is_empty()
2119    }
2120
2121    /// Generate one candidate from the current (frozen) population state.
2122    /// `obj` enables the SolPar generator (parallel optimizers); `None` in
2123    /// ask/tell mode.
2124    fn gen_one(&mut self, obj: Option<&dyn Objective>) -> Candidate {
2125        if let Some(enc) = self.init_queue.pop_front() {
2126            let mut real = std::mem::take(&mut self.real_tmp);
2127            real.resize(self.param_count, 0.0);
2128            for i in 0..self.param_count {
2129                real[i] = self.real_value(&enc, i);
2130            }
2131            return Candidate {
2132                enc,
2133                real,
2134                sels: vec![],
2135                is_init: true,
2136                precomputed_cost: None,
2137            };
2138        }
2139        self.apply_sels.clear();
2140        if let Some(selection) = self.deferred_sels.pop_front() {
2141            self.apply_sels.push(selection);
2142        }
2143        let precomputed = self.generate(obj);
2144        let mut enc = std::mem::take(&mut self.tmp);
2145        for e in enc.iter_mut() {
2146            *e = wrap_param(&mut self.rnd, *e);
2147        }
2148        let (precomputed_cost, real) = match precomputed {
2149            Some((cost, values)) => (Some(cost), values),
2150            None => {
2151                let mut values = std::mem::take(&mut self.real_tmp);
2152                values.resize(self.param_count, 0.0);
2153                for i in 0..self.param_count {
2154                    values[i] = self.real_value(&enc, i);
2155                }
2156                (None, values)
2157            }
2158        };
2159        let sels = std::mem::take(&mut self.apply_sels);
2160        Candidate {
2161            enc,
2162            real,
2163            sels,
2164            is_init: false,
2165            precomputed_cost,
2166        }
2167    }
2168
2169    fn select_deferred(&mut self, index: usize) -> i32 {
2170        let value = self.sels[index].select(&mut self.rnd);
2171        self.deferred_sels
2172            .push_back(self.sels[index].captured(index));
2173        value
2174    }
2175
2176    /// Apply a candidate's evaluated cost, updating population and selectors.
2177    /// Returns whether the solution is push-worthy (inserted at
2178    /// `0 < p <= cur_pop_size1`) for the deep layer.
2179    fn apply_one(&mut self, cand: &Candidate, cost: f64, collect_push: bool) -> bool {
2180        let cost = sanitize_cost(cost);
2181        if cand.is_init {
2182            let p = self.pop.update_pop(cost, &cand.enc, false) as i64;
2183            self.update_best_cost(cost, &cand.real, p);
2184            if self.init_queue.is_empty() && self.pop.cur_pop_pos == self.pop_size {
2185                self.pop.update_centroid();
2186                // Seed the diverging parallel populations from the main one.
2187                for parallel in &mut self.par_pops {
2188                    parallel.copy_from(&self.pop);
2189                }
2190            }
2191            return false;
2192        }
2193        let do_eval = cand.precomputed_cost.is_none();
2194        let p = self.pop.update_pop(cost, &cand.enc, true);
2195        let mut push = false;
2196        if p > self.pop.cur_pop_size1 {
2197            for &selection in &cand.sels {
2198                self.sels[selection.index].decr_captured(selection);
2199            }
2200            self.stall_count += 1;
2201            // Dynamic population sizing: grow on failure.
2202            if do_eval
2203                && self.pop.cur_pop_size < self.pop_size
2204                && self.select_deferred(sel::POP_CHANGE_INCR) != 0
2205            {
2206                self.pop.incr_cur_pop_size();
2207            }
2208        } else {
2209            self.update_best_cost(cost, &cand.real, p as i64);
2210            let v = 1.0 - p as f64 * self.pop.cur_pop_size_i;
2211            for &selection in &cand.sels {
2212                self.sels[selection.index].incr_captured(selection, v);
2213            }
2214            self.stall_count = 0;
2215            if collect_push && p > 0 {
2216                push = true;
2217            }
2218            // Probabilistically push the current worst into OldPop.
2219            if self.rnd.get() < self.param_count_i {
2220                let w = self.pop.cur_pop_size1;
2221                let worst_cost = self.pop.costs[w];
2222                self.old_pop
2223                    .update_pop(worst_cost, self.pop.ordered(w), false);
2224            }
2225            // Dynamic population sizing: shrink on success.
2226            if do_eval
2227                && self.pop.cur_pop_size > self.pop_size / 2
2228                && self.select_deferred(sel::POP_CHANGE_DECR) != 0
2229            {
2230                self.pop.decr_cur_pop_size();
2231            }
2232        }
2233        // "Diverging populations" technique: feed the nearest parallel pop.
2234        self.update_par_pop(cost, &cand.enc);
2235        push
2236    }
2237
2238    /// Push an improving solution into this optimizer (deep-mode PushOpt).
2239    fn push_solution(&mut self, cost: f64, enc: &[i64]) {
2240        if self.in_init() {
2241            return;
2242        }
2243        self.pop.update_pop(cost, enc, true);
2244        self.update_par_pop(cost, enc);
2245    }
2246
2247    /// One optimization iteration (1 objective evaluation).
2248    fn optimize_step(&mut self, obj: &impl Objective) {
2249        let cand = self.gen_one(Some(obj));
2250        let cost = cand
2251            .precomputed_cost
2252            .unwrap_or_else(|| objective_cost(obj, &cand.real));
2253        self.apply_one(&cand, cost, false);
2254        self.recycle_candidate(cand);
2255    }
2256
2257    /// Deep-mode step: generate + evaluate + apply, returning the stall count
2258    /// and any push-worthy `(cost, enc)` for the PushOpt.
2259    fn step_collect(
2260        &mut self,
2261        obj: &impl Objective,
2262        collect_push: bool,
2263    ) -> (i64, Option<(f64, Vec<i64>)>) {
2264        let mut cand = self.gen_one(Some(obj));
2265        let cost = cand
2266            .precomputed_cost
2267            .unwrap_or_else(|| objective_cost(obj, &cand.real));
2268        let should_push = self.apply_one(&cand, cost, collect_push);
2269        let push = should_push.then(|| (cost, std::mem::take(&mut cand.enc)));
2270        self.iterations += 1;
2271        self.evaluations += 1;
2272        self.recycle_candidate(cand);
2273        (self.stall_count, push)
2274    }
2275
2276    fn best_cost(&self) -> f64 {
2277        self.best_cost
2278    }
2279
2280    fn in_init_phase(&self) -> bool {
2281        self.in_init()
2282    }
2283
2284    fn init_remaining(&self) -> usize {
2285        self.init_queue.len()
2286    }
2287
2288    // ---- ask/tell interface (delayed-feedback batching) ----
2289
2290    /// Ask for up to `batch` candidate rows (real-valued). During the initial
2291    /// population fill the batch is capped to the remaining init members.
2292    pub fn ask(&mut self, batch: usize) -> Vec<Vec<f64>> {
2293        if !self.asked.is_empty() {
2294            return self
2295                .asked
2296                .iter()
2297                .map(|candidate| candidate.real.clone())
2298                .collect();
2299        }
2300        if batch == 0 || self.stop != 0 || self.evaluations >= self.max_evaluations {
2301            return Vec::new();
2302        }
2303        let remaining_budget = (self.max_evaluations - self.evaluations) as usize;
2304        let requested = batch.min(remaining_budget);
2305        let n = if self.in_init() {
2306            requested.min(self.init_queue.len())
2307        } else {
2308            requested
2309        };
2310        for _ in 0..n {
2311            let cand = self.gen_one(None);
2312            self.asked.push(cand);
2313        }
2314        self.asked.iter().map(|c| c.real.clone()).collect()
2315    }
2316
2317    /// Tell the costs for the batch returned by [`ask`](BiteOpt::ask).
2318    pub fn tell(&mut self, costs: &[f64]) -> i32 {
2319        if self.asked.is_empty() || costs.len() != self.asked.len() {
2320            return -1;
2321        }
2322        let asked = std::mem::take(&mut self.asked);
2323        for (cand, &cost) in asked.into_iter().zip(costs) {
2324            self.apply_one(&cand, cost, false);
2325            self.iterations += 1;
2326            self.evaluations += 1;
2327            self.recycle_candidate(cand);
2328        }
2329        self.update_stop();
2330        self.stop
2331    }
2332
2333    pub fn current_batch_size(&self) -> usize {
2334        self.asked.len()
2335    }
2336    pub fn dim(&self) -> usize {
2337        self.param_count
2338    }
2339    pub fn population_size(&self) -> usize {
2340        self.pop_size
2341    }
2342    pub fn stop_code(&self) -> i32 {
2343        self.stop
2344    }
2345    pub fn result_public(&self) -> BiteResult {
2346        self.result()
2347    }
2348
2349    fn update_stop(&mut self) {
2350        if self.best_cost < self.stopfitness {
2351            self.stop = 1;
2352        } else if self.stall_criterion > 0
2353            && self.stall_count > self.stall_criterion as i64 * 128 * self.param_count as i64
2354        {
2355            self.stop = 2;
2356        }
2357    }
2358
2359    /// Run until the evaluation budget or a stop condition is reached.
2360    pub fn optimize(&mut self, obj: &impl Objective) -> BiteResult {
2361        while self.evaluations < self.max_evaluations && self.stop == 0 {
2362            self.optimize_step(obj);
2363            self.iterations += 1;
2364            self.evaluations += 1;
2365            self.update_stop();
2366        }
2367        self.result()
2368    }
2369
2370    fn result(&self) -> BiteResult {
2371        BiteResult {
2372            x: self.best_values.clone(),
2373            y: self.best_cost,
2374            evaluations: self.evaluations,
2375            iterations: self.iterations,
2376            stop: self.stop,
2377        }
2378    }
2379}
2380
2381// ---------------------------------------------------------------------------
2382// Deep multi-population layer (port of CBiteOptDeep / CBiteOptDeepAT)
2383// ---------------------------------------------------------------------------
2384
2385/// `M` BiteOpt instances with solution "pushing" between them. `M == 1` is a
2386/// plain single-population BiteOpt.
2387pub struct DeepBiteOpt {
2388    opts: Vec<BiteOpt>,
2389    m: usize,
2390    cur_opt: usize,
2391    push_opt: usize,
2392    best_opt: usize,
2393    at_stall_count: i64,
2394    batch_cur_opt: usize,
2395    rnd: BiteRnd,
2396    max_evaluations: u64,
2397    stopfitness: f64,
2398    stall_criterion: i32,
2399    param_count: usize,
2400    evaluations: u64,
2401    iterations: i32,
2402    stop: i32,
2403}
2404
2405impl DeepBiteOpt {
2406    pub fn new(lower: &[f64], upper: &[f64], init: Option<&[f64]>, p: &BiteParams, m: i32) -> Self {
2407        validate_bite_inputs(lower, upper, init, p, m).expect("invalid deep BiteOpt configuration");
2408        let m = m.max(1) as usize;
2409        let opts: Vec<BiteOpt> = (0..m)
2410            .map(|i| {
2411                let mut pi = p.clone();
2412                // Distinct per-optimizer RNG streams for diversity.
2413                pi.runid = p.runid.wrapping_add((i as i64).wrapping_mul(0x9E3779B1));
2414                BiteOpt::new(lower, upper, init, &pi)
2415            })
2416            .collect();
2417        let mut d = DeepBiteOpt {
2418            opts,
2419            m,
2420            cur_opt: 0,
2421            push_opt: 0,
2422            best_opt: 0,
2423            at_stall_count: 0,
2424            batch_cur_opt: 0,
2425            rnd: BiteRnd::new(p.seed.wrapping_add(p.runid as u64).wrapping_add(0xB17E)),
2426            max_evaluations: if p.max_evaluations > 0 {
2427                p.max_evaluations
2428            } else {
2429                50_000
2430            },
2431            stopfitness: p.stop_fitness,
2432            stall_criterion: p.stall_criterion.max(0),
2433            param_count: lower.len(),
2434            evaluations: 0,
2435            iterations: 0,
2436            stop: 0,
2437        };
2438        d.pick_push();
2439        d
2440    }
2441
2442    fn pick_push(&mut self) {
2443        if self.m == 1 {
2444            self.push_opt = self.cur_opt;
2445        } else if self.m == 2 {
2446            self.push_opt = 1 - self.cur_opt;
2447        } else {
2448            loop {
2449                let p = self.rnd.get_int(self.m as i32) as usize;
2450                if p != self.cur_opt {
2451                    self.push_opt = p;
2452                    break;
2453                }
2454            }
2455        }
2456    }
2457
2458    fn total_evaluations(&self) -> u64 {
2459        self.evaluations
2460    }
2461
2462    fn best(&self) -> &BiteOpt {
2463        &self.opts[self.best_opt]
2464    }
2465
2466    fn update_stop(&mut self) {
2467        if self.best().best_cost < self.stopfitness {
2468            self.stop = 1;
2469        } else if self.stall_criterion > 0
2470            && self.at_stall_count > self.stall_criterion as i64 * 128 * self.param_count as i64
2471        {
2472            self.stop = 2;
2473        }
2474    }
2475
2476    fn track_best(&mut self, idx: usize) {
2477        if self.opts[idx].best_cost() <= self.opts[self.best_opt].best_cost() {
2478            self.best_opt = idx;
2479        }
2480    }
2481
2482    /// One-shot optimization until the (total) evaluation budget or a stop.
2483    pub fn optimize(&mut self, obj: &impl Objective) -> BiteResult {
2484        while self.total_evaluations() < self.max_evaluations && self.stop == 0 {
2485            self.pick_push();
2486            let opt_idx = self.cur_opt;
2487            let (sc, push) = self.opts[opt_idx].step_collect(obj, self.m > 1);
2488            self.evaluations += 1;
2489            self.iterations += 1;
2490            if let Some((cost, enc)) = push {
2491                self.opts[self.push_opt].push_solution(cost, &enc);
2492                self.opts[opt_idx].tmp = enc;
2493            }
2494            self.track_best(opt_idx);
2495            if sc == 0 {
2496                self.at_stall_count = 0;
2497            } else {
2498                self.cur_opt = self.push_opt;
2499                self.at_stall_count += 1;
2500            }
2501            self.update_stop();
2502        }
2503        self.result()
2504    }
2505
2506    /// Ask for up to `batch` candidate rows from the current optimizer.
2507    pub fn ask(&mut self, batch: usize) -> Vec<Vec<f64>> {
2508        if self.current_batch_size() != 0 {
2509            return self.opts[self.batch_cur_opt]
2510                .asked
2511                .iter()
2512                .map(|candidate| candidate.real.clone())
2513                .collect();
2514        }
2515        let evaluations = self.total_evaluations();
2516        if batch == 0 || self.stop != 0 || evaluations >= self.max_evaluations {
2517            return Vec::new();
2518        }
2519        let mut b = batch.min((self.max_evaluations - evaluations) as usize);
2520        if self.opts[self.cur_opt].in_init_phase() {
2521            let rem = self.opts[self.cur_opt].init_remaining();
2522            if b > rem {
2523                b = rem.max(1);
2524            }
2525        }
2526        self.batch_cur_opt = self.cur_opt;
2527        self.opts[self.cur_opt].ask(b)
2528    }
2529
2530    /// Tell the costs for the last [`ask`](DeepBiteOpt::ask). Candidates are
2531    /// applied best-first, with solution pushing and optimizer switching.
2532    pub fn tell(&mut self, costs: &[f64]) -> i32 {
2533        let opt_idx = self.batch_cur_opt;
2534        if costs.len() != self.opts[opt_idx].asked.len() || costs.is_empty() {
2535            return -1;
2536        }
2537        let mut asked = std::mem::take(&mut self.opts[opt_idx].asked);
2538        let mut order: Vec<usize> = (0..asked.len()).collect();
2539        order.sort_by(|&a, &b| {
2540            let ca = sanitize_cost(costs[a]);
2541            let cb = sanitize_cost(costs[b]);
2542            ca.partial_cmp(&cb)
2543                .unwrap_or(std::cmp::Ordering::Equal)
2544                .then(a.cmp(&b))
2545        });
2546        for &i in &order {
2547            let push = self.opts[opt_idx].apply_one(&asked[i], costs[i], self.m > 1);
2548            self.opts[opt_idx].iterations += 1;
2549            self.opts[opt_idx].evaluations += 1;
2550            self.iterations += 1;
2551            self.evaluations += 1;
2552            let sc = self.opts[opt_idx].stall_count;
2553            if push {
2554                self.opts[self.push_opt].push_solution(sanitize_cost(costs[i]), &asked[i].enc);
2555            }
2556            self.track_best(opt_idx);
2557            if self.m > 1 {
2558                if sc == 0 {
2559                    self.at_stall_count = 0;
2560                } else {
2561                    self.at_stall_count += 1;
2562                    self.cur_opt = self.push_opt;
2563                    self.pick_push();
2564                }
2565            } else {
2566                self.at_stall_count = sc;
2567            }
2568        }
2569        if let Some(candidate) = asked.pop() {
2570            self.opts[opt_idx].recycle_candidate(candidate);
2571        }
2572        self.update_stop();
2573        self.stop
2574    }
2575
2576    pub fn dim(&self) -> usize {
2577        self.param_count
2578    }
2579    pub fn population_size(&self) -> usize {
2580        self.opts[0].population_size()
2581    }
2582    pub fn current_batch_size(&self) -> usize {
2583        self.opts[self.batch_cur_opt].current_batch_size()
2584    }
2585    pub fn stop_code(&self) -> i32 {
2586        self.stop
2587    }
2588
2589    pub fn result(&self) -> BiteResult {
2590        let b = self.best();
2591        BiteResult {
2592            x: b.best_values.clone(),
2593            y: b.best_cost,
2594            evaluations: self.evaluations,
2595            iterations: self.iterations,
2596            stop: self.stop,
2597        }
2598    }
2599
2600    pub fn result_public(&self) -> BiteResult {
2601        self.result()
2602    }
2603}
2604
2605/// Run BiteOpt on a bounded problem. `m` is the "deep" depth (number of
2606/// populations); `m <= 1` is a plain single-population run.
2607pub fn optimize_bite(
2608    obj: &impl Objective,
2609    lower: &[f64],
2610    upper: &[f64],
2611    init: Option<&[f64]>,
2612    p: &BiteParams,
2613    m: i32,
2614) -> BiteResult {
2615    let mut opt = DeepBiteOpt::new(lower, upper, init, p, m);
2616    opt.optimize(obj)
2617}
2618
2619#[cfg(test)]
2620mod tests {
2621    use super::*;
2622
2623    fn sphere(x: &[f64]) -> f64 {
2624        x.iter().map(|v| v * v).sum()
2625    }
2626    fn rosen(x: &[f64]) -> f64 {
2627        (0..x.len() - 1)
2628            .map(|i| 100.0 * (x[i + 1] - x[i] * x[i]).powi(2) + (1.0 - x[i]).powi(2))
2629            .sum()
2630    }
2631    fn rastrigin(x: &[f64]) -> f64 {
2632        let n = x.len() as f64;
2633        10.0 * n
2634            + x.iter()
2635                .map(|v| v * v - 10.0 * (2.0 * std::f64::consts::PI * v).cos())
2636                .sum::<f64>()
2637    }
2638
2639    fn run(obj: impl Objective, dim: usize, seed: u64, evals: u64) -> f64 {
2640        let params = BiteParams {
2641            max_evaluations: evals,
2642            seed,
2643            ..Default::default()
2644        };
2645        optimize_bite(&obj, &vec![-5.0; dim], &vec![5.0; dim], None, &params, 1).y
2646    }
2647
2648    #[test]
2649    fn deep_minimizes_rosenbrock() {
2650        let params = BiteParams {
2651            max_evaluations: 30000,
2652            seed: 4,
2653            ..Default::default()
2654        };
2655        // depth M = 3
2656        let r = optimize_bite(
2657            &(rosen as fn(&[f64]) -> f64),
2658            &[-5.0; 6],
2659            &[5.0; 6],
2660            None,
2661            &params,
2662            3,
2663        );
2664        assert!(r.y < 1e-2, "deep rosen: {}", r.y);
2665    }
2666
2667    #[test]
2668    fn minimizes_sphere() {
2669        assert!(run(sphere as fn(&[f64]) -> f64, 5, 1, 15000) < 1e-6);
2670    }
2671
2672    #[test]
2673    fn minimizes_rosenbrock() {
2674        let mut v: Vec<f64> = (0..5)
2675            .map(|s| run(rosen as fn(&[f64]) -> f64, 5, s, 30000))
2676            .collect();
2677        v.sort_by(|a, b| a.partial_cmp(b).unwrap());
2678        assert!(v[2] < 1e-2, "rosen median too large: {v:?}");
2679    }
2680
2681    #[test]
2682    fn minimizes_rastrigin() {
2683        let mut v: Vec<f64> = (0..5)
2684            .map(|s| run(rastrigin as fn(&[f64]) -> f64, 5, s, 30000))
2685            .collect();
2686        v.sort_by(|a, b| a.partial_cmp(b).unwrap());
2687        assert!(v[2] < 5.0, "rastrigin median too large: {v:?}");
2688    }
2689
2690    #[test]
2691    fn ask_tell_converges() {
2692        let params = BiteParams {
2693            max_evaluations: 15000,
2694            seed: 7,
2695            ..Default::default()
2696        };
2697        let mut opt = BiteOpt::new(&[-5.0; 5], &[5.0; 5], None, &params);
2698        while opt.evaluations < 15000 && opt.stop == 0 {
2699            let xs = opt.ask(8);
2700            let ys: Vec<f64> = xs.iter().map(|x| sphere(x)).collect();
2701            opt.tell(&ys);
2702        }
2703        assert!(
2704            opt.result_public().y < 1e-4,
2705            "ask/tell: {}",
2706            opt.result_public().y
2707        );
2708    }
2709
2710    #[test]
2711    fn validates_configuration() {
2712        let params = BiteParams::default();
2713        assert!(validate_bite_inputs(&[], &[], None, &params, 1).is_err());
2714        assert!(validate_bite_inputs(&[0.0], &[1.0, 2.0], None, &params, 1).is_err());
2715        assert!(validate_bite_inputs(&[1.0], &[1.0], None, &params, 1).is_err());
2716        assert!(validate_bite_inputs(&[2.0], &[1.0], None, &params, 1).is_err());
2717        assert!(validate_bite_inputs(&[f64::NAN], &[1.0], None, &params, 1).is_err());
2718        assert!(validate_bite_inputs(&[0.0], &[f64::INFINITY], None, &params, 1).is_err());
2719        assert!(validate_bite_inputs(&[-f64::MAX], &[f64::MAX], None, &params, 1).is_err());
2720        assert!(validate_bite_inputs(&[0.0], &[1.0], Some(&[0.5, 0.5]), &params, 1).is_err());
2721        assert!(validate_bite_inputs(&[0.0], &[1.0], Some(&[f64::NAN]), &params, 1).is_err());
2722        assert!(validate_bite_inputs(&[0.0], &[1.0], None, &params, 37).is_err());
2723
2724        let invalid_pop = BiteParams {
2725            popsize: 3,
2726            ..Default::default()
2727        };
2728        assert!(validate_bite_inputs(&[0.0], &[1.0], None, &invalid_pop, 1).is_err());
2729        let invalid_stop = BiteParams {
2730            stop_fitness: f64::NAN,
2731            ..Default::default()
2732        };
2733        assert!(validate_bite_inputs(&[0.0], &[1.0], None, &invalid_stop, 1).is_err());
2734
2735        // The upstream API treats non-positive depth and population size as
2736        // requests for their defaults.
2737        let defaults = BiteParams {
2738            popsize: -1,
2739            ..Default::default()
2740        };
2741        assert!(validate_bite_inputs(&[0.0], &[1.0], Some(&[0.5]), &defaults, -1).is_ok());
2742    }
2743
2744    #[test]
2745    fn helper_edge_paths_are_bounded_and_stable() {
2746        let mut rnd = BiteRnd::new(101);
2747        for value in [
2748            wrap_param(&mut rnd, -INT_MANT_MULT * 2),
2749            wrap_param(&mut rnd, INT_MANT_MULT * 3),
2750        ] {
2751            assert!((0..=INT_MANT_MULT).contains(&value));
2752        }
2753        for value in [wrap01(&mut rnd, -2.0), wrap01(&mut rnd, 3.0)] {
2754            assert!((0.0..=1.0).contains(&value));
2755        }
2756        for value in [
2757            wrap_param_real(&mut rnd, -30.0, -5.0, 10.0),
2758            wrap_param_real(&mut rnd, 30.0, -5.0, 10.0),
2759        ] {
2760            assert!((-5.0..=5.0).contains(&value));
2761        }
2762
2763        let mut population = BitePop::new(2, 4);
2764        population.update_pop(f64::NAN, &[1, 2], false);
2765        assert_eq!(population.costs[0], BAD_COST);
2766        population.need_cent = true;
2767        assert_eq!(population.get_centroid().len(), 2);
2768
2769        let mut selector = BiteSel::new(2);
2770        selector.reset(&mut rnd, 2);
2771        let fallback = SelUse {
2772            index: 0,
2773            value: selector.sels[0][0],
2774            position: 0,
2775            slot_id: selector.slot_ids[0],
2776            entry_id: u8::MAX,
2777        };
2778        selector.restore(fallback);
2779        assert_eq!(selector.sel, fallback.value);
2780    }
2781
2782    #[test]
2783    fn direct_driver_initial_guess_defaults_and_getters() {
2784        let params = BiteParams {
2785            max_evaluations: 20,
2786            stop_fitness: f64::INFINITY,
2787            seed: 102,
2788            ..Default::default()
2789        };
2790        let mut opt = BiteOpt::new(&[-1.0; 2], &[1.0; 2], Some(&[0.2, -0.2]), &params);
2791        assert_eq!(opt.dim(), 2);
2792        assert_eq!(opt.population_size(), 15);
2793        assert_eq!(opt.stop_code(), 0);
2794        let result = opt.optimize(&(sphere as fn(&[f64]) -> f64));
2795        assert_eq!(result.evaluations, 1);
2796        assert_eq!(result.stop, 1);
2797
2798        let default_budget = BiteParams {
2799            max_evaluations: 0,
2800            ..Default::default()
2801        };
2802        let opt = BiteOpt::new(&[-1.0], &[1.0], None, &default_budget);
2803        assert_eq!(opt.max_evaluations, 50_000);
2804        let mut deep = DeepBiteOpt::new(&[-1.0], &[1.0], None, &default_budget, 1);
2805        assert_eq!(deep.max_evaluations, 50_000);
2806        assert_eq!(deep.dim(), 1);
2807        assert_eq!(deep.population_size(), 12);
2808        assert_eq!(deep.stop_code(), 0);
2809        let asked = deep.ask(2);
2810        assert_eq!(deep.ask(5), asked);
2811    }
2812
2813    #[test]
2814    fn ask_tell_enforces_batches_and_budget() {
2815        let params = BiteParams {
2816            popsize: 4,
2817            max_evaluations: 5,
2818            seed: 11,
2819            ..Default::default()
2820        };
2821        let mut opt = BiteOpt::new(&[-1.0; 2], &[1.0; 2], None, &params);
2822
2823        assert!(opt.ask(0).is_empty());
2824        assert_eq!(opt.tell(&[0.0]), -1);
2825
2826        let first = opt.ask(3);
2827        assert_eq!(first.len(), 3);
2828        assert_eq!(opt.ask(99), first);
2829        assert_eq!(opt.tell(&[1.0, 2.0]), -1);
2830        assert_eq!(opt.current_batch_size(), 3);
2831        assert_eq!(opt.tell(&[1.0, 2.0, 3.0]), 0);
2832
2833        // Initial population fill is not mixed with generated candidates.
2834        let second = opt.ask(8);
2835        assert_eq!(second.len(), 1);
2836        assert_eq!(opt.tell(&[4.0]), 0);
2837        let last = opt.ask(8);
2838        assert_eq!(last.len(), 1);
2839        opt.tell(&[5.0]);
2840
2841        assert!(opt.ask(8).is_empty());
2842        let result = opt.result_public();
2843        assert_eq!(result.evaluations, 5);
2844        assert_eq!(result.iterations, 5);
2845    }
2846
2847    #[test]
2848    fn ask_tell_records_the_resampled_compatible_method() {
2849        let params = BiteParams {
2850            popsize: 4,
2851            max_evaluations: 300,
2852            seed: 111,
2853            ..Default::default()
2854        };
2855        let mut opt = BiteOpt::new(&[-1.0; 2], &[1.0; 2], None, &params);
2856        let initial = opt.ask(4);
2857        opt.tell(&vec![1.0; initial.len()]);
2858
2859        for cost in 2..250 {
2860            let cand = opt.gen_one(None);
2861            let method = cand
2862                .sels
2863                .iter()
2864                .find(|selection| selection.index == sel::METHOD)
2865                .unwrap();
2866            assert_ne!(method.value, 3);
2867            opt.apply_one(&cand, cost as f64, false);
2868            opt.recycle_candidate(cand);
2869        }
2870    }
2871
2872    #[test]
2873    fn deep_ask_tell_enforces_budget_and_sanitizes_costs() {
2874        let params = BiteParams {
2875            popsize: 4,
2876            max_evaluations: 7,
2877            seed: 12,
2878            ..Default::default()
2879        };
2880        let mut opt = DeepBiteOpt::new(&[-1.0; 2], &[1.0; 2], None, &params, 3);
2881
2882        assert_eq!(opt.tell(&[0.0]), -1);
2883        let first = opt.ask(9);
2884        assert_eq!(first.len(), 4);
2885        assert_eq!(opt.tell(&[f64::NAN, f64::INFINITY, f64::NEG_INFINITY]), -1);
2886        assert_eq!(opt.current_batch_size(), 4);
2887        assert_eq!(
2888            opt.tell(&[f64::NAN, f64::INFINITY, f64::NEG_INFINITY, f64::NAN]),
2889            0
2890        );
2891
2892        let second = opt.ask(9);
2893        assert_eq!(second.len(), 3);
2894        opt.tell(&[3.0, 2.0, 1.0]);
2895        assert!(opt.ask(1).is_empty());
2896
2897        let result = opt.result_public();
2898        assert_eq!(result.evaluations, 7);
2899        assert_eq!(result.iterations, 7);
2900        assert!(result.y.is_finite());
2901        assert_eq!(result.y, 1.0);
2902    }
2903
2904    #[test]
2905    fn non_finite_objective_values_are_rejected() {
2906        let params = BiteParams {
2907            max_evaluations: 25,
2908            seed: 13,
2909            ..Default::default()
2910        };
2911        let result = optimize_bite(
2912            &(|_: &[f64]| f64::NAN),
2913            &[-1.0; 2],
2914            &[1.0; 2],
2915            None,
2916            &params,
2917            2,
2918        );
2919        assert_eq!(result.y, BAD_COST);
2920        assert_eq!(result.evaluations, 25);
2921        assert!(result.x.iter().all(|value| value.is_finite()));
2922    }
2923
2924    #[test]
2925    fn stop_fitness_terminates_immediately() {
2926        let params = BiteParams {
2927            max_evaluations: 100,
2928            stop_fitness: f64::INFINITY,
2929            seed: 14,
2930            ..Default::default()
2931        };
2932        let result = optimize_bite(
2933            &(sphere as fn(&[f64]) -> f64),
2934            &[-1.0; 2],
2935            &[1.0; 2],
2936            None,
2937            &params,
2938            1,
2939        );
2940        assert_eq!(result.evaluations, 1);
2941        assert_eq!(result.stop, 1);
2942    }
2943
2944    #[test]
2945    fn delayed_selector_feedback_restores_the_exact_slot() {
2946        let mut rnd = BiteRnd::new(15);
2947        let mut selector = BiteSel::new(3);
2948        selector.reset(&mut rnd, 4);
2949
2950        selector.slot = 2;
2951        selector.selp = 3;
2952        selector.sel = selector.sels[2][3];
2953        selector.sel_id = selector.entry_ids[2][3];
2954        let captured = selector.captured(7);
2955        let captured_id = captured.slot_id;
2956
2957        // Simulate later candidates overwriting the selector's current state.
2958        selector.slot = 4;
2959        selector.selp = 0;
2960        selector.sel = selector.sels[4][0];
2961        selector.sel_id = selector.entry_ids[4][0];
2962        let later_id = selector.slot_ids[4];
2963        selector.incr_captured(captured, 1.0);
2964
2965        let captured_slot = selector
2966            .slot_ids
2967            .iter()
2968            .position(|&id| id == captured_id)
2969            .unwrap();
2970        let later_slot = selector
2971            .slot_ids
2972            .iter()
2973            .position(|&id| id == later_id)
2974            .unwrap();
2975        assert_eq!(selector.slot_accums[captured_slot], 0.5);
2976        assert_eq!(selector.slot_accums[later_slot], 0.0);
2977        selector.decr_captured(captured);
2978        assert_eq!(selector.slot_accums[captured_slot], 0.0);
2979        assert_eq!(
2980            selector.entry_ids[captured_slot]
2981                .iter()
2982                .position(|&id| id == captured.entry_id),
2983            Some(1)
2984        );
2985    }
2986
2987    #[test]
2988    fn nelder_mead_copy_keeps_centroid_consistent() {
2989        let mut nm = NMSeqOpt::new(1, vec![-10.0], vec![20.0]);
2990        for i in 0..nm.m {
2991            nm.x[i][0] = i as f64;
2992            nm.y[i] = i as f64;
2993        }
2994        nm.calc_cent();
2995        assert_eq!(nm.xhi, nm.m - 1);
2996        nm.copy(&[-1.0], -1.0);
2997
2998        let expected =
2999            nm.x.iter()
3000                .enumerate()
3001                .filter(|(i, _)| *i != nm.xhi)
3002                .map(|(_, x)| x[0])
3003                .sum::<f64>()
3004                * nm.m1i;
3005        assert!((nm.x0[0] - expected).abs() < 1e-14);
3006    }
3007
3008    #[test]
3009    fn dynamic_population_size_stays_within_upstream_limits() {
3010        let params = BiteParams {
3011            popsize: 12,
3012            max_evaluations: 600,
3013            seed: 16,
3014            ..Default::default()
3015        };
3016        let mut opt = BiteOpt::new(&[-2.0; 3], &[2.0; 3], None, &params);
3017        let mut next_good = -1.0;
3018        while opt.evaluations < params.max_evaluations {
3019            let xs = opt.ask(1);
3020            let cost = if opt.evaluations.is_multiple_of(2) {
3021                next_good -= 1.0;
3022                next_good
3023            } else {
3024                BAD_COST
3025            };
3026            opt.tell(&vec![cost; xs.len()]);
3027            assert!(opt.pop.cur_pop_size >= opt.pop_size / 2);
3028            assert!(opt.pop.cur_pop_size <= opt.pop_size);
3029        }
3030    }
3031
3032    #[test]
3033    fn secondary_optimizers_make_progress() {
3034        let objective = sphere as fn(&[f64]) -> f64;
3035        let mut rnd = BiteRnd::new(17);
3036        let mut spher = SpherOpt::new(3, vec![-5.0; 3], vec![10.0; 3], 17);
3037        spher.init(&mut rnd, None, 1.0);
3038        for _ in 0..2_000 {
3039            spher.optimize(&mut rnd, &objective);
3040        }
3041        assert!(spher.best_cost < 1e-5, "spher: {}", spher.best_cost);
3042
3043        let mut nm = NMSeqOpt::new(3, vec![-5.0; 3], vec![10.0; 3]);
3044        nm.init(&mut rnd, None, 1.0);
3045        for _ in 0..2_000 {
3046            nm.optimize(&mut rnd, &objective);
3047        }
3048        assert!(nm.best_cost < 1e-8, "nelder-mead: {}", nm.best_cost);
3049    }
3050}