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
241impl Vector for DenseVector {
242    fn dim(&self) -> Index {
243        self.space.dim()
244    }
245
246    fn cache(&self) -> &VectorCache {
247        &self.cache
248    }
249
250    fn make_new(&self) -> Box<dyn Vector> {
251        Box::new(DenseVector::new(Rc::clone(&self.space)))
252    }
253
254    fn as_any(&self) -> &dyn Any {
255        self
256    }
257
258    fn as_any_mut(&mut self) -> &mut dyn Any {
259        self
260    }
261
262    fn as_tagged(&self) -> &dyn TaggedObject {
263        self
264    }
265
266    fn as_dyn_vector(&self) -> &dyn Vector {
267        self
268    }
269
270    fn copy_impl(&mut self, x: &dyn Vector) {
271        let dx = downcast_dense(x);
272        debug_assert!(dx.initialized);
273        debug_assert_eq!(self.space.dim(), dx.space.dim());
274        self.homogeneous = dx.homogeneous;
275        if dx.homogeneous {
276            self.scalar = dx.scalar;
277            self.values.clear();
278        } else {
279            self.ensure_storage();
280            let dim = self.space.dim() as usize;
281            self.values[..dim].copy_from_slice(&dx.values[..dim]);
282        }
283        self.initialized = true;
284    }
285
286    fn scal_impl(&mut self, alpha: Number) {
287        debug_assert!(self.initialized);
288        if self.homogeneous {
289            self.scalar *= alpha;
290        } else {
291            blas1::scal(alpha, &mut self.values, 1, self.space.dim());
292        }
293    }
294
295    fn axpy_impl(&mut self, alpha: Number, x: &dyn Vector) {
296        debug_assert!(self.initialized);
297        let dx = downcast_dense(x);
298        debug_assert!(dx.initialized);
299        let dim = self.space.dim();
300        if dim == 0 {
301            return;
302        }
303        if self.homogeneous {
304            if dx.homogeneous {
305                self.scalar += alpha * dx.scalar;
306            } else {
307                let s0 = self.scalar;
308                self.homogeneous = false;
309                self.ensure_storage();
310                let n = dim as usize;
311                for i in 0..n {
312                    self.values[i] = s0 + alpha * dx.values[i];
313                }
314            }
315        } else if dx.homogeneous {
316            if dx.scalar != 0.0 {
317                let inc = alpha * dx.scalar;
318                for v in &mut self.values[..dim as usize] {
319                    *v += inc;
320                }
321            }
322        } else {
323            blas1::axpy(alpha, &dx.values, 1, &mut self.values, 1, dim);
324        }
325    }
326
327    fn dot_impl(&self, x: &dyn Vector) -> Number {
328        debug_assert!(self.initialized);
329        let dx = downcast_dense(x);
330        debug_assert!(dx.initialized);
331        let dim = self.space.dim();
332        let n = dim as usize;
333        if dim == 0 {
334            return 0.0;
335        }
336        match (self.homogeneous, dx.homogeneous) {
337            (true, true) => (n as Number) * self.scalar * dx.scalar,
338            (true, false) => {
339                // Σ scalar * dx_i = scalar * Σ dx_i
340                let mut s = 0.0;
341                for v in &dx.values[..n] {
342                    s += self.scalar * v;
343                }
344                s
345            }
346            (false, true) => {
347                let mut s = 0.0;
348                for v in &self.values[..n] {
349                    s += dx.scalar * v;
350                }
351                s
352            }
353            (false, false) => blas1::dot(&self.values, 1, &dx.values, 1, dim),
354        }
355    }
356
357    fn nrm2_impl(&self) -> Number {
358        debug_assert!(self.initialized);
359        if self.homogeneous {
360            (self.space.dim() as Number).sqrt() * self.scalar.abs()
361        } else {
362            blas1::nrm2(&self.values, 1, self.space.dim())
363        }
364    }
365
366    fn asum_impl(&self) -> Number {
367        debug_assert!(self.initialized);
368        if self.homogeneous {
369            (self.space.dim() as Number) * self.scalar.abs()
370        } else {
371            blas1::asum(&self.values, 1, self.space.dim())
372        }
373    }
374
375    fn amax_impl(&self) -> Number {
376        debug_assert!(self.initialized);
377        if self.space.dim() == 0 {
378            return 0.0;
379        }
380        if self.homogeneous {
381            return self.scalar.abs();
382        }
383        let i = blas1::iamax(&self.values, 1, self.space.dim()) as usize;
384        self.values[i].abs()
385    }
386
387    fn set_impl(&mut self, value: Number) {
388        self.initialized = true;
389        self.homogeneous = true;
390        self.scalar = value;
391        // Free dense storage like upstream.
392        self.values.clear();
393        self.values.shrink_to_fit();
394    }
395
396    fn element_wise_divide_impl(&mut self, x: &dyn Vector) {
397        debug_assert!(self.initialized);
398        let dx = downcast_dense(x);
399        debug_assert!(dx.initialized);
400        let n = self.space.dim() as usize;
401        if n == 0 {
402            return;
403        }
404        match (self.homogeneous, dx.homogeneous) {
405            (true, true) => self.scalar /= dx.scalar,
406            (true, false) => {
407                let s0 = self.scalar;
408                self.homogeneous = false;
409                self.ensure_storage();
410                for i in 0..n {
411                    self.values[i] = s0 / dx.values[i];
412                }
413            }
414            (false, true) => {
415                for v in &mut self.values[..n] {
416                    *v /= dx.scalar;
417                }
418            }
419            (false, false) => {
420                for i in 0..n {
421                    self.values[i] /= dx.values[i];
422                }
423            }
424        }
425    }
426
427    fn element_wise_multiply_impl(&mut self, x: &dyn Vector) {
428        debug_assert!(self.initialized);
429        let dx = downcast_dense(x);
430        debug_assert!(dx.initialized);
431        let n = self.space.dim() as usize;
432        if n == 0 {
433            return;
434        }
435        match (self.homogeneous, dx.homogeneous) {
436            (true, true) => self.scalar *= dx.scalar,
437            (true, false) => {
438                let s0 = self.scalar;
439                self.homogeneous = false;
440                self.ensure_storage();
441                for i in 0..n {
442                    self.values[i] = s0 * dx.values[i];
443                }
444            }
445            (false, true) => {
446                if dx.scalar != 1.0 {
447                    for v in &mut self.values[..n] {
448                        *v *= dx.scalar;
449                    }
450                }
451            }
452            (false, false) => {
453                for i in 0..n {
454                    self.values[i] *= dx.values[i];
455                }
456            }
457        }
458    }
459
460    fn element_wise_select_impl(&mut self, x: &dyn Vector) {
461        debug_assert!(self.initialized);
462        let dx = downcast_dense(x);
463        debug_assert!(dx.initialized);
464        let n = self.space.dim() as usize;
465        if n == 0 {
466            return;
467        }
468        if self.homogeneous {
469            if self.scalar == 0.0 {
470                return;
471            }
472            if dx.homogeneous {
473                self.scalar *= dx.scalar;
474            } else {
475                let s0 = self.scalar;
476                self.homogeneous = false;
477                self.ensure_storage();
478                for i in 0..n {
479                    self.values[i] = s0 * dx.values[i];
480                }
481            }
482        } else if dx.homogeneous {
483            if dx.scalar != 1.0 {
484                for v in &mut self.values[..n] {
485                    if *v > 0.0 {
486                        *v = dx.scalar;
487                    } else if *v < 0.0 {
488                        *v = -dx.scalar;
489                    }
490                }
491            }
492        } else {
493            for i in 0..n {
494                if self.values[i] > 0.0 {
495                    self.values[i] = dx.values[i];
496                } else if self.values[i] < 0.0 {
497                    self.values[i] = -dx.values[i];
498                }
499            }
500        }
501    }
502
503    fn element_wise_max_impl(&mut self, x: &dyn Vector) {
504        debug_assert!(self.initialized);
505        let dx = downcast_dense(x);
506        debug_assert!(dx.initialized);
507        let n = self.space.dim() as usize;
508        if n == 0 {
509            return;
510        }
511        match (self.homogeneous, dx.homogeneous) {
512            (true, true) => self.scalar = self.scalar.max(dx.scalar),
513            (true, false) => {
514                let s0 = self.scalar;
515                self.homogeneous = false;
516                self.ensure_storage();
517                for i in 0..n {
518                    self.values[i] = s0.max(dx.values[i]);
519                }
520            }
521            (false, true) => {
522                for v in &mut self.values[..n] {
523                    *v = (*v).max(dx.scalar);
524                }
525            }
526            (false, false) => {
527                for i in 0..n {
528                    self.values[i] = self.values[i].max(dx.values[i]);
529                }
530            }
531        }
532    }
533
534    fn element_wise_min_impl(&mut self, x: &dyn Vector) {
535        debug_assert!(self.initialized);
536        let dx = downcast_dense(x);
537        debug_assert!(dx.initialized);
538        let n = self.space.dim() as usize;
539        if n == 0 {
540            return;
541        }
542        match (self.homogeneous, dx.homogeneous) {
543            (true, true) => self.scalar = self.scalar.min(dx.scalar),
544            (true, false) => {
545                let s0 = self.scalar;
546                self.homogeneous = false;
547                self.ensure_storage();
548                for i in 0..n {
549                    self.values[i] = s0.min(dx.values[i]);
550                }
551            }
552            (false, true) => {
553                for v in &mut self.values[..n] {
554                    *v = (*v).min(dx.scalar);
555                }
556            }
557            (false, false) => {
558                for i in 0..n {
559                    self.values[i] = self.values[i].min(dx.values[i]);
560                }
561            }
562        }
563    }
564
565    fn element_wise_reciprocal_impl(&mut self) {
566        debug_assert!(self.initialized);
567        let n = self.space.dim() as usize;
568        if n == 0 {
569            return;
570        }
571        if self.homogeneous {
572            self.scalar = 1.0 / self.scalar;
573        } else {
574            for v in &mut self.values[..n] {
575                *v = 1.0 / *v;
576            }
577        }
578    }
579
580    fn element_wise_abs_impl(&mut self) {
581        debug_assert!(self.initialized);
582        if self.homogeneous {
583            self.scalar = self.scalar.abs();
584        } else {
585            for v in &mut self.values[..self.space.dim() as usize] {
586                *v = v.abs();
587            }
588        }
589    }
590
591    fn element_wise_sqrt_impl(&mut self) {
592        debug_assert!(self.initialized);
593        if self.homogeneous {
594            self.scalar = self.scalar.sqrt();
595        } else {
596            for v in &mut self.values[..self.space.dim() as usize] {
597                *v = v.sqrt();
598            }
599        }
600    }
601
602    fn element_wise_sgn_impl(&mut self) {
603        debug_assert!(self.initialized);
604        let sgn = |v: Number| -> Number {
605            if v > 0.0 {
606                1.0
607            } else if v < 0.0 {
608                -1.0
609            } else {
610                0.0
611            }
612        };
613        if self.homogeneous {
614            self.scalar = sgn(self.scalar);
615        } else {
616            for v in &mut self.values[..self.space.dim() as usize] {
617                *v = sgn(*v);
618            }
619        }
620    }
621
622    fn add_scalar_impl(&mut self, scalar: Number) {
623        debug_assert!(self.initialized);
624        if self.homogeneous {
625            self.scalar += scalar;
626        } else {
627            for v in &mut self.values[..self.space.dim() as usize] {
628                *v += scalar;
629            }
630        }
631    }
632
633    fn max_impl(&self) -> Number {
634        debug_assert!(self.initialized);
635        let n = self.space.dim() as usize;
636        if n == 0 {
637            return -Number::MAX;
638        }
639        if self.homogeneous {
640            return self.scalar;
641        }
642        let mut m = self.values[0];
643        for &v in &self.values[1..n] {
644            if v > m {
645                m = v;
646            }
647        }
648        m
649    }
650
651    fn min_impl(&self) -> Number {
652        debug_assert!(self.initialized);
653        let n = self.space.dim() as usize;
654        if n == 0 {
655            return Number::MAX;
656        }
657        if self.homogeneous {
658            return self.scalar;
659        }
660        let mut m = self.values[0];
661        for &v in &self.values[1..n] {
662            if v < m {
663                m = v;
664            }
665        }
666        m
667    }
668
669    fn sum_impl(&self) -> Number {
670        debug_assert!(self.initialized);
671        let n = self.space.dim() as usize;
672        if self.homogeneous {
673            (n as Number) * self.scalar
674        } else {
675            let mut s = 0.0;
676            for &v in &self.values[..n] {
677                s += v;
678            }
679            s
680        }
681    }
682
683    fn sum_logs_impl(&self) -> Number {
684        debug_assert!(self.initialized);
685        let n = self.space.dim() as usize;
686        if n == 0 {
687            return 0.0;
688        }
689        if self.homogeneous {
690            (n as Number) * self.scalar.ln()
691        } else {
692            let mut s = 0.0;
693            for &v in &self.values[..n] {
694                s += v.ln();
695            }
696            s
697        }
698    }
699
700    fn frac_to_bound_impl(&self, delta: &dyn Vector, tau: Number) -> Number {
701        debug_assert_eq!(self.space.dim(), delta.dim());
702        debug_assert!(tau >= 0.0);
703        let dd = downcast_dense(delta);
704        let n = self.space.dim() as usize;
705        if n == 0 {
706            return 1.0;
707        }
708        let mut alpha: Number = 1.0;
709        match (self.homogeneous, dd.homogeneous) {
710            (true, true) => {
711                if dd.scalar < 0.0 {
712                    alpha = alpha.min(-tau / dd.scalar * self.scalar);
713                }
714            }
715            (true, false) => {
716                for &d in &dd.values[..n] {
717                    if d < 0.0 {
718                        alpha = alpha.min(-tau / d * self.scalar);
719                    }
720                }
721            }
722            (false, true) => {
723                if dd.scalar < 0.0 {
724                    let f = -tau / dd.scalar;
725                    for &x in &self.values[..n] {
726                        alpha = alpha.min(f * x);
727                    }
728                }
729            }
730            (false, false) => {
731                for i in 0..n {
732                    let d = dd.values[i];
733                    if d < 0.0 {
734                        alpha = alpha.min(-tau / d * self.values[i]);
735                    }
736                }
737            }
738        }
739        debug_assert!(alpha >= 0.0);
740        alpha
741    }
742
743    fn add_two_vectors_impl(
744        &mut self,
745        a: Number,
746        v1: &dyn Vector,
747        b: Number,
748        v2: &dyn Vector,
749        c: Number,
750    ) {
751        let n = self.space.dim() as usize;
752        if n == 0 {
753            debug_assert!(self.initialized);
754            return;
755        }
756        let dv1 = if a != 0.0 {
757            Some(downcast_dense(v1))
758        } else {
759            None
760        };
761        let dv2 = if b != 0.0 {
762            Some(downcast_dense(v2))
763        } else {
764            None
765        };
766        let homog_v1 = dv1.map(|d| d.homogeneous).unwrap_or(true);
767        let homog_v2 = dv2.map(|d| d.homogeneous).unwrap_or(true);
768        let s_v1 = dv1.map(|d| d.scalar).unwrap_or(0.0);
769        let s_v2 = dv2.map(|d| d.scalar).unwrap_or(0.0);
770
771        // All-homogeneous fast path — result stays homogeneous.
772        if (c == 0.0 || self.homogeneous) && homog_v1 && homog_v2 {
773            let prev = if c == 0.0 { 0.0 } else { c * self.scalar };
774            self.scalar = prev + a * s_v1 + b * s_v2;
775            self.homogeneous = true;
776            self.initialized = true;
777            return;
778        }
779
780        // Materialize self if needed. With c == 0 we are about to overwrite.
781        if c == 0.0 {
782            self.ensure_storage();
783            self.homogeneous = false;
784        } else if self.homogeneous {
785            self.materialize_from_scalar();
786        }
787
788        // Get expanded slices of v1/v2 (allocate when homogeneous; the
789        // slow path is rare in practice).
790        let v1_arr: Option<Vec<Number>> = dv1.map(|d| {
791            if d.homogeneous {
792                vec![d.scalar; n]
793            } else {
794                d.values[..n].to_vec()
795            }
796        });
797        let v2_arr: Option<Vec<Number>> = dv2.map(|d| {
798            if d.homogeneous {
799                vec![d.scalar; n]
800            } else {
801                d.values[..n].to_vec()
802            }
803        });
804
805        // Single fused expression. IEEE multiplication by 0 / 1 / -1
806        // is exact, so this is bit-equivalent to upstream's 64-case
807        // dispatch in `IpDenseVector.cpp:843-1322`.
808        if c == 0.0 {
809            for i in 0..n {
810                let v1i = v1_arr.as_ref().map(|v| v[i]).unwrap_or(0.0);
811                let v2i = v2_arr.as_ref().map(|v| v[i]).unwrap_or(0.0);
812                self.values[i] = a * v1i + b * v2i;
813            }
814        } else {
815            for i in 0..n {
816                let v1i = v1_arr.as_ref().map(|v| v[i]).unwrap_or(0.0);
817                let v2i = v2_arr.as_ref().map(|v| v[i]).unwrap_or(0.0);
818                self.values[i] = a * v1i + b * v2i + c * self.values[i];
819            }
820        }
821        self.initialized = true;
822    }
823
824    fn add_vector_quotient_impl(&mut self, a: Number, z: &dyn Vector, s: &dyn Vector, c: Number) {
825        debug_assert_eq!(self.space.dim(), z.dim());
826        debug_assert_eq!(self.space.dim(), s.dim());
827        let dz = downcast_dense(z);
828        let ds = downcast_dense(s);
829        debug_assert!(dz.initialized && ds.initialized);
830        let n = self.space.dim() as usize;
831        if n == 0 {
832            return;
833        }
834        let homog_z = dz.homogeneous;
835        let homog_s = ds.homogeneous;
836        if (c == 0.0 || self.homogeneous) && homog_z && homog_s {
837            self.scalar = if c == 0.0 {
838                a * dz.scalar / ds.scalar
839            } else {
840                c * self.scalar + a * dz.scalar / ds.scalar
841            };
842            self.initialized = true;
843            self.homogeneous = true;
844            self.values.clear();
845            return;
846        }
847        // Materialize self if needed.
848        if c == 0.0 {
849            self.ensure_storage();
850            self.homogeneous = false;
851        } else if self.homogeneous {
852            self.materialize_from_scalar();
853        }
854        let z_arr: Vec<Number> = if homog_z {
855            vec![dz.scalar; n]
856        } else {
857            dz.values[..n].to_vec()
858        };
859        let s_arr: Vec<Number> = if homog_s {
860            vec![ds.scalar; n]
861        } else {
862            ds.values[..n].to_vec()
863        };
864        if c == 0.0 {
865            for i in 0..n {
866                self.values[i] = a * z_arr[i] / s_arr[i];
867            }
868        } else {
869            for i in 0..n {
870                self.values[i] = c * self.values[i] + a * z_arr[i] / s_arr[i];
871            }
872        }
873        self.initialized = true;
874        self.homogeneous = false;
875    }
876}
877
878#[cfg(test)]
879mod tests {
880    use super::*;
881
882    fn vec_of(space: &Rc<DenseVectorSpace>, vals: &[Number]) -> DenseVector {
883        let mut v = DenseVector::new(Rc::clone(space));
884        v.set_values(vals);
885        v
886    }
887
888    #[test]
889    fn axpy_basic() {
890        let s = DenseVectorSpace::new(3);
891        let x = vec_of(&s, &[1.0, 2.0, 3.0]);
892        let mut y = vec_of(&s, &[10.0, 20.0, 30.0]);
893        y.axpy(2.0, &x);
894        assert_eq!(y.values(), &[12.0, 24.0, 36.0]);
895    }
896
897    #[test]
898    fn dot_homogeneous_pair() {
899        let s = DenseVectorSpace::new(4);
900        let mut x = DenseVector::new(Rc::clone(&s));
901        x.set(2.0); // homogeneous 2
902        let mut y = DenseVector::new(Rc::clone(&s));
903        y.set(3.0); // homogeneous 3
904                    // 4 entries of 2*3 = 24
905        assert_eq!(x.dot(&y), 24.0);
906    }
907
908    #[test]
909    fn dot_mixed_homog_dense() {
910        let s = DenseVectorSpace::new(3);
911        let mut x = DenseVector::new(Rc::clone(&s));
912        x.set(2.0);
913        let y = vec_of(&s, &[1.0, 2.0, 3.0]);
914        // 2*(1+2+3) = 12
915        assert_eq!(x.dot(&y), 12.0);
916        assert_eq!(y.dot(&x), 12.0);
917    }
918
919    #[test]
920    fn nrm2_homogeneous_uses_sqrt_n() {
921        let s = DenseVectorSpace::new(4);
922        let mut x = DenseVector::new(Rc::clone(&s));
923        x.set(3.0);
924        // sqrt(4) * 3 = 6
925        assert!((x.nrm2() - 6.0).abs() < 1e-15);
926    }
927
928    #[test]
929    fn nrm2_cache_invalidated_by_mutation() {
930        let s = DenseVectorSpace::new(2);
931        let mut x = vec_of(&s, &[3.0, 4.0]);
932        assert_eq!(x.nrm2(), 5.0);
933        x.scal(2.0);
934        assert!((x.nrm2() - 10.0).abs() < 1e-15);
935    }
936
937    #[test]
938    fn dot_cache_hits_after_first_call() {
939        let s = DenseVectorSpace::new(3);
940        let x = vec_of(&s, &[1.0, 2.0, 3.0]);
941        let y = vec_of(&s, &[1.0, 1.0, 1.0]);
942        assert_eq!(x.dot(&y), 6.0);
943        // Second call should be cached but still produce the same value.
944        assert_eq!(x.dot(&y), 6.0);
945    }
946
947    #[test]
948    fn dot_self_uses_nrm2_squared_path() {
949        let s = DenseVectorSpace::new(2);
950        let x = vec_of(&s, &[3.0, 4.0]);
951        // Pass x as both args — cache shortcut should compute 5*5 = 25.
952        assert_eq!(x.dot(&x), 25.0);
953    }
954
955    #[test]
956    fn add_two_vectors_all_homogeneous() {
957        let s = DenseVectorSpace::new(5);
958        let mut y = DenseVector::new(Rc::clone(&s));
959        y.set(1.0);
960        let mut v1 = DenseVector::new(Rc::clone(&s));
961        v1.set(2.0);
962        let mut v2 = DenseVector::new(Rc::clone(&s));
963        v2.set(3.0);
964        // y = 4*v1 + 5*v2 + 0.5*y = 4*2 + 5*3 + 0.5 = 8 + 15 + 0.5 = 23.5
965        y.add_two_vectors(4.0, &v1, 5.0, &v2, 0.5);
966        assert!(y.is_homogeneous());
967        assert_eq!(y.scalar(), 23.5);
968    }
969
970    #[test]
971    fn add_two_vectors_mixed_dense_overrides_homog() {
972        let s = DenseVectorSpace::new(3);
973        let mut y = DenseVector::new(Rc::clone(&s));
974        y.set(0.0);
975        let v1 = vec_of(&s, &[1.0, 2.0, 3.0]);
976        let v2 = vec_of(&s, &[10.0, 10.0, 10.0]);
977        // y = 1*v1 + 1*v2 + 0*y = [11, 12, 13]
978        y.add_two_vectors(1.0, &v1, 1.0, &v2, 0.0);
979        assert!(!y.is_homogeneous());
980        assert_eq!(y.values(), &[11.0, 12.0, 13.0]);
981    }
982
983    #[test]
984    fn frac_to_bound_basic() {
985        let s = DenseVectorSpace::new(3);
986        let x = vec_of(&s, &[1.0, 2.0, 3.0]);
987        let delta = vec_of(&s, &[-2.0, 1.0, -1.5]);
988        // negative components: i=0 → -tau/-2 * 1 = tau/2
989        //                       i=2 → -tau/-1.5 * 3 = tau*2
990        // alpha = min(1, tau/2, tau*2). tau=1 → alpha = 0.5
991        let alpha = x.frac_to_bound(&delta, 1.0);
992        assert!((alpha - 0.5).abs() < 1e-15);
993    }
994
995    #[test]
996    fn element_wise_divide_homog_dense() {
997        let s = DenseVectorSpace::new(3);
998        let mut y = DenseVector::new(Rc::clone(&s));
999        y.set(6.0);
1000        let x = vec_of(&s, &[1.0, 2.0, 3.0]);
1001        y.element_wise_divide(&x);
1002        assert!(!y.is_homogeneous());
1003        assert_eq!(y.values(), &[6.0, 3.0, 2.0]);
1004    }
1005
1006    #[test]
1007    fn element_wise_sgn_handles_all_three_signs() {
1008        let s = DenseVectorSpace::new(3);
1009        let mut x = vec_of(&s, &[-2.5, 0.0, 7.0]);
1010        x.element_wise_sgn();
1011        assert_eq!(x.values(), &[-1.0, 0.0, 1.0]);
1012    }
1013
1014    #[test]
1015    fn sum_and_max_min_homogeneous() {
1016        let s = DenseVectorSpace::new(4);
1017        let mut x = DenseVector::new(Rc::clone(&s));
1018        x.set(2.5);
1019        assert_eq!(x.sum(), 10.0);
1020        assert_eq!(x.max(), 2.5);
1021        assert_eq!(x.min(), 2.5);
1022    }
1023
1024    #[test]
1025    fn has_valid_numbers_detects_nan() {
1026        let s = DenseVectorSpace::new(3);
1027        let bad = vec_of(&s, &[1.0, Number::NAN, 2.0]);
1028        assert!(!bad.has_valid_numbers());
1029        let good = vec_of(&s, &[1.0, 2.0, 3.0]);
1030        assert!(good.has_valid_numbers());
1031    }
1032
1033    #[test]
1034    fn copy_to_pos_pastes_into_subrange() {
1035        let s_big = DenseVectorSpace::new(5);
1036        let s_small = DenseVectorSpace::new(2);
1037        let mut y = DenseVector::new(Rc::clone(&s_big));
1038        y.set(0.0);
1039        let x = vec_of(&s_small, &[7.0, 8.0]);
1040        y.copy_to_pos(2, &x);
1041        assert_eq!(y.values(), &[0.0, 0.0, 7.0, 8.0, 0.0]);
1042    }
1043
1044    #[test]
1045    fn make_new_copy_clones_values() {
1046        let s = DenseVectorSpace::new(3);
1047        let x = vec_of(&s, &[1.0, 2.0, 3.0]);
1048        let y = x.make_new_copy();
1049        let dy = y.as_any().downcast_ref::<DenseVector>().unwrap();
1050        assert_eq!(dy.values(), &[1.0, 2.0, 3.0]);
1051    }
1052
1053    #[test]
1054    fn add_vector_quotient_all_homogeneous() {
1055        let s = DenseVectorSpace::new(4);
1056        let mut y = DenseVector::new(Rc::clone(&s));
1057        y.set(1.0);
1058        let mut z = DenseVector::new(Rc::clone(&s));
1059        z.set(6.0);
1060        let mut sd = DenseVector::new(Rc::clone(&s));
1061        sd.set(2.0);
1062        // y = 2 * z/sd + 0.5 * y = 2*6/2 + 0.5 = 6.5
1063        y.add_vector_quotient(2.0, &z, &sd, 0.5);
1064        assert!(y.is_homogeneous());
1065        assert_eq!(y.scalar(), 6.5);
1066    }
1067
1068    #[test]
1069    fn dim_zero_is_consistent() {
1070        let s = DenseVectorSpace::new(0);
1071        let x = DenseVector::new(Rc::clone(&s));
1072        assert!(x.is_initialized());
1073        assert!(x.is_homogeneous());
1074        assert_eq!(x.nrm2(), 0.0);
1075    }
1076}