1use crate::ipopt_nlp::{IpoptNlp, Nlp, SplitNames};
61use crate::tnlp::{MetaData, NlpInfo, ScalingRequest, SparsityRequest, StartingPoint, IDX_NAMES};
62use crate::tnlp_adapter::{BoundClassification, TNLPAdapter};
63use pounce_common::cached::Cache;
64use pounce_common::timing::TimingStatistics;
65use pounce_common::types::{Index, Number};
66use pounce_linalg::{
67 DenseVector, DenseVectorSpace, ExpansionMatrix, ExpansionMatrixSpace, GenTMatrix,
68 GenTMatrixSpace, Matrix, SymMatrix, SymTMatrix, SymTMatrixSpace, Vector,
69};
70use std::cell::{Cell, RefCell};
71use std::rc::Rc;
72
73pub trait NlpScaling {
85 fn obj_scaling(&self) -> Number {
90 1.0
91 }
92}
93
94#[derive(Debug, Default, Clone, Copy)]
97pub struct NoScaling;
98impl NlpScaling for NoScaling {}
99
100#[derive(Debug, Clone, Copy)]
105pub struct ConstObjScaling(pub Number);
106impl NlpScaling for ConstObjScaling {
107 fn obj_scaling(&self) -> Number {
108 self.0
109 }
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum ScalingMethod {
116 None,
118 GradientBased,
120 UserScaling,
127}
128
129pub struct OrigIpoptNlp {
132 adapter: Rc<RefCell<TNLPAdapter>>,
134 scaling: Rc<dyn NlpScaling>,
139
140 obj_scale_factor: Cell<Number>,
143 c_scale: RefCell<Option<Vec<Number>>>,
147 d_scale: RefCell<Option<Vec<Number>>>,
149
150 x_space: Rc<DenseVectorSpace>,
152 c_space: Rc<DenseVectorSpace>,
153 d_space: Rc<DenseVectorSpace>,
154 x_l_space: Rc<DenseVectorSpace>,
155 x_u_space: Rc<DenseVectorSpace>,
156 d_l_space: Rc<DenseVectorSpace>,
157 d_u_space: Rc<DenseVectorSpace>,
158 px_l_space: Rc<ExpansionMatrixSpace>,
159 px_u_space: Rc<ExpansionMatrixSpace>,
160 pd_l_space: Rc<ExpansionMatrixSpace>,
161 pd_u_space: Rc<ExpansionMatrixSpace>,
162 jac_c_space: Rc<GenTMatrixSpace>,
163 jac_d_space: Rc<GenTMatrixSpace>,
164 h_space: Option<Rc<SymTMatrixSpace>>,
167
168 x_l: Rc<DenseVector>,
170 x_u: Rc<DenseVector>,
171 d_l: Rc<DenseVector>,
172 d_u: Rc<DenseVector>,
173 c_rhs: Vec<Number>,
180
181 px_l: Rc<dyn Matrix>,
183 px_u: Rc<dyn Matrix>,
184 pd_l: Rc<dyn Matrix>,
185 pd_u: Rc<dyn Matrix>,
186
187 jac_c_entry_in_g: Vec<Index>,
191 jac_d_entry_in_g: Vec<Index>,
193 nnz_jac_g_full: Index,
195
196 nnz_h_lag_full: Index,
200 h_entry_in_full: Vec<Index>,
205
206 f_cache: RefCell<Cache<Number>>,
208 grad_f_cache: RefCell<Cache<Rc<dyn Vector>>>,
209 c_cache: RefCell<Cache<Rc<dyn Vector>>>,
210 d_cache: RefCell<Cache<Rc<dyn Vector>>>,
211 jac_c_cache: RefCell<Cache<Rc<dyn Matrix>>>,
212 jac_d_cache: RefCell<Cache<Rc<dyn Matrix>>>,
213 h_cache: RefCell<Cache<Rc<dyn SymMatrix>>>,
214 full_g_cache: RefCell<Cache<Rc<Vec<Number>>>>,
222 full_jac_g_cache: RefCell<Cache<Rc<Vec<Number>>>>,
223
224 f_evals: RefCell<Index>,
226 grad_f_evals: RefCell<Index>,
227 c_evals: RefCell<Index>,
228 d_evals: RefCell<Index>,
229 jac_c_evals: RefCell<Index>,
230 jac_d_evals: RefCell<Index>,
231 h_evals: RefCell<Index>,
232
233 info: NlpInfo,
236
237 timing: RefCell<Option<Rc<TimingStatistics>>>,
242}
243
244impl std::fmt::Debug for OrigIpoptNlp {
245 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
246 f.debug_struct("OrigIpoptNlp")
247 .field("info", &self.info)
248 .field("f_evals", &*self.f_evals.borrow())
249 .field("grad_f_evals", &*self.grad_f_evals.borrow())
250 .field("c_evals", &*self.c_evals.borrow())
251 .field("d_evals", &*self.d_evals.borrow())
252 .field("jac_c_evals", &*self.jac_c_evals.borrow())
253 .field("jac_d_evals", &*self.jac_d_evals.borrow())
254 .field("h_evals", &*self.h_evals.borrow())
255 .finish_non_exhaustive()
256 }
257}
258
259impl OrigIpoptNlp {
260 pub fn new(
266 adapter: Rc<RefCell<TNLPAdapter>>,
267 scaling: Rc<dyn NlpScaling>,
268 ) -> Result<Self, String> {
269 let (info, classification) = {
271 let a = adapter.borrow();
272 (*a.nlp_info(), a.classification().clone())
273 };
274
275 let n_x_var = classification.n_x_var();
277 let x_space = DenseVectorSpace::new(n_x_var);
278 let c_space = DenseVectorSpace::new(classification.n_c);
279 let d_space = DenseVectorSpace::new(classification.n_d);
280 let x_l_space = DenseVectorSpace::new(classification.n_x_l());
281 let x_u_space = DenseVectorSpace::new(classification.n_x_u());
282 let d_l_space = DenseVectorSpace::new(classification.n_d_l());
283 let d_u_space = DenseVectorSpace::new(classification.n_d_u());
284
285 let px_l_space =
287 ExpansionMatrixSpace::new(n_x_var, classification.n_x_l(), &classification.x_l_map, 0);
288 let px_u_space =
289 ExpansionMatrixSpace::new(n_x_var, classification.n_x_u(), &classification.x_u_map, 0);
290 let pd_l_space = ExpansionMatrixSpace::new(
291 classification.n_d,
292 classification.n_d_l(),
293 &classification.d_l_map,
294 0,
295 );
296 let pd_u_space = ExpansionMatrixSpace::new(
297 classification.n_d,
298 classification.n_d_u(),
299 &classification.d_u_map,
300 0,
301 );
302 let px_l: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&px_l_space)));
303 let px_u: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&px_u_space)));
304 let pd_l: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&pd_l_space)));
305 let pd_u: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&pd_u_space)));
306
307 let n_full_x = classification.n_full_x as usize;
311 let n_full_g = classification.n_full_g as usize;
312 let mut full_x_l = vec![0.0; n_full_x];
313 let mut full_x_u = vec![0.0; n_full_x];
314 let mut full_g_l = vec![0.0; n_full_g];
315 let mut full_g_u = vec![0.0; n_full_g];
316 {
317 let a = adapter.borrow();
318 let mut t = a.tnlp().borrow_mut();
319 let ok = t.get_bounds_info(crate::tnlp::BoundsInfo {
320 x_l: &mut full_x_l,
321 x_u: &mut full_x_u,
322 g_l: &mut full_g_l,
323 g_u: &mut full_g_u,
324 });
325 if !ok {
326 return Err("TNLP::get_bounds_info returned false on second call".into());
327 }
328 }
329
330 let x_l = make_dense_from(&x_l_space, |i| {
331 let var_idx = classification.x_l_map[i] as usize;
333 let full_idx = classification.x_not_fixed_map[var_idx] as usize;
334 full_x_l[full_idx]
335 });
336 let x_u = make_dense_from(&x_u_space, |i| {
337 let var_idx = classification.x_u_map[i] as usize;
338 let full_idx = classification.x_not_fixed_map[var_idx] as usize;
339 full_x_u[full_idx]
340 });
341 let d_l = make_dense_from(&d_l_space, |i| {
342 let d_idx = classification.d_l_map[i] as usize;
344 let full_g_idx = classification.d_map[d_idx] as usize;
345 full_g_l[full_g_idx]
346 });
347 let d_u = make_dense_from(&d_u_space, |i| {
348 let d_idx = classification.d_u_map[i] as usize;
349 let full_g_idx = classification.d_map[d_idx] as usize;
350 full_g_u[full_g_idx]
351 });
352
353 let c_rhs: Vec<Number> = classification
359 .c_map
360 .iter()
361 .map(|&g_idx| full_g_l[g_idx as usize])
362 .collect();
363
364 let mut full_irow = vec![0 as Index; info.nnz_jac_g as usize];
374 let mut full_jcol = vec![0 as Index; info.nnz_jac_g as usize];
375 {
376 let a = adapter.borrow();
377 let mut t = a.tnlp().borrow_mut();
378 let ok = t.eval_jac_g(
379 None,
380 false,
381 SparsityRequest::Structure {
382 irow: &mut full_irow,
383 jcol: &mut full_jcol,
384 },
385 );
386 if !ok {
387 return Err("TNLP::eval_jac_g(Structure) returned false".into());
388 }
389 }
390
391 let mut g_to_c = vec![-1 as Index; n_full_g];
393 for (c_idx, &g_idx) in classification.c_map.iter().enumerate() {
394 g_to_c[g_idx as usize] = c_idx as Index;
395 }
396 let mut g_to_d = vec![-1 as Index; n_full_g];
397 for (d_idx, &g_idx) in classification.d_map.iter().enumerate() {
398 g_to_d[g_idx as usize] = d_idx as Index;
399 }
400
401 let style_offset = match info.index_style {
402 crate::tnlp::IndexStyle::C => 0 as Index,
403 crate::tnlp::IndexStyle::Fortran => 1 as Index,
404 };
405
406 let mut jac_c_irow_1based = Vec::new();
407 let mut jac_c_jcol_1based = Vec::new();
408 let mut jac_c_entry_in_g = Vec::new();
409 let mut jac_d_irow_1based = Vec::new();
410 let mut jac_d_jcol_1based = Vec::new();
411 let mut jac_d_entry_in_g = Vec::new();
412
413 let full_to_var = &classification.full_to_var;
417 for k in 0..info.nnz_jac_g as usize {
418 let g_row_0 = (full_irow[k] - style_offset) as usize;
419 let x_col_0 = (full_jcol[k] - style_offset) as usize;
420 let var_col = full_to_var[x_col_0];
421 if var_col < 0 {
422 continue;
423 }
424 let col_1based = var_col + 1;
426 let c_row = g_to_c[g_row_0];
427 if c_row >= 0 {
428 jac_c_irow_1based.push(c_row + 1);
429 jac_c_jcol_1based.push(col_1based);
430 jac_c_entry_in_g.push(k as Index);
431 } else {
432 let d_row = g_to_d[g_row_0];
433 debug_assert!(d_row >= 0, "g row {g_row_0} is neither in c_map nor d_map");
434 jac_d_irow_1based.push(d_row + 1);
435 jac_d_jcol_1based.push(col_1based);
436 jac_d_entry_in_g.push(k as Index);
437 }
438 }
439
440 let jac_c_space = GenTMatrixSpace::new(
441 classification.n_c,
442 n_x_var,
443 jac_c_irow_1based,
444 jac_c_jcol_1based,
445 );
446 let jac_d_space = GenTMatrixSpace::new(
447 classification.n_d,
448 n_x_var,
449 jac_d_irow_1based,
450 jac_d_jcol_1based,
451 );
452
453 let nnz_h_lag_full = info.nnz_h_lag;
458 let mut h_entry_in_full: Vec<Index> = Vec::new();
459 let h_space = if info.nnz_h_lag > 0 {
460 let mut h_irow = vec![0 as Index; info.nnz_h_lag as usize];
461 let mut h_jcol = vec![0 as Index; info.nnz_h_lag as usize];
462 let supports_h = {
463 let a = adapter.borrow();
464 let mut t = a.tnlp().borrow_mut();
465 t.eval_h(
466 None,
467 false,
468 1.0,
469 None,
470 false,
471 SparsityRequest::Structure {
472 irow: &mut h_irow,
473 jcol: &mut h_jcol,
474 },
475 )
476 };
477 if supports_h {
478 let mut h_irow_1: Vec<Index> = Vec::with_capacity(h_irow.len());
484 let mut h_jcol_1: Vec<Index> = Vec::with_capacity(h_jcol.len());
485 for k in 0..h_irow.len() {
486 let i_full = (h_irow[k] - style_offset) as usize;
487 let j_full = (h_jcol[k] - style_offset) as usize;
488 let i_var = full_to_var[i_full];
489 let j_var = full_to_var[j_full];
490 if i_var < 0 || j_var < 0 {
491 continue;
492 }
493 h_irow_1.push(i_var + 1);
494 h_jcol_1.push(j_var + 1);
495 h_entry_in_full.push(k as Index);
496 }
497 Some(SymTMatrixSpace::new(n_x_var, h_irow_1, h_jcol_1))
498 } else {
499 None
501 }
502 } else {
503 Some(SymTMatrixSpace::new(n_x_var, Vec::new(), Vec::new()))
507 };
508
509 let initial_obj_scal = scaling.obj_scaling();
515 Ok(Self {
516 adapter,
517 scaling,
518 obj_scale_factor: Cell::new(initial_obj_scal),
519 c_scale: RefCell::new(None),
520 d_scale: RefCell::new(None),
521 x_space,
522 c_space,
523 d_space,
524 x_l_space,
525 x_u_space,
526 d_l_space,
527 d_u_space,
528 px_l_space,
529 px_u_space,
530 pd_l_space,
531 pd_u_space,
532 jac_c_space,
533 jac_d_space,
534 h_space,
535 x_l: Rc::new(x_l),
536 x_u: Rc::new(x_u),
537 d_l: Rc::new(d_l),
538 d_u: Rc::new(d_u),
539 c_rhs,
540 px_l,
541 px_u,
542 pd_l,
543 pd_u,
544 jac_c_entry_in_g,
545 jac_d_entry_in_g,
546 nnz_jac_g_full: info.nnz_jac_g,
547 nnz_h_lag_full,
548 h_entry_in_full,
549 f_cache: RefCell::new(Cache::new(1)),
550 grad_f_cache: RefCell::new(Cache::new(1)),
551 c_cache: RefCell::new(Cache::new(1)),
552 d_cache: RefCell::new(Cache::new(1)),
553 jac_c_cache: RefCell::new(Cache::new(1)),
554 jac_d_cache: RefCell::new(Cache::new(1)),
555 h_cache: RefCell::new(Cache::new(1)),
556 full_g_cache: RefCell::new(Cache::new(1)),
557 full_jac_g_cache: RefCell::new(Cache::new(1)),
558 f_evals: RefCell::new(0),
559 grad_f_evals: RefCell::new(0),
560 c_evals: RefCell::new(0),
561 d_evals: RefCell::new(0),
562 jac_c_evals: RefCell::new(0),
563 jac_d_evals: RefCell::new(0),
564 h_evals: RefCell::new(0),
565 info,
566 timing: RefCell::new(None),
567 })
568 }
569
570 pub fn set_timing_stats(&self, t: Rc<TimingStatistics>) {
576 *self.timing.borrow_mut() = Some(t);
577 }
578
579 fn timed_eval<R, F>(&self, pick: fn(&TimingStatistics) -> &pounce_common::TimedTask, f: F) -> R
584 where
585 F: FnOnce() -> R,
586 {
587 let guard = self.timing.borrow();
588 match guard.as_deref() {
589 Some(t) => {
590 let task = pick(t);
591 task.start();
592 t.total_function_evaluation_time.start();
593 let r = f();
594 t.total_function_evaluation_time.end();
595 task.end();
596 r
597 }
598 None => {
599 drop(guard);
600 f()
601 }
602 }
603 }
604
605 pub fn nlp_info(&self) -> &NlpInfo {
608 &self.info
609 }
610 pub fn classification_n_x_var(&self) -> Index {
611 self.x_space.dim()
612 }
613 pub fn x_space(&self) -> &Rc<DenseVectorSpace> {
614 &self.x_space
615 }
616 pub fn c_space(&self) -> &Rc<DenseVectorSpace> {
617 &self.c_space
618 }
619 pub fn d_space(&self) -> &Rc<DenseVectorSpace> {
620 &self.d_space
621 }
622 pub fn x_l_space(&self) -> &Rc<DenseVectorSpace> {
623 &self.x_l_space
624 }
625 pub fn x_u_space(&self) -> &Rc<DenseVectorSpace> {
626 &self.x_u_space
627 }
628 pub fn d_l_space(&self) -> &Rc<DenseVectorSpace> {
629 &self.d_l_space
630 }
631 pub fn d_u_space(&self) -> &Rc<DenseVectorSpace> {
632 &self.d_u_space
633 }
634 pub fn px_l_space(&self) -> &Rc<ExpansionMatrixSpace> {
635 &self.px_l_space
636 }
637 pub fn px_u_space(&self) -> &Rc<ExpansionMatrixSpace> {
638 &self.px_u_space
639 }
640 pub fn pd_l_space(&self) -> &Rc<ExpansionMatrixSpace> {
641 &self.pd_l_space
642 }
643 pub fn pd_u_space(&self) -> &Rc<ExpansionMatrixSpace> {
644 &self.pd_u_space
645 }
646 pub fn jac_c_space(&self) -> &Rc<GenTMatrixSpace> {
647 &self.jac_c_space
648 }
649 pub fn jac_d_space(&self) -> &Rc<GenTMatrixSpace> {
650 &self.jac_d_space
651 }
652 pub fn h_space(&self) -> Option<&Rc<SymTMatrixSpace>> {
653 self.h_space.as_ref()
654 }
655
656 pub fn obj_scale_factor(&self) -> Number {
659 self.obj_scale_factor.get()
660 }
661
662 pub fn relax_bounds(&mut self, bound_relax_factor: Number, constr_viol_tol: Number) {
674 if bound_relax_factor <= 0.0 {
675 return;
676 }
677 let relax = bound_relax_factor.abs();
678 let cap = constr_viol_tol;
679 let apply = |v: &mut DenseVector, sign: Number| {
680 let xs = v.values_mut();
681 for x in xs.iter_mut() {
682 let delta = (relax * x.abs().max(1.0)).min(cap);
683 *x += sign * delta;
684 }
685 };
686 apply(
693 Rc::get_mut(&mut self.x_l).expect("relax_bounds: x_l is uniquely owned"),
694 -1.0,
695 );
696 apply(
697 Rc::get_mut(&mut self.x_u).expect("relax_bounds: x_u is uniquely owned"),
698 1.0,
699 );
700 apply(
701 Rc::get_mut(&mut self.d_l).expect("relax_bounds: d_l is uniquely owned"),
702 -1.0,
703 );
704 apply(
705 Rc::get_mut(&mut self.d_u).expect("relax_bounds: d_u is uniquely owned"),
706 1.0,
707 );
708 }
709
710 pub fn determine_scaling_from_starting_point(
732 &mut self,
733 method: ScalingMethod,
734 max_gradient: Number,
735 min_value: Number,
736 obj_target_gradient: Number,
737 constr_target_gradient: Number,
738 ) {
739 let user_obj_factor = self.scaling.obj_scaling();
742 if matches!(method, ScalingMethod::None) {
743 self.obj_scale_factor.set(user_obj_factor);
744 *self.c_scale.borrow_mut() = None;
745 *self.d_scale.borrow_mut() = None;
746 self.invalidate_eval_caches();
747 return;
748 }
749
750 let cls = self.adapter.borrow().classification().clone();
752 let n_full_x = cls.n_full_x as usize;
753 let n_full_g = cls.n_full_g as usize;
754 let mut full_x = vec![0.0; n_full_x];
755 let mut full_z_l = vec![0.0; n_full_x];
756 let mut full_z_u = vec![0.0; n_full_x];
757 let mut full_lambda = vec![0.0; n_full_g];
758 let starting_ok = {
759 let a = self.adapter.borrow();
760 let mut t = a.tnlp().borrow_mut();
761 t.get_starting_point(StartingPoint {
762 init_x: true,
763 x: &mut full_x,
764 init_z: false,
765 z_l: &mut full_z_l,
766 z_u: &mut full_z_u,
767 init_lambda: false,
768 lambda: &mut full_lambda,
769 })
770 };
771 if !starting_ok {
772 self.obj_scale_factor.set(user_obj_factor);
774 *self.c_scale.borrow_mut() = None;
775 *self.d_scale.borrow_mut() = None;
776 self.invalidate_eval_caches();
777 return;
778 }
779
780 for (i, &full_idx) in cls.x_fixed_map.iter().enumerate() {
793 full_x[full_idx as usize] = cls.x_fixed_vals[i];
794 }
795
796 match method {
797 ScalingMethod::None => unreachable!("handled above"),
798 ScalingMethod::GradientBased => {
799 self.scale_gradient_based(
800 &cls,
801 &full_x,
802 user_obj_factor,
803 max_gradient,
804 min_value,
805 obj_target_gradient,
806 constr_target_gradient,
807 );
808 }
809 ScalingMethod::UserScaling => {
810 let applied = self.scale_user_supplied(&cls, user_obj_factor, min_value);
811 if !applied {
812 self.obj_scale_factor.set(user_obj_factor);
816 *self.c_scale.borrow_mut() = None;
817 *self.d_scale.borrow_mut() = None;
818 }
819 }
820 }
821
822 self.apply_d_scale_to_bounds();
825
826 self.invalidate_eval_caches();
829 }
830
831 fn scale_gradient_based(
834 &self,
835 cls: &BoundClassification,
836 full_x: &[Number],
837 user_obj_factor: Number,
838 max_gradient: Number,
839 min_value: Number,
840 obj_target_gradient: Number,
841 constr_target_gradient: Number,
842 ) {
843 let n_full_x = cls.n_full_x as usize;
844 let n_full_g = cls.n_full_g as usize;
845
846 let mut full_grad_f = vec![0.0; n_full_x];
848 let grad_ok = {
849 let a = self.adapter.borrow();
850 let mut t = a.tnlp().borrow_mut();
851 t.eval_grad_f(full_x, true, &mut full_grad_f)
852 };
853 let mut df = 1.0;
854 if grad_ok {
855 let mut max_grad_f: Number = 0.0;
858 for &full_idx in cls.x_not_fixed_map.iter() {
859 let v = full_grad_f[full_idx as usize].abs();
860 if v > max_grad_f {
861 max_grad_f = v;
862 }
863 }
864 if obj_target_gradient > 0.0 && max_grad_f > 0.0 {
865 df = obj_target_gradient / max_grad_f;
868 } else if max_grad_f > max_gradient {
869 df = max_gradient / max_grad_f;
870 }
871 if df < min_value {
872 df = min_value;
873 }
874 }
875 self.obj_scale_factor.set(df * user_obj_factor);
876
877 if cls.n_full_g == 0 {
879 *self.c_scale.borrow_mut() = None;
880 *self.d_scale.borrow_mut() = None;
881 return;
882 }
883 let mut full_jac_vals = vec![0.0; self.nnz_jac_g_full as usize];
885 let jac_ok = {
886 let a = self.adapter.borrow();
887 let mut t = a.tnlp().borrow_mut();
888 t.eval_jac_g(
889 Some(full_x),
890 true,
891 SparsityRequest::Values {
892 values: &mut full_jac_vals,
893 },
894 )
895 };
896 if !jac_ok {
897 *self.c_scale.borrow_mut() = None;
898 *self.d_scale.borrow_mut() = None;
899 return;
900 }
901 let mut full_irow = vec![0 as Index; self.nnz_jac_g_full as usize];
903 let mut full_jcol = vec![0 as Index; self.nnz_jac_g_full as usize];
904 let _ = {
905 let a = self.adapter.borrow();
906 let mut t = a.tnlp().borrow_mut();
907 t.eval_jac_g(
908 None,
909 false,
910 SparsityRequest::Structure {
911 irow: &mut full_irow,
912 jcol: &mut full_jcol,
913 },
914 )
915 };
916 let style_offset: Index = match self.info.index_style {
917 crate::tnlp::IndexStyle::C => 0,
918 crate::tnlp::IndexStyle::Fortran => 1,
919 };
920 let mut g_to_c = vec![-1 as Index; n_full_g];
922 for (c_idx, &g_idx) in cls.c_map.iter().enumerate() {
923 g_to_c[g_idx as usize] = c_idx as Index;
924 }
925 let mut g_to_d = vec![-1 as Index; n_full_g];
926 for (d_idx, &g_idx) in cls.d_map.iter().enumerate() {
927 g_to_d[g_idx as usize] = d_idx as Index;
928 }
929 let n_c = cls.n_c as usize;
930 let n_d = cls.n_d as usize;
931 let dbl_min = Number::MIN_POSITIVE;
933 let mut c_row_max: Vec<Number> = vec![dbl_min; n_c];
934 let mut d_row_max: Vec<Number> = vec![dbl_min; n_d];
935 for k in 0..self.nnz_jac_g_full as usize {
936 let g_row_0 = (full_irow[k] - style_offset) as usize;
937 let v = full_jac_vals[k].abs();
938 let cr = g_to_c[g_row_0];
939 if cr >= 0 {
940 let row = cr as usize;
941 if v > c_row_max[row] {
942 c_row_max[row] = v;
943 }
944 } else {
945 let dr = g_to_d[g_row_0];
946 if dr >= 0 {
947 let row = dr as usize;
948 if v > d_row_max[row] {
949 d_row_max[row] = v;
950 }
951 }
952 }
953 }
954
955 let row_max_to_scale = |row_max: Number| -> Number {
956 let mut s = if constr_target_gradient > 0.0 {
961 constr_target_gradient / row_max
962 } else {
963 let raw = max_gradient / row_max;
964 if raw > 1.0 {
965 1.0
966 } else {
967 raw
968 }
969 };
970 if s < min_value {
971 s = min_value;
972 }
973 s
974 };
975 let any_row_above = |rows: &[Number]| -> bool {
976 constr_target_gradient > 0.0 || rows.iter().any(|&v| v > max_gradient)
977 };
978
979 if n_c > 0 && any_row_above(&c_row_max) {
980 let dc: Vec<Number> = c_row_max.iter().map(|&v| row_max_to_scale(v)).collect();
981 *self.c_scale.borrow_mut() = Some(dc);
982 } else {
983 *self.c_scale.borrow_mut() = None;
984 }
985
986 if n_d > 0 && any_row_above(&d_row_max) {
987 let dd: Vec<Number> = d_row_max.iter().map(|&v| row_max_to_scale(v)).collect();
988 *self.d_scale.borrow_mut() = Some(dd);
989 } else {
990 *self.d_scale.borrow_mut() = None;
991 }
992 }
993
994 fn scale_user_supplied(
1006 &self,
1007 cls: &BoundClassification,
1008 user_obj_factor: Number,
1009 min_value: Number,
1010 ) -> bool {
1011 let n_full_x = cls.n_full_x as usize;
1012 let n_full_g = cls.n_full_g as usize;
1013 let mut obj_scaling: Number = 1.0;
1014 let mut use_x_scaling = false;
1015 let mut x_scaling = vec![1.0; n_full_x];
1016 let mut use_g_scaling = false;
1017 let mut g_scaling = vec![1.0; n_full_g];
1018 let ok = {
1019 let a = self.adapter.borrow();
1020 let mut t = a.tnlp().borrow_mut();
1021 t.get_scaling_parameters(ScalingRequest {
1022 obj_scaling: &mut obj_scaling,
1023 use_x_scaling: &mut use_x_scaling,
1024 x_scaling: &mut x_scaling,
1025 use_g_scaling: &mut use_g_scaling,
1026 g_scaling: &mut g_scaling,
1027 })
1028 };
1029 if !ok {
1030 return false;
1031 }
1032
1033 let mut df = obj_scaling;
1037 if df.abs() < min_value {
1038 df = df.signum().max(0.0).max(1.0) * min_value;
1041 }
1042 self.obj_scale_factor.set(df * user_obj_factor);
1043
1044 if use_g_scaling && g_scaling.len() == n_full_g {
1046 let n_c = cls.n_c as usize;
1047 let n_d = cls.n_d as usize;
1048 let mut dc = vec![1.0; n_c];
1049 for (c_idx, &g_idx) in cls.c_map.iter().enumerate() {
1050 let s = g_scaling[g_idx as usize];
1051 dc[c_idx] = if s < min_value { min_value } else { s };
1052 }
1053 let mut dd = vec![1.0; n_d];
1054 for (d_idx, &g_idx) in cls.d_map.iter().enumerate() {
1055 let s = g_scaling[g_idx as usize];
1056 dd[d_idx] = if s < min_value { min_value } else { s };
1057 }
1058 let nontrivial_c = dc.iter().any(|&s| s != 1.0);
1061 *self.c_scale.borrow_mut() = if nontrivial_c && n_c > 0 {
1062 Some(dc)
1063 } else {
1064 None
1065 };
1066 let nontrivial_d = dd.iter().any(|&s| s != 1.0);
1067 *self.d_scale.borrow_mut() = if nontrivial_d && n_d > 0 {
1068 Some(dd)
1069 } else {
1070 None
1071 };
1072 } else {
1073 *self.c_scale.borrow_mut() = None;
1074 *self.d_scale.borrow_mut() = None;
1075 }
1076 let _ = use_x_scaling;
1078 true
1079 }
1080
1081 fn apply_d_scale_to_bounds(&mut self) {
1086 let cls = self.adapter.borrow().classification().clone();
1087 if let Some(dd) = self.d_scale.borrow().as_ref() {
1088 if let Some(d_l) = Rc::get_mut(&mut self.d_l) {
1089 let xs = d_l.values_mut();
1090 for (i, slot) in xs.iter_mut().enumerate() {
1091 let d_idx = cls.d_l_map[i] as usize;
1092 *slot *= dd[d_idx];
1093 }
1094 }
1095 if let Some(d_u) = Rc::get_mut(&mut self.d_u) {
1096 let xs = d_u.values_mut();
1097 for (i, slot) in xs.iter_mut().enumerate() {
1098 let d_idx = cls.d_u_map[i] as usize;
1099 *slot *= dd[d_idx];
1100 }
1101 }
1102 }
1103 }
1104
1105 fn invalidate_eval_caches(&self) {
1106 self.f_cache.borrow_mut().clear();
1107 self.grad_f_cache.borrow_mut().clear();
1108 self.c_cache.borrow_mut().clear();
1109 self.d_cache.borrow_mut().clear();
1110 self.jac_c_cache.borrow_mut().clear();
1111 self.jac_d_cache.borrow_mut().clear();
1112 self.h_cache.borrow_mut().clear();
1113 }
1114
1115 pub fn f_evals(&self) -> Index {
1116 *self.f_evals.borrow()
1117 }
1118 pub fn grad_f_evals(&self) -> Index {
1119 *self.grad_f_evals.borrow()
1120 }
1121 pub fn c_evals(&self) -> Index {
1122 *self.c_evals.borrow()
1123 }
1124 pub fn d_evals(&self) -> Index {
1125 *self.d_evals.borrow()
1126 }
1127 pub fn jac_c_evals(&self) -> Index {
1128 *self.jac_c_evals.borrow()
1129 }
1130 pub fn jac_d_evals(&self) -> Index {
1131 *self.jac_d_evals.borrow()
1132 }
1133 pub fn h_evals(&self) -> Index {
1134 *self.h_evals.borrow()
1135 }
1136
1137 pub fn lift_x_to_full(&self, x: &dyn Vector) -> Vec<Number> {
1143 let Some(dx) = x.as_any().downcast_ref::<DenseVector>() else {
1144 panic!("OrigIpoptNlp expects DenseVector for x");
1145 };
1146 let a = self.adapter.borrow();
1147 let cls = a.classification();
1148 let mut full = vec![0.0; cls.n_full_x as usize];
1149 let vals = dx.expanded_values();
1150 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
1151 full[full_idx as usize] = vals[var_idx];
1152 }
1153 for (i, &full_idx) in cls.x_fixed_map.iter().enumerate() {
1154 full[full_idx as usize] = cls.x_fixed_vals[i];
1155 }
1156 full
1157 }
1158
1159 pub fn finalize_solution_lambda(&self, y_c: &dyn Vector, y_d: &dyn Vector) -> Vec<Number> {
1171 let cls = self.adapter.borrow().classification().clone();
1172 let mut lambda = self.pack_lambda_for_user(y_c, y_d, &cls);
1173 let obj_scal = self.obj_scale_factor.get();
1174 if obj_scal != 0.0 && obj_scal != 1.0 {
1175 let inv = 1.0 / obj_scal;
1176 for v in lambda.iter_mut() {
1177 *v *= inv;
1178 }
1179 }
1180 lambda
1181 }
1182
1183 pub fn finalize_solution_z_l(&self, z_l: &dyn Vector) -> Vec<Number> {
1191 let cls = self.adapter.borrow().classification().clone();
1192 let n_full_x = cls.n_full_x as usize;
1193 let mut full_z_l = vec![0.0; n_full_x];
1194 let n_x_l = self.x_l.dim() as usize;
1195 if n_x_l == 0 {
1196 return full_z_l;
1197 }
1198 let Some(dz) = z_l.as_any().downcast_ref::<DenseVector>() else {
1199 panic!("OrigIpoptNlp::finalize_solution_z_l expects DenseVector");
1200 };
1201 let vals = dz.expanded_values();
1202 let obj_scal = self.obj_scale_factor.get();
1203 let inv = if obj_scal == 0.0 { 1.0 } else { 1.0 / obj_scal };
1204 for i in 0..n_x_l {
1205 let var_idx = cls.x_l_map[i] as usize;
1206 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1207 full_z_l[full_idx] = vals[i] * inv;
1208 }
1209 full_z_l
1210 }
1211
1212 pub fn finalize_solution_z_u(&self, z_u: &dyn Vector) -> Vec<Number> {
1215 let cls = self.adapter.borrow().classification().clone();
1216 let n_full_x = cls.n_full_x as usize;
1217 let mut full_z_u = vec![0.0; n_full_x];
1218 let n_x_u = self.x_u.dim() as usize;
1219 if n_x_u == 0 {
1220 return full_z_u;
1221 }
1222 let Some(dz) = z_u.as_any().downcast_ref::<DenseVector>() else {
1223 panic!("OrigIpoptNlp::finalize_solution_z_u expects DenseVector");
1224 };
1225 let vals = dz.expanded_values();
1226 let obj_scal = self.obj_scale_factor.get();
1227 let inv = if obj_scal == 0.0 { 1.0 } else { 1.0 / obj_scal };
1228 for i in 0..n_x_u {
1229 let var_idx = cls.x_u_map[i] as usize;
1230 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1231 full_z_u[full_idx] = vals[i] * inv;
1232 }
1233 full_z_u
1234 }
1235
1236 pub fn pack_lambda_for_user(
1246 &self,
1247 y_c: &dyn Vector,
1248 y_d: &dyn Vector,
1249 cls: &BoundClassification,
1250 ) -> Vec<Number> {
1251 let mut lambda = vec![0.0; cls.n_full_g as usize];
1252 if cls.n_c > 0 {
1253 let Some(dy) = y_c.as_any().downcast_ref::<DenseVector>() else {
1254 panic!("OrigIpoptNlp expects DenseVector for y_c");
1255 };
1256 let vals = dy.expanded_values();
1257 let cs = self.c_scale.borrow();
1258 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1259 lambda[g_idx as usize] = match cs.as_ref() {
1260 Some(v) => vals[i] * v[i],
1261 None => vals[i],
1262 };
1263 }
1264 }
1265 if cls.n_d > 0 {
1266 let Some(dy) = y_d.as_any().downcast_ref::<DenseVector>() else {
1267 panic!("OrigIpoptNlp expects DenseVector for y_d");
1268 };
1269 let vals = dy.expanded_values();
1270 let ds = self.d_scale.borrow();
1271 for (i, &g_idx) in cls.d_map.iter().enumerate() {
1272 lambda[g_idx as usize] = match ds.as_ref() {
1273 Some(v) => vals[i] * v[i],
1274 None => vals[i],
1275 };
1276 }
1277 }
1278 lambda
1279 }
1280
1281 #[allow(clippy::too_many_arguments)]
1291 pub fn initialize_starting_point(
1292 &mut self,
1293 x: &mut DenseVector,
1294 init_x: bool,
1295 y_c: &mut DenseVector,
1296 init_y_c: bool,
1297 y_d: &mut DenseVector,
1298 init_y_d: bool,
1299 z_l: &mut DenseVector,
1300 init_z_l: bool,
1301 z_u: &mut DenseVector,
1302 init_z_u: bool,
1303 ) -> bool {
1304 let n_full_x = self.adapter.borrow().classification().n_full_x as usize;
1305 let n_full_g = self.adapter.borrow().classification().n_full_g as usize;
1306 let n_x_l = self.x_l.dim() as usize;
1307 let n_x_u = self.x_u.dim() as usize;
1308
1309 let mut full_x = vec![0.0; n_full_x];
1310 let mut full_z_l = vec![0.0; n_full_x];
1311 let mut full_z_u = vec![0.0; n_full_x];
1312 let mut full_lambda = vec![0.0; n_full_g];
1313
1314 let ok = {
1315 let a = self.adapter.borrow();
1316 let mut t = a.tnlp().borrow_mut();
1317 t.get_starting_point(StartingPoint {
1318 init_x,
1319 x: &mut full_x,
1320 init_z: init_z_l || init_z_u,
1321 z_l: &mut full_z_l,
1322 z_u: &mut full_z_u,
1323 init_lambda: init_y_c || init_y_d,
1324 lambda: &mut full_lambda,
1325 })
1326 };
1327 if !ok {
1328 return false;
1329 }
1330
1331 let cls = self.adapter.borrow().classification().clone();
1332 let obj_scal = self.obj_scale_factor.get();
1333 let c_scale = self.c_scale.borrow();
1334 let d_scale = self.d_scale.borrow();
1335
1336 if init_x {
1338 let xs = x.values_mut();
1339 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
1340 xs[var_idx] = full_x[full_idx as usize];
1341 }
1342 }
1343 if init_y_c && cls.n_c > 0 {
1349 let yc = y_c.values_mut();
1350 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1351 let cs = c_scale.as_ref().map(|v| v[i]).unwrap_or(1.0);
1352 yc[i] = full_lambda[g_idx as usize] / cs * obj_scal;
1353 }
1354 }
1355 if init_y_d && cls.n_d > 0 {
1356 let yd = y_d.values_mut();
1357 for (i, &g_idx) in cls.d_map.iter().enumerate() {
1358 let ds = d_scale.as_ref().map(|v| v[i]).unwrap_or(1.0);
1359 yd[i] = full_lambda[g_idx as usize] / ds * obj_scal;
1360 }
1361 }
1362 if init_z_l && n_x_l > 0 {
1364 let zl = z_l.values_mut();
1365 for (i, slot) in zl.iter_mut().enumerate().take(n_x_l) {
1366 let var_idx = cls.x_l_map[i] as usize;
1367 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1368 *slot = full_z_l[full_idx] * obj_scal;
1369 }
1370 }
1371 if init_z_u && n_x_u > 0 {
1372 let zu = z_u.values_mut();
1373 for (i, slot) in zu.iter_mut().enumerate().take(n_x_u) {
1374 let var_idx = cls.x_u_map[i] as usize;
1375 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1376 *slot = full_z_u[full_idx] * obj_scal;
1377 }
1378 }
1379 true
1380 }
1381
1382 fn eval_f_internal(&self, x: &dyn Vector) -> Number {
1385 if let Some(v) = self.f_cache.borrow().get_1dep(x.as_tagged()) {
1386 return v;
1387 }
1388 *self.f_evals.borrow_mut() += 1;
1389 let full_x = self.lift_x_to_full(x);
1390 let unscaled = {
1391 let a = self.adapter.borrow();
1392 let mut t = a.tnlp().borrow_mut();
1393 t.eval_f(&full_x, true).unwrap_or(f64::NAN)
1398 };
1399 let scaled = unscaled * self.obj_scale_factor.get();
1400 self.f_cache.borrow_mut().add_1dep(scaled, x.as_tagged());
1401 scaled
1402 }
1403
1404 fn eval_grad_f_internal(&self, x: &dyn Vector) -> Rc<dyn Vector> {
1405 if let Some(v) = self.grad_f_cache.borrow().get_1dep(x.as_tagged()) {
1406 return v;
1407 }
1408 *self.grad_f_evals.borrow_mut() += 1;
1409 let full_x = self.lift_x_to_full(x);
1410 let mut full_g = vec![0.0; full_x.len()];
1411 let ok = {
1412 let a = self.adapter.borrow();
1413 let mut t = a.tnlp().borrow_mut();
1414 t.eval_grad_f(&full_x, true, &mut full_g)
1415 };
1416 if !ok {
1419 full_g.fill(f64::NAN);
1420 }
1421 let cls = self.adapter.borrow().classification().clone();
1423 let mut g_compressed = self.x_space.make_new_dense();
1424 let obj_scal = self.obj_scale_factor.get();
1425 {
1426 let gv = g_compressed.values_mut();
1427 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
1428 gv[var_idx] = full_g[full_idx as usize] * obj_scal;
1429 }
1430 }
1431 let result: Rc<dyn Vector> = Rc::new(g_compressed);
1432 self.grad_f_cache
1433 .borrow_mut()
1434 .add_1dep(Rc::clone(&result), x.as_tagged());
1435 result
1436 }
1437
1438 fn full_g(&self, x: &dyn Vector) -> Rc<Vec<Number>> {
1444 if let Some(v) = self.full_g_cache.borrow().get_1dep(x.as_tagged()) {
1445 return v;
1446 }
1447 let n_full_g = self.adapter.borrow().classification().n_full_g as usize;
1448 let full_x = self.lift_x_to_full(x);
1449 let mut full_g = vec![0.0; n_full_g];
1450 let ok = {
1451 let a = self.adapter.borrow();
1452 let mut t = a.tnlp().borrow_mut();
1453 t.eval_g(&full_x, true, &mut full_g)
1454 };
1455 if !ok {
1456 full_g.fill(f64::NAN);
1457 }
1458 let result = Rc::new(full_g);
1459 self.full_g_cache
1460 .borrow_mut()
1461 .add_1dep(Rc::clone(&result), x.as_tagged());
1462 result
1463 }
1464
1465 fn full_jac_g(&self, x: &dyn Vector) -> Rc<Vec<Number>> {
1470 if let Some(v) = self.full_jac_g_cache.borrow().get_1dep(x.as_tagged()) {
1471 return v;
1472 }
1473 let mut full_vals = vec![0.0; self.nnz_jac_g_full as usize];
1474 let full_x = self.lift_x_to_full(x);
1475 let ok = {
1476 let a = self.adapter.borrow();
1477 let mut t = a.tnlp().borrow_mut();
1478 t.eval_jac_g(
1479 Some(&full_x),
1480 true,
1481 SparsityRequest::Values {
1482 values: &mut full_vals,
1483 },
1484 )
1485 };
1486 if !ok {
1487 full_vals.fill(f64::NAN);
1488 }
1489 let result = Rc::new(full_vals);
1490 self.full_jac_g_cache
1491 .borrow_mut()
1492 .add_1dep(Rc::clone(&result), x.as_tagged());
1493 result
1494 }
1495
1496 fn eval_c_internal(&self, x: &dyn Vector) -> Rc<dyn Vector> {
1497 let cls = self.adapter.borrow().classification().clone();
1498 if cls.n_c == 0 {
1499 if let Some(v) = self.c_cache.borrow().get_1dep(x.as_tagged()) {
1501 return v;
1502 }
1503 let v = self.c_space.make_new_dense();
1504 let result: Rc<dyn Vector> = Rc::new(v);
1505 self.c_cache
1506 .borrow_mut()
1507 .add_1dep(Rc::clone(&result), x.as_tagged());
1508 return result;
1509 }
1510 if let Some(v) = self.c_cache.borrow().get_1dep(x.as_tagged()) {
1511 return v;
1512 }
1513 *self.c_evals.borrow_mut() += 1;
1514 let full_g = self.full_g(x);
1519 let mut c = self.c_space.make_new_dense();
1520 {
1528 let cv = c.values_mut();
1529 let cs = self.c_scale.borrow();
1530 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1531 let raw = full_g[g_idx as usize] - self.c_rhs[i];
1532 cv[i] = match cs.as_ref() {
1533 Some(v) => raw * v[i],
1534 None => raw,
1535 };
1536 }
1537 }
1538 let result: Rc<dyn Vector> = Rc::new(c);
1539 self.c_cache
1540 .borrow_mut()
1541 .add_1dep(Rc::clone(&result), x.as_tagged());
1542 result
1543 }
1544
1545 fn eval_d_internal(&self, x: &dyn Vector) -> Rc<dyn Vector> {
1546 let cls = self.adapter.borrow().classification().clone();
1547 if cls.n_d == 0 {
1548 if let Some(v) = self.d_cache.borrow().get_1dep(x.as_tagged()) {
1549 return v;
1550 }
1551 let v = self.d_space.make_new_dense();
1552 let result: Rc<dyn Vector> = Rc::new(v);
1553 self.d_cache
1554 .borrow_mut()
1555 .add_1dep(Rc::clone(&result), x.as_tagged());
1556 return result;
1557 }
1558 if let Some(v) = self.d_cache.borrow().get_1dep(x.as_tagged()) {
1559 return v;
1560 }
1561 *self.d_evals.borrow_mut() += 1;
1562 let full_g = self.full_g(x);
1564 let mut d = self.d_space.make_new_dense();
1565 {
1566 let dv = d.values_mut();
1567 let ds = self.d_scale.borrow();
1568 for (i, &g_idx) in cls.d_map.iter().enumerate() {
1569 let raw = full_g[g_idx as usize];
1570 dv[i] = match ds.as_ref() {
1571 Some(v) => raw * v[i],
1572 None => raw,
1573 };
1574 }
1575 }
1576 let result: Rc<dyn Vector> = Rc::new(d);
1577 self.d_cache
1578 .borrow_mut()
1579 .add_1dep(Rc::clone(&result), x.as_tagged());
1580 result
1581 }
1582
1583 fn eval_jac_c_internal(&self, x: &dyn Vector) -> Rc<dyn Matrix> {
1584 if let Some(m) = self.jac_c_cache.borrow().get_1dep(x.as_tagged()) {
1585 return m;
1586 }
1587 *self.jac_c_evals.borrow_mut() += 1;
1588 let full_vals = self.full_jac_g(x);
1592 let mut jac_c = GenTMatrix::new(Rc::clone(&self.jac_c_space));
1593 {
1594 let cs = self.c_scale.borrow();
1595 let irows = self.jac_c_space.irows().to_vec();
1596 let vs = jac_c.values_mut();
1597 for (k, &src) in self.jac_c_entry_in_g.iter().enumerate() {
1598 let raw = full_vals[src as usize];
1599 vs[k] = match cs.as_ref() {
1600 Some(v) => raw * v[(irows[k] - 1) as usize],
1602 None => raw,
1603 };
1604 }
1605 }
1606 let result: Rc<dyn Matrix> = Rc::new(jac_c);
1607 self.jac_c_cache
1608 .borrow_mut()
1609 .add_1dep(Rc::clone(&result), x.as_tagged());
1610 result
1611 }
1612
1613 fn eval_jac_d_internal(&self, x: &dyn Vector) -> Rc<dyn Matrix> {
1614 if let Some(m) = self.jac_d_cache.borrow().get_1dep(x.as_tagged()) {
1615 return m;
1616 }
1617 *self.jac_d_evals.borrow_mut() += 1;
1618 let full_vals = self.full_jac_g(x);
1621 let mut jac_d = GenTMatrix::new(Rc::clone(&self.jac_d_space));
1622 {
1623 let ds = self.d_scale.borrow();
1624 let irows = self.jac_d_space.irows().to_vec();
1625 let vs = jac_d.values_mut();
1626 for (k, &src) in self.jac_d_entry_in_g.iter().enumerate() {
1627 let raw = full_vals[src as usize];
1628 vs[k] = match ds.as_ref() {
1629 Some(v) => raw * v[(irows[k] - 1) as usize],
1630 None => raw,
1631 };
1632 }
1633 }
1634 let result: Rc<dyn Matrix> = Rc::new(jac_d);
1635 self.jac_d_cache
1636 .borrow_mut()
1637 .add_1dep(Rc::clone(&result), x.as_tagged());
1638 result
1639 }
1640
1641 fn eval_h_internal(
1642 &self,
1643 x: &dyn Vector,
1644 obj_factor: Number,
1645 y_c: &dyn Vector,
1646 y_d: &dyn Vector,
1647 ) -> Rc<dyn SymMatrix> {
1648 if let Some(m) = self.h_cache.borrow().get(
1651 &[x.as_tagged(), y_c.as_tagged(), y_d.as_tagged()],
1652 &[obj_factor],
1653 ) {
1654 return m;
1655 }
1656 *self.h_evals.borrow_mut() += 1;
1657 let Some(h_space) = self.h_space.as_ref() else {
1658 panic!(
1659 "OrigIpoptNlp::eval_h called but the TNLP did not provide \
1660 eval_h sparsity. The L-BFGS path lands in Phase 8."
1661 );
1662 };
1663 let cls = self.adapter.borrow().classification().clone();
1664 let full_x = self.lift_x_to_full(x);
1665 let full_lambda = self.pack_lambda_for_user(y_c, y_d, &cls);
1673 let scaled_obj_factor = obj_factor * self.obj_scale_factor.get();
1674
1675 let mut full_vals = vec![0.0; self.nnz_h_lag_full as usize];
1680 let ok = {
1681 let a = self.adapter.borrow();
1682 let mut t = a.tnlp().borrow_mut();
1683 t.eval_h(
1684 Some(&full_x),
1685 true,
1686 scaled_obj_factor,
1687 Some(&full_lambda),
1688 true,
1689 SparsityRequest::Values {
1690 values: &mut full_vals,
1691 },
1692 )
1693 };
1694 if !ok {
1695 full_vals.fill(f64::NAN);
1696 }
1697 let mut h = SymTMatrix::new(Rc::clone(h_space));
1698 let kept = h_space.nonzeros() as usize;
1699 let h_vals = h.values_mut();
1700 debug_assert_eq!(kept, self.h_entry_in_full.len());
1703 for (k, &src) in self.h_entry_in_full.iter().enumerate() {
1704 h_vals[k] = full_vals[src as usize];
1705 }
1706 let result: Rc<dyn SymMatrix> = Rc::new(h);
1707 self.h_cache.borrow_mut().add(
1708 Rc::clone(&result),
1709 &[x.as_tagged(), y_c.as_tagged(), y_d.as_tagged()],
1710 &[obj_factor],
1711 );
1712 result
1713 }
1714}
1715
1716fn make_dense_from(
1719 space: &Rc<DenseVectorSpace>,
1720 mut f: impl FnMut(usize) -> Number,
1721) -> DenseVector {
1722 let mut v = space.make_new_dense();
1723 let dim = space.dim() as usize;
1724 if dim > 0 {
1725 let vs = v.values_mut();
1726 for (i, slot) in vs.iter_mut().enumerate().take(dim) {
1727 *slot = f(i);
1728 }
1729 }
1730 v
1731}
1732
1733impl Nlp for OrigIpoptNlp {
1736 fn n(&self) -> Index {
1737 self.x_space.dim()
1738 }
1739 fn m_eq(&self) -> Index {
1740 self.c_space.dim()
1741 }
1742 fn m_ineq(&self) -> Index {
1743 self.d_space.dim()
1744 }
1745
1746 fn eval_f(&mut self, x: &dyn Vector) -> Number {
1747 self.timed_eval(|t| &t.eval_obj, || self.eval_f_internal(x))
1748 }
1749 fn eval_grad_f(&mut self, x: &dyn Vector, g: &mut dyn Vector) {
1750 let result = self.timed_eval(|t| &t.eval_grad_obj, || self.eval_grad_f_internal(x));
1751 g.copy(&*result);
1752 }
1753 fn eval_c(&mut self, x: &dyn Vector, c: &mut dyn Vector) {
1754 let result = self.timed_eval(|t| &t.eval_constr, || self.eval_c_internal(x));
1755 c.copy(&*result);
1756 }
1757 fn eval_d(&mut self, x: &dyn Vector, d: &mut dyn Vector) {
1758 let result = self.timed_eval(|t| &t.eval_constr, || self.eval_d_internal(x));
1759 d.copy(&*result);
1760 }
1761 fn eval_jac_c(&mut self, x: &dyn Vector) -> Rc<dyn Matrix> {
1762 self.timed_eval(|t| &t.eval_constr_jac, || self.eval_jac_c_internal(x))
1763 }
1764 fn eval_jac_d(&mut self, x: &dyn Vector) -> Rc<dyn Matrix> {
1765 self.timed_eval(|t| &t.eval_constr_jac, || self.eval_jac_d_internal(x))
1766 }
1767 fn eval_h(
1768 &mut self,
1769 x: &dyn Vector,
1770 obj_factor: Number,
1771 y_c: &dyn Vector,
1772 y_d: &dyn Vector,
1773 ) -> Rc<dyn SymMatrix> {
1774 self.timed_eval(
1775 |t| &t.eval_lag_hess,
1776 || self.eval_h_internal(x, obj_factor, y_c, y_d),
1777 )
1778 }
1779}
1780
1781impl IpoptNlp for OrigIpoptNlp {
1782 fn x_l(&self) -> &dyn Vector {
1783 &*self.x_l
1784 }
1785 fn x_u(&self) -> &dyn Vector {
1786 &*self.x_u
1787 }
1788 fn d_l(&self) -> &dyn Vector {
1789 &*self.d_l
1790 }
1791 fn d_u(&self) -> &dyn Vector {
1792 &*self.d_u
1793 }
1794 fn px_l(&self) -> Rc<dyn Matrix> {
1795 Rc::clone(&self.px_l)
1796 }
1797 fn px_u(&self) -> Rc<dyn Matrix> {
1798 Rc::clone(&self.px_u)
1799 }
1800 fn pd_l(&self) -> Rc<dyn Matrix> {
1801 Rc::clone(&self.pd_l)
1802 }
1803 fn pd_u(&self) -> Rc<dyn Matrix> {
1804 Rc::clone(&self.pd_u)
1805 }
1806
1807 fn adjust_variable_bounds(
1815 &mut self,
1816 new_x_l: &dyn Vector,
1817 new_x_u: &dyn Vector,
1818 new_d_l: &dyn Vector,
1819 new_d_u: &dyn Vector,
1820 ) {
1821 fn install(slot: &mut Rc<DenseVector>, new: &dyn Vector) {
1825 Rc::get_mut(slot)
1826 .expect("adjust_variable_bounds: bound vector is uniquely owned")
1827 .copy(new);
1828 }
1829 install(&mut self.x_l, new_x_l);
1830 install(&mut self.x_u, new_x_u);
1831 install(&mut self.d_l, new_d_l);
1832 install(&mut self.d_u, new_d_u);
1833 }
1834
1835 fn obj_scaling_factor(&self) -> Number {
1836 self.obj_scale_factor.get()
1837 }
1838
1839 fn c_scale_vec(&self) -> Option<Vec<Number>> {
1840 self.c_scale.borrow().clone()
1841 }
1842
1843 fn d_scale_vec(&self) -> Option<Vec<Number>> {
1844 self.d_scale.borrow().clone()
1845 }
1846
1847 fn split_space_names(&self) -> Option<SplitNames> {
1861 let a = self.adapter.borrow();
1862 let cls = a.classification();
1863
1864 let mut var_meta = MetaData::default();
1865 let mut con_meta = MetaData::default();
1866 if !a
1867 .tnlp()
1868 .borrow_mut()
1869 .get_var_con_metadata(&mut var_meta, &mut con_meta)
1870 {
1871 return None;
1872 }
1873
1874 let var_full = var_meta.strings.get(IDX_NAMES);
1877 let con_full = con_meta.strings.get(IDX_NAMES);
1878 if var_full.is_none() && con_full.is_none() {
1879 return None;
1880 }
1881
1882 let pick = |pool: Option<&Vec<String>>, full_idx: Index| -> Option<String> {
1885 pool.and_then(|v| v.get(full_idx as usize))
1886 .filter(|s| !s.is_empty())
1887 .cloned()
1888 };
1889
1890 let x_var = cls
1891 .x_not_fixed_map
1892 .iter()
1893 .map(|&full_idx| pick(var_full, full_idx))
1894 .collect();
1895 let eq = cls
1896 .c_map
1897 .iter()
1898 .map(|&full_idx| pick(con_full, full_idx))
1899 .collect();
1900 let ineq = cls
1901 .d_map
1902 .iter()
1903 .map(|&full_idx| pick(con_full, full_idx))
1904 .collect();
1905
1906 let names = SplitNames { x_var, eq, ineq };
1907 names.any_present().then_some(names)
1908 }
1909
1910 fn get_starting_x(&mut self, x: &mut dyn Vector) -> bool {
1914 let cls = self.adapter.borrow().classification().clone();
1915 let n_full_x = cls.n_full_x as usize;
1916 let n_full_g = cls.n_full_g as usize;
1917 let mut full_x = vec![0.0; n_full_x];
1918 let mut full_z_l = vec![0.0; n_full_x];
1919 let mut full_z_u = vec![0.0; n_full_x];
1920 let mut full_lambda = vec![0.0; n_full_g];
1921 let ok = {
1922 let a = self.adapter.borrow();
1923 let mut t = a.tnlp().borrow_mut();
1924 t.get_starting_point(StartingPoint {
1925 init_x: true,
1926 x: &mut full_x,
1927 init_z: false,
1928 z_l: &mut full_z_l,
1929 z_u: &mut full_z_u,
1930 init_lambda: false,
1931 lambda: &mut full_lambda,
1932 })
1933 };
1934 if !ok {
1935 return false;
1936 }
1937 let Some(dx) = x.as_any_mut().downcast_mut::<DenseVector>() else {
1938 return false;
1939 };
1940 let xs = dx.values_mut();
1941 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
1942 xs[var_idx] = full_x[full_idx as usize];
1943 }
1944 true
1945 }
1946
1947 fn lift_x_to_full(&self, x: &dyn Vector) -> Vec<Number> {
1948 OrigIpoptNlp::lift_x_to_full(self, x)
1949 }
1950
1951 fn n_full_x(&self) -> Index {
1952 self.adapter.borrow().classification().n_full_x
1953 }
1954
1955 fn n_full_g(&self) -> Index {
1956 self.adapter.borrow().classification().n_full_g
1957 }
1958
1959 fn pack_lambda_for_user(&self, y_c: &dyn Vector, y_d: &dyn Vector) -> Vec<Number> {
1960 let cls = self.adapter.borrow().classification().clone();
1961 OrigIpoptNlp::pack_lambda_for_user(self, y_c, y_d, &cls)
1962 }
1963
1964 fn pack_g_for_user(&self, c: &dyn Vector, d: &dyn Vector) -> Vec<Number> {
1965 let cls = self.adapter.borrow().classification().clone();
1966 let mut g = vec![0.0; cls.n_full_g as usize];
1967 if cls.n_c > 0 {
1968 let Some(dc) = c.as_any().downcast_ref::<DenseVector>() else {
1969 panic!("OrigIpoptNlp expects DenseVector for c");
1970 };
1971 let cs = self.c_scale.borrow();
1972 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1973 let v = dc.expanded_values()[i];
1974 g[g_idx as usize] = match cs.as_ref() {
1975 Some(s) => v / s[i],
1976 None => v,
1977 };
1978 }
1979 }
1980 if cls.n_d > 0 {
1981 let Some(dd) = d.as_any().downcast_ref::<DenseVector>() else {
1982 panic!("OrigIpoptNlp expects DenseVector for d");
1983 };
1984 let ds = self.d_scale.borrow();
1985 for (i, &g_idx) in cls.d_map.iter().enumerate() {
1986 let v = dd.expanded_values()[i];
1987 g[g_idx as usize] = match ds.as_ref() {
1988 Some(s) => v / s[i],
1989 None => v,
1990 };
1991 }
1992 }
1993 g
1994 }
1995
1996 fn pack_z_l_for_user(&self, z_l: &dyn Vector) -> Vec<Number> {
1997 let cls = self.adapter.borrow().classification().clone();
1998 let mut full = vec![0.0; cls.n_full_x as usize];
1999 if z_l.dim() == 0 {
2000 return full;
2001 }
2002 let Some(dz) = z_l.as_any().downcast_ref::<DenseVector>() else {
2003 panic!("OrigIpoptNlp expects DenseVector for z_l");
2004 };
2005 let vals = dz.expanded_values();
2006 for (k, &var_idx) in cls.x_l_map.iter().enumerate() {
2007 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
2008 full[full_idx] = vals[k];
2009 }
2010 full
2011 }
2012
2013 fn pack_z_u_for_user(&self, z_u: &dyn Vector) -> Vec<Number> {
2014 let cls = self.adapter.borrow().classification().clone();
2015 let mut full = vec![0.0; cls.n_full_x as usize];
2016 if z_u.dim() == 0 {
2017 return full;
2018 }
2019 let Some(dz) = z_u.as_any().downcast_ref::<DenseVector>() else {
2020 panic!("OrigIpoptNlp expects DenseVector for z_u");
2021 };
2022 let vals = dz.expanded_values();
2023 for (k, &var_idx) in cls.x_u_map.iter().enumerate() {
2024 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
2025 full[full_idx] = vals[k];
2026 }
2027 full
2028 }
2029
2030 fn finalize_solution_lambda(&self, y_c: &dyn Vector, y_d: &dyn Vector) -> Vec<Number> {
2031 OrigIpoptNlp::finalize_solution_lambda(self, y_c, y_d)
2032 }
2033
2034 fn finalize_solution_z_l(&self, z_l: &dyn Vector) -> Vec<Number> {
2035 OrigIpoptNlp::finalize_solution_z_l(self, z_l)
2036 }
2037
2038 fn finalize_solution_z_u(&self, z_u: &dyn Vector) -> Vec<Number> {
2039 OrigIpoptNlp::finalize_solution_z_u(self, z_u)
2040 }
2041
2042 fn full_x_to_var_x(&self, full_idx: Index) -> Option<Index> {
2043 let cls = self.adapter.borrow();
2044 let cls = cls.classification();
2045 let f = full_idx as usize;
2046 if f >= cls.full_to_var.len() {
2047 return None;
2048 }
2049 let v = cls.full_to_var[f];
2050 if v < 0 {
2051 None
2052 } else {
2053 Some(v)
2054 }
2055 }
2056
2057 fn full_g_to_c_block(&self, full_idx: Index) -> Option<Index> {
2058 let cls = self.adapter.borrow();
2059 let cls = cls.classification();
2060 cls.c_map
2061 .iter()
2062 .position(|&g_idx| g_idx == full_idx)
2063 .map(|p| p as Index)
2064 }
2065
2066 fn var_x_to_full_x(&self, var_idx: Index) -> Index {
2067 let cls = self.adapter.borrow();
2068 let cls = cls.classification();
2069 cls.x_not_fixed_map[var_idx as usize]
2070 }
2071}
2072
2073#[cfg(test)]
2076mod tests {
2077 use super::*;
2078 use crate::tnlp::{
2079 BoundsInfo, IndexStyle, IpoptCq, IpoptData, NlpInfo, Solution, SparsityRequest,
2080 StartingPoint, TNLP,
2081 };
2082
2083 #[derive(Default)]
2088 struct Hs071 {
2089 eval_f_calls: usize,
2090 eval_grad_f_calls: usize,
2091 eval_g_calls: usize,
2092 eval_jac_g_value_calls: usize,
2093 eval_h_value_calls: usize,
2094 get_bounds_info_calls: usize,
2095 }
2096
2097 impl TNLP for Hs071 {
2098 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2099 Some(NlpInfo {
2100 n: 4,
2101 m: 2,
2102 nnz_jac_g: 8,
2103 nnz_h_lag: 10,
2104 index_style: IndexStyle::C,
2105 })
2106 }
2107 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2108 self.get_bounds_info_calls += 1;
2109 b.x_l.copy_from_slice(&[1.0; 4]);
2110 b.x_u.copy_from_slice(&[5.0; 4]);
2111 b.g_l.copy_from_slice(&[25.0, 40.0]);
2114 b.g_u.copy_from_slice(&[2.0e19, 40.0]);
2115 true
2116 }
2117 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2118 sp.x.copy_from_slice(&[1.0, 5.0, 5.0, 1.0]);
2119 true
2120 }
2121 fn eval_f(&mut self, x: &[Number], _new_x: bool) -> Option<Number> {
2122 self.eval_f_calls += 1;
2123 Some(x[0] * x[3] * (x[0] + x[1] + x[2]) + x[2])
2124 }
2125 fn eval_grad_f(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
2126 self.eval_grad_f_calls += 1;
2127 g[0] = x[3] * (2.0 * x[0] + x[1] + x[2]);
2132 g[1] = x[0] * x[3];
2133 g[2] = x[0] * x[3] + 1.0;
2134 g[3] = x[0] * (x[0] + x[1] + x[2]);
2135 true
2136 }
2137 fn eval_g(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
2138 self.eval_g_calls += 1;
2139 g[0] = x[0] * x[1] * x[2] * x[3];
2142 g[1] = x[0] * x[0] + x[1] * x[1] + x[2] * x[2] + x[3] * x[3];
2143 true
2144 }
2145 fn eval_jac_g(
2146 &mut self,
2147 x: Option<&[Number]>,
2148 _new_x: bool,
2149 mode: SparsityRequest<'_>,
2150 ) -> bool {
2151 match mode {
2152 SparsityRequest::Structure { irow, jcol } => {
2153 irow.copy_from_slice(&[0, 0, 0, 0, 1, 1, 1, 1]);
2155 jcol.copy_from_slice(&[0, 1, 2, 3, 0, 1, 2, 3]);
2156 }
2157 SparsityRequest::Values { values } => {
2158 self.eval_jac_g_value_calls += 1;
2159 let x = x.expect("eval_jac_g(Values) without x");
2160 values[0] = x[1] * x[2] * x[3];
2162 values[1] = x[0] * x[2] * x[3];
2163 values[2] = x[0] * x[1] * x[3];
2164 values[3] = x[0] * x[1] * x[2];
2165 values[4] = 2.0 * x[0];
2167 values[5] = 2.0 * x[1];
2168 values[6] = 2.0 * x[2];
2169 values[7] = 2.0 * x[3];
2170 }
2171 }
2172 true
2173 }
2174 fn eval_h(
2175 &mut self,
2176 x: Option<&[Number]>,
2177 _new_x: bool,
2178 obj_factor: Number,
2179 lambda: Option<&[Number]>,
2180 _new_lambda: bool,
2181 mode: SparsityRequest<'_>,
2182 ) -> bool {
2183 match mode {
2186 SparsityRequest::Structure { irow, jcol } => {
2187 irow.copy_from_slice(&[0, 1, 1, 2, 2, 2, 3, 3, 3, 3]);
2188 jcol.copy_from_slice(&[0, 0, 1, 0, 1, 2, 0, 1, 2, 3]);
2189 }
2190 SparsityRequest::Values { values } => {
2191 self.eval_h_value_calls += 1;
2192 let x = x.expect("eval_h(Values) without x");
2193 let lam = lambda.expect("eval_h(Values) without lambda");
2194 let of = obj_factor;
2195 let l0 = lam[0];
2205 let l1 = lam[1];
2206 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; }
2217 }
2218 true
2219 }
2220 fn finalize_solution(&mut self, _sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {}
2221 }
2222
2223 fn build_orig_nlp() -> (Rc<RefCell<TNLPAdapter>>, OrigIpoptNlp) {
2224 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071::default()));
2225 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2226 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2227 (adapter, nlp)
2228 }
2229
2230 fn dense_x(values: &[Number], space: &Rc<DenseVectorSpace>) -> DenseVector {
2231 let mut v = space.make_new_dense();
2232 v.values_mut().copy_from_slice(values);
2233 v
2234 }
2235
2236 #[test]
2237 fn dimensions_match_classification() {
2238 let (_, nlp) = build_orig_nlp();
2239 assert_eq!(nlp.n(), 4);
2241 assert_eq!(nlp.m_eq(), 1);
2242 assert_eq!(nlp.m_ineq(), 1);
2243 assert_eq!(nlp.jac_c_space().nonzeros(), 4);
2245 assert_eq!(nlp.jac_d_space().nonzeros(), 4);
2246 assert_eq!(nlp.h_space().unwrap().nonzeros(), 10);
2248 assert_eq!(nlp.x_l().dim(), 4);
2250 assert_eq!(nlp.x_u().dim(), 4);
2251 assert_eq!(nlp.d_l().dim(), 1);
2252 assert_eq!(nlp.d_u().dim(), 0);
2253 }
2254
2255 #[test]
2256 fn eval_f_at_starting_point() {
2257 let (_, mut nlp) = build_orig_nlp();
2258 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2259 assert_eq!(nlp.eval_f(&x), 16.0);
2261 assert_eq!(nlp.f_evals(), 1);
2262 }
2263
2264 #[test]
2265 fn eval_grad_f_at_starting_point() {
2266 let (_, mut nlp) = build_orig_nlp();
2267 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2268 let mut g = nlp.x_space().make_new_dense();
2269 nlp.eval_grad_f(&x, &mut g);
2270 assert_eq!(g.values(), &[12.0, 1.0, 2.0, 11.0]);
2275 assert_eq!(nlp.grad_f_evals(), 1);
2276 }
2277
2278 #[test]
2279 fn eval_c_returns_equality_residual() {
2280 let (_, mut nlp) = build_orig_nlp();
2281 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2282 let mut c = nlp.c_space().make_new_dense();
2283 nlp.eval_c(&x, &mut c);
2284 assert_eq!(c.values(), &[12.0]);
2286 assert_eq!(nlp.c_evals(), 1);
2287 }
2288
2289 #[test]
2290 fn eval_d_returns_inequality_value_unshifted() {
2291 let (_, mut nlp) = build_orig_nlp();
2292 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2293 let mut d = nlp.d_space().make_new_dense();
2294 nlp.eval_d(&x, &mut d);
2295 assert_eq!(d.values(), &[25.0]);
2297 assert_eq!(nlp.d_evals(), 1);
2298 }
2299
2300 #[test]
2301 fn cache_returns_without_re_eval() {
2302 let (_, mut nlp) = build_orig_nlp();
2303 let mut x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2304 let f1 = nlp.eval_f(&x);
2305 let f2 = nlp.eval_f(&x);
2306 assert_eq!(f1, f2);
2307 assert_eq!(nlp.f_evals(), 1, "second call must be served from cache");
2308 x.values_mut()[0] = 1.0; let _ = nlp.eval_f(&x);
2311 assert_eq!(nlp.f_evals(), 2);
2312 }
2313
2314 #[test]
2315 fn jac_c_picks_only_equality_rows() {
2316 let (_, mut nlp) = build_orig_nlp();
2317 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2318 let m = nlp.eval_jac_c(&x);
2319 let g = m
2320 .as_any()
2321 .downcast_ref::<GenTMatrix>()
2322 .expect("jac_c is a GenTMatrix");
2323 assert_eq!(g.values(), &[2.0, 10.0, 10.0, 2.0]);
2325 assert_eq!(g.irows(), &[1, 1, 1, 1]);
2327 assert_eq!(g.jcols(), &[1, 2, 3, 4]);
2328 }
2329
2330 #[test]
2331 fn jac_d_picks_only_inequality_rows() {
2332 let (_, mut nlp) = build_orig_nlp();
2333 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2334 let m = nlp.eval_jac_d(&x);
2335 let g = m
2336 .as_any()
2337 .downcast_ref::<GenTMatrix>()
2338 .expect("jac_d is a GenTMatrix");
2339 assert_eq!(g.values(), &[25.0, 5.0, 5.0, 25.0]);
2342 }
2343
2344 fn build_orig_nlp_counting() -> (Rc<RefCell<Hs071>>, OrigIpoptNlp) {
2349 let concrete = Rc::new(RefCell::new(Hs071::default()));
2350 let tnlp: Rc<RefCell<dyn TNLP>> = concrete.clone();
2351 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2352 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2353 (concrete, nlp)
2354 }
2355
2356 #[test]
2357 fn eval_c_and_eval_d_share_one_eval_g_per_iterate() {
2358 let (tnlp, mut nlp) = build_orig_nlp_counting();
2362 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2363 let mut c = nlp.c_space().make_new_dense();
2364 let mut d = nlp.d_space().make_new_dense();
2365 nlp.eval_c(&x, &mut c);
2366 nlp.eval_d(&x, &mut d);
2367 assert_eq!(
2368 tnlp.borrow().eval_g_calls,
2369 1,
2370 "eval_c + eval_d at one iterate must share a single user eval_g"
2371 );
2372 assert_eq!(nlp.c_evals(), 1);
2374 assert_eq!(nlp.d_evals(), 1);
2375 assert_eq!(c.values(), &[12.0]);
2377 assert_eq!(d.values(), &[25.0]);
2378
2379 let mut x2 = x;
2382 x2.values_mut()[0] = 2.0;
2383 nlp.eval_c(&x2, &mut c);
2384 nlp.eval_d(&x2, &mut d);
2385 assert_eq!(
2386 tnlp.borrow().eval_g_calls,
2387 2,
2388 "a new iterate triggers exactly one more shared eval_g"
2389 );
2390 }
2391
2392 #[test]
2393 fn eval_c_does_not_refetch_bounds_per_iterate() {
2394 let (tnlp, mut nlp) = build_orig_nlp_counting();
2402 let baseline = tnlp.borrow().get_bounds_info_calls;
2405
2406 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2407 let mut c = nlp.c_space().make_new_dense();
2408 nlp.eval_c(&x, &mut c);
2409 assert_eq!(c.values(), &[12.0]);
2411
2412 let mut x2 = x;
2414 for k in 0..5 {
2415 x2.values_mut()[0] = 2.0 + k as Number;
2416 nlp.eval_c(&x2, &mut c);
2417 }
2418
2419 assert_eq!(
2420 tnlp.borrow().get_bounds_info_calls,
2421 baseline,
2422 "eval_c must reuse the captured c_rhs, not re-fetch bounds per iterate"
2423 );
2424 }
2425
2426 #[test]
2427 fn eval_jac_c_and_eval_jac_d_share_one_eval_jac_g_per_iterate() {
2428 let (tnlp, mut nlp) = build_orig_nlp_counting();
2432 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2433 let _ = nlp.eval_jac_c(&x);
2434 let _ = nlp.eval_jac_d(&x);
2435 assert_eq!(
2436 tnlp.borrow().eval_jac_g_value_calls,
2437 1,
2438 "eval_jac_c + eval_jac_d at one iterate must share a single eval_jac_g"
2439 );
2440 assert_eq!(nlp.jac_c_evals(), 1);
2441 assert_eq!(nlp.jac_d_evals(), 1);
2442 }
2443
2444 #[test]
2445 fn starting_point_is_compressed_into_x_var() {
2446 let (_, mut nlp) = build_orig_nlp();
2447 let mut x = nlp.x_space().make_new_dense();
2448 let mut yc = nlp.c_space().make_new_dense();
2449 let mut yd = nlp.d_space().make_new_dense();
2450 let mut zl = nlp.x_l_space().make_new_dense();
2451 let mut zu = nlp.x_u_space().make_new_dense();
2452 let ok = nlp.initialize_starting_point(
2453 &mut x, true, &mut yc, false, &mut yd, false, &mut zl, false, &mut zu, false,
2454 );
2455 assert!(ok);
2456 assert_eq!(x.values(), &[1.0, 5.0, 5.0, 1.0]);
2457 }
2458
2459 struct OneFixedOneFree;
2464 impl TNLP for OneFixedOneFree {
2465 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2466 Some(NlpInfo {
2467 n: 2,
2468 m: 1,
2469 nnz_jac_g: 1,
2470 nnz_h_lag: 0,
2471 index_style: IndexStyle::C,
2472 })
2473 }
2474 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2475 b.x_l[0] = 7.0;
2476 b.x_u[0] = 7.0; b.x_l[1] = -1.0e19;
2478 b.x_u[1] = 1.0e19;
2479 b.g_l[0] = 0.0;
2480 b.g_u[0] = 0.0; true
2482 }
2483 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2484 sp.x[0] = 7.0;
2485 sp.x[1] = 0.5;
2486 true
2487 }
2488 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
2489 Some(x[1])
2490 }
2491 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
2492 g[0] = 0.0;
2493 g[1] = 1.0;
2494 true
2495 }
2496 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
2497 g[0] = x[1];
2498 true
2499 }
2500 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
2501 match m {
2502 SparsityRequest::Structure { irow, jcol } => {
2503 irow[0] = 0;
2504 jcol[0] = 1;
2505 }
2506 SparsityRequest::Values { values } => values[0] = 1.0,
2507 }
2508 true
2509 }
2510 fn eval_h(
2511 &mut self,
2512 _: Option<&[Number]>,
2513 _: bool,
2514 _: Number,
2515 _: Option<&[Number]>,
2516 _: bool,
2517 _: SparsityRequest<'_>,
2518 ) -> bool {
2519 true
2520 }
2521 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
2522 }
2523
2524 #[test]
2525 fn ipopt_nlp_index_mapping_methods_handle_fixed_var() {
2526 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneFixedOneFree));
2527 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2528 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2529
2530 assert_eq!(nlp.n_full_x(), 2);
2532 assert_eq!(nlp.n(), 1);
2533
2534 let nlp_dyn: &dyn crate::ipopt_nlp::IpoptNlp = &nlp;
2536 assert_eq!(nlp_dyn.full_x_to_var_x(0), None);
2537 assert_eq!(nlp_dyn.full_x_to_var_x(1), Some(0));
2538
2539 assert_eq!(nlp_dyn.var_x_to_full_x(0), 1);
2541
2542 assert_eq!(nlp_dyn.full_g_to_c_block(0), Some(0));
2544
2545 let mut x_var = nlp.x_space().make_new_dense();
2547 x_var.values_mut()[0] = 0.5;
2548 let lifted = nlp_dyn.lift_x_to_full(&x_var);
2549 assert_eq!(lifted, vec![7.0, 0.5]);
2550 }
2551
2552 struct NamedFixedOneFree;
2556 impl TNLP for NamedFixedOneFree {
2557 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2558 OneFixedOneFree.get_nlp_info()
2559 }
2560 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2561 OneFixedOneFree.get_bounds_info(b)
2562 }
2563 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2564 OneFixedOneFree.get_starting_point(sp)
2565 }
2566 fn eval_f(&mut self, x: &[Number], n: bool) -> Option<Number> {
2567 OneFixedOneFree.eval_f(x, n)
2568 }
2569 fn eval_grad_f(&mut self, x: &[Number], n: bool, g: &mut [Number]) -> bool {
2570 OneFixedOneFree.eval_grad_f(x, n, g)
2571 }
2572 fn eval_g(&mut self, x: &[Number], n: bool, g: &mut [Number]) -> bool {
2573 OneFixedOneFree.eval_g(x, n, g)
2574 }
2575 fn eval_jac_g(&mut self, x: Option<&[Number]>, n: bool, m: SparsityRequest<'_>) -> bool {
2576 OneFixedOneFree.eval_jac_g(x, n, m)
2577 }
2578 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
2579 fn get_var_con_metadata(&mut self, var: &mut MetaData, con: &mut MetaData) -> bool {
2580 var.strings.insert(
2581 IDX_NAMES.to_string(),
2582 vec!["fixed_x".to_string(), "free_x".to_string()],
2583 );
2584 con.strings
2585 .insert(IDX_NAMES.to_string(), vec!["balance".to_string()]);
2586 true
2587 }
2588 }
2589
2590 #[test]
2591 fn split_space_names_threads_through_fixed_var_and_cd_split() {
2592 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(NamedFixedOneFree));
2593 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2594 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2595
2596 let names = nlp.split_space_names().expect("names present");
2597 assert_eq!(names.x_var, vec![Some("free_x".to_string())]);
2599 assert_eq!(names.eq, vec![Some("balance".to_string())]);
2601 assert!(names.ineq.is_empty());
2603 assert!(names.any_present());
2604 }
2605
2606 #[test]
2607 fn split_space_names_none_when_tnlp_declines() {
2608 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneFixedOneFree));
2610 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2611 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2612 assert!(nlp.split_space_names().is_none());
2613 }
2614
2615 struct FixedOnlyHess;
2621 impl TNLP for FixedOnlyHess {
2622 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2623 Some(NlpInfo {
2624 n: 2,
2625 m: 1,
2626 nnz_jac_g: 1,
2627 nnz_h_lag: 1,
2628 index_style: IndexStyle::C,
2629 })
2630 }
2631 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2632 b.x_l[0] = 7.0;
2633 b.x_u[0] = 7.0; b.x_l[1] = -1.0e19;
2635 b.x_u[1] = 1.0e19;
2636 b.g_l[0] = 0.0;
2637 b.g_u[0] = 0.0;
2638 true
2639 }
2640 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2641 sp.x[0] = 7.0;
2642 sp.x[1] = 0.5;
2643 true
2644 }
2645 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
2646 Some(0.5 * x[0] * x[0] + x[1])
2647 }
2648 fn eval_grad_f(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
2649 g[0] = x[0];
2650 g[1] = 1.0;
2651 true
2652 }
2653 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
2654 g[0] = x[1];
2655 true
2656 }
2657 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
2658 match m {
2659 SparsityRequest::Structure { irow, jcol } => {
2660 irow[0] = 0;
2661 jcol[0] = 1;
2662 }
2663 SparsityRequest::Values { values } => values[0] = 1.0,
2664 }
2665 true
2666 }
2667 fn eval_h(
2668 &mut self,
2669 _: Option<&[Number]>,
2670 _: bool,
2671 obj_factor: Number,
2672 _: Option<&[Number]>,
2673 _: bool,
2674 m: SparsityRequest<'_>,
2675 ) -> bool {
2676 match m {
2677 SparsityRequest::Structure { irow, jcol } => {
2678 irow[0] = 0;
2679 jcol[0] = 0;
2680 }
2681 SparsityRequest::Values { values } => values[0] = obj_factor,
2682 }
2683 true
2684 }
2685 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
2686 }
2687
2688 struct OneIneqLargeOffset;
2697 impl TNLP for OneIneqLargeOffset {
2698 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2699 Some(NlpInfo {
2700 n: 1,
2701 m: 1,
2702 nnz_jac_g: 1,
2703 nnz_h_lag: 0,
2704 index_style: IndexStyle::C,
2705 })
2706 }
2707 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2708 b.x_l[0] = -1.0e19;
2709 b.x_u[0] = 1.0e19;
2710 b.g_l[0] = 4.0e6;
2711 b.g_u[0] = 2.0e19;
2712 true
2713 }
2714 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2715 sp.x[0] = 5000.0;
2716 true
2717 }
2718 fn eval_f(&mut self, _: &[Number], _: bool) -> Option<Number> {
2719 Some(0.0)
2720 }
2721 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
2722 g[0] = 0.0;
2723 true
2724 }
2725 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
2726 g[0] = 1000.0 * x[0];
2727 true
2728 }
2729 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
2730 match m {
2731 SparsityRequest::Structure { irow, jcol } => {
2732 irow[0] = 0;
2733 jcol[0] = 0;
2734 }
2735 SparsityRequest::Values { values } => values[0] = 1000.0,
2736 }
2737 true
2738 }
2739 fn eval_h(
2740 &mut self,
2741 _: Option<&[Number]>,
2742 _: bool,
2743 _: Number,
2744 _: Option<&[Number]>,
2745 _: bool,
2746 _: SparsityRequest<'_>,
2747 ) -> bool {
2748 true
2749 }
2750 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
2751 }
2752
2753 #[test]
2754 fn gradient_based_scaling_scales_d_l_and_d_u() {
2755 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqLargeOffset));
2756 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2757 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2758
2759 assert_eq!(nlp.d_l().dim(), 1);
2761 let pre = nlp
2762 .d_l()
2763 .as_any()
2764 .downcast_ref::<DenseVector>()
2765 .unwrap()
2766 .values()[0];
2767 assert_eq!(pre, 4.0e6);
2768
2769 nlp.determine_scaling_from_starting_point(
2770 ScalingMethod::GradientBased,
2771 100.0,
2772 1e-8,
2773 0.0,
2774 0.0,
2775 );
2776
2777 let post = nlp
2779 .d_l()
2780 .as_any()
2781 .downcast_ref::<DenseVector>()
2782 .unwrap()
2783 .values()[0];
2784 assert!(
2785 (post - 4.0e5).abs() < 1e-9,
2786 "d_l should be scaled by d_scale=0.1; got {}",
2787 post
2788 );
2789
2790 let x = dense_x(&[5000.0], nlp.x_space());
2793 let mut d = nlp.d_space().make_new_dense();
2794 nlp.eval_d(&x, &mut d);
2795 assert!(
2796 (d.values()[0] - 5.0e5).abs() < 1e-6,
2797 "scaled d(x) mismatch; got {}",
2798 d.values()[0]
2799 );
2800 assert!(
2801 d.values()[0] >= post,
2802 "starting point must be feasible in scaled space"
2803 );
2804 }
2805
2806 struct OneIneqWithObj;
2812 impl TNLP for OneIneqWithObj {
2813 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2814 Some(NlpInfo {
2815 n: 1,
2816 m: 1,
2817 nnz_jac_g: 1,
2818 nnz_h_lag: 0,
2819 index_style: IndexStyle::C,
2820 })
2821 }
2822 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2823 b.x_l[0] = -1.0e19;
2824 b.x_u[0] = 1.0e19;
2825 b.g_l[0] = 4.0e6;
2826 b.g_u[0] = 2.0e19;
2827 true
2828 }
2829 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2830 sp.x[0] = 5000.0;
2831 true
2832 }
2833 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
2834 Some(10.0 * x[0])
2835 }
2836 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
2837 g[0] = 10.0;
2838 true
2839 }
2840 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
2841 g[0] = 1000.0 * x[0];
2842 true
2843 }
2844 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
2845 match m {
2846 SparsityRequest::Structure { irow, jcol } => {
2847 irow[0] = 0;
2848 jcol[0] = 0;
2849 }
2850 SparsityRequest::Values { values } => values[0] = 1000.0,
2851 }
2852 true
2853 }
2854 fn eval_h(
2855 &mut self,
2856 _: Option<&[Number]>,
2857 _: bool,
2858 _: Number,
2859 _: Option<&[Number]>,
2860 _: bool,
2861 _: SparsityRequest<'_>,
2862 ) -> bool {
2863 true
2864 }
2865 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
2866 }
2867
2868 #[test]
2869 fn obj_target_gradient_pins_obj_scale() {
2870 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqWithObj));
2873 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2874 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2875 nlp.determine_scaling_from_starting_point(
2876 ScalingMethod::GradientBased,
2877 100.0,
2878 1e-8,
2879 0.0, 0.0,
2881 );
2882 assert!(
2883 (nlp.obj_scale_factor() - 1.0).abs() < 1e-12,
2884 "no-target path leaves df=1 when grad < cutoff; got {}",
2885 nlp.obj_scale_factor()
2886 );
2887
2888 let tnlp2: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqWithObj));
2891 let adapter2 = Rc::new(RefCell::new(TNLPAdapter::new(tnlp2).unwrap()));
2892 let mut nlp2 = OrigIpoptNlp::new(Rc::clone(&adapter2), Rc::new(NoScaling)).unwrap();
2893 nlp2.determine_scaling_from_starting_point(
2894 ScalingMethod::GradientBased,
2895 100.0,
2896 1e-8,
2897 1.0,
2898 0.0,
2899 );
2900 assert!(
2901 (nlp2.obj_scale_factor() - 0.1).abs() < 1e-12,
2902 "target_gradient=1, max_grad_f=10 → df=0.1; got {}",
2903 nlp2.obj_scale_factor()
2904 );
2905 }
2906
2907 struct FixedVarShiftsObjGrad;
2917 impl TNLP for FixedVarShiftsObjGrad {
2918 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2919 Some(NlpInfo {
2920 n: 2,
2921 m: 0,
2922 nnz_jac_g: 0,
2923 nnz_h_lag: 0,
2924 index_style: IndexStyle::C,
2925 })
2926 }
2927 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2928 b.x_l[0] = -1.0e19;
2929 b.x_u[0] = 1.0e19;
2930 b.x_l[1] = 1000.0;
2931 b.x_u[1] = 1000.0; true
2933 }
2934 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2935 sp.x[0] = 1.0;
2936 sp.x[1] = 0.0; true
2938 }
2939 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
2940 Some(x[0] * x[1])
2941 }
2942 fn eval_grad_f(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
2943 g[0] = x[1];
2944 g[1] = x[0];
2945 true
2946 }
2947 fn eval_g(&mut self, _: &[Number], _: bool, _: &mut [Number]) -> bool {
2948 true
2949 }
2950 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, _: SparsityRequest<'_>) -> bool {
2951 true
2952 }
2953 fn eval_h(
2954 &mut self,
2955 _: Option<&[Number]>,
2956 _: bool,
2957 _: Number,
2958 _: Option<&[Number]>,
2959 _: bool,
2960 _: SparsityRequest<'_>,
2961 ) -> bool {
2962 true
2963 }
2964 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
2965 }
2966
2967 #[test]
2968 fn gradient_scaling_lifts_fixed_vars_to_their_value() {
2969 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(FixedVarShiftsObjGrad));
2970 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2971 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2972
2973 assert_eq!(nlp.n_full_x(), 2);
2975 assert_eq!(nlp.n(), 1);
2976
2977 nlp.determine_scaling_from_starting_point(
2978 ScalingMethod::GradientBased,
2979 100.0,
2980 1e-8,
2981 0.0,
2982 0.0,
2983 );
2984
2985 assert!(
2989 (nlp.obj_scale_factor() - 0.1).abs() < 1e-12,
2990 "fixed var must be lifted before scaling; expected df=0.1, got {}",
2991 nlp.obj_scale_factor()
2992 );
2993 }
2994
2995 #[test]
2996 fn constr_target_gradient_overrides_cutoff_and_clamp() {
2997 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqLargeOffset));
3002 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3003 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3004 nlp.determine_scaling_from_starting_point(
3005 ScalingMethod::GradientBased,
3006 100.0,
3007 1e-8,
3008 0.0,
3009 50.0,
3010 );
3011 let x = dense_x(&[5000.0], nlp.x_space());
3012 let mut d = nlp.d_space().make_new_dense();
3013 nlp.eval_d(&x, &mut d);
3014 assert!(
3016 (d.values()[0] - 2.5e5).abs() < 1e-6,
3017 "constr target=50 → dd=0.05; scaled d(5000)=2.5e5, got {}",
3018 d.values()[0]
3019 );
3020 }
3021
3022 struct Hs071UserScaled;
3027 impl TNLP for Hs071UserScaled {
3028 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3029 Hs071::default().get_nlp_info()
3030 }
3031 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3032 Hs071::default().get_bounds_info(b)
3033 }
3034 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3035 Hs071::default().get_starting_point(sp)
3036 }
3037 fn eval_f(&mut self, x: &[Number], new_x: bool) -> Option<Number> {
3038 Hs071::default().eval_f(x, new_x)
3039 }
3040 fn eval_grad_f(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
3041 Hs071::default().eval_grad_f(x, new_x, g)
3042 }
3043 fn eval_g(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
3044 Hs071::default().eval_g(x, new_x, g)
3045 }
3046 fn eval_jac_g(
3047 &mut self,
3048 x: Option<&[Number]>,
3049 new_x: bool,
3050 mode: SparsityRequest<'_>,
3051 ) -> bool {
3052 Hs071::default().eval_jac_g(x, new_x, mode)
3053 }
3054 fn eval_h(
3055 &mut self,
3056 x: Option<&[Number]>,
3057 new_x: bool,
3058 obj_factor: Number,
3059 lambda: Option<&[Number]>,
3060 new_lambda: bool,
3061 mode: SparsityRequest<'_>,
3062 ) -> bool {
3063 Hs071::default().eval_h(x, new_x, obj_factor, lambda, new_lambda, mode)
3064 }
3065 fn get_scaling_parameters(&mut self, req: ScalingRequest<'_>) -> bool {
3066 *req.obj_scaling = 2.0;
3067 *req.use_x_scaling = false;
3068 *req.use_g_scaling = true;
3069 req.g_scaling[0] = 0.5;
3071 req.g_scaling[1] = 0.25;
3072 true
3073 }
3074 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3075 }
3076
3077 #[test]
3078 fn user_scaling_dispatch_applies_obj_and_g_scaling() {
3079 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071UserScaled));
3080 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3081 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3082 nlp.determine_scaling_from_starting_point(
3083 ScalingMethod::UserScaling,
3084 100.0,
3085 1e-8,
3086 0.0,
3087 0.0,
3088 );
3089
3090 assert!(
3093 (nlp.obj_scale_factor() - 2.0).abs() < 1e-12,
3094 "user obj_scaling=2.0 should be installed; got {}",
3095 nlp.obj_scale_factor()
3096 );
3097
3098 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3102 let mut c = nlp.c_space().make_new_dense();
3103 nlp.eval_c(&x, &mut c);
3104 assert!(
3107 (c.values()[0] - 3.0).abs() < 1e-9,
3108 "user g_scaling=0.25 on equality → c=3.0; got {}",
3109 c.values()[0]
3110 );
3111
3112 let mut d = nlp.d_space().make_new_dense();
3115 nlp.eval_d(&x, &mut d);
3116 assert!(
3117 (d.values()[0] - 12.5).abs() < 1e-9,
3118 "user g_scaling=0.5 on inequality → d=12.5; got {}",
3119 d.values()[0]
3120 );
3121
3122 let post_d_l = nlp
3125 .d_l()
3126 .as_any()
3127 .downcast_ref::<DenseVector>()
3128 .unwrap()
3129 .values()[0];
3130 assert!(
3131 (post_d_l - 12.5).abs() < 1e-9,
3132 "d_l scaled in step: got {}",
3133 post_d_l
3134 );
3135 }
3136
3137 struct Hs071DeclinesScaling;
3141 impl TNLP for Hs071DeclinesScaling {
3142 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3143 Hs071::default().get_nlp_info()
3144 }
3145 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3146 Hs071::default().get_bounds_info(b)
3147 }
3148 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3149 Hs071::default().get_starting_point(sp)
3150 }
3151 fn eval_f(&mut self, x: &[Number], new_x: bool) -> Option<Number> {
3152 Hs071::default().eval_f(x, new_x)
3153 }
3154 fn eval_grad_f(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
3155 Hs071::default().eval_grad_f(x, new_x, g)
3156 }
3157 fn eval_g(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
3158 Hs071::default().eval_g(x, new_x, g)
3159 }
3160 fn eval_jac_g(
3161 &mut self,
3162 x: Option<&[Number]>,
3163 new_x: bool,
3164 mode: SparsityRequest<'_>,
3165 ) -> bool {
3166 Hs071::default().eval_jac_g(x, new_x, mode)
3167 }
3168 fn eval_h(
3169 &mut self,
3170 x: Option<&[Number]>,
3171 new_x: bool,
3172 obj_factor: Number,
3173 lambda: Option<&[Number]>,
3174 new_lambda: bool,
3175 mode: SparsityRequest<'_>,
3176 ) -> bool {
3177 Hs071::default().eval_h(x, new_x, obj_factor, lambda, new_lambda, mode)
3178 }
3179 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3180 }
3181
3182 #[test]
3183 fn user_scaling_falls_back_when_tnlp_declines() {
3184 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071DeclinesScaling));
3185 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3186 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3187 nlp.determine_scaling_from_starting_point(
3188 ScalingMethod::UserScaling,
3189 100.0,
3190 1e-8,
3191 0.0,
3192 0.0,
3193 );
3194 assert!((nlp.obj_scale_factor() - 1.0).abs() < 1e-12);
3197 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3198 let mut c = nlp.c_space().make_new_dense();
3199 nlp.eval_c(&x, &mut c);
3200 assert_eq!(c.values(), &[12.0], "unscaled equality residual");
3201 }
3202
3203 #[test]
3204 fn eval_h_with_all_entries_on_fixed_var_does_not_panic() {
3205 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(FixedOnlyHess));
3206 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3207 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3208
3209 assert_eq!(nlp.h_space().unwrap().nonzeros(), 0);
3212
3213 let x = dense_x(&[0.5], &nlp.x_space().clone());
3214 let yc = dense_x(&[0.0], &nlp.c_space().clone());
3215 let yd = nlp.d_space().make_new_dense();
3216 let h = nlp.eval_h(&x, 1.0, &yc, &yd);
3217 assert_eq!(h.n_rows(), 1);
3218 }
3219
3220 #[test]
3221 fn relax_bounds_widens_uniquely_owned_bounds() {
3222 let (_adapter, mut nlp) = build_orig_nlp();
3225 let x_l_before = nlp.x_l.values().to_vec();
3226 let x_u_before = nlp.x_u.values().to_vec();
3227 nlp.relax_bounds(1e-2, 1.0);
3228 for (b, a) in x_l_before.iter().zip(nlp.x_l.values()) {
3229 assert!(a < b, "x_l should relax downward: {a} !< {b}");
3230 }
3231 for (b, a) in x_u_before.iter().zip(nlp.x_u.values()) {
3232 assert!(a > b, "x_u should relax upward: {a} !> {b}");
3233 }
3234 }
3235
3236 #[test]
3237 #[should_panic(expected = "x_l is uniquely owned")]
3238 fn relax_bounds_panics_on_shared_bound_rc() {
3239 let (_adapter, mut nlp) = build_orig_nlp();
3244 let _shared = Rc::clone(&nlp.x_l); nlp.relax_bounds(1e-2, 1.0);
3246 }
3247}