Skip to main content

pounce_linalg/
dense_vector.rs

1//! Dense (contiguous) vector — port of `LinAlg/IpDenseVector.{hpp,cpp}`.
2//!
3//! Matches upstream's homogeneous-value optimization: when every entry
4//! has the same value, only the scalar is stored, and the underlying
5//! `Vec<Number>` is empty. Mutating any single element materializes
6//! the storage (`set_values_from_scalar`) and clears the homogeneous
7//! flag, exactly as in `DenseVector::Values()`.
8
9use crate::blas1;
10use crate::vector::{Vector, VectorCache};
11use pounce_common::tagged::{Tag, TaggedObject};
12use pounce_common::types::{Index, Number};
13use std::any::Any;
14use std::cell::RefCell;
15use std::collections::BTreeMap;
16use std::rc::Rc;
17
18/// Vector space for `DenseVector`. Owns the dimension and any metadata
19/// (string / integer / numeric maps keyed by tag string, mirroring
20/// upstream `DenseVectorSpace::{string,integer,numeric}_meta_data_`).
21#[derive(Debug, Default)]
22pub struct DenseVectorSpace {
23    dim: Index,
24    string_meta: RefCell<BTreeMap<String, Vec<String>>>,
25    integer_meta: RefCell<BTreeMap<String, Vec<Index>>>,
26    numeric_meta: RefCell<BTreeMap<String, Vec<Number>>>,
27}
28
29impl DenseVectorSpace {
30    pub fn new(dim: Index) -> Rc<Self> {
31        Rc::new(Self {
32            dim,
33            string_meta: RefCell::new(BTreeMap::new()),
34            integer_meta: RefCell::new(BTreeMap::new()),
35            numeric_meta: RefCell::new(BTreeMap::new()),
36        })
37    }
38
39    pub fn dim(&self) -> Index {
40        self.dim
41    }
42
43    pub fn make_new_dense(self: &Rc<Self>) -> DenseVector {
44        DenseVector::new(Rc::clone(self))
45    }
46
47    pub fn has_string_meta(&self, tag: &str) -> bool {
48        self.string_meta.borrow().contains_key(tag)
49    }
50    pub fn set_string_meta(&self, tag: &str, data: Vec<String>) {
51        self.string_meta.borrow_mut().insert(tag.to_string(), data);
52    }
53    pub fn get_string_meta(&self, tag: &str) -> Option<Vec<String>> {
54        self.string_meta.borrow().get(tag).cloned()
55    }
56
57    pub fn has_integer_meta(&self, tag: &str) -> bool {
58        self.integer_meta.borrow().contains_key(tag)
59    }
60    pub fn set_integer_meta(&self, tag: &str, data: Vec<Index>) {
61        self.integer_meta.borrow_mut().insert(tag.to_string(), data);
62    }
63    pub fn get_integer_meta(&self, tag: &str) -> Option<Vec<Index>> {
64        self.integer_meta.borrow().get(tag).cloned()
65    }
66
67    pub fn has_numeric_meta(&self, tag: &str) -> bool {
68        self.numeric_meta.borrow().contains_key(tag)
69    }
70    pub fn set_numeric_meta(&self, tag: &str, data: Vec<Number>) {
71        self.numeric_meta.borrow_mut().insert(tag.to_string(), data);
72    }
73    pub fn get_numeric_meta(&self, tag: &str) -> Option<Vec<Number>> {
74        self.numeric_meta.borrow().get(tag).cloned()
75    }
76}
77
78/// Dense vector — port of `IpDenseVector`.
79#[derive(Debug)]
80pub struct DenseVector {
81    space: Rc<DenseVectorSpace>,
82    cache: VectorCache,
83    /// Storage. Empty until materialized; otherwise length == dim.
84    values: Vec<Number>,
85    initialized: bool,
86    homogeneous: bool,
87    scalar: Number,
88}
89
90impl DenseVector {
91    pub fn new(space: Rc<DenseVectorSpace>) -> Self {
92        let dim = space.dim();
93        // Upstream: `if (Dim() == 0) { initialized_ = true; homogeneous_ = true; scalar_ = 0.; }`
94        let (initialized, homogeneous, scalar) = if dim == 0 {
95            (true, true, 0.0)
96        } else {
97            (false, false, 0.0)
98        };
99        Self {
100            space,
101            cache: VectorCache::new(),
102            values: Vec::new(),
103            initialized,
104            homogeneous,
105            scalar,
106        }
107    }
108
109    pub fn space(&self) -> &Rc<DenseVectorSpace> {
110        &self.space
111    }
112
113    pub fn is_homogeneous(&self) -> bool {
114        self.homogeneous
115    }
116
117    pub fn scalar(&self) -> Number {
118        debug_assert!(self.homogeneous);
119        self.scalar
120    }
121
122    pub fn is_initialized(&self) -> bool {
123        self.initialized
124    }
125
126    /// Read-only slice into materialized values. Panics if currently
127    /// homogeneous — mirrors upstream's DBG_ASSERT in
128    /// `DenseVector::Values() const`. Use `expanded_values` to always
129    /// get a slice.
130    pub fn values(&self) -> &[Number] {
131        debug_assert!(self.initialized && !self.homogeneous);
132        &self.values
133    }
134
135    /// Mutable slice. Materializes a homogeneous vector first and
136    /// bumps the change tag, matching upstream's non-const `Values()`.
137    pub fn values_mut(&mut self) -> &mut [Number] {
138        if self.initialized && self.homogeneous {
139            self.materialize_from_scalar();
140        }
141        self.ensure_storage();
142        self.cache.bump();
143        self.initialized = true;
144        self.homogeneous = false;
145        &mut self.values
146    }
147
148    /// Always returns a fully-materialized slice. Allocates a copy if
149    /// the vector is homogeneous (upstream caches this in
150    /// `expanded_values_`; we just allocate on the fly).
151    pub fn expanded_values(&self) -> Vec<Number> {
152        if self.homogeneous {
153            vec![self.scalar; self.space.dim() as usize]
154        } else {
155            self.values.clone()
156        }
157    }
158
159    pub fn set_values(&mut self, x: &[Number]) {
160        let dim = self.space.dim() as usize;
161        assert_eq!(x.len(), dim);
162        self.ensure_storage();
163        self.values[..dim].copy_from_slice(x);
164        self.initialized = true;
165        self.homogeneous = false;
166        self.cache.bump();
167    }
168
169    /// Equivalent to upstream `DenseVector::CopyToPos`.
170    pub fn copy_to_pos(&mut self, pos: Index, x: &dyn Vector) {
171        let pos = pos as usize;
172        let dim_x = x.dim() as usize;
173        assert!(pos + dim_x <= self.space.dim() as usize);
174        let dense_x = downcast_dense(x);
175        if self.homogeneous && self.initialized {
176            self.materialize_from_scalar();
177        }
178        self.ensure_storage();
179        self.homogeneous = false;
180        if dense_x.homogeneous {
181            for v in &mut self.values[pos..pos + dim_x] {
182                *v = dense_x.scalar;
183            }
184        } else {
185            self.values[pos..pos + dim_x].copy_from_slice(&dense_x.values[..dim_x]);
186        }
187        self.initialized = true;
188        self.cache.bump();
189    }
190
191    /// Equivalent to upstream `DenseVector::CopyFromPos`.
192    pub fn copy_from_pos(&mut self, pos: Index, x: &dyn Vector) {
193        let pos = pos as usize;
194        let dim = self.space.dim() as usize;
195        assert!(pos + dim <= x.dim() as usize);
196        let dense_x = downcast_dense(x);
197        if dense_x.homogeneous {
198            self.set(dense_x.scalar);
199        } else {
200            self.ensure_storage();
201            self.values[..dim].copy_from_slice(&dense_x.values[pos..pos + dim]);
202            self.initialized = true;
203            self.homogeneous = false;
204            self.cache.bump();
205        }
206    }
207
208    pub fn ensure_storage(&mut self) {
209        let dim = self.space.dim() as usize;
210        if self.values.len() != dim {
211            self.values.resize(dim, 0.0);
212        }
213    }
214
215    /// Upstream `DenseVector::set_values_from_scalar`.
216    fn materialize_from_scalar(&mut self) {
217        debug_assert!(self.homogeneous);
218        let dim = self.space.dim() as usize;
219        self.values.clear();
220        self.values.resize(dim, self.scalar);
221        self.homogeneous = false;
222        self.initialized = true;
223    }
224}
225
226fn downcast_dense(x: &dyn Vector) -> &DenseVector {
227    match x.as_any().downcast_ref::<DenseVector>() {
228        Some(v) => v,
229        None => panic!(
230            "Vector argument is not a DenseVector — mixed-type linear algebra is not supported in v1.0"
231        ),
232    }
233}
234
235impl TaggedObject for DenseVector {
236    fn get_tag(&self) -> Tag {
237        self.cache.tag()
238    }
239}
240
241/// Allocation-free read view of an elementwise-operand vector.
242///
243/// The three cases are exactly what the dense kernels below have to
244/// cope with: an operand whose coefficient is zero (never read), a
245/// homogeneous vector (one scalar standing in for all `n` entries),
246/// and a materialized dense vector. Broadcasting the scalar rather
247/// than expanding it into `n` copies is what keeps the homogeneous
248/// case free.
249#[derive(Clone, Copy)]
250enum Operand<'a> {
251    Zero,
252    Scalar(Number),
253    Dense(&'a [Number]),
254}
255
256impl<'a> Operand<'a> {
257    fn of(dv: Option<&'a DenseVector>, n: usize) -> Self {
258        match dv {
259            None => Operand::Zero,
260            Some(d) if d.homogeneous => Operand::Scalar(d.scalar),
261            Some(d) => Operand::Dense(&d.values[..n]),
262        }
263    }
264
265    #[inline(always)]
266    fn at(self, i: usize) -> Number {
267        match self {
268            Operand::Zero => 0.0,
269            Operand::Scalar(s) => s,
270            Operand::Dense(v) => v[i],
271        }
272    }
273}
274
275impl Vector for DenseVector {
276    fn dim(&self) -> Index {
277        self.space.dim()
278    }
279
280    fn cache(&self) -> &VectorCache {
281        &self.cache
282    }
283
284    fn make_new(&self) -> Box<dyn Vector> {
285        Box::new(DenseVector::new(Rc::clone(&self.space)))
286    }
287
288    fn as_any(&self) -> &dyn Any {
289        self
290    }
291
292    fn as_any_mut(&mut self) -> &mut dyn Any {
293        self
294    }
295
296    fn as_tagged(&self) -> &dyn TaggedObject {
297        self
298    }
299
300    fn as_dyn_vector(&self) -> &dyn Vector {
301        self
302    }
303
304    fn copy_impl(&mut self, x: &dyn Vector) {
305        let dx = downcast_dense(x);
306        debug_assert!(dx.initialized);
307        debug_assert_eq!(self.space.dim(), dx.space.dim());
308        self.homogeneous = dx.homogeneous;
309        if dx.homogeneous {
310            self.scalar = dx.scalar;
311            self.values.clear();
312        } else {
313            self.ensure_storage();
314            let dim = self.space.dim() as usize;
315            self.values[..dim].copy_from_slice(&dx.values[..dim]);
316        }
317        self.initialized = true;
318    }
319
320    fn scal_impl(&mut self, alpha: Number) {
321        debug_assert!(self.initialized);
322        if self.homogeneous {
323            self.scalar *= alpha;
324        } else {
325            blas1::scal(alpha, &mut self.values, 1, self.space.dim());
326        }
327    }
328
329    fn axpy_impl(&mut self, alpha: Number, x: &dyn Vector) {
330        debug_assert!(self.initialized);
331        let dx = downcast_dense(x);
332        debug_assert!(dx.initialized);
333        let dim = self.space.dim();
334        if dim == 0 {
335            return;
336        }
337        if self.homogeneous {
338            if dx.homogeneous {
339                self.scalar += alpha * dx.scalar;
340            } else {
341                let s0 = self.scalar;
342                self.homogeneous = false;
343                self.ensure_storage();
344                let n = dim as usize;
345                for i in 0..n {
346                    self.values[i] = s0 + alpha * dx.values[i];
347                }
348            }
349        } else if dx.homogeneous {
350            if dx.scalar != 0.0 {
351                let inc = alpha * dx.scalar;
352                for v in &mut self.values[..dim as usize] {
353                    *v += inc;
354                }
355            }
356        } else {
357            blas1::axpy(alpha, &dx.values, 1, &mut self.values, 1, dim);
358        }
359    }
360
361    fn dot_impl(&self, x: &dyn Vector) -> Number {
362        debug_assert!(self.initialized);
363        let dx = downcast_dense(x);
364        debug_assert!(dx.initialized);
365        let dim = self.space.dim();
366        let n = dim as usize;
367        if dim == 0 {
368            return 0.0;
369        }
370        match (self.homogeneous, dx.homogeneous) {
371            (true, true) => (n as Number) * self.scalar * dx.scalar,
372            (true, false) => {
373                // Σ scalar * dx_i = scalar * Σ dx_i
374                let mut s = 0.0;
375                for v in &dx.values[..n] {
376                    s += self.scalar * v;
377                }
378                s
379            }
380            (false, true) => {
381                let mut s = 0.0;
382                for v in &self.values[..n] {
383                    s += dx.scalar * v;
384                }
385                s
386            }
387            (false, false) => blas1::dot(&self.values, 1, &dx.values, 1, dim),
388        }
389    }
390
391    fn nrm2_impl(&self) -> Number {
392        debug_assert!(self.initialized);
393        if self.homogeneous {
394            (self.space.dim() as Number).sqrt() * self.scalar.abs()
395        } else {
396            blas1::nrm2(&self.values, 1, self.space.dim())
397        }
398    }
399
400    fn asum_impl(&self) -> Number {
401        debug_assert!(self.initialized);
402        if self.homogeneous {
403            (self.space.dim() as Number) * self.scalar.abs()
404        } else {
405            blas1::asum(&self.values, 1, self.space.dim())
406        }
407    }
408
409    fn amax_impl(&self) -> Number {
410        debug_assert!(self.initialized);
411        if self.space.dim() == 0 {
412            return 0.0;
413        }
414        if self.homogeneous {
415            return self.scalar.abs();
416        }
417        let i = blas1::iamax(&self.values, 1, self.space.dim()) as usize;
418        self.values[i].abs()
419    }
420
421    fn set_impl(&mut self, value: Number) {
422        self.initialized = true;
423        self.homogeneous = true;
424        self.scalar = value;
425        // Free dense storage like upstream.
426        self.values.clear();
427        self.values.shrink_to_fit();
428    }
429
430    fn element_wise_divide_impl(&mut self, x: &dyn Vector) {
431        debug_assert!(self.initialized);
432        let dx = downcast_dense(x);
433        debug_assert!(dx.initialized);
434        let n = self.space.dim() as usize;
435        if n == 0 {
436            return;
437        }
438        match (self.homogeneous, dx.homogeneous) {
439            (true, true) => self.scalar /= dx.scalar,
440            (true, false) => {
441                let s0 = self.scalar;
442                self.homogeneous = false;
443                self.ensure_storage();
444                for i in 0..n {
445                    self.values[i] = s0 / dx.values[i];
446                }
447            }
448            (false, true) => {
449                for v in &mut self.values[..n] {
450                    *v /= dx.scalar;
451                }
452            }
453            (false, false) => {
454                for i in 0..n {
455                    self.values[i] /= dx.values[i];
456                }
457            }
458        }
459    }
460
461    fn element_wise_multiply_impl(&mut self, x: &dyn Vector) {
462        debug_assert!(self.initialized);
463        let dx = downcast_dense(x);
464        debug_assert!(dx.initialized);
465        let n = self.space.dim() as usize;
466        if n == 0 {
467            return;
468        }
469        match (self.homogeneous, dx.homogeneous) {
470            (true, true) => self.scalar *= dx.scalar,
471            (true, false) => {
472                let s0 = self.scalar;
473                self.homogeneous = false;
474                self.ensure_storage();
475                for i in 0..n {
476                    self.values[i] = s0 * dx.values[i];
477                }
478            }
479            (false, true) => {
480                if dx.scalar != 1.0 {
481                    for v in &mut self.values[..n] {
482                        *v *= dx.scalar;
483                    }
484                }
485            }
486            (false, false) => {
487                for i in 0..n {
488                    self.values[i] *= dx.values[i];
489                }
490            }
491        }
492    }
493
494    fn element_wise_select_impl(&mut self, x: &dyn Vector) {
495        debug_assert!(self.initialized);
496        let dx = downcast_dense(x);
497        debug_assert!(dx.initialized);
498        let n = self.space.dim() as usize;
499        if n == 0 {
500            return;
501        }
502        if self.homogeneous {
503            if self.scalar == 0.0 {
504                return;
505            }
506            if dx.homogeneous {
507                self.scalar *= dx.scalar;
508            } else {
509                let s0 = self.scalar;
510                self.homogeneous = false;
511                self.ensure_storage();
512                for i in 0..n {
513                    self.values[i] = s0 * dx.values[i];
514                }
515            }
516        } else if dx.homogeneous {
517            if dx.scalar != 1.0 {
518                for v in &mut self.values[..n] {
519                    if *v > 0.0 {
520                        *v = dx.scalar;
521                    } else if *v < 0.0 {
522                        *v = -dx.scalar;
523                    }
524                }
525            }
526        } else {
527            for i in 0..n {
528                if self.values[i] > 0.0 {
529                    self.values[i] = dx.values[i];
530                } else if self.values[i] < 0.0 {
531                    self.values[i] = -dx.values[i];
532                }
533            }
534        }
535    }
536
537    fn element_wise_max_impl(&mut self, x: &dyn Vector) {
538        debug_assert!(self.initialized);
539        let dx = downcast_dense(x);
540        debug_assert!(dx.initialized);
541        let n = self.space.dim() as usize;
542        if n == 0 {
543            return;
544        }
545        match (self.homogeneous, dx.homogeneous) {
546            (true, true) => self.scalar = self.scalar.max(dx.scalar),
547            (true, false) => {
548                let s0 = self.scalar;
549                self.homogeneous = false;
550                self.ensure_storage();
551                for i in 0..n {
552                    self.values[i] = s0.max(dx.values[i]);
553                }
554            }
555            (false, true) => {
556                for v in &mut self.values[..n] {
557                    *v = (*v).max(dx.scalar);
558                }
559            }
560            (false, false) => {
561                for i in 0..n {
562                    self.values[i] = self.values[i].max(dx.values[i]);
563                }
564            }
565        }
566    }
567
568    fn element_wise_min_impl(&mut self, x: &dyn Vector) {
569        debug_assert!(self.initialized);
570        let dx = downcast_dense(x);
571        debug_assert!(dx.initialized);
572        let n = self.space.dim() as usize;
573        if n == 0 {
574            return;
575        }
576        match (self.homogeneous, dx.homogeneous) {
577            (true, true) => self.scalar = self.scalar.min(dx.scalar),
578            (true, false) => {
579                let s0 = self.scalar;
580                self.homogeneous = false;
581                self.ensure_storage();
582                for i in 0..n {
583                    self.values[i] = s0.min(dx.values[i]);
584                }
585            }
586            (false, true) => {
587                for v in &mut self.values[..n] {
588                    *v = (*v).min(dx.scalar);
589                }
590            }
591            (false, false) => {
592                for i in 0..n {
593                    self.values[i] = self.values[i].min(dx.values[i]);
594                }
595            }
596        }
597    }
598
599    fn element_wise_reciprocal_impl(&mut self) {
600        debug_assert!(self.initialized);
601        let n = self.space.dim() as usize;
602        if n == 0 {
603            return;
604        }
605        if self.homogeneous {
606            self.scalar = 1.0 / self.scalar;
607        } else {
608            for v in &mut self.values[..n] {
609                *v = 1.0 / *v;
610            }
611        }
612    }
613
614    fn element_wise_abs_impl(&mut self) {
615        debug_assert!(self.initialized);
616        if self.homogeneous {
617            self.scalar = self.scalar.abs();
618        } else {
619            for v in &mut self.values[..self.space.dim() as usize] {
620                *v = v.abs();
621            }
622        }
623    }
624
625    fn element_wise_sqrt_impl(&mut self) {
626        debug_assert!(self.initialized);
627        if self.homogeneous {
628            self.scalar = self.scalar.sqrt();
629        } else {
630            for v in &mut self.values[..self.space.dim() as usize] {
631                *v = v.sqrt();
632            }
633        }
634    }
635
636    fn element_wise_sgn_impl(&mut self) {
637        debug_assert!(self.initialized);
638        let sgn = |v: Number| -> Number {
639            if v > 0.0 {
640                1.0
641            } else if v < 0.0 {
642                -1.0
643            } else {
644                0.0
645            }
646        };
647        if self.homogeneous {
648            self.scalar = sgn(self.scalar);
649        } else {
650            for v in &mut self.values[..self.space.dim() as usize] {
651                *v = sgn(*v);
652            }
653        }
654    }
655
656    fn add_scalar_impl(&mut self, scalar: Number) {
657        debug_assert!(self.initialized);
658        if self.homogeneous {
659            self.scalar += scalar;
660        } else {
661            for v in &mut self.values[..self.space.dim() as usize] {
662                *v += scalar;
663            }
664        }
665    }
666
667    fn max_impl(&self) -> Number {
668        debug_assert!(self.initialized);
669        let n = self.space.dim() as usize;
670        if n == 0 {
671            return -Number::MAX;
672        }
673        if self.homogeneous {
674            return self.scalar;
675        }
676        let mut m = self.values[0];
677        for &v in &self.values[1..n] {
678            if v > m {
679                m = v;
680            }
681        }
682        m
683    }
684
685    fn min_impl(&self) -> Number {
686        debug_assert!(self.initialized);
687        let n = self.space.dim() as usize;
688        if n == 0 {
689            return Number::MAX;
690        }
691        if self.homogeneous {
692            return self.scalar;
693        }
694        let mut m = self.values[0];
695        for &v in &self.values[1..n] {
696            if v < m {
697                m = v;
698            }
699        }
700        m
701    }
702
703    fn sum_impl(&self) -> Number {
704        debug_assert!(self.initialized);
705        let n = self.space.dim() as usize;
706        if self.homogeneous {
707            (n as Number) * self.scalar
708        } else {
709            let mut s = 0.0;
710            for &v in &self.values[..n] {
711                s += v;
712            }
713            s
714        }
715    }
716
717    fn sum_logs_impl(&self) -> Number {
718        debug_assert!(self.initialized);
719        let n = self.space.dim() as usize;
720        if n == 0 {
721            return 0.0;
722        }
723        if self.homogeneous {
724            (n as Number) * self.scalar.ln()
725        } else {
726            let mut s = 0.0;
727            for &v in &self.values[..n] {
728                s += v.ln();
729            }
730            s
731        }
732    }
733
734    fn frac_to_bound_impl(&self, delta: &dyn Vector, tau: Number) -> Number {
735        debug_assert_eq!(self.space.dim(), delta.dim());
736        debug_assert!(tau >= 0.0);
737        let dd = downcast_dense(delta);
738        let n = self.space.dim() as usize;
739        if n == 0 {
740            return 1.0;
741        }
742        let mut alpha: Number = 1.0;
743        match (self.homogeneous, dd.homogeneous) {
744            (true, true) => {
745                if dd.scalar < 0.0 {
746                    alpha = alpha.min(-tau / dd.scalar * self.scalar);
747                }
748            }
749            (true, false) => {
750                for &d in &dd.values[..n] {
751                    if d < 0.0 {
752                        alpha = alpha.min(-tau / d * self.scalar);
753                    }
754                }
755            }
756            (false, true) => {
757                if dd.scalar < 0.0 {
758                    let f = -tau / dd.scalar;
759                    for &x in &self.values[..n] {
760                        alpha = alpha.min(f * x);
761                    }
762                }
763            }
764            (false, false) => {
765                for i in 0..n {
766                    let d = dd.values[i];
767                    if d < 0.0 {
768                        alpha = alpha.min(-tau / d * self.values[i]);
769                    }
770                }
771            }
772        }
773        debug_assert!(alpha >= 0.0);
774        alpha
775    }
776
777    fn add_two_vectors_impl(
778        &mut self,
779        a: Number,
780        v1: &dyn Vector,
781        b: Number,
782        v2: &dyn Vector,
783        c: Number,
784    ) {
785        let n = self.space.dim() as usize;
786        if n == 0 {
787            debug_assert!(self.initialized);
788            return;
789        }
790        let dv1 = if a != 0.0 {
791            Some(downcast_dense(v1))
792        } else {
793            None
794        };
795        let dv2 = if b != 0.0 {
796            Some(downcast_dense(v2))
797        } else {
798            None
799        };
800        let homog_v1 = dv1.map(|d| d.homogeneous).unwrap_or(true);
801        let homog_v2 = dv2.map(|d| d.homogeneous).unwrap_or(true);
802        let s_v1 = dv1.map(|d| d.scalar).unwrap_or(0.0);
803        let s_v2 = dv2.map(|d| d.scalar).unwrap_or(0.0);
804
805        // All-homogeneous fast path — result stays homogeneous.
806        if (c == 0.0 || self.homogeneous) && homog_v1 && homog_v2 {
807            let prev = if c == 0.0 { 0.0 } else { c * self.scalar };
808            self.scalar = prev + a * s_v1 + b * s_v2;
809            self.homogeneous = true;
810            self.initialized = true;
811            return;
812        }
813
814        // Materialize self if needed. With c == 0 we are about to overwrite.
815        if c == 0.0 {
816            self.ensure_storage();
817            self.homogeneous = false;
818        } else if self.homogeneous {
819            self.materialize_from_scalar();
820        }
821
822        // Read the operands in place. These used to be materialized
823        // into two fresh `Vec`s per call. That was a convenience, not
824        // a requirement: `dv1`/`dv2` borrow from the `v1`/`v2`
825        // arguments and never from `self`, so no copy is needed to
826        // satisfy the aliasing rules. The claim that this branch was
827        // "rare in practice" did not hold — the quality-function mu
828        // oracle reaches it for every block update of every trial
829        // sigma, so on a large model the operand copies were the
830        // dominant cost of the adaptive barrier update (pounce#749).
831        let v1_op = Operand::of(dv1, n);
832        let v2_op = Operand::of(dv2, n);
833
834        // Single fused expression. IEEE multiplication by 0 / 1 / -1
835        // is exact, so this is bit-equivalent to upstream's 64-case
836        // dispatch in `IpDenseVector.cpp:843-1322`.
837        let out = &mut self.values[..n];
838        if c == 0.0 {
839            for (i, o) in out.iter_mut().enumerate() {
840                *o = a * v1_op.at(i) + b * v2_op.at(i);
841            }
842        } else {
843            for (i, o) in out.iter_mut().enumerate() {
844                *o = a * v1_op.at(i) + b * v2_op.at(i) + c * *o;
845            }
846        }
847        self.initialized = true;
848    }
849
850    fn add_vector_quotient_impl(&mut self, a: Number, z: &dyn Vector, s: &dyn Vector, c: Number) {
851        debug_assert_eq!(self.space.dim(), z.dim());
852        debug_assert_eq!(self.space.dim(), s.dim());
853        let dz = downcast_dense(z);
854        let ds = downcast_dense(s);
855        debug_assert!(dz.initialized && ds.initialized);
856        let n = self.space.dim() as usize;
857        if n == 0 {
858            return;
859        }
860        let homog_z = dz.homogeneous;
861        let homog_s = ds.homogeneous;
862        if (c == 0.0 || self.homogeneous) && homog_z && homog_s {
863            self.scalar = if c == 0.0 {
864                a * dz.scalar / ds.scalar
865            } else {
866                c * self.scalar + a * dz.scalar / ds.scalar
867            };
868            self.initialized = true;
869            self.homogeneous = true;
870            self.values.clear();
871            return;
872        }
873        // Materialize self if needed.
874        if c == 0.0 {
875            self.ensure_storage();
876            self.homogeneous = false;
877        } else if self.homogeneous {
878            self.materialize_from_scalar();
879        }
880        // In place, for the same reason as `add_two_vectors_impl`
881        // above: `dz`/`ds` borrow from the arguments, so the two
882        // full-length temporaries bought nothing (pounce#749).
883        let z_op = Operand::of(Some(dz), n);
884        let s_op = Operand::of(Some(ds), n);
885        let out = &mut self.values[..n];
886        if c == 0.0 {
887            for (i, o) in out.iter_mut().enumerate() {
888                *o = a * z_op.at(i) / s_op.at(i);
889            }
890        } else {
891            for (i, o) in out.iter_mut().enumerate() {
892                *o = c * *o + a * z_op.at(i) / s_op.at(i);
893            }
894        }
895        self.initialized = true;
896        self.homogeneous = false;
897    }
898}
899
900#[cfg(test)]
901mod tests {
902    use super::*;
903
904    fn vec_of(space: &Rc<DenseVectorSpace>, vals: &[Number]) -> DenseVector {
905        let mut v = DenseVector::new(Rc::clone(space));
906        v.set_values(vals);
907        v
908    }
909
910    #[test]
911    fn axpy_basic() {
912        let s = DenseVectorSpace::new(3);
913        let x = vec_of(&s, &[1.0, 2.0, 3.0]);
914        let mut y = vec_of(&s, &[10.0, 20.0, 30.0]);
915        y.axpy(2.0, &x);
916        assert_eq!(y.values(), &[12.0, 24.0, 36.0]);
917    }
918
919    #[test]
920    fn dot_homogeneous_pair() {
921        let s = DenseVectorSpace::new(4);
922        let mut x = DenseVector::new(Rc::clone(&s));
923        x.set(2.0); // homogeneous 2
924        let mut y = DenseVector::new(Rc::clone(&s));
925        y.set(3.0); // homogeneous 3
926        // 4 entries of 2*3 = 24
927        assert_eq!(x.dot(&y), 24.0);
928    }
929
930    #[test]
931    fn dot_mixed_homog_dense() {
932        let s = DenseVectorSpace::new(3);
933        let mut x = DenseVector::new(Rc::clone(&s));
934        x.set(2.0);
935        let y = vec_of(&s, &[1.0, 2.0, 3.0]);
936        // 2*(1+2+3) = 12
937        assert_eq!(x.dot(&y), 12.0);
938        assert_eq!(y.dot(&x), 12.0);
939    }
940
941    #[test]
942    fn nrm2_homogeneous_uses_sqrt_n() {
943        let s = DenseVectorSpace::new(4);
944        let mut x = DenseVector::new(Rc::clone(&s));
945        x.set(3.0);
946        // sqrt(4) * 3 = 6
947        assert!((x.nrm2() - 6.0).abs() < 1e-15);
948    }
949
950    #[test]
951    fn nrm2_cache_invalidated_by_mutation() {
952        let s = DenseVectorSpace::new(2);
953        let mut x = vec_of(&s, &[3.0, 4.0]);
954        assert_eq!(x.nrm2(), 5.0);
955        x.scal(2.0);
956        assert!((x.nrm2() - 10.0).abs() < 1e-15);
957    }
958
959    #[test]
960    fn dot_cache_hits_after_first_call() {
961        let s = DenseVectorSpace::new(3);
962        let x = vec_of(&s, &[1.0, 2.0, 3.0]);
963        let y = vec_of(&s, &[1.0, 1.0, 1.0]);
964        assert_eq!(x.dot(&y), 6.0);
965        // Second call should be cached but still produce the same value.
966        assert_eq!(x.dot(&y), 6.0);
967    }
968
969    #[test]
970    fn dot_self_uses_nrm2_squared_path() {
971        let s = DenseVectorSpace::new(2);
972        let x = vec_of(&s, &[3.0, 4.0]);
973        // Pass x as both args — cache shortcut should compute 5*5 = 25.
974        assert_eq!(x.dot(&x), 25.0);
975    }
976
977    #[test]
978    fn add_two_vectors_all_homogeneous() {
979        let s = DenseVectorSpace::new(5);
980        let mut y = DenseVector::new(Rc::clone(&s));
981        y.set(1.0);
982        let mut v1 = DenseVector::new(Rc::clone(&s));
983        v1.set(2.0);
984        let mut v2 = DenseVector::new(Rc::clone(&s));
985        v2.set(3.0);
986        // y = 4*v1 + 5*v2 + 0.5*y = 4*2 + 5*3 + 0.5 = 8 + 15 + 0.5 = 23.5
987        y.add_two_vectors(4.0, &v1, 5.0, &v2, 0.5);
988        assert!(y.is_homogeneous());
989        assert_eq!(y.scalar(), 23.5);
990    }
991
992    #[test]
993    fn add_two_vectors_mixed_dense_overrides_homog() {
994        let s = DenseVectorSpace::new(3);
995        let mut y = DenseVector::new(Rc::clone(&s));
996        y.set(0.0);
997        let v1 = vec_of(&s, &[1.0, 2.0, 3.0]);
998        let v2 = vec_of(&s, &[10.0, 10.0, 10.0]);
999        // y = 1*v1 + 1*v2 + 0*y = [11, 12, 13]
1000        y.add_two_vectors(1.0, &v1, 1.0, &v2, 0.0);
1001        assert!(!y.is_homogeneous());
1002        assert_eq!(y.values(), &[11.0, 12.0, 13.0]);
1003    }
1004
1005    #[test]
1006    fn frac_to_bound_basic() {
1007        let s = DenseVectorSpace::new(3);
1008        let x = vec_of(&s, &[1.0, 2.0, 3.0]);
1009        let delta = vec_of(&s, &[-2.0, 1.0, -1.5]);
1010        // negative components: i=0 → -tau/-2 * 1 = tau/2
1011        //                       i=2 → -tau/-1.5 * 3 = tau*2
1012        // alpha = min(1, tau/2, tau*2). tau=1 → alpha = 0.5
1013        let alpha = x.frac_to_bound(&delta, 1.0);
1014        assert!((alpha - 0.5).abs() < 1e-15);
1015    }
1016
1017    #[test]
1018    fn element_wise_divide_homog_dense() {
1019        let s = DenseVectorSpace::new(3);
1020        let mut y = DenseVector::new(Rc::clone(&s));
1021        y.set(6.0);
1022        let x = vec_of(&s, &[1.0, 2.0, 3.0]);
1023        y.element_wise_divide(&x);
1024        assert!(!y.is_homogeneous());
1025        assert_eq!(y.values(), &[6.0, 3.0, 2.0]);
1026    }
1027
1028    #[test]
1029    fn element_wise_sgn_handles_all_three_signs() {
1030        let s = DenseVectorSpace::new(3);
1031        let mut x = vec_of(&s, &[-2.5, 0.0, 7.0]);
1032        x.element_wise_sgn();
1033        assert_eq!(x.values(), &[-1.0, 0.0, 1.0]);
1034    }
1035
1036    #[test]
1037    fn sum_and_max_min_homogeneous() {
1038        let s = DenseVectorSpace::new(4);
1039        let mut x = DenseVector::new(Rc::clone(&s));
1040        x.set(2.5);
1041        assert_eq!(x.sum(), 10.0);
1042        assert_eq!(x.max(), 2.5);
1043        assert_eq!(x.min(), 2.5);
1044    }
1045
1046    #[test]
1047    fn has_valid_numbers_detects_nan() {
1048        let s = DenseVectorSpace::new(3);
1049        let bad = vec_of(&s, &[1.0, Number::NAN, 2.0]);
1050        assert!(!bad.has_valid_numbers());
1051        let good = vec_of(&s, &[1.0, 2.0, 3.0]);
1052        assert!(good.has_valid_numbers());
1053    }
1054
1055    #[test]
1056    fn copy_to_pos_pastes_into_subrange() {
1057        let s_big = DenseVectorSpace::new(5);
1058        let s_small = DenseVectorSpace::new(2);
1059        let mut y = DenseVector::new(Rc::clone(&s_big));
1060        y.set(0.0);
1061        let x = vec_of(&s_small, &[7.0, 8.0]);
1062        y.copy_to_pos(2, &x);
1063        assert_eq!(y.values(), &[0.0, 0.0, 7.0, 8.0, 0.0]);
1064    }
1065
1066    #[test]
1067    fn make_new_copy_clones_values() {
1068        let s = DenseVectorSpace::new(3);
1069        let x = vec_of(&s, &[1.0, 2.0, 3.0]);
1070        let y = x.make_new_copy();
1071        let dy = y.as_any().downcast_ref::<DenseVector>().unwrap();
1072        assert_eq!(dy.values(), &[1.0, 2.0, 3.0]);
1073    }
1074
1075    #[test]
1076    fn add_vector_quotient_all_homogeneous() {
1077        let s = DenseVectorSpace::new(4);
1078        let mut y = DenseVector::new(Rc::clone(&s));
1079        y.set(1.0);
1080        let mut z = DenseVector::new(Rc::clone(&s));
1081        z.set(6.0);
1082        let mut sd = DenseVector::new(Rc::clone(&s));
1083        sd.set(2.0);
1084        // y = 2 * z/sd + 0.5 * y = 2*6/2 + 0.5 = 6.5
1085        y.add_vector_quotient(2.0, &z, &sd, 0.5);
1086        assert!(y.is_homogeneous());
1087        assert_eq!(y.scalar(), 6.5);
1088    }
1089
1090    #[test]
1091    fn dim_zero_is_consistent() {
1092        let s = DenseVectorSpace::new(0);
1093        let x = DenseVector::new(Rc::clone(&s));
1094        assert!(x.is_initialized());
1095        assert!(x.is_homogeneous());
1096        assert_eq!(x.nrm2(), 0.0);
1097    }
1098}