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 declared_d_l: RefCell<Option<Vec<Number>>>,
161 declared_d_u: RefCell<Option<Vec<Number>>>,
162 declared_x_l: RefCell<Option<Vec<Number>>>,
168 declared_x_u: RefCell<Option<Vec<Number>>>,
169 honor_original_bounds: Cell<bool>,
177 x_scaling_rejected: Cell<bool>,
186
187 x_space: Rc<DenseVectorSpace>,
189 c_space: Rc<DenseVectorSpace>,
190 d_space: Rc<DenseVectorSpace>,
191 x_l_space: Rc<DenseVectorSpace>,
192 x_u_space: Rc<DenseVectorSpace>,
193 d_l_space: Rc<DenseVectorSpace>,
194 d_u_space: Rc<DenseVectorSpace>,
195 px_l_space: Rc<ExpansionMatrixSpace>,
196 px_u_space: Rc<ExpansionMatrixSpace>,
197 pd_l_space: Rc<ExpansionMatrixSpace>,
198 pd_u_space: Rc<ExpansionMatrixSpace>,
199 jac_c_space: Rc<GenTMatrixSpace>,
200 jac_d_space: Rc<GenTMatrixSpace>,
201 h_space: Option<Rc<SymTMatrixSpace>>,
204
205 x_l: Rc<DenseVector>,
207 x_u: Rc<DenseVector>,
208 d_l: Rc<DenseVector>,
209 d_u: Rc<DenseVector>,
210 c_rhs: Vec<Number>,
217 warm_start_snapshot: RefCell<Option<StartingPointSnapshot>>,
220
221 px_l: Rc<dyn Matrix>,
223 px_u: Rc<dyn Matrix>,
224 pd_l: Rc<dyn Matrix>,
225 pd_u: Rc<dyn Matrix>,
226
227 jac_c_entry_in_g: Vec<Index>,
231 jac_d_entry_in_g: Vec<Index>,
233 nnz_jac_g_full: Index,
235
236 nnz_h_lag_full: Index,
240 h_entry_in_full: Vec<Index>,
245
246 f_cache: RefCell<Cache<Number>>,
248 grad_f_cache: RefCell<Cache<Rc<dyn Vector>>>,
249 c_cache: RefCell<Cache<Rc<dyn Vector>>>,
250 d_cache: RefCell<Cache<Rc<dyn Vector>>>,
251 jac_c_cache: RefCell<Cache<Rc<dyn Matrix>>>,
252 jac_d_cache: RefCell<Cache<Rc<dyn Matrix>>>,
253 h_cache: RefCell<Cache<Rc<dyn SymMatrix>>>,
254 full_g_cache: RefCell<Cache<Rc<Vec<Number>>>>,
262 full_jac_g_cache: RefCell<Cache<Rc<Vec<Number>>>>,
263
264 f_evals: RefCell<Index>,
266 grad_f_evals: RefCell<Index>,
267 c_evals: RefCell<Index>,
268 d_evals: RefCell<Index>,
269 jac_c_evals: RefCell<Index>,
270 jac_d_evals: RefCell<Index>,
271 h_evals: RefCell<Index>,
272
273 info: NlpInfo,
276
277 timing: RefCell<Option<Rc<TimingStatistics>>>,
282}
283
284#[derive(Clone)]
285struct StartingPointSnapshot {
286 x: Vec<Number>,
287 z_l: Vec<Number>,
288 z_u: Vec<Number>,
289 lambda: Vec<Number>,
290}
291
292impl std::fmt::Debug for OrigIpoptNlp {
293 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
294 f.debug_struct("OrigIpoptNlp")
295 .field("info", &self.info)
296 .field("f_evals", &*self.f_evals.borrow())
297 .field("grad_f_evals", &*self.grad_f_evals.borrow())
298 .field("c_evals", &*self.c_evals.borrow())
299 .field("d_evals", &*self.d_evals.borrow())
300 .field("jac_c_evals", &*self.jac_c_evals.borrow())
301 .field("jac_d_evals", &*self.jac_d_evals.borrow())
302 .field("h_evals", &*self.h_evals.borrow())
303 .finish_non_exhaustive()
304 }
305}
306
307impl OrigIpoptNlp {
308 pub fn new(
314 adapter: Rc<RefCell<TNLPAdapter>>,
315 scaling: Rc<dyn NlpScaling>,
316 ) -> Result<Self, String> {
317 let (info, classification) = {
319 let a = adapter.borrow();
320 (*a.nlp_info(), a.classification().clone())
321 };
322
323 let n_x_var = classification.n_x_var();
325 let x_space = DenseVectorSpace::new(n_x_var);
326 let c_space = DenseVectorSpace::new(classification.n_c);
327 let d_space = DenseVectorSpace::new(classification.n_d);
328 let x_l_space = DenseVectorSpace::new(classification.n_x_l());
329 let x_u_space = DenseVectorSpace::new(classification.n_x_u());
330 let d_l_space = DenseVectorSpace::new(classification.n_d_l());
331 let d_u_space = DenseVectorSpace::new(classification.n_d_u());
332
333 let px_l_space =
335 ExpansionMatrixSpace::new(n_x_var, classification.n_x_l(), &classification.x_l_map, 0);
336 let px_u_space =
337 ExpansionMatrixSpace::new(n_x_var, classification.n_x_u(), &classification.x_u_map, 0);
338 let pd_l_space = ExpansionMatrixSpace::new(
339 classification.n_d,
340 classification.n_d_l(),
341 &classification.d_l_map,
342 0,
343 );
344 let pd_u_space = ExpansionMatrixSpace::new(
345 classification.n_d,
346 classification.n_d_u(),
347 &classification.d_u_map,
348 0,
349 );
350 let px_l: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&px_l_space)));
351 let px_u: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&px_u_space)));
352 let pd_l: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&pd_l_space)));
353 let pd_u: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&pd_u_space)));
354
355 let n_full_x = classification.n_full_x as usize;
359 let n_full_g = classification.n_full_g as usize;
360 let mut full_x_l = vec![0.0; n_full_x];
361 let mut full_x_u = vec![0.0; n_full_x];
362 let mut full_g_l = vec![0.0; n_full_g];
363 let mut full_g_u = vec![0.0; n_full_g];
364 {
365 let a = adapter.borrow();
366 let mut t = a.tnlp().borrow_mut();
367 let ok = t.get_bounds_info(crate::tnlp::BoundsInfo {
368 x_l: &mut full_x_l,
369 x_u: &mut full_x_u,
370 g_l: &mut full_g_l,
371 g_u: &mut full_g_u,
372 });
373 if !ok {
374 return Err("TNLP::get_bounds_info returned false on second call".into());
375 }
376 }
377
378 let x_l = make_dense_from(&x_l_space, |i| {
379 let var_idx = classification.x_l_map[i] as usize;
381 let full_idx = classification.x_not_fixed_map[var_idx] as usize;
382 full_x_l[full_idx]
383 });
384 let x_u = make_dense_from(&x_u_space, |i| {
385 let var_idx = classification.x_u_map[i] as usize;
386 let full_idx = classification.x_not_fixed_map[var_idx] as usize;
387 full_x_u[full_idx]
388 });
389 let d_l = make_dense_from(&d_l_space, |i| {
390 let d_idx = classification.d_l_map[i] as usize;
392 let full_g_idx = classification.d_map[d_idx] as usize;
393 full_g_l[full_g_idx]
394 });
395 let d_u = make_dense_from(&d_u_space, |i| {
396 let d_idx = classification.d_u_map[i] as usize;
397 let full_g_idx = classification.d_map[d_idx] as usize;
398 full_g_u[full_g_idx]
399 });
400
401 let c_rhs: Vec<Number> = classification
407 .c_map
408 .iter()
409 .map(|&g_idx| full_g_l[g_idx as usize])
410 .collect();
411
412 let mut full_irow = vec![0 as Index; info.nnz_jac_g as usize];
422 let mut full_jcol = vec![0 as Index; info.nnz_jac_g as usize];
423 {
424 let a = adapter.borrow();
425 let mut t = a.tnlp().borrow_mut();
426 let ok = t.eval_jac_g(
427 None,
428 false,
429 SparsityRequest::Structure {
430 irow: &mut full_irow,
431 jcol: &mut full_jcol,
432 },
433 );
434 if !ok {
435 return Err("TNLP::eval_jac_g(Structure) returned false".into());
436 }
437 }
438
439 let mut g_to_c = vec![-1 as Index; n_full_g];
441 for (c_idx, &g_idx) in classification.c_map.iter().enumerate() {
442 g_to_c[g_idx as usize] = c_idx as Index;
443 }
444 let mut g_to_d = vec![-1 as Index; n_full_g];
445 for (d_idx, &g_idx) in classification.d_map.iter().enumerate() {
446 g_to_d[g_idx as usize] = d_idx as Index;
447 }
448
449 let style_offset = match info.index_style {
450 crate::tnlp::IndexStyle::C => 0 as Index,
451 crate::tnlp::IndexStyle::Fortran => 1 as Index,
452 };
453
454 let mut jac_c_irow_1based = Vec::new();
455 let mut jac_c_jcol_1based = Vec::new();
456 let mut jac_c_entry_in_g = Vec::new();
457 let mut jac_d_irow_1based = Vec::new();
458 let mut jac_d_jcol_1based = Vec::new();
459 let mut jac_d_entry_in_g = Vec::new();
460
461 let full_to_var = &classification.full_to_var;
465 for k in 0..info.nnz_jac_g as usize {
466 let g_row_0 = (full_irow[k] - style_offset) as usize;
467 let x_col_0 = (full_jcol[k] - style_offset) as usize;
468 let var_col = full_to_var[x_col_0];
469 if var_col < 0 {
470 continue;
471 }
472 let col_1based = var_col + 1;
474 let c_row = g_to_c[g_row_0];
475 if c_row >= 0 {
476 jac_c_irow_1based.push(c_row + 1);
477 jac_c_jcol_1based.push(col_1based);
478 jac_c_entry_in_g.push(k as Index);
479 } else {
480 let d_row = g_to_d[g_row_0];
481 debug_assert!(d_row >= 0, "g row {g_row_0} is neither in c_map nor d_map");
482 jac_d_irow_1based.push(d_row + 1);
483 jac_d_jcol_1based.push(col_1based);
484 jac_d_entry_in_g.push(k as Index);
485 }
486 }
487
488 let jac_c_space = GenTMatrixSpace::new(
489 classification.n_c,
490 n_x_var,
491 jac_c_irow_1based,
492 jac_c_jcol_1based,
493 );
494 let jac_d_space = GenTMatrixSpace::new(
495 classification.n_d,
496 n_x_var,
497 jac_d_irow_1based,
498 jac_d_jcol_1based,
499 );
500
501 let nnz_h_lag_full = info.nnz_h_lag;
506 let mut h_entry_in_full: Vec<Index> = Vec::new();
507 let h_space = if info.nnz_h_lag > 0 {
508 let mut h_irow = vec![0 as Index; info.nnz_h_lag as usize];
509 let mut h_jcol = vec![0 as Index; info.nnz_h_lag as usize];
510 let supports_h = {
511 let a = adapter.borrow();
512 let mut t = a.tnlp().borrow_mut();
513 t.eval_h(
514 None,
515 false,
516 1.0,
517 None,
518 false,
519 SparsityRequest::Structure {
520 irow: &mut h_irow,
521 jcol: &mut h_jcol,
522 },
523 )
524 };
525 if supports_h {
526 let mut h_irow_1: Vec<Index> = Vec::with_capacity(h_irow.len());
532 let mut h_jcol_1: Vec<Index> = Vec::with_capacity(h_jcol.len());
533 for k in 0..h_irow.len() {
534 let i_full = (h_irow[k] - style_offset) as usize;
535 let j_full = (h_jcol[k] - style_offset) as usize;
536 let i_var = full_to_var[i_full];
537 let j_var = full_to_var[j_full];
538 if i_var < 0 || j_var < 0 {
539 continue;
540 }
541 h_irow_1.push(i_var + 1);
542 h_jcol_1.push(j_var + 1);
543 h_entry_in_full.push(k as Index);
544 }
545 Some(SymTMatrixSpace::new(n_x_var, h_irow_1, h_jcol_1))
546 } else {
547 None
549 }
550 } else {
551 Some(SymTMatrixSpace::new(n_x_var, Vec::new(), Vec::new()))
555 };
556
557 let initial_obj_scal = scaling.obj_scaling();
563 Ok(Self {
564 adapter,
565 scaling,
566 obj_scale_factor: Cell::new(initial_obj_scal),
567 computed_obj_scale: Cell::new(1.0),
568 c_scale: RefCell::new(None),
569 d_scale: RefCell::new(None),
570 declared_d_l: RefCell::new(None),
571 declared_d_u: RefCell::new(None),
572 declared_x_l: RefCell::new(None),
573 declared_x_u: RefCell::new(None),
574 honor_original_bounds: Cell::new(false),
575 x_scaling_rejected: Cell::new(false),
576 x_space,
577 c_space,
578 d_space,
579 x_l_space,
580 x_u_space,
581 d_l_space,
582 d_u_space,
583 px_l_space,
584 px_u_space,
585 pd_l_space,
586 pd_u_space,
587 jac_c_space,
588 jac_d_space,
589 h_space,
590 x_l: Rc::new(x_l),
591 x_u: Rc::new(x_u),
592 d_l: Rc::new(d_l),
593 d_u: Rc::new(d_u),
594 c_rhs,
595 warm_start_snapshot: RefCell::new(None),
596 px_l,
597 px_u,
598 pd_l,
599 pd_u,
600 jac_c_entry_in_g,
601 jac_d_entry_in_g,
602 nnz_jac_g_full: info.nnz_jac_g,
603 nnz_h_lag_full,
604 h_entry_in_full,
605 f_cache: RefCell::new(Cache::new(1)),
606 grad_f_cache: RefCell::new(Cache::new(1)),
607 c_cache: RefCell::new(Cache::new(1)),
608 d_cache: RefCell::new(Cache::new(1)),
609 jac_c_cache: RefCell::new(Cache::new(1)),
610 jac_d_cache: RefCell::new(Cache::new(1)),
611 h_cache: RefCell::new(Cache::new(1)),
612 full_g_cache: RefCell::new(Cache::new(1)),
613 full_jac_g_cache: RefCell::new(Cache::new(1)),
614 f_evals: RefCell::new(0),
615 grad_f_evals: RefCell::new(0),
616 c_evals: RefCell::new(0),
617 d_evals: RefCell::new(0),
618 jac_c_evals: RefCell::new(0),
619 jac_d_evals: RefCell::new(0),
620 h_evals: RefCell::new(0),
621 info,
622 timing: RefCell::new(None),
623 })
624 }
625
626 pub fn set_timing_stats(&self, t: Rc<TimingStatistics>) {
632 *self.timing.borrow_mut() = Some(t);
633 }
634
635 fn timed_eval<R, F>(&self, pick: fn(&TimingStatistics) -> &pounce_common::TimedTask, f: F) -> R
640 where
641 F: FnOnce() -> R,
642 {
643 let guard = self.timing.borrow();
644 match guard.as_deref() {
645 Some(t) => {
646 let task = pick(t);
647 task.start();
648 t.total_function_evaluation_time.start();
649 let r = f();
650 t.total_function_evaluation_time.end();
651 task.end();
652 r
653 }
654 None => {
655 drop(guard);
656 f()
657 }
658 }
659 }
660
661 pub fn nlp_info(&self) -> &NlpInfo {
664 &self.info
665 }
666 pub fn classification_n_x_var(&self) -> Index {
667 self.x_space.dim()
668 }
669 pub fn x_space(&self) -> &Rc<DenseVectorSpace> {
670 &self.x_space
671 }
672 pub fn c_space(&self) -> &Rc<DenseVectorSpace> {
673 &self.c_space
674 }
675 pub fn d_space(&self) -> &Rc<DenseVectorSpace> {
676 &self.d_space
677 }
678 pub fn x_l_space(&self) -> &Rc<DenseVectorSpace> {
679 &self.x_l_space
680 }
681 pub fn x_u_space(&self) -> &Rc<DenseVectorSpace> {
682 &self.x_u_space
683 }
684 pub fn d_l_space(&self) -> &Rc<DenseVectorSpace> {
685 &self.d_l_space
686 }
687 pub fn d_u_space(&self) -> &Rc<DenseVectorSpace> {
688 &self.d_u_space
689 }
690 pub fn px_l_space(&self) -> &Rc<ExpansionMatrixSpace> {
691 &self.px_l_space
692 }
693 pub fn px_u_space(&self) -> &Rc<ExpansionMatrixSpace> {
694 &self.px_u_space
695 }
696 pub fn pd_l_space(&self) -> &Rc<ExpansionMatrixSpace> {
697 &self.pd_l_space
698 }
699 pub fn pd_u_space(&self) -> &Rc<ExpansionMatrixSpace> {
700 &self.pd_u_space
701 }
702 pub fn jac_c_space(&self) -> &Rc<GenTMatrixSpace> {
703 &self.jac_c_space
704 }
705 pub fn jac_d_space(&self) -> &Rc<GenTMatrixSpace> {
706 &self.jac_d_space
707 }
708 pub fn h_space(&self) -> Option<&Rc<SymTMatrixSpace>> {
709 self.h_space.as_ref()
710 }
711
712 pub fn obj_scale_factor(&self) -> Number {
715 self.obj_scale_factor.get()
716 }
717
718 pub fn relax_bounds(&mut self, bound_relax_factor: Number, constr_viol_tol: Number) {
730 *self.declared_d_l.borrow_mut() = Some(self.d_l.expanded_values());
735 *self.declared_d_u.borrow_mut() = Some(self.d_u.expanded_values());
736 *self.declared_x_l.borrow_mut() = Some(self.x_l.expanded_values());
740 *self.declared_x_u.borrow_mut() = Some(self.x_u.expanded_values());
741 if bound_relax_factor <= 0.0 {
742 return;
743 }
744 let relax = bound_relax_factor.abs();
745 let cap = constr_viol_tol;
746 let apply = |v: &mut DenseVector, sign: Number| {
747 let xs = v.values_mut();
748 for x in xs.iter_mut() {
749 let delta = (relax * x.abs().max(1.0)).min(cap);
750 *x += sign * delta;
751 }
752 };
753 let apply_d = |v: &mut DenseVector, sign: Number| {
771 let rel_width = relax.min(cap);
772 let xs = v.values_mut();
773 for x in xs.iter_mut() {
774 let scale = if *x == 0.0 { 1.0 } else { x.abs() };
775 *x += sign * rel_width * scale;
776 }
777 };
778 apply(
785 Rc::get_mut(&mut self.x_l).expect("relax_bounds: x_l is uniquely owned"),
786 -1.0,
787 );
788 apply(
789 Rc::get_mut(&mut self.x_u).expect("relax_bounds: x_u is uniquely owned"),
790 1.0,
791 );
792 apply_d(
793 Rc::get_mut(&mut self.d_l).expect("relax_bounds: d_l is uniquely owned"),
794 -1.0,
795 );
796 apply_d(
797 Rc::get_mut(&mut self.d_u).expect("relax_bounds: d_u is uniquely owned"),
798 1.0,
799 );
800 }
801
802 pub fn determine_scaling_from_starting_point(
824 &mut self,
825 method: ScalingMethod,
826 max_gradient: Number,
827 min_value: Number,
828 obj_target_gradient: Number,
829 constr_target_gradient: Number,
830 ) {
831 let user_obj_factor = self.scaling.obj_scaling();
834 if matches!(method, ScalingMethod::None) {
835 self.obj_scale_factor.set(user_obj_factor);
836 *self.c_scale.borrow_mut() = None;
837 *self.d_scale.borrow_mut() = None;
838 self.invalidate_eval_caches();
839 return;
840 }
841
842 let cls = self.adapter.borrow().classification().clone();
844 let n_full_x = cls.n_full_x as usize;
845 let n_full_g = cls.n_full_g as usize;
846 let mut full_x = vec![0.0; n_full_x];
847 let mut full_z_l = vec![0.0; n_full_x];
848 let mut full_z_u = vec![0.0; n_full_x];
849 let mut full_lambda = vec![0.0; n_full_g];
850 let starting_ok = {
851 let a = self.adapter.borrow();
852 let mut t = a.tnlp().borrow_mut();
853 t.get_starting_point(StartingPoint {
854 init_x: true,
855 x: &mut full_x,
856 init_z: false,
857 z_l: &mut full_z_l,
858 z_u: &mut full_z_u,
859 init_lambda: false,
860 lambda: &mut full_lambda,
861 })
862 };
863 if !starting_ok {
864 self.obj_scale_factor.set(user_obj_factor);
866 *self.c_scale.borrow_mut() = None;
867 *self.d_scale.borrow_mut() = None;
868 self.invalidate_eval_caches();
869 return;
870 }
871
872 for (i, &full_idx) in cls.x_fixed_map.iter().enumerate() {
885 full_x[full_idx as usize] = cls.x_fixed_vals[i];
886 }
887
888 match method {
889 ScalingMethod::None => unreachable!("handled above"),
890 ScalingMethod::GradientBased => {
891 self.scale_gradient_based(
892 &cls,
893 &full_x,
894 user_obj_factor,
895 max_gradient,
896 min_value,
897 obj_target_gradient,
898 constr_target_gradient,
899 );
900 }
901 ScalingMethod::UserScaling => {
902 let applied = self.scale_user_supplied(&cls, user_obj_factor, min_value);
903 if !applied {
904 self.obj_scale_factor.set(user_obj_factor);
908 *self.c_scale.borrow_mut() = None;
909 *self.d_scale.borrow_mut() = None;
910 }
911 }
912 }
913
914 self.apply_d_scale_to_bounds();
917
918 self.invalidate_eval_caches();
921 }
922
923 fn scale_gradient_based(
926 &self,
927 cls: &BoundClassification,
928 full_x: &[Number],
929 user_obj_factor: Number,
930 max_gradient: Number,
931 min_value: Number,
932 obj_target_gradient: Number,
933 constr_target_gradient: Number,
934 ) {
935 let n_full_x = cls.n_full_x as usize;
936 let n_full_g = cls.n_full_g as usize;
937
938 let mut full_grad_f = vec![0.0; n_full_x];
940 let grad_ok = {
941 let a = self.adapter.borrow();
942 let mut t = a.tnlp().borrow_mut();
943 t.eval_grad_f(full_x, true, &mut full_grad_f)
944 };
945 let mut df = 1.0;
946 if grad_ok {
947 let mut max_grad_f: Number = 0.0;
950 for &full_idx in cls.x_not_fixed_map.iter() {
951 let v = full_grad_f[full_idx as usize].abs();
952 if v > max_grad_f {
953 max_grad_f = v;
954 }
955 }
956 if obj_target_gradient > 0.0 && max_grad_f > 0.0 {
957 df = obj_target_gradient / max_grad_f;
960 } else if max_grad_f > max_gradient {
961 df = max_gradient / max_grad_f;
962 }
963 if df < min_value {
964 df = min_value;
965 }
966 }
967 self.computed_obj_scale.set(df);
968 self.obj_scale_factor.set(df * user_obj_factor);
969
970 if cls.n_full_g == 0 {
972 *self.c_scale.borrow_mut() = None;
973 *self.d_scale.borrow_mut() = None;
974 return;
975 }
976 let mut full_jac_vals = vec![0.0; self.nnz_jac_g_full as usize];
978 let jac_ok = {
979 let a = self.adapter.borrow();
980 let mut t = a.tnlp().borrow_mut();
981 t.eval_jac_g(
982 Some(full_x),
983 true,
984 SparsityRequest::Values {
985 values: &mut full_jac_vals,
986 },
987 )
988 };
989 if !jac_ok {
990 *self.c_scale.borrow_mut() = None;
991 *self.d_scale.borrow_mut() = None;
992 return;
993 }
994 let mut full_irow = vec![0 as Index; self.nnz_jac_g_full as usize];
996 let mut full_jcol = vec![0 as Index; self.nnz_jac_g_full as usize];
997 let _ = {
998 let a = self.adapter.borrow();
999 let mut t = a.tnlp().borrow_mut();
1000 t.eval_jac_g(
1001 None,
1002 false,
1003 SparsityRequest::Structure {
1004 irow: &mut full_irow,
1005 jcol: &mut full_jcol,
1006 },
1007 )
1008 };
1009 let style_offset: Index = match self.info.index_style {
1010 crate::tnlp::IndexStyle::C => 0,
1011 crate::tnlp::IndexStyle::Fortran => 1,
1012 };
1013 let mut g_to_c = vec![-1 as Index; n_full_g];
1015 for (c_idx, &g_idx) in cls.c_map.iter().enumerate() {
1016 g_to_c[g_idx as usize] = c_idx as Index;
1017 }
1018 let mut g_to_d = vec![-1 as Index; n_full_g];
1019 for (d_idx, &g_idx) in cls.d_map.iter().enumerate() {
1020 g_to_d[g_idx as usize] = d_idx as Index;
1021 }
1022 let n_c = cls.n_c as usize;
1023 let n_d = cls.n_d as usize;
1024 let dbl_min = Number::MIN_POSITIVE;
1026 let mut c_row_max: Vec<Number> = vec![dbl_min; n_c];
1027 let mut d_row_max: Vec<Number> = vec![dbl_min; n_d];
1028 for k in 0..self.nnz_jac_g_full as usize {
1029 let g_row_0 = (full_irow[k] - style_offset) as usize;
1030 let v = full_jac_vals[k].abs();
1031 let cr = g_to_c[g_row_0];
1032 if cr >= 0 {
1033 let row = cr as usize;
1034 if v > c_row_max[row] {
1035 c_row_max[row] = v;
1036 }
1037 } else {
1038 let dr = g_to_d[g_row_0];
1039 if dr >= 0 {
1040 let row = dr as usize;
1041 if v > d_row_max[row] {
1042 d_row_max[row] = v;
1043 }
1044 }
1045 }
1046 }
1047
1048 let row_max_to_scale = |row_max: Number| -> Number {
1049 let mut s = if constr_target_gradient > 0.0 {
1054 constr_target_gradient / row_max
1055 } else {
1056 let raw = max_gradient / row_max;
1057 if raw > 1.0 { 1.0 } else { raw }
1058 };
1059 if s < min_value {
1060 s = min_value;
1061 }
1062 s
1063 };
1064 let any_row_above = |rows: &[Number]| -> bool {
1065 constr_target_gradient > 0.0 || rows.iter().any(|&v| v > max_gradient)
1066 };
1067
1068 if n_c > 0 && any_row_above(&c_row_max) {
1069 let dc: Vec<Number> = c_row_max.iter().map(|&v| row_max_to_scale(v)).collect();
1070 *self.c_scale.borrow_mut() = Some(dc);
1071 } else {
1072 *self.c_scale.borrow_mut() = None;
1073 }
1074
1075 if n_d > 0 && any_row_above(&d_row_max) {
1076 let dd: Vec<Number> = d_row_max.iter().map(|&v| row_max_to_scale(v)).collect();
1077 *self.d_scale.borrow_mut() = Some(dd);
1078 } else {
1079 *self.d_scale.borrow_mut() = None;
1080 }
1081 }
1082
1083 fn scale_user_supplied(
1100 &self,
1101 cls: &BoundClassification,
1102 user_obj_factor: Number,
1103 min_value: Number,
1104 ) -> bool {
1105 let n_full_x = cls.n_full_x as usize;
1106 let n_full_g = cls.n_full_g as usize;
1107 let mut obj_scaling: Number = 1.0;
1108 let mut use_x_scaling = false;
1109 let mut x_scaling = vec![1.0; n_full_x];
1110 let mut use_g_scaling = false;
1111 let mut g_scaling = vec![1.0; n_full_g];
1112 let ok = {
1113 let a = self.adapter.borrow();
1114 let mut t = a.tnlp().borrow_mut();
1115 t.get_scaling_parameters(ScalingRequest {
1116 obj_scaling: &mut obj_scaling,
1117 use_x_scaling: &mut use_x_scaling,
1118 x_scaling: &mut x_scaling,
1119 use_g_scaling: &mut use_g_scaling,
1120 g_scaling: &mut g_scaling,
1121 })
1122 };
1123 if !ok {
1124 return false;
1125 }
1126
1127 let mut df = obj_scaling;
1131 if df.abs() < min_value {
1132 df = df.signum().max(0.0).max(1.0) * min_value;
1135 }
1136 self.obj_scale_factor.set(df * user_obj_factor);
1137
1138 if use_g_scaling && g_scaling.len() == n_full_g {
1140 let n_c = cls.n_c as usize;
1141 let n_d = cls.n_d as usize;
1142 let mut dc = vec![1.0; n_c];
1143 for (c_idx, &g_idx) in cls.c_map.iter().enumerate() {
1144 let s = g_scaling[g_idx as usize];
1145 dc[c_idx] = if s < min_value { min_value } else { s };
1146 }
1147 let mut dd = vec![1.0; n_d];
1148 for (d_idx, &g_idx) in cls.d_map.iter().enumerate() {
1149 let s = g_scaling[g_idx as usize];
1150 dd[d_idx] = if s < min_value { min_value } else { s };
1151 }
1152 let nontrivial_c = dc.iter().any(|&s| s != 1.0);
1155 *self.c_scale.borrow_mut() = if nontrivial_c && n_c > 0 {
1156 Some(dc)
1157 } else {
1158 None
1159 };
1160 let nontrivial_d = dd.iter().any(|&s| s != 1.0);
1161 *self.d_scale.borrow_mut() = if nontrivial_d && n_d > 0 {
1162 Some(dd)
1163 } else {
1164 None
1165 };
1166 } else {
1167 *self.c_scale.borrow_mut() = None;
1168 *self.d_scale.borrow_mut() = None;
1169 }
1170 if use_x_scaling && x_scaling.iter().any(|&s| s != 1.0) {
1174 self.x_scaling_rejected.set(true);
1175 }
1176 true
1177 }
1178
1179 pub fn user_x_scaling_rejected(&self) -> bool {
1185 self.x_scaling_rejected.get()
1186 }
1187
1188 fn apply_d_scale_to_bounds(&mut self) {
1193 let cls = self.adapter.borrow().classification().clone();
1194 if let Some(dd) = self.d_scale.borrow().as_ref() {
1195 if let Some(d_l) = Rc::get_mut(&mut self.d_l) {
1196 let xs = d_l.values_mut();
1197 for (i, slot) in xs.iter_mut().enumerate() {
1198 let d_idx = cls.d_l_map[i] as usize;
1199 *slot *= dd[d_idx];
1200 }
1201 }
1202 if let Some(d_u) = Rc::get_mut(&mut self.d_u) {
1203 let xs = d_u.values_mut();
1204 for (i, slot) in xs.iter_mut().enumerate() {
1205 let d_idx = cls.d_u_map[i] as usize;
1206 *slot *= dd[d_idx];
1207 }
1208 }
1209 }
1210 }
1211
1212 fn invalidate_eval_caches(&self) {
1213 self.f_cache.borrow_mut().clear();
1214 self.grad_f_cache.borrow_mut().clear();
1215 self.c_cache.borrow_mut().clear();
1216 self.d_cache.borrow_mut().clear();
1217 self.jac_c_cache.borrow_mut().clear();
1218 self.jac_d_cache.borrow_mut().clear();
1219 self.h_cache.borrow_mut().clear();
1220 }
1221
1222 pub fn f_evals(&self) -> Index {
1223 *self.f_evals.borrow()
1224 }
1225 pub fn grad_f_evals(&self) -> Index {
1226 *self.grad_f_evals.borrow()
1227 }
1228 pub fn c_evals(&self) -> Index {
1229 *self.c_evals.borrow()
1230 }
1231 pub fn d_evals(&self) -> Index {
1232 *self.d_evals.borrow()
1233 }
1234 pub fn jac_c_evals(&self) -> Index {
1235 *self.jac_c_evals.borrow()
1236 }
1237 pub fn jac_d_evals(&self) -> Index {
1238 *self.jac_d_evals.borrow()
1239 }
1240 pub fn h_evals(&self) -> Index {
1241 *self.h_evals.borrow()
1242 }
1243
1244 pub fn lift_x_to_full(&self, x: &dyn Vector) -> Vec<Number> {
1250 let Some(dx) = x.as_any().downcast_ref::<DenseVector>() else {
1251 panic!("OrigIpoptNlp expects DenseVector for x");
1252 };
1253 let a = self.adapter.borrow();
1254 let cls = a.classification();
1255 let mut full = vec![0.0; cls.n_full_x as usize];
1256 let vals = dx.expanded_values();
1257 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
1258 full[full_idx as usize] = vals[var_idx];
1259 }
1260 for (i, &full_idx) in cls.x_fixed_map.iter().enumerate() {
1261 full[full_idx as usize] = cls.x_fixed_vals[i];
1262 }
1263 full
1264 }
1265
1266 pub fn set_honor_original_bounds(&self, on: bool) {
1270 self.honor_original_bounds.set(on);
1271 }
1272
1273 pub fn finalize_solution_x(&self, x: &dyn Vector) -> Vec<Number> {
1291 let mut full = self.lift_x_to_full(x);
1292 if !self.honor_original_bounds.get() {
1293 return full;
1294 }
1295 let cls = self.adapter.borrow().classification().clone();
1296 if let Some(x_l) = self.declared_x_l.borrow().as_ref() {
1299 for (i, &var_idx) in cls.x_l_map.iter().enumerate() {
1300 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
1301 if full[full_idx] < x_l[i] {
1302 full[full_idx] = x_l[i];
1303 }
1304 }
1305 }
1306 if let Some(x_u) = self.declared_x_u.borrow().as_ref() {
1307 for (i, &var_idx) in cls.x_u_map.iter().enumerate() {
1308 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
1309 if full[full_idx] > x_u[i] {
1310 full[full_idx] = x_u[i];
1311 }
1312 }
1313 }
1314 full
1315 }
1316
1317 pub fn finalize_solution_lambda(&self, y_c: &dyn Vector, y_d: &dyn Vector) -> Vec<Number> {
1329 let cls = self.adapter.borrow().classification().clone();
1330 let mut lambda = self.pack_lambda_for_user(y_c, y_d, &cls);
1331 let obj_scal = self.obj_scale_factor.get();
1332 if obj_scal != 0.0 && obj_scal != 1.0 {
1333 let inv = 1.0 / obj_scal;
1334 for v in lambda.iter_mut() {
1335 *v *= inv;
1336 }
1337 }
1338 lambda
1339 }
1340
1341 pub fn finalize_solution_z_l(&self, z_l: &dyn Vector) -> Vec<Number> {
1349 let cls = self.adapter.borrow().classification().clone();
1350 let n_full_x = cls.n_full_x as usize;
1351 let mut full_z_l = vec![0.0; n_full_x];
1352 let n_x_l = self.x_l.dim() as usize;
1353 if n_x_l == 0 {
1354 return full_z_l;
1355 }
1356 let Some(dz) = z_l.as_any().downcast_ref::<DenseVector>() else {
1357 panic!("OrigIpoptNlp::finalize_solution_z_l expects DenseVector");
1358 };
1359 let vals = dz.expanded_values();
1360 let obj_scal = self.obj_scale_factor.get();
1361 let inv = if obj_scal == 0.0 { 1.0 } else { 1.0 / obj_scal };
1362 for i in 0..n_x_l {
1363 let var_idx = cls.x_l_map[i] as usize;
1364 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1365 full_z_l[full_idx] = vals[i] * inv;
1366 }
1367 full_z_l
1368 }
1369
1370 pub fn finalize_solution_z_u(&self, z_u: &dyn Vector) -> Vec<Number> {
1373 let cls = self.adapter.borrow().classification().clone();
1374 let n_full_x = cls.n_full_x as usize;
1375 let mut full_z_u = vec![0.0; n_full_x];
1376 let n_x_u = self.x_u.dim() as usize;
1377 if n_x_u == 0 {
1378 return full_z_u;
1379 }
1380 let Some(dz) = z_u.as_any().downcast_ref::<DenseVector>() else {
1381 panic!("OrigIpoptNlp::finalize_solution_z_u expects DenseVector");
1382 };
1383 let vals = dz.expanded_values();
1384 let obj_scal = self.obj_scale_factor.get();
1385 let inv = if obj_scal == 0.0 { 1.0 } else { 1.0 / obj_scal };
1386 for i in 0..n_x_u {
1387 let var_idx = cls.x_u_map[i] as usize;
1388 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1389 full_z_u[full_idx] = vals[i] * inv;
1390 }
1391 full_z_u
1392 }
1393
1394 pub fn pack_lambda_for_user(
1404 &self,
1405 y_c: &dyn Vector,
1406 y_d: &dyn Vector,
1407 cls: &BoundClassification,
1408 ) -> Vec<Number> {
1409 let mut lambda = vec![0.0; cls.n_full_g as usize];
1410 if cls.n_c > 0 {
1411 let Some(dy) = y_c.as_any().downcast_ref::<DenseVector>() else {
1412 panic!("OrigIpoptNlp expects DenseVector for y_c");
1413 };
1414 let vals = dy.expanded_values();
1415 let cs = self.c_scale.borrow();
1416 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1417 lambda[g_idx as usize] = match cs.as_ref() {
1418 Some(v) => vals[i] * v[i],
1419 None => vals[i],
1420 };
1421 }
1422 }
1423 if cls.n_d > 0 {
1424 let Some(dy) = y_d.as_any().downcast_ref::<DenseVector>() else {
1425 panic!("OrigIpoptNlp expects DenseVector for y_d");
1426 };
1427 let vals = dy.expanded_values();
1428 let ds = self.d_scale.borrow();
1429 for (i, &g_idx) in cls.d_map.iter().enumerate() {
1430 lambda[g_idx as usize] = match ds.as_ref() {
1431 Some(v) => vals[i] * v[i],
1432 None => vals[i],
1433 };
1434 }
1435 }
1436 lambda
1437 }
1438
1439 fn fetch_warm_start_snapshot(&self) -> Option<StartingPointSnapshot> {
1442 let cls = self.adapter.borrow().classification().clone();
1443 let mut snapshot = StartingPointSnapshot {
1444 x: vec![0.0; cls.n_full_x as usize],
1445 z_l: vec![0.0; cls.n_full_x as usize],
1446 z_u: vec![0.0; cls.n_full_x as usize],
1447 lambda: vec![0.0; cls.n_full_g as usize],
1448 };
1449 let ok = {
1450 let a = self.adapter.borrow();
1451 let mut t = a.tnlp().borrow_mut();
1452 t.get_starting_point(StartingPoint {
1453 init_x: true,
1454 x: &mut snapshot.x,
1455 init_z: true,
1456 z_l: &mut snapshot.z_l,
1457 z_u: &mut snapshot.z_u,
1458 init_lambda: true,
1459 lambda: &mut snapshot.lambda,
1460 })
1461 };
1462 ok.then_some(snapshot)
1463 }
1464
1465 #[allow(clippy::too_many_arguments)]
1473 pub fn initialize_starting_point(
1474 &mut self,
1475 x: &mut DenseVector,
1476 init_x: bool,
1477 y_c: &mut DenseVector,
1478 init_y_c: bool,
1479 y_d: &mut DenseVector,
1480 init_y_d: bool,
1481 z_l: &mut DenseVector,
1482 init_z_l: bool,
1483 z_u: &mut DenseVector,
1484 init_z_u: bool,
1485 ) -> bool {
1486 let n_full_x = self.adapter.borrow().classification().n_full_x as usize;
1487 let n_full_g = self.adapter.borrow().classification().n_full_g as usize;
1488 let n_x_l = self.x_l.dim() as usize;
1489 let n_x_u = self.x_u.dim() as usize;
1490
1491 let mut full_x = vec![0.0; n_full_x];
1492 let mut full_z_l = vec![0.0; n_full_x];
1493 let mut full_z_u = vec![0.0; n_full_x];
1494 let mut full_lambda = vec![0.0; n_full_g];
1495
1496 let ok = {
1497 let a = self.adapter.borrow();
1498 let mut t = a.tnlp().borrow_mut();
1499 t.get_starting_point(StartingPoint {
1500 init_x,
1501 x: &mut full_x,
1502 init_z: init_z_l || init_z_u,
1503 z_l: &mut full_z_l,
1504 z_u: &mut full_z_u,
1505 init_lambda: init_y_c || init_y_d,
1506 lambda: &mut full_lambda,
1507 })
1508 };
1509 if !ok {
1510 return false;
1511 }
1512
1513 let cls = self.adapter.borrow().classification().clone();
1514 let obj_scal = self.obj_scale_factor.get();
1515 let c_scale = self.c_scale.borrow();
1516 let d_scale = self.d_scale.borrow();
1517
1518 if init_x {
1520 let xs = x.values_mut();
1521 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
1522 xs[var_idx] = full_x[full_idx as usize];
1523 }
1524 }
1525 if init_y_c && cls.n_c > 0 {
1531 let yc = y_c.values_mut();
1532 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1533 let cs = c_scale.as_ref().map(|v| v[i]).unwrap_or(1.0);
1534 yc[i] = full_lambda[g_idx as usize] / cs * obj_scal;
1535 }
1536 }
1537 if init_y_d && cls.n_d > 0 {
1538 let yd = y_d.values_mut();
1539 for (i, &g_idx) in cls.d_map.iter().enumerate() {
1540 let ds = d_scale.as_ref().map(|v| v[i]).unwrap_or(1.0);
1541 yd[i] = full_lambda[g_idx as usize] / ds * obj_scal;
1542 }
1543 }
1544 if init_z_l && n_x_l > 0 {
1546 let zl = z_l.values_mut();
1547 for (i, slot) in zl.iter_mut().enumerate().take(n_x_l) {
1548 let var_idx = cls.x_l_map[i] as usize;
1549 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1550 *slot = full_z_l[full_idx] * obj_scal;
1551 }
1552 }
1553 if init_z_u && n_x_u > 0 {
1554 let zu = z_u.values_mut();
1555 for (i, slot) in zu.iter_mut().enumerate().take(n_x_u) {
1556 let var_idx = cls.x_u_map[i] as usize;
1557 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1558 *slot = full_z_u[full_idx] * obj_scal;
1559 }
1560 }
1561 true
1562 }
1563
1564 fn eval_f_internal(&self, x: &dyn Vector) -> Number {
1567 if let Some(v) = self.f_cache.borrow().get_1dep(x.as_tagged()) {
1568 return v;
1569 }
1570 *self.f_evals.borrow_mut() += 1;
1571 let full_x = self.lift_x_to_full(x);
1572 let unscaled = {
1573 let a = self.adapter.borrow();
1574 let mut t = a.tnlp().borrow_mut();
1575 t.eval_f(&full_x, true).unwrap_or(f64::NAN)
1580 };
1581 let scaled = unscaled * self.obj_scale_factor.get();
1582 self.f_cache.borrow_mut().add_1dep(scaled, x.as_tagged());
1583 scaled
1584 }
1585
1586 fn eval_grad_f_internal(&self, x: &dyn Vector) -> Rc<dyn Vector> {
1587 if let Some(v) = self.grad_f_cache.borrow().get_1dep(x.as_tagged()) {
1588 return v;
1589 }
1590 *self.grad_f_evals.borrow_mut() += 1;
1591 let full_x = self.lift_x_to_full(x);
1592 let mut full_g = vec![0.0; full_x.len()];
1593 let ok = {
1594 let a = self.adapter.borrow();
1595 let mut t = a.tnlp().borrow_mut();
1596 t.eval_grad_f(&full_x, true, &mut full_g)
1597 };
1598 if !ok {
1601 full_g.fill(f64::NAN);
1602 }
1603 let cls = self.adapter.borrow().classification().clone();
1605 let mut g_compressed = self.x_space.make_new_dense();
1606 let obj_scal = self.obj_scale_factor.get();
1607 {
1608 let gv = g_compressed.values_mut();
1609 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
1610 gv[var_idx] = full_g[full_idx as usize] * obj_scal;
1611 }
1612 }
1613 let result: Rc<dyn Vector> = Rc::new(g_compressed);
1614 self.grad_f_cache
1615 .borrow_mut()
1616 .add_1dep(Rc::clone(&result), x.as_tagged());
1617 result
1618 }
1619
1620 fn full_g(&self, x: &dyn Vector) -> Rc<Vec<Number>> {
1626 if let Some(v) = self.full_g_cache.borrow().get_1dep(x.as_tagged()) {
1627 return v;
1628 }
1629 let n_full_g = self.adapter.borrow().classification().n_full_g as usize;
1630 let full_x = self.lift_x_to_full(x);
1631 let mut full_g = vec![0.0; n_full_g];
1632 let ok = {
1633 let a = self.adapter.borrow();
1634 let mut t = a.tnlp().borrow_mut();
1635 t.eval_g(&full_x, true, &mut full_g)
1636 };
1637 if !ok {
1638 full_g.fill(f64::NAN);
1639 }
1640 let result = Rc::new(full_g);
1641 self.full_g_cache
1642 .borrow_mut()
1643 .add_1dep(Rc::clone(&result), x.as_tagged());
1644 result
1645 }
1646
1647 fn full_jac_g(&self, x: &dyn Vector) -> Rc<Vec<Number>> {
1652 if let Some(v) = self.full_jac_g_cache.borrow().get_1dep(x.as_tagged()) {
1653 return v;
1654 }
1655 let mut full_vals = vec![0.0; self.nnz_jac_g_full as usize];
1656 let full_x = self.lift_x_to_full(x);
1657 let ok = {
1658 let a = self.adapter.borrow();
1659 let mut t = a.tnlp().borrow_mut();
1660 t.eval_jac_g(
1661 Some(&full_x),
1662 true,
1663 SparsityRequest::Values {
1664 values: &mut full_vals,
1665 },
1666 )
1667 };
1668 if !ok {
1669 full_vals.fill(f64::NAN);
1670 }
1671 let result = Rc::new(full_vals);
1672 self.full_jac_g_cache
1673 .borrow_mut()
1674 .add_1dep(Rc::clone(&result), x.as_tagged());
1675 result
1676 }
1677
1678 fn eval_c_internal(&self, x: &dyn Vector) -> Rc<dyn Vector> {
1679 let cls = self.adapter.borrow().classification().clone();
1680 if cls.n_c == 0 {
1681 if let Some(v) = self.c_cache.borrow().get_1dep(x.as_tagged()) {
1683 return v;
1684 }
1685 let v = self.c_space.make_new_dense();
1686 let result: Rc<dyn Vector> = Rc::new(v);
1687 self.c_cache
1688 .borrow_mut()
1689 .add_1dep(Rc::clone(&result), x.as_tagged());
1690 return result;
1691 }
1692 if let Some(v) = self.c_cache.borrow().get_1dep(x.as_tagged()) {
1693 return v;
1694 }
1695 *self.c_evals.borrow_mut() += 1;
1696 let full_g = self.full_g(x);
1701 let mut c = self.c_space.make_new_dense();
1702 {
1710 let cv = c.values_mut();
1711 let cs = self.c_scale.borrow();
1712 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1713 let raw = full_g[g_idx as usize] - self.c_rhs[i];
1714 cv[i] = match cs.as_ref() {
1715 Some(v) => raw * v[i],
1716 None => raw,
1717 };
1718 }
1719 }
1720 let result: Rc<dyn Vector> = Rc::new(c);
1721 self.c_cache
1722 .borrow_mut()
1723 .add_1dep(Rc::clone(&result), x.as_tagged());
1724 result
1725 }
1726
1727 fn eval_d_internal(&self, x: &dyn Vector) -> Rc<dyn Vector> {
1728 let cls = self.adapter.borrow().classification().clone();
1729 if cls.n_d == 0 {
1730 if let Some(v) = self.d_cache.borrow().get_1dep(x.as_tagged()) {
1731 return v;
1732 }
1733 let v = self.d_space.make_new_dense();
1734 let result: Rc<dyn Vector> = Rc::new(v);
1735 self.d_cache
1736 .borrow_mut()
1737 .add_1dep(Rc::clone(&result), x.as_tagged());
1738 return result;
1739 }
1740 if let Some(v) = self.d_cache.borrow().get_1dep(x.as_tagged()) {
1741 return v;
1742 }
1743 *self.d_evals.borrow_mut() += 1;
1744 let full_g = self.full_g(x);
1746 let mut d = self.d_space.make_new_dense();
1747 {
1748 let dv = d.values_mut();
1749 let ds = self.d_scale.borrow();
1750 for (i, &g_idx) in cls.d_map.iter().enumerate() {
1751 let raw = full_g[g_idx as usize];
1752 dv[i] = match ds.as_ref() {
1753 Some(v) => raw * v[i],
1754 None => raw,
1755 };
1756 }
1757 }
1758 let result: Rc<dyn Vector> = Rc::new(d);
1759 self.d_cache
1760 .borrow_mut()
1761 .add_1dep(Rc::clone(&result), x.as_tagged());
1762 result
1763 }
1764
1765 fn eval_jac_c_internal(&self, x: &dyn Vector) -> Rc<dyn Matrix> {
1766 if let Some(m) = self.jac_c_cache.borrow().get_1dep(x.as_tagged()) {
1767 return m;
1768 }
1769 *self.jac_c_evals.borrow_mut() += 1;
1770 let full_vals = self.full_jac_g(x);
1774 let mut jac_c = GenTMatrix::new(Rc::clone(&self.jac_c_space));
1775 {
1776 let cs = self.c_scale.borrow();
1777 let irows = self.jac_c_space.irows().to_vec();
1778 let vs = jac_c.values_mut();
1779 for (k, &src) in self.jac_c_entry_in_g.iter().enumerate() {
1780 let raw = full_vals[src as usize];
1781 vs[k] = match cs.as_ref() {
1782 Some(v) => raw * v[(irows[k] - 1) as usize],
1784 None => raw,
1785 };
1786 }
1787 }
1788 let result: Rc<dyn Matrix> = Rc::new(jac_c);
1789 self.jac_c_cache
1790 .borrow_mut()
1791 .add_1dep(Rc::clone(&result), x.as_tagged());
1792 result
1793 }
1794
1795 fn eval_jac_d_internal(&self, x: &dyn Vector) -> Rc<dyn Matrix> {
1796 if let Some(m) = self.jac_d_cache.borrow().get_1dep(x.as_tagged()) {
1797 return m;
1798 }
1799 *self.jac_d_evals.borrow_mut() += 1;
1800 let full_vals = self.full_jac_g(x);
1803 let mut jac_d = GenTMatrix::new(Rc::clone(&self.jac_d_space));
1804 {
1805 let ds = self.d_scale.borrow();
1806 let irows = self.jac_d_space.irows().to_vec();
1807 let vs = jac_d.values_mut();
1808 for (k, &src) in self.jac_d_entry_in_g.iter().enumerate() {
1809 let raw = full_vals[src as usize];
1810 vs[k] = match ds.as_ref() {
1811 Some(v) => raw * v[(irows[k] - 1) as usize],
1812 None => raw,
1813 };
1814 }
1815 }
1816 let result: Rc<dyn Matrix> = Rc::new(jac_d);
1817 self.jac_d_cache
1818 .borrow_mut()
1819 .add_1dep(Rc::clone(&result), x.as_tagged());
1820 result
1821 }
1822
1823 fn eval_h_internal(
1824 &self,
1825 x: &dyn Vector,
1826 obj_factor: Number,
1827 y_c: &dyn Vector,
1828 y_d: &dyn Vector,
1829 ) -> Rc<dyn SymMatrix> {
1830 if let Some(m) = self.h_cache.borrow().get(
1833 &[x.as_tagged(), y_c.as_tagged(), y_d.as_tagged()],
1834 &[obj_factor],
1835 ) {
1836 return m;
1837 }
1838 *self.h_evals.borrow_mut() += 1;
1839 let Some(h_space) = self.h_space.as_ref() else {
1840 panic!(
1841 "OrigIpoptNlp::eval_h called but the TNLP did not provide \
1842 eval_h sparsity. The L-BFGS path lands in Phase 8."
1843 );
1844 };
1845 let cls = self.adapter.borrow().classification().clone();
1846 let full_x = self.lift_x_to_full(x);
1847 let full_lambda = self.pack_lambda_for_user(y_c, y_d, &cls);
1855 let scaled_obj_factor = obj_factor * self.obj_scale_factor.get();
1856
1857 let mut full_vals = vec![0.0; self.nnz_h_lag_full as usize];
1862 let ok = {
1863 let a = self.adapter.borrow();
1864 let mut t = a.tnlp().borrow_mut();
1865 t.eval_h(
1866 Some(&full_x),
1867 true,
1868 scaled_obj_factor,
1869 Some(&full_lambda),
1870 true,
1871 SparsityRequest::Values {
1872 values: &mut full_vals,
1873 },
1874 )
1875 };
1876 if !ok {
1877 full_vals.fill(f64::NAN);
1878 }
1879 let mut h = SymTMatrix::new(Rc::clone(h_space));
1880 let kept = h_space.nonzeros() as usize;
1881 let h_vals = h.values_mut();
1882 debug_assert_eq!(kept, self.h_entry_in_full.len());
1885 for (k, &src) in self.h_entry_in_full.iter().enumerate() {
1886 h_vals[k] = full_vals[src as usize];
1887 }
1888 let result: Rc<dyn SymMatrix> = Rc::new(h);
1889 self.h_cache.borrow_mut().add(
1890 Rc::clone(&result),
1891 &[x.as_tagged(), y_c.as_tagged(), y_d.as_tagged()],
1892 &[obj_factor],
1893 );
1894 result
1895 }
1896}
1897
1898fn make_dense_from(
1901 space: &Rc<DenseVectorSpace>,
1902 mut f: impl FnMut(usize) -> Number,
1903) -> DenseVector {
1904 let mut v = space.make_new_dense();
1905 let dim = space.dim() as usize;
1906 if dim > 0 {
1907 let vs = v.values_mut();
1908 for (i, slot) in vs.iter_mut().enumerate().take(dim) {
1909 *slot = f(i);
1910 }
1911 }
1912 v
1913}
1914
1915impl Nlp for OrigIpoptNlp {
1918 fn n(&self) -> Index {
1919 self.x_space.dim()
1920 }
1921 fn m_eq(&self) -> Index {
1922 self.c_space.dim()
1923 }
1924 fn m_ineq(&self) -> Index {
1925 self.d_space.dim()
1926 }
1927
1928 fn eval_f(&mut self, x: &dyn Vector) -> Number {
1929 self.timed_eval(|t| &t.eval_obj, || self.eval_f_internal(x))
1930 }
1931 fn eval_grad_f(&mut self, x: &dyn Vector, g: &mut dyn Vector) {
1932 let result = self.timed_eval(|t| &t.eval_grad_obj, || self.eval_grad_f_internal(x));
1933 g.copy(&*result);
1934 }
1935 fn eval_c(&mut self, x: &dyn Vector, c: &mut dyn Vector) {
1936 let result = self.timed_eval(|t| &t.eval_constr, || self.eval_c_internal(x));
1937 c.copy(&*result);
1938 }
1939 fn eval_d(&mut self, x: &dyn Vector, d: &mut dyn Vector) {
1940 let result = self.timed_eval(|t| &t.eval_constr, || self.eval_d_internal(x));
1941 d.copy(&*result);
1942 }
1943 fn eval_jac_c(&mut self, x: &dyn Vector) -> Rc<dyn Matrix> {
1944 self.timed_eval(|t| &t.eval_constr_jac, || self.eval_jac_c_internal(x))
1945 }
1946 fn eval_jac_d(&mut self, x: &dyn Vector) -> Rc<dyn Matrix> {
1947 self.timed_eval(|t| &t.eval_constr_jac, || self.eval_jac_d_internal(x))
1948 }
1949 fn eval_h(
1950 &mut self,
1951 x: &dyn Vector,
1952 obj_factor: Number,
1953 y_c: &dyn Vector,
1954 y_d: &dyn Vector,
1955 ) -> Rc<dyn SymMatrix> {
1956 self.timed_eval(
1957 |t| &t.eval_lag_hess,
1958 || self.eval_h_internal(x, obj_factor, y_c, y_d),
1959 )
1960 }
1961}
1962
1963impl IpoptNlp for OrigIpoptNlp {
1964 fn eval_counts(&self) -> [Index; 7] {
1965 [
1966 self.f_evals(),
1967 self.grad_f_evals(),
1968 self.c_evals(),
1969 self.d_evals(),
1970 self.jac_c_evals(),
1971 self.jac_d_evals(),
1972 self.h_evals(),
1973 ]
1974 }
1975 fn x_l(&self) -> &dyn Vector {
1976 &*self.x_l
1977 }
1978 fn x_u(&self) -> &dyn Vector {
1979 &*self.x_u
1980 }
1981 fn d_l(&self) -> &dyn Vector {
1982 &*self.d_l
1983 }
1984 fn d_u(&self) -> &dyn Vector {
1985 &*self.d_u
1986 }
1987
1988 fn declared_d_bounds(&self) -> Option<(Vec<Number>, Vec<Number>)> {
1989 let mut dl = self.declared_d_l.borrow().clone()?;
1990 let mut du = self.declared_d_u.borrow().clone()?;
1991 if let Some(dd) = self.d_scale.borrow().as_ref() {
1995 let cls = self.adapter.borrow().classification().clone();
1996 for (i, slot) in dl.iter_mut().enumerate() {
1997 *slot *= dd[cls.d_l_map[i] as usize];
1998 }
1999 for (i, slot) in du.iter_mut().enumerate() {
2000 *slot *= dd[cls.d_u_map[i] as usize];
2001 }
2002 }
2003 Some((dl, du))
2004 }
2005
2006 fn declared_c_rhs(&self) -> Option<Vec<Number>> {
2007 let mut b = self.c_rhs.clone();
2013 if let Some(dc) = self.c_scale.borrow().as_ref() {
2014 for (i, slot) in b.iter_mut().enumerate() {
2015 *slot *= dc[i];
2016 }
2017 }
2018 Some(b)
2019 }
2020
2021 fn px_l(&self) -> Rc<dyn Matrix> {
2022 Rc::clone(&self.px_l)
2023 }
2024 fn px_u(&self) -> Rc<dyn Matrix> {
2025 Rc::clone(&self.px_u)
2026 }
2027 fn pd_l(&self) -> Rc<dyn Matrix> {
2028 Rc::clone(&self.pd_l)
2029 }
2030 fn pd_u(&self) -> Rc<dyn Matrix> {
2031 Rc::clone(&self.pd_u)
2032 }
2033
2034 fn adjust_variable_bounds(
2042 &mut self,
2043 new_x_l: &dyn Vector,
2044 new_x_u: &dyn Vector,
2045 new_d_l: &dyn Vector,
2046 new_d_u: &dyn Vector,
2047 ) {
2048 fn install(slot: &mut Rc<DenseVector>, new: &dyn Vector) {
2052 Rc::get_mut(slot)
2053 .expect("adjust_variable_bounds: bound vector is uniquely owned")
2054 .copy(new);
2055 }
2056 install(&mut self.x_l, new_x_l);
2057 install(&mut self.x_u, new_x_u);
2058 install(&mut self.d_l, new_d_l);
2059 install(&mut self.d_u, new_d_u);
2060 }
2061
2062 fn obj_scaling_factor(&self) -> Number {
2063 self.obj_scale_factor.get()
2064 }
2065
2066 fn computed_obj_scaling_factor(&self) -> Number {
2067 self.computed_obj_scale.get()
2068 }
2069
2070 fn c_scale_vec(&self) -> Option<Vec<Number>> {
2071 self.c_scale.borrow().clone()
2072 }
2073
2074 fn d_scale_vec(&self) -> Option<Vec<Number>> {
2075 self.d_scale.borrow().clone()
2076 }
2077
2078 fn split_space_names(&self) -> Option<SplitNames> {
2092 let a = self.adapter.borrow();
2093 let cls = a.classification();
2094
2095 let mut var_meta = MetaData::default();
2096 let mut con_meta = MetaData::default();
2097 if !a
2098 .tnlp()
2099 .borrow_mut()
2100 .get_var_con_metadata(&mut var_meta, &mut con_meta)
2101 {
2102 return None;
2103 }
2104
2105 let var_full = var_meta.strings.get(IDX_NAMES);
2108 let con_full = con_meta.strings.get(IDX_NAMES);
2109 if var_full.is_none() && con_full.is_none() {
2110 return None;
2111 }
2112
2113 let pick = |pool: Option<&Vec<String>>, full_idx: Index| -> Option<String> {
2116 pool.and_then(|v| v.get(full_idx as usize))
2117 .filter(|s| !s.is_empty())
2118 .cloned()
2119 };
2120
2121 let x_var = cls
2122 .x_not_fixed_map
2123 .iter()
2124 .map(|&full_idx| pick(var_full, full_idx))
2125 .collect();
2126 let eq = cls
2127 .c_map
2128 .iter()
2129 .map(|&full_idx| pick(con_full, full_idx))
2130 .collect();
2131 let ineq = cls
2132 .d_map
2133 .iter()
2134 .map(|&full_idx| pick(con_full, full_idx))
2135 .collect();
2136
2137 let names = SplitNames { x_var, eq, ineq };
2138 names.any_present().then_some(names)
2139 }
2140
2141 fn prepare_warm_start(&mut self) -> bool {
2142 let Some(snapshot) = self.fetch_warm_start_snapshot() else {
2143 return false;
2144 };
2145 *self.warm_start_snapshot.borrow_mut() = Some(snapshot);
2146 true
2147 }
2148
2149 fn finish_warm_start(&mut self) {
2150 self.warm_start_snapshot.borrow_mut().take();
2151 }
2152
2153 fn get_starting_x(&mut self, x: &mut dyn Vector) -> bool {
2157 let cls = self.adapter.borrow().classification().clone();
2158 if let Some(snapshot) = self.warm_start_snapshot.borrow().as_ref() {
2159 let Some(dx) = x.as_any_mut().downcast_mut::<DenseVector>() else {
2160 return false;
2161 };
2162 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
2163 dx.values_mut()[var_idx] = snapshot.x[full_idx as usize];
2164 }
2165 return true;
2166 }
2167 let n_full_x = cls.n_full_x as usize;
2168 let n_full_g = cls.n_full_g as usize;
2169 let mut full_x = vec![0.0; n_full_x];
2170 let mut full_z_l = vec![0.0; n_full_x];
2171 let mut full_z_u = vec![0.0; n_full_x];
2172 let mut full_lambda = vec![0.0; n_full_g];
2173 let ok = {
2174 let a = self.adapter.borrow();
2175 let mut t = a.tnlp().borrow_mut();
2176 t.get_starting_point(StartingPoint {
2177 init_x: true,
2178 x: &mut full_x,
2179 init_z: false,
2180 z_l: &mut full_z_l,
2181 z_u: &mut full_z_u,
2182 init_lambda: false,
2183 lambda: &mut full_lambda,
2184 })
2185 };
2186 if !ok {
2187 return false;
2188 }
2189 let Some(dx) = x.as_any_mut().downcast_mut::<DenseVector>() else {
2190 return false;
2191 };
2192 let xs = dx.values_mut();
2193 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
2194 xs[var_idx] = full_x[full_idx as usize];
2195 }
2196 true
2197 }
2198
2199 fn get_starting_y(&mut self, y_c: &mut dyn Vector, y_d: &mut dyn Vector) -> bool {
2200 let Some(y_c) = y_c.as_any_mut().downcast_mut::<DenseVector>() else {
2201 return false;
2202 };
2203 let Some(y_d) = y_d.as_any_mut().downcast_mut::<DenseVector>() else {
2204 return false;
2205 };
2206 if let Some(snapshot) = self.warm_start_snapshot.borrow().as_ref() {
2207 let cls = self.adapter.borrow().classification().clone();
2208 let obj_scal = self.obj_scale_factor.get();
2209 let c_scale = self.c_scale.borrow();
2210 for (i, &g_idx) in cls.c_map.iter().enumerate() {
2211 let cs = c_scale.as_ref().map(|v| v[i]).unwrap_or(1.0);
2212 y_c.values_mut()[i] = snapshot.lambda[g_idx as usize] / cs * obj_scal;
2213 }
2214 let d_scale = self.d_scale.borrow();
2215 for (i, &g_idx) in cls.d_map.iter().enumerate() {
2216 let ds = d_scale.as_ref().map(|v| v[i]).unwrap_or(1.0);
2217 y_d.values_mut()[i] = snapshot.lambda[g_idx as usize] / ds * obj_scal;
2218 }
2219 return true;
2220 }
2221 let mut x = DenseVectorSpace::new(self.n()).make_new_dense();
2222 let mut z_l = DenseVectorSpace::new(self.x_l.dim()).make_new_dense();
2223 let mut z_u = DenseVectorSpace::new(self.x_u.dim()).make_new_dense();
2224 self.initialize_starting_point(
2225 &mut x, false, y_c, true, y_d, true, &mut z_l, false, &mut z_u, false,
2226 )
2227 }
2228
2229 fn get_starting_z(
2230 &mut self,
2231 z_l: &mut dyn Vector,
2232 z_u: &mut dyn Vector,
2233 _v_l: &mut dyn Vector,
2234 _v_u: &mut dyn Vector,
2235 ) -> bool {
2236 let Some(z_l) = z_l.as_any_mut().downcast_mut::<DenseVector>() else {
2239 return false;
2240 };
2241 let Some(z_u) = z_u.as_any_mut().downcast_mut::<DenseVector>() else {
2242 return false;
2243 };
2244 if let Some(snapshot) = self.warm_start_snapshot.borrow().as_ref() {
2245 let cls = self.adapter.borrow().classification().clone();
2246 let obj_scal = self.obj_scale_factor.get();
2247 for (i, slot) in z_l.values_mut().iter_mut().enumerate() {
2248 let var_idx = cls.x_l_map[i] as usize;
2249 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
2250 *slot = snapshot.z_l[full_idx] * obj_scal;
2251 }
2252 for (i, slot) in z_u.values_mut().iter_mut().enumerate() {
2253 let var_idx = cls.x_u_map[i] as usize;
2254 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
2255 *slot = snapshot.z_u[full_idx] * obj_scal;
2256 }
2257 return true;
2258 }
2259 let mut x = DenseVectorSpace::new(self.n()).make_new_dense();
2260 let mut y_c = DenseVectorSpace::new(self.m_eq()).make_new_dense();
2261 let mut y_d = DenseVectorSpace::new(self.m_ineq()).make_new_dense();
2262 self.initialize_starting_point(
2263 &mut x, false, &mut y_c, false, &mut y_d, false, z_l, true, z_u, true,
2264 )
2265 }
2266
2267 fn lift_x_to_full(&self, x: &dyn Vector) -> Vec<Number> {
2268 OrigIpoptNlp::lift_x_to_full(self, x)
2269 }
2270
2271 fn finalize_solution_x(&self, x: &dyn Vector) -> Vec<Number> {
2272 OrigIpoptNlp::finalize_solution_x(self, x)
2273 }
2274
2275 fn n_full_x(&self) -> Index {
2276 self.adapter.borrow().classification().n_full_x
2277 }
2278
2279 fn n_full_g(&self) -> Index {
2280 self.adapter.borrow().classification().n_full_g
2281 }
2282
2283 fn pack_lambda_for_user(&self, y_c: &dyn Vector, y_d: &dyn Vector) -> Vec<Number> {
2284 let cls = self.adapter.borrow().classification().clone();
2285 OrigIpoptNlp::pack_lambda_for_user(self, y_c, y_d, &cls)
2286 }
2287
2288 fn pack_g_for_user(&self, c: &dyn Vector, d: &dyn Vector) -> Vec<Number> {
2289 let cls = self.adapter.borrow().classification().clone();
2290 let mut g = vec![0.0; cls.n_full_g as usize];
2291 if cls.n_c > 0 {
2292 let Some(dc) = c.as_any().downcast_ref::<DenseVector>() else {
2293 panic!("OrigIpoptNlp expects DenseVector for c");
2294 };
2295 let cs = self.c_scale.borrow();
2296 for (i, &g_idx) in cls.c_map.iter().enumerate() {
2297 let v = dc.expanded_values()[i];
2298 g[g_idx as usize] = match cs.as_ref() {
2299 Some(s) => v / s[i],
2300 None => v,
2301 };
2302 }
2303 }
2304 if cls.n_d > 0 {
2305 let Some(dd) = d.as_any().downcast_ref::<DenseVector>() else {
2306 panic!("OrigIpoptNlp expects DenseVector for d");
2307 };
2308 let ds = self.d_scale.borrow();
2309 for (i, &g_idx) in cls.d_map.iter().enumerate() {
2310 let v = dd.expanded_values()[i];
2311 g[g_idx as usize] = match ds.as_ref() {
2312 Some(s) => v / s[i],
2313 None => v,
2314 };
2315 }
2316 }
2317 g
2318 }
2319
2320 fn pack_z_l_for_user(&self, z_l: &dyn Vector) -> Vec<Number> {
2321 let cls = self.adapter.borrow().classification().clone();
2322 let mut full = vec![0.0; cls.n_full_x as usize];
2323 if z_l.dim() == 0 {
2324 return full;
2325 }
2326 let Some(dz) = z_l.as_any().downcast_ref::<DenseVector>() else {
2327 panic!("OrigIpoptNlp expects DenseVector for z_l");
2328 };
2329 let vals = dz.expanded_values();
2330 for (k, &var_idx) in cls.x_l_map.iter().enumerate() {
2331 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
2332 full[full_idx] = vals[k];
2333 }
2334 full
2335 }
2336
2337 fn pack_z_u_for_user(&self, z_u: &dyn Vector) -> Vec<Number> {
2338 let cls = self.adapter.borrow().classification().clone();
2339 let mut full = vec![0.0; cls.n_full_x as usize];
2340 if z_u.dim() == 0 {
2341 return full;
2342 }
2343 let Some(dz) = z_u.as_any().downcast_ref::<DenseVector>() else {
2344 panic!("OrigIpoptNlp expects DenseVector for z_u");
2345 };
2346 let vals = dz.expanded_values();
2347 for (k, &var_idx) in cls.x_u_map.iter().enumerate() {
2348 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
2349 full[full_idx] = vals[k];
2350 }
2351 full
2352 }
2353
2354 fn finalize_solution_lambda(&self, y_c: &dyn Vector, y_d: &dyn Vector) -> Vec<Number> {
2355 OrigIpoptNlp::finalize_solution_lambda(self, y_c, y_d)
2356 }
2357
2358 fn finalize_solution_z_l(&self, z_l: &dyn Vector) -> Vec<Number> {
2359 OrigIpoptNlp::finalize_solution_z_l(self, z_l)
2360 }
2361
2362 fn finalize_solution_z_u(&self, z_u: &dyn Vector) -> Vec<Number> {
2363 OrigIpoptNlp::finalize_solution_z_u(self, z_u)
2364 }
2365
2366 fn variable_scaling(&self) -> Option<Vec<Number>> {
2367 self.adapter.borrow().tnlp().borrow().scaling_factors()
2373 }
2374
2375 fn full_x_to_var_x(&self, full_idx: Index) -> Option<Index> {
2376 let cls = self.adapter.borrow();
2377 let cls = cls.classification();
2378 let f = full_idx as usize;
2379 if f >= cls.full_to_var.len() {
2380 return None;
2381 }
2382 let v = cls.full_to_var[f];
2383 if v < 0 { None } else { Some(v) }
2384 }
2385
2386 fn full_g_to_c_block(&self, full_idx: Index) -> Option<Index> {
2387 let cls = self.adapter.borrow();
2388 let cls = cls.classification();
2389 let f = full_idx as usize;
2390 if f >= cls.full_to_c.len() {
2391 return None;
2392 }
2393 let c = cls.full_to_c[f];
2394 if c < 0 { None } else { Some(c) }
2395 }
2396
2397 fn var_x_to_full_x(&self, var_idx: Index) -> Index {
2398 let cls = self.adapter.borrow();
2399 let cls = cls.classification();
2400 cls.x_not_fixed_map[var_idx as usize]
2401 }
2402}
2403
2404#[cfg(test)]
2407mod tests {
2408 use super::*;
2409 use crate::tnlp::{
2410 BoundsInfo, IndexStyle, IpoptCq, IpoptData, NlpInfo, Solution, SparsityRequest,
2411 StartingPoint, TNLP,
2412 };
2413
2414 #[derive(Default)]
2419 struct Hs071 {
2420 eval_f_calls: usize,
2421 eval_grad_f_calls: usize,
2422 eval_g_calls: usize,
2423 eval_jac_g_value_calls: usize,
2424 eval_h_value_calls: usize,
2425 get_bounds_info_calls: usize,
2426 get_starting_point_calls: usize,
2427 }
2428
2429 impl TNLP for Hs071 {
2430 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2431 Some(NlpInfo {
2432 n: 4,
2433 m: 2,
2434 nnz_jac_g: 8,
2435 nnz_h_lag: 10,
2436 index_style: IndexStyle::C,
2437 })
2438 }
2439 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2440 self.get_bounds_info_calls += 1;
2441 b.x_l.copy_from_slice(&[1.0; 4]);
2442 b.x_u.copy_from_slice(&[5.0; 4]);
2443 b.g_l.copy_from_slice(&[25.0, 40.0]);
2446 b.g_u.copy_from_slice(&[2.0e19, 40.0]);
2447 true
2448 }
2449 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2450 self.get_starting_point_calls += 1;
2451 sp.x.copy_from_slice(&[1.0, 5.0, 5.0, 1.0]);
2452 if sp.init_z {
2453 sp.z_l.copy_from_slice(&[1.0, 2.0, 3.0, 4.0]);
2454 sp.z_u.copy_from_slice(&[5.0, 6.0, 7.0, 8.0]);
2455 }
2456 if sp.init_lambda {
2457 sp.lambda.copy_from_slice(&[11.0, 13.0]);
2458 }
2459 true
2460 }
2461 fn eval_f(&mut self, x: &[Number], _new_x: bool) -> Option<Number> {
2462 self.eval_f_calls += 1;
2463 Some(x[0] * x[3] * (x[0] + x[1] + x[2]) + x[2])
2464 }
2465 fn eval_grad_f(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
2466 self.eval_grad_f_calls += 1;
2467 g[0] = x[3] * (2.0 * x[0] + x[1] + x[2]);
2472 g[1] = x[0] * x[3];
2473 g[2] = x[0] * x[3] + 1.0;
2474 g[3] = x[0] * (x[0] + x[1] + x[2]);
2475 true
2476 }
2477 fn eval_g(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
2478 self.eval_g_calls += 1;
2479 g[0] = x[0] * x[1] * x[2] * x[3];
2482 g[1] = x[0] * x[0] + x[1] * x[1] + x[2] * x[2] + x[3] * x[3];
2483 true
2484 }
2485 fn eval_jac_g(
2486 &mut self,
2487 x: Option<&[Number]>,
2488 _new_x: bool,
2489 mode: SparsityRequest<'_>,
2490 ) -> bool {
2491 match mode {
2492 SparsityRequest::Structure { irow, jcol } => {
2493 irow.copy_from_slice(&[0, 0, 0, 0, 1, 1, 1, 1]);
2495 jcol.copy_from_slice(&[0, 1, 2, 3, 0, 1, 2, 3]);
2496 }
2497 SparsityRequest::Values { values } => {
2498 self.eval_jac_g_value_calls += 1;
2499 let x = x.expect("eval_jac_g(Values) without x");
2500 values[0] = x[1] * x[2] * x[3];
2502 values[1] = x[0] * x[2] * x[3];
2503 values[2] = x[0] * x[1] * x[3];
2504 values[3] = x[0] * x[1] * x[2];
2505 values[4] = 2.0 * x[0];
2507 values[5] = 2.0 * x[1];
2508 values[6] = 2.0 * x[2];
2509 values[7] = 2.0 * x[3];
2510 }
2511 }
2512 true
2513 }
2514 fn eval_h(
2515 &mut self,
2516 x: Option<&[Number]>,
2517 _new_x: bool,
2518 obj_factor: Number,
2519 lambda: Option<&[Number]>,
2520 _new_lambda: bool,
2521 mode: SparsityRequest<'_>,
2522 ) -> bool {
2523 match mode {
2526 SparsityRequest::Structure { irow, jcol } => {
2527 irow.copy_from_slice(&[0, 1, 1, 2, 2, 2, 3, 3, 3, 3]);
2528 jcol.copy_from_slice(&[0, 0, 1, 0, 1, 2, 0, 1, 2, 3]);
2529 }
2530 SparsityRequest::Values { values } => {
2531 self.eval_h_value_calls += 1;
2532 let x = x.expect("eval_h(Values) without x");
2533 let lam = lambda.expect("eval_h(Values) without lambda");
2534 let of = obj_factor;
2535 let l0 = lam[0];
2545 let l1 = lam[1];
2546 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; }
2557 }
2558 true
2559 }
2560 fn finalize_solution(&mut self, _sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {}
2561 }
2562
2563 fn build_orig_nlp() -> (Rc<RefCell<TNLPAdapter>>, OrigIpoptNlp) {
2564 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071::default()));
2565 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2566 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2567 (adapter, nlp)
2568 }
2569
2570 fn dense_x(values: &[Number], space: &Rc<DenseVectorSpace>) -> DenseVector {
2571 let mut v = space.make_new_dense();
2572 v.values_mut().copy_from_slice(values);
2573 v
2574 }
2575
2576 #[test]
2577 fn dimensions_match_classification() {
2578 let (_, nlp) = build_orig_nlp();
2579 assert_eq!(nlp.n(), 4);
2581 assert_eq!(nlp.m_eq(), 1);
2582 assert_eq!(nlp.m_ineq(), 1);
2583 assert_eq!(nlp.jac_c_space().nonzeros(), 4);
2585 assert_eq!(nlp.jac_d_space().nonzeros(), 4);
2586 assert_eq!(nlp.h_space().unwrap().nonzeros(), 10);
2588 assert_eq!(nlp.x_l().dim(), 4);
2590 assert_eq!(nlp.x_u().dim(), 4);
2591 assert_eq!(nlp.d_l().dim(), 1);
2592 assert_eq!(nlp.d_u().dim(), 0);
2593 }
2594
2595 #[test]
2596 fn eval_f_at_starting_point() {
2597 let (_, mut nlp) = build_orig_nlp();
2598 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2599 assert_eq!(nlp.eval_f(&x), 16.0);
2601 assert_eq!(nlp.f_evals(), 1);
2602 }
2603
2604 #[test]
2605 fn eval_grad_f_at_starting_point() {
2606 let (_, mut nlp) = build_orig_nlp();
2607 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2608 let mut g = nlp.x_space().make_new_dense();
2609 nlp.eval_grad_f(&x, &mut g);
2610 assert_eq!(g.values(), &[12.0, 1.0, 2.0, 11.0]);
2615 assert_eq!(nlp.grad_f_evals(), 1);
2616 }
2617
2618 #[test]
2619 fn eval_c_returns_equality_residual() {
2620 let (_, mut nlp) = build_orig_nlp();
2621 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2622 let mut c = nlp.c_space().make_new_dense();
2623 nlp.eval_c(&x, &mut c);
2624 assert_eq!(c.values(), &[12.0]);
2626 assert_eq!(nlp.c_evals(), 1);
2627 }
2628
2629 #[test]
2630 fn eval_d_returns_inequality_value_unshifted() {
2631 let (_, mut nlp) = build_orig_nlp();
2632 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2633 let mut d = nlp.d_space().make_new_dense();
2634 nlp.eval_d(&x, &mut d);
2635 assert_eq!(d.values(), &[25.0]);
2637 assert_eq!(nlp.d_evals(), 1);
2638 }
2639
2640 #[test]
2641 fn cache_returns_without_re_eval() {
2642 let (_, mut nlp) = build_orig_nlp();
2643 let mut x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2644 let f1 = nlp.eval_f(&x);
2645 let f2 = nlp.eval_f(&x);
2646 assert_eq!(f1, f2);
2647 assert_eq!(nlp.f_evals(), 1, "second call must be served from cache");
2648 x.values_mut()[0] = 1.0; let _ = nlp.eval_f(&x);
2651 assert_eq!(nlp.f_evals(), 2);
2652 }
2653
2654 #[test]
2655 fn jac_c_picks_only_equality_rows() {
2656 let (_, mut nlp) = build_orig_nlp();
2657 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2658 let m = nlp.eval_jac_c(&x);
2659 let g = m
2660 .as_any()
2661 .downcast_ref::<GenTMatrix>()
2662 .expect("jac_c is a GenTMatrix");
2663 assert_eq!(g.values(), &[2.0, 10.0, 10.0, 2.0]);
2665 assert_eq!(g.irows(), &[1, 1, 1, 1]);
2667 assert_eq!(g.jcols(), &[1, 2, 3, 4]);
2668 }
2669
2670 #[test]
2671 fn jac_d_picks_only_inequality_rows() {
2672 let (_, mut nlp) = build_orig_nlp();
2673 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2674 let m = nlp.eval_jac_d(&x);
2675 let g = m
2676 .as_any()
2677 .downcast_ref::<GenTMatrix>()
2678 .expect("jac_d is a GenTMatrix");
2679 assert_eq!(g.values(), &[25.0, 5.0, 5.0, 25.0]);
2682 }
2683
2684 fn build_orig_nlp_counting() -> (Rc<RefCell<Hs071>>, OrigIpoptNlp) {
2689 let concrete = Rc::new(RefCell::new(Hs071::default()));
2690 let tnlp: Rc<RefCell<dyn TNLP>> = concrete.clone();
2691 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2692 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2693 (concrete, nlp)
2694 }
2695
2696 #[test]
2697 fn eval_c_and_eval_d_share_one_eval_g_per_iterate() {
2698 let (tnlp, mut nlp) = build_orig_nlp_counting();
2702 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2703 let mut c = nlp.c_space().make_new_dense();
2704 let mut d = nlp.d_space().make_new_dense();
2705 nlp.eval_c(&x, &mut c);
2706 nlp.eval_d(&x, &mut d);
2707 assert_eq!(
2708 tnlp.borrow().eval_g_calls,
2709 1,
2710 "eval_c + eval_d at one iterate must share a single user eval_g"
2711 );
2712 assert_eq!(nlp.c_evals(), 1);
2714 assert_eq!(nlp.d_evals(), 1);
2715 assert_eq!(c.values(), &[12.0]);
2717 assert_eq!(d.values(), &[25.0]);
2718
2719 let mut x2 = x;
2722 x2.values_mut()[0] = 2.0;
2723 nlp.eval_c(&x2, &mut c);
2724 nlp.eval_d(&x2, &mut d);
2725 assert_eq!(
2726 tnlp.borrow().eval_g_calls,
2727 2,
2728 "a new iterate triggers exactly one more shared eval_g"
2729 );
2730 }
2731
2732 #[test]
2733 fn eval_c_does_not_refetch_bounds_per_iterate() {
2734 let (tnlp, mut nlp) = build_orig_nlp_counting();
2742 let baseline = tnlp.borrow().get_bounds_info_calls;
2745
2746 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2747 let mut c = nlp.c_space().make_new_dense();
2748 nlp.eval_c(&x, &mut c);
2749 assert_eq!(c.values(), &[12.0]);
2751
2752 let mut x2 = x;
2754 for k in 0..5 {
2755 x2.values_mut()[0] = 2.0 + k as Number;
2756 nlp.eval_c(&x2, &mut c);
2757 }
2758
2759 assert_eq!(
2760 tnlp.borrow().get_bounds_info_calls,
2761 baseline,
2762 "eval_c must reuse the captured c_rhs, not re-fetch bounds per iterate"
2763 );
2764 }
2765
2766 #[test]
2767 fn eval_jac_c_and_eval_jac_d_share_one_eval_jac_g_per_iterate() {
2768 let (tnlp, mut nlp) = build_orig_nlp_counting();
2772 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2773 let _ = nlp.eval_jac_c(&x);
2774 let _ = nlp.eval_jac_d(&x);
2775 assert_eq!(
2776 tnlp.borrow().eval_jac_g_value_calls,
2777 1,
2778 "eval_jac_c + eval_jac_d at one iterate must share a single eval_jac_g"
2779 );
2780 assert_eq!(nlp.jac_c_evals(), 1);
2781 assert_eq!(nlp.jac_d_evals(), 1);
2782 }
2783
2784 #[test]
2785 fn starting_point_is_compressed_into_x_var() {
2786 let (_, mut nlp) = build_orig_nlp();
2787 let mut x = nlp.x_space().make_new_dense();
2788 let mut yc = nlp.c_space().make_new_dense();
2789 let mut yd = nlp.d_space().make_new_dense();
2790 let mut zl = nlp.x_l_space().make_new_dense();
2791 let mut zu = nlp.x_u_space().make_new_dense();
2792 let ok = nlp.initialize_starting_point(
2793 &mut x, true, &mut yc, false, &mut yd, false, &mut zl, false, &mut zu, false,
2794 );
2795 assert!(ok);
2796 assert_eq!(x.values(), &[1.0, 5.0, 5.0, 1.0]);
2797 }
2798
2799 #[test]
2800 fn warm_start_duals_are_forwarded_into_algorithm_vectors() {
2801 let (_, mut nlp) = build_orig_nlp();
2802 let mut y_c = nlp.c_space().make_new_dense();
2803 let mut y_d = nlp.d_space().make_new_dense();
2804 assert!(nlp.get_starting_y(&mut y_c, &mut y_d));
2805 assert_eq!(y_c.values(), &[13.0], "equality multiplier g1");
2806 assert_eq!(y_d.values(), &[11.0], "inequality multiplier g0");
2807
2808 let mut z_l = nlp.x_l_space().make_new_dense();
2809 let mut z_u = nlp.x_u_space().make_new_dense();
2810 let mut v_l = nlp.d_l_space().make_new_dense();
2811 let mut v_u = nlp.d_u_space().make_new_dense();
2812 assert!(nlp.get_starting_z(&mut z_l, &mut z_u, &mut v_l, &mut v_u));
2813 assert_eq!(z_l.values(), &[1.0, 2.0, 3.0, 4.0]);
2814 assert_eq!(z_u.values(), &[5.0, 6.0, 7.0, 8.0]);
2815 }
2816
2817 #[test]
2818 fn warm_start_prefetches_one_tnlp_snapshot_for_x_y_and_z() {
2819 let (tnlp, mut nlp) = build_orig_nlp_counting();
2820 assert!(nlp.prepare_warm_start());
2821
2822 let mut x = nlp.x_space().make_new_dense();
2823 let mut y_c = nlp.c_space().make_new_dense();
2824 let mut y_d = nlp.d_space().make_new_dense();
2825 let mut z_l = nlp.x_l_space().make_new_dense();
2826 let mut z_u = nlp.x_u_space().make_new_dense();
2827 let mut v_l = nlp.d_l_space().make_new_dense();
2828 let mut v_u = nlp.d_u_space().make_new_dense();
2829 assert!(nlp.get_starting_x(&mut x));
2830 assert!(nlp.get_starting_y(&mut y_c, &mut y_d));
2831 assert!(nlp.get_starting_z(&mut z_l, &mut z_u, &mut v_l, &mut v_u));
2832
2833 assert_eq!(tnlp.borrow().get_starting_point_calls, 1);
2834 assert_eq!(x.values(), &[1.0, 5.0, 5.0, 1.0]);
2835 assert_eq!(y_c.values(), &[13.0]);
2836 assert_eq!(y_d.values(), &[11.0]);
2837 assert_eq!(z_l.values(), &[1.0, 2.0, 3.0, 4.0]);
2838 assert_eq!(z_u.values(), &[5.0, 6.0, 7.0, 8.0]);
2839
2840 nlp.finish_warm_start();
2841 let mut x_after_init = nlp.x_space().make_new_dense();
2842 assert!(nlp.get_starting_x(&mut x_after_init));
2843 assert_eq!(
2844 tnlp.borrow().get_starting_point_calls,
2845 2,
2846 "the snapshot must not affect later starting-point requests"
2847 );
2848 }
2849
2850 struct OneFixedOneFree;
2855 impl TNLP for OneFixedOneFree {
2856 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2857 Some(NlpInfo {
2858 n: 2,
2859 m: 1,
2860 nnz_jac_g: 1,
2861 nnz_h_lag: 0,
2862 index_style: IndexStyle::C,
2863 })
2864 }
2865 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2866 b.x_l[0] = 7.0;
2867 b.x_u[0] = 7.0; b.x_l[1] = -1.0e19;
2869 b.x_u[1] = 1.0e19;
2870 b.g_l[0] = 0.0;
2871 b.g_u[0] = 0.0; true
2873 }
2874 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2875 sp.x[0] = 7.0;
2876 sp.x[1] = 0.5;
2877 true
2878 }
2879 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
2880 Some(x[1])
2881 }
2882 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
2883 g[0] = 0.0;
2884 g[1] = 1.0;
2885 true
2886 }
2887 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
2888 g[0] = x[1];
2889 true
2890 }
2891 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
2892 match m {
2893 SparsityRequest::Structure { irow, jcol } => {
2894 irow[0] = 0;
2895 jcol[0] = 1;
2896 }
2897 SparsityRequest::Values { values } => values[0] = 1.0,
2898 }
2899 true
2900 }
2901 fn eval_h(
2902 &mut self,
2903 _: Option<&[Number]>,
2904 _: bool,
2905 _: Number,
2906 _: Option<&[Number]>,
2907 _: bool,
2908 _: SparsityRequest<'_>,
2909 ) -> bool {
2910 true
2911 }
2912 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
2913 }
2914
2915 #[test]
2916 fn ipopt_nlp_index_mapping_methods_handle_fixed_var() {
2917 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneFixedOneFree));
2918 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2919 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2920
2921 assert_eq!(nlp.n_full_x(), 2);
2923 assert_eq!(nlp.n(), 1);
2924
2925 let nlp_dyn: &dyn crate::ipopt_nlp::IpoptNlp = &nlp;
2927 assert_eq!(nlp_dyn.full_x_to_var_x(0), None);
2928 assert_eq!(nlp_dyn.full_x_to_var_x(1), Some(0));
2929
2930 assert_eq!(nlp_dyn.var_x_to_full_x(0), 1);
2932
2933 assert_eq!(nlp_dyn.full_g_to_c_block(0), Some(0));
2935
2936 let mut x_var = nlp.x_space().make_new_dense();
2938 x_var.values_mut()[0] = 0.5;
2939 let lifted = nlp_dyn.lift_x_to_full(&x_var);
2940 assert_eq!(lifted, vec![7.0, 0.5]);
2941 }
2942
2943 struct NamedFixedOneFree;
2947 impl TNLP for NamedFixedOneFree {
2948 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2949 OneFixedOneFree.get_nlp_info()
2950 }
2951 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2952 OneFixedOneFree.get_bounds_info(b)
2953 }
2954 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2955 OneFixedOneFree.get_starting_point(sp)
2956 }
2957 fn eval_f(&mut self, x: &[Number], n: bool) -> Option<Number> {
2958 OneFixedOneFree.eval_f(x, n)
2959 }
2960 fn eval_grad_f(&mut self, x: &[Number], n: bool, g: &mut [Number]) -> bool {
2961 OneFixedOneFree.eval_grad_f(x, n, g)
2962 }
2963 fn eval_g(&mut self, x: &[Number], n: bool, g: &mut [Number]) -> bool {
2964 OneFixedOneFree.eval_g(x, n, g)
2965 }
2966 fn eval_jac_g(&mut self, x: Option<&[Number]>, n: bool, m: SparsityRequest<'_>) -> bool {
2967 OneFixedOneFree.eval_jac_g(x, n, m)
2968 }
2969 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
2970 fn get_var_con_metadata(&mut self, var: &mut MetaData, con: &mut MetaData) -> bool {
2971 var.strings.insert(
2972 IDX_NAMES.to_string(),
2973 vec!["fixed_x".to_string(), "free_x".to_string()],
2974 );
2975 con.strings
2976 .insert(IDX_NAMES.to_string(), vec!["balance".to_string()]);
2977 true
2978 }
2979 }
2980
2981 #[test]
2982 fn split_space_names_threads_through_fixed_var_and_cd_split() {
2983 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(NamedFixedOneFree));
2984 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2985 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2986
2987 let names = nlp.split_space_names().expect("names present");
2988 assert_eq!(names.x_var, vec![Some("free_x".to_string())]);
2990 assert_eq!(names.eq, vec![Some("balance".to_string())]);
2992 assert!(names.ineq.is_empty());
2994 assert!(names.any_present());
2995 }
2996
2997 #[test]
2998 fn split_space_names_none_when_tnlp_declines() {
2999 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneFixedOneFree));
3001 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3002 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3003 assert!(nlp.split_space_names().is_none());
3004 }
3005
3006 struct FixedOnlyHess;
3012 impl TNLP for FixedOnlyHess {
3013 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3014 Some(NlpInfo {
3015 n: 2,
3016 m: 1,
3017 nnz_jac_g: 1,
3018 nnz_h_lag: 1,
3019 index_style: IndexStyle::C,
3020 })
3021 }
3022 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3023 b.x_l[0] = 7.0;
3024 b.x_u[0] = 7.0; b.x_l[1] = -1.0e19;
3026 b.x_u[1] = 1.0e19;
3027 b.g_l[0] = 0.0;
3028 b.g_u[0] = 0.0;
3029 true
3030 }
3031 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3032 sp.x[0] = 7.0;
3033 sp.x[1] = 0.5;
3034 true
3035 }
3036 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
3037 Some(0.5 * x[0] * x[0] + x[1])
3038 }
3039 fn eval_grad_f(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3040 g[0] = x[0];
3041 g[1] = 1.0;
3042 true
3043 }
3044 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3045 g[0] = x[1];
3046 true
3047 }
3048 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
3049 match m {
3050 SparsityRequest::Structure { irow, jcol } => {
3051 irow[0] = 0;
3052 jcol[0] = 1;
3053 }
3054 SparsityRequest::Values { values } => values[0] = 1.0,
3055 }
3056 true
3057 }
3058 fn eval_h(
3059 &mut self,
3060 _: Option<&[Number]>,
3061 _: bool,
3062 obj_factor: Number,
3063 _: Option<&[Number]>,
3064 _: bool,
3065 m: SparsityRequest<'_>,
3066 ) -> bool {
3067 match m {
3068 SparsityRequest::Structure { irow, jcol } => {
3069 irow[0] = 0;
3070 jcol[0] = 0;
3071 }
3072 SparsityRequest::Values { values } => values[0] = obj_factor,
3073 }
3074 true
3075 }
3076 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3077 }
3078
3079 struct OneIneqLargeOffset;
3088 impl TNLP for OneIneqLargeOffset {
3089 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3090 Some(NlpInfo {
3091 n: 1,
3092 m: 1,
3093 nnz_jac_g: 1,
3094 nnz_h_lag: 0,
3095 index_style: IndexStyle::C,
3096 })
3097 }
3098 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3099 b.x_l[0] = -1.0e19;
3100 b.x_u[0] = 1.0e19;
3101 b.g_l[0] = 4.0e6;
3102 b.g_u[0] = 2.0e19;
3103 true
3104 }
3105 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3106 sp.x[0] = 5000.0;
3107 true
3108 }
3109 fn eval_f(&mut self, _: &[Number], _: bool) -> Option<Number> {
3110 Some(0.0)
3111 }
3112 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
3113 g[0] = 0.0;
3114 true
3115 }
3116 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3117 g[0] = 1000.0 * x[0];
3118 true
3119 }
3120 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
3121 match m {
3122 SparsityRequest::Structure { irow, jcol } => {
3123 irow[0] = 0;
3124 jcol[0] = 0;
3125 }
3126 SparsityRequest::Values { values } => values[0] = 1000.0,
3127 }
3128 true
3129 }
3130 fn eval_h(
3131 &mut self,
3132 _: Option<&[Number]>,
3133 _: bool,
3134 _: Number,
3135 _: Option<&[Number]>,
3136 _: bool,
3137 _: SparsityRequest<'_>,
3138 ) -> bool {
3139 true
3140 }
3141 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3142 }
3143
3144 struct OneEqLargeOffset;
3149 impl TNLP for OneEqLargeOffset {
3150 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3151 Some(NlpInfo {
3152 n: 1,
3153 m: 1,
3154 nnz_jac_g: 1,
3155 nnz_h_lag: 0,
3156 index_style: IndexStyle::C,
3157 })
3158 }
3159 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3160 b.x_l[0] = -1.0e19;
3161 b.x_u[0] = 1.0e19;
3162 b.g_l[0] = 4.0e6;
3163 b.g_u[0] = 4.0e6;
3164 true
3165 }
3166 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3167 sp.x[0] = 5000.0;
3168 true
3169 }
3170 fn eval_f(&mut self, _: &[Number], _: bool) -> Option<Number> {
3171 Some(0.0)
3172 }
3173 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
3174 g[0] = 0.0;
3175 true
3176 }
3177 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3178 g[0] = 1000.0 * x[0];
3179 true
3180 }
3181 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
3182 match m {
3183 SparsityRequest::Structure { irow, jcol } => {
3184 irow[0] = 0;
3185 jcol[0] = 0;
3186 }
3187 SparsityRequest::Values { values } => values[0] = 1000.0,
3188 }
3189 true
3190 }
3191 fn eval_h(
3192 &mut self,
3193 _: Option<&[Number]>,
3194 _: bool,
3195 _: Number,
3196 _: Option<&[Number]>,
3197 _: bool,
3198 _: SparsityRequest<'_>,
3199 ) -> bool {
3200 true
3201 }
3202 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3203 }
3204
3205 #[test]
3206 fn gradient_based_scaling_scales_d_l_and_d_u() {
3207 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqLargeOffset));
3208 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3209 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3210
3211 assert_eq!(nlp.d_l().dim(), 1);
3213 let pre = nlp
3214 .d_l()
3215 .as_any()
3216 .downcast_ref::<DenseVector>()
3217 .unwrap()
3218 .values()[0];
3219 assert_eq!(pre, 4.0e6);
3220
3221 nlp.determine_scaling_from_starting_point(
3222 ScalingMethod::GradientBased,
3223 100.0,
3224 1e-8,
3225 0.0,
3226 0.0,
3227 );
3228
3229 let post = nlp
3231 .d_l()
3232 .as_any()
3233 .downcast_ref::<DenseVector>()
3234 .unwrap()
3235 .values()[0];
3236 assert!(
3237 (post - 4.0e5).abs() < 1e-9,
3238 "d_l should be scaled by d_scale=0.1; got {}",
3239 post
3240 );
3241
3242 let x = dense_x(&[5000.0], nlp.x_space());
3245 let mut d = nlp.d_space().make_new_dense();
3246 nlp.eval_d(&x, &mut d);
3247 assert!(
3248 (d.values()[0] - 5.0e5).abs() < 1e-6,
3249 "scaled d(x) mismatch; got {}",
3250 d.values()[0]
3251 );
3252 assert!(
3253 d.values()[0] >= post,
3254 "starting point must be feasible in scaled space"
3255 );
3256 }
3257
3258 struct OneIneqWithObj;
3264 impl TNLP for OneIneqWithObj {
3265 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3266 Some(NlpInfo {
3267 n: 1,
3268 m: 1,
3269 nnz_jac_g: 1,
3270 nnz_h_lag: 0,
3271 index_style: IndexStyle::C,
3272 })
3273 }
3274 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3275 b.x_l[0] = -1.0e19;
3276 b.x_u[0] = 1.0e19;
3277 b.g_l[0] = 4.0e6;
3278 b.g_u[0] = 2.0e19;
3279 true
3280 }
3281 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3282 sp.x[0] = 5000.0;
3283 true
3284 }
3285 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
3286 Some(10.0 * x[0])
3287 }
3288 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
3289 g[0] = 10.0;
3290 true
3291 }
3292 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3293 g[0] = 1000.0 * x[0];
3294 true
3295 }
3296 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
3297 match m {
3298 SparsityRequest::Structure { irow, jcol } => {
3299 irow[0] = 0;
3300 jcol[0] = 0;
3301 }
3302 SparsityRequest::Values { values } => values[0] = 1000.0,
3303 }
3304 true
3305 }
3306 fn eval_h(
3307 &mut self,
3308 _: Option<&[Number]>,
3309 _: bool,
3310 _: Number,
3311 _: Option<&[Number]>,
3312 _: bool,
3313 _: SparsityRequest<'_>,
3314 ) -> bool {
3315 true
3316 }
3317 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3318 }
3319
3320 #[test]
3321 fn obj_target_gradient_pins_obj_scale() {
3322 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqWithObj));
3325 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3326 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3327 nlp.determine_scaling_from_starting_point(
3328 ScalingMethod::GradientBased,
3329 100.0,
3330 1e-8,
3331 0.0, 0.0,
3333 );
3334 assert!(
3335 (nlp.obj_scale_factor() - 1.0).abs() < 1e-12,
3336 "no-target path leaves df=1 when grad < cutoff; got {}",
3337 nlp.obj_scale_factor()
3338 );
3339
3340 let tnlp2: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqWithObj));
3343 let adapter2 = Rc::new(RefCell::new(TNLPAdapter::new(tnlp2).unwrap()));
3344 let mut nlp2 = OrigIpoptNlp::new(Rc::clone(&adapter2), Rc::new(NoScaling)).unwrap();
3345 nlp2.determine_scaling_from_starting_point(
3346 ScalingMethod::GradientBased,
3347 100.0,
3348 1e-8,
3349 1.0,
3350 0.0,
3351 );
3352 assert!(
3353 (nlp2.obj_scale_factor() - 0.1).abs() < 1e-12,
3354 "target_gradient=1, max_grad_f=10 → df=0.1; got {}",
3355 nlp2.obj_scale_factor()
3356 );
3357 }
3358
3359 struct FixedVarShiftsObjGrad;
3369 impl TNLP for FixedVarShiftsObjGrad {
3370 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3371 Some(NlpInfo {
3372 n: 2,
3373 m: 0,
3374 nnz_jac_g: 0,
3375 nnz_h_lag: 0,
3376 index_style: IndexStyle::C,
3377 })
3378 }
3379 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3380 b.x_l[0] = -1.0e19;
3381 b.x_u[0] = 1.0e19;
3382 b.x_l[1] = 1000.0;
3383 b.x_u[1] = 1000.0; true
3385 }
3386 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3387 sp.x[0] = 1.0;
3388 sp.x[1] = 0.0; true
3390 }
3391 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
3392 Some(x[0] * x[1])
3393 }
3394 fn eval_grad_f(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
3395 g[0] = x[1];
3396 g[1] = x[0];
3397 true
3398 }
3399 fn eval_g(&mut self, _: &[Number], _: bool, _: &mut [Number]) -> bool {
3400 true
3401 }
3402 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, _: SparsityRequest<'_>) -> bool {
3403 true
3404 }
3405 fn eval_h(
3406 &mut self,
3407 _: Option<&[Number]>,
3408 _: bool,
3409 _: Number,
3410 _: Option<&[Number]>,
3411 _: bool,
3412 _: SparsityRequest<'_>,
3413 ) -> bool {
3414 true
3415 }
3416 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3417 }
3418
3419 #[test]
3420 fn gradient_scaling_lifts_fixed_vars_to_their_value() {
3421 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(FixedVarShiftsObjGrad));
3422 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3423 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3424
3425 assert_eq!(nlp.n_full_x(), 2);
3427 assert_eq!(nlp.n(), 1);
3428
3429 nlp.determine_scaling_from_starting_point(
3430 ScalingMethod::GradientBased,
3431 100.0,
3432 1e-8,
3433 0.0,
3434 0.0,
3435 );
3436
3437 assert!(
3441 (nlp.obj_scale_factor() - 0.1).abs() < 1e-12,
3442 "fixed var must be lifted before scaling; expected df=0.1, got {}",
3443 nlp.obj_scale_factor()
3444 );
3445 }
3446
3447 #[test]
3448 fn constr_target_gradient_overrides_cutoff_and_clamp() {
3449 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqLargeOffset));
3454 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3455 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3456 nlp.determine_scaling_from_starting_point(
3457 ScalingMethod::GradientBased,
3458 100.0,
3459 1e-8,
3460 0.0,
3461 50.0,
3462 );
3463 let x = dense_x(&[5000.0], nlp.x_space());
3464 let mut d = nlp.d_space().make_new_dense();
3465 nlp.eval_d(&x, &mut d);
3466 assert!(
3468 (d.values()[0] - 2.5e5).abs() < 1e-6,
3469 "constr target=50 → dd=0.05; scaled d(5000)=2.5e5, got {}",
3470 d.values()[0]
3471 );
3472 }
3473
3474 struct Hs071UserScaled;
3479 impl TNLP for Hs071UserScaled {
3480 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3481 Hs071::default().get_nlp_info()
3482 }
3483 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3484 Hs071::default().get_bounds_info(b)
3485 }
3486 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3487 Hs071::default().get_starting_point(sp)
3488 }
3489 fn eval_f(&mut self, x: &[Number], new_x: bool) -> Option<Number> {
3490 Hs071::default().eval_f(x, new_x)
3491 }
3492 fn eval_grad_f(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
3493 Hs071::default().eval_grad_f(x, new_x, g)
3494 }
3495 fn eval_g(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
3496 Hs071::default().eval_g(x, new_x, g)
3497 }
3498 fn eval_jac_g(
3499 &mut self,
3500 x: Option<&[Number]>,
3501 new_x: bool,
3502 mode: SparsityRequest<'_>,
3503 ) -> bool {
3504 Hs071::default().eval_jac_g(x, new_x, mode)
3505 }
3506 fn eval_h(
3507 &mut self,
3508 x: Option<&[Number]>,
3509 new_x: bool,
3510 obj_factor: Number,
3511 lambda: Option<&[Number]>,
3512 new_lambda: bool,
3513 mode: SparsityRequest<'_>,
3514 ) -> bool {
3515 Hs071::default().eval_h(x, new_x, obj_factor, lambda, new_lambda, mode)
3516 }
3517 fn get_scaling_parameters(&mut self, req: ScalingRequest<'_>) -> bool {
3518 *req.obj_scaling = 2.0;
3519 *req.use_x_scaling = false;
3520 *req.use_g_scaling = true;
3521 req.g_scaling[0] = 0.5;
3523 req.g_scaling[1] = 0.25;
3524 true
3525 }
3526 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3527 }
3528
3529 #[test]
3530 fn user_scaling_dispatch_applies_obj_and_g_scaling() {
3531 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071UserScaled));
3532 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3533 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3534 nlp.determine_scaling_from_starting_point(
3535 ScalingMethod::UserScaling,
3536 100.0,
3537 1e-8,
3538 0.0,
3539 0.0,
3540 );
3541
3542 assert!(
3545 (nlp.obj_scale_factor() - 2.0).abs() < 1e-12,
3546 "user obj_scaling=2.0 should be installed; got {}",
3547 nlp.obj_scale_factor()
3548 );
3549
3550 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3554 let mut c = nlp.c_space().make_new_dense();
3555 nlp.eval_c(&x, &mut c);
3556 assert!(
3559 (c.values()[0] - 3.0).abs() < 1e-9,
3560 "user g_scaling=0.25 on equality → c=3.0; got {}",
3561 c.values()[0]
3562 );
3563
3564 let mut d = nlp.d_space().make_new_dense();
3567 nlp.eval_d(&x, &mut d);
3568 assert!(
3569 (d.values()[0] - 12.5).abs() < 1e-9,
3570 "user g_scaling=0.5 on inequality → d=12.5; got {}",
3571 d.values()[0]
3572 );
3573
3574 let post_d_l = nlp
3577 .d_l()
3578 .as_any()
3579 .downcast_ref::<DenseVector>()
3580 .unwrap()
3581 .values()[0];
3582 assert!(
3583 (post_d_l - 12.5).abs() < 1e-9,
3584 "d_l scaled in step: got {}",
3585 post_d_l
3586 );
3587 }
3588
3589 struct Hs071DeclinesScaling;
3593 impl TNLP for Hs071DeclinesScaling {
3594 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3595 Hs071::default().get_nlp_info()
3596 }
3597 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3598 Hs071::default().get_bounds_info(b)
3599 }
3600 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3601 Hs071::default().get_starting_point(sp)
3602 }
3603 fn eval_f(&mut self, x: &[Number], new_x: bool) -> Option<Number> {
3604 Hs071::default().eval_f(x, new_x)
3605 }
3606 fn eval_grad_f(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
3607 Hs071::default().eval_grad_f(x, new_x, g)
3608 }
3609 fn eval_g(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
3610 Hs071::default().eval_g(x, new_x, g)
3611 }
3612 fn eval_jac_g(
3613 &mut self,
3614 x: Option<&[Number]>,
3615 new_x: bool,
3616 mode: SparsityRequest<'_>,
3617 ) -> bool {
3618 Hs071::default().eval_jac_g(x, new_x, mode)
3619 }
3620 fn eval_h(
3621 &mut self,
3622 x: Option<&[Number]>,
3623 new_x: bool,
3624 obj_factor: Number,
3625 lambda: Option<&[Number]>,
3626 new_lambda: bool,
3627 mode: SparsityRequest<'_>,
3628 ) -> bool {
3629 Hs071::default().eval_h(x, new_x, obj_factor, lambda, new_lambda, mode)
3630 }
3631 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3632 }
3633
3634 #[test]
3635 fn user_scaling_falls_back_when_tnlp_declines() {
3636 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071DeclinesScaling));
3637 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3638 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3639 nlp.determine_scaling_from_starting_point(
3640 ScalingMethod::UserScaling,
3641 100.0,
3642 1e-8,
3643 0.0,
3644 0.0,
3645 );
3646 assert!((nlp.obj_scale_factor() - 1.0).abs() < 1e-12);
3649 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3650 let mut c = nlp.c_space().make_new_dense();
3651 nlp.eval_c(&x, &mut c);
3652 assert_eq!(c.values(), &[12.0], "unscaled equality residual");
3653 }
3654
3655 struct Hs071XScaled(Vec<Number>);
3658 impl TNLP for Hs071XScaled {
3659 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
3660 Hs071::default().get_nlp_info()
3661 }
3662 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
3663 Hs071::default().get_bounds_info(b)
3664 }
3665 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
3666 Hs071::default().get_starting_point(sp)
3667 }
3668 fn eval_f(&mut self, x: &[Number], new_x: bool) -> Option<Number> {
3669 Hs071::default().eval_f(x, new_x)
3670 }
3671 fn eval_grad_f(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
3672 Hs071::default().eval_grad_f(x, new_x, g)
3673 }
3674 fn eval_g(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
3675 Hs071::default().eval_g(x, new_x, g)
3676 }
3677 fn eval_jac_g(
3678 &mut self,
3679 x: Option<&[Number]>,
3680 new_x: bool,
3681 mode: SparsityRequest<'_>,
3682 ) -> bool {
3683 Hs071::default().eval_jac_g(x, new_x, mode)
3684 }
3685 fn eval_h(
3686 &mut self,
3687 x: Option<&[Number]>,
3688 new_x: bool,
3689 obj_factor: Number,
3690 lambda: Option<&[Number]>,
3691 new_lambda: bool,
3692 mode: SparsityRequest<'_>,
3693 ) -> bool {
3694 Hs071::default().eval_h(x, new_x, obj_factor, lambda, new_lambda, mode)
3695 }
3696 fn get_scaling_parameters(&mut self, req: ScalingRequest<'_>) -> bool {
3697 *req.obj_scaling = 2.0;
3698 *req.use_x_scaling = true;
3699 req.x_scaling.copy_from_slice(&self.0);
3700 *req.use_g_scaling = false;
3701 true
3702 }
3703 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3704 }
3705
3706 fn user_x_scaling_run(factors: &[Number]) -> OrigIpoptNlp {
3707 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071XScaled(factors.to_vec())));
3708 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3709 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3710 nlp.determine_scaling_from_starting_point(
3711 ScalingMethod::UserScaling,
3712 100.0,
3713 1e-8,
3714 0.0,
3715 0.0,
3716 );
3717 nlp
3718 }
3719
3720 #[test]
3725 fn user_x_scaling_request_is_flagged_not_discarded() {
3726 let nlp = user_x_scaling_run(&[1.0, 1e3, 1.0, 1.0]);
3727 assert!(
3728 nlp.user_x_scaling_rejected(),
3729 "a non-unit x_scaling must be refused, not dropped"
3730 );
3731 assert!((nlp.obj_scale_factor() - 2.0).abs() < 1e-12);
3734 }
3735
3736 #[test]
3739 fn unit_x_scaling_request_is_not_rejected() {
3740 let nlp = user_x_scaling_run(&[1.0, 1.0, 1.0, 1.0]);
3741 assert!(!nlp.user_x_scaling_rejected());
3742 }
3743
3744 #[test]
3745 fn eval_h_with_all_entries_on_fixed_var_does_not_panic() {
3746 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(FixedOnlyHess));
3747 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3748 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3749
3750 assert_eq!(nlp.h_space().unwrap().nonzeros(), 0);
3753
3754 let x = dense_x(&[0.5], &nlp.x_space().clone());
3755 let yc = dense_x(&[0.0], &nlp.c_space().clone());
3756 let yd = nlp.d_space().make_new_dense();
3757 let h = nlp.eval_h(&x, 1.0, &yc, &yd);
3758 assert_eq!(h.n_rows(), 1);
3759 }
3760
3761 #[test]
3762 fn relax_bounds_widens_uniquely_owned_bounds() {
3763 let (_adapter, mut nlp) = build_orig_nlp();
3766 let x_l_before = nlp.x_l.values().to_vec();
3767 let x_u_before = nlp.x_u.values().to_vec();
3768 nlp.relax_bounds(1e-2, 1.0);
3769 for (b, a) in x_l_before.iter().zip(nlp.x_l.values()) {
3770 assert!(a < b, "x_l should relax downward: {a} !< {b}");
3771 }
3772 for (b, a) in x_u_before.iter().zip(nlp.x_u.values()) {
3773 assert!(a > b, "x_u should relax upward: {a} !> {b}");
3774 }
3775 }
3776
3777 #[test]
3782 fn relax_bounds_is_scale_relative_on_d_and_snapshots_declared() {
3783 let (_adapter, mut nlp) = build_orig_nlp();
3785 assert_eq!(nlp.d_l.values(), &[25.0]);
3786 assert_eq!(nlp.declared_d_bounds(), None, "no snapshot before relax");
3787 nlp.relax_bounds(1e-2, 1.0);
3788 assert_eq!(nlp.d_l.values(), &[25.0 - 0.25]);
3794 let (dl, du) = nlp.declared_d_bounds().expect("snapshotted at relax");
3795 assert_eq!(dl, vec![25.0], "declared bound is the pre-relax value");
3796 assert!(du.is_empty() || du[0] >= 25.0); }
3798
3799 #[test]
3805 fn declared_c_rhs_is_the_pre_fold_right_hand_side() {
3806 let (_adapter, mut nlp) = build_orig_nlp();
3808 assert_eq!(nlp.declared_c_rhs(), Some(vec![40.0]));
3809 nlp.relax_bounds(1e-2, 1.0);
3810 assert_eq!(
3811 nlp.declared_c_rhs(),
3812 Some(vec![40.0]),
3813 "bound relaxation must not reach the equality RHS"
3814 );
3815 }
3816
3817 #[test]
3822 fn declared_c_rhs_carries_the_row_scaling() {
3823 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneEqLargeOffset));
3824 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3825 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3826 assert_eq!(nlp.declared_c_rhs(), Some(vec![4.0e6]));
3827
3828 nlp.determine_scaling_from_starting_point(
3829 ScalingMethod::GradientBased,
3830 100.0,
3831 1e-8,
3832 0.0,
3833 0.0,
3834 );
3835 let rhs = nlp.declared_c_rhs().unwrap();
3837 assert!(
3838 (rhs[0] - 4.0e5).abs() < 1e-9,
3839 "declared RHS should carry c_scale=0.1; got {}",
3840 rhs[0]
3841 );
3842
3843 let x = dense_x(&[5000.0], nlp.x_space());
3846 let mut c = nlp.c_space().make_new_dense();
3847 nlp.eval_c(&x, &mut c);
3848 assert!((c.values()[0] / rhs[0] - 0.25).abs() < 1e-12);
3849 }
3850
3851 #[test]
3852 #[should_panic(expected = "x_l is uniquely owned")]
3853 fn relax_bounds_panics_on_shared_bound_rc() {
3854 let (_adapter, mut nlp) = build_orig_nlp();
3859 let _shared = Rc::clone(&nlp.x_l); nlp.relax_bounds(1e-2, 1.0);
3861 }
3862}