1use crate::constant_derivatives::{
61 ConstantDerivatives, DerivativeProof, DerivativeProofs, subsystem_proof,
62};
63use crate::ipopt_nlp::{IpoptNlp, Nlp, SplitNames};
64use crate::tnlp::{IDX_NAMES, MetaData, NlpInfo, ScalingRequest, SparsityRequest, StartingPoint};
65use crate::tnlp_adapter::{BoundClassification, TNLPAdapter};
66use pounce_common::cached::Cache;
67use pounce_common::timing::TimingStatistics;
68use pounce_common::types::{Index, Number};
69use pounce_linalg::{
70 DenseVector, DenseVectorSpace, ExpansionMatrix, ExpansionMatrixSpace, GenTMatrix,
71 GenTMatrixSpace, Matrix, SymMatrix, SymTMatrix, SymTMatrixSpace, Vector,
72};
73use std::cell::{Cell, RefCell};
74use std::rc::Rc;
75
76pub trait NlpScaling {
88 fn obj_scaling(&self) -> Number {
93 1.0
94 }
95}
96
97#[derive(Debug, Default, Clone, Copy)]
100pub struct NoScaling;
101impl NlpScaling for NoScaling {}
102
103#[derive(Debug, Clone, Copy)]
108pub struct ConstObjScaling(pub Number);
109impl NlpScaling for ConstObjScaling {
110 fn obj_scaling(&self) -> Number {
111 self.0
112 }
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub enum ScalingMethod {
119 None,
121 GradientBased,
123 UserScaling,
130}
131
132pub fn gradient_obj_scale(
146 max_grad_f: Number,
147 max_gradient: Number,
148 min_value: Number,
149 obj_target_gradient: Number,
150) -> Number {
151 let mut df = 1.0;
152 if obj_target_gradient > 0.0 && max_grad_f > 0.0 {
153 df = obj_target_gradient / max_grad_f;
156 } else if max_grad_f > max_gradient {
157 df = max_gradient / max_grad_f;
158 }
159 if df < min_value {
160 df = min_value;
161 }
162 df
163}
164
165pub fn gradient_row_scale(
183 row_max: Number,
184 max_gradient: Number,
185 min_value: Number,
186 constr_target_gradient: Number,
187) -> Number {
188 let mut s = if constr_target_gradient > 0.0 {
189 constr_target_gradient / row_max
190 } else {
191 let raw = max_gradient / row_max;
192 if raw > 1.0 { 1.0 } else { raw }
193 };
194 if s < min_value {
195 s = min_value;
196 }
197 s
198}
199
200pub fn gradient_scaling_fires(
206 row_max: &[Number],
207 max_gradient: Number,
208 constr_target_gradient: Number,
209) -> bool {
210 constr_target_gradient > 0.0 || row_max.iter().any(|&v| v > max_gradient)
211}
212
213pub struct OrigIpoptNlp {
216 adapter: Rc<RefCell<TNLPAdapter>>,
218 scaling: Rc<dyn NlpScaling>,
223
224 obj_scale_factor: Cell<Number>,
227 computed_obj_scale: Cell<Number>,
230 c_scale: RefCell<Option<Vec<Number>>>,
234 d_scale: RefCell<Option<Vec<Number>>>,
236 declared_d_l: RefCell<Option<Vec<Number>>>,
245 declared_d_u: RefCell<Option<Vec<Number>>>,
246 declared_x_l: RefCell<Option<Vec<Number>>>,
252 declared_x_u: RefCell<Option<Vec<Number>>>,
253 honor_original_bounds: Cell<bool>,
261 x_scaling_rejected: Cell<bool>,
270
271 x_space: Rc<DenseVectorSpace>,
273 c_space: Rc<DenseVectorSpace>,
274 d_space: Rc<DenseVectorSpace>,
275 x_l_space: Rc<DenseVectorSpace>,
276 x_u_space: Rc<DenseVectorSpace>,
277 d_l_space: Rc<DenseVectorSpace>,
278 d_u_space: Rc<DenseVectorSpace>,
279 px_l_space: Rc<ExpansionMatrixSpace>,
280 px_u_space: Rc<ExpansionMatrixSpace>,
281 pd_l_space: Rc<ExpansionMatrixSpace>,
282 pd_u_space: Rc<ExpansionMatrixSpace>,
283 jac_c_space: Rc<GenTMatrixSpace>,
284 jac_d_space: Rc<GenTMatrixSpace>,
285 h_space: Option<Rc<SymTMatrixSpace>>,
288
289 x_l: Rc<DenseVector>,
291 x_u: Rc<DenseVector>,
292 d_l: Rc<DenseVector>,
293 d_u: Rc<DenseVector>,
294 c_rhs: Vec<Number>,
301 warm_start_snapshot: RefCell<Option<StartingPointSnapshot>>,
304
305 px_l: Rc<dyn Matrix>,
307 px_u: Rc<dyn Matrix>,
308 pd_l: Rc<dyn Matrix>,
309 pd_u: Rc<dyn Matrix>,
310
311 jac_c_entry_in_g: Vec<Index>,
315 jac_d_entry_in_g: Vec<Index>,
317 nnz_jac_g_full: Index,
319
320 nnz_h_lag_full: Index,
324 h_entry_in_full: Vec<Index>,
329
330 f_cache: RefCell<Cache<Number>>,
332 grad_f_cache: RefCell<Cache<Rc<dyn Vector>>>,
333 c_cache: RefCell<Cache<Rc<dyn Vector>>>,
334 d_cache: RefCell<Cache<Rc<dyn Vector>>>,
335 jac_c_cache: RefCell<Cache<Rc<dyn Matrix>>>,
336 jac_d_cache: RefCell<Cache<Rc<dyn Matrix>>>,
337 h_cache: RefCell<Cache<Rc<dyn SymMatrix>>>,
338 full_g_cache: RefCell<Cache<Rc<Vec<Number>>>>,
346 full_jac_g_cache: RefCell<Cache<Rc<Vec<Number>>>>,
347
348 f_evals: RefCell<Index>,
350 grad_f_evals: RefCell<Index>,
351 c_evals: RefCell<Index>,
352 d_evals: RefCell<Index>,
353 jac_c_evals: RefCell<Index>,
354 jac_d_evals: RefCell<Index>,
355 h_evals: RefCell<Index>,
356
357 info: NlpInfo,
360
361 timing: RefCell<Option<Rc<TimingStatistics>>>,
366
367 const_deriv: ConstantDerivatives,
379
380 fixed_reach: FixedVarReach,
384}
385
386#[derive(Clone, Debug, Default)]
402struct FixedVarReach {
403 g_row_touches_fixed: Vec<bool>,
409 second_order_touches_fixed: bool,
421}
422
423#[derive(Clone)]
424struct StartingPointSnapshot {
425 x: Vec<Number>,
426 z_l: Vec<Number>,
427 z_u: Vec<Number>,
428 lambda: Vec<Number>,
429}
430
431impl std::fmt::Debug for OrigIpoptNlp {
432 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
433 f.debug_struct("OrigIpoptNlp")
434 .field("info", &self.info)
435 .field("f_evals", &*self.f_evals.borrow())
436 .field("grad_f_evals", &*self.grad_f_evals.borrow())
437 .field("c_evals", &*self.c_evals.borrow())
438 .field("d_evals", &*self.d_evals.borrow())
439 .field("jac_c_evals", &*self.jac_c_evals.borrow())
440 .field("jac_d_evals", &*self.jac_d_evals.borrow())
441 .field("h_evals", &*self.h_evals.borrow())
442 .finish_non_exhaustive()
443 }
444}
445
446impl OrigIpoptNlp {
447 pub fn new(
453 adapter: Rc<RefCell<TNLPAdapter>>,
454 scaling: Rc<dyn NlpScaling>,
455 ) -> Result<Self, String> {
456 let (info, classification) = {
458 let a = adapter.borrow();
459 (*a.nlp_info(), a.classification().clone())
460 };
461
462 let n_x_var = classification.n_x_var();
464 let x_space = DenseVectorSpace::new(n_x_var);
465 let c_space = DenseVectorSpace::new(classification.n_c);
466 let d_space = DenseVectorSpace::new(classification.n_d);
467 let x_l_space = DenseVectorSpace::new(classification.n_x_l());
468 let x_u_space = DenseVectorSpace::new(classification.n_x_u());
469 let d_l_space = DenseVectorSpace::new(classification.n_d_l());
470 let d_u_space = DenseVectorSpace::new(classification.n_d_u());
471
472 let px_l_space =
474 ExpansionMatrixSpace::new(n_x_var, classification.n_x_l(), &classification.x_l_map, 0);
475 let px_u_space =
476 ExpansionMatrixSpace::new(n_x_var, classification.n_x_u(), &classification.x_u_map, 0);
477 let pd_l_space = ExpansionMatrixSpace::new(
478 classification.n_d,
479 classification.n_d_l(),
480 &classification.d_l_map,
481 0,
482 );
483 let pd_u_space = ExpansionMatrixSpace::new(
484 classification.n_d,
485 classification.n_d_u(),
486 &classification.d_u_map,
487 0,
488 );
489 let px_l: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&px_l_space)));
490 let px_u: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&px_u_space)));
491 let pd_l: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&pd_l_space)));
492 let pd_u: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&pd_u_space)));
493
494 let n_full_x = classification.n_full_x as usize;
498 let n_full_g = classification.n_full_g as usize;
499 let mut full_x_l = vec![0.0; n_full_x];
500 let mut full_x_u = vec![0.0; n_full_x];
501 let mut full_g_l = vec![0.0; n_full_g];
502 let mut full_g_u = vec![0.0; n_full_g];
503 {
504 let a = adapter.borrow();
505 let mut t = a.tnlp().borrow_mut();
506 let ok = t.get_bounds_info(crate::tnlp::BoundsInfo {
507 x_l: &mut full_x_l,
508 x_u: &mut full_x_u,
509 g_l: &mut full_g_l,
510 g_u: &mut full_g_u,
511 });
512 if !ok {
513 return Err("TNLP::get_bounds_info returned false on second call".into());
514 }
515 }
516
517 let x_l = make_dense_from(&x_l_space, |i| {
518 let var_idx = classification.x_l_map[i] as usize;
520 let full_idx = classification.x_not_fixed_map[var_idx] as usize;
521 full_x_l[full_idx]
522 });
523 let x_u = make_dense_from(&x_u_space, |i| {
524 let var_idx = classification.x_u_map[i] as usize;
525 let full_idx = classification.x_not_fixed_map[var_idx] as usize;
526 full_x_u[full_idx]
527 });
528 let d_l = make_dense_from(&d_l_space, |i| {
529 let d_idx = classification.d_l_map[i] as usize;
531 let full_g_idx = classification.d_map[d_idx] as usize;
532 full_g_l[full_g_idx]
533 });
534 let d_u = make_dense_from(&d_u_space, |i| {
535 let d_idx = classification.d_u_map[i] as usize;
536 let full_g_idx = classification.d_map[d_idx] as usize;
537 full_g_u[full_g_idx]
538 });
539
540 let c_rhs: Vec<Number> = classification
546 .c_map
547 .iter()
548 .map(|&g_idx| full_g_l[g_idx as usize])
549 .collect();
550
551 let mut full_irow = vec![0 as Index; info.nnz_jac_g as usize];
561 let mut full_jcol = vec![0 as Index; info.nnz_jac_g as usize];
562 {
563 let a = adapter.borrow();
564 let mut t = a.tnlp().borrow_mut();
565 let ok = t.eval_jac_g(
566 None,
567 false,
568 SparsityRequest::Structure {
569 irow: &mut full_irow,
570 jcol: &mut full_jcol,
571 },
572 );
573 if !ok {
574 return Err("TNLP::eval_jac_g(Structure) returned false".into());
575 }
576 }
577
578 let mut g_to_c = vec![-1 as Index; n_full_g];
580 for (c_idx, &g_idx) in classification.c_map.iter().enumerate() {
581 g_to_c[g_idx as usize] = c_idx as Index;
582 }
583 let mut g_to_d = vec![-1 as Index; n_full_g];
584 for (d_idx, &g_idx) in classification.d_map.iter().enumerate() {
585 g_to_d[g_idx as usize] = d_idx as Index;
586 }
587
588 let style_offset = match info.index_style {
589 crate::tnlp::IndexStyle::C => 0 as Index,
590 crate::tnlp::IndexStyle::Fortran => 1 as Index,
591 };
592
593 let mut jac_c_irow_1based = Vec::new();
594 let mut jac_c_jcol_1based = Vec::new();
595 let mut jac_c_entry_in_g = Vec::new();
596 let mut jac_d_irow_1based = Vec::new();
597 let mut jac_d_jcol_1based = Vec::new();
598 let mut jac_d_entry_in_g = Vec::new();
599
600 let full_to_var = &classification.full_to_var;
604 let mut g_row_touches_fixed = vec![false; info.m as usize];
608 for k in 0..info.nnz_jac_g as usize {
609 let g_row_0 = (full_irow[k] - style_offset) as usize;
610 let x_col_0 = (full_jcol[k] - style_offset) as usize;
611 let var_col = full_to_var[x_col_0];
612 if var_col < 0 {
613 g_row_touches_fixed[g_row_0] = true;
614 continue;
615 }
616 let col_1based = var_col + 1;
618 let c_row = g_to_c[g_row_0];
619 if c_row >= 0 {
620 jac_c_irow_1based.push(c_row + 1);
621 jac_c_jcol_1based.push(col_1based);
622 jac_c_entry_in_g.push(k as Index);
623 } else {
624 let d_row = g_to_d[g_row_0];
625 debug_assert!(d_row >= 0, "g row {g_row_0} is neither in c_map nor d_map");
626 jac_d_irow_1based.push(d_row + 1);
627 jac_d_jcol_1based.push(col_1based);
628 jac_d_entry_in_g.push(k as Index);
629 }
630 }
631
632 let jac_c_space = GenTMatrixSpace::new(
633 classification.n_c,
634 n_x_var,
635 jac_c_irow_1based,
636 jac_c_jcol_1based,
637 );
638 let jac_d_space = GenTMatrixSpace::new(
639 classification.n_d,
640 n_x_var,
641 jac_d_irow_1based,
642 jac_d_jcol_1based,
643 );
644
645 let nnz_h_lag_full = info.nnz_h_lag;
650 let mut h_entry_in_full: Vec<Index> = Vec::new();
651 let mut second_order_touches_fixed = info.nnz_h_lag > 0;
656 let h_space = if info.nnz_h_lag > 0 {
657 let mut h_irow = vec![0 as Index; info.nnz_h_lag as usize];
658 let mut h_jcol = vec![0 as Index; info.nnz_h_lag as usize];
659 let supports_h = {
660 let a = adapter.borrow();
661 let mut t = a.tnlp().borrow_mut();
662 t.eval_h(
663 None,
664 false,
665 1.0,
666 None,
667 false,
668 SparsityRequest::Structure {
669 irow: &mut h_irow,
670 jcol: &mut h_jcol,
671 },
672 )
673 };
674 if supports_h {
675 second_order_touches_fixed = false;
676 let mut h_irow_1: Vec<Index> = Vec::with_capacity(h_irow.len());
682 let mut h_jcol_1: Vec<Index> = Vec::with_capacity(h_jcol.len());
683 for k in 0..h_irow.len() {
684 let i_full = (h_irow[k] - style_offset) as usize;
685 let j_full = (h_jcol[k] - style_offset) as usize;
686 let i_var = full_to_var[i_full];
687 let j_var = full_to_var[j_full];
688 if i_var < 0 || j_var < 0 {
689 second_order_touches_fixed = true;
690 continue;
691 }
692 h_irow_1.push(i_var + 1);
693 h_jcol_1.push(j_var + 1);
694 h_entry_in_full.push(k as Index);
695 }
696 Some(SymTMatrixSpace::new(n_x_var, h_irow_1, h_jcol_1))
697 } else {
698 None
700 }
701 } else {
702 Some(SymTMatrixSpace::new(n_x_var, Vec::new(), Vec::new()))
706 };
707
708 let initial_obj_scal = scaling.obj_scaling();
714 Ok(Self {
715 adapter,
716 scaling,
717 obj_scale_factor: Cell::new(initial_obj_scal),
718 computed_obj_scale: Cell::new(1.0),
719 c_scale: RefCell::new(None),
720 d_scale: RefCell::new(None),
721 declared_d_l: RefCell::new(None),
722 declared_d_u: RefCell::new(None),
723 declared_x_l: RefCell::new(None),
724 declared_x_u: RefCell::new(None),
725 honor_original_bounds: Cell::new(false),
726 x_scaling_rejected: Cell::new(false),
727 x_space,
728 c_space,
729 d_space,
730 x_l_space,
731 x_u_space,
732 d_l_space,
733 d_u_space,
734 px_l_space,
735 px_u_space,
736 pd_l_space,
737 pd_u_space,
738 jac_c_space,
739 jac_d_space,
740 h_space,
741 x_l: Rc::new(x_l),
742 x_u: Rc::new(x_u),
743 d_l: Rc::new(d_l),
744 d_u: Rc::new(d_u),
745 c_rhs,
746 warm_start_snapshot: RefCell::new(None),
747 px_l,
748 px_u,
749 pd_l,
750 pd_u,
751 jac_c_entry_in_g,
752 jac_d_entry_in_g,
753 nnz_jac_g_full: info.nnz_jac_g,
754 nnz_h_lag_full,
755 h_entry_in_full,
756 f_cache: RefCell::new(Cache::new(1)),
757 grad_f_cache: RefCell::new(Cache::new(1)),
758 c_cache: RefCell::new(Cache::new(1)),
759 d_cache: RefCell::new(Cache::new(1)),
760 jac_c_cache: RefCell::new(Cache::new(1)),
761 jac_d_cache: RefCell::new(Cache::new(1)),
762 h_cache: RefCell::new(Cache::new(1)),
763 full_g_cache: RefCell::new(Cache::new(1)),
764 full_jac_g_cache: RefCell::new(Cache::new(1)),
765 f_evals: RefCell::new(0),
766 grad_f_evals: RefCell::new(0),
767 c_evals: RefCell::new(0),
768 d_evals: RefCell::new(0),
769 jac_c_evals: RefCell::new(0),
770 jac_d_evals: RefCell::new(0),
771 h_evals: RefCell::new(0),
772 info,
773 timing: RefCell::new(None),
774 const_deriv: ConstantDerivatives::default(),
775 fixed_reach: FixedVarReach {
776 g_row_touches_fixed,
777 second_order_touches_fixed,
778 },
779 })
780 }
781
782 pub fn set_timing_stats(&self, t: Rc<TimingStatistics>) {
788 *self.timing.borrow_mut() = Some(t);
789 }
790
791 fn timed_eval<R, F>(&self, pick: fn(&TimingStatistics) -> &pounce_common::TimedTask, f: F) -> R
796 where
797 F: FnOnce() -> R,
798 {
799 let guard = self.timing.borrow();
800 match guard.as_deref() {
801 Some(t) => {
802 let task = pick(t);
803 task.start();
804 t.total_function_evaluation_time.start();
805 let r = f();
806 t.total_function_evaluation_time.end();
807 task.end();
808 r
809 }
810 None => {
811 drop(guard);
812 f()
813 }
814 }
815 }
816
817 pub fn derivative_proofs(&self) -> [DerivativeProof; 4] {
844 let mut proofs: DerivativeProofs = {
845 let a = self.adapter.borrow();
846 let mut t = a.tnlp().borrow_mut();
847 t.derivative_proofs()
848 };
849 let cls = self.adapter.borrow().classification().clone();
850 if cls.n_x_fixed > 0 {
851 let reach = &self.fixed_reach;
865 for (row, proof) in proofs.jac.iter_mut().enumerate() {
866 if reach.g_row_touches_fixed.get(row).copied().unwrap_or(true) {
867 *proof = proof.forget_variation();
868 }
869 }
870 if reach.second_order_touches_fixed {
871 proofs.grad_f = proofs.grad_f.forget_variation();
872 proofs.hessian = proofs.hessian.forget_variation();
873 }
874 }
875 [
876 proofs.grad_f,
877 proofs.hessian,
878 subsystem_proof(&proofs, &cls.c_map),
879 subsystem_proof(&proofs, &cls.d_map),
880 ]
881 }
882
883 pub fn set_constant_derivatives(&mut self, cd: ConstantDerivatives) {
889 self.const_deriv = cd;
890 self.invalidate_eval_caches();
893 }
894
895 pub fn constant_derivatives(&self) -> ConstantDerivatives {
897 self.const_deriv
898 }
899
900 pub fn nlp_info(&self) -> &NlpInfo {
903 &self.info
904 }
905 pub fn classification_n_x_var(&self) -> Index {
906 self.x_space.dim()
907 }
908 pub fn x_space(&self) -> &Rc<DenseVectorSpace> {
909 &self.x_space
910 }
911 pub fn c_space(&self) -> &Rc<DenseVectorSpace> {
912 &self.c_space
913 }
914 pub fn d_space(&self) -> &Rc<DenseVectorSpace> {
915 &self.d_space
916 }
917 pub fn x_l_space(&self) -> &Rc<DenseVectorSpace> {
918 &self.x_l_space
919 }
920 pub fn x_u_space(&self) -> &Rc<DenseVectorSpace> {
921 &self.x_u_space
922 }
923 pub fn d_l_space(&self) -> &Rc<DenseVectorSpace> {
924 &self.d_l_space
925 }
926 pub fn d_u_space(&self) -> &Rc<DenseVectorSpace> {
927 &self.d_u_space
928 }
929 pub fn px_l_space(&self) -> &Rc<ExpansionMatrixSpace> {
930 &self.px_l_space
931 }
932 pub fn px_u_space(&self) -> &Rc<ExpansionMatrixSpace> {
933 &self.px_u_space
934 }
935 pub fn pd_l_space(&self) -> &Rc<ExpansionMatrixSpace> {
936 &self.pd_l_space
937 }
938 pub fn pd_u_space(&self) -> &Rc<ExpansionMatrixSpace> {
939 &self.pd_u_space
940 }
941 pub fn jac_c_space(&self) -> &Rc<GenTMatrixSpace> {
942 &self.jac_c_space
943 }
944 pub fn jac_d_space(&self) -> &Rc<GenTMatrixSpace> {
945 &self.jac_d_space
946 }
947 pub fn h_space(&self) -> Option<&Rc<SymTMatrixSpace>> {
948 self.h_space.as_ref()
949 }
950
951 pub fn obj_scale_factor(&self) -> Number {
954 self.obj_scale_factor.get()
955 }
956
957 pub fn relax_bounds(&mut self, bound_relax_factor: Number, constr_viol_tol: Number) {
969 *self.declared_d_l.borrow_mut() = Some(self.d_l.expanded_values());
974 *self.declared_d_u.borrow_mut() = Some(self.d_u.expanded_values());
975 *self.declared_x_l.borrow_mut() = Some(self.x_l.expanded_values());
979 *self.declared_x_u.borrow_mut() = Some(self.x_u.expanded_values());
980 if bound_relax_factor <= 0.0 {
981 return;
982 }
983 let relax = bound_relax_factor.abs();
984 let cap = constr_viol_tol;
985 let apply = |v: &mut DenseVector, sign: Number| {
986 let xs = v.values_mut();
987 for x in xs.iter_mut() {
988 let delta = (relax * x.abs().max(1.0)).min(cap);
989 *x += sign * delta;
990 }
991 };
992 let apply_d = |v: &mut DenseVector, sign: Number| {
1010 let rel_width = relax.min(cap);
1011 let xs = v.values_mut();
1012 for x in xs.iter_mut() {
1013 let scale = if *x == 0.0 { 1.0 } else { x.abs() };
1014 *x += sign * rel_width * scale;
1015 }
1016 };
1017 apply(
1024 Rc::get_mut(&mut self.x_l).expect("relax_bounds: x_l is uniquely owned"),
1025 -1.0,
1026 );
1027 apply(
1028 Rc::get_mut(&mut self.x_u).expect("relax_bounds: x_u is uniquely owned"),
1029 1.0,
1030 );
1031 apply_d(
1032 Rc::get_mut(&mut self.d_l).expect("relax_bounds: d_l is uniquely owned"),
1033 -1.0,
1034 );
1035 apply_d(
1036 Rc::get_mut(&mut self.d_u).expect("relax_bounds: d_u is uniquely owned"),
1037 1.0,
1038 );
1039 }
1040
1041 pub fn determine_scaling_from_starting_point(
1063 &mut self,
1064 method: ScalingMethod,
1065 max_gradient: Number,
1066 min_value: Number,
1067 obj_target_gradient: Number,
1068 constr_target_gradient: Number,
1069 ) {
1070 let user_obj_factor = self.scaling.obj_scaling();
1073 if matches!(method, ScalingMethod::None) {
1074 self.obj_scale_factor.set(user_obj_factor);
1075 *self.c_scale.borrow_mut() = None;
1076 *self.d_scale.borrow_mut() = None;
1077 self.invalidate_eval_caches();
1078 return;
1079 }
1080
1081 let cls = self.adapter.borrow().classification().clone();
1083 let n_full_x = cls.n_full_x as usize;
1084 let n_full_g = cls.n_full_g as usize;
1085 let mut full_x = vec![0.0; n_full_x];
1086 let mut full_z_l = vec![0.0; n_full_x];
1087 let mut full_z_u = vec![0.0; n_full_x];
1088 let mut full_lambda = vec![0.0; n_full_g];
1089 let starting_ok = {
1090 let a = self.adapter.borrow();
1091 let mut t = a.tnlp().borrow_mut();
1092 t.get_starting_point(StartingPoint {
1093 init_x: true,
1094 x: &mut full_x,
1095 init_z: false,
1096 z_l: &mut full_z_l,
1097 z_u: &mut full_z_u,
1098 init_lambda: false,
1099 lambda: &mut full_lambda,
1100 })
1101 };
1102 if !starting_ok {
1103 self.obj_scale_factor.set(user_obj_factor);
1105 *self.c_scale.borrow_mut() = None;
1106 *self.d_scale.borrow_mut() = None;
1107 self.invalidate_eval_caches();
1108 return;
1109 }
1110
1111 for (i, &full_idx) in cls.x_fixed_map.iter().enumerate() {
1124 full_x[full_idx as usize] = cls.x_fixed_vals[i];
1125 }
1126
1127 match method {
1128 ScalingMethod::None => unreachable!("handled above"),
1129 ScalingMethod::GradientBased => {
1130 self.scale_gradient_based(
1131 &cls,
1132 &full_x,
1133 user_obj_factor,
1134 max_gradient,
1135 min_value,
1136 obj_target_gradient,
1137 constr_target_gradient,
1138 );
1139 }
1140 ScalingMethod::UserScaling => {
1141 let applied = self.scale_user_supplied(&cls, user_obj_factor, min_value);
1142 if !applied {
1143 self.obj_scale_factor.set(user_obj_factor);
1147 *self.c_scale.borrow_mut() = None;
1148 *self.d_scale.borrow_mut() = None;
1149 }
1150 }
1151 }
1152
1153 self.apply_d_scale_to_bounds();
1156
1157 self.invalidate_eval_caches();
1160 }
1161
1162 fn scale_gradient_based(
1165 &self,
1166 cls: &BoundClassification,
1167 full_x: &[Number],
1168 user_obj_factor: Number,
1169 max_gradient: Number,
1170 min_value: Number,
1171 obj_target_gradient: Number,
1172 constr_target_gradient: Number,
1173 ) {
1174 let n_full_x = cls.n_full_x as usize;
1175 let n_full_g = cls.n_full_g as usize;
1176
1177 let mut full_grad_f = vec![0.0; n_full_x];
1179 let grad_ok = {
1180 let a = self.adapter.borrow();
1181 let mut t = a.tnlp().borrow_mut();
1182 t.eval_grad_f(full_x, true, &mut full_grad_f)
1183 };
1184 let mut df = 1.0;
1185 if grad_ok {
1186 let mut max_grad_f: Number = 0.0;
1189 for &full_idx in cls.x_not_fixed_map.iter() {
1190 let v = full_grad_f[full_idx as usize].abs();
1191 if v > max_grad_f {
1192 max_grad_f = v;
1193 }
1194 }
1195 df = gradient_obj_scale(max_grad_f, max_gradient, min_value, obj_target_gradient);
1196 }
1197 self.computed_obj_scale.set(df);
1198 self.obj_scale_factor.set(df * user_obj_factor);
1199
1200 if cls.n_full_g == 0 {
1202 *self.c_scale.borrow_mut() = None;
1203 *self.d_scale.borrow_mut() = None;
1204 return;
1205 }
1206 let mut full_jac_vals = vec![0.0; self.nnz_jac_g_full as usize];
1208 let jac_ok = {
1209 let a = self.adapter.borrow();
1210 let mut t = a.tnlp().borrow_mut();
1211 t.eval_jac_g(
1212 Some(full_x),
1213 true,
1214 SparsityRequest::Values {
1215 values: &mut full_jac_vals,
1216 },
1217 )
1218 };
1219 if !jac_ok {
1220 *self.c_scale.borrow_mut() = None;
1221 *self.d_scale.borrow_mut() = None;
1222 return;
1223 }
1224 let mut full_irow = vec![0 as Index; self.nnz_jac_g_full as usize];
1226 let mut full_jcol = vec![0 as Index; self.nnz_jac_g_full as usize];
1227 let _ = {
1228 let a = self.adapter.borrow();
1229 let mut t = a.tnlp().borrow_mut();
1230 t.eval_jac_g(
1231 None,
1232 false,
1233 SparsityRequest::Structure {
1234 irow: &mut full_irow,
1235 jcol: &mut full_jcol,
1236 },
1237 )
1238 };
1239 let style_offset: Index = match self.info.index_style {
1240 crate::tnlp::IndexStyle::C => 0,
1241 crate::tnlp::IndexStyle::Fortran => 1,
1242 };
1243 let mut g_to_c = vec![-1 as Index; n_full_g];
1245 for (c_idx, &g_idx) in cls.c_map.iter().enumerate() {
1246 g_to_c[g_idx as usize] = c_idx as Index;
1247 }
1248 let mut g_to_d = vec![-1 as Index; n_full_g];
1249 for (d_idx, &g_idx) in cls.d_map.iter().enumerate() {
1250 g_to_d[g_idx as usize] = d_idx as Index;
1251 }
1252 let n_c = cls.n_c as usize;
1253 let n_d = cls.n_d as usize;
1254 let dbl_min = Number::MIN_POSITIVE;
1256 let mut c_row_max: Vec<Number> = vec![dbl_min; n_c];
1257 let mut d_row_max: Vec<Number> = vec![dbl_min; n_d];
1258 for k in 0..self.nnz_jac_g_full as usize {
1259 let g_row_0 = (full_irow[k] - style_offset) as usize;
1260 let v = full_jac_vals[k].abs();
1261 let cr = g_to_c[g_row_0];
1262 if cr >= 0 {
1263 let row = cr as usize;
1264 if v > c_row_max[row] {
1265 c_row_max[row] = v;
1266 }
1267 } else {
1268 let dr = g_to_d[g_row_0];
1269 if dr >= 0 {
1270 let row = dr as usize;
1271 if v > d_row_max[row] {
1272 d_row_max[row] = v;
1273 }
1274 }
1275 }
1276 }
1277
1278 let row_max_to_scale = |row_max: Number| -> Number {
1279 gradient_row_scale(row_max, max_gradient, min_value, constr_target_gradient)
1280 };
1281 let any_row_above = |rows: &[Number]| -> bool {
1282 gradient_scaling_fires(rows, max_gradient, constr_target_gradient)
1283 };
1284
1285 if n_c > 0 && any_row_above(&c_row_max) {
1286 let dc: Vec<Number> = c_row_max.iter().map(|&v| row_max_to_scale(v)).collect();
1287 *self.c_scale.borrow_mut() = Some(dc);
1288 } else {
1289 *self.c_scale.borrow_mut() = None;
1290 }
1291
1292 if n_d > 0 && any_row_above(&d_row_max) {
1293 let dd: Vec<Number> = d_row_max.iter().map(|&v| row_max_to_scale(v)).collect();
1294 *self.d_scale.borrow_mut() = Some(dd);
1295 } else {
1296 *self.d_scale.borrow_mut() = None;
1297 }
1298 }
1299
1300 fn scale_user_supplied(
1317 &self,
1318 cls: &BoundClassification,
1319 user_obj_factor: Number,
1320 min_value: Number,
1321 ) -> bool {
1322 let n_full_x = cls.n_full_x as usize;
1323 let n_full_g = cls.n_full_g as usize;
1324 let mut obj_scaling: Number = 1.0;
1325 let mut use_x_scaling = false;
1326 let mut x_scaling = vec![1.0; n_full_x];
1327 let mut use_g_scaling = false;
1328 let mut g_scaling = vec![1.0; n_full_g];
1329 let ok = {
1330 let a = self.adapter.borrow();
1331 let mut t = a.tnlp().borrow_mut();
1332 t.get_scaling_parameters(ScalingRequest {
1333 obj_scaling: &mut obj_scaling,
1334 use_x_scaling: &mut use_x_scaling,
1335 x_scaling: &mut x_scaling,
1336 use_g_scaling: &mut use_g_scaling,
1337 g_scaling: &mut g_scaling,
1338 })
1339 };
1340 if !ok {
1341 return false;
1342 }
1343
1344 let mut df = obj_scaling;
1348 if df.abs() < min_value {
1349 df = df.signum().max(0.0).max(1.0) * min_value;
1352 }
1353 self.obj_scale_factor.set(df * user_obj_factor);
1354
1355 if use_g_scaling && g_scaling.len() == n_full_g {
1357 let n_c = cls.n_c as usize;
1358 let n_d = cls.n_d as usize;
1359 let mut dc = vec![1.0; n_c];
1360 for (c_idx, &g_idx) in cls.c_map.iter().enumerate() {
1361 let s = g_scaling[g_idx as usize];
1362 dc[c_idx] = if s < min_value { min_value } else { s };
1363 }
1364 let mut dd = vec![1.0; n_d];
1365 for (d_idx, &g_idx) in cls.d_map.iter().enumerate() {
1366 let s = g_scaling[g_idx as usize];
1367 dd[d_idx] = if s < min_value { min_value } else { s };
1368 }
1369 let nontrivial_c = dc.iter().any(|&s| s != 1.0);
1372 *self.c_scale.borrow_mut() = if nontrivial_c && n_c > 0 {
1373 Some(dc)
1374 } else {
1375 None
1376 };
1377 let nontrivial_d = dd.iter().any(|&s| s != 1.0);
1378 *self.d_scale.borrow_mut() = if nontrivial_d && n_d > 0 {
1379 Some(dd)
1380 } else {
1381 None
1382 };
1383 } else {
1384 *self.c_scale.borrow_mut() = None;
1385 *self.d_scale.borrow_mut() = None;
1386 }
1387 if use_x_scaling && x_scaling.iter().any(|&s| s != 1.0) {
1391 self.x_scaling_rejected.set(true);
1392 }
1393 true
1394 }
1395
1396 pub fn user_x_scaling_rejected(&self) -> bool {
1402 self.x_scaling_rejected.get()
1403 }
1404
1405 fn apply_d_scale_to_bounds(&mut self) {
1410 let cls = self.adapter.borrow().classification().clone();
1411 if let Some(dd) = self.d_scale.borrow().as_ref() {
1412 if let Some(d_l) = Rc::get_mut(&mut self.d_l) {
1413 let xs = d_l.values_mut();
1414 for (i, slot) in xs.iter_mut().enumerate() {
1415 let d_idx = cls.d_l_map[i] as usize;
1416 *slot *= dd[d_idx];
1417 }
1418 }
1419 if let Some(d_u) = Rc::get_mut(&mut self.d_u) {
1420 let xs = d_u.values_mut();
1421 for (i, slot) in xs.iter_mut().enumerate() {
1422 let d_idx = cls.d_u_map[i] as usize;
1423 *slot *= dd[d_idx];
1424 }
1425 }
1426 }
1427 }
1428
1429 fn invalidate_eval_caches(&self) {
1430 self.f_cache.borrow_mut().clear();
1431 self.grad_f_cache.borrow_mut().clear();
1432 self.c_cache.borrow_mut().clear();
1433 self.d_cache.borrow_mut().clear();
1434 self.jac_c_cache.borrow_mut().clear();
1435 self.jac_d_cache.borrow_mut().clear();
1436 self.h_cache.borrow_mut().clear();
1437 }
1438
1439 pub fn f_evals(&self) -> Index {
1440 *self.f_evals.borrow()
1441 }
1442 pub fn grad_f_evals(&self) -> Index {
1443 *self.grad_f_evals.borrow()
1444 }
1445 pub fn c_evals(&self) -> Index {
1446 *self.c_evals.borrow()
1447 }
1448 pub fn d_evals(&self) -> Index {
1449 *self.d_evals.borrow()
1450 }
1451 pub fn jac_c_evals(&self) -> Index {
1452 *self.jac_c_evals.borrow()
1453 }
1454 pub fn jac_d_evals(&self) -> Index {
1455 *self.jac_d_evals.borrow()
1456 }
1457 pub fn h_evals(&self) -> Index {
1458 *self.h_evals.borrow()
1459 }
1460
1461 pub fn lift_x_to_full(&self, x: &dyn Vector) -> Vec<Number> {
1467 let Some(dx) = x.as_any().downcast_ref::<DenseVector>() else {
1468 panic!("OrigIpoptNlp expects DenseVector for x");
1469 };
1470 let a = self.adapter.borrow();
1471 let cls = a.classification();
1472 let mut full = vec![0.0; cls.n_full_x as usize];
1473 let vals = dx.expanded_values();
1474 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
1475 full[full_idx as usize] = vals[var_idx];
1476 }
1477 for (i, &full_idx) in cls.x_fixed_map.iter().enumerate() {
1478 full[full_idx as usize] = cls.x_fixed_vals[i];
1479 }
1480 full
1481 }
1482
1483 pub fn set_honor_original_bounds(&self, on: bool) {
1487 self.honor_original_bounds.set(on);
1488 }
1489
1490 pub fn finalize_solution_x(&self, x: &dyn Vector) -> Vec<Number> {
1508 let mut full = self.lift_x_to_full(x);
1509 if !self.honor_original_bounds.get() {
1510 return full;
1511 }
1512 let cls = self.adapter.borrow().classification().clone();
1513 if let Some(x_l) = self.declared_x_l.borrow().as_ref() {
1516 for (i, &var_idx) in cls.x_l_map.iter().enumerate() {
1517 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
1518 if full[full_idx] < x_l[i] {
1519 full[full_idx] = x_l[i];
1520 }
1521 }
1522 }
1523 if let Some(x_u) = self.declared_x_u.borrow().as_ref() {
1524 for (i, &var_idx) in cls.x_u_map.iter().enumerate() {
1525 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
1526 if full[full_idx] > x_u[i] {
1527 full[full_idx] = x_u[i];
1528 }
1529 }
1530 }
1531 full
1532 }
1533
1534 pub fn finalize_solution_lambda(&self, y_c: &dyn Vector, y_d: &dyn Vector) -> Vec<Number> {
1546 let cls = self.adapter.borrow().classification().clone();
1547 let mut lambda = self.pack_lambda_for_user(y_c, y_d, &cls);
1548 let obj_scal = self.obj_scale_factor.get();
1549 if obj_scal != 0.0 && obj_scal != 1.0 {
1550 let inv = 1.0 / obj_scal;
1551 for v in lambda.iter_mut() {
1552 *v *= inv;
1553 }
1554 }
1555 lambda
1556 }
1557
1558 pub fn finalize_solution_z_l(&self, z_l: &dyn Vector) -> Vec<Number> {
1566 let cls = self.adapter.borrow().classification().clone();
1567 let n_full_x = cls.n_full_x as usize;
1568 let mut full_z_l = vec![0.0; n_full_x];
1569 let n_x_l = self.x_l.dim() as usize;
1570 if n_x_l == 0 {
1571 return full_z_l;
1572 }
1573 let Some(dz) = z_l.as_any().downcast_ref::<DenseVector>() else {
1574 panic!("OrigIpoptNlp::finalize_solution_z_l expects DenseVector");
1575 };
1576 let vals = dz.expanded_values();
1577 let obj_scal = self.obj_scale_factor.get();
1578 let inv = if obj_scal == 0.0 { 1.0 } else { 1.0 / obj_scal };
1579 for i in 0..n_x_l {
1580 let var_idx = cls.x_l_map[i] as usize;
1581 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1582 full_z_l[full_idx] = vals[i] * inv;
1583 }
1584 full_z_l
1585 }
1586
1587 pub fn finalize_solution_z_u(&self, z_u: &dyn Vector) -> Vec<Number> {
1590 let cls = self.adapter.borrow().classification().clone();
1591 let n_full_x = cls.n_full_x as usize;
1592 let mut full_z_u = vec![0.0; n_full_x];
1593 let n_x_u = self.x_u.dim() as usize;
1594 if n_x_u == 0 {
1595 return full_z_u;
1596 }
1597 let Some(dz) = z_u.as_any().downcast_ref::<DenseVector>() else {
1598 panic!("OrigIpoptNlp::finalize_solution_z_u expects DenseVector");
1599 };
1600 let vals = dz.expanded_values();
1601 let obj_scal = self.obj_scale_factor.get();
1602 let inv = if obj_scal == 0.0 { 1.0 } else { 1.0 / obj_scal };
1603 for i in 0..n_x_u {
1604 let var_idx = cls.x_u_map[i] as usize;
1605 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1606 full_z_u[full_idx] = vals[i] * inv;
1607 }
1608 full_z_u
1609 }
1610
1611 pub fn pack_lambda_for_user(
1621 &self,
1622 y_c: &dyn Vector,
1623 y_d: &dyn Vector,
1624 cls: &BoundClassification,
1625 ) -> Vec<Number> {
1626 let mut lambda = vec![0.0; cls.n_full_g as usize];
1627 if cls.n_c > 0 {
1628 let Some(dy) = y_c.as_any().downcast_ref::<DenseVector>() else {
1629 panic!("OrigIpoptNlp expects DenseVector for y_c");
1630 };
1631 let vals = dy.expanded_values();
1632 let cs = self.c_scale.borrow();
1633 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1634 lambda[g_idx as usize] = match cs.as_ref() {
1635 Some(v) => vals[i] * v[i],
1636 None => vals[i],
1637 };
1638 }
1639 }
1640 if cls.n_d > 0 {
1641 let Some(dy) = y_d.as_any().downcast_ref::<DenseVector>() else {
1642 panic!("OrigIpoptNlp expects DenseVector for y_d");
1643 };
1644 let vals = dy.expanded_values();
1645 let ds = self.d_scale.borrow();
1646 for (i, &g_idx) in cls.d_map.iter().enumerate() {
1647 lambda[g_idx as usize] = match ds.as_ref() {
1648 Some(v) => vals[i] * v[i],
1649 None => vals[i],
1650 };
1651 }
1652 }
1653 lambda
1654 }
1655
1656 fn fetch_warm_start_snapshot(&self) -> Option<StartingPointSnapshot> {
1659 let cls = self.adapter.borrow().classification().clone();
1660 let mut snapshot = StartingPointSnapshot {
1682 x: vec![0.0; cls.n_full_x as usize],
1683 z_l: vec![Number::NAN; cls.n_full_x as usize],
1684 z_u: vec![Number::NAN; cls.n_full_x as usize],
1685 lambda: vec![0.0; cls.n_full_g as usize],
1686 };
1687 let ok = {
1688 let a = self.adapter.borrow();
1689 let mut t = a.tnlp().borrow_mut();
1690 t.get_starting_point(StartingPoint {
1691 init_x: true,
1692 x: &mut snapshot.x,
1693 init_z: true,
1694 z_l: &mut snapshot.z_l,
1695 z_u: &mut snapshot.z_u,
1696 init_lambda: true,
1697 lambda: &mut snapshot.lambda,
1698 })
1699 };
1700 ok.then_some(snapshot)
1701 }
1702
1703 #[allow(clippy::too_many_arguments)]
1711 pub fn initialize_starting_point(
1712 &mut self,
1713 x: &mut DenseVector,
1714 init_x: bool,
1715 y_c: &mut DenseVector,
1716 init_y_c: bool,
1717 y_d: &mut DenseVector,
1718 init_y_d: bool,
1719 z_l: &mut DenseVector,
1720 init_z_l: bool,
1721 z_u: &mut DenseVector,
1722 init_z_u: bool,
1723 ) -> bool {
1724 let n_full_x = self.adapter.borrow().classification().n_full_x as usize;
1725 let n_full_g = self.adapter.borrow().classification().n_full_g as usize;
1726 let n_x_l = self.x_l.dim() as usize;
1727 let n_x_u = self.x_u.dim() as usize;
1728
1729 let mut full_x = vec![0.0; n_full_x];
1730 let mut full_z_l = vec![Number::NAN; n_full_x];
1735 let mut full_z_u = vec![Number::NAN; n_full_x];
1736 let mut full_lambda = vec![0.0; n_full_g];
1737
1738 let ok = {
1739 let a = self.adapter.borrow();
1740 let mut t = a.tnlp().borrow_mut();
1741 t.get_starting_point(StartingPoint {
1742 init_x,
1743 x: &mut full_x,
1744 init_z: init_z_l || init_z_u,
1745 z_l: &mut full_z_l,
1746 z_u: &mut full_z_u,
1747 init_lambda: init_y_c || init_y_d,
1748 lambda: &mut full_lambda,
1749 })
1750 };
1751 if !ok {
1752 return false;
1753 }
1754
1755 let cls = self.adapter.borrow().classification().clone();
1756 let obj_scal = self.obj_scale_factor.get();
1757 let c_scale = self.c_scale.borrow();
1758 let d_scale = self.d_scale.borrow();
1759
1760 if init_x {
1762 let xs = x.values_mut();
1763 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
1764 xs[var_idx] = full_x[full_idx as usize];
1765 }
1766 }
1767 if init_y_c && cls.n_c > 0 {
1773 let yc = y_c.values_mut();
1774 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1775 let cs = c_scale.as_ref().map(|v| v[i]).unwrap_or(1.0);
1776 yc[i] = full_lambda[g_idx as usize] / cs * obj_scal;
1777 }
1778 }
1779 if init_y_d && cls.n_d > 0 {
1780 let yd = y_d.values_mut();
1781 for (i, &g_idx) in cls.d_map.iter().enumerate() {
1782 let ds = d_scale.as_ref().map(|v| v[i]).unwrap_or(1.0);
1783 yd[i] = full_lambda[g_idx as usize] / ds * obj_scal;
1784 }
1785 }
1786 if init_z_l && n_x_l > 0 {
1788 let zl = z_l.values_mut();
1789 for (i, slot) in zl.iter_mut().enumerate().take(n_x_l) {
1790 let var_idx = cls.x_l_map[i] as usize;
1791 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1792 *slot = full_z_l[full_idx] * obj_scal;
1793 }
1794 }
1795 if init_z_u && n_x_u > 0 {
1796 let zu = z_u.values_mut();
1797 for (i, slot) in zu.iter_mut().enumerate().take(n_x_u) {
1798 let var_idx = cls.x_u_map[i] as usize;
1799 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1800 *slot = full_z_u[full_idx] * obj_scal;
1801 }
1802 }
1803 true
1804 }
1805
1806 fn eval_f_internal(&self, x: &dyn Vector) -> Number {
1809 if let Some(v) = self.f_cache.borrow().get_1dep(x.as_tagged()) {
1810 return v;
1811 }
1812 *self.f_evals.borrow_mut() += 1;
1813 let full_x = self.lift_x_to_full(x);
1814 let unscaled = {
1815 let a = self.adapter.borrow();
1816 let mut t = a.tnlp().borrow_mut();
1817 t.eval_f(&full_x, true).unwrap_or(f64::NAN)
1822 };
1823 let scaled = unscaled * self.obj_scale_factor.get();
1824 self.f_cache.borrow_mut().add_1dep(scaled, x.as_tagged());
1825 scaled
1826 }
1827
1828 fn eval_grad_f_internal(&self, x: &dyn Vector) -> Rc<dyn Vector> {
1829 if self.const_deriv.grad_f
1832 && let Some(v) = self.grad_f_cache.borrow().get(&[], &[])
1833 {
1834 return v;
1835 }
1836 if let Some(v) = self.grad_f_cache.borrow().get_1dep(x.as_tagged()) {
1837 return v;
1838 }
1839 *self.grad_f_evals.borrow_mut() += 1;
1840 let full_x = self.lift_x_to_full(x);
1841 let mut full_g = vec![0.0; full_x.len()];
1842 let ok = {
1843 let a = self.adapter.borrow();
1844 let mut t = a.tnlp().borrow_mut();
1845 t.eval_grad_f(&full_x, true, &mut full_g)
1846 };
1847 if !ok {
1850 full_g.fill(f64::NAN);
1851 }
1852 let cls = self.adapter.borrow().classification().clone();
1854 let mut g_compressed = self.x_space.make_new_dense();
1855 let obj_scal = self.obj_scale_factor.get();
1856 {
1857 let gv = g_compressed.values_mut();
1858 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
1859 gv[var_idx] = full_g[full_idx as usize] * obj_scal;
1860 }
1861 }
1862 let reuse = self.const_deriv.grad_f && all_finite(g_compressed.values());
1867 let result: Rc<dyn Vector> = Rc::new(g_compressed);
1868 if reuse {
1869 self.grad_f_cache
1870 .borrow_mut()
1871 .add(Rc::clone(&result), &[], &[]);
1872 } else {
1873 self.grad_f_cache
1874 .borrow_mut()
1875 .add_1dep(Rc::clone(&result), x.as_tagged());
1876 }
1877 result
1878 }
1879
1880 fn full_g(&self, x: &dyn Vector) -> Rc<Vec<Number>> {
1886 if let Some(v) = self.full_g_cache.borrow().get_1dep(x.as_tagged()) {
1887 return v;
1888 }
1889 let n_full_g = self.adapter.borrow().classification().n_full_g as usize;
1890 let full_x = self.lift_x_to_full(x);
1891 let mut full_g = vec![0.0; n_full_g];
1892 let ok = {
1893 let a = self.adapter.borrow();
1894 let mut t = a.tnlp().borrow_mut();
1895 t.eval_g(&full_x, true, &mut full_g)
1896 };
1897 if !ok {
1898 full_g.fill(f64::NAN);
1899 }
1900 let result = Rc::new(full_g);
1901 self.full_g_cache
1902 .borrow_mut()
1903 .add_1dep(Rc::clone(&result), x.as_tagged());
1904 result
1905 }
1906
1907 fn full_jac_g(&self, x: &dyn Vector) -> Rc<Vec<Number>> {
1912 if let Some(v) = self.full_jac_g_cache.borrow().get_1dep(x.as_tagged()) {
1913 return v;
1914 }
1915 let mut full_vals = vec![0.0; self.nnz_jac_g_full as usize];
1916 let full_x = self.lift_x_to_full(x);
1917 let ok = {
1918 let a = self.adapter.borrow();
1919 let mut t = a.tnlp().borrow_mut();
1920 t.eval_jac_g(
1921 Some(&full_x),
1922 true,
1923 SparsityRequest::Values {
1924 values: &mut full_vals,
1925 },
1926 )
1927 };
1928 if !ok {
1929 full_vals.fill(f64::NAN);
1930 }
1931 let result = Rc::new(full_vals);
1932 self.full_jac_g_cache
1933 .borrow_mut()
1934 .add_1dep(Rc::clone(&result), x.as_tagged());
1935 result
1936 }
1937
1938 fn eval_c_internal(&self, x: &dyn Vector) -> Rc<dyn Vector> {
1939 let cls = self.adapter.borrow().classification().clone();
1940 if cls.n_c == 0 {
1941 if let Some(v) = self.c_cache.borrow().get_1dep(x.as_tagged()) {
1943 return v;
1944 }
1945 let v = self.c_space.make_new_dense();
1946 let result: Rc<dyn Vector> = Rc::new(v);
1947 self.c_cache
1948 .borrow_mut()
1949 .add_1dep(Rc::clone(&result), x.as_tagged());
1950 return result;
1951 }
1952 if let Some(v) = self.c_cache.borrow().get_1dep(x.as_tagged()) {
1953 return v;
1954 }
1955 *self.c_evals.borrow_mut() += 1;
1956 let full_g = self.full_g(x);
1961 let mut c = self.c_space.make_new_dense();
1962 {
1970 let cv = c.values_mut();
1971 let cs = self.c_scale.borrow();
1972 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1973 let raw = full_g[g_idx as usize] - self.c_rhs[i];
1974 cv[i] = match cs.as_ref() {
1975 Some(v) => raw * v[i],
1976 None => raw,
1977 };
1978 }
1979 }
1980 let result: Rc<dyn Vector> = Rc::new(c);
1981 self.c_cache
1982 .borrow_mut()
1983 .add_1dep(Rc::clone(&result), x.as_tagged());
1984 result
1985 }
1986
1987 fn eval_d_internal(&self, x: &dyn Vector) -> Rc<dyn Vector> {
1988 let cls = self.adapter.borrow().classification().clone();
1989 if cls.n_d == 0 {
1990 if let Some(v) = self.d_cache.borrow().get_1dep(x.as_tagged()) {
1991 return v;
1992 }
1993 let v = self.d_space.make_new_dense();
1994 let result: Rc<dyn Vector> = Rc::new(v);
1995 self.d_cache
1996 .borrow_mut()
1997 .add_1dep(Rc::clone(&result), x.as_tagged());
1998 return result;
1999 }
2000 if let Some(v) = self.d_cache.borrow().get_1dep(x.as_tagged()) {
2001 return v;
2002 }
2003 *self.d_evals.borrow_mut() += 1;
2004 let full_g = self.full_g(x);
2006 let mut d = self.d_space.make_new_dense();
2007 {
2008 let dv = d.values_mut();
2009 let ds = self.d_scale.borrow();
2010 for (i, &g_idx) in cls.d_map.iter().enumerate() {
2011 let raw = full_g[g_idx as usize];
2012 dv[i] = match ds.as_ref() {
2013 Some(v) => raw * v[i],
2014 None => raw,
2015 };
2016 }
2017 }
2018 let result: Rc<dyn Vector> = Rc::new(d);
2019 self.d_cache
2020 .borrow_mut()
2021 .add_1dep(Rc::clone(&result), x.as_tagged());
2022 result
2023 }
2024
2025 fn eval_jac_c_internal(&self, x: &dyn Vector) -> Rc<dyn Matrix> {
2026 if self.const_deriv.jac_c
2027 && let Some(m) = self.jac_c_cache.borrow().get(&[], &[])
2028 {
2029 return m;
2030 }
2031 if let Some(m) = self.jac_c_cache.borrow().get_1dep(x.as_tagged()) {
2032 return m;
2033 }
2034 *self.jac_c_evals.borrow_mut() += 1;
2035 let full_vals = self.full_jac_g(x);
2039 let mut jac_c = GenTMatrix::new(Rc::clone(&self.jac_c_space));
2040 {
2041 let cs = self.c_scale.borrow();
2042 let irows = self.jac_c_space.irows().to_vec();
2043 let vs = jac_c.values_mut();
2044 for (k, &src) in self.jac_c_entry_in_g.iter().enumerate() {
2045 let raw = full_vals[src as usize];
2046 vs[k] = match cs.as_ref() {
2047 Some(v) => raw * v[(irows[k] - 1) as usize],
2049 None => raw,
2050 };
2051 }
2052 }
2053 let reuse = self.const_deriv.jac_c && all_finite(jac_c.values());
2054 let result: Rc<dyn Matrix> = Rc::new(jac_c);
2055 if reuse {
2056 self.jac_c_cache
2057 .borrow_mut()
2058 .add(Rc::clone(&result), &[], &[]);
2059 } else {
2060 self.jac_c_cache
2061 .borrow_mut()
2062 .add_1dep(Rc::clone(&result), x.as_tagged());
2063 }
2064 result
2065 }
2066
2067 fn eval_jac_d_internal(&self, x: &dyn Vector) -> Rc<dyn Matrix> {
2068 if self.const_deriv.jac_d
2069 && let Some(m) = self.jac_d_cache.borrow().get(&[], &[])
2070 {
2071 return m;
2072 }
2073 if let Some(m) = self.jac_d_cache.borrow().get_1dep(x.as_tagged()) {
2074 return m;
2075 }
2076 *self.jac_d_evals.borrow_mut() += 1;
2077 let full_vals = self.full_jac_g(x);
2080 let mut jac_d = GenTMatrix::new(Rc::clone(&self.jac_d_space));
2081 {
2082 let ds = self.d_scale.borrow();
2083 let irows = self.jac_d_space.irows().to_vec();
2084 let vs = jac_d.values_mut();
2085 for (k, &src) in self.jac_d_entry_in_g.iter().enumerate() {
2086 let raw = full_vals[src as usize];
2087 vs[k] = match ds.as_ref() {
2088 Some(v) => raw * v[(irows[k] - 1) as usize],
2089 None => raw,
2090 };
2091 }
2092 }
2093 let reuse = self.const_deriv.jac_d && all_finite(jac_d.values());
2094 let result: Rc<dyn Matrix> = Rc::new(jac_d);
2095 if reuse {
2096 self.jac_d_cache
2097 .borrow_mut()
2098 .add(Rc::clone(&result), &[], &[]);
2099 } else {
2100 self.jac_d_cache
2101 .borrow_mut()
2102 .add_1dep(Rc::clone(&result), x.as_tagged());
2103 }
2104 result
2105 }
2106
2107 fn eval_h_internal(
2108 &self,
2109 x: &dyn Vector,
2110 obj_factor: Number,
2111 y_c: &dyn Vector,
2112 y_d: &dyn Vector,
2113 ) -> Rc<dyn SymMatrix> {
2114 if self.const_deriv.hessian
2126 && let Some(m) = self.h_cache.borrow().get(&[], &[obj_factor])
2127 {
2128 return m;
2129 }
2130 if let Some(m) = self.h_cache.borrow().get(
2131 &[x.as_tagged(), y_c.as_tagged(), y_d.as_tagged()],
2132 &[obj_factor],
2133 ) {
2134 return m;
2135 }
2136 *self.h_evals.borrow_mut() += 1;
2137 let Some(h_space) = self.h_space.as_ref() else {
2138 panic!(
2139 "OrigIpoptNlp::eval_h called but the TNLP did not provide \
2140 eval_h sparsity. The L-BFGS path lands in Phase 8."
2141 );
2142 };
2143 let cls = self.adapter.borrow().classification().clone();
2144 let full_x = self.lift_x_to_full(x);
2145 let full_lambda = self.pack_lambda_for_user(y_c, y_d, &cls);
2153 let scaled_obj_factor = obj_factor * self.obj_scale_factor.get();
2154
2155 let mut full_vals = vec![0.0; self.nnz_h_lag_full as usize];
2160 let ok = {
2161 let a = self.adapter.borrow();
2162 let mut t = a.tnlp().borrow_mut();
2163 t.eval_h(
2164 Some(&full_x),
2165 true,
2166 scaled_obj_factor,
2167 Some(&full_lambda),
2168 true,
2169 SparsityRequest::Values {
2170 values: &mut full_vals,
2171 },
2172 )
2173 };
2174 if !ok {
2175 full_vals.fill(f64::NAN);
2176 }
2177 let mut h = SymTMatrix::new(Rc::clone(h_space));
2178 let kept = h_space.nonzeros() as usize;
2179 let h_vals = h.values_mut();
2180 debug_assert_eq!(kept, self.h_entry_in_full.len());
2183 for (k, &src) in self.h_entry_in_full.iter().enumerate() {
2184 h_vals[k] = full_vals[src as usize];
2185 }
2186 let reuse = self.const_deriv.hessian && all_finite(h.values());
2187 let result: Rc<dyn SymMatrix> = Rc::new(h);
2188 if reuse {
2189 self.h_cache
2190 .borrow_mut()
2191 .add(Rc::clone(&result), &[], &[obj_factor]);
2192 } else {
2193 self.h_cache.borrow_mut().add(
2194 Rc::clone(&result),
2195 &[x.as_tagged(), y_c.as_tagged(), y_d.as_tagged()],
2196 &[obj_factor],
2197 );
2198 }
2199 result
2200 }
2201}
2202
2203fn all_finite(v: &[Number]) -> bool {
2210 v.iter().all(|x| x.is_finite())
2211}
2212
2213fn make_dense_from(
2214 space: &Rc<DenseVectorSpace>,
2215 mut f: impl FnMut(usize) -> Number,
2216) -> DenseVector {
2217 let mut v = space.make_new_dense();
2218 let dim = space.dim() as usize;
2219 if dim > 0 {
2220 let vs = v.values_mut();
2221 for (i, slot) in vs.iter_mut().enumerate().take(dim) {
2222 *slot = f(i);
2223 }
2224 }
2225 v
2226}
2227
2228impl Nlp for OrigIpoptNlp {
2231 fn n(&self) -> Index {
2232 self.x_space.dim()
2233 }
2234 fn m_eq(&self) -> Index {
2235 self.c_space.dim()
2236 }
2237 fn m_ineq(&self) -> Index {
2238 self.d_space.dim()
2239 }
2240
2241 fn eval_f(&mut self, x: &dyn Vector) -> Number {
2242 self.timed_eval(|t| &t.eval_obj, || self.eval_f_internal(x))
2243 }
2244 fn eval_grad_f(&mut self, x: &dyn Vector, g: &mut dyn Vector) {
2245 let result = self.timed_eval(|t| &t.eval_grad_obj, || self.eval_grad_f_internal(x));
2246 g.copy(&*result);
2247 }
2248 fn eval_c(&mut self, x: &dyn Vector, c: &mut dyn Vector) {
2249 let result = self.timed_eval(|t| &t.eval_constr, || self.eval_c_internal(x));
2250 c.copy(&*result);
2251 }
2252 fn eval_d(&mut self, x: &dyn Vector, d: &mut dyn Vector) {
2253 let result = self.timed_eval(|t| &t.eval_constr, || self.eval_d_internal(x));
2254 d.copy(&*result);
2255 }
2256 fn eval_jac_c(&mut self, x: &dyn Vector) -> Rc<dyn Matrix> {
2257 self.timed_eval(|t| &t.eval_constr_jac, || self.eval_jac_c_internal(x))
2258 }
2259 fn eval_jac_d(&mut self, x: &dyn Vector) -> Rc<dyn Matrix> {
2260 self.timed_eval(|t| &t.eval_constr_jac, || self.eval_jac_d_internal(x))
2261 }
2262 fn eval_h(
2263 &mut self,
2264 x: &dyn Vector,
2265 obj_factor: Number,
2266 y_c: &dyn Vector,
2267 y_d: &dyn Vector,
2268 ) -> Rc<dyn SymMatrix> {
2269 self.timed_eval(
2270 |t| &t.eval_lag_hess,
2271 || self.eval_h_internal(x, obj_factor, y_c, y_d),
2272 )
2273 }
2274}
2275
2276impl IpoptNlp for OrigIpoptNlp {
2277 fn uninitialized_h(&self) -> Rc<dyn SymMatrix> {
2283 match self.h_space.as_ref() {
2284 Some(space) => Rc::new(crate::ipopt_nlp::zeroed_sym_t(Rc::clone(space))),
2285 None => Rc::new(crate::ipopt_nlp::zeroed_sym_t(SymTMatrixSpace::new(
2286 self.x_space.dim(),
2287 Vec::new(),
2288 Vec::new(),
2289 ))),
2290 }
2291 }
2292
2293 fn eval_counts(&self) -> [Index; 7] {
2294 [
2295 self.f_evals(),
2296 self.grad_f_evals(),
2297 self.c_evals(),
2298 self.d_evals(),
2299 self.jac_c_evals(),
2300 self.jac_d_evals(),
2301 self.h_evals(),
2302 ]
2303 }
2304 fn x_l(&self) -> &dyn Vector {
2305 &*self.x_l
2306 }
2307 fn x_u(&self) -> &dyn Vector {
2308 &*self.x_u
2309 }
2310 fn d_l(&self) -> &dyn Vector {
2311 &*self.d_l
2312 }
2313 fn d_u(&self) -> &dyn Vector {
2314 &*self.d_u
2315 }
2316
2317 fn declared_d_bounds(&self) -> Option<(Vec<Number>, Vec<Number>)> {
2318 let mut dl = self.declared_d_l.borrow().clone()?;
2319 let mut du = self.declared_d_u.borrow().clone()?;
2320 if let Some(dd) = self.d_scale.borrow().as_ref() {
2324 let cls = self.adapter.borrow().classification().clone();
2325 for (i, slot) in dl.iter_mut().enumerate() {
2326 *slot *= dd[cls.d_l_map[i] as usize];
2327 }
2328 for (i, slot) in du.iter_mut().enumerate() {
2329 *slot *= dd[cls.d_u_map[i] as usize];
2330 }
2331 }
2332 Some((dl, du))
2333 }
2334
2335 fn declared_box_violation(&self, x: &dyn Vector) -> Option<Number> {
2336 let x_l = self.declared_x_l.borrow();
2342 let x_u = self.declared_x_u.borrow();
2343 if x_l.is_none() && x_u.is_none() {
2344 return None;
2345 }
2346 let full = self.lift_x_to_full(x);
2347 let cls = self.adapter.borrow().classification().clone();
2348 let mut worst = 0.0_f64;
2349 if let Some(x_l) = x_l.as_ref() {
2350 for (i, &var_idx) in cls.x_l_map.iter().enumerate() {
2351 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
2352 let viol = x_l[i] - full[full_idx];
2353 if viol.is_finite() && viol > worst {
2354 worst = viol;
2355 }
2356 }
2357 }
2358 if let Some(x_u) = x_u.as_ref() {
2359 for (i, &var_idx) in cls.x_u_map.iter().enumerate() {
2360 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
2361 let viol = full[full_idx] - x_u[i];
2362 if viol.is_finite() && viol > worst {
2363 worst = viol;
2364 }
2365 }
2366 }
2367 Some(worst)
2368 }
2369
2370 fn declared_x_bounds(&self) -> Option<(Vec<Number>, Vec<Number>)> {
2371 let xl = self.declared_x_l.borrow().clone()?;
2376 let xu = self.declared_x_u.borrow().clone()?;
2377 Some((xl, xu))
2378 }
2379
2380 fn declared_c_rhs(&self) -> Option<Vec<Number>> {
2381 let mut b = self.c_rhs.clone();
2387 if let Some(dc) = self.c_scale.borrow().as_ref() {
2388 for (i, slot) in b.iter_mut().enumerate() {
2389 *slot *= dc[i];
2390 }
2391 }
2392 Some(b)
2393 }
2394
2395 fn px_l(&self) -> Rc<dyn Matrix> {
2396 Rc::clone(&self.px_l)
2397 }
2398 fn px_u(&self) -> Rc<dyn Matrix> {
2399 Rc::clone(&self.px_u)
2400 }
2401 fn pd_l(&self) -> Rc<dyn Matrix> {
2402 Rc::clone(&self.pd_l)
2403 }
2404 fn pd_u(&self) -> Rc<dyn Matrix> {
2405 Rc::clone(&self.pd_u)
2406 }
2407
2408 fn adjust_variable_bounds(
2416 &mut self,
2417 new_x_l: &dyn Vector,
2418 new_x_u: &dyn Vector,
2419 new_d_l: &dyn Vector,
2420 new_d_u: &dyn Vector,
2421 ) {
2422 fn install(slot: &mut Rc<DenseVector>, new: &dyn Vector) {
2426 Rc::get_mut(slot)
2427 .expect("adjust_variable_bounds: bound vector is uniquely owned")
2428 .copy(new);
2429 }
2430 install(&mut self.x_l, new_x_l);
2431 install(&mut self.x_u, new_x_u);
2432 install(&mut self.d_l, new_d_l);
2433 install(&mut self.d_u, new_d_u);
2434 }
2435
2436 fn obj_scaling_factor(&self) -> Number {
2437 self.obj_scale_factor.get()
2438 }
2439
2440 fn computed_obj_scaling_factor(&self) -> Number {
2441 self.computed_obj_scale.get()
2442 }
2443
2444 fn c_scale_vec(&self) -> Option<Vec<Number>> {
2445 self.c_scale.borrow().clone()
2446 }
2447
2448 fn d_scale_vec(&self) -> Option<Vec<Number>> {
2449 self.d_scale.borrow().clone()
2450 }
2451
2452 fn split_space_names(&self) -> Option<SplitNames> {
2466 let a = self.adapter.borrow();
2467 let cls = a.classification();
2468
2469 let mut var_meta = MetaData::default();
2470 let mut con_meta = MetaData::default();
2471 if !a
2472 .tnlp()
2473 .borrow_mut()
2474 .get_var_con_metadata(&mut var_meta, &mut con_meta)
2475 {
2476 return None;
2477 }
2478
2479 let var_full = var_meta.strings.get(IDX_NAMES);
2482 let con_full = con_meta.strings.get(IDX_NAMES);
2483 if var_full.is_none() && con_full.is_none() {
2484 return None;
2485 }
2486
2487 let pick = |pool: Option<&Vec<String>>, full_idx: Index| -> Option<String> {
2490 pool.and_then(|v| v.get(full_idx as usize))
2491 .filter(|s| !s.is_empty())
2492 .cloned()
2493 };
2494
2495 let x_var = cls
2496 .x_not_fixed_map
2497 .iter()
2498 .map(|&full_idx| pick(var_full, full_idx))
2499 .collect();
2500 let eq = cls
2501 .c_map
2502 .iter()
2503 .map(|&full_idx| pick(con_full, full_idx))
2504 .collect();
2505 let ineq = cls
2506 .d_map
2507 .iter()
2508 .map(|&full_idx| pick(con_full, full_idx))
2509 .collect();
2510
2511 let names = SplitNames { x_var, eq, ineq };
2512 names.any_present().then_some(names)
2513 }
2514
2515 fn prepare_warm_start(&mut self) -> bool {
2516 let Some(snapshot) = self.fetch_warm_start_snapshot() else {
2517 return false;
2518 };
2519 *self.warm_start_snapshot.borrow_mut() = Some(snapshot);
2520 true
2521 }
2522
2523 fn finish_warm_start(&mut self) {
2524 self.warm_start_snapshot.borrow_mut().take();
2525 }
2526
2527 fn get_starting_x(&mut self, x: &mut dyn Vector) -> bool {
2531 let cls = self.adapter.borrow().classification().clone();
2532 if let Some(snapshot) = self.warm_start_snapshot.borrow().as_ref() {
2533 let Some(dx) = x.as_any_mut().downcast_mut::<DenseVector>() else {
2534 return false;
2535 };
2536 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
2537 dx.values_mut()[var_idx] = snapshot.x[full_idx as usize];
2538 }
2539 return true;
2540 }
2541 let n_full_x = cls.n_full_x as usize;
2542 let n_full_g = cls.n_full_g as usize;
2543 let mut full_x = vec![0.0; n_full_x];
2544 let mut full_z_l = vec![0.0; n_full_x];
2545 let mut full_z_u = vec![0.0; n_full_x];
2546 let mut full_lambda = vec![0.0; n_full_g];
2547 let ok = {
2548 let a = self.adapter.borrow();
2549 let mut t = a.tnlp().borrow_mut();
2550 t.get_starting_point(StartingPoint {
2551 init_x: true,
2552 x: &mut full_x,
2553 init_z: false,
2554 z_l: &mut full_z_l,
2555 z_u: &mut full_z_u,
2556 init_lambda: false,
2557 lambda: &mut full_lambda,
2558 })
2559 };
2560 if !ok {
2561 return false;
2562 }
2563 let Some(dx) = x.as_any_mut().downcast_mut::<DenseVector>() else {
2564 return false;
2565 };
2566 let xs = dx.values_mut();
2567 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
2568 xs[var_idx] = full_x[full_idx as usize];
2569 }
2570 true
2571 }
2572
2573 fn get_starting_y(&mut self, y_c: &mut dyn Vector, y_d: &mut dyn Vector) -> bool {
2574 let Some(y_c) = y_c.as_any_mut().downcast_mut::<DenseVector>() else {
2575 return false;
2576 };
2577 let Some(y_d) = y_d.as_any_mut().downcast_mut::<DenseVector>() else {
2578 return false;
2579 };
2580 if let Some(snapshot) = self.warm_start_snapshot.borrow().as_ref() {
2581 let cls = self.adapter.borrow().classification().clone();
2582 let obj_scal = self.obj_scale_factor.get();
2583 let c_scale = self.c_scale.borrow();
2584 for (i, &g_idx) in cls.c_map.iter().enumerate() {
2585 let cs = c_scale.as_ref().map(|v| v[i]).unwrap_or(1.0);
2586 y_c.values_mut()[i] = snapshot.lambda[g_idx as usize] / cs * obj_scal;
2587 }
2588 let d_scale = self.d_scale.borrow();
2589 for (i, &g_idx) in cls.d_map.iter().enumerate() {
2590 let ds = d_scale.as_ref().map(|v| v[i]).unwrap_or(1.0);
2591 y_d.values_mut()[i] = snapshot.lambda[g_idx as usize] / ds * obj_scal;
2592 }
2593 return true;
2594 }
2595 let mut x = DenseVectorSpace::new(self.n()).make_new_dense();
2596 let mut z_l = DenseVectorSpace::new(self.x_l.dim()).make_new_dense();
2597 let mut z_u = DenseVectorSpace::new(self.x_u.dim()).make_new_dense();
2598 self.initialize_starting_point(
2599 &mut x, false, y_c, true, y_d, true, &mut z_l, false, &mut z_u, false,
2600 )
2601 }
2602
2603 fn get_starting_z(
2604 &mut self,
2605 z_l: &mut dyn Vector,
2606 z_u: &mut dyn Vector,
2607 _v_l: &mut dyn Vector,
2608 _v_u: &mut dyn Vector,
2609 ) -> bool {
2610 let Some(z_l) = z_l.as_any_mut().downcast_mut::<DenseVector>() else {
2613 return false;
2614 };
2615 let Some(z_u) = z_u.as_any_mut().downcast_mut::<DenseVector>() else {
2616 return false;
2617 };
2618 if let Some(snapshot) = self.warm_start_snapshot.borrow().as_ref() {
2619 let cls = self.adapter.borrow().classification().clone();
2620 let obj_scal = self.obj_scale_factor.get();
2621 for (i, slot) in z_l.values_mut().iter_mut().enumerate() {
2622 let var_idx = cls.x_l_map[i] as usize;
2623 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
2624 *slot = snapshot.z_l[full_idx] * obj_scal;
2625 }
2626 for (i, slot) in z_u.values_mut().iter_mut().enumerate() {
2627 let var_idx = cls.x_u_map[i] as usize;
2628 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
2629 *slot = snapshot.z_u[full_idx] * obj_scal;
2630 }
2631 return true;
2632 }
2633 let mut x = DenseVectorSpace::new(self.n()).make_new_dense();
2634 let mut y_c = DenseVectorSpace::new(self.m_eq()).make_new_dense();
2635 let mut y_d = DenseVectorSpace::new(self.m_ineq()).make_new_dense();
2636 self.initialize_starting_point(
2637 &mut x, false, &mut y_c, false, &mut y_d, false, z_l, true, z_u, true,
2638 )
2639 }
2640
2641 fn lift_x_to_full(&self, x: &dyn Vector) -> Vec<Number> {
2642 OrigIpoptNlp::lift_x_to_full(self, x)
2643 }
2644
2645 fn finalize_solution_x(&self, x: &dyn Vector) -> Vec<Number> {
2646 OrigIpoptNlp::finalize_solution_x(self, x)
2647 }
2648
2649 fn n_full_x(&self) -> Index {
2650 self.adapter.borrow().classification().n_full_x
2651 }
2652
2653 fn n_full_g(&self) -> Index {
2654 self.adapter.borrow().classification().n_full_g
2655 }
2656
2657 fn pack_lambda_for_user(&self, y_c: &dyn Vector, y_d: &dyn Vector) -> Vec<Number> {
2658 let cls = self.adapter.borrow().classification().clone();
2659 OrigIpoptNlp::pack_lambda_for_user(self, y_c, y_d, &cls)
2660 }
2661
2662 fn pack_g_for_user(&self, c: &dyn Vector, d: &dyn Vector) -> Vec<Number> {
2663 let cls = self.adapter.borrow().classification().clone();
2664 let mut g = vec![0.0; cls.n_full_g as usize];
2665 if cls.n_c > 0 {
2666 let Some(dc) = c.as_any().downcast_ref::<DenseVector>() else {
2667 panic!("OrigIpoptNlp expects DenseVector for c");
2668 };
2669 let cs = self.c_scale.borrow();
2670 let c_vals = dc.expanded_values();
2680 for (i, &g_idx) in cls.c_map.iter().enumerate() {
2681 let v = c_vals[i];
2682 g[g_idx as usize] = match cs.as_ref() {
2683 Some(s) => v / s[i],
2684 None => v,
2685 };
2686 }
2687 }
2688 if cls.n_d > 0 {
2689 let Some(dd) = d.as_any().downcast_ref::<DenseVector>() else {
2690 panic!("OrigIpoptNlp expects DenseVector for d");
2691 };
2692 let ds = self.d_scale.borrow();
2693 let d_vals = dd.expanded_values();
2695 for (i, &g_idx) in cls.d_map.iter().enumerate() {
2696 let v = d_vals[i];
2697 g[g_idx as usize] = match ds.as_ref() {
2698 Some(s) => v / s[i],
2699 None => v,
2700 };
2701 }
2702 }
2703 g
2704 }
2705
2706 fn pack_z_l_for_user(&self, z_l: &dyn Vector) -> Vec<Number> {
2707 let cls = self.adapter.borrow().classification().clone();
2708 let mut full = vec![0.0; cls.n_full_x as usize];
2709 if z_l.dim() == 0 {
2710 return full;
2711 }
2712 let Some(dz) = z_l.as_any().downcast_ref::<DenseVector>() else {
2713 panic!("OrigIpoptNlp expects DenseVector for z_l");
2714 };
2715 let vals = dz.expanded_values();
2716 for (k, &var_idx) in cls.x_l_map.iter().enumerate() {
2717 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
2718 full[full_idx] = vals[k];
2719 }
2720 full
2721 }
2722
2723 fn pack_z_u_for_user(&self, z_u: &dyn Vector) -> Vec<Number> {
2724 let cls = self.adapter.borrow().classification().clone();
2725 let mut full = vec![0.0; cls.n_full_x as usize];
2726 if z_u.dim() == 0 {
2727 return full;
2728 }
2729 let Some(dz) = z_u.as_any().downcast_ref::<DenseVector>() else {
2730 panic!("OrigIpoptNlp expects DenseVector for z_u");
2731 };
2732 let vals = dz.expanded_values();
2733 for (k, &var_idx) in cls.x_u_map.iter().enumerate() {
2734 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
2735 full[full_idx] = vals[k];
2736 }
2737 full
2738 }
2739
2740 fn finalize_solution_lambda(&self, y_c: &dyn Vector, y_d: &dyn Vector) -> Vec<Number> {
2741 OrigIpoptNlp::finalize_solution_lambda(self, y_c, y_d)
2742 }
2743
2744 fn finalize_solution_z_l(&self, z_l: &dyn Vector) -> Vec<Number> {
2745 OrigIpoptNlp::finalize_solution_z_l(self, z_l)
2746 }
2747
2748 fn finalize_solution_z_u(&self, z_u: &dyn Vector) -> Vec<Number> {
2749 OrigIpoptNlp::finalize_solution_z_u(self, z_u)
2750 }
2751
2752 fn variable_scaling(&self) -> Option<Vec<Number>> {
2753 self.adapter.borrow().tnlp().borrow().scaling_factors()
2759 }
2760
2761 fn full_x_to_var_x(&self, full_idx: Index) -> Option<Index> {
2762 let cls = self.adapter.borrow();
2763 let cls = cls.classification();
2764 let f = full_idx as usize;
2765 if f >= cls.full_to_var.len() {
2766 return None;
2767 }
2768 let v = cls.full_to_var[f];
2769 if v < 0 { None } else { Some(v) }
2770 }
2771
2772 fn full_g_to_c_block(&self, full_idx: Index) -> Option<Index> {
2773 let cls = self.adapter.borrow();
2774 let cls = cls.classification();
2775 let f = full_idx as usize;
2776 if f >= cls.full_to_c.len() {
2777 return None;
2778 }
2779 let c = cls.full_to_c[f];
2780 if c < 0 { None } else { Some(c) }
2781 }
2782
2783 fn var_x_to_full_x(&self, var_idx: Index) -> Index {
2784 let cls = self.adapter.borrow();
2785 let cls = cls.classification();
2786 cls.x_not_fixed_map[var_idx as usize]
2787 }
2788}
2789
2790#[cfg(test)]
2793mod tests {
2794 use super::*;
2795 use crate::tnlp::{
2796 BoundsInfo, IndexStyle, IpoptCq, IpoptData, NlpInfo, Solution, SparsityRequest,
2797 StartingPoint, TNLP,
2798 };
2799
2800 #[derive(Default)]
2805 struct Hs071 {
2806 eval_f_calls: usize,
2807 eval_grad_f_calls: usize,
2808 eval_g_calls: usize,
2809 eval_jac_g_value_calls: usize,
2810 eval_h_value_calls: usize,
2811 get_bounds_info_calls: usize,
2812 get_starting_point_calls: usize,
2813 }
2814
2815 impl TNLP for Hs071 {
2816 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2817 Some(NlpInfo {
2818 n: 4,
2819 m: 2,
2820 nnz_jac_g: 8,
2821 nnz_h_lag: 10,
2822 index_style: IndexStyle::C,
2823 })
2824 }
2825 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2826 self.get_bounds_info_calls += 1;
2827 b.x_l.copy_from_slice(&[1.0; 4]);
2828 b.x_u.copy_from_slice(&[5.0; 4]);
2829 b.g_l.copy_from_slice(&[25.0, 40.0]);
2832 b.g_u.copy_from_slice(&[2.0e19, 40.0]);
2833 true
2834 }
2835 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2836 self.get_starting_point_calls += 1;
2837 sp.x.copy_from_slice(&[1.0, 5.0, 5.0, 1.0]);
2838 if sp.init_z {
2839 sp.z_l.copy_from_slice(&[1.0, 2.0, 3.0, 4.0]);
2840 sp.z_u.copy_from_slice(&[5.0, 6.0, 7.0, 8.0]);
2841 }
2842 if sp.init_lambda {
2843 sp.lambda.copy_from_slice(&[11.0, 13.0]);
2844 }
2845 true
2846 }
2847 fn eval_f(&mut self, x: &[Number], _new_x: bool) -> Option<Number> {
2848 self.eval_f_calls += 1;
2849 Some(x[0] * x[3] * (x[0] + x[1] + x[2]) + x[2])
2850 }
2851 fn eval_grad_f(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
2852 self.eval_grad_f_calls += 1;
2853 g[0] = x[3] * (2.0 * x[0] + x[1] + x[2]);
2858 g[1] = x[0] * x[3];
2859 g[2] = x[0] * x[3] + 1.0;
2860 g[3] = x[0] * (x[0] + x[1] + x[2]);
2861 true
2862 }
2863 fn eval_g(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
2864 self.eval_g_calls += 1;
2865 g[0] = x[0] * x[1] * x[2] * x[3];
2868 g[1] = x[0] * x[0] + x[1] * x[1] + x[2] * x[2] + x[3] * x[3];
2869 true
2870 }
2871 fn eval_jac_g(
2872 &mut self,
2873 x: Option<&[Number]>,
2874 _new_x: bool,
2875 mode: SparsityRequest<'_>,
2876 ) -> bool {
2877 match mode {
2878 SparsityRequest::Structure { irow, jcol } => {
2879 irow.copy_from_slice(&[0, 0, 0, 0, 1, 1, 1, 1]);
2881 jcol.copy_from_slice(&[0, 1, 2, 3, 0, 1, 2, 3]);
2882 }
2883 SparsityRequest::Values { values } => {
2884 self.eval_jac_g_value_calls += 1;
2885 let x = x.expect("eval_jac_g(Values) without x");
2886 values[0] = x[1] * x[2] * x[3];
2888 values[1] = x[0] * x[2] * x[3];
2889 values[2] = x[0] * x[1] * x[3];
2890 values[3] = x[0] * x[1] * x[2];
2891 values[4] = 2.0 * x[0];
2893 values[5] = 2.0 * x[1];
2894 values[6] = 2.0 * x[2];
2895 values[7] = 2.0 * x[3];
2896 }
2897 }
2898 true
2899 }
2900 fn eval_h(
2901 &mut self,
2902 x: Option<&[Number]>,
2903 _new_x: bool,
2904 obj_factor: Number,
2905 lambda: Option<&[Number]>,
2906 _new_lambda: bool,
2907 mode: SparsityRequest<'_>,
2908 ) -> bool {
2909 match mode {
2912 SparsityRequest::Structure { irow, jcol } => {
2913 irow.copy_from_slice(&[0, 1, 1, 2, 2, 2, 3, 3, 3, 3]);
2914 jcol.copy_from_slice(&[0, 0, 1, 0, 1, 2, 0, 1, 2, 3]);
2915 }
2916 SparsityRequest::Values { values } => {
2917 self.eval_h_value_calls += 1;
2918 let x = x.expect("eval_h(Values) without x");
2919 let lam = lambda.expect("eval_h(Values) without lambda");
2920 let of = obj_factor;
2921 let l0 = lam[0];
2931 let l1 = lam[1];
2932 values[0] = of * (2.0 * x[3]) + l1 * 2.0; values[1] = of * x[3] + l0 * (x[2] * x[3]); values[2] = l1 * 2.0; values[3] = of * x[3] + l0 * (x[1] * x[3]); values[4] = l0 * (x[0] * x[3]); values[5] = l1 * 2.0; values[6] = of * (2.0 * x[0] + x[1] + x[2]) + l0 * (x[1] * x[2]); values[7] = of * x[0] + l0 * (x[0] * x[2]); values[8] = of * x[0] + l0 * (x[0] * x[1]); values[9] = l1 * 2.0; }
2943 }
2944 true
2945 }
2946 fn finalize_solution(&mut self, _sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {}
2947 }
2948
2949 fn build_orig_nlp() -> (Rc<RefCell<TNLPAdapter>>, OrigIpoptNlp) {
2950 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071::default()));
2951 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2952 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2953 (adapter, nlp)
2954 }
2955
2956 fn dense_x(values: &[Number], space: &Rc<DenseVectorSpace>) -> DenseVector {
2957 let mut v = space.make_new_dense();
2958 v.values_mut().copy_from_slice(values);
2959 v
2960 }
2961
2962 #[test]
2963 fn dimensions_match_classification() {
2964 let (_, nlp) = build_orig_nlp();
2965 assert_eq!(nlp.n(), 4);
2967 assert_eq!(nlp.m_eq(), 1);
2968 assert_eq!(nlp.m_ineq(), 1);
2969 assert_eq!(nlp.jac_c_space().nonzeros(), 4);
2971 assert_eq!(nlp.jac_d_space().nonzeros(), 4);
2972 assert_eq!(nlp.h_space().unwrap().nonzeros(), 10);
2974 assert_eq!(nlp.x_l().dim(), 4);
2976 assert_eq!(nlp.x_u().dim(), 4);
2977 assert_eq!(nlp.d_l().dim(), 1);
2978 assert_eq!(nlp.d_u().dim(), 0);
2979 }
2980
2981 #[test]
2982 fn eval_f_at_starting_point() {
2983 let (_, mut nlp) = build_orig_nlp();
2984 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2985 assert_eq!(nlp.eval_f(&x), 16.0);
2987 assert_eq!(nlp.f_evals(), 1);
2988 }
2989
2990 #[test]
2991 fn eval_grad_f_at_starting_point() {
2992 let (_, mut nlp) = build_orig_nlp();
2993 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2994 let mut g = nlp.x_space().make_new_dense();
2995 nlp.eval_grad_f(&x, &mut g);
2996 assert_eq!(g.values(), &[12.0, 1.0, 2.0, 11.0]);
3001 assert_eq!(nlp.grad_f_evals(), 1);
3002 }
3003
3004 #[test]
3005 fn eval_c_returns_equality_residual() {
3006 let (_, mut nlp) = build_orig_nlp();
3007 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3008 let mut c = nlp.c_space().make_new_dense();
3009 nlp.eval_c(&x, &mut c);
3010 assert_eq!(c.values(), &[12.0]);
3012 assert_eq!(nlp.c_evals(), 1);
3013 }
3014
3015 #[test]
3016 fn eval_d_returns_inequality_value_unshifted() {
3017 let (_, mut nlp) = build_orig_nlp();
3018 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3019 let mut d = nlp.d_space().make_new_dense();
3020 nlp.eval_d(&x, &mut d);
3021 assert_eq!(d.values(), &[25.0]);
3023 assert_eq!(nlp.d_evals(), 1);
3024 }
3025
3026 #[test]
3027 fn cache_returns_without_re_eval() {
3028 let (_, mut nlp) = build_orig_nlp();
3029 let mut x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3030 let f1 = nlp.eval_f(&x);
3031 let f2 = nlp.eval_f(&x);
3032 assert_eq!(f1, f2);
3033 assert_eq!(nlp.f_evals(), 1, "second call must be served from cache");
3034 x.values_mut()[0] = 1.0; let _ = nlp.eval_f(&x);
3037 assert_eq!(nlp.f_evals(), 2);
3038 }
3039
3040 #[test]
3041 fn jac_c_picks_only_equality_rows() {
3042 let (_, mut nlp) = build_orig_nlp();
3043 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3044 let m = nlp.eval_jac_c(&x);
3045 let g = m
3046 .as_any()
3047 .downcast_ref::<GenTMatrix>()
3048 .expect("jac_c is a GenTMatrix");
3049 assert_eq!(g.values(), &[2.0, 10.0, 10.0, 2.0]);
3051 assert_eq!(g.irows(), &[1, 1, 1, 1]);
3053 assert_eq!(g.jcols(), &[1, 2, 3, 4]);
3054 }
3055
3056 #[test]
3057 fn jac_d_picks_only_inequality_rows() {
3058 let (_, mut nlp) = build_orig_nlp();
3059 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3060 let m = nlp.eval_jac_d(&x);
3061 let g = m
3062 .as_any()
3063 .downcast_ref::<GenTMatrix>()
3064 .expect("jac_d is a GenTMatrix");
3065 assert_eq!(g.values(), &[25.0, 5.0, 5.0, 25.0]);
3068 }
3069
3070 fn build_orig_nlp_counting() -> (Rc<RefCell<Hs071>>, OrigIpoptNlp) {
3075 let concrete = Rc::new(RefCell::new(Hs071::default()));
3076 let tnlp: Rc<RefCell<dyn TNLP>> = concrete.clone();
3077 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3078 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3079 (concrete, nlp)
3080 }
3081
3082 #[test]
3083 fn eval_c_and_eval_d_share_one_eval_g_per_iterate() {
3084 let (tnlp, mut nlp) = build_orig_nlp_counting();
3088 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3089 let mut c = nlp.c_space().make_new_dense();
3090 let mut d = nlp.d_space().make_new_dense();
3091 nlp.eval_c(&x, &mut c);
3092 nlp.eval_d(&x, &mut d);
3093 assert_eq!(
3094 tnlp.borrow().eval_g_calls,
3095 1,
3096 "eval_c + eval_d at one iterate must share a single user eval_g"
3097 );
3098 assert_eq!(nlp.c_evals(), 1);
3100 assert_eq!(nlp.d_evals(), 1);
3101 assert_eq!(c.values(), &[12.0]);
3103 assert_eq!(d.values(), &[25.0]);
3104
3105 let mut x2 = x;
3108 x2.values_mut()[0] = 2.0;
3109 nlp.eval_c(&x2, &mut c);
3110 nlp.eval_d(&x2, &mut d);
3111 assert_eq!(
3112 tnlp.borrow().eval_g_calls,
3113 2,
3114 "a new iterate triggers exactly one more shared eval_g"
3115 );
3116 }
3117
3118 #[test]
3119 fn eval_c_does_not_refetch_bounds_per_iterate() {
3120 let (tnlp, mut nlp) = build_orig_nlp_counting();
3128 let baseline = tnlp.borrow().get_bounds_info_calls;
3131
3132 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3133 let mut c = nlp.c_space().make_new_dense();
3134 nlp.eval_c(&x, &mut c);
3135 assert_eq!(c.values(), &[12.0]);
3137
3138 let mut x2 = x;
3140 for k in 0..5 {
3141 x2.values_mut()[0] = 2.0 + k as Number;
3142 nlp.eval_c(&x2, &mut c);
3143 }
3144
3145 assert_eq!(
3146 tnlp.borrow().get_bounds_info_calls,
3147 baseline,
3148 "eval_c must reuse the captured c_rhs, not re-fetch bounds per iterate"
3149 );
3150 }
3151
3152 #[test]
3153 fn eval_jac_c_and_eval_jac_d_share_one_eval_jac_g_per_iterate() {
3154 let (tnlp, mut nlp) = build_orig_nlp_counting();
3158 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3159 let _ = nlp.eval_jac_c(&x);
3160 let _ = nlp.eval_jac_d(&x);
3161 assert_eq!(
3162 tnlp.borrow().eval_jac_g_value_calls,
3163 1,
3164 "eval_jac_c + eval_jac_d at one iterate must share a single eval_jac_g"
3165 );
3166 assert_eq!(nlp.jac_c_evals(), 1);
3167 assert_eq!(nlp.jac_d_evals(), 1);
3168 }
3169
3170 #[test]
3171 fn starting_point_is_compressed_into_x_var() {
3172 let (_, mut nlp) = build_orig_nlp();
3173 let mut x = nlp.x_space().make_new_dense();
3174 let mut yc = nlp.c_space().make_new_dense();
3175 let mut yd = nlp.d_space().make_new_dense();
3176 let mut zl = nlp.x_l_space().make_new_dense();
3177 let mut zu = nlp.x_u_space().make_new_dense();
3178 let ok = nlp.initialize_starting_point(
3179 &mut x, true, &mut yc, false, &mut yd, false, &mut zl, false, &mut zu, false,
3180 );
3181 assert!(ok);
3182 assert_eq!(x.values(), &[1.0, 5.0, 5.0, 1.0]);
3183 }
3184
3185 #[test]
3186 fn warm_start_duals_are_forwarded_into_algorithm_vectors() {
3187 let (_, mut nlp) = build_orig_nlp();
3188 let mut y_c = nlp.c_space().make_new_dense();
3189 let mut y_d = nlp.d_space().make_new_dense();
3190 assert!(nlp.get_starting_y(&mut y_c, &mut y_d));
3191 assert_eq!(y_c.values(), &[13.0], "equality multiplier g1");
3192 assert_eq!(y_d.values(), &[11.0], "inequality multiplier g0");
3193
3194 let mut z_l = nlp.x_l_space().make_new_dense();
3195 let mut z_u = nlp.x_u_space().make_new_dense();
3196 let mut v_l = nlp.d_l_space().make_new_dense();
3197 let mut v_u = nlp.d_u_space().make_new_dense();
3198 assert!(nlp.get_starting_z(&mut z_l, &mut z_u, &mut v_l, &mut v_u));
3199 assert_eq!(z_l.values(), &[1.0, 2.0, 3.0, 4.0]);
3200 assert_eq!(z_u.values(), &[5.0, 6.0, 7.0, 8.0]);
3201 }
3202
3203 #[test]
3204 fn warm_start_prefetches_one_tnlp_snapshot_for_x_y_and_z() {
3205 let (tnlp, mut nlp) = build_orig_nlp_counting();
3206 assert!(nlp.prepare_warm_start());
3207
3208 let mut x = nlp.x_space().make_new_dense();
3209 let mut y_c = nlp.c_space().make_new_dense();
3210 let mut y_d = nlp.d_space().make_new_dense();
3211 let mut z_l = nlp.x_l_space().make_new_dense();
3212 let mut z_u = nlp.x_u_space().make_new_dense();
3213 let mut v_l = nlp.d_l_space().make_new_dense();
3214 let mut v_u = nlp.d_u_space().make_new_dense();
3215 assert!(nlp.get_starting_x(&mut x));
3216 assert!(nlp.get_starting_y(&mut y_c, &mut y_d));
3217 assert!(nlp.get_starting_z(&mut z_l, &mut z_u, &mut v_l, &mut v_u));
3218
3219 assert_eq!(tnlp.borrow().get_starting_point_calls, 1);
3220 assert_eq!(x.values(), &[1.0, 5.0, 5.0, 1.0]);
3221 assert_eq!(y_c.values(), &[13.0]);
3222 assert_eq!(y_d.values(), &[11.0]);
3223 assert_eq!(z_l.values(), &[1.0, 2.0, 3.0, 4.0]);
3224 assert_eq!(z_u.values(), &[5.0, 6.0, 7.0, 8.0]);
3225
3226 nlp.finish_warm_start();
3227 let mut x_after_init = nlp.x_space().make_new_dense();
3228 assert!(nlp.get_starting_x(&mut x_after_init));
3229 assert_eq!(
3230 tnlp.borrow().get_starting_point_calls,
3231 2,
3232 "the snapshot must not affect later starting-point requests"
3233 );
3234 }
3235
3236 struct OneFixedOneFree;
3241 impl TNLP for OneFixedOneFree {
3242 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3243 Some(NlpInfo {
3244 n: 2,
3245 m: 1,
3246 nnz_jac_g: 1,
3247 nnz_h_lag: 0,
3248 index_style: IndexStyle::C,
3249 })
3250 }
3251 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3252 b.x_l[0] = 7.0;
3253 b.x_u[0] = 7.0; b.x_l[1] = -1.0e19;
3255 b.x_u[1] = 1.0e19;
3256 b.g_l[0] = 0.0;
3257 b.g_u[0] = 0.0; true
3259 }
3260 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3261 sp.x[0] = 7.0;
3262 sp.x[1] = 0.5;
3263 true
3264 }
3265 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
3266 Some(x[1])
3267 }
3268 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
3269 g[0] = 0.0;
3270 g[1] = 1.0;
3271 true
3272 }
3273 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3274 g[0] = x[1];
3275 true
3276 }
3277 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
3278 match m {
3279 SparsityRequest::Structure { irow, jcol } => {
3280 irow[0] = 0;
3281 jcol[0] = 1;
3282 }
3283 SparsityRequest::Values { values } => values[0] = 1.0,
3284 }
3285 true
3286 }
3287 fn eval_h(
3288 &mut self,
3289 _: Option<&[Number]>,
3290 _: bool,
3291 _: Number,
3292 _: Option<&[Number]>,
3293 _: bool,
3294 _: SparsityRequest<'_>,
3295 ) -> bool {
3296 true
3297 }
3298 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3299 }
3300
3301 struct FixedVarReachModel {
3316 second_order: bool,
3317 }
3318 impl TNLP for FixedVarReachModel {
3319 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3320 Some(NlpInfo {
3321 n: 3,
3322 m: 2,
3323 nnz_jac_g: 4,
3324 nnz_h_lag: 3,
3325 index_style: IndexStyle::C,
3326 })
3327 }
3328 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3329 b.x_l.copy_from_slice(&[-1.0e19, -1.0e19, 2.0]);
3330 b.x_u.copy_from_slice(&[1.0e19, 1.0e19, 2.0]); b.g_l.copy_from_slice(&[0.0, 0.0]);
3332 b.g_u.copy_from_slice(&[0.0, 1.0e19]); true
3334 }
3335 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3336 sp.x.copy_from_slice(&[1.0, 1.0, 2.0]);
3337 true
3338 }
3339 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
3340 Some(x[0] * x[0] * x[0])
3341 }
3342 fn eval_grad_f(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3343 g.copy_from_slice(&[3.0 * x[0] * x[0], 0.0, 0.0]);
3344 true
3345 }
3346 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3347 g[0] = x[0] * x[1] - 1.0;
3348 g[1] = if self.second_order {
3349 x[1] * x[2]
3350 } else {
3351 x[1] * x[1] + x[2]
3352 };
3353 true
3354 }
3355 fn eval_jac_g(&mut self, x: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
3356 match m {
3357 SparsityRequest::Structure { irow, jcol } => {
3358 irow.copy_from_slice(&[0, 0, 1, 1]);
3361 jcol.copy_from_slice(&[0, 1, 1, 2]);
3362 }
3363 SparsityRequest::Values { values } => {
3364 let x = x.expect("values need x");
3365 values[0] = x[1];
3366 values[1] = x[0];
3367 let (d1, d2) = if self.second_order {
3368 (x[2], x[1])
3369 } else {
3370 (2.0 * x[1], 1.0)
3371 };
3372 values[2] = d1;
3373 values[3] = d2;
3374 }
3375 }
3376 true
3377 }
3378 fn eval_h(
3379 &mut self,
3380 x: Option<&[Number]>,
3381 _: bool,
3382 sigma: Number,
3383 lambda: Option<&[Number]>,
3384 _: bool,
3385 m: SparsityRequest<'_>,
3386 ) -> bool {
3387 match m {
3388 SparsityRequest::Structure { irow, jcol } => {
3389 irow.copy_from_slice(&[0, 1, if self.second_order { 2 } else { 1 }]);
3393 jcol.copy_from_slice(&[0, 0, 1]);
3394 }
3395 SparsityRequest::Values { values } => {
3396 let (x, l) = (x.expect("values need x"), lambda.expect("values need λ"));
3397 values[0] = sigma * 6.0 * x[0];
3398 values[1] = l[0];
3399 values[2] = if self.second_order { l[1] } else { 2.0 * l[1] };
3400 }
3401 }
3402 true
3403 }
3404 fn derivative_proofs(&mut self) -> DerivativeProofs {
3405 DerivativeProofs {
3406 grad_f: DerivativeProof::Varying,
3407 hessian: DerivativeProof::Varying,
3408 jac: vec![DerivativeProof::Varying; 2],
3409 }
3410 }
3411 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3412 }
3413
3414 fn reach_proofs(second_order: bool) -> [DerivativeProof; 4] {
3415 let tnlp: Rc<RefCell<dyn TNLP>> =
3416 Rc::new(RefCell::new(FixedVarReachModel { second_order }));
3417 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3418 let nlp = OrigIpoptNlp::new(adapter, Rc::new(NoScaling)).unwrap();
3419 nlp.derivative_proofs()
3420 }
3421
3422 #[test]
3431 fn only_the_forms_a_fixed_variable_reaches_lose_their_refusal() {
3432 use DerivativeProof::*;
3433 let [grad_f, hessian, jac_c, jac_d] = reach_proofs(false);
3434 assert_eq!(grad_f, Varying, "x[2] is nowhere in ∇f");
3435 assert_eq!(hessian, Varying, "x[2] appears linearly; ∇²L cannot see it");
3436 assert_eq!(jac_c, Varying, "row 0 has no nonzero in the fixed column");
3437 assert_eq!(jac_d, Unknown, "row 1 does, so fixing x[2] may flatten it");
3438 }
3439
3440 #[test]
3445 fn a_second_order_coupling_to_a_fixed_variable_weakens_the_hessian() {
3446 use DerivativeProof::*;
3447 let [grad_f, hessian, jac_c, jac_d] = reach_proofs(true);
3448 assert_eq!(hessian, Unknown, "x1·x2 puts a fixed index in ∇²L");
3449 assert_eq!(grad_f, Unknown, "same test, and ∇f is reached the same way");
3450 assert_eq!(jac_c, Varying, "row 0 is still untouched by x[2]");
3451 assert_eq!(jac_d, Unknown);
3452 }
3453
3454 #[test]
3455 fn ipopt_nlp_index_mapping_methods_handle_fixed_var() {
3456 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneFixedOneFree));
3457 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3458 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3459
3460 assert_eq!(nlp.n_full_x(), 2);
3462 assert_eq!(nlp.n(), 1);
3463
3464 let nlp_dyn: &dyn crate::ipopt_nlp::IpoptNlp = &nlp;
3466 assert_eq!(nlp_dyn.full_x_to_var_x(0), None);
3467 assert_eq!(nlp_dyn.full_x_to_var_x(1), Some(0));
3468
3469 assert_eq!(nlp_dyn.var_x_to_full_x(0), 1);
3471
3472 assert_eq!(nlp_dyn.full_g_to_c_block(0), Some(0));
3474
3475 let mut x_var = nlp.x_space().make_new_dense();
3477 x_var.values_mut()[0] = 0.5;
3478 let lifted = nlp_dyn.lift_x_to_full(&x_var);
3479 assert_eq!(lifted, vec![7.0, 0.5]);
3480 }
3481
3482 struct NamedFixedOneFree;
3486 impl TNLP for NamedFixedOneFree {
3487 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3488 OneFixedOneFree.get_nlp_info()
3489 }
3490 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3491 OneFixedOneFree.get_bounds_info(b)
3492 }
3493 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3494 OneFixedOneFree.get_starting_point(sp)
3495 }
3496 fn eval_f(&mut self, x: &[Number], n: bool) -> Option<Number> {
3497 OneFixedOneFree.eval_f(x, n)
3498 }
3499 fn eval_grad_f(&mut self, x: &[Number], n: bool, g: &mut [Number]) -> bool {
3500 OneFixedOneFree.eval_grad_f(x, n, g)
3501 }
3502 fn eval_g(&mut self, x: &[Number], n: bool, g: &mut [Number]) -> bool {
3503 OneFixedOneFree.eval_g(x, n, g)
3504 }
3505 fn eval_jac_g(&mut self, x: Option<&[Number]>, n: bool, m: SparsityRequest<'_>) -> bool {
3506 OneFixedOneFree.eval_jac_g(x, n, m)
3507 }
3508 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3509 fn get_var_con_metadata(&mut self, var: &mut MetaData, con: &mut MetaData) -> bool {
3510 var.strings.insert(
3511 IDX_NAMES.to_string(),
3512 vec!["fixed_x".to_string(), "free_x".to_string()],
3513 );
3514 con.strings
3515 .insert(IDX_NAMES.to_string(), vec!["balance".to_string()]);
3516 true
3517 }
3518 }
3519
3520 #[test]
3521 fn split_space_names_threads_through_fixed_var_and_cd_split() {
3522 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(NamedFixedOneFree));
3523 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3524 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3525
3526 let names = nlp.split_space_names().expect("names present");
3527 assert_eq!(names.x_var, vec![Some("free_x".to_string())]);
3529 assert_eq!(names.eq, vec![Some("balance".to_string())]);
3531 assert!(names.ineq.is_empty());
3533 assert!(names.any_present());
3534 }
3535
3536 #[test]
3537 fn split_space_names_none_when_tnlp_declines() {
3538 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneFixedOneFree));
3540 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3541 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3542 assert!(nlp.split_space_names().is_none());
3543 }
3544
3545 struct FixedOnlyHess;
3551 impl TNLP for FixedOnlyHess {
3552 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3553 Some(NlpInfo {
3554 n: 2,
3555 m: 1,
3556 nnz_jac_g: 1,
3557 nnz_h_lag: 1,
3558 index_style: IndexStyle::C,
3559 })
3560 }
3561 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3562 b.x_l[0] = 7.0;
3563 b.x_u[0] = 7.0; b.x_l[1] = -1.0e19;
3565 b.x_u[1] = 1.0e19;
3566 b.g_l[0] = 0.0;
3567 b.g_u[0] = 0.0;
3568 true
3569 }
3570 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3571 sp.x[0] = 7.0;
3572 sp.x[1] = 0.5;
3573 true
3574 }
3575 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
3576 Some(0.5 * x[0] * x[0] + x[1])
3577 }
3578 fn eval_grad_f(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3579 g[0] = x[0];
3580 g[1] = 1.0;
3581 true
3582 }
3583 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3584 g[0] = x[1];
3585 true
3586 }
3587 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
3588 match m {
3589 SparsityRequest::Structure { irow, jcol } => {
3590 irow[0] = 0;
3591 jcol[0] = 1;
3592 }
3593 SparsityRequest::Values { values } => values[0] = 1.0,
3594 }
3595 true
3596 }
3597 fn eval_h(
3598 &mut self,
3599 _: Option<&[Number]>,
3600 _: bool,
3601 obj_factor: Number,
3602 _: Option<&[Number]>,
3603 _: bool,
3604 m: SparsityRequest<'_>,
3605 ) -> bool {
3606 match m {
3607 SparsityRequest::Structure { irow, jcol } => {
3608 irow[0] = 0;
3609 jcol[0] = 0;
3610 }
3611 SparsityRequest::Values { values } => values[0] = obj_factor,
3612 }
3613 true
3614 }
3615 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3616 }
3617
3618 struct OneIneqLargeOffset;
3627 impl TNLP for OneIneqLargeOffset {
3628 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3629 Some(NlpInfo {
3630 n: 1,
3631 m: 1,
3632 nnz_jac_g: 1,
3633 nnz_h_lag: 0,
3634 index_style: IndexStyle::C,
3635 })
3636 }
3637 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3638 b.x_l[0] = -1.0e19;
3639 b.x_u[0] = 1.0e19;
3640 b.g_l[0] = 4.0e6;
3641 b.g_u[0] = 2.0e19;
3642 true
3643 }
3644 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3645 sp.x[0] = 5000.0;
3646 true
3647 }
3648 fn eval_f(&mut self, _: &[Number], _: bool) -> Option<Number> {
3649 Some(0.0)
3650 }
3651 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
3652 g[0] = 0.0;
3653 true
3654 }
3655 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3656 g[0] = 1000.0 * x[0];
3657 true
3658 }
3659 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
3660 match m {
3661 SparsityRequest::Structure { irow, jcol } => {
3662 irow[0] = 0;
3663 jcol[0] = 0;
3664 }
3665 SparsityRequest::Values { values } => values[0] = 1000.0,
3666 }
3667 true
3668 }
3669 fn eval_h(
3670 &mut self,
3671 _: Option<&[Number]>,
3672 _: bool,
3673 _: Number,
3674 _: Option<&[Number]>,
3675 _: bool,
3676 _: SparsityRequest<'_>,
3677 ) -> bool {
3678 true
3679 }
3680 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3681 }
3682
3683 struct OneEqLargeOffset;
3688 impl TNLP for OneEqLargeOffset {
3689 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3690 Some(NlpInfo {
3691 n: 1,
3692 m: 1,
3693 nnz_jac_g: 1,
3694 nnz_h_lag: 0,
3695 index_style: IndexStyle::C,
3696 })
3697 }
3698 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3699 b.x_l[0] = -1.0e19;
3700 b.x_u[0] = 1.0e19;
3701 b.g_l[0] = 4.0e6;
3702 b.g_u[0] = 4.0e6;
3703 true
3704 }
3705 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3706 sp.x[0] = 5000.0;
3707 true
3708 }
3709 fn eval_f(&mut self, _: &[Number], _: bool) -> Option<Number> {
3710 Some(0.0)
3711 }
3712 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
3713 g[0] = 0.0;
3714 true
3715 }
3716 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3717 g[0] = 1000.0 * x[0];
3718 true
3719 }
3720 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
3721 match m {
3722 SparsityRequest::Structure { irow, jcol } => {
3723 irow[0] = 0;
3724 jcol[0] = 0;
3725 }
3726 SparsityRequest::Values { values } => values[0] = 1000.0,
3727 }
3728 true
3729 }
3730 fn eval_h(
3731 &mut self,
3732 _: Option<&[Number]>,
3733 _: bool,
3734 _: Number,
3735 _: Option<&[Number]>,
3736 _: bool,
3737 _: SparsityRequest<'_>,
3738 ) -> bool {
3739 true
3740 }
3741 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3742 }
3743
3744 #[test]
3745 fn gradient_based_scaling_scales_d_l_and_d_u() {
3746 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqLargeOffset));
3747 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3748 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3749
3750 assert_eq!(nlp.d_l().dim(), 1);
3752 let pre = nlp
3753 .d_l()
3754 .as_any()
3755 .downcast_ref::<DenseVector>()
3756 .unwrap()
3757 .values()[0];
3758 assert_eq!(pre, 4.0e6);
3759
3760 nlp.determine_scaling_from_starting_point(
3761 ScalingMethod::GradientBased,
3762 100.0,
3763 1e-8,
3764 0.0,
3765 0.0,
3766 );
3767
3768 let post = nlp
3770 .d_l()
3771 .as_any()
3772 .downcast_ref::<DenseVector>()
3773 .unwrap()
3774 .values()[0];
3775 assert!(
3776 (post - 4.0e5).abs() < 1e-9,
3777 "d_l should be scaled by d_scale=0.1; got {}",
3778 post
3779 );
3780
3781 let x = dense_x(&[5000.0], nlp.x_space());
3784 let mut d = nlp.d_space().make_new_dense();
3785 nlp.eval_d(&x, &mut d);
3786 assert!(
3787 (d.values()[0] - 5.0e5).abs() < 1e-6,
3788 "scaled d(x) mismatch; got {}",
3789 d.values()[0]
3790 );
3791 assert!(
3792 d.values()[0] >= post,
3793 "starting point must be feasible in scaled space"
3794 );
3795 }
3796
3797 struct OneIneqWithObj;
3803 impl TNLP for OneIneqWithObj {
3804 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3805 Some(NlpInfo {
3806 n: 1,
3807 m: 1,
3808 nnz_jac_g: 1,
3809 nnz_h_lag: 0,
3810 index_style: IndexStyle::C,
3811 })
3812 }
3813 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3814 b.x_l[0] = -1.0e19;
3815 b.x_u[0] = 1.0e19;
3816 b.g_l[0] = 4.0e6;
3817 b.g_u[0] = 2.0e19;
3818 true
3819 }
3820 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3821 sp.x[0] = 5000.0;
3822 true
3823 }
3824 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
3825 Some(10.0 * x[0])
3826 }
3827 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
3828 g[0] = 10.0;
3829 true
3830 }
3831 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3832 g[0] = 1000.0 * x[0];
3833 true
3834 }
3835 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
3836 match m {
3837 SparsityRequest::Structure { irow, jcol } => {
3838 irow[0] = 0;
3839 jcol[0] = 0;
3840 }
3841 SparsityRequest::Values { values } => values[0] = 1000.0,
3842 }
3843 true
3844 }
3845 fn eval_h(
3846 &mut self,
3847 _: Option<&[Number]>,
3848 _: bool,
3849 _: Number,
3850 _: Option<&[Number]>,
3851 _: bool,
3852 _: SparsityRequest<'_>,
3853 ) -> bool {
3854 true
3855 }
3856 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3857 }
3858
3859 #[test]
3860 fn obj_target_gradient_pins_obj_scale() {
3861 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqWithObj));
3864 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3865 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3866 nlp.determine_scaling_from_starting_point(
3867 ScalingMethod::GradientBased,
3868 100.0,
3869 1e-8,
3870 0.0, 0.0,
3872 );
3873 assert!(
3874 (nlp.obj_scale_factor() - 1.0).abs() < 1e-12,
3875 "no-target path leaves df=1 when grad < cutoff; got {}",
3876 nlp.obj_scale_factor()
3877 );
3878
3879 let tnlp2: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqWithObj));
3882 let adapter2 = Rc::new(RefCell::new(TNLPAdapter::new(tnlp2).unwrap()));
3883 let mut nlp2 = OrigIpoptNlp::new(Rc::clone(&adapter2), Rc::new(NoScaling)).unwrap();
3884 nlp2.determine_scaling_from_starting_point(
3885 ScalingMethod::GradientBased,
3886 100.0,
3887 1e-8,
3888 1.0,
3889 0.0,
3890 );
3891 assert!(
3892 (nlp2.obj_scale_factor() - 0.1).abs() < 1e-12,
3893 "target_gradient=1, max_grad_f=10 → df=0.1; got {}",
3894 nlp2.obj_scale_factor()
3895 );
3896 }
3897
3898 struct FixedVarShiftsObjGrad;
3908 impl TNLP for FixedVarShiftsObjGrad {
3909 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3910 Some(NlpInfo {
3911 n: 2,
3912 m: 0,
3913 nnz_jac_g: 0,
3914 nnz_h_lag: 0,
3915 index_style: IndexStyle::C,
3916 })
3917 }
3918 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3919 b.x_l[0] = -1.0e19;
3920 b.x_u[0] = 1.0e19;
3921 b.x_l[1] = 1000.0;
3922 b.x_u[1] = 1000.0; true
3924 }
3925 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3926 sp.x[0] = 1.0;
3927 sp.x[1] = 0.0; true
3929 }
3930 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
3931 Some(x[0] * x[1])
3932 }
3933 fn eval_grad_f(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3934 g[0] = x[1];
3935 g[1] = x[0];
3936 true
3937 }
3938 fn eval_g(&mut self, _: &[Number], _: bool, _: &mut [Number]) -> bool {
3939 true
3940 }
3941 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, _: SparsityRequest<'_>) -> bool {
3942 true
3943 }
3944 fn eval_h(
3945 &mut self,
3946 _: Option<&[Number]>,
3947 _: bool,
3948 _: Number,
3949 _: Option<&[Number]>,
3950 _: bool,
3951 _: SparsityRequest<'_>,
3952 ) -> bool {
3953 true
3954 }
3955 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3956 }
3957
3958 #[test]
3959 fn gradient_scaling_lifts_fixed_vars_to_their_value() {
3960 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(FixedVarShiftsObjGrad));
3961 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3962 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3963
3964 assert_eq!(nlp.n_full_x(), 2);
3966 assert_eq!(nlp.n(), 1);
3967
3968 nlp.determine_scaling_from_starting_point(
3969 ScalingMethod::GradientBased,
3970 100.0,
3971 1e-8,
3972 0.0,
3973 0.0,
3974 );
3975
3976 assert!(
3980 (nlp.obj_scale_factor() - 0.1).abs() < 1e-12,
3981 "fixed var must be lifted before scaling; expected df=0.1, got {}",
3982 nlp.obj_scale_factor()
3983 );
3984 }
3985
3986 #[test]
3987 fn constr_target_gradient_overrides_cutoff_and_clamp() {
3988 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqLargeOffset));
3993 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3994 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3995 nlp.determine_scaling_from_starting_point(
3996 ScalingMethod::GradientBased,
3997 100.0,
3998 1e-8,
3999 0.0,
4000 50.0,
4001 );
4002 let x = dense_x(&[5000.0], nlp.x_space());
4003 let mut d = nlp.d_space().make_new_dense();
4004 nlp.eval_d(&x, &mut d);
4005 assert!(
4007 (d.values()[0] - 2.5e5).abs() < 1e-6,
4008 "constr target=50 → dd=0.05; scaled d(5000)=2.5e5, got {}",
4009 d.values()[0]
4010 );
4011 }
4012
4013 struct Hs071UserScaled;
4018 impl TNLP for Hs071UserScaled {
4019 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
4020 Hs071::default().get_nlp_info()
4021 }
4022 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
4023 Hs071::default().get_bounds_info(b)
4024 }
4025 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
4026 Hs071::default().get_starting_point(sp)
4027 }
4028 fn eval_f(&mut self, x: &[Number], new_x: bool) -> Option<Number> {
4029 Hs071::default().eval_f(x, new_x)
4030 }
4031 fn eval_grad_f(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
4032 Hs071::default().eval_grad_f(x, new_x, g)
4033 }
4034 fn eval_g(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
4035 Hs071::default().eval_g(x, new_x, g)
4036 }
4037 fn eval_jac_g(
4038 &mut self,
4039 x: Option<&[Number]>,
4040 new_x: bool,
4041 mode: SparsityRequest<'_>,
4042 ) -> bool {
4043 Hs071::default().eval_jac_g(x, new_x, mode)
4044 }
4045 fn eval_h(
4046 &mut self,
4047 x: Option<&[Number]>,
4048 new_x: bool,
4049 obj_factor: Number,
4050 lambda: Option<&[Number]>,
4051 new_lambda: bool,
4052 mode: SparsityRequest<'_>,
4053 ) -> bool {
4054 Hs071::default().eval_h(x, new_x, obj_factor, lambda, new_lambda, mode)
4055 }
4056 fn get_scaling_parameters(&mut self, req: ScalingRequest<'_>) -> bool {
4057 *req.obj_scaling = 2.0;
4058 *req.use_x_scaling = false;
4059 *req.use_g_scaling = true;
4060 req.g_scaling[0] = 0.5;
4062 req.g_scaling[1] = 0.25;
4063 true
4064 }
4065 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
4066 }
4067
4068 #[test]
4069 fn user_scaling_dispatch_applies_obj_and_g_scaling() {
4070 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071UserScaled));
4071 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4072 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
4073 nlp.determine_scaling_from_starting_point(
4074 ScalingMethod::UserScaling,
4075 100.0,
4076 1e-8,
4077 0.0,
4078 0.0,
4079 );
4080
4081 assert!(
4084 (nlp.obj_scale_factor() - 2.0).abs() < 1e-12,
4085 "user obj_scaling=2.0 should be installed; got {}",
4086 nlp.obj_scale_factor()
4087 );
4088
4089 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
4093 let mut c = nlp.c_space().make_new_dense();
4094 nlp.eval_c(&x, &mut c);
4095 assert!(
4098 (c.values()[0] - 3.0).abs() < 1e-9,
4099 "user g_scaling=0.25 on equality → c=3.0; got {}",
4100 c.values()[0]
4101 );
4102
4103 let mut d = nlp.d_space().make_new_dense();
4106 nlp.eval_d(&x, &mut d);
4107 assert!(
4108 (d.values()[0] - 12.5).abs() < 1e-9,
4109 "user g_scaling=0.5 on inequality → d=12.5; got {}",
4110 d.values()[0]
4111 );
4112
4113 let post_d_l = nlp
4116 .d_l()
4117 .as_any()
4118 .downcast_ref::<DenseVector>()
4119 .unwrap()
4120 .values()[0];
4121 assert!(
4122 (post_d_l - 12.5).abs() < 1e-9,
4123 "d_l scaled in step: got {}",
4124 post_d_l
4125 );
4126 }
4127
4128 struct Hs071DeclinesScaling;
4132 impl TNLP for Hs071DeclinesScaling {
4133 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
4134 Hs071::default().get_nlp_info()
4135 }
4136 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
4137 Hs071::default().get_bounds_info(b)
4138 }
4139 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
4140 Hs071::default().get_starting_point(sp)
4141 }
4142 fn eval_f(&mut self, x: &[Number], new_x: bool) -> Option<Number> {
4143 Hs071::default().eval_f(x, new_x)
4144 }
4145 fn eval_grad_f(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
4146 Hs071::default().eval_grad_f(x, new_x, g)
4147 }
4148 fn eval_g(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
4149 Hs071::default().eval_g(x, new_x, g)
4150 }
4151 fn eval_jac_g(
4152 &mut self,
4153 x: Option<&[Number]>,
4154 new_x: bool,
4155 mode: SparsityRequest<'_>,
4156 ) -> bool {
4157 Hs071::default().eval_jac_g(x, new_x, mode)
4158 }
4159 fn eval_h(
4160 &mut self,
4161 x: Option<&[Number]>,
4162 new_x: bool,
4163 obj_factor: Number,
4164 lambda: Option<&[Number]>,
4165 new_lambda: bool,
4166 mode: SparsityRequest<'_>,
4167 ) -> bool {
4168 Hs071::default().eval_h(x, new_x, obj_factor, lambda, new_lambda, mode)
4169 }
4170 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
4171 }
4172
4173 #[test]
4174 fn user_scaling_falls_back_when_tnlp_declines() {
4175 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071DeclinesScaling));
4176 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4177 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
4178 nlp.determine_scaling_from_starting_point(
4179 ScalingMethod::UserScaling,
4180 100.0,
4181 1e-8,
4182 0.0,
4183 0.0,
4184 );
4185 assert!((nlp.obj_scale_factor() - 1.0).abs() < 1e-12);
4188 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
4189 let mut c = nlp.c_space().make_new_dense();
4190 nlp.eval_c(&x, &mut c);
4191 assert_eq!(c.values(), &[12.0], "unscaled equality residual");
4192 }
4193
4194 struct Hs071XScaled(Vec<Number>);
4197 impl TNLP for Hs071XScaled {
4198 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
4199 Hs071::default().get_nlp_info()
4200 }
4201 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
4202 Hs071::default().get_bounds_info(b)
4203 }
4204 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
4205 Hs071::default().get_starting_point(sp)
4206 }
4207 fn eval_f(&mut self, x: &[Number], new_x: bool) -> Option<Number> {
4208 Hs071::default().eval_f(x, new_x)
4209 }
4210 fn eval_grad_f(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
4211 Hs071::default().eval_grad_f(x, new_x, g)
4212 }
4213 fn eval_g(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
4214 Hs071::default().eval_g(x, new_x, g)
4215 }
4216 fn eval_jac_g(
4217 &mut self,
4218 x: Option<&[Number]>,
4219 new_x: bool,
4220 mode: SparsityRequest<'_>,
4221 ) -> bool {
4222 Hs071::default().eval_jac_g(x, new_x, mode)
4223 }
4224 fn eval_h(
4225 &mut self,
4226 x: Option<&[Number]>,
4227 new_x: bool,
4228 obj_factor: Number,
4229 lambda: Option<&[Number]>,
4230 new_lambda: bool,
4231 mode: SparsityRequest<'_>,
4232 ) -> bool {
4233 Hs071::default().eval_h(x, new_x, obj_factor, lambda, new_lambda, mode)
4234 }
4235 fn get_scaling_parameters(&mut self, req: ScalingRequest<'_>) -> bool {
4236 *req.obj_scaling = 2.0;
4237 *req.use_x_scaling = true;
4238 req.x_scaling.copy_from_slice(&self.0);
4239 *req.use_g_scaling = false;
4240 true
4241 }
4242 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
4243 }
4244
4245 fn user_x_scaling_run(factors: &[Number]) -> OrigIpoptNlp {
4246 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071XScaled(factors.to_vec())));
4247 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4248 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
4249 nlp.determine_scaling_from_starting_point(
4250 ScalingMethod::UserScaling,
4251 100.0,
4252 1e-8,
4253 0.0,
4254 0.0,
4255 );
4256 nlp
4257 }
4258
4259 #[test]
4264 fn user_x_scaling_request_is_flagged_not_discarded() {
4265 let nlp = user_x_scaling_run(&[1.0, 1e3, 1.0, 1.0]);
4266 assert!(
4267 nlp.user_x_scaling_rejected(),
4268 "a non-unit x_scaling must be refused, not dropped"
4269 );
4270 assert!((nlp.obj_scale_factor() - 2.0).abs() < 1e-12);
4273 }
4274
4275 #[test]
4278 fn unit_x_scaling_request_is_not_rejected() {
4279 let nlp = user_x_scaling_run(&[1.0, 1.0, 1.0, 1.0]);
4280 assert!(!nlp.user_x_scaling_rejected());
4281 }
4282
4283 #[test]
4284 fn eval_h_with_all_entries_on_fixed_var_does_not_panic() {
4285 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(FixedOnlyHess));
4286 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4287 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
4288
4289 assert_eq!(nlp.h_space().unwrap().nonzeros(), 0);
4292
4293 let x = dense_x(&[0.5], &nlp.x_space().clone());
4294 let yc = dense_x(&[0.0], &nlp.c_space().clone());
4295 let yd = nlp.d_space().make_new_dense();
4296 let h = nlp.eval_h(&x, 1.0, &yc, &yd);
4297 assert_eq!(h.n_rows(), 1);
4298 }
4299
4300 #[test]
4301 fn relax_bounds_widens_uniquely_owned_bounds() {
4302 let (_adapter, mut nlp) = build_orig_nlp();
4305 let x_l_before = nlp.x_l.values().to_vec();
4306 let x_u_before = nlp.x_u.values().to_vec();
4307 nlp.relax_bounds(1e-2, 1.0);
4308 for (b, a) in x_l_before.iter().zip(nlp.x_l.values()) {
4309 assert!(a < b, "x_l should relax downward: {a} !< {b}");
4310 }
4311 for (b, a) in x_u_before.iter().zip(nlp.x_u.values()) {
4312 assert!(a > b, "x_u should relax upward: {a} !> {b}");
4313 }
4314 }
4315
4316 #[test]
4321 fn relax_bounds_is_scale_relative_on_d_and_snapshots_declared() {
4322 let (_adapter, mut nlp) = build_orig_nlp();
4324 assert_eq!(nlp.d_l.values(), &[25.0]);
4325 assert_eq!(nlp.declared_d_bounds(), None, "no snapshot before relax");
4326 nlp.relax_bounds(1e-2, 1.0);
4327 assert_eq!(nlp.d_l.values(), &[25.0 - 0.25]);
4333 let (dl, du) = nlp.declared_d_bounds().expect("snapshotted at relax");
4334 assert_eq!(dl, vec![25.0], "declared bound is the pre-relax value");
4335 assert!(du.is_empty() || du[0] >= 25.0); }
4337
4338 #[test]
4346 fn declared_x_bounds_are_the_pre_relax_box() {
4347 let (_adapter, mut nlp) = build_orig_nlp();
4348 assert_eq!(nlp.declared_x_bounds(), None, "no snapshot before relax");
4349 let x_l_before = nlp.x_l.values().to_vec();
4350 let x_u_before = nlp.x_u.values().to_vec();
4351 nlp.relax_bounds(1e-2, 1.0);
4352 let (xl, xu) = nlp.declared_x_bounds().expect("snapshotted at relax");
4353 assert_eq!(xl, x_l_before, "declared lower box is the pre-relax value");
4354 assert_eq!(xu, x_u_before, "declared upper box is the pre-relax value");
4355 assert!(nlp.x_l.values()[0] < xl[0]);
4358 assert!(nlp.x_u.values()[0] > xu[0]);
4359 }
4360
4361 #[test]
4367 fn declared_c_rhs_is_the_pre_fold_right_hand_side() {
4368 let (_adapter, mut nlp) = build_orig_nlp();
4370 assert_eq!(nlp.declared_c_rhs(), Some(vec![40.0]));
4371 nlp.relax_bounds(1e-2, 1.0);
4372 assert_eq!(
4373 nlp.declared_c_rhs(),
4374 Some(vec![40.0]),
4375 "bound relaxation must not reach the equality RHS"
4376 );
4377 }
4378
4379 #[test]
4384 fn declared_c_rhs_carries_the_row_scaling() {
4385 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneEqLargeOffset));
4386 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4387 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
4388 assert_eq!(nlp.declared_c_rhs(), Some(vec![4.0e6]));
4389
4390 nlp.determine_scaling_from_starting_point(
4391 ScalingMethod::GradientBased,
4392 100.0,
4393 1e-8,
4394 0.0,
4395 0.0,
4396 );
4397 let rhs = nlp.declared_c_rhs().unwrap();
4399 assert!(
4400 (rhs[0] - 4.0e5).abs() < 1e-9,
4401 "declared RHS should carry c_scale=0.1; got {}",
4402 rhs[0]
4403 );
4404
4405 let x = dense_x(&[5000.0], nlp.x_space());
4408 let mut c = nlp.c_space().make_new_dense();
4409 nlp.eval_c(&x, &mut c);
4410 assert!((c.values()[0] / rhs[0] - 0.25).abs() < 1e-12);
4411 }
4412
4413 #[test]
4414 #[should_panic(expected = "x_l is uniquely owned")]
4415 fn relax_bounds_panics_on_shared_bound_rc() {
4416 let (_adapter, mut nlp) = build_orig_nlp();
4421 let _shared = Rc::clone(&nlp.x_l); nlp.relax_bounds(1e-2, 1.0);
4423 }
4424}