1use crate::constant_derivatives::{
61 ConstantDerivatives, DerivativeProof, DerivativeProofs, subsystem_proof,
62};
63use crate::ipopt_nlp::{IpoptNlp, Nlp, SplitNames};
64use crate::tnlp::{
65 IDX_NAMES, IndexStyle, MetaData, NlpInfo, ScalingRequest, SparsityRequest, StartingPoint, TNLP,
66};
67use crate::tnlp_adapter::{BoundClassification, TNLPAdapter};
68use pounce_common::cached::Cache;
69use pounce_common::timing::TimingStatistics;
70use pounce_common::types::{Index, Number};
71use pounce_linalg::{
72 DenseVector, DenseVectorSpace, ExpansionMatrix, ExpansionMatrixSpace, GenTMatrix,
73 GenTMatrixSpace, Matrix, SymMatrix, SymTMatrix, SymTMatrixSpace, Vector,
74};
75use std::cell::{Cell, RefCell};
76use std::rc::Rc;
77
78pub trait NlpScaling {
90 fn obj_scaling(&self) -> Number {
95 1.0
96 }
97}
98
99#[derive(Debug, Default, Clone, Copy)]
102pub struct NoScaling;
103impl NlpScaling for NoScaling {}
104
105#[derive(Debug, Clone, Copy)]
110pub struct ConstObjScaling(pub Number);
111impl NlpScaling for ConstObjScaling {
112 fn obj_scaling(&self) -> Number {
113 self.0
114 }
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120pub enum ScalingMethod {
121 None,
123 GradientBased,
125 UserScaling,
132}
133
134pub fn gradient_obj_scale(
148 max_grad_f: Number,
149 max_gradient: Number,
150 min_value: Number,
151 obj_target_gradient: Number,
152) -> Number {
153 let mut df = 1.0;
154 if obj_target_gradient > 0.0 && max_grad_f > 0.0 {
155 df = obj_target_gradient / max_grad_f;
158 } else if max_grad_f > max_gradient {
159 df = max_gradient / max_grad_f;
160 }
161 if df < min_value {
162 df = min_value;
163 }
164 df
165}
166
167pub fn gradient_row_scale(
185 row_max: Number,
186 max_gradient: Number,
187 min_value: Number,
188 constr_target_gradient: Number,
189) -> Number {
190 let mut s = if constr_target_gradient > 0.0 {
191 constr_target_gradient / row_max
192 } else {
193 let raw = max_gradient / row_max;
194 if raw > 1.0 { 1.0 } else { raw }
195 };
196 if s < min_value {
197 s = min_value;
198 }
199 s
200}
201
202pub fn gradient_scaling_fires(
208 row_max: &[Number],
209 max_gradient: Number,
210 constr_target_gradient: Number,
211) -> bool {
212 constr_target_gradient > 0.0 || row_max.iter().any(|&v| v > max_gradient)
213}
214
215pub struct OrigIpoptNlp {
218 adapter: Rc<RefCell<TNLPAdapter>>,
220 scaling: Rc<dyn NlpScaling>,
225
226 obj_scale_factor: Cell<Number>,
229 computed_obj_scale: Cell<Number>,
232 start_obj_grad_max: Cell<Number>,
235 c_scale: RefCell<Option<Vec<Number>>>,
239 d_scale: RefCell<Option<Vec<Number>>>,
241 declared_d_l: RefCell<Option<Vec<Number>>>,
250 declared_d_u: RefCell<Option<Vec<Number>>>,
251 declared_x_l: RefCell<Option<Vec<Number>>>,
257 declared_x_u: RefCell<Option<Vec<Number>>>,
258 honor_original_bounds: Cell<bool>,
266 variable_scaling: Option<Vec<Number>>,
274 x_scaling_rejected: Cell<bool>,
283
284 x_space: Rc<DenseVectorSpace>,
286 c_space: Rc<DenseVectorSpace>,
287 d_space: Rc<DenseVectorSpace>,
288 x_l_space: Rc<DenseVectorSpace>,
289 x_u_space: Rc<DenseVectorSpace>,
290 d_l_space: Rc<DenseVectorSpace>,
291 d_u_space: Rc<DenseVectorSpace>,
292 px_l_space: Rc<ExpansionMatrixSpace>,
293 px_u_space: Rc<ExpansionMatrixSpace>,
294 pd_l_space: Rc<ExpansionMatrixSpace>,
295 pd_u_space: Rc<ExpansionMatrixSpace>,
296 jac_c_space: Rc<GenTMatrixSpace>,
297 jac_d_space: Rc<GenTMatrixSpace>,
298 h_space: Option<Rc<SymTMatrixSpace>>,
301
302 x_l: Rc<DenseVector>,
304 x_u: Rc<DenseVector>,
305 d_l: Rc<DenseVector>,
306 d_u: Rc<DenseVector>,
307 c_rhs: Vec<Number>,
314 warm_start_snapshot: RefCell<Option<StartingPointSnapshot>>,
317
318 px_l: Rc<dyn Matrix>,
320 px_u: Rc<dyn Matrix>,
321 pd_l: Rc<dyn Matrix>,
322 pd_u: Rc<dyn Matrix>,
323
324 jac_c_entry_in_g: Vec<Index>,
328 jac_d_entry_in_g: Vec<Index>,
330 nnz_jac_g_full: Index,
332
333 nnz_h_lag_full: Index,
337 h_entry_in_full: Vec<Index>,
342
343 f_cache: RefCell<Cache<Number>>,
345 grad_f_cache: RefCell<Cache<Rc<dyn Vector>>>,
346 c_cache: RefCell<Cache<Rc<dyn Vector>>>,
347 d_cache: RefCell<Cache<Rc<dyn Vector>>>,
348 jac_c_cache: RefCell<Cache<Rc<dyn Matrix>>>,
349 jac_d_cache: RefCell<Cache<Rc<dyn Matrix>>>,
350 h_cache: RefCell<Cache<Rc<dyn SymMatrix>>>,
351 full_g_cache: RefCell<Cache<Rc<Vec<Number>>>>,
359 full_jac_g_cache: RefCell<Cache<Rc<Vec<Number>>>>,
360
361 f_evals: RefCell<Index>,
363 grad_f_evals: RefCell<Index>,
364 c_evals: RefCell<Index>,
365 d_evals: RefCell<Index>,
366 jac_c_evals: RefCell<Index>,
367 jac_d_evals: RefCell<Index>,
368 h_evals: RefCell<Index>,
369
370 info: NlpInfo,
373
374 timing: RefCell<Option<Rc<TimingStatistics>>>,
379
380 const_deriv: ConstantDerivatives,
392
393 fixed_reach: FixedVarReach,
397}
398
399#[derive(Clone, Debug, Default)]
415struct FixedVarReach {
416 g_row_touches_fixed: Vec<bool>,
422 jac_fixed_entries: Vec<(usize, usize, usize)>,
427 last_full_grad_f: RefCell<Option<(Vec<Number>, Rc<Vec<Number>>)>>,
433 last_full_jac_g: RefCell<Option<(Vec<Number>, Rc<Vec<Number>>)>>,
434 second_order_touches_fixed: bool,
446}
447
448#[derive(Clone)]
449struct StartingPointSnapshot {
450 x: Vec<Number>,
451 z_l: Vec<Number>,
452 z_u: Vec<Number>,
453 lambda: Vec<Number>,
454}
455
456impl std::fmt::Debug for OrigIpoptNlp {
457 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
458 f.debug_struct("OrigIpoptNlp")
459 .field("info", &self.info)
460 .field("f_evals", &*self.f_evals.borrow())
461 .field("grad_f_evals", &*self.grad_f_evals.borrow())
462 .field("c_evals", &*self.c_evals.borrow())
463 .field("d_evals", &*self.d_evals.borrow())
464 .field("jac_c_evals", &*self.jac_c_evals.borrow())
465 .field("jac_d_evals", &*self.jac_d_evals.borrow())
466 .field("h_evals", &*self.h_evals.borrow())
467 .finish_non_exhaustive()
468 }
469}
470
471impl OrigIpoptNlp {
472 pub fn new(
478 adapter: Rc<RefCell<TNLPAdapter>>,
479 scaling: Rc<dyn NlpScaling>,
480 ) -> Result<Self, String> {
481 let (info, classification) = {
483 let a = adapter.borrow();
484 (*a.nlp_info(), a.classification().clone())
485 };
486 let variable_scaling = adapter.borrow().tnlp().borrow().scaling_factors();
487
488 let n_x_var = classification.n_x_var();
490 let x_space = DenseVectorSpace::new(n_x_var);
491 let c_space = DenseVectorSpace::new(classification.n_c);
492 let d_space = DenseVectorSpace::new(classification.n_d);
493 let x_l_space = DenseVectorSpace::new(classification.n_x_l());
494 let x_u_space = DenseVectorSpace::new(classification.n_x_u());
495 let d_l_space = DenseVectorSpace::new(classification.n_d_l());
496 let d_u_space = DenseVectorSpace::new(classification.n_d_u());
497
498 let px_l_space =
500 ExpansionMatrixSpace::new(n_x_var, classification.n_x_l(), &classification.x_l_map, 0);
501 let px_u_space =
502 ExpansionMatrixSpace::new(n_x_var, classification.n_x_u(), &classification.x_u_map, 0);
503 let pd_l_space = ExpansionMatrixSpace::new(
504 classification.n_d,
505 classification.n_d_l(),
506 &classification.d_l_map,
507 0,
508 );
509 let pd_u_space = ExpansionMatrixSpace::new(
510 classification.n_d,
511 classification.n_d_u(),
512 &classification.d_u_map,
513 0,
514 );
515 let px_l: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&px_l_space)));
516 let px_u: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&px_u_space)));
517 let pd_l: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&pd_l_space)));
518 let pd_u: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&pd_u_space)));
519
520 let n_full_x = classification.n_full_x as usize;
524 let n_full_g = classification.n_full_g as usize;
525 let mut full_x_l = vec![0.0; n_full_x];
526 let mut full_x_u = vec![0.0; n_full_x];
527 let mut full_g_l = vec![0.0; n_full_g];
528 let mut full_g_u = vec![0.0; n_full_g];
529 {
530 let a = adapter.borrow();
531 let mut t = a.tnlp().borrow_mut();
532 let ok = t.get_bounds_info(crate::tnlp::BoundsInfo {
533 x_l: &mut full_x_l,
534 x_u: &mut full_x_u,
535 g_l: &mut full_g_l,
536 g_u: &mut full_g_u,
537 });
538 if !ok {
539 return Err("TNLP::get_bounds_info returned false on second call".into());
540 }
541 }
542
543 let x_l = make_dense_from(&x_l_space, |i| {
544 let var_idx = classification.x_l_map[i] as usize;
546 let full_idx = classification.x_not_fixed_map[var_idx] as usize;
547 full_x_l[full_idx]
548 });
549 let x_u = make_dense_from(&x_u_space, |i| {
550 let var_idx = classification.x_u_map[i] as usize;
551 let full_idx = classification.x_not_fixed_map[var_idx] as usize;
552 full_x_u[full_idx]
553 });
554 let d_l = make_dense_from(&d_l_space, |i| {
555 let d_idx = classification.d_l_map[i] as usize;
557 let full_g_idx = classification.d_map[d_idx] as usize;
558 full_g_l[full_g_idx]
559 });
560 let d_u = make_dense_from(&d_u_space, |i| {
561 let d_idx = classification.d_u_map[i] as usize;
562 let full_g_idx = classification.d_map[d_idx] as usize;
563 full_g_u[full_g_idx]
564 });
565
566 let c_rhs: Vec<Number> = classification
572 .c_map
573 .iter()
574 .map(|&g_idx| full_g_l[g_idx as usize])
575 .collect();
576
577 let mut full_irow = vec![0 as Index; info.nnz_jac_g as usize];
587 let mut full_jcol = vec![0 as Index; info.nnz_jac_g as usize];
588 {
589 let a = adapter.borrow();
590 let mut t = a.tnlp().borrow_mut();
591 let ok = t.eval_jac_g(
592 None,
593 false,
594 SparsityRequest::Structure {
595 irow: &mut full_irow,
596 jcol: &mut full_jcol,
597 },
598 );
599 if !ok {
600 return Err("TNLP::eval_jac_g(Structure) returned false".into());
601 }
602 }
603
604 let mut g_to_c = vec![-1 as Index; n_full_g];
606 for (c_idx, &g_idx) in classification.c_map.iter().enumerate() {
607 g_to_c[g_idx as usize] = c_idx as Index;
608 }
609 let mut g_to_d = vec![-1 as Index; n_full_g];
610 for (d_idx, &g_idx) in classification.d_map.iter().enumerate() {
611 g_to_d[g_idx as usize] = d_idx as Index;
612 }
613
614 let style_offset = match info.index_style {
615 crate::tnlp::IndexStyle::C => 0 as Index,
616 crate::tnlp::IndexStyle::Fortran => 1 as Index,
617 };
618
619 let mut jac_c_irow_1based = Vec::new();
620 let mut jac_c_jcol_1based = Vec::new();
621 let mut jac_c_entry_in_g = Vec::new();
622 let mut jac_d_irow_1based = Vec::new();
623 let mut jac_d_jcol_1based = Vec::new();
624 let mut jac_d_entry_in_g = Vec::new();
625
626 let full_to_var = &classification.full_to_var;
630 let mut g_row_touches_fixed = vec![false; info.m as usize];
634 let mut jac_fixed_entries = Vec::new();
635 for k in 0..info.nnz_jac_g as usize {
636 let g_row_0 = (full_irow[k] - style_offset) as usize;
637 let x_col_0 = (full_jcol[k] - style_offset) as usize;
638 let var_col = full_to_var[x_col_0];
639 if var_col < 0 {
640 g_row_touches_fixed[g_row_0] = true;
641 jac_fixed_entries.push((k, g_row_0, x_col_0));
642 continue;
643 }
644 let col_1based = var_col + 1;
646 let c_row = g_to_c[g_row_0];
647 if c_row >= 0 {
648 jac_c_irow_1based.push(c_row + 1);
649 jac_c_jcol_1based.push(col_1based);
650 jac_c_entry_in_g.push(k as Index);
651 } else {
652 let d_row = g_to_d[g_row_0];
653 debug_assert!(d_row >= 0, "g row {g_row_0} is neither in c_map nor d_map");
654 jac_d_irow_1based.push(d_row + 1);
655 jac_d_jcol_1based.push(col_1based);
656 jac_d_entry_in_g.push(k as Index);
657 }
658 }
659
660 let jac_c_space = GenTMatrixSpace::new(
661 classification.n_c,
662 n_x_var,
663 jac_c_irow_1based,
664 jac_c_jcol_1based,
665 );
666 let jac_d_space = GenTMatrixSpace::new(
667 classification.n_d,
668 n_x_var,
669 jac_d_irow_1based,
670 jac_d_jcol_1based,
671 );
672
673 let nnz_h_lag_full = info.nnz_h_lag;
678 let mut h_entry_in_full: Vec<Index> = Vec::new();
679 let mut second_order_touches_fixed = info.nnz_h_lag > 0;
684 let h_space = if info.nnz_h_lag > 0 {
685 let mut h_irow = vec![0 as Index; info.nnz_h_lag as usize];
686 let mut h_jcol = vec![0 as Index; info.nnz_h_lag as usize];
687 let supports_h = {
688 let a = adapter.borrow();
689 let mut t = a.tnlp().borrow_mut();
690 t.eval_h(
691 None,
692 false,
693 1.0,
694 None,
695 false,
696 SparsityRequest::Structure {
697 irow: &mut h_irow,
698 jcol: &mut h_jcol,
699 },
700 )
701 };
702 if supports_h {
703 second_order_touches_fixed = false;
704 let mut h_irow_1: Vec<Index> = Vec::with_capacity(h_irow.len());
710 let mut h_jcol_1: Vec<Index> = Vec::with_capacity(h_jcol.len());
711 for k in 0..h_irow.len() {
712 let i_full = (h_irow[k] - style_offset) as usize;
713 let j_full = (h_jcol[k] - style_offset) as usize;
714 let i_var = full_to_var[i_full];
715 let j_var = full_to_var[j_full];
716 if i_var < 0 || j_var < 0 {
717 second_order_touches_fixed = true;
718 continue;
719 }
720 h_irow_1.push(i_var + 1);
721 h_jcol_1.push(j_var + 1);
722 h_entry_in_full.push(k as Index);
723 }
724 Some(SymTMatrixSpace::new(n_x_var, h_irow_1, h_jcol_1))
725 } else {
726 None
728 }
729 } else {
730 Some(SymTMatrixSpace::new(n_x_var, Vec::new(), Vec::new()))
734 };
735
736 let initial_obj_scal = scaling.obj_scaling();
742 Ok(Self {
743 adapter,
744 scaling,
745 obj_scale_factor: Cell::new(initial_obj_scal),
746 computed_obj_scale: Cell::new(1.0),
747 start_obj_grad_max: Cell::new(Number::NAN),
748 c_scale: RefCell::new(None),
749 d_scale: RefCell::new(None),
750 declared_d_l: RefCell::new(None),
751 declared_d_u: RefCell::new(None),
752 declared_x_l: RefCell::new(None),
753 declared_x_u: RefCell::new(None),
754 honor_original_bounds: Cell::new(false),
755 variable_scaling,
756 x_scaling_rejected: Cell::new(false),
757 x_space,
758 c_space,
759 d_space,
760 x_l_space,
761 x_u_space,
762 d_l_space,
763 d_u_space,
764 px_l_space,
765 px_u_space,
766 pd_l_space,
767 pd_u_space,
768 jac_c_space,
769 jac_d_space,
770 h_space,
771 x_l: Rc::new(x_l),
772 x_u: Rc::new(x_u),
773 d_l: Rc::new(d_l),
774 d_u: Rc::new(d_u),
775 c_rhs,
776 warm_start_snapshot: RefCell::new(None),
777 px_l,
778 px_u,
779 pd_l,
780 pd_u,
781 jac_c_entry_in_g,
782 jac_d_entry_in_g,
783 nnz_jac_g_full: info.nnz_jac_g,
784 nnz_h_lag_full,
785 h_entry_in_full,
786 f_cache: RefCell::new(Cache::new(1)),
787 grad_f_cache: RefCell::new(Cache::new(1)),
788 c_cache: RefCell::new(Cache::new(1)),
789 d_cache: RefCell::new(Cache::new(1)),
790 jac_c_cache: RefCell::new(Cache::new(1)),
791 jac_d_cache: RefCell::new(Cache::new(1)),
792 h_cache: RefCell::new(Cache::new(1)),
793 full_g_cache: RefCell::new(Cache::new(1)),
794 full_jac_g_cache: RefCell::new(Cache::new(1)),
795 f_evals: RefCell::new(0),
796 grad_f_evals: RefCell::new(0),
797 c_evals: RefCell::new(0),
798 d_evals: RefCell::new(0),
799 jac_c_evals: RefCell::new(0),
800 jac_d_evals: RefCell::new(0),
801 h_evals: RefCell::new(0),
802 info,
803 timing: RefCell::new(None),
804 const_deriv: ConstantDerivatives::default(),
805 fixed_reach: FixedVarReach {
806 g_row_touches_fixed,
807 jac_fixed_entries,
808 last_full_grad_f: RefCell::new(None),
809 last_full_jac_g: RefCell::new(None),
810 second_order_touches_fixed,
811 },
812 })
813 }
814
815 pub fn set_timing_stats(&self, t: Rc<TimingStatistics>) {
821 *self.timing.borrow_mut() = Some(t);
822 }
823
824 fn timed_eval<R, F>(&self, pick: fn(&TimingStatistics) -> &pounce_common::TimedTask, f: F) -> R
829 where
830 F: FnOnce() -> R,
831 {
832 let guard = self.timing.borrow();
833 match guard.as_deref() {
834 Some(t) => {
835 let task = pick(t);
836 task.start();
837 t.total_function_evaluation_time.start();
838 let r = f();
839 t.total_function_evaluation_time.end();
840 task.end();
841 r
842 }
843 None => {
844 drop(guard);
845 f()
846 }
847 }
848 }
849
850 pub fn derivative_proofs(&self) -> [DerivativeProof; 4] {
877 let mut proofs: DerivativeProofs = {
878 let a = self.adapter.borrow();
879 let mut t = a.tnlp().borrow_mut();
880 t.derivative_proofs()
881 };
882 let cls = self.adapter.borrow().classification().clone();
883 if cls.n_x_fixed > 0 {
884 let reach = &self.fixed_reach;
898 for (row, proof) in proofs.jac.iter_mut().enumerate() {
899 if reach.g_row_touches_fixed.get(row).copied().unwrap_or(true) {
900 *proof = proof.forget_variation();
901 }
902 }
903 if reach.second_order_touches_fixed {
904 proofs.grad_f = proofs.grad_f.forget_variation();
905 proofs.hessian = proofs.hessian.forget_variation();
906 }
907 }
908 [
909 proofs.grad_f,
910 proofs.hessian,
911 subsystem_proof(&proofs, &cls.c_map),
912 subsystem_proof(&proofs, &cls.d_map),
913 ]
914 }
915
916 pub fn set_constant_derivatives(&mut self, cd: ConstantDerivatives) {
922 self.const_deriv = cd;
923 self.invalidate_eval_caches();
926 }
927
928 pub fn constant_derivatives(&self) -> ConstantDerivatives {
930 self.const_deriv
931 }
932
933 pub fn nlp_info(&self) -> &NlpInfo {
936 &self.info
937 }
938 pub fn classification_n_x_var(&self) -> Index {
939 self.x_space.dim()
940 }
941 pub fn x_space(&self) -> &Rc<DenseVectorSpace> {
942 &self.x_space
943 }
944 pub fn c_space(&self) -> &Rc<DenseVectorSpace> {
945 &self.c_space
946 }
947 pub fn d_space(&self) -> &Rc<DenseVectorSpace> {
948 &self.d_space
949 }
950 pub fn x_l_space(&self) -> &Rc<DenseVectorSpace> {
951 &self.x_l_space
952 }
953 pub fn x_u_space(&self) -> &Rc<DenseVectorSpace> {
954 &self.x_u_space
955 }
956 pub fn d_l_space(&self) -> &Rc<DenseVectorSpace> {
957 &self.d_l_space
958 }
959 pub fn d_u_space(&self) -> &Rc<DenseVectorSpace> {
960 &self.d_u_space
961 }
962 pub fn px_l_space(&self) -> &Rc<ExpansionMatrixSpace> {
963 &self.px_l_space
964 }
965 pub fn px_u_space(&self) -> &Rc<ExpansionMatrixSpace> {
966 &self.px_u_space
967 }
968 pub fn pd_l_space(&self) -> &Rc<ExpansionMatrixSpace> {
969 &self.pd_l_space
970 }
971 pub fn pd_u_space(&self) -> &Rc<ExpansionMatrixSpace> {
972 &self.pd_u_space
973 }
974 pub fn jac_c_space(&self) -> &Rc<GenTMatrixSpace> {
975 &self.jac_c_space
976 }
977 pub fn jac_d_space(&self) -> &Rc<GenTMatrixSpace> {
978 &self.jac_d_space
979 }
980 pub fn h_space(&self) -> Option<&Rc<SymTMatrixSpace>> {
981 self.h_space.as_ref()
982 }
983
984 pub fn obj_scale_factor(&self) -> Number {
987 self.obj_scale_factor.get()
988 }
989
990 pub fn snapshot_declared_bounds(&mut self) {
1017 *self.declared_d_l.borrow_mut() = Some(self.d_l.expanded_values());
1018 *self.declared_d_u.borrow_mut() = Some(self.d_u.expanded_values());
1019 *self.declared_x_l.borrow_mut() = Some(self.x_l.expanded_values());
1020 *self.declared_x_u.borrow_mut() = Some(self.x_u.expanded_values());
1021 }
1022
1023 pub fn relax_bounds(&mut self, bound_relax_factor: Number, constr_viol_tol: Number) {
1024 self.relax_bounds_impl(bound_relax_factor, constr_viol_tol, true);
1025 }
1026
1027 pub fn relax_bounds_keeping_box(
1036 &mut self,
1037 bound_relax_factor: Number,
1038 constr_viol_tol: Number,
1039 ) {
1040 self.relax_bounds_impl(bound_relax_factor, constr_viol_tol, false);
1041 }
1042
1043 fn relax_bounds_impl(
1044 &mut self,
1045 bound_relax_factor: Number,
1046 constr_viol_tol: Number,
1047 widen_box: bool,
1048 ) {
1049 self.snapshot_declared_bounds();
1050 if bound_relax_factor <= 0.0 {
1051 return;
1052 }
1053 let relax = bound_relax_factor.abs();
1054 let cap = constr_viol_tol;
1055 let apply = |v: &mut DenseVector, sign: Number| {
1056 let xs = v.values_mut();
1057 for x in xs.iter_mut() {
1058 let delta = (relax * x.abs().max(1.0)).min(cap);
1059 *x += sign * delta;
1060 }
1061 };
1062 let apply_d = |v: &mut DenseVector, sign: Number| {
1080 let rel_width = relax.min(cap);
1081 let xs = v.values_mut();
1082 for x in xs.iter_mut() {
1083 let scale = if *x == 0.0 { 1.0 } else { x.abs() };
1084 *x += sign * rel_width * scale;
1085 }
1086 };
1087 if widen_box {
1094 apply(
1095 Rc::get_mut(&mut self.x_l).expect("relax_bounds: x_l is uniquely owned"),
1096 -1.0,
1097 );
1098 apply(
1099 Rc::get_mut(&mut self.x_u).expect("relax_bounds: x_u is uniquely owned"),
1100 1.0,
1101 );
1102 } else {
1103 let cls = self.adapter.borrow().classification().clone();
1113 let n_var = cls.x_not_fixed_map.len();
1114 let mut lower = vec![Number::NEG_INFINITY; n_var];
1115 let mut upper = vec![Number::INFINITY; n_var];
1116 for (&v, &b) in cls.x_l_map.iter().zip(self.x_l.expanded_values().iter()) {
1117 lower[v as usize] = b;
1118 }
1119 for (&v, &b) in cls.x_u_map.iter().zip(self.x_u.expanded_values().iter()) {
1120 upper[v as usize] = b;
1121 }
1122 let point = |v: Index| lower[v as usize] == upper[v as usize];
1123 let delta = |b: Number| (relax * b.abs().max(1.0)).min(cap);
1124 {
1125 let xl = Rc::get_mut(&mut self.x_l).expect("relax_bounds: x_l is uniquely owned");
1126 let vals = xl.values_mut();
1127 for (i, &v) in cls.x_l_map.iter().enumerate() {
1128 if point(v) {
1129 vals[i] -= delta(vals[i]);
1130 }
1131 }
1132 }
1133 {
1134 let xu = Rc::get_mut(&mut self.x_u).expect("relax_bounds: x_u is uniquely owned");
1135 let vals = xu.values_mut();
1136 for (i, &v) in cls.x_u_map.iter().enumerate() {
1137 if point(v) {
1138 vals[i] += delta(vals[i]);
1139 }
1140 }
1141 }
1142 }
1143 apply_d(
1144 Rc::get_mut(&mut self.d_l).expect("relax_bounds: d_l is uniquely owned"),
1145 -1.0,
1146 );
1147 apply_d(
1148 Rc::get_mut(&mut self.d_u).expect("relax_bounds: d_u is uniquely owned"),
1149 1.0,
1150 );
1151 }
1152
1153 pub fn determine_scaling_from_starting_point(
1175 &mut self,
1176 method: ScalingMethod,
1177 max_gradient: Number,
1178 min_value: Number,
1179 obj_target_gradient: Number,
1180 constr_target_gradient: Number,
1181 ) {
1182 let user_obj_factor = self.scaling.obj_scaling();
1185 if matches!(method, ScalingMethod::None) {
1186 self.obj_scale_factor.set(user_obj_factor);
1187 *self.c_scale.borrow_mut() = None;
1188 *self.d_scale.borrow_mut() = None;
1189 self.invalidate_eval_caches();
1190 return;
1191 }
1192
1193 let cls = self.adapter.borrow().classification().clone();
1195 let n_full_x = cls.n_full_x as usize;
1196 let n_full_g = cls.n_full_g as usize;
1197 let mut full_x = vec![0.0; n_full_x];
1198 let mut full_z_l = vec![0.0; n_full_x];
1199 let mut full_z_u = vec![0.0; n_full_x];
1200 let mut full_lambda = vec![0.0; n_full_g];
1201 let starting_ok = {
1202 let a = self.adapter.borrow();
1203 let mut t = a.tnlp().borrow_mut();
1204 t.get_starting_point(StartingPoint {
1205 init_x: true,
1206 x: &mut full_x,
1207 init_z: false,
1208 z_l: &mut full_z_l,
1209 z_u: &mut full_z_u,
1210 init_lambda: false,
1211 lambda: &mut full_lambda,
1212 })
1213 };
1214 if !starting_ok {
1215 self.obj_scale_factor.set(user_obj_factor);
1217 *self.c_scale.borrow_mut() = None;
1218 *self.d_scale.borrow_mut() = None;
1219 self.invalidate_eval_caches();
1220 return;
1221 }
1222
1223 for (i, &full_idx) in cls.x_fixed_map.iter().enumerate() {
1236 full_x[full_idx as usize] = cls.x_fixed_vals[i];
1237 }
1238
1239 match method {
1240 ScalingMethod::None => unreachable!("handled above"),
1241 ScalingMethod::GradientBased => {
1242 self.scale_gradient_based(
1243 &cls,
1244 &full_x,
1245 user_obj_factor,
1246 max_gradient,
1247 min_value,
1248 obj_target_gradient,
1249 constr_target_gradient,
1250 );
1251 }
1252 ScalingMethod::UserScaling => {
1253 let applied = self.scale_user_supplied(&cls, user_obj_factor, min_value);
1254 if !applied {
1255 self.obj_scale_factor.set(user_obj_factor);
1259 *self.c_scale.borrow_mut() = None;
1260 *self.d_scale.borrow_mut() = None;
1261 }
1262 }
1263 }
1264
1265 self.apply_d_scale_to_bounds();
1268
1269 self.invalidate_eval_caches();
1272 }
1273
1274 fn scale_gradient_based(
1277 &self,
1278 cls: &BoundClassification,
1279 full_x: &[Number],
1280 user_obj_factor: Number,
1281 max_gradient: Number,
1282 min_value: Number,
1283 obj_target_gradient: Number,
1284 constr_target_gradient: Number,
1285 ) {
1286 let n_full_x = cls.n_full_x as usize;
1287 let n_full_g = cls.n_full_g as usize;
1288
1289 let mut full_grad_f = vec![0.0; n_full_x];
1291 let grad_ok = {
1292 let a = self.adapter.borrow();
1293 let mut t = a.tnlp().borrow_mut();
1294 t.eval_grad_f(full_x, true, &mut full_grad_f)
1295 };
1296 let mut df = 1.0;
1297 if grad_ok {
1298 let mut max_grad_f: Number = 0.0;
1301 for &full_idx in cls.x_not_fixed_map.iter() {
1302 let v = full_grad_f[full_idx as usize].abs();
1303 if v > max_grad_f {
1304 max_grad_f = v;
1305 }
1306 }
1307 self.start_obj_grad_max.set(max_grad_f);
1308 if max_grad_f.is_finite() {
1316 df = gradient_obj_scale(max_grad_f, max_gradient, min_value, obj_target_gradient);
1317 }
1318 }
1319 self.computed_obj_scale.set(df);
1320 self.obj_scale_factor.set(df * user_obj_factor);
1321
1322 if cls.n_full_g == 0 {
1324 *self.c_scale.borrow_mut() = None;
1325 *self.d_scale.borrow_mut() = None;
1326 return;
1327 }
1328 let mut full_jac_vals = vec![0.0; self.nnz_jac_g_full as usize];
1330 let jac_ok = {
1331 let a = self.adapter.borrow();
1332 let mut t = a.tnlp().borrow_mut();
1333 t.eval_jac_g(
1334 Some(full_x),
1335 true,
1336 SparsityRequest::Values {
1337 values: &mut full_jac_vals,
1338 },
1339 )
1340 };
1341 if !jac_ok {
1342 *self.c_scale.borrow_mut() = None;
1343 *self.d_scale.borrow_mut() = None;
1344 return;
1345 }
1346 let mut full_irow = vec![0 as Index; self.nnz_jac_g_full as usize];
1348 let mut full_jcol = vec![0 as Index; self.nnz_jac_g_full as usize];
1349 let _ = {
1350 let a = self.adapter.borrow();
1351 let mut t = a.tnlp().borrow_mut();
1352 t.eval_jac_g(
1353 None,
1354 false,
1355 SparsityRequest::Structure {
1356 irow: &mut full_irow,
1357 jcol: &mut full_jcol,
1358 },
1359 )
1360 };
1361 let style_offset: Index = match self.info.index_style {
1362 crate::tnlp::IndexStyle::C => 0,
1363 crate::tnlp::IndexStyle::Fortran => 1,
1364 };
1365 let mut g_to_c = vec![-1 as Index; n_full_g];
1367 for (c_idx, &g_idx) in cls.c_map.iter().enumerate() {
1368 g_to_c[g_idx as usize] = c_idx as Index;
1369 }
1370 let mut g_to_d = vec![-1 as Index; n_full_g];
1371 for (d_idx, &g_idx) in cls.d_map.iter().enumerate() {
1372 g_to_d[g_idx as usize] = d_idx as Index;
1373 }
1374 let n_c = cls.n_c as usize;
1375 let n_d = cls.n_d as usize;
1376 let dbl_min = Number::MIN_POSITIVE;
1378 let mut c_row_max: Vec<Number> = vec![dbl_min; n_c];
1379 let mut d_row_max: Vec<Number> = vec![dbl_min; n_d];
1380 for k in 0..self.nnz_jac_g_full as usize {
1381 let g_row_0 = (full_irow[k] - style_offset) as usize;
1382 let v = full_jac_vals[k].abs();
1383 let cr = g_to_c[g_row_0];
1384 if cr >= 0 {
1385 let row = cr as usize;
1386 if v > c_row_max[row] {
1387 c_row_max[row] = v;
1388 }
1389 } else {
1390 let dr = g_to_d[g_row_0];
1391 if dr >= 0 {
1392 let row = dr as usize;
1393 if v > d_row_max[row] {
1394 d_row_max[row] = v;
1395 }
1396 }
1397 }
1398 }
1399
1400 let row_max_to_scale = |row_max: Number| -> Number {
1401 gradient_row_scale(row_max, max_gradient, min_value, constr_target_gradient)
1402 };
1403 let any_row_above = |rows: &[Number]| -> bool {
1404 gradient_scaling_fires(rows, max_gradient, constr_target_gradient)
1405 };
1406
1407 if n_c > 0 && any_row_above(&c_row_max) {
1408 let dc: Vec<Number> = c_row_max.iter().map(|&v| row_max_to_scale(v)).collect();
1409 *self.c_scale.borrow_mut() = Some(dc);
1410 } else {
1411 *self.c_scale.borrow_mut() = None;
1412 }
1413
1414 if n_d > 0 && any_row_above(&d_row_max) {
1415 let dd: Vec<Number> = d_row_max.iter().map(|&v| row_max_to_scale(v)).collect();
1416 *self.d_scale.borrow_mut() = Some(dd);
1417 } else {
1418 *self.d_scale.borrow_mut() = None;
1419 }
1420 }
1421
1422 fn scale_user_supplied(
1439 &self,
1440 cls: &BoundClassification,
1441 user_obj_factor: Number,
1442 min_value: Number,
1443 ) -> bool {
1444 let n_full_x = cls.n_full_x as usize;
1445 let n_full_g = cls.n_full_g as usize;
1446 let mut obj_scaling: Number = 1.0;
1447 let mut use_x_scaling = false;
1448 let mut x_scaling = vec![1.0; n_full_x];
1449 let mut use_g_scaling = false;
1450 let mut g_scaling = vec![1.0; n_full_g];
1451 let ok = {
1452 let a = self.adapter.borrow();
1453 let mut t = a.tnlp().borrow_mut();
1454 t.get_scaling_parameters(ScalingRequest {
1455 obj_scaling: &mut obj_scaling,
1456 use_x_scaling: &mut use_x_scaling,
1457 x_scaling: &mut x_scaling,
1458 use_g_scaling: &mut use_g_scaling,
1459 g_scaling: &mut g_scaling,
1460 })
1461 };
1462 if !ok {
1463 return false;
1464 }
1465
1466 let mut df = obj_scaling;
1470 if df.abs() < min_value {
1471 df = df.signum().max(0.0).max(1.0) * min_value;
1474 }
1475 self.obj_scale_factor.set(df * user_obj_factor);
1476
1477 if use_g_scaling && g_scaling.len() == n_full_g {
1479 let n_c = cls.n_c as usize;
1480 let n_d = cls.n_d as usize;
1481 let mut dc = vec![1.0; n_c];
1482 for (c_idx, &g_idx) in cls.c_map.iter().enumerate() {
1483 let s = g_scaling[g_idx as usize];
1484 dc[c_idx] = if s < min_value { min_value } else { s };
1485 }
1486 let mut dd = vec![1.0; n_d];
1487 for (d_idx, &g_idx) in cls.d_map.iter().enumerate() {
1488 let s = g_scaling[g_idx as usize];
1489 dd[d_idx] = if s < min_value { min_value } else { s };
1490 }
1491 let nontrivial_c = dc.iter().any(|&s| s != 1.0);
1494 *self.c_scale.borrow_mut() = if nontrivial_c && n_c > 0 {
1495 Some(dc)
1496 } else {
1497 None
1498 };
1499 let nontrivial_d = dd.iter().any(|&s| s != 1.0);
1500 *self.d_scale.borrow_mut() = if nontrivial_d && n_d > 0 {
1501 Some(dd)
1502 } else {
1503 None
1504 };
1505 } else {
1506 *self.c_scale.borrow_mut() = None;
1507 *self.d_scale.borrow_mut() = None;
1508 }
1509 if use_x_scaling && x_scaling.iter().any(|&s| s != 1.0) {
1513 self.x_scaling_rejected.set(true);
1514 }
1515 true
1516 }
1517
1518 pub fn user_x_scaling_rejected(&self) -> bool {
1524 self.x_scaling_rejected.get()
1525 }
1526
1527 fn apply_d_scale_to_bounds(&mut self) {
1532 let cls = self.adapter.borrow().classification().clone();
1533 if let Some(dd) = self.d_scale.borrow().as_ref() {
1534 if let Some(d_l) = Rc::get_mut(&mut self.d_l) {
1535 let xs = d_l.values_mut();
1536 for (i, slot) in xs.iter_mut().enumerate() {
1537 let d_idx = cls.d_l_map[i] as usize;
1538 *slot *= dd[d_idx];
1539 }
1540 }
1541 if let Some(d_u) = Rc::get_mut(&mut self.d_u) {
1542 let xs = d_u.values_mut();
1543 for (i, slot) in xs.iter_mut().enumerate() {
1544 let d_idx = cls.d_u_map[i] as usize;
1545 *slot *= dd[d_idx];
1546 }
1547 }
1548 }
1549 }
1550
1551 fn invalidate_eval_caches(&self) {
1552 self.f_cache.borrow_mut().clear();
1553 self.grad_f_cache.borrow_mut().clear();
1554 self.c_cache.borrow_mut().clear();
1555 self.d_cache.borrow_mut().clear();
1556 self.jac_c_cache.borrow_mut().clear();
1557 self.jac_d_cache.borrow_mut().clear();
1558 self.h_cache.borrow_mut().clear();
1559 }
1560
1561 pub fn f_evals(&self) -> Index {
1562 *self.f_evals.borrow()
1563 }
1564 pub fn grad_f_evals(&self) -> Index {
1565 *self.grad_f_evals.borrow()
1566 }
1567 pub fn c_evals(&self) -> Index {
1568 *self.c_evals.borrow()
1569 }
1570 pub fn d_evals(&self) -> Index {
1571 *self.d_evals.borrow()
1572 }
1573 pub fn jac_c_evals(&self) -> Index {
1574 *self.jac_c_evals.borrow()
1575 }
1576 pub fn jac_d_evals(&self) -> Index {
1577 *self.jac_d_evals.borrow()
1578 }
1579 pub fn h_evals(&self) -> Index {
1580 *self.h_evals.borrow()
1581 }
1582
1583 pub fn lift_x_to_full(&self, x: &dyn Vector) -> Vec<Number> {
1589 let Some(dx) = x.as_any().downcast_ref::<DenseVector>() else {
1590 panic!("OrigIpoptNlp expects DenseVector for x");
1591 };
1592 let a = self.adapter.borrow();
1593 let cls = a.classification();
1594 let mut full = vec![0.0; cls.n_full_x as usize];
1595 let vals = dx.expanded_values();
1596 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
1597 full[full_idx as usize] = vals[var_idx];
1598 }
1599 for (i, &full_idx) in cls.x_fixed_map.iter().enumerate() {
1600 full[full_idx as usize] = cls.x_fixed_vals[i];
1601 }
1602 full
1603 }
1604
1605 pub fn set_honor_original_bounds(&self, on: bool) {
1609 self.honor_original_bounds.set(on);
1610 }
1611
1612 pub fn finalize_solution_x(&self, x: &dyn Vector) -> Vec<Number> {
1630 let mut full = self.lift_x_to_full(x);
1631 if !self.honor_original_bounds.get() {
1632 return full;
1633 }
1634 let cls = self.adapter.borrow().classification().clone();
1635 if let Some(x_l) = self.declared_x_l.borrow().as_ref() {
1638 for (i, &var_idx) in cls.x_l_map.iter().enumerate() {
1639 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
1640 if full[full_idx] < x_l[i] {
1641 full[full_idx] = x_l[i];
1642 }
1643 }
1644 }
1645 if let Some(x_u) = self.declared_x_u.borrow().as_ref() {
1646 for (i, &var_idx) in cls.x_u_map.iter().enumerate() {
1647 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
1648 if full[full_idx] > x_u[i] {
1649 full[full_idx] = x_u[i];
1650 }
1651 }
1652 }
1653 full
1654 }
1655
1656 pub fn finalize_solution_lambda(&self, y_c: &dyn Vector, y_d: &dyn Vector) -> Vec<Number> {
1668 let cls = self.adapter.borrow().classification().clone();
1669 let mut lambda = self.pack_lambda_for_user(y_c, y_d, &cls);
1670 let obj_scal = self.obj_scale_factor.get();
1671 if obj_scal != 0.0 && obj_scal != 1.0 {
1672 let inv = 1.0 / obj_scal;
1673 for v in lambda.iter_mut() {
1674 *v *= inv;
1675 }
1676 }
1677 lambda
1678 }
1679
1680 pub fn finalize_solution_z_l(&self, z_l: &dyn Vector) -> Vec<Number> {
1688 let cls = self.adapter.borrow().classification().clone();
1689 let n_full_x = cls.n_full_x as usize;
1690 let mut full_z_l = vec![0.0; n_full_x];
1691 let n_x_l = self.x_l.dim() as usize;
1692 if n_x_l == 0 {
1693 return full_z_l;
1694 }
1695 let Some(dz) = z_l.as_any().downcast_ref::<DenseVector>() else {
1696 panic!("OrigIpoptNlp::finalize_solution_z_l expects DenseVector");
1697 };
1698 let vals = dz.expanded_values();
1699 let obj_scal = self.obj_scale_factor.get();
1700 let inv = if obj_scal == 0.0 { 1.0 } else { 1.0 / obj_scal };
1701 for i in 0..n_x_l {
1702 let var_idx = cls.x_l_map[i] as usize;
1703 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1704 full_z_l[full_idx] = vals[i] * inv;
1705 }
1706 full_z_l
1707 }
1708
1709 pub fn complete_fixed_var_bound_multipliers(
1730 &self,
1731 x: &[Number],
1732 lambda: &[Number],
1733 z_l: &mut [Number],
1734 z_u: &mut [Number],
1735 ) {
1736 let (fixed, tnlp) = {
1737 let adapter = self.adapter.borrow();
1738 let fixed = adapter.classification().x_fixed_map.clone();
1739 (fixed, Rc::clone(adapter.tnlp()))
1740 };
1741 if fixed.is_empty() {
1742 return;
1743 }
1744 let Some(s) = self
1745 .recorded_fixed_lagrangian(x, lambda)
1746 .or_else(|| lagrangian_gradient_full(&tnlp, x, lambda))
1747 else {
1748 return;
1749 };
1750 for j in fixed {
1751 let j = j as usize;
1752 if let (Some(&sj), Some(l), Some(u)) = (s.get(j), z_l.get_mut(j), z_u.get_mut(j)) {
1753 *l = sj.max(0.0);
1754 *u = (-sj).max(0.0);
1755 }
1756 }
1757 }
1758
1759 fn recorded_fixed_lagrangian(&self, x: &[Number], lambda: &[Number]) -> Option<Vec<Number>> {
1769 let grad = self
1770 .fixed_reach
1771 .last_full_grad_f
1772 .borrow()
1773 .as_ref()
1774 .and_then(|(at, g)| {
1775 (self.const_deriv.grad_f || at.as_slice() == x).then(|| g.as_ref().clone())
1776 })?;
1777 let mut s = grad;
1778 if !self.fixed_reach.jac_fixed_entries.is_empty() {
1779 let jac_constant = self.const_deriv.jac_c && self.const_deriv.jac_d;
1780 let rec = self.fixed_reach.last_full_jac_g.borrow();
1781 let (at, vals) = rec.as_ref()?;
1782 if !(jac_constant || at.as_slice() == x) {
1783 return None;
1784 }
1785 for &(k, row, col) in &self.fixed_reach.jac_fixed_entries {
1786 if let (Some(&v), Some(&l), Some(sc)) =
1787 (vals.get(k), lambda.get(row), s.get_mut(col))
1788 {
1789 *sc += v * l;
1790 }
1791 }
1792 }
1793 Some(s)
1794 }
1795
1796 pub fn finalize_solution_z_u(&self, z_u: &dyn Vector) -> Vec<Number> {
1799 let cls = self.adapter.borrow().classification().clone();
1800 let n_full_x = cls.n_full_x as usize;
1801 let mut full_z_u = vec![0.0; n_full_x];
1802 let n_x_u = self.x_u.dim() as usize;
1803 if n_x_u == 0 {
1804 return full_z_u;
1805 }
1806 let Some(dz) = z_u.as_any().downcast_ref::<DenseVector>() else {
1807 panic!("OrigIpoptNlp::finalize_solution_z_u expects DenseVector");
1808 };
1809 let vals = dz.expanded_values();
1810 let obj_scal = self.obj_scale_factor.get();
1811 let inv = if obj_scal == 0.0 { 1.0 } else { 1.0 / obj_scal };
1812 for i in 0..n_x_u {
1813 let var_idx = cls.x_u_map[i] as usize;
1814 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1815 full_z_u[full_idx] = vals[i] * inv;
1816 }
1817 full_z_u
1818 }
1819
1820 pub fn pack_lambda_for_user(
1830 &self,
1831 y_c: &dyn Vector,
1832 y_d: &dyn Vector,
1833 cls: &BoundClassification,
1834 ) -> Vec<Number> {
1835 let mut lambda = vec![0.0; cls.n_full_g as usize];
1836 if cls.n_c > 0 {
1837 let Some(dy) = y_c.as_any().downcast_ref::<DenseVector>() else {
1838 panic!("OrigIpoptNlp expects DenseVector for y_c");
1839 };
1840 let vals = dy.expanded_values();
1841 let cs = self.c_scale.borrow();
1842 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1843 lambda[g_idx as usize] = match cs.as_ref() {
1844 Some(v) => vals[i] * v[i],
1845 None => vals[i],
1846 };
1847 }
1848 }
1849 if cls.n_d > 0 {
1850 let Some(dy) = y_d.as_any().downcast_ref::<DenseVector>() else {
1851 panic!("OrigIpoptNlp expects DenseVector for y_d");
1852 };
1853 let vals = dy.expanded_values();
1854 let ds = self.d_scale.borrow();
1855 for (i, &g_idx) in cls.d_map.iter().enumerate() {
1856 lambda[g_idx as usize] = match ds.as_ref() {
1857 Some(v) => vals[i] * v[i],
1858 None => vals[i],
1859 };
1860 }
1861 }
1862 lambda
1863 }
1864
1865 fn fetch_warm_start_snapshot(&self) -> Option<StartingPointSnapshot> {
1868 let cls = self.adapter.borrow().classification().clone();
1869 let mut snapshot = StartingPointSnapshot {
1891 x: vec![0.0; cls.n_full_x as usize],
1892 z_l: vec![Number::NAN; cls.n_full_x as usize],
1893 z_u: vec![Number::NAN; cls.n_full_x as usize],
1894 lambda: vec![0.0; cls.n_full_g as usize],
1895 };
1896 let ok = {
1897 let a = self.adapter.borrow();
1898 let mut t = a.tnlp().borrow_mut();
1899 t.get_starting_point(StartingPoint {
1900 init_x: true,
1901 x: &mut snapshot.x,
1902 init_z: true,
1903 z_l: &mut snapshot.z_l,
1904 z_u: &mut snapshot.z_u,
1905 init_lambda: true,
1906 lambda: &mut snapshot.lambda,
1907 })
1908 };
1909 ok.then_some(snapshot)
1910 }
1911
1912 #[allow(clippy::too_many_arguments)]
1920 pub fn initialize_starting_point(
1921 &mut self,
1922 x: &mut DenseVector,
1923 init_x: bool,
1924 y_c: &mut DenseVector,
1925 init_y_c: bool,
1926 y_d: &mut DenseVector,
1927 init_y_d: bool,
1928 z_l: &mut DenseVector,
1929 init_z_l: bool,
1930 z_u: &mut DenseVector,
1931 init_z_u: bool,
1932 ) -> bool {
1933 let n_full_x = self.adapter.borrow().classification().n_full_x as usize;
1934 let n_full_g = self.adapter.borrow().classification().n_full_g as usize;
1935 let n_x_l = self.x_l.dim() as usize;
1936 let n_x_u = self.x_u.dim() as usize;
1937
1938 let mut full_x = vec![0.0; n_full_x];
1939 let mut full_z_l = vec![Number::NAN; n_full_x];
1944 let mut full_z_u = vec![Number::NAN; n_full_x];
1945 let mut full_lambda = vec![0.0; n_full_g];
1946
1947 let ok = {
1948 let a = self.adapter.borrow();
1949 let mut t = a.tnlp().borrow_mut();
1950 t.get_starting_point(StartingPoint {
1951 init_x,
1952 x: &mut full_x,
1953 init_z: init_z_l || init_z_u,
1954 z_l: &mut full_z_l,
1955 z_u: &mut full_z_u,
1956 init_lambda: init_y_c || init_y_d,
1957 lambda: &mut full_lambda,
1958 })
1959 };
1960 if !ok {
1961 return false;
1962 }
1963
1964 let cls = self.adapter.borrow().classification().clone();
1965 let obj_scal = self.obj_scale_factor.get();
1966 let c_scale = self.c_scale.borrow();
1967 let d_scale = self.d_scale.borrow();
1968
1969 if init_x {
1971 let xs = x.values_mut();
1972 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
1973 xs[var_idx] = full_x[full_idx as usize];
1974 }
1975 }
1976 if init_y_c && cls.n_c > 0 {
1982 let yc = y_c.values_mut();
1983 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1984 let cs = c_scale.as_ref().map(|v| v[i]).unwrap_or(1.0);
1985 yc[i] = full_lambda[g_idx as usize] / cs * obj_scal;
1986 }
1987 }
1988 if init_y_d && cls.n_d > 0 {
1989 let yd = y_d.values_mut();
1990 for (i, &g_idx) in cls.d_map.iter().enumerate() {
1991 let ds = d_scale.as_ref().map(|v| v[i]).unwrap_or(1.0);
1992 yd[i] = full_lambda[g_idx as usize] / ds * obj_scal;
1993 }
1994 }
1995 if init_z_l && n_x_l > 0 {
1997 let zl = z_l.values_mut();
1998 for (i, slot) in zl.iter_mut().enumerate().take(n_x_l) {
1999 let var_idx = cls.x_l_map[i] as usize;
2000 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
2001 *slot = full_z_l[full_idx] * obj_scal;
2002 }
2003 }
2004 if init_z_u && n_x_u > 0 {
2005 let zu = z_u.values_mut();
2006 for (i, slot) in zu.iter_mut().enumerate().take(n_x_u) {
2007 let var_idx = cls.x_u_map[i] as usize;
2008 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
2009 *slot = full_z_u[full_idx] * obj_scal;
2010 }
2011 }
2012 true
2013 }
2014
2015 fn eval_f_internal(&self, x: &dyn Vector) -> Number {
2018 if let Some(v) = self.f_cache.borrow().get_1dep(x.as_tagged()) {
2019 return v;
2020 }
2021 *self.f_evals.borrow_mut() += 1;
2022 let full_x = self.lift_x_to_full(x);
2023 let unscaled = {
2024 let a = self.adapter.borrow();
2025 let mut t = a.tnlp().borrow_mut();
2026 t.eval_f(&full_x, true).unwrap_or(f64::NAN)
2031 };
2032 let scaled = unscaled * self.obj_scale_factor.get();
2033 self.f_cache.borrow_mut().add_1dep(scaled, x.as_tagged());
2034 scaled
2035 }
2036
2037 fn eval_grad_f_internal(&self, x: &dyn Vector) -> Rc<dyn Vector> {
2038 if self.const_deriv.grad_f
2041 && let Some(v) = self.grad_f_cache.borrow().get(&[], &[])
2042 {
2043 return v;
2044 }
2045 if let Some(v) = self.grad_f_cache.borrow().get_1dep(x.as_tagged()) {
2046 return v;
2047 }
2048 *self.grad_f_evals.borrow_mut() += 1;
2049 let full_x = self.lift_x_to_full(x);
2050 let mut full_g = vec![0.0; full_x.len()];
2051 let ok = {
2052 let a = self.adapter.borrow();
2053 let mut t = a.tnlp().borrow_mut();
2054 t.eval_grad_f(&full_x, true, &mut full_g)
2055 };
2056 if !ok {
2059 full_g.fill(f64::NAN);
2060 }
2061 let cls = self.adapter.borrow().classification().clone();
2063 if ok && !cls.x_fixed_map.is_empty() {
2064 *self.fixed_reach.last_full_grad_f.borrow_mut() =
2065 Some((full_x.clone(), Rc::new(full_g.clone())));
2066 }
2067 let mut g_compressed = self.x_space.make_new_dense();
2068 let obj_scal = self.obj_scale_factor.get();
2069 {
2070 let gv = g_compressed.values_mut();
2071 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
2072 gv[var_idx] = full_g[full_idx as usize] * obj_scal;
2073 }
2074 }
2075 let reuse = self.const_deriv.grad_f && all_finite(g_compressed.values());
2080 let result: Rc<dyn Vector> = Rc::new(g_compressed);
2081 if reuse {
2082 self.grad_f_cache
2083 .borrow_mut()
2084 .add(Rc::clone(&result), &[], &[]);
2085 } else {
2086 self.grad_f_cache
2087 .borrow_mut()
2088 .add_1dep(Rc::clone(&result), x.as_tagged());
2089 }
2090 result
2091 }
2092
2093 fn full_g(&self, x: &dyn Vector) -> Rc<Vec<Number>> {
2099 if let Some(v) = self.full_g_cache.borrow().get_1dep(x.as_tagged()) {
2100 return v;
2101 }
2102 let n_full_g = self.adapter.borrow().classification().n_full_g as usize;
2103 let full_x = self.lift_x_to_full(x);
2104 let mut full_g = vec![0.0; n_full_g];
2105 let ok = {
2106 let a = self.adapter.borrow();
2107 let mut t = a.tnlp().borrow_mut();
2108 t.eval_g(&full_x, true, &mut full_g)
2109 };
2110 if !ok {
2111 full_g.fill(f64::NAN);
2112 }
2113 let result = Rc::new(full_g);
2114 self.full_g_cache
2115 .borrow_mut()
2116 .add_1dep(Rc::clone(&result), x.as_tagged());
2117 result
2118 }
2119
2120 fn full_jac_g(&self, x: &dyn Vector) -> Rc<Vec<Number>> {
2125 if let Some(v) = self.full_jac_g_cache.borrow().get_1dep(x.as_tagged()) {
2126 return v;
2127 }
2128 let mut full_vals = vec![0.0; self.nnz_jac_g_full as usize];
2129 let full_x = self.lift_x_to_full(x);
2130 let ok = {
2131 let a = self.adapter.borrow();
2132 let mut t = a.tnlp().borrow_mut();
2133 t.eval_jac_g(
2134 Some(&full_x),
2135 true,
2136 SparsityRequest::Values {
2137 values: &mut full_vals,
2138 },
2139 )
2140 };
2141 if !ok {
2142 full_vals.fill(f64::NAN);
2143 }
2144 let result = Rc::new(full_vals);
2145 if ok && !self.fixed_reach.jac_fixed_entries.is_empty() {
2146 *self.fixed_reach.last_full_jac_g.borrow_mut() = Some((full_x, Rc::clone(&result)));
2147 }
2148 self.full_jac_g_cache
2149 .borrow_mut()
2150 .add_1dep(Rc::clone(&result), x.as_tagged());
2151 result
2152 }
2153
2154 fn eval_c_internal(&self, x: &dyn Vector) -> Rc<dyn Vector> {
2155 let cls = self.adapter.borrow().classification().clone();
2156 if cls.n_c == 0 {
2157 if let Some(v) = self.c_cache.borrow().get_1dep(x.as_tagged()) {
2159 return v;
2160 }
2161 let v = self.c_space.make_new_dense();
2162 let result: Rc<dyn Vector> = Rc::new(v);
2163 self.c_cache
2164 .borrow_mut()
2165 .add_1dep(Rc::clone(&result), x.as_tagged());
2166 return result;
2167 }
2168 if let Some(v) = self.c_cache.borrow().get_1dep(x.as_tagged()) {
2169 return v;
2170 }
2171 *self.c_evals.borrow_mut() += 1;
2172 let full_g = self.full_g(x);
2177 let mut c = self.c_space.make_new_dense();
2178 {
2186 let cv = c.values_mut();
2187 let cs = self.c_scale.borrow();
2188 for (i, &g_idx) in cls.c_map.iter().enumerate() {
2189 let raw = full_g[g_idx as usize] - self.c_rhs[i];
2190 cv[i] = match cs.as_ref() {
2191 Some(v) => raw * v[i],
2192 None => raw,
2193 };
2194 }
2195 }
2196 let result: Rc<dyn Vector> = Rc::new(c);
2197 self.c_cache
2198 .borrow_mut()
2199 .add_1dep(Rc::clone(&result), x.as_tagged());
2200 result
2201 }
2202
2203 fn eval_d_internal(&self, x: &dyn Vector) -> Rc<dyn Vector> {
2204 let cls = self.adapter.borrow().classification().clone();
2205 if cls.n_d == 0 {
2206 if let Some(v) = self.d_cache.borrow().get_1dep(x.as_tagged()) {
2207 return v;
2208 }
2209 let v = self.d_space.make_new_dense();
2210 let result: Rc<dyn Vector> = Rc::new(v);
2211 self.d_cache
2212 .borrow_mut()
2213 .add_1dep(Rc::clone(&result), x.as_tagged());
2214 return result;
2215 }
2216 if let Some(v) = self.d_cache.borrow().get_1dep(x.as_tagged()) {
2217 return v;
2218 }
2219 *self.d_evals.borrow_mut() += 1;
2220 let full_g = self.full_g(x);
2222 let mut d = self.d_space.make_new_dense();
2223 {
2224 let dv = d.values_mut();
2225 let ds = self.d_scale.borrow();
2226 for (i, &g_idx) in cls.d_map.iter().enumerate() {
2227 let raw = full_g[g_idx as usize];
2228 dv[i] = match ds.as_ref() {
2229 Some(v) => raw * v[i],
2230 None => raw,
2231 };
2232 }
2233 }
2234 let result: Rc<dyn Vector> = Rc::new(d);
2235 self.d_cache
2236 .borrow_mut()
2237 .add_1dep(Rc::clone(&result), x.as_tagged());
2238 result
2239 }
2240
2241 fn eval_jac_c_internal(&self, x: &dyn Vector) -> Rc<dyn Matrix> {
2242 if self.const_deriv.jac_c
2243 && let Some(m) = self.jac_c_cache.borrow().get(&[], &[])
2244 {
2245 return m;
2246 }
2247 if let Some(m) = self.jac_c_cache.borrow().get_1dep(x.as_tagged()) {
2248 return m;
2249 }
2250 *self.jac_c_evals.borrow_mut() += 1;
2251 let full_vals = self.full_jac_g(x);
2255 let mut jac_c = GenTMatrix::new(Rc::clone(&self.jac_c_space));
2256 {
2257 let cs = self.c_scale.borrow();
2258 let irows = self.jac_c_space.irows().to_vec();
2259 let vs = jac_c.values_mut();
2260 for (k, &src) in self.jac_c_entry_in_g.iter().enumerate() {
2261 let raw = full_vals[src as usize];
2262 vs[k] = match cs.as_ref() {
2263 Some(v) => raw * v[(irows[k] - 1) as usize],
2265 None => raw,
2266 };
2267 }
2268 }
2269 let reuse = self.const_deriv.jac_c && all_finite(jac_c.values());
2270 let result: Rc<dyn Matrix> = Rc::new(jac_c);
2271 if reuse {
2272 self.jac_c_cache
2273 .borrow_mut()
2274 .add(Rc::clone(&result), &[], &[]);
2275 } else {
2276 self.jac_c_cache
2277 .borrow_mut()
2278 .add_1dep(Rc::clone(&result), x.as_tagged());
2279 }
2280 result
2281 }
2282
2283 fn eval_jac_d_internal(&self, x: &dyn Vector) -> Rc<dyn Matrix> {
2284 if self.const_deriv.jac_d
2285 && let Some(m) = self.jac_d_cache.borrow().get(&[], &[])
2286 {
2287 return m;
2288 }
2289 if let Some(m) = self.jac_d_cache.borrow().get_1dep(x.as_tagged()) {
2290 return m;
2291 }
2292 *self.jac_d_evals.borrow_mut() += 1;
2293 let full_vals = self.full_jac_g(x);
2296 let mut jac_d = GenTMatrix::new(Rc::clone(&self.jac_d_space));
2297 {
2298 let ds = self.d_scale.borrow();
2299 let irows = self.jac_d_space.irows().to_vec();
2300 let vs = jac_d.values_mut();
2301 for (k, &src) in self.jac_d_entry_in_g.iter().enumerate() {
2302 let raw = full_vals[src as usize];
2303 vs[k] = match ds.as_ref() {
2304 Some(v) => raw * v[(irows[k] - 1) as usize],
2305 None => raw,
2306 };
2307 }
2308 }
2309 let reuse = self.const_deriv.jac_d && all_finite(jac_d.values());
2310 let result: Rc<dyn Matrix> = Rc::new(jac_d);
2311 if reuse {
2312 self.jac_d_cache
2313 .borrow_mut()
2314 .add(Rc::clone(&result), &[], &[]);
2315 } else {
2316 self.jac_d_cache
2317 .borrow_mut()
2318 .add_1dep(Rc::clone(&result), x.as_tagged());
2319 }
2320 result
2321 }
2322
2323 fn eval_h_internal(
2324 &self,
2325 x: &dyn Vector,
2326 obj_factor: Number,
2327 y_c: &dyn Vector,
2328 y_d: &dyn Vector,
2329 ) -> Rc<dyn SymMatrix> {
2330 if self.const_deriv.hessian
2342 && let Some(m) = self.h_cache.borrow().get(&[], &[obj_factor])
2343 {
2344 return m;
2345 }
2346 if let Some(m) = self.h_cache.borrow().get(
2347 &[x.as_tagged(), y_c.as_tagged(), y_d.as_tagged()],
2348 &[obj_factor],
2349 ) {
2350 return m;
2351 }
2352 *self.h_evals.borrow_mut() += 1;
2353 let Some(h_space) = self.h_space.as_ref() else {
2354 panic!(
2355 "OrigIpoptNlp::eval_h called but the TNLP did not provide \
2356 eval_h sparsity. The L-BFGS path lands in Phase 8."
2357 );
2358 };
2359 let cls = self.adapter.borrow().classification().clone();
2360 let full_x = self.lift_x_to_full(x);
2361 let full_lambda = self.pack_lambda_for_user(y_c, y_d, &cls);
2369 let scaled_obj_factor = obj_factor * self.obj_scale_factor.get();
2370
2371 let mut full_vals = vec![0.0; self.nnz_h_lag_full as usize];
2376 let ok = {
2377 let a = self.adapter.borrow();
2378 let mut t = a.tnlp().borrow_mut();
2379 t.eval_h(
2380 Some(&full_x),
2381 true,
2382 scaled_obj_factor,
2383 Some(&full_lambda),
2384 true,
2385 SparsityRequest::Values {
2386 values: &mut full_vals,
2387 },
2388 )
2389 };
2390 if !ok {
2391 full_vals.fill(f64::NAN);
2392 }
2393 let mut h = SymTMatrix::new(Rc::clone(h_space));
2394 let kept = h_space.nonzeros() as usize;
2395 let h_vals = h.values_mut();
2396 debug_assert_eq!(kept, self.h_entry_in_full.len());
2399 for (k, &src) in self.h_entry_in_full.iter().enumerate() {
2400 h_vals[k] = full_vals[src as usize];
2401 }
2402 let reuse = self.const_deriv.hessian && all_finite(h.values());
2403 let result: Rc<dyn SymMatrix> = Rc::new(h);
2404 if reuse {
2405 self.h_cache
2406 .borrow_mut()
2407 .add(Rc::clone(&result), &[], &[obj_factor]);
2408 } else {
2409 self.h_cache.borrow_mut().add(
2410 Rc::clone(&result),
2411 &[x.as_tagged(), y_c.as_tagged(), y_d.as_tagged()],
2412 &[obj_factor],
2413 );
2414 }
2415 result
2416 }
2417}
2418
2419fn all_finite(v: &[Number]) -> bool {
2426 v.iter().all(|x| x.is_finite())
2427}
2428
2429fn make_dense_from(
2430 space: &Rc<DenseVectorSpace>,
2431 mut f: impl FnMut(usize) -> Number,
2432) -> DenseVector {
2433 let mut v = space.make_new_dense();
2434 let dim = space.dim() as usize;
2435 if dim > 0 {
2436 let vs = v.values_mut();
2437 for (i, slot) in vs.iter_mut().enumerate().take(dim) {
2438 *slot = f(i);
2439 }
2440 }
2441 v
2442}
2443
2444impl Nlp for OrigIpoptNlp {
2447 fn n(&self) -> Index {
2448 self.x_space.dim()
2449 }
2450 fn m_eq(&self) -> Index {
2451 self.c_space.dim()
2452 }
2453 fn m_ineq(&self) -> Index {
2454 self.d_space.dim()
2455 }
2456
2457 fn eval_f(&mut self, x: &dyn Vector) -> Number {
2458 self.timed_eval(|t| &t.eval_obj, || self.eval_f_internal(x))
2459 }
2460 fn eval_grad_f(&mut self, x: &dyn Vector, g: &mut dyn Vector) {
2461 let result = self.timed_eval(|t| &t.eval_grad_obj, || self.eval_grad_f_internal(x));
2462 g.copy(&*result);
2463 }
2464 fn eval_c(&mut self, x: &dyn Vector, c: &mut dyn Vector) {
2465 let result = self.timed_eval(|t| &t.eval_constr, || self.eval_c_internal(x));
2466 c.copy(&*result);
2467 }
2468 fn eval_d(&mut self, x: &dyn Vector, d: &mut dyn Vector) {
2469 let result = self.timed_eval(|t| &t.eval_constr, || self.eval_d_internal(x));
2470 d.copy(&*result);
2471 }
2472 fn eval_jac_c(&mut self, x: &dyn Vector) -> Rc<dyn Matrix> {
2473 self.timed_eval(|t| &t.eval_constr_jac, || self.eval_jac_c_internal(x))
2474 }
2475 fn eval_jac_d(&mut self, x: &dyn Vector) -> Rc<dyn Matrix> {
2476 self.timed_eval(|t| &t.eval_constr_jac, || self.eval_jac_d_internal(x))
2477 }
2478 fn eval_h(
2479 &mut self,
2480 x: &dyn Vector,
2481 obj_factor: Number,
2482 y_c: &dyn Vector,
2483 y_d: &dyn Vector,
2484 ) -> Rc<dyn SymMatrix> {
2485 self.timed_eval(
2486 |t| &t.eval_lag_hess,
2487 || self.eval_h_internal(x, obj_factor, y_c, y_d),
2488 )
2489 }
2490}
2491
2492impl IpoptNlp for OrigIpoptNlp {
2493 fn invalidate_constraint_values(&self) -> bool {
2494 self.c_cache.borrow_mut().clear();
2497 self.d_cache.borrow_mut().clear();
2498 self.full_g_cache.borrow_mut().clear();
2499 true
2500 }
2501
2502 fn uninitialized_h(&self) -> Rc<dyn SymMatrix> {
2508 match self.h_space.as_ref() {
2509 Some(space) => Rc::new(crate::ipopt_nlp::zeroed_sym_t(Rc::clone(space))),
2510 None => Rc::new(crate::ipopt_nlp::zeroed_sym_t(SymTMatrixSpace::new(
2511 self.x_space.dim(),
2512 Vec::new(),
2513 Vec::new(),
2514 ))),
2515 }
2516 }
2517
2518 fn eval_counts(&self) -> [Index; 7] {
2519 [
2520 self.f_evals(),
2521 self.grad_f_evals(),
2522 self.c_evals(),
2523 self.d_evals(),
2524 self.jac_c_evals(),
2525 self.jac_d_evals(),
2526 self.h_evals(),
2527 ]
2528 }
2529 fn x_l(&self) -> &dyn Vector {
2530 &*self.x_l
2531 }
2532 fn x_u(&self) -> &dyn Vector {
2533 &*self.x_u
2534 }
2535 fn d_l(&self) -> &dyn Vector {
2536 &*self.d_l
2537 }
2538 fn d_u(&self) -> &dyn Vector {
2539 &*self.d_u
2540 }
2541
2542 fn declared_d_bounds(&self) -> Option<(Vec<Number>, Vec<Number>)> {
2543 let mut dl = self.declared_d_l.borrow().clone()?;
2544 let mut du = self.declared_d_u.borrow().clone()?;
2545 if let Some(dd) = self.d_scale.borrow().as_ref() {
2549 let cls = self.adapter.borrow().classification().clone();
2550 for (i, slot) in dl.iter_mut().enumerate() {
2551 *slot *= dd[cls.d_l_map[i] as usize];
2552 }
2553 for (i, slot) in du.iter_mut().enumerate() {
2554 *slot *= dd[cls.d_u_map[i] as usize];
2555 }
2556 }
2557 Some((dl, du))
2558 }
2559
2560 fn declared_box_violation(&self, x: &dyn Vector) -> Option<Number> {
2561 let x_l = self.declared_x_l.borrow();
2567 let x_u = self.declared_x_u.borrow();
2568 if x_l.is_none() && x_u.is_none() {
2569 return None;
2570 }
2571 let full = self.lift_x_to_full(x);
2572 let cls = self.adapter.borrow().classification().clone();
2573 let mut worst = 0.0_f64;
2574 if let Some(x_l) = x_l.as_ref() {
2575 for (i, &var_idx) in cls.x_l_map.iter().enumerate() {
2576 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
2577 let viol = x_l[i] - full[full_idx];
2578 if viol.is_finite() && viol > worst {
2579 worst = viol;
2580 }
2581 }
2582 }
2583 if let Some(x_u) = x_u.as_ref() {
2584 for (i, &var_idx) in cls.x_u_map.iter().enumerate() {
2585 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
2586 let viol = full[full_idx] - x_u[i];
2587 if viol.is_finite() && viol > worst {
2588 worst = viol;
2589 }
2590 }
2591 }
2592 Some(worst)
2593 }
2594
2595 fn declared_x_bounds(&self) -> Option<(Vec<Number>, Vec<Number>)> {
2596 let xl = self.declared_x_l.borrow().clone()?;
2601 let xu = self.declared_x_u.borrow().clone()?;
2602 Some((xl, xu))
2603 }
2604
2605 fn declared_c_rhs(&self) -> Option<Vec<Number>> {
2606 let mut b = self.c_rhs.clone();
2612 if let Some(dc) = self.c_scale.borrow().as_ref() {
2613 for (i, slot) in b.iter_mut().enumerate() {
2614 *slot *= dc[i];
2615 }
2616 }
2617 Some(b)
2618 }
2619
2620 fn px_l(&self) -> Rc<dyn Matrix> {
2621 Rc::clone(&self.px_l)
2622 }
2623 fn px_u(&self) -> Rc<dyn Matrix> {
2624 Rc::clone(&self.px_u)
2625 }
2626 fn pd_l(&self) -> Rc<dyn Matrix> {
2627 Rc::clone(&self.pd_l)
2628 }
2629 fn pd_u(&self) -> Rc<dyn Matrix> {
2630 Rc::clone(&self.pd_u)
2631 }
2632
2633 fn adjust_variable_bounds(
2641 &mut self,
2642 new_x_l: &dyn Vector,
2643 new_x_u: &dyn Vector,
2644 new_d_l: &dyn Vector,
2645 new_d_u: &dyn Vector,
2646 ) {
2647 fn install(slot: &mut Rc<DenseVector>, new: &dyn Vector) {
2651 Rc::get_mut(slot)
2652 .expect("adjust_variable_bounds: bound vector is uniquely owned")
2653 .copy(new);
2654 }
2655 install(&mut self.x_l, new_x_l);
2656 install(&mut self.x_u, new_x_u);
2657 install(&mut self.d_l, new_d_l);
2658 install(&mut self.d_u, new_d_u);
2659 }
2660
2661 fn obj_scaling_factor(&self) -> Number {
2662 self.obj_scale_factor.get()
2663 }
2664
2665 fn computed_obj_scaling_factor(&self) -> Number {
2666 self.computed_obj_scale.get()
2667 }
2668
2669 fn start_obj_gradient_max(&self) -> Number {
2670 self.start_obj_grad_max.get()
2671 }
2672
2673 fn c_scale_vec(&self) -> Option<Vec<Number>> {
2674 self.c_scale.borrow().clone()
2675 }
2676
2677 fn d_scale_vec(&self) -> Option<Vec<Number>> {
2678 self.d_scale.borrow().clone()
2679 }
2680
2681 fn split_space_names(&self) -> Option<SplitNames> {
2695 let a = self.adapter.borrow();
2696 let cls = a.classification();
2697
2698 let mut var_meta = MetaData::default();
2699 let mut con_meta = MetaData::default();
2700 if !a
2701 .tnlp()
2702 .borrow_mut()
2703 .get_var_con_metadata(&mut var_meta, &mut con_meta)
2704 {
2705 return None;
2706 }
2707
2708 let var_full = var_meta.strings.get(IDX_NAMES);
2711 let con_full = con_meta.strings.get(IDX_NAMES);
2712 if var_full.is_none() && con_full.is_none() {
2713 return None;
2714 }
2715
2716 let pick = |pool: Option<&Vec<String>>, full_idx: Index| -> Option<String> {
2719 pool.and_then(|v| v.get(full_idx as usize))
2720 .filter(|s| !s.is_empty())
2721 .cloned()
2722 };
2723
2724 let x_var = cls
2725 .x_not_fixed_map
2726 .iter()
2727 .map(|&full_idx| pick(var_full, full_idx))
2728 .collect();
2729 let eq = cls
2730 .c_map
2731 .iter()
2732 .map(|&full_idx| pick(con_full, full_idx))
2733 .collect();
2734 let ineq = cls
2735 .d_map
2736 .iter()
2737 .map(|&full_idx| pick(con_full, full_idx))
2738 .collect();
2739
2740 let names = SplitNames { x_var, eq, ineq };
2741 names.any_present().then_some(names)
2742 }
2743
2744 fn prepare_warm_start(&mut self) -> bool {
2745 let Some(snapshot) = self.fetch_warm_start_snapshot() else {
2746 return false;
2747 };
2748 *self.warm_start_snapshot.borrow_mut() = Some(snapshot);
2749 true
2750 }
2751
2752 fn finish_warm_start(&mut self) {
2753 self.warm_start_snapshot.borrow_mut().take();
2754 }
2755
2756 fn get_starting_x(&mut self, x: &mut dyn Vector) -> bool {
2760 let cls = self.adapter.borrow().classification().clone();
2761 if let Some(snapshot) = self.warm_start_snapshot.borrow().as_ref() {
2762 let Some(dx) = x.as_any_mut().downcast_mut::<DenseVector>() else {
2763 return false;
2764 };
2765 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
2766 dx.values_mut()[var_idx] = snapshot.x[full_idx as usize];
2767 }
2768 return true;
2769 }
2770 let n_full_x = cls.n_full_x as usize;
2771 let n_full_g = cls.n_full_g as usize;
2772 let mut full_x = vec![0.0; n_full_x];
2773 let mut full_z_l = vec![0.0; n_full_x];
2774 let mut full_z_u = vec![0.0; n_full_x];
2775 let mut full_lambda = vec![0.0; n_full_g];
2776 let ok = {
2777 let a = self.adapter.borrow();
2778 let mut t = a.tnlp().borrow_mut();
2779 t.get_starting_point(StartingPoint {
2780 init_x: true,
2781 x: &mut full_x,
2782 init_z: false,
2783 z_l: &mut full_z_l,
2784 z_u: &mut full_z_u,
2785 init_lambda: false,
2786 lambda: &mut full_lambda,
2787 })
2788 };
2789 if !ok {
2790 return false;
2791 }
2792 let Some(dx) = x.as_any_mut().downcast_mut::<DenseVector>() else {
2793 return false;
2794 };
2795 let xs = dx.values_mut();
2796 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
2797 xs[var_idx] = full_x[full_idx as usize];
2798 }
2799 true
2800 }
2801
2802 fn get_starting_y(&mut self, y_c: &mut dyn Vector, y_d: &mut dyn Vector) -> bool {
2803 let Some(y_c) = y_c.as_any_mut().downcast_mut::<DenseVector>() else {
2804 return false;
2805 };
2806 let Some(y_d) = y_d.as_any_mut().downcast_mut::<DenseVector>() else {
2807 return false;
2808 };
2809 if let Some(snapshot) = self.warm_start_snapshot.borrow().as_ref() {
2810 let cls = self.adapter.borrow().classification().clone();
2811 let obj_scal = self.obj_scale_factor.get();
2812 let c_scale = self.c_scale.borrow();
2813 for (i, &g_idx) in cls.c_map.iter().enumerate() {
2814 let cs = c_scale.as_ref().map(|v| v[i]).unwrap_or(1.0);
2815 y_c.values_mut()[i] = snapshot.lambda[g_idx as usize] / cs * obj_scal;
2816 }
2817 let d_scale = self.d_scale.borrow();
2818 for (i, &g_idx) in cls.d_map.iter().enumerate() {
2819 let ds = d_scale.as_ref().map(|v| v[i]).unwrap_or(1.0);
2820 y_d.values_mut()[i] = snapshot.lambda[g_idx as usize] / ds * obj_scal;
2821 }
2822 return true;
2823 }
2824 let mut x = DenseVectorSpace::new(self.n()).make_new_dense();
2825 let mut z_l = DenseVectorSpace::new(self.x_l.dim()).make_new_dense();
2826 let mut z_u = DenseVectorSpace::new(self.x_u.dim()).make_new_dense();
2827 self.initialize_starting_point(
2828 &mut x, false, y_c, true, y_d, true, &mut z_l, false, &mut z_u, false,
2829 )
2830 }
2831
2832 fn get_starting_z(
2833 &mut self,
2834 z_l: &mut dyn Vector,
2835 z_u: &mut dyn Vector,
2836 _v_l: &mut dyn Vector,
2837 _v_u: &mut dyn Vector,
2838 ) -> bool {
2839 let Some(z_l) = z_l.as_any_mut().downcast_mut::<DenseVector>() else {
2842 return false;
2843 };
2844 let Some(z_u) = z_u.as_any_mut().downcast_mut::<DenseVector>() else {
2845 return false;
2846 };
2847 if let Some(snapshot) = self.warm_start_snapshot.borrow().as_ref() {
2848 let cls = self.adapter.borrow().classification().clone();
2849 let obj_scal = self.obj_scale_factor.get();
2850 for (i, slot) in z_l.values_mut().iter_mut().enumerate() {
2851 let var_idx = cls.x_l_map[i] as usize;
2852 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
2853 *slot = snapshot.z_l[full_idx] * obj_scal;
2854 }
2855 for (i, slot) in z_u.values_mut().iter_mut().enumerate() {
2856 let var_idx = cls.x_u_map[i] as usize;
2857 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
2858 *slot = snapshot.z_u[full_idx] * obj_scal;
2859 }
2860 return true;
2861 }
2862 let mut x = DenseVectorSpace::new(self.n()).make_new_dense();
2863 let mut y_c = DenseVectorSpace::new(self.m_eq()).make_new_dense();
2864 let mut y_d = DenseVectorSpace::new(self.m_ineq()).make_new_dense();
2865 self.initialize_starting_point(
2866 &mut x, false, &mut y_c, false, &mut y_d, false, z_l, true, z_u, true,
2867 )
2868 }
2869
2870 fn lift_x_to_full(&self, x: &dyn Vector) -> Vec<Number> {
2871 OrigIpoptNlp::lift_x_to_full(self, x)
2872 }
2873
2874 fn finalize_solution_x(&self, x: &dyn Vector) -> Vec<Number> {
2875 OrigIpoptNlp::finalize_solution_x(self, x)
2876 }
2877
2878 fn n_full_x(&self) -> Index {
2879 self.adapter.borrow().classification().n_full_x
2880 }
2881
2882 fn n_full_g(&self) -> Index {
2883 self.adapter.borrow().classification().n_full_g
2884 }
2885
2886 fn pack_lambda_for_user(&self, y_c: &dyn Vector, y_d: &dyn Vector) -> Vec<Number> {
2887 let cls = self.adapter.borrow().classification().clone();
2888 OrigIpoptNlp::pack_lambda_for_user(self, y_c, y_d, &cls)
2889 }
2890
2891 fn pack_g_for_user(&self, c: &dyn Vector, d: &dyn Vector) -> Vec<Number> {
2892 let cls = self.adapter.borrow().classification().clone();
2893 let mut g = vec![0.0; cls.n_full_g as usize];
2894 if cls.n_c > 0 {
2895 let Some(dc) = c.as_any().downcast_ref::<DenseVector>() else {
2896 panic!("OrigIpoptNlp expects DenseVector for c");
2897 };
2898 let cs = self.c_scale.borrow();
2899 let c_vals = dc.expanded_values();
2909 for (i, &g_idx) in cls.c_map.iter().enumerate() {
2910 let v = c_vals[i];
2911 g[g_idx as usize] = match cs.as_ref() {
2912 Some(s) => v / s[i],
2913 None => v,
2914 };
2915 }
2916 }
2917 if cls.n_d > 0 {
2918 let Some(dd) = d.as_any().downcast_ref::<DenseVector>() else {
2919 panic!("OrigIpoptNlp expects DenseVector for d");
2920 };
2921 let ds = self.d_scale.borrow();
2922 let d_vals = dd.expanded_values();
2924 for (i, &g_idx) in cls.d_map.iter().enumerate() {
2925 let v = d_vals[i];
2926 g[g_idx as usize] = match ds.as_ref() {
2927 Some(s) => v / s[i],
2928 None => v,
2929 };
2930 }
2931 }
2932 g
2933 }
2934
2935 fn pack_z_l_for_user(&self, z_l: &dyn Vector) -> Vec<Number> {
2936 let cls = self.adapter.borrow().classification().clone();
2937 let mut full = vec![0.0; cls.n_full_x as usize];
2938 if z_l.dim() == 0 {
2939 return full;
2940 }
2941 let Some(dz) = z_l.as_any().downcast_ref::<DenseVector>() else {
2942 panic!("OrigIpoptNlp expects DenseVector for z_l");
2943 };
2944 let vals = dz.expanded_values();
2945 for (k, &var_idx) in cls.x_l_map.iter().enumerate() {
2946 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
2947 full[full_idx] = vals[k];
2948 }
2949 full
2950 }
2951
2952 fn pack_z_u_for_user(&self, z_u: &dyn Vector) -> Vec<Number> {
2953 let cls = self.adapter.borrow().classification().clone();
2954 let mut full = vec![0.0; cls.n_full_x as usize];
2955 if z_u.dim() == 0 {
2956 return full;
2957 }
2958 let Some(dz) = z_u.as_any().downcast_ref::<DenseVector>() else {
2959 panic!("OrigIpoptNlp expects DenseVector for z_u");
2960 };
2961 let vals = dz.expanded_values();
2962 for (k, &var_idx) in cls.x_u_map.iter().enumerate() {
2963 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
2964 full[full_idx] = vals[k];
2965 }
2966 full
2967 }
2968
2969 fn finalize_solution_lambda(&self, y_c: &dyn Vector, y_d: &dyn Vector) -> Vec<Number> {
2970 OrigIpoptNlp::finalize_solution_lambda(self, y_c, y_d)
2971 }
2972
2973 fn finalize_solution_z_l(&self, z_l: &dyn Vector) -> Vec<Number> {
2974 OrigIpoptNlp::finalize_solution_z_l(self, z_l)
2975 }
2976
2977 fn finalize_solution_z_u(&self, z_u: &dyn Vector) -> Vec<Number> {
2978 OrigIpoptNlp::finalize_solution_z_u(self, z_u)
2979 }
2980
2981 fn complete_fixed_var_bound_multipliers(
2982 &self,
2983 x: &[Number],
2984 lambda: &[Number],
2985 z_l: &mut [Number],
2986 z_u: &mut [Number],
2987 ) {
2988 OrigIpoptNlp::complete_fixed_var_bound_multipliers(self, x, lambda, z_l, z_u)
2989 }
2990
2991 fn variable_scaling(&self) -> Option<Vec<Number>> {
2992 self.variable_scaling.clone()
2998 }
2999
3000 fn full_x_to_var_x(&self, full_idx: Index) -> Option<Index> {
3001 let cls = self.adapter.borrow();
3002 let cls = cls.classification();
3003 let f = full_idx as usize;
3004 if f >= cls.full_to_var.len() {
3005 return None;
3006 }
3007 let v = cls.full_to_var[f];
3008 if v < 0 { None } else { Some(v) }
3009 }
3010
3011 fn full_g_to_c_block(&self, full_idx: Index) -> Option<Index> {
3012 let cls = self.adapter.borrow();
3013 let cls = cls.classification();
3014 let f = full_idx as usize;
3015 if f >= cls.full_to_c.len() {
3016 return None;
3017 }
3018 let c = cls.full_to_c[f];
3019 if c < 0 { None } else { Some(c) }
3020 }
3021
3022 fn full_g_to_d_block(&self, full_idx: Index) -> Option<Index> {
3023 let cls = self.adapter.borrow();
3024 let cls = cls.classification();
3025 let f = full_idx as usize;
3026 if f >= cls.full_to_d.len() {
3027 return None;
3028 }
3029 let d = cls.full_to_d[f];
3030 if d < 0 { None } else { Some(d) }
3031 }
3032
3033 fn var_x_to_full_x(&self, var_idx: Index) -> Index {
3034 let cls = self.adapter.borrow();
3035 let cls = cls.classification();
3036 cls.x_not_fixed_map[var_idx as usize]
3037 }
3038}
3039
3040fn lagrangian_gradient_full(
3047 tnlp: &Rc<RefCell<dyn TNLP>>,
3048 x: &[Number],
3049 lambda: &[Number],
3050) -> Option<Vec<Number>> {
3051 let mut t = tnlp.borrow_mut();
3052 let info = t.get_nlp_info()?;
3053 let n = info.n as usize;
3054 let nnz = info.nnz_jac_g as usize;
3055 let mut s = vec![0.0; n];
3056 if !t.eval_grad_f(x, true, &mut s) {
3057 return None;
3058 }
3059 if nnz > 0 {
3060 let mut irow = vec![0 as Index; nnz];
3061 let mut jcol = vec![0 as Index; nnz];
3062 if !t.eval_jac_g(
3063 None,
3064 false,
3065 SparsityRequest::Structure {
3066 irow: &mut irow,
3067 jcol: &mut jcol,
3068 },
3069 ) {
3070 return None;
3071 }
3072 let mut vals = vec![0.0; nnz];
3073 if !t.eval_jac_g(
3074 Some(x),
3075 false,
3076 SparsityRequest::Values { values: &mut vals },
3077 ) {
3078 return None;
3079 }
3080 let off: Index = match info.index_style {
3081 IndexStyle::Fortran => 1,
3082 IndexStyle::C => 0,
3083 };
3084 for k in 0..nnz {
3085 let (Ok(r), Ok(c)) = (
3086 usize::try_from(irow[k] - off),
3087 usize::try_from(jcol[k] - off),
3088 ) else {
3089 continue;
3090 };
3091 if let (Some(&l), Some(sc)) = (lambda.get(r), s.get_mut(c)) {
3092 *sc += vals[k] * l;
3093 }
3094 }
3095 }
3096 Some(s)
3097}
3098
3099#[cfg(test)]
3100mod tests {
3101 use super::*;
3102 use crate::tnlp::{
3103 BoundsInfo, IndexStyle, IpoptCq, IpoptData, NlpInfo, Solution, SparsityRequest,
3104 StartingPoint, TNLP,
3105 };
3106
3107 #[derive(Default)]
3112 struct Hs071 {
3113 eval_f_calls: usize,
3114 eval_grad_f_calls: usize,
3115 eval_g_calls: usize,
3116 eval_jac_g_value_calls: usize,
3117 eval_h_value_calls: usize,
3118 get_bounds_info_calls: usize,
3119 get_starting_point_calls: usize,
3120 }
3121
3122 impl TNLP for Hs071 {
3123 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3124 Some(NlpInfo {
3125 n: 4,
3126 m: 2,
3127 nnz_jac_g: 8,
3128 nnz_h_lag: 10,
3129 index_style: IndexStyle::C,
3130 })
3131 }
3132 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3133 self.get_bounds_info_calls += 1;
3134 b.x_l.copy_from_slice(&[1.0; 4]);
3135 b.x_u.copy_from_slice(&[5.0; 4]);
3136 b.g_l.copy_from_slice(&[25.0, 40.0]);
3139 b.g_u.copy_from_slice(&[2.0e19, 40.0]);
3140 true
3141 }
3142 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3143 self.get_starting_point_calls += 1;
3144 sp.x.copy_from_slice(&[1.0, 5.0, 5.0, 1.0]);
3145 if sp.init_z {
3146 sp.z_l.copy_from_slice(&[1.0, 2.0, 3.0, 4.0]);
3147 sp.z_u.copy_from_slice(&[5.0, 6.0, 7.0, 8.0]);
3148 }
3149 if sp.init_lambda {
3150 sp.lambda.copy_from_slice(&[11.0, 13.0]);
3151 }
3152 true
3153 }
3154 fn eval_f(&mut self, x: &[Number], _new_x: bool) -> Option<Number> {
3155 self.eval_f_calls += 1;
3156 Some(x[0] * x[3] * (x[0] + x[1] + x[2]) + x[2])
3157 }
3158 fn eval_grad_f(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
3159 self.eval_grad_f_calls += 1;
3160 g[0] = x[3] * (2.0 * x[0] + x[1] + x[2]);
3165 g[1] = x[0] * x[3];
3166 g[2] = x[0] * x[3] + 1.0;
3167 g[3] = x[0] * (x[0] + x[1] + x[2]);
3168 true
3169 }
3170 fn eval_g(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
3171 self.eval_g_calls += 1;
3172 g[0] = x[0] * x[1] * x[2] * x[3];
3175 g[1] = x[0] * x[0] + x[1] * x[1] + x[2] * x[2] + x[3] * x[3];
3176 true
3177 }
3178 fn eval_jac_g(
3179 &mut self,
3180 x: Option<&[Number]>,
3181 _new_x: bool,
3182 mode: SparsityRequest<'_>,
3183 ) -> bool {
3184 match mode {
3185 SparsityRequest::Structure { irow, jcol } => {
3186 irow.copy_from_slice(&[0, 0, 0, 0, 1, 1, 1, 1]);
3188 jcol.copy_from_slice(&[0, 1, 2, 3, 0, 1, 2, 3]);
3189 }
3190 SparsityRequest::Values { values } => {
3191 self.eval_jac_g_value_calls += 1;
3192 let x = x.expect("eval_jac_g(Values) without x");
3193 values[0] = x[1] * x[2] * x[3];
3195 values[1] = x[0] * x[2] * x[3];
3196 values[2] = x[0] * x[1] * x[3];
3197 values[3] = x[0] * x[1] * x[2];
3198 values[4] = 2.0 * x[0];
3200 values[5] = 2.0 * x[1];
3201 values[6] = 2.0 * x[2];
3202 values[7] = 2.0 * x[3];
3203 }
3204 }
3205 true
3206 }
3207 fn eval_h(
3208 &mut self,
3209 x: Option<&[Number]>,
3210 _new_x: bool,
3211 obj_factor: Number,
3212 lambda: Option<&[Number]>,
3213 _new_lambda: bool,
3214 mode: SparsityRequest<'_>,
3215 ) -> bool {
3216 match mode {
3219 SparsityRequest::Structure { irow, jcol } => {
3220 irow.copy_from_slice(&[0, 1, 1, 2, 2, 2, 3, 3, 3, 3]);
3221 jcol.copy_from_slice(&[0, 0, 1, 0, 1, 2, 0, 1, 2, 3]);
3222 }
3223 SparsityRequest::Values { values } => {
3224 self.eval_h_value_calls += 1;
3225 let x = x.expect("eval_h(Values) without x");
3226 let lam = lambda.expect("eval_h(Values) without lambda");
3227 let of = obj_factor;
3228 let l0 = lam[0];
3238 let l1 = lam[1];
3239 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; }
3250 }
3251 true
3252 }
3253 fn finalize_solution(&mut self, _sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {}
3254 }
3255
3256 fn build_orig_nlp() -> (Rc<RefCell<TNLPAdapter>>, OrigIpoptNlp) {
3257 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071::default()));
3258 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3259 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3260 (adapter, nlp)
3261 }
3262
3263 fn dense_x(values: &[Number], space: &Rc<DenseVectorSpace>) -> DenseVector {
3264 let mut v = space.make_new_dense();
3265 v.values_mut().copy_from_slice(values);
3266 v
3267 }
3268
3269 #[test]
3270 fn dimensions_match_classification() {
3271 let (_, nlp) = build_orig_nlp();
3272 assert_eq!(nlp.n(), 4);
3274 assert_eq!(nlp.m_eq(), 1);
3275 assert_eq!(nlp.m_ineq(), 1);
3276 assert_eq!(nlp.jac_c_space().nonzeros(), 4);
3278 assert_eq!(nlp.jac_d_space().nonzeros(), 4);
3279 assert_eq!(nlp.h_space().unwrap().nonzeros(), 10);
3281 assert_eq!(nlp.x_l().dim(), 4);
3283 assert_eq!(nlp.x_u().dim(), 4);
3284 assert_eq!(nlp.d_l().dim(), 1);
3285 assert_eq!(nlp.d_u().dim(), 0);
3286 }
3287
3288 #[test]
3289 fn eval_f_at_starting_point() {
3290 let (_, mut nlp) = build_orig_nlp();
3291 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3292 assert_eq!(nlp.eval_f(&x), 16.0);
3294 assert_eq!(nlp.f_evals(), 1);
3295 }
3296
3297 #[test]
3298 fn eval_grad_f_at_starting_point() {
3299 let (_, mut nlp) = build_orig_nlp();
3300 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3301 let mut g = nlp.x_space().make_new_dense();
3302 nlp.eval_grad_f(&x, &mut g);
3303 assert_eq!(g.values(), &[12.0, 1.0, 2.0, 11.0]);
3308 assert_eq!(nlp.grad_f_evals(), 1);
3309 }
3310
3311 #[test]
3312 fn eval_c_returns_equality_residual() {
3313 let (_, mut nlp) = build_orig_nlp();
3314 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3315 let mut c = nlp.c_space().make_new_dense();
3316 nlp.eval_c(&x, &mut c);
3317 assert_eq!(c.values(), &[12.0]);
3319 assert_eq!(nlp.c_evals(), 1);
3320 }
3321
3322 #[test]
3323 fn eval_d_returns_inequality_value_unshifted() {
3324 let (_, mut nlp) = build_orig_nlp();
3325 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3326 let mut d = nlp.d_space().make_new_dense();
3327 nlp.eval_d(&x, &mut d);
3328 assert_eq!(d.values(), &[25.0]);
3330 assert_eq!(nlp.d_evals(), 1);
3331 }
3332
3333 #[test]
3334 fn cache_returns_without_re_eval() {
3335 let (_, mut nlp) = build_orig_nlp();
3336 let mut x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3337 let f1 = nlp.eval_f(&x);
3338 let f2 = nlp.eval_f(&x);
3339 assert_eq!(f1, f2);
3340 assert_eq!(nlp.f_evals(), 1, "second call must be served from cache");
3341 x.values_mut()[0] = 1.0; let _ = nlp.eval_f(&x);
3344 assert_eq!(nlp.f_evals(), 2);
3345 }
3346
3347 #[test]
3348 fn jac_c_picks_only_equality_rows() {
3349 let (_, mut nlp) = build_orig_nlp();
3350 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3351 let m = nlp.eval_jac_c(&x);
3352 let g = m
3353 .as_any()
3354 .downcast_ref::<GenTMatrix>()
3355 .expect("jac_c is a GenTMatrix");
3356 assert_eq!(g.values(), &[2.0, 10.0, 10.0, 2.0]);
3358 assert_eq!(g.irows(), &[1, 1, 1, 1]);
3360 assert_eq!(g.jcols(), &[1, 2, 3, 4]);
3361 }
3362
3363 #[test]
3364 fn jac_d_picks_only_inequality_rows() {
3365 let (_, mut nlp) = build_orig_nlp();
3366 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3367 let m = nlp.eval_jac_d(&x);
3368 let g = m
3369 .as_any()
3370 .downcast_ref::<GenTMatrix>()
3371 .expect("jac_d is a GenTMatrix");
3372 assert_eq!(g.values(), &[25.0, 5.0, 5.0, 25.0]);
3375 }
3376
3377 fn build_orig_nlp_counting() -> (Rc<RefCell<Hs071>>, OrigIpoptNlp) {
3382 let concrete = Rc::new(RefCell::new(Hs071::default()));
3383 let tnlp: Rc<RefCell<dyn TNLP>> = concrete.clone();
3384 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3385 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3386 (concrete, nlp)
3387 }
3388
3389 #[test]
3390 fn eval_c_and_eval_d_share_one_eval_g_per_iterate() {
3391 let (tnlp, mut nlp) = build_orig_nlp_counting();
3395 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3396 let mut c = nlp.c_space().make_new_dense();
3397 let mut d = nlp.d_space().make_new_dense();
3398 nlp.eval_c(&x, &mut c);
3399 nlp.eval_d(&x, &mut d);
3400 assert_eq!(
3401 tnlp.borrow().eval_g_calls,
3402 1,
3403 "eval_c + eval_d at one iterate must share a single user eval_g"
3404 );
3405 assert_eq!(nlp.c_evals(), 1);
3407 assert_eq!(nlp.d_evals(), 1);
3408 assert_eq!(c.values(), &[12.0]);
3410 assert_eq!(d.values(), &[25.0]);
3411
3412 let mut x2 = x;
3415 x2.values_mut()[0] = 2.0;
3416 nlp.eval_c(&x2, &mut c);
3417 nlp.eval_d(&x2, &mut d);
3418 assert_eq!(
3419 tnlp.borrow().eval_g_calls,
3420 2,
3421 "a new iterate triggers exactly one more shared eval_g"
3422 );
3423 }
3424
3425 #[test]
3426 fn eval_c_does_not_refetch_bounds_per_iterate() {
3427 let (tnlp, mut nlp) = build_orig_nlp_counting();
3435 let baseline = tnlp.borrow().get_bounds_info_calls;
3438
3439 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3440 let mut c = nlp.c_space().make_new_dense();
3441 nlp.eval_c(&x, &mut c);
3442 assert_eq!(c.values(), &[12.0]);
3444
3445 let mut x2 = x;
3447 for k in 0..5 {
3448 x2.values_mut()[0] = 2.0 + k as Number;
3449 nlp.eval_c(&x2, &mut c);
3450 }
3451
3452 assert_eq!(
3453 tnlp.borrow().get_bounds_info_calls,
3454 baseline,
3455 "eval_c must reuse the captured c_rhs, not re-fetch bounds per iterate"
3456 );
3457 }
3458
3459 #[test]
3460 fn eval_jac_c_and_eval_jac_d_share_one_eval_jac_g_per_iterate() {
3461 let (tnlp, mut nlp) = build_orig_nlp_counting();
3465 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3466 let _ = nlp.eval_jac_c(&x);
3467 let _ = nlp.eval_jac_d(&x);
3468 assert_eq!(
3469 tnlp.borrow().eval_jac_g_value_calls,
3470 1,
3471 "eval_jac_c + eval_jac_d at one iterate must share a single eval_jac_g"
3472 );
3473 assert_eq!(nlp.jac_c_evals(), 1);
3474 assert_eq!(nlp.jac_d_evals(), 1);
3475 }
3476
3477 #[test]
3478 fn starting_point_is_compressed_into_x_var() {
3479 let (_, mut nlp) = build_orig_nlp();
3480 let mut x = nlp.x_space().make_new_dense();
3481 let mut yc = nlp.c_space().make_new_dense();
3482 let mut yd = nlp.d_space().make_new_dense();
3483 let mut zl = nlp.x_l_space().make_new_dense();
3484 let mut zu = nlp.x_u_space().make_new_dense();
3485 let ok = nlp.initialize_starting_point(
3486 &mut x, true, &mut yc, false, &mut yd, false, &mut zl, false, &mut zu, false,
3487 );
3488 assert!(ok);
3489 assert_eq!(x.values(), &[1.0, 5.0, 5.0, 1.0]);
3490 }
3491
3492 #[test]
3493 fn warm_start_duals_are_forwarded_into_algorithm_vectors() {
3494 let (_, mut nlp) = build_orig_nlp();
3495 let mut y_c = nlp.c_space().make_new_dense();
3496 let mut y_d = nlp.d_space().make_new_dense();
3497 assert!(nlp.get_starting_y(&mut y_c, &mut y_d));
3498 assert_eq!(y_c.values(), &[13.0], "equality multiplier g1");
3499 assert_eq!(y_d.values(), &[11.0], "inequality multiplier g0");
3500
3501 let mut z_l = nlp.x_l_space().make_new_dense();
3502 let mut z_u = nlp.x_u_space().make_new_dense();
3503 let mut v_l = nlp.d_l_space().make_new_dense();
3504 let mut v_u = nlp.d_u_space().make_new_dense();
3505 assert!(nlp.get_starting_z(&mut z_l, &mut z_u, &mut v_l, &mut v_u));
3506 assert_eq!(z_l.values(), &[1.0, 2.0, 3.0, 4.0]);
3507 assert_eq!(z_u.values(), &[5.0, 6.0, 7.0, 8.0]);
3508 }
3509
3510 #[test]
3511 fn warm_start_prefetches_one_tnlp_snapshot_for_x_y_and_z() {
3512 let (tnlp, mut nlp) = build_orig_nlp_counting();
3513 assert!(nlp.prepare_warm_start());
3514
3515 let mut x = nlp.x_space().make_new_dense();
3516 let mut y_c = nlp.c_space().make_new_dense();
3517 let mut y_d = nlp.d_space().make_new_dense();
3518 let mut z_l = nlp.x_l_space().make_new_dense();
3519 let mut z_u = nlp.x_u_space().make_new_dense();
3520 let mut v_l = nlp.d_l_space().make_new_dense();
3521 let mut v_u = nlp.d_u_space().make_new_dense();
3522 assert!(nlp.get_starting_x(&mut x));
3523 assert!(nlp.get_starting_y(&mut y_c, &mut y_d));
3524 assert!(nlp.get_starting_z(&mut z_l, &mut z_u, &mut v_l, &mut v_u));
3525
3526 assert_eq!(tnlp.borrow().get_starting_point_calls, 1);
3527 assert_eq!(x.values(), &[1.0, 5.0, 5.0, 1.0]);
3528 assert_eq!(y_c.values(), &[13.0]);
3529 assert_eq!(y_d.values(), &[11.0]);
3530 assert_eq!(z_l.values(), &[1.0, 2.0, 3.0, 4.0]);
3531 assert_eq!(z_u.values(), &[5.0, 6.0, 7.0, 8.0]);
3532
3533 nlp.finish_warm_start();
3534 let mut x_after_init = nlp.x_space().make_new_dense();
3535 assert!(nlp.get_starting_x(&mut x_after_init));
3536 assert_eq!(
3537 tnlp.borrow().get_starting_point_calls,
3538 2,
3539 "the snapshot must not affect later starting-point requests"
3540 );
3541 }
3542
3543 struct OneFixedOneFree;
3548 impl TNLP for OneFixedOneFree {
3549 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3550 Some(NlpInfo {
3551 n: 2,
3552 m: 1,
3553 nnz_jac_g: 1,
3554 nnz_h_lag: 0,
3555 index_style: IndexStyle::C,
3556 })
3557 }
3558 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3559 b.x_l[0] = 7.0;
3560 b.x_u[0] = 7.0; b.x_l[1] = -1.0e19;
3562 b.x_u[1] = 1.0e19;
3563 b.g_l[0] = 0.0;
3564 b.g_u[0] = 0.0; true
3566 }
3567 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3568 sp.x[0] = 7.0;
3569 sp.x[1] = 0.5;
3570 true
3571 }
3572 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
3573 Some(x[1])
3574 }
3575 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
3576 g[0] = 0.0;
3577 g[1] = 1.0;
3578 true
3579 }
3580 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3581 g[0] = x[1];
3582 true
3583 }
3584 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
3585 match m {
3586 SparsityRequest::Structure { irow, jcol } => {
3587 irow[0] = 0;
3588 jcol[0] = 1;
3589 }
3590 SparsityRequest::Values { values } => values[0] = 1.0,
3591 }
3592 true
3593 }
3594 fn eval_h(
3595 &mut self,
3596 _: Option<&[Number]>,
3597 _: bool,
3598 _: Number,
3599 _: Option<&[Number]>,
3600 _: bool,
3601 _: SparsityRequest<'_>,
3602 ) -> bool {
3603 true
3604 }
3605 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3606 }
3607
3608 struct FixedVarReachModel {
3623 second_order: bool,
3624 }
3625 impl TNLP for FixedVarReachModel {
3626 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3627 Some(NlpInfo {
3628 n: 3,
3629 m: 2,
3630 nnz_jac_g: 4,
3631 nnz_h_lag: 3,
3632 index_style: IndexStyle::C,
3633 })
3634 }
3635 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3636 b.x_l.copy_from_slice(&[-1.0e19, -1.0e19, 2.0]);
3637 b.x_u.copy_from_slice(&[1.0e19, 1.0e19, 2.0]); b.g_l.copy_from_slice(&[0.0, 0.0]);
3639 b.g_u.copy_from_slice(&[0.0, 1.0e19]); true
3641 }
3642 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3643 sp.x.copy_from_slice(&[1.0, 1.0, 2.0]);
3644 true
3645 }
3646 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
3647 Some(x[0] * x[0] * x[0])
3648 }
3649 fn eval_grad_f(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3650 g.copy_from_slice(&[3.0 * x[0] * x[0], 0.0, 0.0]);
3651 true
3652 }
3653 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3654 g[0] = x[0] * x[1] - 1.0;
3655 g[1] = if self.second_order {
3656 x[1] * x[2]
3657 } else {
3658 x[1] * x[1] + x[2]
3659 };
3660 true
3661 }
3662 fn eval_jac_g(&mut self, x: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
3663 match m {
3664 SparsityRequest::Structure { irow, jcol } => {
3665 irow.copy_from_slice(&[0, 0, 1, 1]);
3668 jcol.copy_from_slice(&[0, 1, 1, 2]);
3669 }
3670 SparsityRequest::Values { values } => {
3671 let x = x.expect("values need x");
3672 values[0] = x[1];
3673 values[1] = x[0];
3674 let (d1, d2) = if self.second_order {
3675 (x[2], x[1])
3676 } else {
3677 (2.0 * x[1], 1.0)
3678 };
3679 values[2] = d1;
3680 values[3] = d2;
3681 }
3682 }
3683 true
3684 }
3685 fn eval_h(
3686 &mut self,
3687 x: Option<&[Number]>,
3688 _: bool,
3689 sigma: Number,
3690 lambda: Option<&[Number]>,
3691 _: bool,
3692 m: SparsityRequest<'_>,
3693 ) -> bool {
3694 match m {
3695 SparsityRequest::Structure { irow, jcol } => {
3696 irow.copy_from_slice(&[0, 1, if self.second_order { 2 } else { 1 }]);
3700 jcol.copy_from_slice(&[0, 0, 1]);
3701 }
3702 SparsityRequest::Values { values } => {
3703 let (x, l) = (x.expect("values need x"), lambda.expect("values need λ"));
3704 values[0] = sigma * 6.0 * x[0];
3705 values[1] = l[0];
3706 values[2] = if self.second_order { l[1] } else { 2.0 * l[1] };
3707 }
3708 }
3709 true
3710 }
3711 fn derivative_proofs(&mut self) -> DerivativeProofs {
3712 DerivativeProofs {
3713 grad_f: DerivativeProof::Varying,
3714 hessian: DerivativeProof::Varying,
3715 jac: vec![DerivativeProof::Varying; 2],
3716 }
3717 }
3718 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3719 }
3720
3721 fn reach_proofs(second_order: bool) -> [DerivativeProof; 4] {
3722 let tnlp: Rc<RefCell<dyn TNLP>> =
3723 Rc::new(RefCell::new(FixedVarReachModel { second_order }));
3724 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3725 let nlp = OrigIpoptNlp::new(adapter, Rc::new(NoScaling)).unwrap();
3726 nlp.derivative_proofs()
3727 }
3728
3729 #[test]
3738 fn only_the_forms_a_fixed_variable_reaches_lose_their_refusal() {
3739 use DerivativeProof::*;
3740 let [grad_f, hessian, jac_c, jac_d] = reach_proofs(false);
3741 assert_eq!(grad_f, Varying, "x[2] is nowhere in ∇f");
3742 assert_eq!(hessian, Varying, "x[2] appears linearly; ∇²L cannot see it");
3743 assert_eq!(jac_c, Varying, "row 0 has no nonzero in the fixed column");
3744 assert_eq!(jac_d, Unknown, "row 1 does, so fixing x[2] may flatten it");
3745 }
3746
3747 #[test]
3752 fn a_second_order_coupling_to_a_fixed_variable_weakens_the_hessian() {
3753 use DerivativeProof::*;
3754 let [grad_f, hessian, jac_c, jac_d] = reach_proofs(true);
3755 assert_eq!(hessian, Unknown, "x1·x2 puts a fixed index in ∇²L");
3756 assert_eq!(grad_f, Unknown, "same test, and ∇f is reached the same way");
3757 assert_eq!(jac_c, Varying, "row 0 is still untouched by x[2]");
3758 assert_eq!(jac_d, Unknown);
3759 }
3760
3761 #[test]
3762 fn ipopt_nlp_index_mapping_methods_handle_fixed_var() {
3763 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneFixedOneFree));
3764 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3765 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3766
3767 assert_eq!(nlp.n_full_x(), 2);
3769 assert_eq!(nlp.n(), 1);
3770
3771 let nlp_dyn: &dyn crate::ipopt_nlp::IpoptNlp = &nlp;
3773 assert_eq!(nlp_dyn.full_x_to_var_x(0), None);
3774 assert_eq!(nlp_dyn.full_x_to_var_x(1), Some(0));
3775
3776 assert_eq!(nlp_dyn.var_x_to_full_x(0), 1);
3778
3779 assert_eq!(nlp_dyn.full_g_to_c_block(0), Some(0));
3781
3782 assert_eq!(nlp_dyn.full_g_to_d_block(0), None);
3786 assert_eq!(nlp_dyn.full_g_to_d_block(1), None);
3787
3788 let mut x_var = nlp.x_space().make_new_dense();
3790 x_var.values_mut()[0] = 0.5;
3791 let lifted = nlp_dyn.lift_x_to_full(&x_var);
3792 assert_eq!(lifted, vec![7.0, 0.5]);
3793 }
3794
3795 struct NamedFixedOneFree;
3799 impl TNLP for NamedFixedOneFree {
3800 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3801 OneFixedOneFree.get_nlp_info()
3802 }
3803 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3804 OneFixedOneFree.get_bounds_info(b)
3805 }
3806 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3807 OneFixedOneFree.get_starting_point(sp)
3808 }
3809 fn eval_f(&mut self, x: &[Number], n: bool) -> Option<Number> {
3810 OneFixedOneFree.eval_f(x, n)
3811 }
3812 fn eval_grad_f(&mut self, x: &[Number], n: bool, g: &mut [Number]) -> bool {
3813 OneFixedOneFree.eval_grad_f(x, n, g)
3814 }
3815 fn eval_g(&mut self, x: &[Number], n: bool, g: &mut [Number]) -> bool {
3816 OneFixedOneFree.eval_g(x, n, g)
3817 }
3818 fn eval_jac_g(&mut self, x: Option<&[Number]>, n: bool, m: SparsityRequest<'_>) -> bool {
3819 OneFixedOneFree.eval_jac_g(x, n, m)
3820 }
3821 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3822 fn get_var_con_metadata(&mut self, var: &mut MetaData, con: &mut MetaData) -> bool {
3823 var.strings.insert(
3824 IDX_NAMES.to_string(),
3825 vec!["fixed_x".to_string(), "free_x".to_string()],
3826 );
3827 con.strings
3828 .insert(IDX_NAMES.to_string(), vec!["balance".to_string()]);
3829 true
3830 }
3831 }
3832
3833 #[test]
3834 fn split_space_names_threads_through_fixed_var_and_cd_split() {
3835 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(NamedFixedOneFree));
3836 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3837 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3838
3839 let names = nlp.split_space_names().expect("names present");
3840 assert_eq!(names.x_var, vec![Some("free_x".to_string())]);
3842 assert_eq!(names.eq, vec![Some("balance".to_string())]);
3844 assert!(names.ineq.is_empty());
3846 assert!(names.any_present());
3847 }
3848
3849 #[test]
3850 fn split_space_names_none_when_tnlp_declines() {
3851 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneFixedOneFree));
3853 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3854 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3855 assert!(nlp.split_space_names().is_none());
3856 }
3857
3858 struct FixedOnlyHess;
3864 impl TNLP for FixedOnlyHess {
3865 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3866 Some(NlpInfo {
3867 n: 2,
3868 m: 1,
3869 nnz_jac_g: 1,
3870 nnz_h_lag: 1,
3871 index_style: IndexStyle::C,
3872 })
3873 }
3874 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3875 b.x_l[0] = 7.0;
3876 b.x_u[0] = 7.0; b.x_l[1] = -1.0e19;
3878 b.x_u[1] = 1.0e19;
3879 b.g_l[0] = 0.0;
3880 b.g_u[0] = 0.0;
3881 true
3882 }
3883 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3884 sp.x[0] = 7.0;
3885 sp.x[1] = 0.5;
3886 true
3887 }
3888 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
3889 Some(0.5 * x[0] * x[0] + x[1])
3890 }
3891 fn eval_grad_f(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3892 g[0] = x[0];
3893 g[1] = 1.0;
3894 true
3895 }
3896 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3897 g[0] = x[1];
3898 true
3899 }
3900 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
3901 match m {
3902 SparsityRequest::Structure { irow, jcol } => {
3903 irow[0] = 0;
3904 jcol[0] = 1;
3905 }
3906 SparsityRequest::Values { values } => values[0] = 1.0,
3907 }
3908 true
3909 }
3910 fn eval_h(
3911 &mut self,
3912 _: Option<&[Number]>,
3913 _: bool,
3914 obj_factor: Number,
3915 _: Option<&[Number]>,
3916 _: bool,
3917 m: SparsityRequest<'_>,
3918 ) -> bool {
3919 match m {
3920 SparsityRequest::Structure { irow, jcol } => {
3921 irow[0] = 0;
3922 jcol[0] = 0;
3923 }
3924 SparsityRequest::Values { values } => values[0] = obj_factor,
3925 }
3926 true
3927 }
3928 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3929 }
3930
3931 struct OneIneqLargeOffset;
3940 impl TNLP for OneIneqLargeOffset {
3941 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3942 Some(NlpInfo {
3943 n: 1,
3944 m: 1,
3945 nnz_jac_g: 1,
3946 nnz_h_lag: 0,
3947 index_style: IndexStyle::C,
3948 })
3949 }
3950 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3951 b.x_l[0] = -1.0e19;
3952 b.x_u[0] = 1.0e19;
3953 b.g_l[0] = 4.0e6;
3954 b.g_u[0] = 2.0e19;
3955 true
3956 }
3957 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3958 sp.x[0] = 5000.0;
3959 true
3960 }
3961 fn eval_f(&mut self, _: &[Number], _: bool) -> Option<Number> {
3962 Some(0.0)
3963 }
3964 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
3965 g[0] = 0.0;
3966 true
3967 }
3968 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3969 g[0] = 1000.0 * x[0];
3970 true
3971 }
3972 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
3973 match m {
3974 SparsityRequest::Structure { irow, jcol } => {
3975 irow[0] = 0;
3976 jcol[0] = 0;
3977 }
3978 SparsityRequest::Values { values } => values[0] = 1000.0,
3979 }
3980 true
3981 }
3982 fn eval_h(
3983 &mut self,
3984 _: Option<&[Number]>,
3985 _: bool,
3986 _: Number,
3987 _: Option<&[Number]>,
3988 _: bool,
3989 _: SparsityRequest<'_>,
3990 ) -> bool {
3991 true
3992 }
3993 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3994 }
3995
3996 struct OneEqLargeOffset;
4001 impl TNLP for OneEqLargeOffset {
4002 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
4003 Some(NlpInfo {
4004 n: 1,
4005 m: 1,
4006 nnz_jac_g: 1,
4007 nnz_h_lag: 0,
4008 index_style: IndexStyle::C,
4009 })
4010 }
4011 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
4012 b.x_l[0] = -1.0e19;
4013 b.x_u[0] = 1.0e19;
4014 b.g_l[0] = 4.0e6;
4015 b.g_u[0] = 4.0e6;
4016 true
4017 }
4018 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
4019 sp.x[0] = 5000.0;
4020 true
4021 }
4022 fn eval_f(&mut self, _: &[Number], _: bool) -> Option<Number> {
4023 Some(0.0)
4024 }
4025 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
4026 g[0] = 0.0;
4027 true
4028 }
4029 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
4030 g[0] = 1000.0 * x[0];
4031 true
4032 }
4033 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
4034 match m {
4035 SparsityRequest::Structure { irow, jcol } => {
4036 irow[0] = 0;
4037 jcol[0] = 0;
4038 }
4039 SparsityRequest::Values { values } => values[0] = 1000.0,
4040 }
4041 true
4042 }
4043 fn eval_h(
4044 &mut self,
4045 _: Option<&[Number]>,
4046 _: bool,
4047 _: Number,
4048 _: Option<&[Number]>,
4049 _: bool,
4050 _: SparsityRequest<'_>,
4051 ) -> bool {
4052 true
4053 }
4054 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
4055 }
4056
4057 #[test]
4058 fn gradient_based_scaling_scales_d_l_and_d_u() {
4059 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqLargeOffset));
4060 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4061 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
4062
4063 assert_eq!(nlp.d_l().dim(), 1);
4065 let pre = nlp
4066 .d_l()
4067 .as_any()
4068 .downcast_ref::<DenseVector>()
4069 .unwrap()
4070 .values()[0];
4071 assert_eq!(pre, 4.0e6);
4072
4073 nlp.determine_scaling_from_starting_point(
4074 ScalingMethod::GradientBased,
4075 100.0,
4076 1e-8,
4077 0.0,
4078 0.0,
4079 );
4080
4081 let post = nlp
4083 .d_l()
4084 .as_any()
4085 .downcast_ref::<DenseVector>()
4086 .unwrap()
4087 .values()[0];
4088 assert!(
4089 (post - 4.0e5).abs() < 1e-9,
4090 "d_l should be scaled by d_scale=0.1; got {}",
4091 post
4092 );
4093
4094 let x = dense_x(&[5000.0], nlp.x_space());
4097 let mut d = nlp.d_space().make_new_dense();
4098 nlp.eval_d(&x, &mut d);
4099 assert!(
4100 (d.values()[0] - 5.0e5).abs() < 1e-6,
4101 "scaled d(x) mismatch; got {}",
4102 d.values()[0]
4103 );
4104 assert!(
4105 d.values()[0] >= post,
4106 "starting point must be feasible in scaled space"
4107 );
4108 }
4109
4110 struct OneIneqWithObj;
4116 impl TNLP for OneIneqWithObj {
4117 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
4118 Some(NlpInfo {
4119 n: 1,
4120 m: 1,
4121 nnz_jac_g: 1,
4122 nnz_h_lag: 0,
4123 index_style: IndexStyle::C,
4124 })
4125 }
4126 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
4127 b.x_l[0] = -1.0e19;
4128 b.x_u[0] = 1.0e19;
4129 b.g_l[0] = 4.0e6;
4130 b.g_u[0] = 2.0e19;
4131 true
4132 }
4133 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
4134 sp.x[0] = 5000.0;
4135 true
4136 }
4137 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
4138 Some(10.0 * x[0])
4139 }
4140 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
4141 g[0] = 10.0;
4142 true
4143 }
4144 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
4145 g[0] = 1000.0 * x[0];
4146 true
4147 }
4148 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
4149 match m {
4150 SparsityRequest::Structure { irow, jcol } => {
4151 irow[0] = 0;
4152 jcol[0] = 0;
4153 }
4154 SparsityRequest::Values { values } => values[0] = 1000.0,
4155 }
4156 true
4157 }
4158 fn eval_h(
4159 &mut self,
4160 _: Option<&[Number]>,
4161 _: bool,
4162 _: Number,
4163 _: Option<&[Number]>,
4164 _: bool,
4165 _: SparsityRequest<'_>,
4166 ) -> bool {
4167 true
4168 }
4169 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
4170 }
4171
4172 #[test]
4173 fn obj_target_gradient_pins_obj_scale() {
4174 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqWithObj));
4177 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4178 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
4179 nlp.determine_scaling_from_starting_point(
4180 ScalingMethod::GradientBased,
4181 100.0,
4182 1e-8,
4183 0.0, 0.0,
4185 );
4186 assert!(
4187 (nlp.obj_scale_factor() - 1.0).abs() < 1e-12,
4188 "no-target path leaves df=1 when grad < cutoff; got {}",
4189 nlp.obj_scale_factor()
4190 );
4191
4192 let tnlp2: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqWithObj));
4195 let adapter2 = Rc::new(RefCell::new(TNLPAdapter::new(tnlp2).unwrap()));
4196 let mut nlp2 = OrigIpoptNlp::new(Rc::clone(&adapter2), Rc::new(NoScaling)).unwrap();
4197 nlp2.determine_scaling_from_starting_point(
4198 ScalingMethod::GradientBased,
4199 100.0,
4200 1e-8,
4201 1.0,
4202 0.0,
4203 );
4204 assert!(
4205 (nlp2.obj_scale_factor() - 0.1).abs() < 1e-12,
4206 "target_gradient=1, max_grad_f=10 → df=0.1; got {}",
4207 nlp2.obj_scale_factor()
4208 );
4209 }
4210
4211 struct FixedVarShiftsObjGrad;
4221 impl TNLP for FixedVarShiftsObjGrad {
4222 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
4223 Some(NlpInfo {
4224 n: 2,
4225 m: 0,
4226 nnz_jac_g: 0,
4227 nnz_h_lag: 0,
4228 index_style: IndexStyle::C,
4229 })
4230 }
4231 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
4232 b.x_l[0] = -1.0e19;
4233 b.x_u[0] = 1.0e19;
4234 b.x_l[1] = 1000.0;
4235 b.x_u[1] = 1000.0; true
4237 }
4238 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
4239 sp.x[0] = 1.0;
4240 sp.x[1] = 0.0; true
4242 }
4243 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
4244 Some(x[0] * x[1])
4245 }
4246 fn eval_grad_f(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
4247 g[0] = x[1];
4248 g[1] = x[0];
4249 true
4250 }
4251 fn eval_g(&mut self, _: &[Number], _: bool, _: &mut [Number]) -> bool {
4252 true
4253 }
4254 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, _: SparsityRequest<'_>) -> bool {
4255 true
4256 }
4257 fn eval_h(
4258 &mut self,
4259 _: Option<&[Number]>,
4260 _: bool,
4261 _: Number,
4262 _: Option<&[Number]>,
4263 _: bool,
4264 _: SparsityRequest<'_>,
4265 ) -> bool {
4266 true
4267 }
4268 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
4269 }
4270
4271 struct BoxedVar;
4275 impl TNLP for BoxedVar {
4276 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
4277 Some(NlpInfo {
4278 n: 1,
4279 m: 0,
4280 nnz_jac_g: 0,
4281 nnz_h_lag: 0,
4282 index_style: IndexStyle::C,
4283 })
4284 }
4285 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
4286 b.x_l[0] = -1.0;
4287 b.x_u[0] = 2.0;
4288 true
4289 }
4290 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
4291 sp.x[0] = 0.0;
4292 true
4293 }
4294 fn eval_f(&mut self, _: &[Number], _: bool) -> Option<Number> {
4295 Some(0.0)
4296 }
4297 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
4298 g[0] = 0.0;
4299 true
4300 }
4301 fn eval_g(&mut self, _: &[Number], _: bool, _: &mut [Number]) -> bool {
4302 true
4303 }
4304 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, _: SparsityRequest<'_>) -> bool {
4305 true
4306 }
4307 fn eval_h(
4308 &mut self,
4309 _: Option<&[Number]>,
4310 _: bool,
4311 _: Number,
4312 _: Option<&[Number]>,
4313 _: bool,
4314 _: SparsityRequest<'_>,
4315 ) -> bool {
4316 true
4317 }
4318 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
4319 }
4320
4321 fn boxed_var_nlp() -> OrigIpoptNlp {
4322 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(BoxedVar));
4323 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4324 OrigIpoptNlp::new(adapter, Rc::new(NoScaling)).unwrap()
4325 }
4326
4327 fn at(x: Number) -> DenseVector {
4328 let space = DenseVectorSpace::new(1);
4329 let mut v = space.make_new_dense();
4330 v.set_values(&[x]);
4331 v
4332 }
4333
4334 #[test]
4345 fn the_box_violation_abstains_until_the_declared_bounds_are_snapshotted() {
4346 let nlp = boxed_var_nlp();
4347 assert!(
4348 nlp.declared_box_violation(&at(5.0)).is_none(),
4349 "no snapshot means no answer, not a fabricated zero"
4350 );
4351 }
4352
4353 #[test]
4366 fn the_box_violation_measures_the_distance_outside_the_declared_box() {
4367 let mut nlp = boxed_var_nlp();
4368 nlp.snapshot_declared_bounds();
4369 assert_eq!(nlp.declared_box_violation(&at(0.5)), Some(0.0));
4371 assert_eq!(nlp.declared_box_violation(&at(-1.0)), Some(0.0));
4373 assert_eq!(nlp.declared_box_violation(&at(2.0)), Some(0.0));
4374 let over = nlp.declared_box_violation(&at(2.25)).unwrap();
4376 assert!((over - 0.25).abs() < 1e-15, "expected 0.25, got {over}");
4377 let under = nlp.declared_box_violation(&at(-1.5)).unwrap();
4378 assert!((under - 0.5).abs() < 1e-15, "expected 0.5, got {under}");
4379 }
4380
4381 #[test]
4385 fn relaxing_the_bounds_still_snapshots_the_declared_ones_first() {
4386 let mut nlp = boxed_var_nlp();
4387 nlp.relax_bounds(1e-2, 1.0);
4388 let v = nlp.declared_box_violation(&at(2.02)).unwrap();
4393 assert!((v - 0.02).abs() < 1e-12, "expected 0.02, got {v}");
4394 }
4395
4396 #[test]
4397 fn gradient_scaling_lifts_fixed_vars_to_their_value() {
4398 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(FixedVarShiftsObjGrad));
4399 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4400 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
4401
4402 assert_eq!(nlp.n_full_x(), 2);
4404 assert_eq!(nlp.n(), 1);
4405
4406 nlp.determine_scaling_from_starting_point(
4407 ScalingMethod::GradientBased,
4408 100.0,
4409 1e-8,
4410 0.0,
4411 0.0,
4412 );
4413
4414 assert!(
4418 (nlp.obj_scale_factor() - 0.1).abs() < 1e-12,
4419 "fixed var must be lifted before scaling; expected df=0.1, got {}",
4420 nlp.obj_scale_factor()
4421 );
4422 }
4423
4424 #[test]
4425 fn constr_target_gradient_overrides_cutoff_and_clamp() {
4426 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqLargeOffset));
4431 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4432 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
4433 nlp.determine_scaling_from_starting_point(
4434 ScalingMethod::GradientBased,
4435 100.0,
4436 1e-8,
4437 0.0,
4438 50.0,
4439 );
4440 let x = dense_x(&[5000.0], nlp.x_space());
4441 let mut d = nlp.d_space().make_new_dense();
4442 nlp.eval_d(&x, &mut d);
4443 assert!(
4445 (d.values()[0] - 2.5e5).abs() < 1e-6,
4446 "constr target=50 → dd=0.05; scaled d(5000)=2.5e5, got {}",
4447 d.values()[0]
4448 );
4449 }
4450
4451 struct Hs071UserScaled;
4456 impl TNLP for Hs071UserScaled {
4457 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
4458 Hs071::default().get_nlp_info()
4459 }
4460 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
4461 Hs071::default().get_bounds_info(b)
4462 }
4463 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
4464 Hs071::default().get_starting_point(sp)
4465 }
4466 fn eval_f(&mut self, x: &[Number], new_x: bool) -> Option<Number> {
4467 Hs071::default().eval_f(x, new_x)
4468 }
4469 fn eval_grad_f(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
4470 Hs071::default().eval_grad_f(x, new_x, g)
4471 }
4472 fn eval_g(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
4473 Hs071::default().eval_g(x, new_x, g)
4474 }
4475 fn eval_jac_g(
4476 &mut self,
4477 x: Option<&[Number]>,
4478 new_x: bool,
4479 mode: SparsityRequest<'_>,
4480 ) -> bool {
4481 Hs071::default().eval_jac_g(x, new_x, mode)
4482 }
4483 fn eval_h(
4484 &mut self,
4485 x: Option<&[Number]>,
4486 new_x: bool,
4487 obj_factor: Number,
4488 lambda: Option<&[Number]>,
4489 new_lambda: bool,
4490 mode: SparsityRequest<'_>,
4491 ) -> bool {
4492 Hs071::default().eval_h(x, new_x, obj_factor, lambda, new_lambda, mode)
4493 }
4494 fn get_scaling_parameters(&mut self, req: ScalingRequest<'_>) -> bool {
4495 *req.obj_scaling = 2.0;
4496 *req.use_x_scaling = false;
4497 *req.use_g_scaling = true;
4498 req.g_scaling[0] = 0.5;
4500 req.g_scaling[1] = 0.25;
4501 true
4502 }
4503 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
4504 }
4505
4506 #[test]
4507 fn user_scaling_dispatch_applies_obj_and_g_scaling() {
4508 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071UserScaled));
4509 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4510 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
4511 nlp.determine_scaling_from_starting_point(
4512 ScalingMethod::UserScaling,
4513 100.0,
4514 1e-8,
4515 0.0,
4516 0.0,
4517 );
4518
4519 assert!(
4522 (nlp.obj_scale_factor() - 2.0).abs() < 1e-12,
4523 "user obj_scaling=2.0 should be installed; got {}",
4524 nlp.obj_scale_factor()
4525 );
4526
4527 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
4531 let mut c = nlp.c_space().make_new_dense();
4532 nlp.eval_c(&x, &mut c);
4533 assert!(
4536 (c.values()[0] - 3.0).abs() < 1e-9,
4537 "user g_scaling=0.25 on equality → c=3.0; got {}",
4538 c.values()[0]
4539 );
4540
4541 let mut d = nlp.d_space().make_new_dense();
4544 nlp.eval_d(&x, &mut d);
4545 assert!(
4546 (d.values()[0] - 12.5).abs() < 1e-9,
4547 "user g_scaling=0.5 on inequality → d=12.5; got {}",
4548 d.values()[0]
4549 );
4550
4551 let post_d_l = nlp
4554 .d_l()
4555 .as_any()
4556 .downcast_ref::<DenseVector>()
4557 .unwrap()
4558 .values()[0];
4559 assert!(
4560 (post_d_l - 12.5).abs() < 1e-9,
4561 "d_l scaled in step: got {}",
4562 post_d_l
4563 );
4564 }
4565
4566 struct Hs071DeclinesScaling;
4570 impl TNLP for Hs071DeclinesScaling {
4571 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
4572 Hs071::default().get_nlp_info()
4573 }
4574 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
4575 Hs071::default().get_bounds_info(b)
4576 }
4577 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
4578 Hs071::default().get_starting_point(sp)
4579 }
4580 fn eval_f(&mut self, x: &[Number], new_x: bool) -> Option<Number> {
4581 Hs071::default().eval_f(x, new_x)
4582 }
4583 fn eval_grad_f(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
4584 Hs071::default().eval_grad_f(x, new_x, g)
4585 }
4586 fn eval_g(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
4587 Hs071::default().eval_g(x, new_x, g)
4588 }
4589 fn eval_jac_g(
4590 &mut self,
4591 x: Option<&[Number]>,
4592 new_x: bool,
4593 mode: SparsityRequest<'_>,
4594 ) -> bool {
4595 Hs071::default().eval_jac_g(x, new_x, mode)
4596 }
4597 fn eval_h(
4598 &mut self,
4599 x: Option<&[Number]>,
4600 new_x: bool,
4601 obj_factor: Number,
4602 lambda: Option<&[Number]>,
4603 new_lambda: bool,
4604 mode: SparsityRequest<'_>,
4605 ) -> bool {
4606 Hs071::default().eval_h(x, new_x, obj_factor, lambda, new_lambda, mode)
4607 }
4608 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
4609 }
4610
4611 #[test]
4612 fn user_scaling_falls_back_when_tnlp_declines() {
4613 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071DeclinesScaling));
4614 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4615 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
4616 nlp.determine_scaling_from_starting_point(
4617 ScalingMethod::UserScaling,
4618 100.0,
4619 1e-8,
4620 0.0,
4621 0.0,
4622 );
4623 assert!((nlp.obj_scale_factor() - 1.0).abs() < 1e-12);
4626 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
4627 let mut c = nlp.c_space().make_new_dense();
4628 nlp.eval_c(&x, &mut c);
4629 assert_eq!(c.values(), &[12.0], "unscaled equality residual");
4630 }
4631
4632 struct Hs071XScaled(Vec<Number>);
4635 impl TNLP for Hs071XScaled {
4636 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
4637 Hs071::default().get_nlp_info()
4638 }
4639 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
4640 Hs071::default().get_bounds_info(b)
4641 }
4642 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
4643 Hs071::default().get_starting_point(sp)
4644 }
4645 fn eval_f(&mut self, x: &[Number], new_x: bool) -> Option<Number> {
4646 Hs071::default().eval_f(x, new_x)
4647 }
4648 fn eval_grad_f(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
4649 Hs071::default().eval_grad_f(x, new_x, g)
4650 }
4651 fn eval_g(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
4652 Hs071::default().eval_g(x, new_x, g)
4653 }
4654 fn eval_jac_g(
4655 &mut self,
4656 x: Option<&[Number]>,
4657 new_x: bool,
4658 mode: SparsityRequest<'_>,
4659 ) -> bool {
4660 Hs071::default().eval_jac_g(x, new_x, mode)
4661 }
4662 fn eval_h(
4663 &mut self,
4664 x: Option<&[Number]>,
4665 new_x: bool,
4666 obj_factor: Number,
4667 lambda: Option<&[Number]>,
4668 new_lambda: bool,
4669 mode: SparsityRequest<'_>,
4670 ) -> bool {
4671 Hs071::default().eval_h(x, new_x, obj_factor, lambda, new_lambda, mode)
4672 }
4673 fn get_scaling_parameters(&mut self, req: ScalingRequest<'_>) -> bool {
4674 *req.obj_scaling = 2.0;
4675 *req.use_x_scaling = true;
4676 req.x_scaling.copy_from_slice(&self.0);
4677 *req.use_g_scaling = false;
4678 true
4679 }
4680 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
4681 }
4682
4683 fn user_x_scaling_run(factors: &[Number]) -> OrigIpoptNlp {
4684 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071XScaled(factors.to_vec())));
4685 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4686 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
4687 nlp.determine_scaling_from_starting_point(
4688 ScalingMethod::UserScaling,
4689 100.0,
4690 1e-8,
4691 0.0,
4692 0.0,
4693 );
4694 nlp
4695 }
4696
4697 #[test]
4702 fn user_x_scaling_request_is_flagged_not_discarded() {
4703 let nlp = user_x_scaling_run(&[1.0, 1e3, 1.0, 1.0]);
4704 assert!(
4705 nlp.user_x_scaling_rejected(),
4706 "a non-unit x_scaling must be refused, not dropped"
4707 );
4708 assert!((nlp.obj_scale_factor() - 2.0).abs() < 1e-12);
4711 }
4712
4713 #[test]
4716 fn unit_x_scaling_request_is_not_rejected() {
4717 let nlp = user_x_scaling_run(&[1.0, 1.0, 1.0, 1.0]);
4718 assert!(!nlp.user_x_scaling_rejected());
4719 }
4720
4721 #[test]
4722 fn eval_h_with_all_entries_on_fixed_var_does_not_panic() {
4723 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(FixedOnlyHess));
4724 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4725 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
4726
4727 assert_eq!(nlp.h_space().unwrap().nonzeros(), 0);
4730
4731 let x = dense_x(&[0.5], &nlp.x_space().clone());
4732 let yc = dense_x(&[0.0], &nlp.c_space().clone());
4733 let yd = nlp.d_space().make_new_dense();
4734 let h = nlp.eval_h(&x, 1.0, &yc, &yd);
4735 assert_eq!(h.n_rows(), 1);
4736 }
4737
4738 #[test]
4739 fn relax_bounds_widens_uniquely_owned_bounds() {
4740 let (_adapter, mut nlp) = build_orig_nlp();
4743 let x_l_before = nlp.x_l.values().to_vec();
4744 let x_u_before = nlp.x_u.values().to_vec();
4745 nlp.relax_bounds(1e-2, 1.0);
4746 for (b, a) in x_l_before.iter().zip(nlp.x_l.values()) {
4747 assert!(a < b, "x_l should relax downward: {a} !< {b}");
4748 }
4749 for (b, a) in x_u_before.iter().zip(nlp.x_u.values()) {
4750 assert!(a > b, "x_u should relax upward: {a} !> {b}");
4751 }
4752 }
4753
4754 #[test]
4759 fn relax_bounds_is_scale_relative_on_d_and_snapshots_declared() {
4760 let (_adapter, mut nlp) = build_orig_nlp();
4762 assert_eq!(nlp.d_l.values(), &[25.0]);
4763 assert_eq!(nlp.declared_d_bounds(), None, "no snapshot before relax");
4764 nlp.relax_bounds(1e-2, 1.0);
4765 assert_eq!(nlp.d_l.values(), &[25.0 - 0.25]);
4771 let (dl, du) = nlp.declared_d_bounds().expect("snapshotted at relax");
4772 assert_eq!(dl, vec![25.0], "declared bound is the pre-relax value");
4773 assert!(du.is_empty() || du[0] >= 25.0); }
4775
4776 #[test]
4784 fn declared_x_bounds_are_the_pre_relax_box() {
4785 let (_adapter, mut nlp) = build_orig_nlp();
4786 assert_eq!(nlp.declared_x_bounds(), None, "no snapshot before relax");
4787 let x_l_before = nlp.x_l.values().to_vec();
4788 let x_u_before = nlp.x_u.values().to_vec();
4789 nlp.relax_bounds(1e-2, 1.0);
4790 let (xl, xu) = nlp.declared_x_bounds().expect("snapshotted at relax");
4791 assert_eq!(xl, x_l_before, "declared lower box is the pre-relax value");
4792 assert_eq!(xu, x_u_before, "declared upper box is the pre-relax value");
4793 assert!(nlp.x_l.values()[0] < xl[0]);
4796 assert!(nlp.x_u.values()[0] > xu[0]);
4797 }
4798
4799 #[test]
4805 fn declared_c_rhs_is_the_pre_fold_right_hand_side() {
4806 let (_adapter, mut nlp) = build_orig_nlp();
4808 assert_eq!(nlp.declared_c_rhs(), Some(vec![40.0]));
4809 nlp.relax_bounds(1e-2, 1.0);
4810 assert_eq!(
4811 nlp.declared_c_rhs(),
4812 Some(vec![40.0]),
4813 "bound relaxation must not reach the equality RHS"
4814 );
4815 }
4816
4817 #[test]
4822 fn declared_c_rhs_carries_the_row_scaling() {
4823 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneEqLargeOffset));
4824 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4825 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
4826 assert_eq!(nlp.declared_c_rhs(), Some(vec![4.0e6]));
4827
4828 nlp.determine_scaling_from_starting_point(
4829 ScalingMethod::GradientBased,
4830 100.0,
4831 1e-8,
4832 0.0,
4833 0.0,
4834 );
4835 let rhs = nlp.declared_c_rhs().unwrap();
4837 assert!(
4838 (rhs[0] - 4.0e5).abs() < 1e-9,
4839 "declared RHS should carry c_scale=0.1; got {}",
4840 rhs[0]
4841 );
4842
4843 let x = dense_x(&[5000.0], nlp.x_space());
4846 let mut c = nlp.c_space().make_new_dense();
4847 nlp.eval_c(&x, &mut c);
4848 assert!((c.values()[0] / rhs[0] - 0.25).abs() < 1e-12);
4849 }
4850
4851 #[test]
4852 #[should_panic(expected = "x_l is uniquely owned")]
4853 fn relax_bounds_panics_on_shared_bound_rc() {
4854 let (_adapter, mut nlp) = build_orig_nlp();
4859 let _shared = Rc::clone(&nlp.x_l); nlp.relax_bounds(1e-2, 1.0);
4861 }
4862}