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