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 uninitialized_h(&self) -> Rc<dyn SymMatrix> {
2499 match self.h_space.as_ref() {
2500 Some(space) => Rc::new(crate::ipopt_nlp::zeroed_sym_t(Rc::clone(space))),
2501 None => Rc::new(crate::ipopt_nlp::zeroed_sym_t(SymTMatrixSpace::new(
2502 self.x_space.dim(),
2503 Vec::new(),
2504 Vec::new(),
2505 ))),
2506 }
2507 }
2508
2509 fn eval_counts(&self) -> [Index; 7] {
2510 [
2511 self.f_evals(),
2512 self.grad_f_evals(),
2513 self.c_evals(),
2514 self.d_evals(),
2515 self.jac_c_evals(),
2516 self.jac_d_evals(),
2517 self.h_evals(),
2518 ]
2519 }
2520 fn x_l(&self) -> &dyn Vector {
2521 &*self.x_l
2522 }
2523 fn x_u(&self) -> &dyn Vector {
2524 &*self.x_u
2525 }
2526 fn d_l(&self) -> &dyn Vector {
2527 &*self.d_l
2528 }
2529 fn d_u(&self) -> &dyn Vector {
2530 &*self.d_u
2531 }
2532
2533 fn declared_d_bounds(&self) -> Option<(Vec<Number>, Vec<Number>)> {
2534 let mut dl = self.declared_d_l.borrow().clone()?;
2535 let mut du = self.declared_d_u.borrow().clone()?;
2536 if let Some(dd) = self.d_scale.borrow().as_ref() {
2540 let cls = self.adapter.borrow().classification().clone();
2541 for (i, slot) in dl.iter_mut().enumerate() {
2542 *slot *= dd[cls.d_l_map[i] as usize];
2543 }
2544 for (i, slot) in du.iter_mut().enumerate() {
2545 *slot *= dd[cls.d_u_map[i] as usize];
2546 }
2547 }
2548 Some((dl, du))
2549 }
2550
2551 fn declared_box_violation(&self, x: &dyn Vector) -> Option<Number> {
2552 let x_l = self.declared_x_l.borrow();
2558 let x_u = self.declared_x_u.borrow();
2559 if x_l.is_none() && x_u.is_none() {
2560 return None;
2561 }
2562 let full = self.lift_x_to_full(x);
2563 let cls = self.adapter.borrow().classification().clone();
2564 let mut worst = 0.0_f64;
2565 if let Some(x_l) = x_l.as_ref() {
2566 for (i, &var_idx) in cls.x_l_map.iter().enumerate() {
2567 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
2568 let viol = x_l[i] - full[full_idx];
2569 if viol.is_finite() && viol > worst {
2570 worst = viol;
2571 }
2572 }
2573 }
2574 if let Some(x_u) = x_u.as_ref() {
2575 for (i, &var_idx) in cls.x_u_map.iter().enumerate() {
2576 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
2577 let viol = full[full_idx] - x_u[i];
2578 if viol.is_finite() && viol > worst {
2579 worst = viol;
2580 }
2581 }
2582 }
2583 Some(worst)
2584 }
2585
2586 fn declared_x_bounds(&self) -> Option<(Vec<Number>, Vec<Number>)> {
2587 let xl = self.declared_x_l.borrow().clone()?;
2592 let xu = self.declared_x_u.borrow().clone()?;
2593 Some((xl, xu))
2594 }
2595
2596 fn declared_c_rhs(&self) -> Option<Vec<Number>> {
2597 let mut b = self.c_rhs.clone();
2603 if let Some(dc) = self.c_scale.borrow().as_ref() {
2604 for (i, slot) in b.iter_mut().enumerate() {
2605 *slot *= dc[i];
2606 }
2607 }
2608 Some(b)
2609 }
2610
2611 fn px_l(&self) -> Rc<dyn Matrix> {
2612 Rc::clone(&self.px_l)
2613 }
2614 fn px_u(&self) -> Rc<dyn Matrix> {
2615 Rc::clone(&self.px_u)
2616 }
2617 fn pd_l(&self) -> Rc<dyn Matrix> {
2618 Rc::clone(&self.pd_l)
2619 }
2620 fn pd_u(&self) -> Rc<dyn Matrix> {
2621 Rc::clone(&self.pd_u)
2622 }
2623
2624 fn adjust_variable_bounds(
2632 &mut self,
2633 new_x_l: &dyn Vector,
2634 new_x_u: &dyn Vector,
2635 new_d_l: &dyn Vector,
2636 new_d_u: &dyn Vector,
2637 ) {
2638 fn install(slot: &mut Rc<DenseVector>, new: &dyn Vector) {
2642 Rc::get_mut(slot)
2643 .expect("adjust_variable_bounds: bound vector is uniquely owned")
2644 .copy(new);
2645 }
2646 install(&mut self.x_l, new_x_l);
2647 install(&mut self.x_u, new_x_u);
2648 install(&mut self.d_l, new_d_l);
2649 install(&mut self.d_u, new_d_u);
2650 }
2651
2652 fn obj_scaling_factor(&self) -> Number {
2653 self.obj_scale_factor.get()
2654 }
2655
2656 fn computed_obj_scaling_factor(&self) -> Number {
2657 self.computed_obj_scale.get()
2658 }
2659
2660 fn start_obj_gradient_max(&self) -> Number {
2661 self.start_obj_grad_max.get()
2662 }
2663
2664 fn c_scale_vec(&self) -> Option<Vec<Number>> {
2665 self.c_scale.borrow().clone()
2666 }
2667
2668 fn d_scale_vec(&self) -> Option<Vec<Number>> {
2669 self.d_scale.borrow().clone()
2670 }
2671
2672 fn split_space_names(&self) -> Option<SplitNames> {
2686 let a = self.adapter.borrow();
2687 let cls = a.classification();
2688
2689 let mut var_meta = MetaData::default();
2690 let mut con_meta = MetaData::default();
2691 if !a
2692 .tnlp()
2693 .borrow_mut()
2694 .get_var_con_metadata(&mut var_meta, &mut con_meta)
2695 {
2696 return None;
2697 }
2698
2699 let var_full = var_meta.strings.get(IDX_NAMES);
2702 let con_full = con_meta.strings.get(IDX_NAMES);
2703 if var_full.is_none() && con_full.is_none() {
2704 return None;
2705 }
2706
2707 let pick = |pool: Option<&Vec<String>>, full_idx: Index| -> Option<String> {
2710 pool.and_then(|v| v.get(full_idx as usize))
2711 .filter(|s| !s.is_empty())
2712 .cloned()
2713 };
2714
2715 let x_var = cls
2716 .x_not_fixed_map
2717 .iter()
2718 .map(|&full_idx| pick(var_full, full_idx))
2719 .collect();
2720 let eq = cls
2721 .c_map
2722 .iter()
2723 .map(|&full_idx| pick(con_full, full_idx))
2724 .collect();
2725 let ineq = cls
2726 .d_map
2727 .iter()
2728 .map(|&full_idx| pick(con_full, full_idx))
2729 .collect();
2730
2731 let names = SplitNames { x_var, eq, ineq };
2732 names.any_present().then_some(names)
2733 }
2734
2735 fn prepare_warm_start(&mut self) -> bool {
2736 let Some(snapshot) = self.fetch_warm_start_snapshot() else {
2737 return false;
2738 };
2739 *self.warm_start_snapshot.borrow_mut() = Some(snapshot);
2740 true
2741 }
2742
2743 fn finish_warm_start(&mut self) {
2744 self.warm_start_snapshot.borrow_mut().take();
2745 }
2746
2747 fn get_starting_x(&mut self, x: &mut dyn Vector) -> bool {
2751 let cls = self.adapter.borrow().classification().clone();
2752 if let Some(snapshot) = self.warm_start_snapshot.borrow().as_ref() {
2753 let Some(dx) = x.as_any_mut().downcast_mut::<DenseVector>() else {
2754 return false;
2755 };
2756 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
2757 dx.values_mut()[var_idx] = snapshot.x[full_idx as usize];
2758 }
2759 return true;
2760 }
2761 let n_full_x = cls.n_full_x as usize;
2762 let n_full_g = cls.n_full_g as usize;
2763 let mut full_x = vec![0.0; n_full_x];
2764 let mut full_z_l = vec![0.0; n_full_x];
2765 let mut full_z_u = vec![0.0; n_full_x];
2766 let mut full_lambda = vec![0.0; n_full_g];
2767 let ok = {
2768 let a = self.adapter.borrow();
2769 let mut t = a.tnlp().borrow_mut();
2770 t.get_starting_point(StartingPoint {
2771 init_x: true,
2772 x: &mut full_x,
2773 init_z: false,
2774 z_l: &mut full_z_l,
2775 z_u: &mut full_z_u,
2776 init_lambda: false,
2777 lambda: &mut full_lambda,
2778 })
2779 };
2780 if !ok {
2781 return false;
2782 }
2783 let Some(dx) = x.as_any_mut().downcast_mut::<DenseVector>() else {
2784 return false;
2785 };
2786 let xs = dx.values_mut();
2787 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
2788 xs[var_idx] = full_x[full_idx as usize];
2789 }
2790 true
2791 }
2792
2793 fn get_starting_y(&mut self, y_c: &mut dyn Vector, y_d: &mut dyn Vector) -> bool {
2794 let Some(y_c) = y_c.as_any_mut().downcast_mut::<DenseVector>() else {
2795 return false;
2796 };
2797 let Some(y_d) = y_d.as_any_mut().downcast_mut::<DenseVector>() else {
2798 return false;
2799 };
2800 if let Some(snapshot) = self.warm_start_snapshot.borrow().as_ref() {
2801 let cls = self.adapter.borrow().classification().clone();
2802 let obj_scal = self.obj_scale_factor.get();
2803 let c_scale = self.c_scale.borrow();
2804 for (i, &g_idx) in cls.c_map.iter().enumerate() {
2805 let cs = c_scale.as_ref().map(|v| v[i]).unwrap_or(1.0);
2806 y_c.values_mut()[i] = snapshot.lambda[g_idx as usize] / cs * obj_scal;
2807 }
2808 let d_scale = self.d_scale.borrow();
2809 for (i, &g_idx) in cls.d_map.iter().enumerate() {
2810 let ds = d_scale.as_ref().map(|v| v[i]).unwrap_or(1.0);
2811 y_d.values_mut()[i] = snapshot.lambda[g_idx as usize] / ds * obj_scal;
2812 }
2813 return true;
2814 }
2815 let mut x = DenseVectorSpace::new(self.n()).make_new_dense();
2816 let mut z_l = DenseVectorSpace::new(self.x_l.dim()).make_new_dense();
2817 let mut z_u = DenseVectorSpace::new(self.x_u.dim()).make_new_dense();
2818 self.initialize_starting_point(
2819 &mut x, false, y_c, true, y_d, true, &mut z_l, false, &mut z_u, false,
2820 )
2821 }
2822
2823 fn get_starting_z(
2824 &mut self,
2825 z_l: &mut dyn Vector,
2826 z_u: &mut dyn Vector,
2827 _v_l: &mut dyn Vector,
2828 _v_u: &mut dyn Vector,
2829 ) -> bool {
2830 let Some(z_l) = z_l.as_any_mut().downcast_mut::<DenseVector>() else {
2833 return false;
2834 };
2835 let Some(z_u) = z_u.as_any_mut().downcast_mut::<DenseVector>() else {
2836 return false;
2837 };
2838 if let Some(snapshot) = self.warm_start_snapshot.borrow().as_ref() {
2839 let cls = self.adapter.borrow().classification().clone();
2840 let obj_scal = self.obj_scale_factor.get();
2841 for (i, slot) in z_l.values_mut().iter_mut().enumerate() {
2842 let var_idx = cls.x_l_map[i] as usize;
2843 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
2844 *slot = snapshot.z_l[full_idx] * obj_scal;
2845 }
2846 for (i, slot) in z_u.values_mut().iter_mut().enumerate() {
2847 let var_idx = cls.x_u_map[i] as usize;
2848 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
2849 *slot = snapshot.z_u[full_idx] * obj_scal;
2850 }
2851 return true;
2852 }
2853 let mut x = DenseVectorSpace::new(self.n()).make_new_dense();
2854 let mut y_c = DenseVectorSpace::new(self.m_eq()).make_new_dense();
2855 let mut y_d = DenseVectorSpace::new(self.m_ineq()).make_new_dense();
2856 self.initialize_starting_point(
2857 &mut x, false, &mut y_c, false, &mut y_d, false, z_l, true, z_u, true,
2858 )
2859 }
2860
2861 fn lift_x_to_full(&self, x: &dyn Vector) -> Vec<Number> {
2862 OrigIpoptNlp::lift_x_to_full(self, x)
2863 }
2864
2865 fn finalize_solution_x(&self, x: &dyn Vector) -> Vec<Number> {
2866 OrigIpoptNlp::finalize_solution_x(self, x)
2867 }
2868
2869 fn n_full_x(&self) -> Index {
2870 self.adapter.borrow().classification().n_full_x
2871 }
2872
2873 fn n_full_g(&self) -> Index {
2874 self.adapter.borrow().classification().n_full_g
2875 }
2876
2877 fn pack_lambda_for_user(&self, y_c: &dyn Vector, y_d: &dyn Vector) -> Vec<Number> {
2878 let cls = self.adapter.borrow().classification().clone();
2879 OrigIpoptNlp::pack_lambda_for_user(self, y_c, y_d, &cls)
2880 }
2881
2882 fn pack_g_for_user(&self, c: &dyn Vector, d: &dyn Vector) -> Vec<Number> {
2883 let cls = self.adapter.borrow().classification().clone();
2884 let mut g = vec![0.0; cls.n_full_g as usize];
2885 if cls.n_c > 0 {
2886 let Some(dc) = c.as_any().downcast_ref::<DenseVector>() else {
2887 panic!("OrigIpoptNlp expects DenseVector for c");
2888 };
2889 let cs = self.c_scale.borrow();
2890 let c_vals = dc.expanded_values();
2900 for (i, &g_idx) in cls.c_map.iter().enumerate() {
2901 let v = c_vals[i];
2902 g[g_idx as usize] = match cs.as_ref() {
2903 Some(s) => v / s[i],
2904 None => v,
2905 };
2906 }
2907 }
2908 if cls.n_d > 0 {
2909 let Some(dd) = d.as_any().downcast_ref::<DenseVector>() else {
2910 panic!("OrigIpoptNlp expects DenseVector for d");
2911 };
2912 let ds = self.d_scale.borrow();
2913 let d_vals = dd.expanded_values();
2915 for (i, &g_idx) in cls.d_map.iter().enumerate() {
2916 let v = d_vals[i];
2917 g[g_idx as usize] = match ds.as_ref() {
2918 Some(s) => v / s[i],
2919 None => v,
2920 };
2921 }
2922 }
2923 g
2924 }
2925
2926 fn pack_z_l_for_user(&self, z_l: &dyn Vector) -> Vec<Number> {
2927 let cls = self.adapter.borrow().classification().clone();
2928 let mut full = vec![0.0; cls.n_full_x as usize];
2929 if z_l.dim() == 0 {
2930 return full;
2931 }
2932 let Some(dz) = z_l.as_any().downcast_ref::<DenseVector>() else {
2933 panic!("OrigIpoptNlp expects DenseVector for z_l");
2934 };
2935 let vals = dz.expanded_values();
2936 for (k, &var_idx) in cls.x_l_map.iter().enumerate() {
2937 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
2938 full[full_idx] = vals[k];
2939 }
2940 full
2941 }
2942
2943 fn pack_z_u_for_user(&self, z_u: &dyn Vector) -> Vec<Number> {
2944 let cls = self.adapter.borrow().classification().clone();
2945 let mut full = vec![0.0; cls.n_full_x as usize];
2946 if z_u.dim() == 0 {
2947 return full;
2948 }
2949 let Some(dz) = z_u.as_any().downcast_ref::<DenseVector>() else {
2950 panic!("OrigIpoptNlp expects DenseVector for z_u");
2951 };
2952 let vals = dz.expanded_values();
2953 for (k, &var_idx) in cls.x_u_map.iter().enumerate() {
2954 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
2955 full[full_idx] = vals[k];
2956 }
2957 full
2958 }
2959
2960 fn finalize_solution_lambda(&self, y_c: &dyn Vector, y_d: &dyn Vector) -> Vec<Number> {
2961 OrigIpoptNlp::finalize_solution_lambda(self, y_c, y_d)
2962 }
2963
2964 fn finalize_solution_z_l(&self, z_l: &dyn Vector) -> Vec<Number> {
2965 OrigIpoptNlp::finalize_solution_z_l(self, z_l)
2966 }
2967
2968 fn finalize_solution_z_u(&self, z_u: &dyn Vector) -> Vec<Number> {
2969 OrigIpoptNlp::finalize_solution_z_u(self, z_u)
2970 }
2971
2972 fn complete_fixed_var_bound_multipliers(
2973 &self,
2974 x: &[Number],
2975 lambda: &[Number],
2976 z_l: &mut [Number],
2977 z_u: &mut [Number],
2978 ) {
2979 OrigIpoptNlp::complete_fixed_var_bound_multipliers(self, x, lambda, z_l, z_u)
2980 }
2981
2982 fn variable_scaling(&self) -> Option<Vec<Number>> {
2983 self.variable_scaling.clone()
2989 }
2990
2991 fn full_x_to_var_x(&self, full_idx: Index) -> Option<Index> {
2992 let cls = self.adapter.borrow();
2993 let cls = cls.classification();
2994 let f = full_idx as usize;
2995 if f >= cls.full_to_var.len() {
2996 return None;
2997 }
2998 let v = cls.full_to_var[f];
2999 if v < 0 { None } else { Some(v) }
3000 }
3001
3002 fn full_g_to_c_block(&self, full_idx: Index) -> Option<Index> {
3003 let cls = self.adapter.borrow();
3004 let cls = cls.classification();
3005 let f = full_idx as usize;
3006 if f >= cls.full_to_c.len() {
3007 return None;
3008 }
3009 let c = cls.full_to_c[f];
3010 if c < 0 { None } else { Some(c) }
3011 }
3012
3013 fn full_g_to_d_block(&self, full_idx: Index) -> Option<Index> {
3014 let cls = self.adapter.borrow();
3015 let cls = cls.classification();
3016 let f = full_idx as usize;
3017 if f >= cls.full_to_d.len() {
3018 return None;
3019 }
3020 let d = cls.full_to_d[f];
3021 if d < 0 { None } else { Some(d) }
3022 }
3023
3024 fn var_x_to_full_x(&self, var_idx: Index) -> Index {
3025 let cls = self.adapter.borrow();
3026 let cls = cls.classification();
3027 cls.x_not_fixed_map[var_idx as usize]
3028 }
3029}
3030
3031fn lagrangian_gradient_full(
3038 tnlp: &Rc<RefCell<dyn TNLP>>,
3039 x: &[Number],
3040 lambda: &[Number],
3041) -> Option<Vec<Number>> {
3042 let mut t = tnlp.borrow_mut();
3043 let info = t.get_nlp_info()?;
3044 let n = info.n as usize;
3045 let nnz = info.nnz_jac_g as usize;
3046 let mut s = vec![0.0; n];
3047 if !t.eval_grad_f(x, true, &mut s) {
3048 return None;
3049 }
3050 if nnz > 0 {
3051 let mut irow = vec![0 as Index; nnz];
3052 let mut jcol = vec![0 as Index; nnz];
3053 if !t.eval_jac_g(
3054 None,
3055 false,
3056 SparsityRequest::Structure {
3057 irow: &mut irow,
3058 jcol: &mut jcol,
3059 },
3060 ) {
3061 return None;
3062 }
3063 let mut vals = vec![0.0; nnz];
3064 if !t.eval_jac_g(
3065 Some(x),
3066 false,
3067 SparsityRequest::Values { values: &mut vals },
3068 ) {
3069 return None;
3070 }
3071 let off: Index = match info.index_style {
3072 IndexStyle::Fortran => 1,
3073 IndexStyle::C => 0,
3074 };
3075 for k in 0..nnz {
3076 let (Ok(r), Ok(c)) = (
3077 usize::try_from(irow[k] - off),
3078 usize::try_from(jcol[k] - off),
3079 ) else {
3080 continue;
3081 };
3082 if let (Some(&l), Some(sc)) = (lambda.get(r), s.get_mut(c)) {
3083 *sc += vals[k] * l;
3084 }
3085 }
3086 }
3087 Some(s)
3088}
3089
3090#[cfg(test)]
3091mod tests {
3092 use super::*;
3093 use crate::tnlp::{
3094 BoundsInfo, IndexStyle, IpoptCq, IpoptData, NlpInfo, Solution, SparsityRequest,
3095 StartingPoint, TNLP,
3096 };
3097
3098 #[derive(Default)]
3103 struct Hs071 {
3104 eval_f_calls: usize,
3105 eval_grad_f_calls: usize,
3106 eval_g_calls: usize,
3107 eval_jac_g_value_calls: usize,
3108 eval_h_value_calls: usize,
3109 get_bounds_info_calls: usize,
3110 get_starting_point_calls: usize,
3111 }
3112
3113 impl TNLP for Hs071 {
3114 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3115 Some(NlpInfo {
3116 n: 4,
3117 m: 2,
3118 nnz_jac_g: 8,
3119 nnz_h_lag: 10,
3120 index_style: IndexStyle::C,
3121 })
3122 }
3123 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3124 self.get_bounds_info_calls += 1;
3125 b.x_l.copy_from_slice(&[1.0; 4]);
3126 b.x_u.copy_from_slice(&[5.0; 4]);
3127 b.g_l.copy_from_slice(&[25.0, 40.0]);
3130 b.g_u.copy_from_slice(&[2.0e19, 40.0]);
3131 true
3132 }
3133 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3134 self.get_starting_point_calls += 1;
3135 sp.x.copy_from_slice(&[1.0, 5.0, 5.0, 1.0]);
3136 if sp.init_z {
3137 sp.z_l.copy_from_slice(&[1.0, 2.0, 3.0, 4.0]);
3138 sp.z_u.copy_from_slice(&[5.0, 6.0, 7.0, 8.0]);
3139 }
3140 if sp.init_lambda {
3141 sp.lambda.copy_from_slice(&[11.0, 13.0]);
3142 }
3143 true
3144 }
3145 fn eval_f(&mut self, x: &[Number], _new_x: bool) -> Option<Number> {
3146 self.eval_f_calls += 1;
3147 Some(x[0] * x[3] * (x[0] + x[1] + x[2]) + x[2])
3148 }
3149 fn eval_grad_f(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
3150 self.eval_grad_f_calls += 1;
3151 g[0] = x[3] * (2.0 * x[0] + x[1] + x[2]);
3156 g[1] = x[0] * x[3];
3157 g[2] = x[0] * x[3] + 1.0;
3158 g[3] = x[0] * (x[0] + x[1] + x[2]);
3159 true
3160 }
3161 fn eval_g(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
3162 self.eval_g_calls += 1;
3163 g[0] = x[0] * x[1] * x[2] * x[3];
3166 g[1] = x[0] * x[0] + x[1] * x[1] + x[2] * x[2] + x[3] * x[3];
3167 true
3168 }
3169 fn eval_jac_g(
3170 &mut self,
3171 x: Option<&[Number]>,
3172 _new_x: bool,
3173 mode: SparsityRequest<'_>,
3174 ) -> bool {
3175 match mode {
3176 SparsityRequest::Structure { irow, jcol } => {
3177 irow.copy_from_slice(&[0, 0, 0, 0, 1, 1, 1, 1]);
3179 jcol.copy_from_slice(&[0, 1, 2, 3, 0, 1, 2, 3]);
3180 }
3181 SparsityRequest::Values { values } => {
3182 self.eval_jac_g_value_calls += 1;
3183 let x = x.expect("eval_jac_g(Values) without x");
3184 values[0] = x[1] * x[2] * x[3];
3186 values[1] = x[0] * x[2] * x[3];
3187 values[2] = x[0] * x[1] * x[3];
3188 values[3] = x[0] * x[1] * x[2];
3189 values[4] = 2.0 * x[0];
3191 values[5] = 2.0 * x[1];
3192 values[6] = 2.0 * x[2];
3193 values[7] = 2.0 * x[3];
3194 }
3195 }
3196 true
3197 }
3198 fn eval_h(
3199 &mut self,
3200 x: Option<&[Number]>,
3201 _new_x: bool,
3202 obj_factor: Number,
3203 lambda: Option<&[Number]>,
3204 _new_lambda: bool,
3205 mode: SparsityRequest<'_>,
3206 ) -> bool {
3207 match mode {
3210 SparsityRequest::Structure { irow, jcol } => {
3211 irow.copy_from_slice(&[0, 1, 1, 2, 2, 2, 3, 3, 3, 3]);
3212 jcol.copy_from_slice(&[0, 0, 1, 0, 1, 2, 0, 1, 2, 3]);
3213 }
3214 SparsityRequest::Values { values } => {
3215 self.eval_h_value_calls += 1;
3216 let x = x.expect("eval_h(Values) without x");
3217 let lam = lambda.expect("eval_h(Values) without lambda");
3218 let of = obj_factor;
3219 let l0 = lam[0];
3229 let l1 = lam[1];
3230 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; }
3241 }
3242 true
3243 }
3244 fn finalize_solution(&mut self, _sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {}
3245 }
3246
3247 fn build_orig_nlp() -> (Rc<RefCell<TNLPAdapter>>, OrigIpoptNlp) {
3248 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071::default()));
3249 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3250 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3251 (adapter, nlp)
3252 }
3253
3254 fn dense_x(values: &[Number], space: &Rc<DenseVectorSpace>) -> DenseVector {
3255 let mut v = space.make_new_dense();
3256 v.values_mut().copy_from_slice(values);
3257 v
3258 }
3259
3260 #[test]
3261 fn dimensions_match_classification() {
3262 let (_, nlp) = build_orig_nlp();
3263 assert_eq!(nlp.n(), 4);
3265 assert_eq!(nlp.m_eq(), 1);
3266 assert_eq!(nlp.m_ineq(), 1);
3267 assert_eq!(nlp.jac_c_space().nonzeros(), 4);
3269 assert_eq!(nlp.jac_d_space().nonzeros(), 4);
3270 assert_eq!(nlp.h_space().unwrap().nonzeros(), 10);
3272 assert_eq!(nlp.x_l().dim(), 4);
3274 assert_eq!(nlp.x_u().dim(), 4);
3275 assert_eq!(nlp.d_l().dim(), 1);
3276 assert_eq!(nlp.d_u().dim(), 0);
3277 }
3278
3279 #[test]
3280 fn eval_f_at_starting_point() {
3281 let (_, mut nlp) = build_orig_nlp();
3282 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3283 assert_eq!(nlp.eval_f(&x), 16.0);
3285 assert_eq!(nlp.f_evals(), 1);
3286 }
3287
3288 #[test]
3289 fn eval_grad_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 let mut g = nlp.x_space().make_new_dense();
3293 nlp.eval_grad_f(&x, &mut g);
3294 assert_eq!(g.values(), &[12.0, 1.0, 2.0, 11.0]);
3299 assert_eq!(nlp.grad_f_evals(), 1);
3300 }
3301
3302 #[test]
3303 fn eval_c_returns_equality_residual() {
3304 let (_, mut nlp) = build_orig_nlp();
3305 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3306 let mut c = nlp.c_space().make_new_dense();
3307 nlp.eval_c(&x, &mut c);
3308 assert_eq!(c.values(), &[12.0]);
3310 assert_eq!(nlp.c_evals(), 1);
3311 }
3312
3313 #[test]
3314 fn eval_d_returns_inequality_value_unshifted() {
3315 let (_, mut nlp) = build_orig_nlp();
3316 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3317 let mut d = nlp.d_space().make_new_dense();
3318 nlp.eval_d(&x, &mut d);
3319 assert_eq!(d.values(), &[25.0]);
3321 assert_eq!(nlp.d_evals(), 1);
3322 }
3323
3324 #[test]
3325 fn cache_returns_without_re_eval() {
3326 let (_, mut nlp) = build_orig_nlp();
3327 let mut x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3328 let f1 = nlp.eval_f(&x);
3329 let f2 = nlp.eval_f(&x);
3330 assert_eq!(f1, f2);
3331 assert_eq!(nlp.f_evals(), 1, "second call must be served from cache");
3332 x.values_mut()[0] = 1.0; let _ = nlp.eval_f(&x);
3335 assert_eq!(nlp.f_evals(), 2);
3336 }
3337
3338 #[test]
3339 fn jac_c_picks_only_equality_rows() {
3340 let (_, mut nlp) = build_orig_nlp();
3341 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3342 let m = nlp.eval_jac_c(&x);
3343 let g = m
3344 .as_any()
3345 .downcast_ref::<GenTMatrix>()
3346 .expect("jac_c is a GenTMatrix");
3347 assert_eq!(g.values(), &[2.0, 10.0, 10.0, 2.0]);
3349 assert_eq!(g.irows(), &[1, 1, 1, 1]);
3351 assert_eq!(g.jcols(), &[1, 2, 3, 4]);
3352 }
3353
3354 #[test]
3355 fn jac_d_picks_only_inequality_rows() {
3356 let (_, mut nlp) = build_orig_nlp();
3357 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3358 let m = nlp.eval_jac_d(&x);
3359 let g = m
3360 .as_any()
3361 .downcast_ref::<GenTMatrix>()
3362 .expect("jac_d is a GenTMatrix");
3363 assert_eq!(g.values(), &[25.0, 5.0, 5.0, 25.0]);
3366 }
3367
3368 fn build_orig_nlp_counting() -> (Rc<RefCell<Hs071>>, OrigIpoptNlp) {
3373 let concrete = Rc::new(RefCell::new(Hs071::default()));
3374 let tnlp: Rc<RefCell<dyn TNLP>> = concrete.clone();
3375 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3376 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3377 (concrete, nlp)
3378 }
3379
3380 #[test]
3381 fn eval_c_and_eval_d_share_one_eval_g_per_iterate() {
3382 let (tnlp, mut nlp) = build_orig_nlp_counting();
3386 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3387 let mut c = nlp.c_space().make_new_dense();
3388 let mut d = nlp.d_space().make_new_dense();
3389 nlp.eval_c(&x, &mut c);
3390 nlp.eval_d(&x, &mut d);
3391 assert_eq!(
3392 tnlp.borrow().eval_g_calls,
3393 1,
3394 "eval_c + eval_d at one iterate must share a single user eval_g"
3395 );
3396 assert_eq!(nlp.c_evals(), 1);
3398 assert_eq!(nlp.d_evals(), 1);
3399 assert_eq!(c.values(), &[12.0]);
3401 assert_eq!(d.values(), &[25.0]);
3402
3403 let mut x2 = x;
3406 x2.values_mut()[0] = 2.0;
3407 nlp.eval_c(&x2, &mut c);
3408 nlp.eval_d(&x2, &mut d);
3409 assert_eq!(
3410 tnlp.borrow().eval_g_calls,
3411 2,
3412 "a new iterate triggers exactly one more shared eval_g"
3413 );
3414 }
3415
3416 #[test]
3417 fn eval_c_does_not_refetch_bounds_per_iterate() {
3418 let (tnlp, mut nlp) = build_orig_nlp_counting();
3426 let baseline = tnlp.borrow().get_bounds_info_calls;
3429
3430 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3431 let mut c = nlp.c_space().make_new_dense();
3432 nlp.eval_c(&x, &mut c);
3433 assert_eq!(c.values(), &[12.0]);
3435
3436 let mut x2 = x;
3438 for k in 0..5 {
3439 x2.values_mut()[0] = 2.0 + k as Number;
3440 nlp.eval_c(&x2, &mut c);
3441 }
3442
3443 assert_eq!(
3444 tnlp.borrow().get_bounds_info_calls,
3445 baseline,
3446 "eval_c must reuse the captured c_rhs, not re-fetch bounds per iterate"
3447 );
3448 }
3449
3450 #[test]
3451 fn eval_jac_c_and_eval_jac_d_share_one_eval_jac_g_per_iterate() {
3452 let (tnlp, mut nlp) = build_orig_nlp_counting();
3456 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3457 let _ = nlp.eval_jac_c(&x);
3458 let _ = nlp.eval_jac_d(&x);
3459 assert_eq!(
3460 tnlp.borrow().eval_jac_g_value_calls,
3461 1,
3462 "eval_jac_c + eval_jac_d at one iterate must share a single eval_jac_g"
3463 );
3464 assert_eq!(nlp.jac_c_evals(), 1);
3465 assert_eq!(nlp.jac_d_evals(), 1);
3466 }
3467
3468 #[test]
3469 fn starting_point_is_compressed_into_x_var() {
3470 let (_, mut nlp) = build_orig_nlp();
3471 let mut x = nlp.x_space().make_new_dense();
3472 let mut yc = nlp.c_space().make_new_dense();
3473 let mut yd = nlp.d_space().make_new_dense();
3474 let mut zl = nlp.x_l_space().make_new_dense();
3475 let mut zu = nlp.x_u_space().make_new_dense();
3476 let ok = nlp.initialize_starting_point(
3477 &mut x, true, &mut yc, false, &mut yd, false, &mut zl, false, &mut zu, false,
3478 );
3479 assert!(ok);
3480 assert_eq!(x.values(), &[1.0, 5.0, 5.0, 1.0]);
3481 }
3482
3483 #[test]
3484 fn warm_start_duals_are_forwarded_into_algorithm_vectors() {
3485 let (_, mut nlp) = build_orig_nlp();
3486 let mut y_c = nlp.c_space().make_new_dense();
3487 let mut y_d = nlp.d_space().make_new_dense();
3488 assert!(nlp.get_starting_y(&mut y_c, &mut y_d));
3489 assert_eq!(y_c.values(), &[13.0], "equality multiplier g1");
3490 assert_eq!(y_d.values(), &[11.0], "inequality multiplier g0");
3491
3492 let mut z_l = nlp.x_l_space().make_new_dense();
3493 let mut z_u = nlp.x_u_space().make_new_dense();
3494 let mut v_l = nlp.d_l_space().make_new_dense();
3495 let mut v_u = nlp.d_u_space().make_new_dense();
3496 assert!(nlp.get_starting_z(&mut z_l, &mut z_u, &mut v_l, &mut v_u));
3497 assert_eq!(z_l.values(), &[1.0, 2.0, 3.0, 4.0]);
3498 assert_eq!(z_u.values(), &[5.0, 6.0, 7.0, 8.0]);
3499 }
3500
3501 #[test]
3502 fn warm_start_prefetches_one_tnlp_snapshot_for_x_y_and_z() {
3503 let (tnlp, mut nlp) = build_orig_nlp_counting();
3504 assert!(nlp.prepare_warm_start());
3505
3506 let mut x = nlp.x_space().make_new_dense();
3507 let mut y_c = nlp.c_space().make_new_dense();
3508 let mut y_d = nlp.d_space().make_new_dense();
3509 let mut z_l = nlp.x_l_space().make_new_dense();
3510 let mut z_u = nlp.x_u_space().make_new_dense();
3511 let mut v_l = nlp.d_l_space().make_new_dense();
3512 let mut v_u = nlp.d_u_space().make_new_dense();
3513 assert!(nlp.get_starting_x(&mut x));
3514 assert!(nlp.get_starting_y(&mut y_c, &mut y_d));
3515 assert!(nlp.get_starting_z(&mut z_l, &mut z_u, &mut v_l, &mut v_u));
3516
3517 assert_eq!(tnlp.borrow().get_starting_point_calls, 1);
3518 assert_eq!(x.values(), &[1.0, 5.0, 5.0, 1.0]);
3519 assert_eq!(y_c.values(), &[13.0]);
3520 assert_eq!(y_d.values(), &[11.0]);
3521 assert_eq!(z_l.values(), &[1.0, 2.0, 3.0, 4.0]);
3522 assert_eq!(z_u.values(), &[5.0, 6.0, 7.0, 8.0]);
3523
3524 nlp.finish_warm_start();
3525 let mut x_after_init = nlp.x_space().make_new_dense();
3526 assert!(nlp.get_starting_x(&mut x_after_init));
3527 assert_eq!(
3528 tnlp.borrow().get_starting_point_calls,
3529 2,
3530 "the snapshot must not affect later starting-point requests"
3531 );
3532 }
3533
3534 struct OneFixedOneFree;
3539 impl TNLP for OneFixedOneFree {
3540 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3541 Some(NlpInfo {
3542 n: 2,
3543 m: 1,
3544 nnz_jac_g: 1,
3545 nnz_h_lag: 0,
3546 index_style: IndexStyle::C,
3547 })
3548 }
3549 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3550 b.x_l[0] = 7.0;
3551 b.x_u[0] = 7.0; b.x_l[1] = -1.0e19;
3553 b.x_u[1] = 1.0e19;
3554 b.g_l[0] = 0.0;
3555 b.g_u[0] = 0.0; true
3557 }
3558 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3559 sp.x[0] = 7.0;
3560 sp.x[1] = 0.5;
3561 true
3562 }
3563 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
3564 Some(x[1])
3565 }
3566 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
3567 g[0] = 0.0;
3568 g[1] = 1.0;
3569 true
3570 }
3571 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3572 g[0] = x[1];
3573 true
3574 }
3575 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
3576 match m {
3577 SparsityRequest::Structure { irow, jcol } => {
3578 irow[0] = 0;
3579 jcol[0] = 1;
3580 }
3581 SparsityRequest::Values { values } => values[0] = 1.0,
3582 }
3583 true
3584 }
3585 fn eval_h(
3586 &mut self,
3587 _: Option<&[Number]>,
3588 _: bool,
3589 _: Number,
3590 _: Option<&[Number]>,
3591 _: bool,
3592 _: SparsityRequest<'_>,
3593 ) -> bool {
3594 true
3595 }
3596 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3597 }
3598
3599 struct FixedVarReachModel {
3614 second_order: bool,
3615 }
3616 impl TNLP for FixedVarReachModel {
3617 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3618 Some(NlpInfo {
3619 n: 3,
3620 m: 2,
3621 nnz_jac_g: 4,
3622 nnz_h_lag: 3,
3623 index_style: IndexStyle::C,
3624 })
3625 }
3626 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3627 b.x_l.copy_from_slice(&[-1.0e19, -1.0e19, 2.0]);
3628 b.x_u.copy_from_slice(&[1.0e19, 1.0e19, 2.0]); b.g_l.copy_from_slice(&[0.0, 0.0]);
3630 b.g_u.copy_from_slice(&[0.0, 1.0e19]); true
3632 }
3633 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3634 sp.x.copy_from_slice(&[1.0, 1.0, 2.0]);
3635 true
3636 }
3637 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
3638 Some(x[0] * x[0] * x[0])
3639 }
3640 fn eval_grad_f(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3641 g.copy_from_slice(&[3.0 * x[0] * x[0], 0.0, 0.0]);
3642 true
3643 }
3644 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3645 g[0] = x[0] * x[1] - 1.0;
3646 g[1] = if self.second_order {
3647 x[1] * x[2]
3648 } else {
3649 x[1] * x[1] + x[2]
3650 };
3651 true
3652 }
3653 fn eval_jac_g(&mut self, x: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
3654 match m {
3655 SparsityRequest::Structure { irow, jcol } => {
3656 irow.copy_from_slice(&[0, 0, 1, 1]);
3659 jcol.copy_from_slice(&[0, 1, 1, 2]);
3660 }
3661 SparsityRequest::Values { values } => {
3662 let x = x.expect("values need x");
3663 values[0] = x[1];
3664 values[1] = x[0];
3665 let (d1, d2) = if self.second_order {
3666 (x[2], x[1])
3667 } else {
3668 (2.0 * x[1], 1.0)
3669 };
3670 values[2] = d1;
3671 values[3] = d2;
3672 }
3673 }
3674 true
3675 }
3676 fn eval_h(
3677 &mut self,
3678 x: Option<&[Number]>,
3679 _: bool,
3680 sigma: Number,
3681 lambda: Option<&[Number]>,
3682 _: bool,
3683 m: SparsityRequest<'_>,
3684 ) -> bool {
3685 match m {
3686 SparsityRequest::Structure { irow, jcol } => {
3687 irow.copy_from_slice(&[0, 1, if self.second_order { 2 } else { 1 }]);
3691 jcol.copy_from_slice(&[0, 0, 1]);
3692 }
3693 SparsityRequest::Values { values } => {
3694 let (x, l) = (x.expect("values need x"), lambda.expect("values need λ"));
3695 values[0] = sigma * 6.0 * x[0];
3696 values[1] = l[0];
3697 values[2] = if self.second_order { l[1] } else { 2.0 * l[1] };
3698 }
3699 }
3700 true
3701 }
3702 fn derivative_proofs(&mut self) -> DerivativeProofs {
3703 DerivativeProofs {
3704 grad_f: DerivativeProof::Varying,
3705 hessian: DerivativeProof::Varying,
3706 jac: vec![DerivativeProof::Varying; 2],
3707 }
3708 }
3709 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3710 }
3711
3712 fn reach_proofs(second_order: bool) -> [DerivativeProof; 4] {
3713 let tnlp: Rc<RefCell<dyn TNLP>> =
3714 Rc::new(RefCell::new(FixedVarReachModel { second_order }));
3715 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3716 let nlp = OrigIpoptNlp::new(adapter, Rc::new(NoScaling)).unwrap();
3717 nlp.derivative_proofs()
3718 }
3719
3720 #[test]
3729 fn only_the_forms_a_fixed_variable_reaches_lose_their_refusal() {
3730 use DerivativeProof::*;
3731 let [grad_f, hessian, jac_c, jac_d] = reach_proofs(false);
3732 assert_eq!(grad_f, Varying, "x[2] is nowhere in ∇f");
3733 assert_eq!(hessian, Varying, "x[2] appears linearly; ∇²L cannot see it");
3734 assert_eq!(jac_c, Varying, "row 0 has no nonzero in the fixed column");
3735 assert_eq!(jac_d, Unknown, "row 1 does, so fixing x[2] may flatten it");
3736 }
3737
3738 #[test]
3743 fn a_second_order_coupling_to_a_fixed_variable_weakens_the_hessian() {
3744 use DerivativeProof::*;
3745 let [grad_f, hessian, jac_c, jac_d] = reach_proofs(true);
3746 assert_eq!(hessian, Unknown, "x1·x2 puts a fixed index in ∇²L");
3747 assert_eq!(grad_f, Unknown, "same test, and ∇f is reached the same way");
3748 assert_eq!(jac_c, Varying, "row 0 is still untouched by x[2]");
3749 assert_eq!(jac_d, Unknown);
3750 }
3751
3752 #[test]
3753 fn ipopt_nlp_index_mapping_methods_handle_fixed_var() {
3754 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneFixedOneFree));
3755 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3756 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3757
3758 assert_eq!(nlp.n_full_x(), 2);
3760 assert_eq!(nlp.n(), 1);
3761
3762 let nlp_dyn: &dyn crate::ipopt_nlp::IpoptNlp = &nlp;
3764 assert_eq!(nlp_dyn.full_x_to_var_x(0), None);
3765 assert_eq!(nlp_dyn.full_x_to_var_x(1), Some(0));
3766
3767 assert_eq!(nlp_dyn.var_x_to_full_x(0), 1);
3769
3770 assert_eq!(nlp_dyn.full_g_to_c_block(0), Some(0));
3772
3773 assert_eq!(nlp_dyn.full_g_to_d_block(0), None);
3777 assert_eq!(nlp_dyn.full_g_to_d_block(1), None);
3778
3779 let mut x_var = nlp.x_space().make_new_dense();
3781 x_var.values_mut()[0] = 0.5;
3782 let lifted = nlp_dyn.lift_x_to_full(&x_var);
3783 assert_eq!(lifted, vec![7.0, 0.5]);
3784 }
3785
3786 struct NamedFixedOneFree;
3790 impl TNLP for NamedFixedOneFree {
3791 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3792 OneFixedOneFree.get_nlp_info()
3793 }
3794 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3795 OneFixedOneFree.get_bounds_info(b)
3796 }
3797 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3798 OneFixedOneFree.get_starting_point(sp)
3799 }
3800 fn eval_f(&mut self, x: &[Number], n: bool) -> Option<Number> {
3801 OneFixedOneFree.eval_f(x, n)
3802 }
3803 fn eval_grad_f(&mut self, x: &[Number], n: bool, g: &mut [Number]) -> bool {
3804 OneFixedOneFree.eval_grad_f(x, n, g)
3805 }
3806 fn eval_g(&mut self, x: &[Number], n: bool, g: &mut [Number]) -> bool {
3807 OneFixedOneFree.eval_g(x, n, g)
3808 }
3809 fn eval_jac_g(&mut self, x: Option<&[Number]>, n: bool, m: SparsityRequest<'_>) -> bool {
3810 OneFixedOneFree.eval_jac_g(x, n, m)
3811 }
3812 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3813 fn get_var_con_metadata(&mut self, var: &mut MetaData, con: &mut MetaData) -> bool {
3814 var.strings.insert(
3815 IDX_NAMES.to_string(),
3816 vec!["fixed_x".to_string(), "free_x".to_string()],
3817 );
3818 con.strings
3819 .insert(IDX_NAMES.to_string(), vec!["balance".to_string()]);
3820 true
3821 }
3822 }
3823
3824 #[test]
3825 fn split_space_names_threads_through_fixed_var_and_cd_split() {
3826 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(NamedFixedOneFree));
3827 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3828 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3829
3830 let names = nlp.split_space_names().expect("names present");
3831 assert_eq!(names.x_var, vec![Some("free_x".to_string())]);
3833 assert_eq!(names.eq, vec![Some("balance".to_string())]);
3835 assert!(names.ineq.is_empty());
3837 assert!(names.any_present());
3838 }
3839
3840 #[test]
3841 fn split_space_names_none_when_tnlp_declines() {
3842 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneFixedOneFree));
3844 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3845 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3846 assert!(nlp.split_space_names().is_none());
3847 }
3848
3849 struct FixedOnlyHess;
3855 impl TNLP for FixedOnlyHess {
3856 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3857 Some(NlpInfo {
3858 n: 2,
3859 m: 1,
3860 nnz_jac_g: 1,
3861 nnz_h_lag: 1,
3862 index_style: IndexStyle::C,
3863 })
3864 }
3865 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3866 b.x_l[0] = 7.0;
3867 b.x_u[0] = 7.0; b.x_l[1] = -1.0e19;
3869 b.x_u[1] = 1.0e19;
3870 b.g_l[0] = 0.0;
3871 b.g_u[0] = 0.0;
3872 true
3873 }
3874 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3875 sp.x[0] = 7.0;
3876 sp.x[1] = 0.5;
3877 true
3878 }
3879 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
3880 Some(0.5 * x[0] * x[0] + x[1])
3881 }
3882 fn eval_grad_f(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3883 g[0] = x[0];
3884 g[1] = 1.0;
3885 true
3886 }
3887 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3888 g[0] = x[1];
3889 true
3890 }
3891 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
3892 match m {
3893 SparsityRequest::Structure { irow, jcol } => {
3894 irow[0] = 0;
3895 jcol[0] = 1;
3896 }
3897 SparsityRequest::Values { values } => values[0] = 1.0,
3898 }
3899 true
3900 }
3901 fn eval_h(
3902 &mut self,
3903 _: Option<&[Number]>,
3904 _: bool,
3905 obj_factor: Number,
3906 _: Option<&[Number]>,
3907 _: bool,
3908 m: SparsityRequest<'_>,
3909 ) -> bool {
3910 match m {
3911 SparsityRequest::Structure { irow, jcol } => {
3912 irow[0] = 0;
3913 jcol[0] = 0;
3914 }
3915 SparsityRequest::Values { values } => values[0] = obj_factor,
3916 }
3917 true
3918 }
3919 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3920 }
3921
3922 struct OneIneqLargeOffset;
3931 impl TNLP for OneIneqLargeOffset {
3932 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3933 Some(NlpInfo {
3934 n: 1,
3935 m: 1,
3936 nnz_jac_g: 1,
3937 nnz_h_lag: 0,
3938 index_style: IndexStyle::C,
3939 })
3940 }
3941 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3942 b.x_l[0] = -1.0e19;
3943 b.x_u[0] = 1.0e19;
3944 b.g_l[0] = 4.0e6;
3945 b.g_u[0] = 2.0e19;
3946 true
3947 }
3948 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3949 sp.x[0] = 5000.0;
3950 true
3951 }
3952 fn eval_f(&mut self, _: &[Number], _: bool) -> Option<Number> {
3953 Some(0.0)
3954 }
3955 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
3956 g[0] = 0.0;
3957 true
3958 }
3959 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3960 g[0] = 1000.0 * x[0];
3961 true
3962 }
3963 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
3964 match m {
3965 SparsityRequest::Structure { irow, jcol } => {
3966 irow[0] = 0;
3967 jcol[0] = 0;
3968 }
3969 SparsityRequest::Values { values } => values[0] = 1000.0,
3970 }
3971 true
3972 }
3973 fn eval_h(
3974 &mut self,
3975 _: Option<&[Number]>,
3976 _: bool,
3977 _: Number,
3978 _: Option<&[Number]>,
3979 _: bool,
3980 _: SparsityRequest<'_>,
3981 ) -> bool {
3982 true
3983 }
3984 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3985 }
3986
3987 struct OneEqLargeOffset;
3992 impl TNLP for OneEqLargeOffset {
3993 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3994 Some(NlpInfo {
3995 n: 1,
3996 m: 1,
3997 nnz_jac_g: 1,
3998 nnz_h_lag: 0,
3999 index_style: IndexStyle::C,
4000 })
4001 }
4002 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
4003 b.x_l[0] = -1.0e19;
4004 b.x_u[0] = 1.0e19;
4005 b.g_l[0] = 4.0e6;
4006 b.g_u[0] = 4.0e6;
4007 true
4008 }
4009 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
4010 sp.x[0] = 5000.0;
4011 true
4012 }
4013 fn eval_f(&mut self, _: &[Number], _: bool) -> Option<Number> {
4014 Some(0.0)
4015 }
4016 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
4017 g[0] = 0.0;
4018 true
4019 }
4020 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
4021 g[0] = 1000.0 * x[0];
4022 true
4023 }
4024 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
4025 match m {
4026 SparsityRequest::Structure { irow, jcol } => {
4027 irow[0] = 0;
4028 jcol[0] = 0;
4029 }
4030 SparsityRequest::Values { values } => values[0] = 1000.0,
4031 }
4032 true
4033 }
4034 fn eval_h(
4035 &mut self,
4036 _: Option<&[Number]>,
4037 _: bool,
4038 _: Number,
4039 _: Option<&[Number]>,
4040 _: bool,
4041 _: SparsityRequest<'_>,
4042 ) -> bool {
4043 true
4044 }
4045 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
4046 }
4047
4048 #[test]
4049 fn gradient_based_scaling_scales_d_l_and_d_u() {
4050 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqLargeOffset));
4051 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4052 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
4053
4054 assert_eq!(nlp.d_l().dim(), 1);
4056 let pre = nlp
4057 .d_l()
4058 .as_any()
4059 .downcast_ref::<DenseVector>()
4060 .unwrap()
4061 .values()[0];
4062 assert_eq!(pre, 4.0e6);
4063
4064 nlp.determine_scaling_from_starting_point(
4065 ScalingMethod::GradientBased,
4066 100.0,
4067 1e-8,
4068 0.0,
4069 0.0,
4070 );
4071
4072 let post = nlp
4074 .d_l()
4075 .as_any()
4076 .downcast_ref::<DenseVector>()
4077 .unwrap()
4078 .values()[0];
4079 assert!(
4080 (post - 4.0e5).abs() < 1e-9,
4081 "d_l should be scaled by d_scale=0.1; got {}",
4082 post
4083 );
4084
4085 let x = dense_x(&[5000.0], nlp.x_space());
4088 let mut d = nlp.d_space().make_new_dense();
4089 nlp.eval_d(&x, &mut d);
4090 assert!(
4091 (d.values()[0] - 5.0e5).abs() < 1e-6,
4092 "scaled d(x) mismatch; got {}",
4093 d.values()[0]
4094 );
4095 assert!(
4096 d.values()[0] >= post,
4097 "starting point must be feasible in scaled space"
4098 );
4099 }
4100
4101 struct OneIneqWithObj;
4107 impl TNLP for OneIneqWithObj {
4108 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
4109 Some(NlpInfo {
4110 n: 1,
4111 m: 1,
4112 nnz_jac_g: 1,
4113 nnz_h_lag: 0,
4114 index_style: IndexStyle::C,
4115 })
4116 }
4117 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
4118 b.x_l[0] = -1.0e19;
4119 b.x_u[0] = 1.0e19;
4120 b.g_l[0] = 4.0e6;
4121 b.g_u[0] = 2.0e19;
4122 true
4123 }
4124 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
4125 sp.x[0] = 5000.0;
4126 true
4127 }
4128 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
4129 Some(10.0 * x[0])
4130 }
4131 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
4132 g[0] = 10.0;
4133 true
4134 }
4135 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
4136 g[0] = 1000.0 * x[0];
4137 true
4138 }
4139 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
4140 match m {
4141 SparsityRequest::Structure { irow, jcol } => {
4142 irow[0] = 0;
4143 jcol[0] = 0;
4144 }
4145 SparsityRequest::Values { values } => values[0] = 1000.0,
4146 }
4147 true
4148 }
4149 fn eval_h(
4150 &mut self,
4151 _: Option<&[Number]>,
4152 _: bool,
4153 _: Number,
4154 _: Option<&[Number]>,
4155 _: bool,
4156 _: SparsityRequest<'_>,
4157 ) -> bool {
4158 true
4159 }
4160 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
4161 }
4162
4163 #[test]
4164 fn obj_target_gradient_pins_obj_scale() {
4165 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqWithObj));
4168 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4169 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
4170 nlp.determine_scaling_from_starting_point(
4171 ScalingMethod::GradientBased,
4172 100.0,
4173 1e-8,
4174 0.0, 0.0,
4176 );
4177 assert!(
4178 (nlp.obj_scale_factor() - 1.0).abs() < 1e-12,
4179 "no-target path leaves df=1 when grad < cutoff; got {}",
4180 nlp.obj_scale_factor()
4181 );
4182
4183 let tnlp2: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqWithObj));
4186 let adapter2 = Rc::new(RefCell::new(TNLPAdapter::new(tnlp2).unwrap()));
4187 let mut nlp2 = OrigIpoptNlp::new(Rc::clone(&adapter2), Rc::new(NoScaling)).unwrap();
4188 nlp2.determine_scaling_from_starting_point(
4189 ScalingMethod::GradientBased,
4190 100.0,
4191 1e-8,
4192 1.0,
4193 0.0,
4194 );
4195 assert!(
4196 (nlp2.obj_scale_factor() - 0.1).abs() < 1e-12,
4197 "target_gradient=1, max_grad_f=10 → df=0.1; got {}",
4198 nlp2.obj_scale_factor()
4199 );
4200 }
4201
4202 struct FixedVarShiftsObjGrad;
4212 impl TNLP for FixedVarShiftsObjGrad {
4213 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
4214 Some(NlpInfo {
4215 n: 2,
4216 m: 0,
4217 nnz_jac_g: 0,
4218 nnz_h_lag: 0,
4219 index_style: IndexStyle::C,
4220 })
4221 }
4222 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
4223 b.x_l[0] = -1.0e19;
4224 b.x_u[0] = 1.0e19;
4225 b.x_l[1] = 1000.0;
4226 b.x_u[1] = 1000.0; true
4228 }
4229 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
4230 sp.x[0] = 1.0;
4231 sp.x[1] = 0.0; true
4233 }
4234 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
4235 Some(x[0] * x[1])
4236 }
4237 fn eval_grad_f(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
4238 g[0] = x[1];
4239 g[1] = x[0];
4240 true
4241 }
4242 fn eval_g(&mut self, _: &[Number], _: bool, _: &mut [Number]) -> bool {
4243 true
4244 }
4245 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, _: SparsityRequest<'_>) -> bool {
4246 true
4247 }
4248 fn eval_h(
4249 &mut self,
4250 _: Option<&[Number]>,
4251 _: bool,
4252 _: Number,
4253 _: Option<&[Number]>,
4254 _: bool,
4255 _: SparsityRequest<'_>,
4256 ) -> bool {
4257 true
4258 }
4259 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
4260 }
4261
4262 struct BoxedVar;
4266 impl TNLP for BoxedVar {
4267 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
4268 Some(NlpInfo {
4269 n: 1,
4270 m: 0,
4271 nnz_jac_g: 0,
4272 nnz_h_lag: 0,
4273 index_style: IndexStyle::C,
4274 })
4275 }
4276 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
4277 b.x_l[0] = -1.0;
4278 b.x_u[0] = 2.0;
4279 true
4280 }
4281 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
4282 sp.x[0] = 0.0;
4283 true
4284 }
4285 fn eval_f(&mut self, _: &[Number], _: bool) -> Option<Number> {
4286 Some(0.0)
4287 }
4288 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
4289 g[0] = 0.0;
4290 true
4291 }
4292 fn eval_g(&mut self, _: &[Number], _: bool, _: &mut [Number]) -> bool {
4293 true
4294 }
4295 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, _: SparsityRequest<'_>) -> bool {
4296 true
4297 }
4298 fn eval_h(
4299 &mut self,
4300 _: Option<&[Number]>,
4301 _: bool,
4302 _: Number,
4303 _: Option<&[Number]>,
4304 _: bool,
4305 _: SparsityRequest<'_>,
4306 ) -> bool {
4307 true
4308 }
4309 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
4310 }
4311
4312 fn boxed_var_nlp() -> OrigIpoptNlp {
4313 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(BoxedVar));
4314 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4315 OrigIpoptNlp::new(adapter, Rc::new(NoScaling)).unwrap()
4316 }
4317
4318 fn at(x: Number) -> DenseVector {
4319 let space = DenseVectorSpace::new(1);
4320 let mut v = space.make_new_dense();
4321 v.set_values(&[x]);
4322 v
4323 }
4324
4325 #[test]
4336 fn the_box_violation_abstains_until_the_declared_bounds_are_snapshotted() {
4337 let nlp = boxed_var_nlp();
4338 assert!(
4339 nlp.declared_box_violation(&at(5.0)).is_none(),
4340 "no snapshot means no answer, not a fabricated zero"
4341 );
4342 }
4343
4344 #[test]
4357 fn the_box_violation_measures_the_distance_outside_the_declared_box() {
4358 let mut nlp = boxed_var_nlp();
4359 nlp.snapshot_declared_bounds();
4360 assert_eq!(nlp.declared_box_violation(&at(0.5)), Some(0.0));
4362 assert_eq!(nlp.declared_box_violation(&at(-1.0)), Some(0.0));
4364 assert_eq!(nlp.declared_box_violation(&at(2.0)), Some(0.0));
4365 let over = nlp.declared_box_violation(&at(2.25)).unwrap();
4367 assert!((over - 0.25).abs() < 1e-15, "expected 0.25, got {over}");
4368 let under = nlp.declared_box_violation(&at(-1.5)).unwrap();
4369 assert!((under - 0.5).abs() < 1e-15, "expected 0.5, got {under}");
4370 }
4371
4372 #[test]
4376 fn relaxing_the_bounds_still_snapshots_the_declared_ones_first() {
4377 let mut nlp = boxed_var_nlp();
4378 nlp.relax_bounds(1e-2, 1.0);
4379 let v = nlp.declared_box_violation(&at(2.02)).unwrap();
4384 assert!((v - 0.02).abs() < 1e-12, "expected 0.02, got {v}");
4385 }
4386
4387 #[test]
4388 fn gradient_scaling_lifts_fixed_vars_to_their_value() {
4389 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(FixedVarShiftsObjGrad));
4390 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4391 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
4392
4393 assert_eq!(nlp.n_full_x(), 2);
4395 assert_eq!(nlp.n(), 1);
4396
4397 nlp.determine_scaling_from_starting_point(
4398 ScalingMethod::GradientBased,
4399 100.0,
4400 1e-8,
4401 0.0,
4402 0.0,
4403 );
4404
4405 assert!(
4409 (nlp.obj_scale_factor() - 0.1).abs() < 1e-12,
4410 "fixed var must be lifted before scaling; expected df=0.1, got {}",
4411 nlp.obj_scale_factor()
4412 );
4413 }
4414
4415 #[test]
4416 fn constr_target_gradient_overrides_cutoff_and_clamp() {
4417 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqLargeOffset));
4422 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4423 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
4424 nlp.determine_scaling_from_starting_point(
4425 ScalingMethod::GradientBased,
4426 100.0,
4427 1e-8,
4428 0.0,
4429 50.0,
4430 );
4431 let x = dense_x(&[5000.0], nlp.x_space());
4432 let mut d = nlp.d_space().make_new_dense();
4433 nlp.eval_d(&x, &mut d);
4434 assert!(
4436 (d.values()[0] - 2.5e5).abs() < 1e-6,
4437 "constr target=50 → dd=0.05; scaled d(5000)=2.5e5, got {}",
4438 d.values()[0]
4439 );
4440 }
4441
4442 struct Hs071UserScaled;
4447 impl TNLP for Hs071UserScaled {
4448 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
4449 Hs071::default().get_nlp_info()
4450 }
4451 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
4452 Hs071::default().get_bounds_info(b)
4453 }
4454 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
4455 Hs071::default().get_starting_point(sp)
4456 }
4457 fn eval_f(&mut self, x: &[Number], new_x: bool) -> Option<Number> {
4458 Hs071::default().eval_f(x, new_x)
4459 }
4460 fn eval_grad_f(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
4461 Hs071::default().eval_grad_f(x, new_x, g)
4462 }
4463 fn eval_g(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
4464 Hs071::default().eval_g(x, new_x, g)
4465 }
4466 fn eval_jac_g(
4467 &mut self,
4468 x: Option<&[Number]>,
4469 new_x: bool,
4470 mode: SparsityRequest<'_>,
4471 ) -> bool {
4472 Hs071::default().eval_jac_g(x, new_x, mode)
4473 }
4474 fn eval_h(
4475 &mut self,
4476 x: Option<&[Number]>,
4477 new_x: bool,
4478 obj_factor: Number,
4479 lambda: Option<&[Number]>,
4480 new_lambda: bool,
4481 mode: SparsityRequest<'_>,
4482 ) -> bool {
4483 Hs071::default().eval_h(x, new_x, obj_factor, lambda, new_lambda, mode)
4484 }
4485 fn get_scaling_parameters(&mut self, req: ScalingRequest<'_>) -> bool {
4486 *req.obj_scaling = 2.0;
4487 *req.use_x_scaling = false;
4488 *req.use_g_scaling = true;
4489 req.g_scaling[0] = 0.5;
4491 req.g_scaling[1] = 0.25;
4492 true
4493 }
4494 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
4495 }
4496
4497 #[test]
4498 fn user_scaling_dispatch_applies_obj_and_g_scaling() {
4499 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071UserScaled));
4500 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4501 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
4502 nlp.determine_scaling_from_starting_point(
4503 ScalingMethod::UserScaling,
4504 100.0,
4505 1e-8,
4506 0.0,
4507 0.0,
4508 );
4509
4510 assert!(
4513 (nlp.obj_scale_factor() - 2.0).abs() < 1e-12,
4514 "user obj_scaling=2.0 should be installed; got {}",
4515 nlp.obj_scale_factor()
4516 );
4517
4518 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
4522 let mut c = nlp.c_space().make_new_dense();
4523 nlp.eval_c(&x, &mut c);
4524 assert!(
4527 (c.values()[0] - 3.0).abs() < 1e-9,
4528 "user g_scaling=0.25 on equality → c=3.0; got {}",
4529 c.values()[0]
4530 );
4531
4532 let mut d = nlp.d_space().make_new_dense();
4535 nlp.eval_d(&x, &mut d);
4536 assert!(
4537 (d.values()[0] - 12.5).abs() < 1e-9,
4538 "user g_scaling=0.5 on inequality → d=12.5; got {}",
4539 d.values()[0]
4540 );
4541
4542 let post_d_l = nlp
4545 .d_l()
4546 .as_any()
4547 .downcast_ref::<DenseVector>()
4548 .unwrap()
4549 .values()[0];
4550 assert!(
4551 (post_d_l - 12.5).abs() < 1e-9,
4552 "d_l scaled in step: got {}",
4553 post_d_l
4554 );
4555 }
4556
4557 struct Hs071DeclinesScaling;
4561 impl TNLP for Hs071DeclinesScaling {
4562 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
4563 Hs071::default().get_nlp_info()
4564 }
4565 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
4566 Hs071::default().get_bounds_info(b)
4567 }
4568 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
4569 Hs071::default().get_starting_point(sp)
4570 }
4571 fn eval_f(&mut self, x: &[Number], new_x: bool) -> Option<Number> {
4572 Hs071::default().eval_f(x, new_x)
4573 }
4574 fn eval_grad_f(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
4575 Hs071::default().eval_grad_f(x, new_x, g)
4576 }
4577 fn eval_g(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
4578 Hs071::default().eval_g(x, new_x, g)
4579 }
4580 fn eval_jac_g(
4581 &mut self,
4582 x: Option<&[Number]>,
4583 new_x: bool,
4584 mode: SparsityRequest<'_>,
4585 ) -> bool {
4586 Hs071::default().eval_jac_g(x, new_x, mode)
4587 }
4588 fn eval_h(
4589 &mut self,
4590 x: Option<&[Number]>,
4591 new_x: bool,
4592 obj_factor: Number,
4593 lambda: Option<&[Number]>,
4594 new_lambda: bool,
4595 mode: SparsityRequest<'_>,
4596 ) -> bool {
4597 Hs071::default().eval_h(x, new_x, obj_factor, lambda, new_lambda, mode)
4598 }
4599 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
4600 }
4601
4602 #[test]
4603 fn user_scaling_falls_back_when_tnlp_declines() {
4604 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071DeclinesScaling));
4605 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4606 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
4607 nlp.determine_scaling_from_starting_point(
4608 ScalingMethod::UserScaling,
4609 100.0,
4610 1e-8,
4611 0.0,
4612 0.0,
4613 );
4614 assert!((nlp.obj_scale_factor() - 1.0).abs() < 1e-12);
4617 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
4618 let mut c = nlp.c_space().make_new_dense();
4619 nlp.eval_c(&x, &mut c);
4620 assert_eq!(c.values(), &[12.0], "unscaled equality residual");
4621 }
4622
4623 struct Hs071XScaled(Vec<Number>);
4626 impl TNLP for Hs071XScaled {
4627 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
4628 Hs071::default().get_nlp_info()
4629 }
4630 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
4631 Hs071::default().get_bounds_info(b)
4632 }
4633 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
4634 Hs071::default().get_starting_point(sp)
4635 }
4636 fn eval_f(&mut self, x: &[Number], new_x: bool) -> Option<Number> {
4637 Hs071::default().eval_f(x, new_x)
4638 }
4639 fn eval_grad_f(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
4640 Hs071::default().eval_grad_f(x, new_x, g)
4641 }
4642 fn eval_g(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
4643 Hs071::default().eval_g(x, new_x, g)
4644 }
4645 fn eval_jac_g(
4646 &mut self,
4647 x: Option<&[Number]>,
4648 new_x: bool,
4649 mode: SparsityRequest<'_>,
4650 ) -> bool {
4651 Hs071::default().eval_jac_g(x, new_x, mode)
4652 }
4653 fn eval_h(
4654 &mut self,
4655 x: Option<&[Number]>,
4656 new_x: bool,
4657 obj_factor: Number,
4658 lambda: Option<&[Number]>,
4659 new_lambda: bool,
4660 mode: SparsityRequest<'_>,
4661 ) -> bool {
4662 Hs071::default().eval_h(x, new_x, obj_factor, lambda, new_lambda, mode)
4663 }
4664 fn get_scaling_parameters(&mut self, req: ScalingRequest<'_>) -> bool {
4665 *req.obj_scaling = 2.0;
4666 *req.use_x_scaling = true;
4667 req.x_scaling.copy_from_slice(&self.0);
4668 *req.use_g_scaling = false;
4669 true
4670 }
4671 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
4672 }
4673
4674 fn user_x_scaling_run(factors: &[Number]) -> OrigIpoptNlp {
4675 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071XScaled(factors.to_vec())));
4676 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4677 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
4678 nlp.determine_scaling_from_starting_point(
4679 ScalingMethod::UserScaling,
4680 100.0,
4681 1e-8,
4682 0.0,
4683 0.0,
4684 );
4685 nlp
4686 }
4687
4688 #[test]
4693 fn user_x_scaling_request_is_flagged_not_discarded() {
4694 let nlp = user_x_scaling_run(&[1.0, 1e3, 1.0, 1.0]);
4695 assert!(
4696 nlp.user_x_scaling_rejected(),
4697 "a non-unit x_scaling must be refused, not dropped"
4698 );
4699 assert!((nlp.obj_scale_factor() - 2.0).abs() < 1e-12);
4702 }
4703
4704 #[test]
4707 fn unit_x_scaling_request_is_not_rejected() {
4708 let nlp = user_x_scaling_run(&[1.0, 1.0, 1.0, 1.0]);
4709 assert!(!nlp.user_x_scaling_rejected());
4710 }
4711
4712 #[test]
4713 fn eval_h_with_all_entries_on_fixed_var_does_not_panic() {
4714 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(FixedOnlyHess));
4715 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4716 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
4717
4718 assert_eq!(nlp.h_space().unwrap().nonzeros(), 0);
4721
4722 let x = dense_x(&[0.5], &nlp.x_space().clone());
4723 let yc = dense_x(&[0.0], &nlp.c_space().clone());
4724 let yd = nlp.d_space().make_new_dense();
4725 let h = nlp.eval_h(&x, 1.0, &yc, &yd);
4726 assert_eq!(h.n_rows(), 1);
4727 }
4728
4729 #[test]
4730 fn relax_bounds_widens_uniquely_owned_bounds() {
4731 let (_adapter, mut nlp) = build_orig_nlp();
4734 let x_l_before = nlp.x_l.values().to_vec();
4735 let x_u_before = nlp.x_u.values().to_vec();
4736 nlp.relax_bounds(1e-2, 1.0);
4737 for (b, a) in x_l_before.iter().zip(nlp.x_l.values()) {
4738 assert!(a < b, "x_l should relax downward: {a} !< {b}");
4739 }
4740 for (b, a) in x_u_before.iter().zip(nlp.x_u.values()) {
4741 assert!(a > b, "x_u should relax upward: {a} !> {b}");
4742 }
4743 }
4744
4745 #[test]
4750 fn relax_bounds_is_scale_relative_on_d_and_snapshots_declared() {
4751 let (_adapter, mut nlp) = build_orig_nlp();
4753 assert_eq!(nlp.d_l.values(), &[25.0]);
4754 assert_eq!(nlp.declared_d_bounds(), None, "no snapshot before relax");
4755 nlp.relax_bounds(1e-2, 1.0);
4756 assert_eq!(nlp.d_l.values(), &[25.0 - 0.25]);
4762 let (dl, du) = nlp.declared_d_bounds().expect("snapshotted at relax");
4763 assert_eq!(dl, vec![25.0], "declared bound is the pre-relax value");
4764 assert!(du.is_empty() || du[0] >= 25.0); }
4766
4767 #[test]
4775 fn declared_x_bounds_are_the_pre_relax_box() {
4776 let (_adapter, mut nlp) = build_orig_nlp();
4777 assert_eq!(nlp.declared_x_bounds(), None, "no snapshot before relax");
4778 let x_l_before = nlp.x_l.values().to_vec();
4779 let x_u_before = nlp.x_u.values().to_vec();
4780 nlp.relax_bounds(1e-2, 1.0);
4781 let (xl, xu) = nlp.declared_x_bounds().expect("snapshotted at relax");
4782 assert_eq!(xl, x_l_before, "declared lower box is the pre-relax value");
4783 assert_eq!(xu, x_u_before, "declared upper box is the pre-relax value");
4784 assert!(nlp.x_l.values()[0] < xl[0]);
4787 assert!(nlp.x_u.values()[0] > xu[0]);
4788 }
4789
4790 #[test]
4796 fn declared_c_rhs_is_the_pre_fold_right_hand_side() {
4797 let (_adapter, mut nlp) = build_orig_nlp();
4799 assert_eq!(nlp.declared_c_rhs(), Some(vec![40.0]));
4800 nlp.relax_bounds(1e-2, 1.0);
4801 assert_eq!(
4802 nlp.declared_c_rhs(),
4803 Some(vec![40.0]),
4804 "bound relaxation must not reach the equality RHS"
4805 );
4806 }
4807
4808 #[test]
4813 fn declared_c_rhs_carries_the_row_scaling() {
4814 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneEqLargeOffset));
4815 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
4816 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
4817 assert_eq!(nlp.declared_c_rhs(), Some(vec![4.0e6]));
4818
4819 nlp.determine_scaling_from_starting_point(
4820 ScalingMethod::GradientBased,
4821 100.0,
4822 1e-8,
4823 0.0,
4824 0.0,
4825 );
4826 let rhs = nlp.declared_c_rhs().unwrap();
4828 assert!(
4829 (rhs[0] - 4.0e5).abs() < 1e-9,
4830 "declared RHS should carry c_scale=0.1; got {}",
4831 rhs[0]
4832 );
4833
4834 let x = dense_x(&[5000.0], nlp.x_space());
4837 let mut c = nlp.c_space().make_new_dense();
4838 nlp.eval_c(&x, &mut c);
4839 assert!((c.values()[0] / rhs[0] - 0.25).abs() < 1e-12);
4840 }
4841
4842 #[test]
4843 #[should_panic(expected = "x_l is uniquely owned")]
4844 fn relax_bounds_panics_on_shared_bound_rc() {
4845 let (_adapter, mut nlp) = build_orig_nlp();
4850 let _shared = Rc::clone(&nlp.x_l); nlp.relax_bounds(1e-2, 1.0);
4852 }
4853}