1use crate::ipopt_nlp::{IpoptNlp, Nlp, SplitNames};
61use crate::tnlp::{MetaData, NlpInfo, ScalingRequest, SparsityRequest, StartingPoint, IDX_NAMES};
62use crate::tnlp_adapter::{BoundClassification, TNLPAdapter};
63use pounce_common::cached::Cache;
64use pounce_common::timing::TimingStatistics;
65use pounce_common::types::{Index, Number};
66use pounce_linalg::{
67 DenseVector, DenseVectorSpace, ExpansionMatrix, ExpansionMatrixSpace, GenTMatrix,
68 GenTMatrixSpace, Matrix, SymMatrix, SymTMatrix, SymTMatrixSpace, Vector,
69};
70use std::cell::{Cell, RefCell};
71use std::rc::Rc;
72
73pub trait NlpScaling {
85 fn obj_scaling(&self) -> Number {
90 1.0
91 }
92}
93
94#[derive(Debug, Default, Clone, Copy)]
97pub struct NoScaling;
98impl NlpScaling for NoScaling {}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum ScalingMethod {
104 None,
106 GradientBased,
108 UserScaling,
115}
116
117pub struct OrigIpoptNlp {
120 adapter: Rc<RefCell<TNLPAdapter>>,
122 scaling: Rc<dyn NlpScaling>,
127
128 obj_scale_factor: Cell<Number>,
131 c_scale: RefCell<Option<Vec<Number>>>,
135 d_scale: RefCell<Option<Vec<Number>>>,
137
138 x_space: Rc<DenseVectorSpace>,
140 c_space: Rc<DenseVectorSpace>,
141 d_space: Rc<DenseVectorSpace>,
142 x_l_space: Rc<DenseVectorSpace>,
143 x_u_space: Rc<DenseVectorSpace>,
144 d_l_space: Rc<DenseVectorSpace>,
145 d_u_space: Rc<DenseVectorSpace>,
146 px_l_space: Rc<ExpansionMatrixSpace>,
147 px_u_space: Rc<ExpansionMatrixSpace>,
148 pd_l_space: Rc<ExpansionMatrixSpace>,
149 pd_u_space: Rc<ExpansionMatrixSpace>,
150 jac_c_space: Rc<GenTMatrixSpace>,
151 jac_d_space: Rc<GenTMatrixSpace>,
152 h_space: Option<Rc<SymTMatrixSpace>>,
155
156 x_l: Rc<DenseVector>,
158 x_u: Rc<DenseVector>,
159 d_l: Rc<DenseVector>,
160 d_u: Rc<DenseVector>,
161
162 px_l: Rc<dyn Matrix>,
164 px_u: Rc<dyn Matrix>,
165 pd_l: Rc<dyn Matrix>,
166 pd_u: Rc<dyn Matrix>,
167
168 jac_c_entry_in_g: Vec<Index>,
172 jac_d_entry_in_g: Vec<Index>,
174 nnz_jac_g_full: Index,
176
177 nnz_h_lag_full: Index,
181 h_entry_in_full: Vec<Index>,
186
187 f_cache: RefCell<Cache<Number>>,
189 grad_f_cache: RefCell<Cache<Rc<dyn Vector>>>,
190 c_cache: RefCell<Cache<Rc<dyn Vector>>>,
191 d_cache: RefCell<Cache<Rc<dyn Vector>>>,
192 jac_c_cache: RefCell<Cache<Rc<dyn Matrix>>>,
193 jac_d_cache: RefCell<Cache<Rc<dyn Matrix>>>,
194 h_cache: RefCell<Cache<Rc<dyn SymMatrix>>>,
195
196 f_evals: RefCell<Index>,
198 grad_f_evals: RefCell<Index>,
199 c_evals: RefCell<Index>,
200 d_evals: RefCell<Index>,
201 jac_c_evals: RefCell<Index>,
202 jac_d_evals: RefCell<Index>,
203 h_evals: RefCell<Index>,
204
205 info: NlpInfo,
208
209 timing: RefCell<Option<Rc<TimingStatistics>>>,
214}
215
216impl std::fmt::Debug for OrigIpoptNlp {
217 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218 f.debug_struct("OrigIpoptNlp")
219 .field("info", &self.info)
220 .field("f_evals", &*self.f_evals.borrow())
221 .field("grad_f_evals", &*self.grad_f_evals.borrow())
222 .field("c_evals", &*self.c_evals.borrow())
223 .field("d_evals", &*self.d_evals.borrow())
224 .field("jac_c_evals", &*self.jac_c_evals.borrow())
225 .field("jac_d_evals", &*self.jac_d_evals.borrow())
226 .field("h_evals", &*self.h_evals.borrow())
227 .finish_non_exhaustive()
228 }
229}
230
231impl OrigIpoptNlp {
232 pub fn new(
238 adapter: Rc<RefCell<TNLPAdapter>>,
239 scaling: Rc<dyn NlpScaling>,
240 ) -> Result<Self, String> {
241 let (info, classification) = {
243 let a = adapter.borrow();
244 (*a.nlp_info(), a.classification().clone())
245 };
246
247 let n_x_var = classification.n_x_var();
249 let x_space = DenseVectorSpace::new(n_x_var);
250 let c_space = DenseVectorSpace::new(classification.n_c);
251 let d_space = DenseVectorSpace::new(classification.n_d);
252 let x_l_space = DenseVectorSpace::new(classification.n_x_l());
253 let x_u_space = DenseVectorSpace::new(classification.n_x_u());
254 let d_l_space = DenseVectorSpace::new(classification.n_d_l());
255 let d_u_space = DenseVectorSpace::new(classification.n_d_u());
256
257 let px_l_space =
259 ExpansionMatrixSpace::new(n_x_var, classification.n_x_l(), &classification.x_l_map, 0);
260 let px_u_space =
261 ExpansionMatrixSpace::new(n_x_var, classification.n_x_u(), &classification.x_u_map, 0);
262 let pd_l_space = ExpansionMatrixSpace::new(
263 classification.n_d,
264 classification.n_d_l(),
265 &classification.d_l_map,
266 0,
267 );
268 let pd_u_space = ExpansionMatrixSpace::new(
269 classification.n_d,
270 classification.n_d_u(),
271 &classification.d_u_map,
272 0,
273 );
274 let px_l: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&px_l_space)));
275 let px_u: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&px_u_space)));
276 let pd_l: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&pd_l_space)));
277 let pd_u: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&pd_u_space)));
278
279 let n_full_x = classification.n_full_x as usize;
283 let n_full_g = classification.n_full_g as usize;
284 let mut full_x_l = vec![0.0; n_full_x];
285 let mut full_x_u = vec![0.0; n_full_x];
286 let mut full_g_l = vec![0.0; n_full_g];
287 let mut full_g_u = vec![0.0; n_full_g];
288 {
289 let a = adapter.borrow();
290 let mut t = a.tnlp().borrow_mut();
291 let ok = t.get_bounds_info(crate::tnlp::BoundsInfo {
292 x_l: &mut full_x_l,
293 x_u: &mut full_x_u,
294 g_l: &mut full_g_l,
295 g_u: &mut full_g_u,
296 });
297 if !ok {
298 return Err("TNLP::get_bounds_info returned false on second call".into());
299 }
300 }
301
302 let x_l = make_dense_from(&x_l_space, |i| {
303 let var_idx = classification.x_l_map[i] as usize;
305 let full_idx = classification.x_not_fixed_map[var_idx] as usize;
306 full_x_l[full_idx]
307 });
308 let x_u = make_dense_from(&x_u_space, |i| {
309 let var_idx = classification.x_u_map[i] as usize;
310 let full_idx = classification.x_not_fixed_map[var_idx] as usize;
311 full_x_u[full_idx]
312 });
313 let d_l = make_dense_from(&d_l_space, |i| {
314 let d_idx = classification.d_l_map[i] as usize;
316 let full_g_idx = classification.d_map[d_idx] as usize;
317 full_g_l[full_g_idx]
318 });
319 let d_u = make_dense_from(&d_u_space, |i| {
320 let d_idx = classification.d_u_map[i] as usize;
321 let full_g_idx = classification.d_map[d_idx] as usize;
322 full_g_u[full_g_idx]
323 });
324
325 let mut full_irow = vec![0 as Index; info.nnz_jac_g as usize];
335 let mut full_jcol = vec![0 as Index; info.nnz_jac_g as usize];
336 {
337 let a = adapter.borrow();
338 let mut t = a.tnlp().borrow_mut();
339 let ok = t.eval_jac_g(
340 None,
341 false,
342 SparsityRequest::Structure {
343 irow: &mut full_irow,
344 jcol: &mut full_jcol,
345 },
346 );
347 if !ok {
348 return Err("TNLP::eval_jac_g(Structure) returned false".into());
349 }
350 }
351
352 let mut g_to_c = vec![-1 as Index; n_full_g];
354 for (c_idx, &g_idx) in classification.c_map.iter().enumerate() {
355 g_to_c[g_idx as usize] = c_idx as Index;
356 }
357 let mut g_to_d = vec![-1 as Index; n_full_g];
358 for (d_idx, &g_idx) in classification.d_map.iter().enumerate() {
359 g_to_d[g_idx as usize] = d_idx as Index;
360 }
361
362 let style_offset = match info.index_style {
363 crate::tnlp::IndexStyle::C => 0 as Index,
364 crate::tnlp::IndexStyle::Fortran => 1 as Index,
365 };
366
367 let mut jac_c_irow_1based = Vec::new();
368 let mut jac_c_jcol_1based = Vec::new();
369 let mut jac_c_entry_in_g = Vec::new();
370 let mut jac_d_irow_1based = Vec::new();
371 let mut jac_d_jcol_1based = Vec::new();
372 let mut jac_d_entry_in_g = Vec::new();
373
374 let full_to_var = &classification.full_to_var;
378 for k in 0..info.nnz_jac_g as usize {
379 let g_row_0 = (full_irow[k] - style_offset) as usize;
380 let x_col_0 = (full_jcol[k] - style_offset) as usize;
381 let var_col = full_to_var[x_col_0];
382 if var_col < 0 {
383 continue;
384 }
385 let col_1based = var_col + 1;
387 let c_row = g_to_c[g_row_0];
388 if c_row >= 0 {
389 jac_c_irow_1based.push(c_row + 1);
390 jac_c_jcol_1based.push(col_1based);
391 jac_c_entry_in_g.push(k as Index);
392 } else {
393 let d_row = g_to_d[g_row_0];
394 debug_assert!(d_row >= 0, "g row {g_row_0} is neither in c_map nor d_map");
395 jac_d_irow_1based.push(d_row + 1);
396 jac_d_jcol_1based.push(col_1based);
397 jac_d_entry_in_g.push(k as Index);
398 }
399 }
400
401 let jac_c_space = GenTMatrixSpace::new(
402 classification.n_c,
403 n_x_var,
404 jac_c_irow_1based,
405 jac_c_jcol_1based,
406 );
407 let jac_d_space = GenTMatrixSpace::new(
408 classification.n_d,
409 n_x_var,
410 jac_d_irow_1based,
411 jac_d_jcol_1based,
412 );
413
414 let nnz_h_lag_full = info.nnz_h_lag;
419 let mut h_entry_in_full: Vec<Index> = Vec::new();
420 let h_space = if info.nnz_h_lag > 0 {
421 let mut h_irow = vec![0 as Index; info.nnz_h_lag as usize];
422 let mut h_jcol = vec![0 as Index; info.nnz_h_lag as usize];
423 let supports_h = {
424 let a = adapter.borrow();
425 let mut t = a.tnlp().borrow_mut();
426 t.eval_h(
427 None,
428 false,
429 1.0,
430 None,
431 false,
432 SparsityRequest::Structure {
433 irow: &mut h_irow,
434 jcol: &mut h_jcol,
435 },
436 )
437 };
438 if supports_h {
439 let mut h_irow_1: Vec<Index> = Vec::with_capacity(h_irow.len());
445 let mut h_jcol_1: Vec<Index> = Vec::with_capacity(h_jcol.len());
446 for k in 0..h_irow.len() {
447 let i_full = (h_irow[k] - style_offset) as usize;
448 let j_full = (h_jcol[k] - style_offset) as usize;
449 let i_var = full_to_var[i_full];
450 let j_var = full_to_var[j_full];
451 if i_var < 0 || j_var < 0 {
452 continue;
453 }
454 h_irow_1.push(i_var + 1);
455 h_jcol_1.push(j_var + 1);
456 h_entry_in_full.push(k as Index);
457 }
458 Some(SymTMatrixSpace::new(n_x_var, h_irow_1, h_jcol_1))
459 } else {
460 None
462 }
463 } else {
464 Some(SymTMatrixSpace::new(n_x_var, Vec::new(), Vec::new()))
468 };
469
470 Ok(Self {
471 adapter,
472 scaling,
473 obj_scale_factor: Cell::new(1.0),
474 c_scale: RefCell::new(None),
475 d_scale: RefCell::new(None),
476 x_space,
477 c_space,
478 d_space,
479 x_l_space,
480 x_u_space,
481 d_l_space,
482 d_u_space,
483 px_l_space,
484 px_u_space,
485 pd_l_space,
486 pd_u_space,
487 jac_c_space,
488 jac_d_space,
489 h_space,
490 x_l: Rc::new(x_l),
491 x_u: Rc::new(x_u),
492 d_l: Rc::new(d_l),
493 d_u: Rc::new(d_u),
494 px_l,
495 px_u,
496 pd_l,
497 pd_u,
498 jac_c_entry_in_g,
499 jac_d_entry_in_g,
500 nnz_jac_g_full: info.nnz_jac_g,
501 nnz_h_lag_full,
502 h_entry_in_full,
503 f_cache: RefCell::new(Cache::new(1)),
504 grad_f_cache: RefCell::new(Cache::new(1)),
505 c_cache: RefCell::new(Cache::new(1)),
506 d_cache: RefCell::new(Cache::new(1)),
507 jac_c_cache: RefCell::new(Cache::new(1)),
508 jac_d_cache: RefCell::new(Cache::new(1)),
509 h_cache: RefCell::new(Cache::new(1)),
510 f_evals: RefCell::new(0),
511 grad_f_evals: RefCell::new(0),
512 c_evals: RefCell::new(0),
513 d_evals: RefCell::new(0),
514 jac_c_evals: RefCell::new(0),
515 jac_d_evals: RefCell::new(0),
516 h_evals: RefCell::new(0),
517 info,
518 timing: RefCell::new(None),
519 })
520 }
521
522 pub fn set_timing_stats(&self, t: Rc<TimingStatistics>) {
528 *self.timing.borrow_mut() = Some(t);
529 }
530
531 fn timed_eval<R, F>(&self, pick: fn(&TimingStatistics) -> &pounce_common::TimedTask, f: F) -> R
536 where
537 F: FnOnce() -> R,
538 {
539 let guard = self.timing.borrow();
540 match guard.as_deref() {
541 Some(t) => {
542 let task = pick(t);
543 task.start();
544 t.total_function_evaluation_time.start();
545 let r = f();
546 t.total_function_evaluation_time.end();
547 task.end();
548 r
549 }
550 None => {
551 drop(guard);
552 f()
553 }
554 }
555 }
556
557 pub fn nlp_info(&self) -> &NlpInfo {
560 &self.info
561 }
562 pub fn classification_n_x_var(&self) -> Index {
563 self.x_space.dim()
564 }
565 pub fn x_space(&self) -> &Rc<DenseVectorSpace> {
566 &self.x_space
567 }
568 pub fn c_space(&self) -> &Rc<DenseVectorSpace> {
569 &self.c_space
570 }
571 pub fn d_space(&self) -> &Rc<DenseVectorSpace> {
572 &self.d_space
573 }
574 pub fn x_l_space(&self) -> &Rc<DenseVectorSpace> {
575 &self.x_l_space
576 }
577 pub fn x_u_space(&self) -> &Rc<DenseVectorSpace> {
578 &self.x_u_space
579 }
580 pub fn d_l_space(&self) -> &Rc<DenseVectorSpace> {
581 &self.d_l_space
582 }
583 pub fn d_u_space(&self) -> &Rc<DenseVectorSpace> {
584 &self.d_u_space
585 }
586 pub fn px_l_space(&self) -> &Rc<ExpansionMatrixSpace> {
587 &self.px_l_space
588 }
589 pub fn px_u_space(&self) -> &Rc<ExpansionMatrixSpace> {
590 &self.px_u_space
591 }
592 pub fn pd_l_space(&self) -> &Rc<ExpansionMatrixSpace> {
593 &self.pd_l_space
594 }
595 pub fn pd_u_space(&self) -> &Rc<ExpansionMatrixSpace> {
596 &self.pd_u_space
597 }
598 pub fn jac_c_space(&self) -> &Rc<GenTMatrixSpace> {
599 &self.jac_c_space
600 }
601 pub fn jac_d_space(&self) -> &Rc<GenTMatrixSpace> {
602 &self.jac_d_space
603 }
604 pub fn h_space(&self) -> Option<&Rc<SymTMatrixSpace>> {
605 self.h_space.as_ref()
606 }
607
608 pub fn obj_scale_factor(&self) -> Number {
611 self.obj_scale_factor.get()
612 }
613
614 pub fn relax_bounds(&mut self, bound_relax_factor: Number, constr_viol_tol: Number) {
626 if bound_relax_factor <= 0.0 {
627 return;
628 }
629 let relax = bound_relax_factor.abs();
630 let cap = constr_viol_tol;
631 let apply = |v: &mut DenseVector, sign: Number| {
632 let xs = v.values_mut();
633 for x in xs.iter_mut() {
634 let delta = (relax * x.abs().max(1.0)).min(cap);
635 *x += sign * delta;
636 }
637 };
638 if let Some(x_l) = Rc::get_mut(&mut self.x_l) {
639 apply(x_l, -1.0);
640 }
641 if let Some(x_u) = Rc::get_mut(&mut self.x_u) {
642 apply(x_u, 1.0);
643 }
644 if let Some(d_l) = Rc::get_mut(&mut self.d_l) {
645 apply(d_l, -1.0);
646 }
647 if let Some(d_u) = Rc::get_mut(&mut self.d_u) {
648 apply(d_u, 1.0);
649 }
650 }
651
652 pub fn determine_scaling_from_starting_point(
674 &mut self,
675 method: ScalingMethod,
676 max_gradient: Number,
677 min_value: Number,
678 obj_target_gradient: Number,
679 constr_target_gradient: Number,
680 ) {
681 let user_obj_factor = self.scaling.obj_scaling();
684 if matches!(method, ScalingMethod::None) {
685 self.obj_scale_factor.set(user_obj_factor);
686 *self.c_scale.borrow_mut() = None;
687 *self.d_scale.borrow_mut() = None;
688 self.invalidate_eval_caches();
689 return;
690 }
691
692 let cls = self.adapter.borrow().classification().clone();
694 let n_full_x = cls.n_full_x as usize;
695 let n_full_g = cls.n_full_g as usize;
696 let mut full_x = vec![0.0; n_full_x];
697 let mut full_z_l = vec![0.0; n_full_x];
698 let mut full_z_u = vec![0.0; n_full_x];
699 let mut full_lambda = vec![0.0; n_full_g];
700 let starting_ok = {
701 let a = self.adapter.borrow();
702 let mut t = a.tnlp().borrow_mut();
703 t.get_starting_point(StartingPoint {
704 init_x: true,
705 x: &mut full_x,
706 init_z: false,
707 z_l: &mut full_z_l,
708 z_u: &mut full_z_u,
709 init_lambda: false,
710 lambda: &mut full_lambda,
711 })
712 };
713 if !starting_ok {
714 self.obj_scale_factor.set(user_obj_factor);
716 *self.c_scale.borrow_mut() = None;
717 *self.d_scale.borrow_mut() = None;
718 self.invalidate_eval_caches();
719 return;
720 }
721
722 for (i, &full_idx) in cls.x_fixed_map.iter().enumerate() {
735 full_x[full_idx as usize] = cls.x_fixed_vals[i];
736 }
737
738 match method {
739 ScalingMethod::None => unreachable!("handled above"),
740 ScalingMethod::GradientBased => {
741 self.scale_gradient_based(
742 &cls,
743 &full_x,
744 user_obj_factor,
745 max_gradient,
746 min_value,
747 obj_target_gradient,
748 constr_target_gradient,
749 );
750 }
751 ScalingMethod::UserScaling => {
752 let applied = self.scale_user_supplied(&cls, user_obj_factor, min_value);
753 if !applied {
754 self.obj_scale_factor.set(user_obj_factor);
758 *self.c_scale.borrow_mut() = None;
759 *self.d_scale.borrow_mut() = None;
760 }
761 }
762 }
763
764 self.apply_d_scale_to_bounds();
767
768 self.invalidate_eval_caches();
771 }
772
773 fn scale_gradient_based(
776 &self,
777 cls: &BoundClassification,
778 full_x: &[Number],
779 user_obj_factor: Number,
780 max_gradient: Number,
781 min_value: Number,
782 obj_target_gradient: Number,
783 constr_target_gradient: Number,
784 ) {
785 let n_full_x = cls.n_full_x as usize;
786 let n_full_g = cls.n_full_g as usize;
787
788 let mut full_grad_f = vec![0.0; n_full_x];
790 let grad_ok = {
791 let a = self.adapter.borrow();
792 let mut t = a.tnlp().borrow_mut();
793 t.eval_grad_f(full_x, true, &mut full_grad_f)
794 };
795 let mut df = 1.0;
796 if grad_ok {
797 let mut max_grad_f: Number = 0.0;
800 for &full_idx in cls.x_not_fixed_map.iter() {
801 let v = full_grad_f[full_idx as usize].abs();
802 if v > max_grad_f {
803 max_grad_f = v;
804 }
805 }
806 if obj_target_gradient > 0.0 && max_grad_f > 0.0 {
807 df = obj_target_gradient / max_grad_f;
810 } else if max_grad_f > max_gradient {
811 df = max_gradient / max_grad_f;
812 }
813 if df < min_value {
814 df = min_value;
815 }
816 }
817 self.obj_scale_factor.set(df * user_obj_factor);
818
819 if cls.n_full_g == 0 {
821 *self.c_scale.borrow_mut() = None;
822 *self.d_scale.borrow_mut() = None;
823 return;
824 }
825 let mut full_jac_vals = vec![0.0; self.nnz_jac_g_full as usize];
827 let jac_ok = {
828 let a = self.adapter.borrow();
829 let mut t = a.tnlp().borrow_mut();
830 t.eval_jac_g(
831 Some(full_x),
832 true,
833 SparsityRequest::Values {
834 values: &mut full_jac_vals,
835 },
836 )
837 };
838 if !jac_ok {
839 *self.c_scale.borrow_mut() = None;
840 *self.d_scale.borrow_mut() = None;
841 return;
842 }
843 let mut full_irow = vec![0 as Index; self.nnz_jac_g_full as usize];
845 let mut full_jcol = vec![0 as Index; self.nnz_jac_g_full as usize];
846 let _ = {
847 let a = self.adapter.borrow();
848 let mut t = a.tnlp().borrow_mut();
849 t.eval_jac_g(
850 None,
851 false,
852 SparsityRequest::Structure {
853 irow: &mut full_irow,
854 jcol: &mut full_jcol,
855 },
856 )
857 };
858 let style_offset: Index = match self.info.index_style {
859 crate::tnlp::IndexStyle::C => 0,
860 crate::tnlp::IndexStyle::Fortran => 1,
861 };
862 let mut g_to_c = vec![-1 as Index; n_full_g];
864 for (c_idx, &g_idx) in cls.c_map.iter().enumerate() {
865 g_to_c[g_idx as usize] = c_idx as Index;
866 }
867 let mut g_to_d = vec![-1 as Index; n_full_g];
868 for (d_idx, &g_idx) in cls.d_map.iter().enumerate() {
869 g_to_d[g_idx as usize] = d_idx as Index;
870 }
871 let n_c = cls.n_c as usize;
872 let n_d = cls.n_d as usize;
873 let dbl_min = Number::MIN_POSITIVE;
875 let mut c_row_max: Vec<Number> = vec![dbl_min; n_c];
876 let mut d_row_max: Vec<Number> = vec![dbl_min; n_d];
877 for k in 0..self.nnz_jac_g_full as usize {
878 let g_row_0 = (full_irow[k] - style_offset) as usize;
879 let v = full_jac_vals[k].abs();
880 let cr = g_to_c[g_row_0];
881 if cr >= 0 {
882 let row = cr as usize;
883 if v > c_row_max[row] {
884 c_row_max[row] = v;
885 }
886 } else {
887 let dr = g_to_d[g_row_0];
888 if dr >= 0 {
889 let row = dr as usize;
890 if v > d_row_max[row] {
891 d_row_max[row] = v;
892 }
893 }
894 }
895 }
896
897 let row_max_to_scale = |row_max: Number| -> Number {
898 let mut s = if constr_target_gradient > 0.0 {
903 constr_target_gradient / row_max
904 } else {
905 let raw = max_gradient / row_max;
906 if raw > 1.0 {
907 1.0
908 } else {
909 raw
910 }
911 };
912 if s < min_value {
913 s = min_value;
914 }
915 s
916 };
917 let any_row_above = |rows: &[Number]| -> bool {
918 constr_target_gradient > 0.0 || rows.iter().any(|&v| v > max_gradient)
919 };
920
921 if n_c > 0 && any_row_above(&c_row_max) {
922 let dc: Vec<Number> = c_row_max.iter().map(|&v| row_max_to_scale(v)).collect();
923 *self.c_scale.borrow_mut() = Some(dc);
924 } else {
925 *self.c_scale.borrow_mut() = None;
926 }
927
928 if n_d > 0 && any_row_above(&d_row_max) {
929 let dd: Vec<Number> = d_row_max.iter().map(|&v| row_max_to_scale(v)).collect();
930 *self.d_scale.borrow_mut() = Some(dd);
931 } else {
932 *self.d_scale.borrow_mut() = None;
933 }
934 }
935
936 fn scale_user_supplied(
948 &self,
949 cls: &BoundClassification,
950 user_obj_factor: Number,
951 min_value: Number,
952 ) -> bool {
953 let n_full_x = cls.n_full_x as usize;
954 let n_full_g = cls.n_full_g as usize;
955 let mut obj_scaling: Number = 1.0;
956 let mut use_x_scaling = false;
957 let mut x_scaling = vec![1.0; n_full_x];
958 let mut use_g_scaling = false;
959 let mut g_scaling = vec![1.0; n_full_g];
960 let ok = {
961 let a = self.adapter.borrow();
962 let mut t = a.tnlp().borrow_mut();
963 t.get_scaling_parameters(ScalingRequest {
964 obj_scaling: &mut obj_scaling,
965 use_x_scaling: &mut use_x_scaling,
966 x_scaling: &mut x_scaling,
967 use_g_scaling: &mut use_g_scaling,
968 g_scaling: &mut g_scaling,
969 })
970 };
971 if !ok {
972 return false;
973 }
974
975 let mut df = obj_scaling;
979 if df.abs() < min_value {
980 df = df.signum().max(0.0).max(1.0) * min_value;
983 }
984 self.obj_scale_factor.set(df * user_obj_factor);
985
986 if use_g_scaling && g_scaling.len() == n_full_g {
988 let n_c = cls.n_c as usize;
989 let n_d = cls.n_d as usize;
990 let mut dc = vec![1.0; n_c];
991 for (c_idx, &g_idx) in cls.c_map.iter().enumerate() {
992 let s = g_scaling[g_idx as usize];
993 dc[c_idx] = if s < min_value { min_value } else { s };
994 }
995 let mut dd = vec![1.0; n_d];
996 for (d_idx, &g_idx) in cls.d_map.iter().enumerate() {
997 let s = g_scaling[g_idx as usize];
998 dd[d_idx] = if s < min_value { min_value } else { s };
999 }
1000 let nontrivial_c = dc.iter().any(|&s| s != 1.0);
1003 *self.c_scale.borrow_mut() = if nontrivial_c && n_c > 0 {
1004 Some(dc)
1005 } else {
1006 None
1007 };
1008 let nontrivial_d = dd.iter().any(|&s| s != 1.0);
1009 *self.d_scale.borrow_mut() = if nontrivial_d && n_d > 0 {
1010 Some(dd)
1011 } else {
1012 None
1013 };
1014 } else {
1015 *self.c_scale.borrow_mut() = None;
1016 *self.d_scale.borrow_mut() = None;
1017 }
1018 let _ = use_x_scaling;
1020 true
1021 }
1022
1023 fn apply_d_scale_to_bounds(&mut self) {
1028 let cls = self.adapter.borrow().classification().clone();
1029 if let Some(dd) = self.d_scale.borrow().as_ref() {
1030 if let Some(d_l) = Rc::get_mut(&mut self.d_l) {
1031 let xs = d_l.values_mut();
1032 for (i, slot) in xs.iter_mut().enumerate() {
1033 let d_idx = cls.d_l_map[i] as usize;
1034 *slot *= dd[d_idx];
1035 }
1036 }
1037 if let Some(d_u) = Rc::get_mut(&mut self.d_u) {
1038 let xs = d_u.values_mut();
1039 for (i, slot) in xs.iter_mut().enumerate() {
1040 let d_idx = cls.d_u_map[i] as usize;
1041 *slot *= dd[d_idx];
1042 }
1043 }
1044 }
1045 }
1046
1047 fn invalidate_eval_caches(&self) {
1048 self.f_cache.borrow_mut().clear();
1049 self.grad_f_cache.borrow_mut().clear();
1050 self.c_cache.borrow_mut().clear();
1051 self.d_cache.borrow_mut().clear();
1052 self.jac_c_cache.borrow_mut().clear();
1053 self.jac_d_cache.borrow_mut().clear();
1054 self.h_cache.borrow_mut().clear();
1055 }
1056
1057 pub fn f_evals(&self) -> Index {
1058 *self.f_evals.borrow()
1059 }
1060 pub fn grad_f_evals(&self) -> Index {
1061 *self.grad_f_evals.borrow()
1062 }
1063 pub fn c_evals(&self) -> Index {
1064 *self.c_evals.borrow()
1065 }
1066 pub fn d_evals(&self) -> Index {
1067 *self.d_evals.borrow()
1068 }
1069 pub fn jac_c_evals(&self) -> Index {
1070 *self.jac_c_evals.borrow()
1071 }
1072 pub fn jac_d_evals(&self) -> Index {
1073 *self.jac_d_evals.borrow()
1074 }
1075 pub fn h_evals(&self) -> Index {
1076 *self.h_evals.borrow()
1077 }
1078
1079 pub fn lift_x_to_full(&self, x: &dyn Vector) -> Vec<Number> {
1085 let Some(dx) = x.as_any().downcast_ref::<DenseVector>() else {
1086 panic!("OrigIpoptNlp expects DenseVector for x");
1087 };
1088 let a = self.adapter.borrow();
1089 let cls = a.classification();
1090 let mut full = vec![0.0; cls.n_full_x as usize];
1091 let vals = dx.expanded_values();
1092 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
1093 full[full_idx as usize] = vals[var_idx];
1094 }
1095 for (i, &full_idx) in cls.x_fixed_map.iter().enumerate() {
1096 full[full_idx as usize] = cls.x_fixed_vals[i];
1097 }
1098 full
1099 }
1100
1101 pub fn finalize_solution_lambda(&self, y_c: &dyn Vector, y_d: &dyn Vector) -> Vec<Number> {
1113 let cls = self.adapter.borrow().classification().clone();
1114 let mut lambda = self.pack_lambda_for_user(y_c, y_d, &cls);
1115 let obj_scal = self.obj_scale_factor.get();
1116 if obj_scal != 0.0 && obj_scal != 1.0 {
1117 let inv = 1.0 / obj_scal;
1118 for v in lambda.iter_mut() {
1119 *v *= inv;
1120 }
1121 }
1122 lambda
1123 }
1124
1125 pub fn finalize_solution_z_l(&self, z_l: &dyn Vector) -> Vec<Number> {
1133 let cls = self.adapter.borrow().classification().clone();
1134 let n_full_x = cls.n_full_x as usize;
1135 let mut full_z_l = vec![0.0; n_full_x];
1136 let n_x_l = self.x_l.dim() as usize;
1137 if n_x_l == 0 {
1138 return full_z_l;
1139 }
1140 let Some(dz) = z_l.as_any().downcast_ref::<DenseVector>() else {
1141 panic!("OrigIpoptNlp::finalize_solution_z_l expects DenseVector");
1142 };
1143 let vals = dz.expanded_values();
1144 let obj_scal = self.obj_scale_factor.get();
1145 let inv = if obj_scal == 0.0 { 1.0 } else { 1.0 / obj_scal };
1146 for i in 0..n_x_l {
1147 let var_idx = cls.x_l_map[i] as usize;
1148 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1149 full_z_l[full_idx] = vals[i] * inv;
1150 }
1151 full_z_l
1152 }
1153
1154 pub fn finalize_solution_z_u(&self, z_u: &dyn Vector) -> Vec<Number> {
1157 let cls = self.adapter.borrow().classification().clone();
1158 let n_full_x = cls.n_full_x as usize;
1159 let mut full_z_u = vec![0.0; n_full_x];
1160 let n_x_u = self.x_u.dim() as usize;
1161 if n_x_u == 0 {
1162 return full_z_u;
1163 }
1164 let Some(dz) = z_u.as_any().downcast_ref::<DenseVector>() else {
1165 panic!("OrigIpoptNlp::finalize_solution_z_u expects DenseVector");
1166 };
1167 let vals = dz.expanded_values();
1168 let obj_scal = self.obj_scale_factor.get();
1169 let inv = if obj_scal == 0.0 { 1.0 } else { 1.0 / obj_scal };
1170 for i in 0..n_x_u {
1171 let var_idx = cls.x_u_map[i] as usize;
1172 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1173 full_z_u[full_idx] = vals[i] * inv;
1174 }
1175 full_z_u
1176 }
1177
1178 pub fn pack_lambda_for_user(
1188 &self,
1189 y_c: &dyn Vector,
1190 y_d: &dyn Vector,
1191 cls: &BoundClassification,
1192 ) -> Vec<Number> {
1193 let mut lambda = vec![0.0; cls.n_full_g as usize];
1194 if cls.n_c > 0 {
1195 let Some(dy) = y_c.as_any().downcast_ref::<DenseVector>() else {
1196 panic!("OrigIpoptNlp expects DenseVector for y_c");
1197 };
1198 let vals = dy.expanded_values();
1199 let cs = self.c_scale.borrow();
1200 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1201 lambda[g_idx as usize] = match cs.as_ref() {
1202 Some(v) => vals[i] * v[i],
1203 None => vals[i],
1204 };
1205 }
1206 }
1207 if cls.n_d > 0 {
1208 let Some(dy) = y_d.as_any().downcast_ref::<DenseVector>() else {
1209 panic!("OrigIpoptNlp expects DenseVector for y_d");
1210 };
1211 let vals = dy.expanded_values();
1212 let ds = self.d_scale.borrow();
1213 for (i, &g_idx) in cls.d_map.iter().enumerate() {
1214 lambda[g_idx as usize] = match ds.as_ref() {
1215 Some(v) => vals[i] * v[i],
1216 None => vals[i],
1217 };
1218 }
1219 }
1220 lambda
1221 }
1222
1223 #[allow(clippy::too_many_arguments)]
1233 pub fn initialize_starting_point(
1234 &mut self,
1235 x: &mut DenseVector,
1236 init_x: bool,
1237 y_c: &mut DenseVector,
1238 init_y_c: bool,
1239 y_d: &mut DenseVector,
1240 init_y_d: bool,
1241 z_l: &mut DenseVector,
1242 init_z_l: bool,
1243 z_u: &mut DenseVector,
1244 init_z_u: bool,
1245 ) -> bool {
1246 let n_full_x = self.adapter.borrow().classification().n_full_x as usize;
1247 let n_full_g = self.adapter.borrow().classification().n_full_g as usize;
1248 let n_x_l = self.x_l.dim() as usize;
1249 let n_x_u = self.x_u.dim() as usize;
1250
1251 let mut full_x = vec![0.0; n_full_x];
1252 let mut full_z_l = vec![0.0; n_full_x];
1253 let mut full_z_u = vec![0.0; n_full_x];
1254 let mut full_lambda = vec![0.0; n_full_g];
1255
1256 let ok = {
1257 let a = self.adapter.borrow();
1258 let mut t = a.tnlp().borrow_mut();
1259 t.get_starting_point(StartingPoint {
1260 init_x,
1261 x: &mut full_x,
1262 init_z: init_z_l || init_z_u,
1263 z_l: &mut full_z_l,
1264 z_u: &mut full_z_u,
1265 init_lambda: init_y_c || init_y_d,
1266 lambda: &mut full_lambda,
1267 })
1268 };
1269 if !ok {
1270 return false;
1271 }
1272
1273 let cls = self.adapter.borrow().classification().clone();
1274 let obj_scal = self.obj_scale_factor.get();
1275 let c_scale = self.c_scale.borrow();
1276 let d_scale = self.d_scale.borrow();
1277
1278 if init_x {
1280 let xs = x.values_mut();
1281 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
1282 xs[var_idx] = full_x[full_idx as usize];
1283 }
1284 }
1285 if init_y_c && cls.n_c > 0 {
1291 let yc = y_c.values_mut();
1292 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1293 let cs = c_scale.as_ref().map(|v| v[i]).unwrap_or(1.0);
1294 yc[i] = full_lambda[g_idx as usize] / cs * obj_scal;
1295 }
1296 }
1297 if init_y_d && cls.n_d > 0 {
1298 let yd = y_d.values_mut();
1299 for (i, &g_idx) in cls.d_map.iter().enumerate() {
1300 let ds = d_scale.as_ref().map(|v| v[i]).unwrap_or(1.0);
1301 yd[i] = full_lambda[g_idx as usize] / ds * obj_scal;
1302 }
1303 }
1304 if init_z_l && n_x_l > 0 {
1306 let zl = z_l.values_mut();
1307 for (i, slot) in zl.iter_mut().enumerate().take(n_x_l) {
1308 let var_idx = cls.x_l_map[i] as usize;
1309 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1310 *slot = full_z_l[full_idx] * obj_scal;
1311 }
1312 }
1313 if init_z_u && n_x_u > 0 {
1314 let zu = z_u.values_mut();
1315 for (i, slot) in zu.iter_mut().enumerate().take(n_x_u) {
1316 let var_idx = cls.x_u_map[i] as usize;
1317 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1318 *slot = full_z_u[full_idx] * obj_scal;
1319 }
1320 }
1321 true
1322 }
1323
1324 fn eval_f_internal(&self, x: &dyn Vector) -> Number {
1327 if let Some(v) = self.f_cache.borrow().get_1dep(x.as_tagged()) {
1328 return v;
1329 }
1330 *self.f_evals.borrow_mut() += 1;
1331 let full_x = self.lift_x_to_full(x);
1332 let unscaled = {
1333 let a = self.adapter.borrow();
1334 let mut t = a.tnlp().borrow_mut();
1335 t.eval_f(&full_x, true).unwrap_or(f64::NAN)
1340 };
1341 let scaled = unscaled * self.obj_scale_factor.get();
1342 self.f_cache.borrow_mut().add_1dep(scaled, x.as_tagged());
1343 scaled
1344 }
1345
1346 fn eval_grad_f_internal(&self, x: &dyn Vector) -> Rc<dyn Vector> {
1347 if let Some(v) = self.grad_f_cache.borrow().get_1dep(x.as_tagged()) {
1348 return v;
1349 }
1350 *self.grad_f_evals.borrow_mut() += 1;
1351 let full_x = self.lift_x_to_full(x);
1352 let mut full_g = vec![0.0; full_x.len()];
1353 let ok = {
1354 let a = self.adapter.borrow();
1355 let mut t = a.tnlp().borrow_mut();
1356 t.eval_grad_f(&full_x, true, &mut full_g)
1357 };
1358 if !ok {
1361 full_g.fill(f64::NAN);
1362 }
1363 let cls = self.adapter.borrow().classification().clone();
1365 let mut g_compressed = self.x_space.make_new_dense();
1366 let obj_scal = self.obj_scale_factor.get();
1367 {
1368 let gv = g_compressed.values_mut();
1369 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
1370 gv[var_idx] = full_g[full_idx as usize] * obj_scal;
1371 }
1372 }
1373 let result: Rc<dyn Vector> = Rc::new(g_compressed);
1374 self.grad_f_cache
1375 .borrow_mut()
1376 .add_1dep(Rc::clone(&result), x.as_tagged());
1377 result
1378 }
1379
1380 fn eval_c_internal(&self, x: &dyn Vector) -> Rc<dyn Vector> {
1381 let cls = self.adapter.borrow().classification().clone();
1382 if cls.n_c == 0 {
1383 if let Some(v) = self.c_cache.borrow().get_1dep(x.as_tagged()) {
1385 return v;
1386 }
1387 let v = self.c_space.make_new_dense();
1388 let result: Rc<dyn Vector> = Rc::new(v);
1389 self.c_cache
1390 .borrow_mut()
1391 .add_1dep(Rc::clone(&result), x.as_tagged());
1392 return result;
1393 }
1394 if let Some(v) = self.c_cache.borrow().get_1dep(x.as_tagged()) {
1395 return v;
1396 }
1397 *self.c_evals.borrow_mut() += 1;
1398 let full_x = self.lift_x_to_full(x);
1399 let mut full_g = vec![0.0; cls.n_full_g as usize];
1400 let ok = {
1401 let a = self.adapter.borrow();
1402 let mut t = a.tnlp().borrow_mut();
1403 t.eval_g(&full_x, true, &mut full_g)
1404 };
1405 if !ok {
1408 full_g.fill(f64::NAN);
1409 }
1410 let mut c = self.c_space.make_new_dense();
1411 let n_full_g = cls.n_full_g as usize;
1417 let mut full_g_l = vec![0.0; n_full_g];
1418 let mut full_g_u = vec![0.0; n_full_g];
1419 {
1420 let mut tmp_x_l = vec![0.0; cls.n_full_x as usize];
1421 let mut tmp_x_u = vec![0.0; cls.n_full_x as usize];
1422 let a = self.adapter.borrow();
1423 let mut t = a.tnlp().borrow_mut();
1424 t.get_bounds_info(crate::tnlp::BoundsInfo {
1425 x_l: &mut tmp_x_l,
1426 x_u: &mut tmp_x_u,
1427 g_l: &mut full_g_l,
1428 g_u: &mut full_g_u,
1429 });
1430 }
1431 {
1432 let cv = c.values_mut();
1433 let cs = self.c_scale.borrow();
1434 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1435 let raw = full_g[g_idx as usize] - full_g_l[g_idx as usize];
1436 cv[i] = match cs.as_ref() {
1437 Some(v) => raw * v[i],
1438 None => raw,
1439 };
1440 }
1441 }
1442 let result: Rc<dyn Vector> = Rc::new(c);
1443 self.c_cache
1444 .borrow_mut()
1445 .add_1dep(Rc::clone(&result), x.as_tagged());
1446 result
1447 }
1448
1449 fn eval_d_internal(&self, x: &dyn Vector) -> Rc<dyn Vector> {
1450 let cls = self.adapter.borrow().classification().clone();
1451 if cls.n_d == 0 {
1452 if let Some(v) = self.d_cache.borrow().get_1dep(x.as_tagged()) {
1453 return v;
1454 }
1455 let v = self.d_space.make_new_dense();
1456 let result: Rc<dyn Vector> = Rc::new(v);
1457 self.d_cache
1458 .borrow_mut()
1459 .add_1dep(Rc::clone(&result), x.as_tagged());
1460 return result;
1461 }
1462 if let Some(v) = self.d_cache.borrow().get_1dep(x.as_tagged()) {
1463 return v;
1464 }
1465 *self.d_evals.borrow_mut() += 1;
1466 let full_x = self.lift_x_to_full(x);
1467 let mut full_g = vec![0.0; cls.n_full_g as usize];
1468 let ok = {
1469 let a = self.adapter.borrow();
1470 let mut t = a.tnlp().borrow_mut();
1471 t.eval_g(&full_x, true, &mut full_g)
1472 };
1473 if !ok {
1474 full_g.fill(f64::NAN);
1475 }
1476 let mut d = self.d_space.make_new_dense();
1477 {
1478 let dv = d.values_mut();
1479 let ds = self.d_scale.borrow();
1480 for (i, &g_idx) in cls.d_map.iter().enumerate() {
1481 let raw = full_g[g_idx as usize];
1482 dv[i] = match ds.as_ref() {
1483 Some(v) => raw * v[i],
1484 None => raw,
1485 };
1486 }
1487 }
1488 let result: Rc<dyn Vector> = Rc::new(d);
1489 self.d_cache
1490 .borrow_mut()
1491 .add_1dep(Rc::clone(&result), x.as_tagged());
1492 result
1493 }
1494
1495 fn eval_jac_c_internal(&self, x: &dyn Vector) -> Rc<dyn Matrix> {
1496 if let Some(m) = self.jac_c_cache.borrow().get_1dep(x.as_tagged()) {
1497 return m;
1498 }
1499 *self.jac_c_evals.borrow_mut() += 1;
1500 let mut full_vals = vec![0.0; self.nnz_jac_g_full as usize];
1501 let full_x = self.lift_x_to_full(x);
1502 let ok = {
1503 let a = self.adapter.borrow();
1504 let mut t = a.tnlp().borrow_mut();
1505 t.eval_jac_g(
1506 Some(&full_x),
1507 true,
1508 SparsityRequest::Values {
1509 values: &mut full_vals,
1510 },
1511 )
1512 };
1513 if !ok {
1514 full_vals.fill(f64::NAN);
1515 }
1516 let mut jac_c = GenTMatrix::new(Rc::clone(&self.jac_c_space));
1517 {
1518 let cs = self.c_scale.borrow();
1519 let irows = self.jac_c_space.irows().to_vec();
1520 let vs = jac_c.values_mut();
1521 for (k, &src) in self.jac_c_entry_in_g.iter().enumerate() {
1522 let raw = full_vals[src as usize];
1523 vs[k] = match cs.as_ref() {
1524 Some(v) => raw * v[(irows[k] - 1) as usize],
1526 None => raw,
1527 };
1528 }
1529 }
1530 let result: Rc<dyn Matrix> = Rc::new(jac_c);
1531 self.jac_c_cache
1532 .borrow_mut()
1533 .add_1dep(Rc::clone(&result), x.as_tagged());
1534 result
1535 }
1536
1537 fn eval_jac_d_internal(&self, x: &dyn Vector) -> Rc<dyn Matrix> {
1538 if let Some(m) = self.jac_d_cache.borrow().get_1dep(x.as_tagged()) {
1539 return m;
1540 }
1541 *self.jac_d_evals.borrow_mut() += 1;
1542 let mut full_vals = vec![0.0; self.nnz_jac_g_full as usize];
1543 let full_x = self.lift_x_to_full(x);
1544 let ok = {
1545 let a = self.adapter.borrow();
1546 let mut t = a.tnlp().borrow_mut();
1547 t.eval_jac_g(
1548 Some(&full_x),
1549 true,
1550 SparsityRequest::Values {
1551 values: &mut full_vals,
1552 },
1553 )
1554 };
1555 if !ok {
1556 full_vals.fill(f64::NAN);
1557 }
1558 let mut jac_d = GenTMatrix::new(Rc::clone(&self.jac_d_space));
1559 {
1560 let ds = self.d_scale.borrow();
1561 let irows = self.jac_d_space.irows().to_vec();
1562 let vs = jac_d.values_mut();
1563 for (k, &src) in self.jac_d_entry_in_g.iter().enumerate() {
1564 let raw = full_vals[src as usize];
1565 vs[k] = match ds.as_ref() {
1566 Some(v) => raw * v[(irows[k] - 1) as usize],
1567 None => raw,
1568 };
1569 }
1570 }
1571 let result: Rc<dyn Matrix> = Rc::new(jac_d);
1572 self.jac_d_cache
1573 .borrow_mut()
1574 .add_1dep(Rc::clone(&result), x.as_tagged());
1575 result
1576 }
1577
1578 fn eval_h_internal(
1579 &self,
1580 x: &dyn Vector,
1581 obj_factor: Number,
1582 y_c: &dyn Vector,
1583 y_d: &dyn Vector,
1584 ) -> Rc<dyn SymMatrix> {
1585 if let Some(m) = self.h_cache.borrow().get(
1588 &[x.as_tagged(), y_c.as_tagged(), y_d.as_tagged()],
1589 &[obj_factor],
1590 ) {
1591 return m;
1592 }
1593 *self.h_evals.borrow_mut() += 1;
1594 let Some(h_space) = self.h_space.as_ref() else {
1595 panic!(
1596 "OrigIpoptNlp::eval_h called but the TNLP did not provide \
1597 eval_h sparsity. The L-BFGS path lands in Phase 8."
1598 );
1599 };
1600 let cls = self.adapter.borrow().classification().clone();
1601 let full_x = self.lift_x_to_full(x);
1602 let full_lambda = self.pack_lambda_for_user(y_c, y_d, &cls);
1610 let scaled_obj_factor = obj_factor * self.obj_scale_factor.get();
1611
1612 let mut full_vals = vec![0.0; self.nnz_h_lag_full as usize];
1617 let ok = {
1618 let a = self.adapter.borrow();
1619 let mut t = a.tnlp().borrow_mut();
1620 t.eval_h(
1621 Some(&full_x),
1622 true,
1623 scaled_obj_factor,
1624 Some(&full_lambda),
1625 true,
1626 SparsityRequest::Values {
1627 values: &mut full_vals,
1628 },
1629 )
1630 };
1631 if !ok {
1632 full_vals.fill(f64::NAN);
1633 }
1634 let mut h = SymTMatrix::new(Rc::clone(h_space));
1635 let kept = h_space.nonzeros() as usize;
1636 let h_vals = h.values_mut();
1637 debug_assert_eq!(kept, self.h_entry_in_full.len());
1640 for (k, &src) in self.h_entry_in_full.iter().enumerate() {
1641 h_vals[k] = full_vals[src as usize];
1642 }
1643 let result: Rc<dyn SymMatrix> = Rc::new(h);
1644 self.h_cache.borrow_mut().add(
1645 Rc::clone(&result),
1646 &[x.as_tagged(), y_c.as_tagged(), y_d.as_tagged()],
1647 &[obj_factor],
1648 );
1649 result
1650 }
1651}
1652
1653fn make_dense_from(
1656 space: &Rc<DenseVectorSpace>,
1657 mut f: impl FnMut(usize) -> Number,
1658) -> DenseVector {
1659 let mut v = space.make_new_dense();
1660 let dim = space.dim() as usize;
1661 if dim > 0 {
1662 let vs = v.values_mut();
1663 for (i, slot) in vs.iter_mut().enumerate().take(dim) {
1664 *slot = f(i);
1665 }
1666 }
1667 v
1668}
1669
1670impl Nlp for OrigIpoptNlp {
1673 fn n(&self) -> Index {
1674 self.x_space.dim()
1675 }
1676 fn m_eq(&self) -> Index {
1677 self.c_space.dim()
1678 }
1679 fn m_ineq(&self) -> Index {
1680 self.d_space.dim()
1681 }
1682
1683 fn eval_f(&mut self, x: &dyn Vector) -> Number {
1684 self.timed_eval(|t| &t.eval_obj, || self.eval_f_internal(x))
1685 }
1686 fn eval_grad_f(&mut self, x: &dyn Vector, g: &mut dyn Vector) {
1687 let result = self.timed_eval(|t| &t.eval_grad_obj, || self.eval_grad_f_internal(x));
1688 g.copy(&*result);
1689 }
1690 fn eval_c(&mut self, x: &dyn Vector, c: &mut dyn Vector) {
1691 let result = self.timed_eval(|t| &t.eval_constr, || self.eval_c_internal(x));
1692 c.copy(&*result);
1693 }
1694 fn eval_d(&mut self, x: &dyn Vector, d: &mut dyn Vector) {
1695 let result = self.timed_eval(|t| &t.eval_constr, || self.eval_d_internal(x));
1696 d.copy(&*result);
1697 }
1698 fn eval_jac_c(&mut self, x: &dyn Vector) -> Rc<dyn Matrix> {
1699 self.timed_eval(|t| &t.eval_constr_jac, || self.eval_jac_c_internal(x))
1700 }
1701 fn eval_jac_d(&mut self, x: &dyn Vector) -> Rc<dyn Matrix> {
1702 self.timed_eval(|t| &t.eval_constr_jac, || self.eval_jac_d_internal(x))
1703 }
1704 fn eval_h(
1705 &mut self,
1706 x: &dyn Vector,
1707 obj_factor: Number,
1708 y_c: &dyn Vector,
1709 y_d: &dyn Vector,
1710 ) -> Rc<dyn SymMatrix> {
1711 self.timed_eval(
1712 |t| &t.eval_lag_hess,
1713 || self.eval_h_internal(x, obj_factor, y_c, y_d),
1714 )
1715 }
1716}
1717
1718impl IpoptNlp for OrigIpoptNlp {
1719 fn x_l(&self) -> &dyn Vector {
1720 &*self.x_l
1721 }
1722 fn x_u(&self) -> &dyn Vector {
1723 &*self.x_u
1724 }
1725 fn d_l(&self) -> &dyn Vector {
1726 &*self.d_l
1727 }
1728 fn d_u(&self) -> &dyn Vector {
1729 &*self.d_u
1730 }
1731 fn px_l(&self) -> Rc<dyn Matrix> {
1732 Rc::clone(&self.px_l)
1733 }
1734 fn px_u(&self) -> Rc<dyn Matrix> {
1735 Rc::clone(&self.px_u)
1736 }
1737 fn pd_l(&self) -> Rc<dyn Matrix> {
1738 Rc::clone(&self.pd_l)
1739 }
1740 fn pd_u(&self) -> Rc<dyn Matrix> {
1741 Rc::clone(&self.pd_u)
1742 }
1743
1744 fn adjust_variable_bounds(
1752 &mut self,
1753 new_x_l: &dyn Vector,
1754 new_x_u: &dyn Vector,
1755 new_d_l: &dyn Vector,
1756 new_d_u: &dyn Vector,
1757 ) {
1758 fn install(slot: &mut Rc<DenseVector>, new: &dyn Vector) {
1762 Rc::get_mut(slot)
1763 .expect("adjust_variable_bounds: bound vector is uniquely owned")
1764 .copy(new);
1765 }
1766 install(&mut self.x_l, new_x_l);
1767 install(&mut self.x_u, new_x_u);
1768 install(&mut self.d_l, new_d_l);
1769 install(&mut self.d_u, new_d_u);
1770 }
1771
1772 fn obj_scaling_factor(&self) -> Number {
1773 self.obj_scale_factor.get()
1774 }
1775
1776 fn split_space_names(&self) -> Option<SplitNames> {
1790 let a = self.adapter.borrow();
1791 let cls = a.classification();
1792
1793 let mut var_meta = MetaData::default();
1794 let mut con_meta = MetaData::default();
1795 if !a
1796 .tnlp()
1797 .borrow_mut()
1798 .get_var_con_metadata(&mut var_meta, &mut con_meta)
1799 {
1800 return None;
1801 }
1802
1803 let var_full = var_meta.strings.get(IDX_NAMES);
1806 let con_full = con_meta.strings.get(IDX_NAMES);
1807 if var_full.is_none() && con_full.is_none() {
1808 return None;
1809 }
1810
1811 let pick = |pool: Option<&Vec<String>>, full_idx: Index| -> Option<String> {
1814 pool.and_then(|v| v.get(full_idx as usize))
1815 .filter(|s| !s.is_empty())
1816 .cloned()
1817 };
1818
1819 let x_var = cls
1820 .x_not_fixed_map
1821 .iter()
1822 .map(|&full_idx| pick(var_full, full_idx))
1823 .collect();
1824 let eq = cls
1825 .c_map
1826 .iter()
1827 .map(|&full_idx| pick(con_full, full_idx))
1828 .collect();
1829 let ineq = cls
1830 .d_map
1831 .iter()
1832 .map(|&full_idx| pick(con_full, full_idx))
1833 .collect();
1834
1835 let names = SplitNames { x_var, eq, ineq };
1836 names.any_present().then_some(names)
1837 }
1838
1839 fn get_starting_x(&mut self, x: &mut dyn Vector) -> bool {
1843 let cls = self.adapter.borrow().classification().clone();
1844 let n_full_x = cls.n_full_x as usize;
1845 let n_full_g = cls.n_full_g as usize;
1846 let mut full_x = vec![0.0; n_full_x];
1847 let mut full_z_l = vec![0.0; n_full_x];
1848 let mut full_z_u = vec![0.0; n_full_x];
1849 let mut full_lambda = vec![0.0; n_full_g];
1850 let ok = {
1851 let a = self.adapter.borrow();
1852 let mut t = a.tnlp().borrow_mut();
1853 t.get_starting_point(StartingPoint {
1854 init_x: true,
1855 x: &mut full_x,
1856 init_z: false,
1857 z_l: &mut full_z_l,
1858 z_u: &mut full_z_u,
1859 init_lambda: false,
1860 lambda: &mut full_lambda,
1861 })
1862 };
1863 if !ok {
1864 return false;
1865 }
1866 let Some(dx) = x.as_any_mut().downcast_mut::<DenseVector>() else {
1867 return false;
1868 };
1869 let xs = dx.values_mut();
1870 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
1871 xs[var_idx] = full_x[full_idx as usize];
1872 }
1873 true
1874 }
1875
1876 fn lift_x_to_full(&self, x: &dyn Vector) -> Vec<Number> {
1877 OrigIpoptNlp::lift_x_to_full(self, x)
1878 }
1879
1880 fn n_full_x(&self) -> Index {
1881 self.adapter.borrow().classification().n_full_x
1882 }
1883
1884 fn n_full_g(&self) -> Index {
1885 self.adapter.borrow().classification().n_full_g
1886 }
1887
1888 fn pack_lambda_for_user(&self, y_c: &dyn Vector, y_d: &dyn Vector) -> Vec<Number> {
1889 let cls = self.adapter.borrow().classification().clone();
1890 OrigIpoptNlp::pack_lambda_for_user(self, y_c, y_d, &cls)
1891 }
1892
1893 fn pack_g_for_user(&self, c: &dyn Vector, d: &dyn Vector) -> Vec<Number> {
1894 let cls = self.adapter.borrow().classification().clone();
1895 let mut g = vec![0.0; cls.n_full_g as usize];
1896 if cls.n_c > 0 {
1897 let Some(dc) = c.as_any().downcast_ref::<DenseVector>() else {
1898 panic!("OrigIpoptNlp expects DenseVector for c");
1899 };
1900 let cs = self.c_scale.borrow();
1901 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1902 let v = dc.expanded_values()[i];
1903 g[g_idx as usize] = match cs.as_ref() {
1904 Some(s) => v / s[i],
1905 None => v,
1906 };
1907 }
1908 }
1909 if cls.n_d > 0 {
1910 let Some(dd) = d.as_any().downcast_ref::<DenseVector>() else {
1911 panic!("OrigIpoptNlp expects DenseVector for d");
1912 };
1913 let ds = self.d_scale.borrow();
1914 for (i, &g_idx) in cls.d_map.iter().enumerate() {
1915 let v = dd.expanded_values()[i];
1916 g[g_idx as usize] = match ds.as_ref() {
1917 Some(s) => v / s[i],
1918 None => v,
1919 };
1920 }
1921 }
1922 g
1923 }
1924
1925 fn pack_z_l_for_user(&self, z_l: &dyn Vector) -> Vec<Number> {
1926 let cls = self.adapter.borrow().classification().clone();
1927 let mut full = vec![0.0; cls.n_full_x as usize];
1928 if z_l.dim() == 0 {
1929 return full;
1930 }
1931 let Some(dz) = z_l.as_any().downcast_ref::<DenseVector>() else {
1932 panic!("OrigIpoptNlp expects DenseVector for z_l");
1933 };
1934 let vals = dz.expanded_values();
1935 for (k, &var_idx) in cls.x_l_map.iter().enumerate() {
1936 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
1937 full[full_idx] = vals[k];
1938 }
1939 full
1940 }
1941
1942 fn pack_z_u_for_user(&self, z_u: &dyn Vector) -> Vec<Number> {
1943 let cls = self.adapter.borrow().classification().clone();
1944 let mut full = vec![0.0; cls.n_full_x as usize];
1945 if z_u.dim() == 0 {
1946 return full;
1947 }
1948 let Some(dz) = z_u.as_any().downcast_ref::<DenseVector>() else {
1949 panic!("OrigIpoptNlp expects DenseVector for z_u");
1950 };
1951 let vals = dz.expanded_values();
1952 for (k, &var_idx) in cls.x_u_map.iter().enumerate() {
1953 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
1954 full[full_idx] = vals[k];
1955 }
1956 full
1957 }
1958
1959 fn finalize_solution_lambda(&self, y_c: &dyn Vector, y_d: &dyn Vector) -> Vec<Number> {
1960 OrigIpoptNlp::finalize_solution_lambda(self, y_c, y_d)
1961 }
1962
1963 fn finalize_solution_z_l(&self, z_l: &dyn Vector) -> Vec<Number> {
1964 OrigIpoptNlp::finalize_solution_z_l(self, z_l)
1965 }
1966
1967 fn finalize_solution_z_u(&self, z_u: &dyn Vector) -> Vec<Number> {
1968 OrigIpoptNlp::finalize_solution_z_u(self, z_u)
1969 }
1970
1971 fn full_x_to_var_x(&self, full_idx: Index) -> Option<Index> {
1972 let cls = self.adapter.borrow();
1973 let cls = cls.classification();
1974 let f = full_idx as usize;
1975 if f >= cls.full_to_var.len() {
1976 return None;
1977 }
1978 let v = cls.full_to_var[f];
1979 if v < 0 {
1980 None
1981 } else {
1982 Some(v)
1983 }
1984 }
1985
1986 fn full_g_to_c_block(&self, full_idx: Index) -> Option<Index> {
1987 let cls = self.adapter.borrow();
1988 let cls = cls.classification();
1989 cls.c_map
1990 .iter()
1991 .position(|&g_idx| g_idx == full_idx)
1992 .map(|p| p as Index)
1993 }
1994
1995 fn var_x_to_full_x(&self, var_idx: Index) -> Index {
1996 let cls = self.adapter.borrow();
1997 let cls = cls.classification();
1998 cls.x_not_fixed_map[var_idx as usize]
1999 }
2000}
2001
2002#[cfg(test)]
2005mod tests {
2006 use super::*;
2007 use crate::tnlp::{
2008 BoundsInfo, IndexStyle, IpoptCq, IpoptData, NlpInfo, Solution, SparsityRequest,
2009 StartingPoint, TNLP,
2010 };
2011
2012 #[derive(Default)]
2017 struct Hs071 {
2018 eval_f_calls: usize,
2019 eval_grad_f_calls: usize,
2020 eval_g_calls: usize,
2021 eval_jac_g_value_calls: usize,
2022 eval_h_value_calls: usize,
2023 }
2024
2025 impl TNLP for Hs071 {
2026 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2027 Some(NlpInfo {
2028 n: 4,
2029 m: 2,
2030 nnz_jac_g: 8,
2031 nnz_h_lag: 10,
2032 index_style: IndexStyle::C,
2033 })
2034 }
2035 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2036 b.x_l.copy_from_slice(&[1.0; 4]);
2037 b.x_u.copy_from_slice(&[5.0; 4]);
2038 b.g_l.copy_from_slice(&[25.0, 40.0]);
2041 b.g_u.copy_from_slice(&[2.0e19, 40.0]);
2042 true
2043 }
2044 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2045 sp.x.copy_from_slice(&[1.0, 5.0, 5.0, 1.0]);
2046 true
2047 }
2048 fn eval_f(&mut self, x: &[Number], _new_x: bool) -> Option<Number> {
2049 self.eval_f_calls += 1;
2050 Some(x[0] * x[3] * (x[0] + x[1] + x[2]) + x[2])
2051 }
2052 fn eval_grad_f(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
2053 self.eval_grad_f_calls += 1;
2054 g[0] = x[3] * (2.0 * x[0] + x[1] + x[2]);
2059 g[1] = x[0] * x[3];
2060 g[2] = x[0] * x[3] + 1.0;
2061 g[3] = x[0] * (x[0] + x[1] + x[2]);
2062 true
2063 }
2064 fn eval_g(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
2065 self.eval_g_calls += 1;
2066 g[0] = x[0] * x[1] * x[2] * x[3];
2069 g[1] = x[0] * x[0] + x[1] * x[1] + x[2] * x[2] + x[3] * x[3];
2070 true
2071 }
2072 fn eval_jac_g(
2073 &mut self,
2074 x: Option<&[Number]>,
2075 _new_x: bool,
2076 mode: SparsityRequest<'_>,
2077 ) -> bool {
2078 match mode {
2079 SparsityRequest::Structure { irow, jcol } => {
2080 irow.copy_from_slice(&[0, 0, 0, 0, 1, 1, 1, 1]);
2082 jcol.copy_from_slice(&[0, 1, 2, 3, 0, 1, 2, 3]);
2083 }
2084 SparsityRequest::Values { values } => {
2085 self.eval_jac_g_value_calls += 1;
2086 let x = x.expect("eval_jac_g(Values) without x");
2087 values[0] = x[1] * x[2] * x[3];
2089 values[1] = x[0] * x[2] * x[3];
2090 values[2] = x[0] * x[1] * x[3];
2091 values[3] = x[0] * x[1] * x[2];
2092 values[4] = 2.0 * x[0];
2094 values[5] = 2.0 * x[1];
2095 values[6] = 2.0 * x[2];
2096 values[7] = 2.0 * x[3];
2097 }
2098 }
2099 true
2100 }
2101 fn eval_h(
2102 &mut self,
2103 x: Option<&[Number]>,
2104 _new_x: bool,
2105 obj_factor: Number,
2106 lambda: Option<&[Number]>,
2107 _new_lambda: bool,
2108 mode: SparsityRequest<'_>,
2109 ) -> bool {
2110 match mode {
2113 SparsityRequest::Structure { irow, jcol } => {
2114 irow.copy_from_slice(&[0, 1, 1, 2, 2, 2, 3, 3, 3, 3]);
2115 jcol.copy_from_slice(&[0, 0, 1, 0, 1, 2, 0, 1, 2, 3]);
2116 }
2117 SparsityRequest::Values { values } => {
2118 self.eval_h_value_calls += 1;
2119 let x = x.expect("eval_h(Values) without x");
2120 let lam = lambda.expect("eval_h(Values) without lambda");
2121 let of = obj_factor;
2122 let l0 = lam[0];
2132 let l1 = lam[1];
2133 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; }
2144 }
2145 true
2146 }
2147 fn finalize_solution(&mut self, _sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {}
2148 }
2149
2150 fn build_orig_nlp() -> (Rc<RefCell<TNLPAdapter>>, OrigIpoptNlp) {
2151 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071::default()));
2152 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2153 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2154 (adapter, nlp)
2155 }
2156
2157 fn dense_x(values: &[Number], space: &Rc<DenseVectorSpace>) -> DenseVector {
2158 let mut v = space.make_new_dense();
2159 v.values_mut().copy_from_slice(values);
2160 v
2161 }
2162
2163 #[test]
2164 fn dimensions_match_classification() {
2165 let (_, nlp) = build_orig_nlp();
2166 assert_eq!(nlp.n(), 4);
2168 assert_eq!(nlp.m_eq(), 1);
2169 assert_eq!(nlp.m_ineq(), 1);
2170 assert_eq!(nlp.jac_c_space().nonzeros(), 4);
2172 assert_eq!(nlp.jac_d_space().nonzeros(), 4);
2173 assert_eq!(nlp.h_space().unwrap().nonzeros(), 10);
2175 assert_eq!(nlp.x_l().dim(), 4);
2177 assert_eq!(nlp.x_u().dim(), 4);
2178 assert_eq!(nlp.d_l().dim(), 1);
2179 assert_eq!(nlp.d_u().dim(), 0);
2180 }
2181
2182 #[test]
2183 fn eval_f_at_starting_point() {
2184 let (_, mut nlp) = build_orig_nlp();
2185 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2186 assert_eq!(nlp.eval_f(&x), 16.0);
2188 assert_eq!(nlp.f_evals(), 1);
2189 }
2190
2191 #[test]
2192 fn eval_grad_f_at_starting_point() {
2193 let (_, mut nlp) = build_orig_nlp();
2194 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2195 let mut g = nlp.x_space().make_new_dense();
2196 nlp.eval_grad_f(&x, &mut g);
2197 assert_eq!(g.values(), &[12.0, 1.0, 2.0, 11.0]);
2202 assert_eq!(nlp.grad_f_evals(), 1);
2203 }
2204
2205 #[test]
2206 fn eval_c_returns_equality_residual() {
2207 let (_, mut nlp) = build_orig_nlp();
2208 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2209 let mut c = nlp.c_space().make_new_dense();
2210 nlp.eval_c(&x, &mut c);
2211 assert_eq!(c.values(), &[12.0]);
2213 assert_eq!(nlp.c_evals(), 1);
2214 }
2215
2216 #[test]
2217 fn eval_d_returns_inequality_value_unshifted() {
2218 let (_, mut nlp) = build_orig_nlp();
2219 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2220 let mut d = nlp.d_space().make_new_dense();
2221 nlp.eval_d(&x, &mut d);
2222 assert_eq!(d.values(), &[25.0]);
2224 assert_eq!(nlp.d_evals(), 1);
2225 }
2226
2227 #[test]
2228 fn cache_returns_without_re_eval() {
2229 let (_, mut nlp) = build_orig_nlp();
2230 let mut x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2231 let f1 = nlp.eval_f(&x);
2232 let f2 = nlp.eval_f(&x);
2233 assert_eq!(f1, f2);
2234 assert_eq!(nlp.f_evals(), 1, "second call must be served from cache");
2235 x.values_mut()[0] = 1.0; let _ = nlp.eval_f(&x);
2238 assert_eq!(nlp.f_evals(), 2);
2239 }
2240
2241 #[test]
2242 fn jac_c_picks_only_equality_rows() {
2243 let (_, mut nlp) = build_orig_nlp();
2244 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2245 let m = nlp.eval_jac_c(&x);
2246 let g = m
2247 .as_any()
2248 .downcast_ref::<GenTMatrix>()
2249 .expect("jac_c is a GenTMatrix");
2250 assert_eq!(g.values(), &[2.0, 10.0, 10.0, 2.0]);
2252 assert_eq!(g.irows(), &[1, 1, 1, 1]);
2254 assert_eq!(g.jcols(), &[1, 2, 3, 4]);
2255 }
2256
2257 #[test]
2258 fn jac_d_picks_only_inequality_rows() {
2259 let (_, mut nlp) = build_orig_nlp();
2260 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2261 let m = nlp.eval_jac_d(&x);
2262 let g = m
2263 .as_any()
2264 .downcast_ref::<GenTMatrix>()
2265 .expect("jac_d is a GenTMatrix");
2266 assert_eq!(g.values(), &[25.0, 5.0, 5.0, 25.0]);
2269 }
2270
2271 #[test]
2272 fn starting_point_is_compressed_into_x_var() {
2273 let (_, mut nlp) = build_orig_nlp();
2274 let mut x = nlp.x_space().make_new_dense();
2275 let mut yc = nlp.c_space().make_new_dense();
2276 let mut yd = nlp.d_space().make_new_dense();
2277 let mut zl = nlp.x_l_space().make_new_dense();
2278 let mut zu = nlp.x_u_space().make_new_dense();
2279 let ok = nlp.initialize_starting_point(
2280 &mut x, true, &mut yc, false, &mut yd, false, &mut zl, false, &mut zu, false,
2281 );
2282 assert!(ok);
2283 assert_eq!(x.values(), &[1.0, 5.0, 5.0, 1.0]);
2284 }
2285
2286 struct OneFixedOneFree;
2291 impl TNLP for OneFixedOneFree {
2292 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2293 Some(NlpInfo {
2294 n: 2,
2295 m: 1,
2296 nnz_jac_g: 1,
2297 nnz_h_lag: 0,
2298 index_style: IndexStyle::C,
2299 })
2300 }
2301 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2302 b.x_l[0] = 7.0;
2303 b.x_u[0] = 7.0; b.x_l[1] = -1.0e19;
2305 b.x_u[1] = 1.0e19;
2306 b.g_l[0] = 0.0;
2307 b.g_u[0] = 0.0; true
2309 }
2310 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2311 sp.x[0] = 7.0;
2312 sp.x[1] = 0.5;
2313 true
2314 }
2315 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
2316 Some(x[1])
2317 }
2318 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
2319 g[0] = 0.0;
2320 g[1] = 1.0;
2321 true
2322 }
2323 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
2324 g[0] = x[1];
2325 true
2326 }
2327 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
2328 match m {
2329 SparsityRequest::Structure { irow, jcol } => {
2330 irow[0] = 0;
2331 jcol[0] = 1;
2332 }
2333 SparsityRequest::Values { values } => values[0] = 1.0,
2334 }
2335 true
2336 }
2337 fn eval_h(
2338 &mut self,
2339 _: Option<&[Number]>,
2340 _: bool,
2341 _: Number,
2342 _: Option<&[Number]>,
2343 _: bool,
2344 _: SparsityRequest<'_>,
2345 ) -> bool {
2346 true
2347 }
2348 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
2349 }
2350
2351 #[test]
2352 fn ipopt_nlp_index_mapping_methods_handle_fixed_var() {
2353 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneFixedOneFree));
2354 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2355 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2356
2357 assert_eq!(nlp.n_full_x(), 2);
2359 assert_eq!(nlp.n(), 1);
2360
2361 let nlp_dyn: &dyn crate::ipopt_nlp::IpoptNlp = &nlp;
2363 assert_eq!(nlp_dyn.full_x_to_var_x(0), None);
2364 assert_eq!(nlp_dyn.full_x_to_var_x(1), Some(0));
2365
2366 assert_eq!(nlp_dyn.var_x_to_full_x(0), 1);
2368
2369 assert_eq!(nlp_dyn.full_g_to_c_block(0), Some(0));
2371
2372 let mut x_var = nlp.x_space().make_new_dense();
2374 x_var.values_mut()[0] = 0.5;
2375 let lifted = nlp_dyn.lift_x_to_full(&x_var);
2376 assert_eq!(lifted, vec![7.0, 0.5]);
2377 }
2378
2379 struct NamedFixedOneFree;
2383 impl TNLP for NamedFixedOneFree {
2384 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2385 OneFixedOneFree.get_nlp_info()
2386 }
2387 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2388 OneFixedOneFree.get_bounds_info(b)
2389 }
2390 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2391 OneFixedOneFree.get_starting_point(sp)
2392 }
2393 fn eval_f(&mut self, x: &[Number], n: bool) -> Option<Number> {
2394 OneFixedOneFree.eval_f(x, n)
2395 }
2396 fn eval_grad_f(&mut self, x: &[Number], n: bool, g: &mut [Number]) -> bool {
2397 OneFixedOneFree.eval_grad_f(x, n, g)
2398 }
2399 fn eval_g(&mut self, x: &[Number], n: bool, g: &mut [Number]) -> bool {
2400 OneFixedOneFree.eval_g(x, n, g)
2401 }
2402 fn eval_jac_g(&mut self, x: Option<&[Number]>, n: bool, m: SparsityRequest<'_>) -> bool {
2403 OneFixedOneFree.eval_jac_g(x, n, m)
2404 }
2405 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
2406 fn get_var_con_metadata(&mut self, var: &mut MetaData, con: &mut MetaData) -> bool {
2407 var.strings.insert(
2408 IDX_NAMES.to_string(),
2409 vec!["fixed_x".to_string(), "free_x".to_string()],
2410 );
2411 con.strings
2412 .insert(IDX_NAMES.to_string(), vec!["balance".to_string()]);
2413 true
2414 }
2415 }
2416
2417 #[test]
2418 fn split_space_names_threads_through_fixed_var_and_cd_split() {
2419 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(NamedFixedOneFree));
2420 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2421 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2422
2423 let names = nlp.split_space_names().expect("names present");
2424 assert_eq!(names.x_var, vec![Some("free_x".to_string())]);
2426 assert_eq!(names.eq, vec![Some("balance".to_string())]);
2428 assert!(names.ineq.is_empty());
2430 assert!(names.any_present());
2431 }
2432
2433 #[test]
2434 fn split_space_names_none_when_tnlp_declines() {
2435 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneFixedOneFree));
2437 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2438 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2439 assert!(nlp.split_space_names().is_none());
2440 }
2441
2442 struct FixedOnlyHess;
2448 impl TNLP for FixedOnlyHess {
2449 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2450 Some(NlpInfo {
2451 n: 2,
2452 m: 1,
2453 nnz_jac_g: 1,
2454 nnz_h_lag: 1,
2455 index_style: IndexStyle::C,
2456 })
2457 }
2458 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2459 b.x_l[0] = 7.0;
2460 b.x_u[0] = 7.0; b.x_l[1] = -1.0e19;
2462 b.x_u[1] = 1.0e19;
2463 b.g_l[0] = 0.0;
2464 b.g_u[0] = 0.0;
2465 true
2466 }
2467 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2468 sp.x[0] = 7.0;
2469 sp.x[1] = 0.5;
2470 true
2471 }
2472 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
2473 Some(0.5 * x[0] * x[0] + x[1])
2474 }
2475 fn eval_grad_f(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
2476 g[0] = x[0];
2477 g[1] = 1.0;
2478 true
2479 }
2480 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
2481 g[0] = x[1];
2482 true
2483 }
2484 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
2485 match m {
2486 SparsityRequest::Structure { irow, jcol } => {
2487 irow[0] = 0;
2488 jcol[0] = 1;
2489 }
2490 SparsityRequest::Values { values } => values[0] = 1.0,
2491 }
2492 true
2493 }
2494 fn eval_h(
2495 &mut self,
2496 _: Option<&[Number]>,
2497 _: bool,
2498 obj_factor: Number,
2499 _: Option<&[Number]>,
2500 _: bool,
2501 m: SparsityRequest<'_>,
2502 ) -> bool {
2503 match m {
2504 SparsityRequest::Structure { irow, jcol } => {
2505 irow[0] = 0;
2506 jcol[0] = 0;
2507 }
2508 SparsityRequest::Values { values } => values[0] = obj_factor,
2509 }
2510 true
2511 }
2512 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
2513 }
2514
2515 struct OneIneqLargeOffset;
2524 impl TNLP for OneIneqLargeOffset {
2525 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2526 Some(NlpInfo {
2527 n: 1,
2528 m: 1,
2529 nnz_jac_g: 1,
2530 nnz_h_lag: 0,
2531 index_style: IndexStyle::C,
2532 })
2533 }
2534 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2535 b.x_l[0] = -1.0e19;
2536 b.x_u[0] = 1.0e19;
2537 b.g_l[0] = 4.0e6;
2538 b.g_u[0] = 2.0e19;
2539 true
2540 }
2541 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2542 sp.x[0] = 5000.0;
2543 true
2544 }
2545 fn eval_f(&mut self, _: &[Number], _: bool) -> Option<Number> {
2546 Some(0.0)
2547 }
2548 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
2549 g[0] = 0.0;
2550 true
2551 }
2552 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
2553 g[0] = 1000.0 * x[0];
2554 true
2555 }
2556 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
2557 match m {
2558 SparsityRequest::Structure { irow, jcol } => {
2559 irow[0] = 0;
2560 jcol[0] = 0;
2561 }
2562 SparsityRequest::Values { values } => values[0] = 1000.0,
2563 }
2564 true
2565 }
2566 fn eval_h(
2567 &mut self,
2568 _: Option<&[Number]>,
2569 _: bool,
2570 _: Number,
2571 _: Option<&[Number]>,
2572 _: bool,
2573 _: SparsityRequest<'_>,
2574 ) -> bool {
2575 true
2576 }
2577 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
2578 }
2579
2580 #[test]
2581 fn gradient_based_scaling_scales_d_l_and_d_u() {
2582 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqLargeOffset));
2583 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2584 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2585
2586 assert_eq!(nlp.d_l().dim(), 1);
2588 let pre = nlp
2589 .d_l()
2590 .as_any()
2591 .downcast_ref::<DenseVector>()
2592 .unwrap()
2593 .values()[0];
2594 assert_eq!(pre, 4.0e6);
2595
2596 nlp.determine_scaling_from_starting_point(
2597 ScalingMethod::GradientBased,
2598 100.0,
2599 1e-8,
2600 0.0,
2601 0.0,
2602 );
2603
2604 let post = nlp
2606 .d_l()
2607 .as_any()
2608 .downcast_ref::<DenseVector>()
2609 .unwrap()
2610 .values()[0];
2611 assert!(
2612 (post - 4.0e5).abs() < 1e-9,
2613 "d_l should be scaled by d_scale=0.1; got {}",
2614 post
2615 );
2616
2617 let x = dense_x(&[5000.0], nlp.x_space());
2620 let mut d = nlp.d_space().make_new_dense();
2621 nlp.eval_d(&x, &mut d);
2622 assert!(
2623 (d.values()[0] - 5.0e5).abs() < 1e-6,
2624 "scaled d(x) mismatch; got {}",
2625 d.values()[0]
2626 );
2627 assert!(
2628 d.values()[0] >= post,
2629 "starting point must be feasible in scaled space"
2630 );
2631 }
2632
2633 struct OneIneqWithObj;
2639 impl TNLP for OneIneqWithObj {
2640 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2641 Some(NlpInfo {
2642 n: 1,
2643 m: 1,
2644 nnz_jac_g: 1,
2645 nnz_h_lag: 0,
2646 index_style: IndexStyle::C,
2647 })
2648 }
2649 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2650 b.x_l[0] = -1.0e19;
2651 b.x_u[0] = 1.0e19;
2652 b.g_l[0] = 4.0e6;
2653 b.g_u[0] = 2.0e19;
2654 true
2655 }
2656 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2657 sp.x[0] = 5000.0;
2658 true
2659 }
2660 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
2661 Some(10.0 * x[0])
2662 }
2663 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
2664 g[0] = 10.0;
2665 true
2666 }
2667 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
2668 g[0] = 1000.0 * x[0];
2669 true
2670 }
2671 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
2672 match m {
2673 SparsityRequest::Structure { irow, jcol } => {
2674 irow[0] = 0;
2675 jcol[0] = 0;
2676 }
2677 SparsityRequest::Values { values } => values[0] = 1000.0,
2678 }
2679 true
2680 }
2681 fn eval_h(
2682 &mut self,
2683 _: Option<&[Number]>,
2684 _: bool,
2685 _: Number,
2686 _: Option<&[Number]>,
2687 _: bool,
2688 _: SparsityRequest<'_>,
2689 ) -> bool {
2690 true
2691 }
2692 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
2693 }
2694
2695 #[test]
2696 fn obj_target_gradient_pins_obj_scale() {
2697 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqWithObj));
2700 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2701 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2702 nlp.determine_scaling_from_starting_point(
2703 ScalingMethod::GradientBased,
2704 100.0,
2705 1e-8,
2706 0.0, 0.0,
2708 );
2709 assert!(
2710 (nlp.obj_scale_factor() - 1.0).abs() < 1e-12,
2711 "no-target path leaves df=1 when grad < cutoff; got {}",
2712 nlp.obj_scale_factor()
2713 );
2714
2715 let tnlp2: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqWithObj));
2718 let adapter2 = Rc::new(RefCell::new(TNLPAdapter::new(tnlp2).unwrap()));
2719 let mut nlp2 = OrigIpoptNlp::new(Rc::clone(&adapter2), Rc::new(NoScaling)).unwrap();
2720 nlp2.determine_scaling_from_starting_point(
2721 ScalingMethod::GradientBased,
2722 100.0,
2723 1e-8,
2724 1.0,
2725 0.0,
2726 );
2727 assert!(
2728 (nlp2.obj_scale_factor() - 0.1).abs() < 1e-12,
2729 "target_gradient=1, max_grad_f=10 → df=0.1; got {}",
2730 nlp2.obj_scale_factor()
2731 );
2732 }
2733
2734 struct FixedVarShiftsObjGrad;
2744 impl TNLP for FixedVarShiftsObjGrad {
2745 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2746 Some(NlpInfo {
2747 n: 2,
2748 m: 0,
2749 nnz_jac_g: 0,
2750 nnz_h_lag: 0,
2751 index_style: IndexStyle::C,
2752 })
2753 }
2754 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2755 b.x_l[0] = -1.0e19;
2756 b.x_u[0] = 1.0e19;
2757 b.x_l[1] = 1000.0;
2758 b.x_u[1] = 1000.0; true
2760 }
2761 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2762 sp.x[0] = 1.0;
2763 sp.x[1] = 0.0; true
2765 }
2766 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
2767 Some(x[0] * x[1])
2768 }
2769 fn eval_grad_f(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
2770 g[0] = x[1];
2771 g[1] = x[0];
2772 true
2773 }
2774 fn eval_g(&mut self, _: &[Number], _: bool, _: &mut [Number]) -> bool {
2775 true
2776 }
2777 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, _: SparsityRequest<'_>) -> bool {
2778 true
2779 }
2780 fn eval_h(
2781 &mut self,
2782 _: Option<&[Number]>,
2783 _: bool,
2784 _: Number,
2785 _: Option<&[Number]>,
2786 _: bool,
2787 _: SparsityRequest<'_>,
2788 ) -> bool {
2789 true
2790 }
2791 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
2792 }
2793
2794 #[test]
2795 fn gradient_scaling_lifts_fixed_vars_to_their_value() {
2796 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(FixedVarShiftsObjGrad));
2797 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2798 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2799
2800 assert_eq!(nlp.n_full_x(), 2);
2802 assert_eq!(nlp.n(), 1);
2803
2804 nlp.determine_scaling_from_starting_point(
2805 ScalingMethod::GradientBased,
2806 100.0,
2807 1e-8,
2808 0.0,
2809 0.0,
2810 );
2811
2812 assert!(
2816 (nlp.obj_scale_factor() - 0.1).abs() < 1e-12,
2817 "fixed var must be lifted before scaling; expected df=0.1, got {}",
2818 nlp.obj_scale_factor()
2819 );
2820 }
2821
2822 #[test]
2823 fn constr_target_gradient_overrides_cutoff_and_clamp() {
2824 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqLargeOffset));
2829 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2830 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2831 nlp.determine_scaling_from_starting_point(
2832 ScalingMethod::GradientBased,
2833 100.0,
2834 1e-8,
2835 0.0,
2836 50.0,
2837 );
2838 let x = dense_x(&[5000.0], nlp.x_space());
2839 let mut d = nlp.d_space().make_new_dense();
2840 nlp.eval_d(&x, &mut d);
2841 assert!(
2843 (d.values()[0] - 2.5e5).abs() < 1e-6,
2844 "constr target=50 → dd=0.05; scaled d(5000)=2.5e5, got {}",
2845 d.values()[0]
2846 );
2847 }
2848
2849 struct Hs071UserScaled;
2854 impl TNLP for Hs071UserScaled {
2855 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2856 Hs071::default().get_nlp_info()
2857 }
2858 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2859 Hs071::default().get_bounds_info(b)
2860 }
2861 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2862 Hs071::default().get_starting_point(sp)
2863 }
2864 fn eval_f(&mut self, x: &[Number], new_x: bool) -> Option<Number> {
2865 Hs071::default().eval_f(x, new_x)
2866 }
2867 fn eval_grad_f(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
2868 Hs071::default().eval_grad_f(x, new_x, g)
2869 }
2870 fn eval_g(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
2871 Hs071::default().eval_g(x, new_x, g)
2872 }
2873 fn eval_jac_g(
2874 &mut self,
2875 x: Option<&[Number]>,
2876 new_x: bool,
2877 mode: SparsityRequest<'_>,
2878 ) -> bool {
2879 Hs071::default().eval_jac_g(x, new_x, mode)
2880 }
2881 fn eval_h(
2882 &mut self,
2883 x: Option<&[Number]>,
2884 new_x: bool,
2885 obj_factor: Number,
2886 lambda: Option<&[Number]>,
2887 new_lambda: bool,
2888 mode: SparsityRequest<'_>,
2889 ) -> bool {
2890 Hs071::default().eval_h(x, new_x, obj_factor, lambda, new_lambda, mode)
2891 }
2892 fn get_scaling_parameters(&mut self, req: ScalingRequest<'_>) -> bool {
2893 *req.obj_scaling = 2.0;
2894 *req.use_x_scaling = false;
2895 *req.use_g_scaling = true;
2896 req.g_scaling[0] = 0.5;
2898 req.g_scaling[1] = 0.25;
2899 true
2900 }
2901 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
2902 }
2903
2904 #[test]
2905 fn user_scaling_dispatch_applies_obj_and_g_scaling() {
2906 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071UserScaled));
2907 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2908 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2909 nlp.determine_scaling_from_starting_point(
2910 ScalingMethod::UserScaling,
2911 100.0,
2912 1e-8,
2913 0.0,
2914 0.0,
2915 );
2916
2917 assert!(
2920 (nlp.obj_scale_factor() - 2.0).abs() < 1e-12,
2921 "user obj_scaling=2.0 should be installed; got {}",
2922 nlp.obj_scale_factor()
2923 );
2924
2925 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2929 let mut c = nlp.c_space().make_new_dense();
2930 nlp.eval_c(&x, &mut c);
2931 assert!(
2934 (c.values()[0] - 3.0).abs() < 1e-9,
2935 "user g_scaling=0.25 on equality → c=3.0; got {}",
2936 c.values()[0]
2937 );
2938
2939 let mut d = nlp.d_space().make_new_dense();
2942 nlp.eval_d(&x, &mut d);
2943 assert!(
2944 (d.values()[0] - 12.5).abs() < 1e-9,
2945 "user g_scaling=0.5 on inequality → d=12.5; got {}",
2946 d.values()[0]
2947 );
2948
2949 let post_d_l = nlp
2952 .d_l()
2953 .as_any()
2954 .downcast_ref::<DenseVector>()
2955 .unwrap()
2956 .values()[0];
2957 assert!(
2958 (post_d_l - 12.5).abs() < 1e-9,
2959 "d_l scaled in step: got {}",
2960 post_d_l
2961 );
2962 }
2963
2964 struct Hs071DeclinesScaling;
2968 impl TNLP for Hs071DeclinesScaling {
2969 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2970 Hs071::default().get_nlp_info()
2971 }
2972 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2973 Hs071::default().get_bounds_info(b)
2974 }
2975 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2976 Hs071::default().get_starting_point(sp)
2977 }
2978 fn eval_f(&mut self, x: &[Number], new_x: bool) -> Option<Number> {
2979 Hs071::default().eval_f(x, new_x)
2980 }
2981 fn eval_grad_f(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
2982 Hs071::default().eval_grad_f(x, new_x, g)
2983 }
2984 fn eval_g(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
2985 Hs071::default().eval_g(x, new_x, g)
2986 }
2987 fn eval_jac_g(
2988 &mut self,
2989 x: Option<&[Number]>,
2990 new_x: bool,
2991 mode: SparsityRequest<'_>,
2992 ) -> bool {
2993 Hs071::default().eval_jac_g(x, new_x, mode)
2994 }
2995 fn eval_h(
2996 &mut self,
2997 x: Option<&[Number]>,
2998 new_x: bool,
2999 obj_factor: Number,
3000 lambda: Option<&[Number]>,
3001 new_lambda: bool,
3002 mode: SparsityRequest<'_>,
3003 ) -> bool {
3004 Hs071::default().eval_h(x, new_x, obj_factor, lambda, new_lambda, mode)
3005 }
3006 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
3007 }
3008
3009 #[test]
3010 fn user_scaling_falls_back_when_tnlp_declines() {
3011 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071DeclinesScaling));
3012 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3013 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3014 nlp.determine_scaling_from_starting_point(
3015 ScalingMethod::UserScaling,
3016 100.0,
3017 1e-8,
3018 0.0,
3019 0.0,
3020 );
3021 assert!((nlp.obj_scale_factor() - 1.0).abs() < 1e-12);
3024 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
3025 let mut c = nlp.c_space().make_new_dense();
3026 nlp.eval_c(&x, &mut c);
3027 assert_eq!(c.values(), &[12.0], "unscaled equality residual");
3028 }
3029
3030 #[test]
3031 fn eval_h_with_all_entries_on_fixed_var_does_not_panic() {
3032 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(FixedOnlyHess));
3033 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
3034 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
3035
3036 assert_eq!(nlp.h_space().unwrap().nonzeros(), 0);
3039
3040 let x = dense_x(&[0.5], &nlp.x_space().clone());
3041 let yc = dense_x(&[0.0], &nlp.c_space().clone());
3042 let yd = nlp.d_space().make_new_dense();
3043 let h = nlp.eval_h(&x, 1.0, &yc, &yd);
3044 assert_eq!(h.n_rows(), 1);
3045 }
3046}