1use crate::ipopt_nlp::{IpoptNlp, Nlp};
61use crate::tnlp::{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, 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 match method {
723 ScalingMethod::None => unreachable!("handled above"),
724 ScalingMethod::GradientBased => {
725 self.scale_gradient_based(
726 &cls,
727 &full_x,
728 user_obj_factor,
729 max_gradient,
730 min_value,
731 obj_target_gradient,
732 constr_target_gradient,
733 );
734 }
735 ScalingMethod::UserScaling => {
736 let applied = self.scale_user_supplied(&cls, user_obj_factor, min_value);
737 if !applied {
738 self.obj_scale_factor.set(user_obj_factor);
742 *self.c_scale.borrow_mut() = None;
743 *self.d_scale.borrow_mut() = None;
744 }
745 }
746 }
747
748 self.apply_d_scale_to_bounds();
751
752 self.invalidate_eval_caches();
755 }
756
757 fn scale_gradient_based(
760 &self,
761 cls: &BoundClassification,
762 full_x: &[Number],
763 user_obj_factor: Number,
764 max_gradient: Number,
765 min_value: Number,
766 obj_target_gradient: Number,
767 constr_target_gradient: Number,
768 ) {
769 let n_full_x = cls.n_full_x as usize;
770 let n_full_g = cls.n_full_g as usize;
771
772 let mut full_grad_f = vec![0.0; n_full_x];
774 let grad_ok = {
775 let a = self.adapter.borrow();
776 let mut t = a.tnlp().borrow_mut();
777 t.eval_grad_f(full_x, true, &mut full_grad_f)
778 };
779 let mut df = 1.0;
780 if grad_ok {
781 let mut max_grad_f: Number = 0.0;
784 for &full_idx in cls.x_not_fixed_map.iter() {
785 let v = full_grad_f[full_idx as usize].abs();
786 if v > max_grad_f {
787 max_grad_f = v;
788 }
789 }
790 if obj_target_gradient > 0.0 && max_grad_f > 0.0 {
791 df = obj_target_gradient / max_grad_f;
794 } else if max_grad_f > max_gradient {
795 df = max_gradient / max_grad_f;
796 }
797 if df < min_value {
798 df = min_value;
799 }
800 }
801 self.obj_scale_factor.set(df * user_obj_factor);
802
803 if cls.n_full_g == 0 {
805 *self.c_scale.borrow_mut() = None;
806 *self.d_scale.borrow_mut() = None;
807 return;
808 }
809 let mut full_jac_vals = vec![0.0; self.nnz_jac_g_full as usize];
811 let jac_ok = {
812 let a = self.adapter.borrow();
813 let mut t = a.tnlp().borrow_mut();
814 t.eval_jac_g(
815 Some(full_x),
816 true,
817 SparsityRequest::Values {
818 values: &mut full_jac_vals,
819 },
820 )
821 };
822 if !jac_ok {
823 *self.c_scale.borrow_mut() = None;
824 *self.d_scale.borrow_mut() = None;
825 return;
826 }
827 let mut full_irow = vec![0 as Index; self.nnz_jac_g_full as usize];
829 let mut full_jcol = vec![0 as Index; self.nnz_jac_g_full as usize];
830 let _ = {
831 let a = self.adapter.borrow();
832 let mut t = a.tnlp().borrow_mut();
833 t.eval_jac_g(
834 None,
835 false,
836 SparsityRequest::Structure {
837 irow: &mut full_irow,
838 jcol: &mut full_jcol,
839 },
840 )
841 };
842 let style_offset: Index = match self.info.index_style {
843 crate::tnlp::IndexStyle::C => 0,
844 crate::tnlp::IndexStyle::Fortran => 1,
845 };
846 let mut g_to_c = vec![-1 as Index; n_full_g];
848 for (c_idx, &g_idx) in cls.c_map.iter().enumerate() {
849 g_to_c[g_idx as usize] = c_idx as Index;
850 }
851 let mut g_to_d = vec![-1 as Index; n_full_g];
852 for (d_idx, &g_idx) in cls.d_map.iter().enumerate() {
853 g_to_d[g_idx as usize] = d_idx as Index;
854 }
855 let n_c = cls.n_c as usize;
856 let n_d = cls.n_d as usize;
857 let dbl_min = Number::MIN_POSITIVE;
859 let mut c_row_max: Vec<Number> = vec![dbl_min; n_c];
860 let mut d_row_max: Vec<Number> = vec![dbl_min; n_d];
861 for k in 0..self.nnz_jac_g_full as usize {
862 let g_row_0 = (full_irow[k] - style_offset) as usize;
863 let v = full_jac_vals[k].abs();
864 let cr = g_to_c[g_row_0];
865 if cr >= 0 {
866 let row = cr as usize;
867 if v > c_row_max[row] {
868 c_row_max[row] = v;
869 }
870 } else {
871 let dr = g_to_d[g_row_0];
872 if dr >= 0 {
873 let row = dr as usize;
874 if v > d_row_max[row] {
875 d_row_max[row] = v;
876 }
877 }
878 }
879 }
880
881 let row_max_to_scale = |row_max: Number| -> Number {
882 let mut s = if constr_target_gradient > 0.0 {
887 constr_target_gradient / row_max
888 } else {
889 let raw = max_gradient / row_max;
890 if raw > 1.0 {
891 1.0
892 } else {
893 raw
894 }
895 };
896 if s < min_value {
897 s = min_value;
898 }
899 s
900 };
901 let any_row_above = |rows: &[Number]| -> bool {
902 constr_target_gradient > 0.0 || rows.iter().any(|&v| v > max_gradient)
903 };
904
905 if n_c > 0 && any_row_above(&c_row_max) {
906 let dc: Vec<Number> = c_row_max.iter().map(|&v| row_max_to_scale(v)).collect();
907 *self.c_scale.borrow_mut() = Some(dc);
908 } else {
909 *self.c_scale.borrow_mut() = None;
910 }
911
912 if n_d > 0 && any_row_above(&d_row_max) {
913 let dd: Vec<Number> = d_row_max.iter().map(|&v| row_max_to_scale(v)).collect();
914 *self.d_scale.borrow_mut() = Some(dd);
915 } else {
916 *self.d_scale.borrow_mut() = None;
917 }
918 }
919
920 fn scale_user_supplied(
932 &self,
933 cls: &BoundClassification,
934 user_obj_factor: Number,
935 min_value: Number,
936 ) -> bool {
937 let n_full_x = cls.n_full_x as usize;
938 let n_full_g = cls.n_full_g as usize;
939 let mut obj_scaling: Number = 1.0;
940 let mut use_x_scaling = false;
941 let mut x_scaling = vec![1.0; n_full_x];
942 let mut use_g_scaling = false;
943 let mut g_scaling = vec![1.0; n_full_g];
944 let ok = {
945 let a = self.adapter.borrow();
946 let mut t = a.tnlp().borrow_mut();
947 t.get_scaling_parameters(ScalingRequest {
948 obj_scaling: &mut obj_scaling,
949 use_x_scaling: &mut use_x_scaling,
950 x_scaling: &mut x_scaling,
951 use_g_scaling: &mut use_g_scaling,
952 g_scaling: &mut g_scaling,
953 })
954 };
955 if !ok {
956 return false;
957 }
958
959 let mut df = obj_scaling;
963 if df.abs() < min_value {
964 df = df.signum().max(0.0).max(1.0) * min_value;
967 }
968 self.obj_scale_factor.set(df * user_obj_factor);
969
970 if use_g_scaling && g_scaling.len() == n_full_g {
972 let n_c = cls.n_c as usize;
973 let n_d = cls.n_d as usize;
974 let mut dc = vec![1.0; n_c];
975 for (c_idx, &g_idx) in cls.c_map.iter().enumerate() {
976 let s = g_scaling[g_idx as usize];
977 dc[c_idx] = if s < min_value { min_value } else { s };
978 }
979 let mut dd = vec![1.0; n_d];
980 for (d_idx, &g_idx) in cls.d_map.iter().enumerate() {
981 let s = g_scaling[g_idx as usize];
982 dd[d_idx] = if s < min_value { min_value } else { s };
983 }
984 let nontrivial_c = dc.iter().any(|&s| s != 1.0);
987 *self.c_scale.borrow_mut() = if nontrivial_c && n_c > 0 {
988 Some(dc)
989 } else {
990 None
991 };
992 let nontrivial_d = dd.iter().any(|&s| s != 1.0);
993 *self.d_scale.borrow_mut() = if nontrivial_d && n_d > 0 {
994 Some(dd)
995 } else {
996 None
997 };
998 } else {
999 *self.c_scale.borrow_mut() = None;
1000 *self.d_scale.borrow_mut() = None;
1001 }
1002 let _ = use_x_scaling;
1004 true
1005 }
1006
1007 fn apply_d_scale_to_bounds(&mut self) {
1012 let cls = self.adapter.borrow().classification().clone();
1013 if let Some(dd) = self.d_scale.borrow().as_ref() {
1014 if let Some(d_l) = Rc::get_mut(&mut self.d_l) {
1015 let xs = d_l.values_mut();
1016 for (i, slot) in xs.iter_mut().enumerate() {
1017 let d_idx = cls.d_l_map[i] as usize;
1018 *slot *= dd[d_idx];
1019 }
1020 }
1021 if let Some(d_u) = Rc::get_mut(&mut self.d_u) {
1022 let xs = d_u.values_mut();
1023 for (i, slot) in xs.iter_mut().enumerate() {
1024 let d_idx = cls.d_u_map[i] as usize;
1025 *slot *= dd[d_idx];
1026 }
1027 }
1028 }
1029 }
1030
1031 fn invalidate_eval_caches(&self) {
1032 self.f_cache.borrow_mut().clear();
1033 self.grad_f_cache.borrow_mut().clear();
1034 self.c_cache.borrow_mut().clear();
1035 self.d_cache.borrow_mut().clear();
1036 self.jac_c_cache.borrow_mut().clear();
1037 self.jac_d_cache.borrow_mut().clear();
1038 self.h_cache.borrow_mut().clear();
1039 }
1040
1041 pub fn f_evals(&self) -> Index {
1042 *self.f_evals.borrow()
1043 }
1044 pub fn grad_f_evals(&self) -> Index {
1045 *self.grad_f_evals.borrow()
1046 }
1047 pub fn c_evals(&self) -> Index {
1048 *self.c_evals.borrow()
1049 }
1050 pub fn d_evals(&self) -> Index {
1051 *self.d_evals.borrow()
1052 }
1053 pub fn jac_c_evals(&self) -> Index {
1054 *self.jac_c_evals.borrow()
1055 }
1056 pub fn jac_d_evals(&self) -> Index {
1057 *self.jac_d_evals.borrow()
1058 }
1059 pub fn h_evals(&self) -> Index {
1060 *self.h_evals.borrow()
1061 }
1062
1063 pub fn lift_x_to_full(&self, x: &dyn Vector) -> Vec<Number> {
1069 let Some(dx) = x.as_any().downcast_ref::<DenseVector>() else {
1070 panic!("OrigIpoptNlp expects DenseVector for x");
1071 };
1072 let a = self.adapter.borrow();
1073 let cls = a.classification();
1074 let mut full = vec![0.0; cls.n_full_x as usize];
1075 let vals = dx.expanded_values();
1076 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
1077 full[full_idx as usize] = vals[var_idx];
1078 }
1079 for (i, &full_idx) in cls.x_fixed_map.iter().enumerate() {
1080 full[full_idx as usize] = cls.x_fixed_vals[i];
1081 }
1082 full
1083 }
1084
1085 pub fn finalize_solution_lambda(&self, y_c: &dyn Vector, y_d: &dyn Vector) -> Vec<Number> {
1097 let cls = self.adapter.borrow().classification().clone();
1098 let mut lambda = self.pack_lambda_for_user(y_c, y_d, &cls);
1099 let obj_scal = self.obj_scale_factor.get();
1100 if obj_scal != 0.0 && obj_scal != 1.0 {
1101 let inv = 1.0 / obj_scal;
1102 for v in lambda.iter_mut() {
1103 *v *= inv;
1104 }
1105 }
1106 lambda
1107 }
1108
1109 pub fn finalize_solution_z_l(&self, z_l: &dyn Vector) -> Vec<Number> {
1117 let cls = self.adapter.borrow().classification().clone();
1118 let n_full_x = cls.n_full_x as usize;
1119 let mut full_z_l = vec![0.0; n_full_x];
1120 let n_x_l = self.x_l.dim() as usize;
1121 if n_x_l == 0 {
1122 return full_z_l;
1123 }
1124 let Some(dz) = z_l.as_any().downcast_ref::<DenseVector>() else {
1125 panic!("OrigIpoptNlp::finalize_solution_z_l expects DenseVector");
1126 };
1127 let vals = dz.expanded_values();
1128 let obj_scal = self.obj_scale_factor.get();
1129 let inv = if obj_scal == 0.0 { 1.0 } else { 1.0 / obj_scal };
1130 for i in 0..n_x_l {
1131 let var_idx = cls.x_l_map[i] as usize;
1132 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1133 full_z_l[full_idx] = vals[i] * inv;
1134 }
1135 full_z_l
1136 }
1137
1138 pub fn finalize_solution_z_u(&self, z_u: &dyn Vector) -> Vec<Number> {
1141 let cls = self.adapter.borrow().classification().clone();
1142 let n_full_x = cls.n_full_x as usize;
1143 let mut full_z_u = vec![0.0; n_full_x];
1144 let n_x_u = self.x_u.dim() as usize;
1145 if n_x_u == 0 {
1146 return full_z_u;
1147 }
1148 let Some(dz) = z_u.as_any().downcast_ref::<DenseVector>() else {
1149 panic!("OrigIpoptNlp::finalize_solution_z_u expects DenseVector");
1150 };
1151 let vals = dz.expanded_values();
1152 let obj_scal = self.obj_scale_factor.get();
1153 let inv = if obj_scal == 0.0 { 1.0 } else { 1.0 / obj_scal };
1154 for i in 0..n_x_u {
1155 let var_idx = cls.x_u_map[i] as usize;
1156 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1157 full_z_u[full_idx] = vals[i] * inv;
1158 }
1159 full_z_u
1160 }
1161
1162 pub fn pack_lambda_for_user(
1172 &self,
1173 y_c: &dyn Vector,
1174 y_d: &dyn Vector,
1175 cls: &BoundClassification,
1176 ) -> Vec<Number> {
1177 let mut lambda = vec![0.0; cls.n_full_g as usize];
1178 if cls.n_c > 0 {
1179 let Some(dy) = y_c.as_any().downcast_ref::<DenseVector>() else {
1180 panic!("OrigIpoptNlp expects DenseVector for y_c");
1181 };
1182 let vals = dy.expanded_values();
1183 let cs = self.c_scale.borrow();
1184 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1185 lambda[g_idx as usize] = match cs.as_ref() {
1186 Some(v) => vals[i] * v[i],
1187 None => vals[i],
1188 };
1189 }
1190 }
1191 if cls.n_d > 0 {
1192 let Some(dy) = y_d.as_any().downcast_ref::<DenseVector>() else {
1193 panic!("OrigIpoptNlp expects DenseVector for y_d");
1194 };
1195 let vals = dy.expanded_values();
1196 let ds = self.d_scale.borrow();
1197 for (i, &g_idx) in cls.d_map.iter().enumerate() {
1198 lambda[g_idx as usize] = match ds.as_ref() {
1199 Some(v) => vals[i] * v[i],
1200 None => vals[i],
1201 };
1202 }
1203 }
1204 lambda
1205 }
1206
1207 #[allow(clippy::too_many_arguments)]
1217 pub fn initialize_starting_point(
1218 &mut self,
1219 x: &mut DenseVector,
1220 init_x: bool,
1221 y_c: &mut DenseVector,
1222 init_y_c: bool,
1223 y_d: &mut DenseVector,
1224 init_y_d: bool,
1225 z_l: &mut DenseVector,
1226 init_z_l: bool,
1227 z_u: &mut DenseVector,
1228 init_z_u: bool,
1229 ) -> bool {
1230 let n_full_x = self.adapter.borrow().classification().n_full_x as usize;
1231 let n_full_g = self.adapter.borrow().classification().n_full_g as usize;
1232 let n_x_l = self.x_l.dim() as usize;
1233 let n_x_u = self.x_u.dim() as usize;
1234
1235 let mut full_x = vec![0.0; n_full_x];
1236 let mut full_z_l = vec![0.0; n_full_x];
1237 let mut full_z_u = vec![0.0; n_full_x];
1238 let mut full_lambda = vec![0.0; n_full_g];
1239
1240 let ok = {
1241 let a = self.adapter.borrow();
1242 let mut t = a.tnlp().borrow_mut();
1243 t.get_starting_point(StartingPoint {
1244 init_x,
1245 x: &mut full_x,
1246 init_z: init_z_l || init_z_u,
1247 z_l: &mut full_z_l,
1248 z_u: &mut full_z_u,
1249 init_lambda: init_y_c || init_y_d,
1250 lambda: &mut full_lambda,
1251 })
1252 };
1253 if !ok {
1254 return false;
1255 }
1256
1257 let cls = self.adapter.borrow().classification().clone();
1258 let obj_scal = self.obj_scale_factor.get();
1259 let c_scale = self.c_scale.borrow();
1260 let d_scale = self.d_scale.borrow();
1261
1262 if init_x {
1264 let xs = x.values_mut();
1265 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
1266 xs[var_idx] = full_x[full_idx as usize];
1267 }
1268 }
1269 if init_y_c && cls.n_c > 0 {
1275 let yc = y_c.values_mut();
1276 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1277 let cs = c_scale.as_ref().map(|v| v[i]).unwrap_or(1.0);
1278 yc[i] = full_lambda[g_idx as usize] / cs * obj_scal;
1279 }
1280 }
1281 if init_y_d && cls.n_d > 0 {
1282 let yd = y_d.values_mut();
1283 for (i, &g_idx) in cls.d_map.iter().enumerate() {
1284 let ds = d_scale.as_ref().map(|v| v[i]).unwrap_or(1.0);
1285 yd[i] = full_lambda[g_idx as usize] / ds * obj_scal;
1286 }
1287 }
1288 if init_z_l && n_x_l > 0 {
1290 let zl = z_l.values_mut();
1291 for (i, slot) in zl.iter_mut().enumerate().take(n_x_l) {
1292 let var_idx = cls.x_l_map[i] as usize;
1293 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1294 *slot = full_z_l[full_idx] * obj_scal;
1295 }
1296 }
1297 if init_z_u && n_x_u > 0 {
1298 let zu = z_u.values_mut();
1299 for (i, slot) in zu.iter_mut().enumerate().take(n_x_u) {
1300 let var_idx = cls.x_u_map[i] as usize;
1301 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1302 *slot = full_z_u[full_idx] * obj_scal;
1303 }
1304 }
1305 true
1306 }
1307
1308 fn eval_f_internal(&self, x: &dyn Vector) -> Number {
1311 if let Some(v) = self.f_cache.borrow().get_1dep(x.as_tagged()) {
1312 return v;
1313 }
1314 *self.f_evals.borrow_mut() += 1;
1315 let full_x = self.lift_x_to_full(x);
1316 let unscaled = {
1317 let a = self.adapter.borrow();
1318 let mut t = a.tnlp().borrow_mut();
1319 t.eval_f(&full_x, true).unwrap_or(f64::NAN)
1324 };
1325 let scaled = unscaled * self.obj_scale_factor.get();
1326 self.f_cache.borrow_mut().add_1dep(scaled, x.as_tagged());
1327 scaled
1328 }
1329
1330 fn eval_grad_f_internal(&self, x: &dyn Vector) -> Rc<dyn Vector> {
1331 if let Some(v) = self.grad_f_cache.borrow().get_1dep(x.as_tagged()) {
1332 return v;
1333 }
1334 *self.grad_f_evals.borrow_mut() += 1;
1335 let full_x = self.lift_x_to_full(x);
1336 let mut full_g = vec![0.0; full_x.len()];
1337 let ok = {
1338 let a = self.adapter.borrow();
1339 let mut t = a.tnlp().borrow_mut();
1340 t.eval_grad_f(&full_x, true, &mut full_g)
1341 };
1342 if !ok {
1345 full_g.fill(f64::NAN);
1346 }
1347 let cls = self.adapter.borrow().classification().clone();
1349 let mut g_compressed = self.x_space.make_new_dense();
1350 let obj_scal = self.obj_scale_factor.get();
1351 {
1352 let gv = g_compressed.values_mut();
1353 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
1354 gv[var_idx] = full_g[full_idx as usize] * obj_scal;
1355 }
1356 }
1357 let result: Rc<dyn Vector> = Rc::new(g_compressed);
1358 self.grad_f_cache
1359 .borrow_mut()
1360 .add_1dep(Rc::clone(&result), x.as_tagged());
1361 result
1362 }
1363
1364 fn eval_c_internal(&self, x: &dyn Vector) -> Rc<dyn Vector> {
1365 let cls = self.adapter.borrow().classification().clone();
1366 if cls.n_c == 0 {
1367 if let Some(v) = self.c_cache.borrow().get_1dep(x.as_tagged()) {
1369 return v;
1370 }
1371 let v = self.c_space.make_new_dense();
1372 let result: Rc<dyn Vector> = Rc::new(v);
1373 self.c_cache
1374 .borrow_mut()
1375 .add_1dep(Rc::clone(&result), x.as_tagged());
1376 return result;
1377 }
1378 if let Some(v) = self.c_cache.borrow().get_1dep(x.as_tagged()) {
1379 return v;
1380 }
1381 *self.c_evals.borrow_mut() += 1;
1382 let full_x = self.lift_x_to_full(x);
1383 let mut full_g = vec![0.0; cls.n_full_g as usize];
1384 let ok = {
1385 let a = self.adapter.borrow();
1386 let mut t = a.tnlp().borrow_mut();
1387 t.eval_g(&full_x, true, &mut full_g)
1388 };
1389 if !ok {
1392 full_g.fill(f64::NAN);
1393 }
1394 let mut c = self.c_space.make_new_dense();
1395 let n_full_g = cls.n_full_g as usize;
1401 let mut full_g_l = vec![0.0; n_full_g];
1402 let mut full_g_u = vec![0.0; n_full_g];
1403 {
1404 let mut tmp_x_l = vec![0.0; cls.n_full_x as usize];
1405 let mut tmp_x_u = vec![0.0; cls.n_full_x as usize];
1406 let a = self.adapter.borrow();
1407 let mut t = a.tnlp().borrow_mut();
1408 t.get_bounds_info(crate::tnlp::BoundsInfo {
1409 x_l: &mut tmp_x_l,
1410 x_u: &mut tmp_x_u,
1411 g_l: &mut full_g_l,
1412 g_u: &mut full_g_u,
1413 });
1414 }
1415 {
1416 let cv = c.values_mut();
1417 let cs = self.c_scale.borrow();
1418 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1419 let raw = full_g[g_idx as usize] - full_g_l[g_idx as usize];
1420 cv[i] = match cs.as_ref() {
1421 Some(v) => raw * v[i],
1422 None => raw,
1423 };
1424 }
1425 }
1426 let result: Rc<dyn Vector> = Rc::new(c);
1427 self.c_cache
1428 .borrow_mut()
1429 .add_1dep(Rc::clone(&result), x.as_tagged());
1430 result
1431 }
1432
1433 fn eval_d_internal(&self, x: &dyn Vector) -> Rc<dyn Vector> {
1434 let cls = self.adapter.borrow().classification().clone();
1435 if cls.n_d == 0 {
1436 if let Some(v) = self.d_cache.borrow().get_1dep(x.as_tagged()) {
1437 return v;
1438 }
1439 let v = self.d_space.make_new_dense();
1440 let result: Rc<dyn Vector> = Rc::new(v);
1441 self.d_cache
1442 .borrow_mut()
1443 .add_1dep(Rc::clone(&result), x.as_tagged());
1444 return result;
1445 }
1446 if let Some(v) = self.d_cache.borrow().get_1dep(x.as_tagged()) {
1447 return v;
1448 }
1449 *self.d_evals.borrow_mut() += 1;
1450 let full_x = self.lift_x_to_full(x);
1451 let mut full_g = vec![0.0; cls.n_full_g as usize];
1452 let ok = {
1453 let a = self.adapter.borrow();
1454 let mut t = a.tnlp().borrow_mut();
1455 t.eval_g(&full_x, true, &mut full_g)
1456 };
1457 if !ok {
1458 full_g.fill(f64::NAN);
1459 }
1460 let mut d = self.d_space.make_new_dense();
1461 {
1462 let dv = d.values_mut();
1463 let ds = self.d_scale.borrow();
1464 for (i, &g_idx) in cls.d_map.iter().enumerate() {
1465 let raw = full_g[g_idx as usize];
1466 dv[i] = match ds.as_ref() {
1467 Some(v) => raw * v[i],
1468 None => raw,
1469 };
1470 }
1471 }
1472 let result: Rc<dyn Vector> = Rc::new(d);
1473 self.d_cache
1474 .borrow_mut()
1475 .add_1dep(Rc::clone(&result), x.as_tagged());
1476 result
1477 }
1478
1479 fn eval_jac_c_internal(&self, x: &dyn Vector) -> Rc<dyn Matrix> {
1480 if let Some(m) = self.jac_c_cache.borrow().get_1dep(x.as_tagged()) {
1481 return m;
1482 }
1483 *self.jac_c_evals.borrow_mut() += 1;
1484 let mut full_vals = vec![0.0; self.nnz_jac_g_full as usize];
1485 let full_x = self.lift_x_to_full(x);
1486 let ok = {
1487 let a = self.adapter.borrow();
1488 let mut t = a.tnlp().borrow_mut();
1489 t.eval_jac_g(
1490 Some(&full_x),
1491 true,
1492 SparsityRequest::Values {
1493 values: &mut full_vals,
1494 },
1495 )
1496 };
1497 if !ok {
1498 full_vals.fill(f64::NAN);
1499 }
1500 let mut jac_c = GenTMatrix::new(Rc::clone(&self.jac_c_space));
1501 {
1502 let cs = self.c_scale.borrow();
1503 let irows = self.jac_c_space.irows().to_vec();
1504 let vs = jac_c.values_mut();
1505 for (k, &src) in self.jac_c_entry_in_g.iter().enumerate() {
1506 let raw = full_vals[src as usize];
1507 vs[k] = match cs.as_ref() {
1508 Some(v) => raw * v[(irows[k] - 1) as usize],
1510 None => raw,
1511 };
1512 }
1513 }
1514 let result: Rc<dyn Matrix> = Rc::new(jac_c);
1515 self.jac_c_cache
1516 .borrow_mut()
1517 .add_1dep(Rc::clone(&result), x.as_tagged());
1518 result
1519 }
1520
1521 fn eval_jac_d_internal(&self, x: &dyn Vector) -> Rc<dyn Matrix> {
1522 if let Some(m) = self.jac_d_cache.borrow().get_1dep(x.as_tagged()) {
1523 return m;
1524 }
1525 *self.jac_d_evals.borrow_mut() += 1;
1526 let mut full_vals = vec![0.0; self.nnz_jac_g_full as usize];
1527 let full_x = self.lift_x_to_full(x);
1528 let ok = {
1529 let a = self.adapter.borrow();
1530 let mut t = a.tnlp().borrow_mut();
1531 t.eval_jac_g(
1532 Some(&full_x),
1533 true,
1534 SparsityRequest::Values {
1535 values: &mut full_vals,
1536 },
1537 )
1538 };
1539 if !ok {
1540 full_vals.fill(f64::NAN);
1541 }
1542 let mut jac_d = GenTMatrix::new(Rc::clone(&self.jac_d_space));
1543 {
1544 let ds = self.d_scale.borrow();
1545 let irows = self.jac_d_space.irows().to_vec();
1546 let vs = jac_d.values_mut();
1547 for (k, &src) in self.jac_d_entry_in_g.iter().enumerate() {
1548 let raw = full_vals[src as usize];
1549 vs[k] = match ds.as_ref() {
1550 Some(v) => raw * v[(irows[k] - 1) as usize],
1551 None => raw,
1552 };
1553 }
1554 }
1555 let result: Rc<dyn Matrix> = Rc::new(jac_d);
1556 self.jac_d_cache
1557 .borrow_mut()
1558 .add_1dep(Rc::clone(&result), x.as_tagged());
1559 result
1560 }
1561
1562 fn eval_h_internal(
1563 &self,
1564 x: &dyn Vector,
1565 obj_factor: Number,
1566 y_c: &dyn Vector,
1567 y_d: &dyn Vector,
1568 ) -> Rc<dyn SymMatrix> {
1569 if let Some(m) = self.h_cache.borrow().get(
1572 &[x.as_tagged(), y_c.as_tagged(), y_d.as_tagged()],
1573 &[obj_factor],
1574 ) {
1575 return m;
1576 }
1577 *self.h_evals.borrow_mut() += 1;
1578 let Some(h_space) = self.h_space.as_ref() else {
1579 panic!(
1580 "OrigIpoptNlp::eval_h called but the TNLP did not provide \
1581 eval_h sparsity. The L-BFGS path lands in Phase 8."
1582 );
1583 };
1584 let cls = self.adapter.borrow().classification().clone();
1585 let full_x = self.lift_x_to_full(x);
1586 let full_lambda = self.pack_lambda_for_user(y_c, y_d, &cls);
1594 let scaled_obj_factor = obj_factor * self.obj_scale_factor.get();
1595
1596 let mut full_vals = vec![0.0; self.nnz_h_lag_full as usize];
1601 let ok = {
1602 let a = self.adapter.borrow();
1603 let mut t = a.tnlp().borrow_mut();
1604 t.eval_h(
1605 Some(&full_x),
1606 true,
1607 scaled_obj_factor,
1608 Some(&full_lambda),
1609 true,
1610 SparsityRequest::Values {
1611 values: &mut full_vals,
1612 },
1613 )
1614 };
1615 if !ok {
1616 full_vals.fill(f64::NAN);
1617 }
1618 let mut h = SymTMatrix::new(Rc::clone(h_space));
1619 let kept = h_space.nonzeros() as usize;
1620 let h_vals = h.values_mut();
1621 debug_assert_eq!(kept, self.h_entry_in_full.len());
1624 for (k, &src) in self.h_entry_in_full.iter().enumerate() {
1625 h_vals[k] = full_vals[src as usize];
1626 }
1627 let result: Rc<dyn SymMatrix> = Rc::new(h);
1628 self.h_cache.borrow_mut().add(
1629 Rc::clone(&result),
1630 &[x.as_tagged(), y_c.as_tagged(), y_d.as_tagged()],
1631 &[obj_factor],
1632 );
1633 result
1634 }
1635}
1636
1637fn make_dense_from(
1640 space: &Rc<DenseVectorSpace>,
1641 mut f: impl FnMut(usize) -> Number,
1642) -> DenseVector {
1643 let mut v = space.make_new_dense();
1644 let dim = space.dim() as usize;
1645 if dim > 0 {
1646 let vs = v.values_mut();
1647 for (i, slot) in vs.iter_mut().enumerate().take(dim) {
1648 *slot = f(i);
1649 }
1650 }
1651 v
1652}
1653
1654impl Nlp for OrigIpoptNlp {
1657 fn n(&self) -> Index {
1658 self.x_space.dim()
1659 }
1660 fn m_eq(&self) -> Index {
1661 self.c_space.dim()
1662 }
1663 fn m_ineq(&self) -> Index {
1664 self.d_space.dim()
1665 }
1666
1667 fn eval_f(&mut self, x: &dyn Vector) -> Number {
1668 self.timed_eval(|t| &t.eval_obj, || self.eval_f_internal(x))
1669 }
1670 fn eval_grad_f(&mut self, x: &dyn Vector, g: &mut dyn Vector) {
1671 let result = self.timed_eval(|t| &t.eval_grad_obj, || self.eval_grad_f_internal(x));
1672 g.copy(&*result);
1673 }
1674 fn eval_c(&mut self, x: &dyn Vector, c: &mut dyn Vector) {
1675 let result = self.timed_eval(|t| &t.eval_constr, || self.eval_c_internal(x));
1676 c.copy(&*result);
1677 }
1678 fn eval_d(&mut self, x: &dyn Vector, d: &mut dyn Vector) {
1679 let result = self.timed_eval(|t| &t.eval_constr, || self.eval_d_internal(x));
1680 d.copy(&*result);
1681 }
1682 fn eval_jac_c(&mut self, x: &dyn Vector) -> Rc<dyn Matrix> {
1683 self.timed_eval(|t| &t.eval_constr_jac, || self.eval_jac_c_internal(x))
1684 }
1685 fn eval_jac_d(&mut self, x: &dyn Vector) -> Rc<dyn Matrix> {
1686 self.timed_eval(|t| &t.eval_constr_jac, || self.eval_jac_d_internal(x))
1687 }
1688 fn eval_h(
1689 &mut self,
1690 x: &dyn Vector,
1691 obj_factor: Number,
1692 y_c: &dyn Vector,
1693 y_d: &dyn Vector,
1694 ) -> Rc<dyn SymMatrix> {
1695 self.timed_eval(
1696 |t| &t.eval_lag_hess,
1697 || self.eval_h_internal(x, obj_factor, y_c, y_d),
1698 )
1699 }
1700}
1701
1702impl IpoptNlp for OrigIpoptNlp {
1703 fn x_l(&self) -> &dyn Vector {
1704 &*self.x_l
1705 }
1706 fn x_u(&self) -> &dyn Vector {
1707 &*self.x_u
1708 }
1709 fn d_l(&self) -> &dyn Vector {
1710 &*self.d_l
1711 }
1712 fn d_u(&self) -> &dyn Vector {
1713 &*self.d_u
1714 }
1715 fn px_l(&self) -> Rc<dyn Matrix> {
1716 Rc::clone(&self.px_l)
1717 }
1718 fn px_u(&self) -> Rc<dyn Matrix> {
1719 Rc::clone(&self.px_u)
1720 }
1721 fn pd_l(&self) -> Rc<dyn Matrix> {
1722 Rc::clone(&self.pd_l)
1723 }
1724 fn pd_u(&self) -> Rc<dyn Matrix> {
1725 Rc::clone(&self.pd_u)
1726 }
1727
1728 fn obj_scaling_factor(&self) -> Number {
1729 self.obj_scale_factor.get()
1730 }
1731
1732 fn get_starting_x(&mut self, x: &mut dyn Vector) -> bool {
1736 let cls = self.adapter.borrow().classification().clone();
1737 let n_full_x = cls.n_full_x as usize;
1738 let n_full_g = cls.n_full_g as usize;
1739 let mut full_x = vec![0.0; n_full_x];
1740 let mut full_z_l = vec![0.0; n_full_x];
1741 let mut full_z_u = vec![0.0; n_full_x];
1742 let mut full_lambda = vec![0.0; n_full_g];
1743 let ok = {
1744 let a = self.adapter.borrow();
1745 let mut t = a.tnlp().borrow_mut();
1746 t.get_starting_point(StartingPoint {
1747 init_x: true,
1748 x: &mut full_x,
1749 init_z: false,
1750 z_l: &mut full_z_l,
1751 z_u: &mut full_z_u,
1752 init_lambda: false,
1753 lambda: &mut full_lambda,
1754 })
1755 };
1756 if !ok {
1757 return false;
1758 }
1759 let Some(dx) = x.as_any_mut().downcast_mut::<DenseVector>() else {
1760 return false;
1761 };
1762 let xs = dx.values_mut();
1763 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
1764 xs[var_idx] = full_x[full_idx as usize];
1765 }
1766 true
1767 }
1768
1769 fn lift_x_to_full(&self, x: &dyn Vector) -> Vec<Number> {
1770 OrigIpoptNlp::lift_x_to_full(self, x)
1771 }
1772
1773 fn n_full_x(&self) -> Index {
1774 self.adapter.borrow().classification().n_full_x
1775 }
1776
1777 fn n_full_g(&self) -> Index {
1778 self.adapter.borrow().classification().n_full_g
1779 }
1780
1781 fn pack_lambda_for_user(&self, y_c: &dyn Vector, y_d: &dyn Vector) -> Vec<Number> {
1782 let cls = self.adapter.borrow().classification().clone();
1783 OrigIpoptNlp::pack_lambda_for_user(self, y_c, y_d, &cls)
1784 }
1785
1786 fn pack_g_for_user(&self, c: &dyn Vector, d: &dyn Vector) -> Vec<Number> {
1787 let cls = self.adapter.borrow().classification().clone();
1788 let mut g = vec![0.0; cls.n_full_g as usize];
1789 if cls.n_c > 0 {
1790 let Some(dc) = c.as_any().downcast_ref::<DenseVector>() else {
1791 panic!("OrigIpoptNlp expects DenseVector for c");
1792 };
1793 let cs = self.c_scale.borrow();
1794 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1795 let v = dc.expanded_values()[i];
1796 g[g_idx as usize] = match cs.as_ref() {
1797 Some(s) => v / s[i],
1798 None => v,
1799 };
1800 }
1801 }
1802 if cls.n_d > 0 {
1803 let Some(dd) = d.as_any().downcast_ref::<DenseVector>() else {
1804 panic!("OrigIpoptNlp expects DenseVector for d");
1805 };
1806 let ds = self.d_scale.borrow();
1807 for (i, &g_idx) in cls.d_map.iter().enumerate() {
1808 let v = dd.expanded_values()[i];
1809 g[g_idx as usize] = match ds.as_ref() {
1810 Some(s) => v / s[i],
1811 None => v,
1812 };
1813 }
1814 }
1815 g
1816 }
1817
1818 fn pack_z_l_for_user(&self, z_l: &dyn Vector) -> Vec<Number> {
1819 let cls = self.adapter.borrow().classification().clone();
1820 let mut full = vec![0.0; cls.n_full_x as usize];
1821 if z_l.dim() == 0 {
1822 return full;
1823 }
1824 let Some(dz) = z_l.as_any().downcast_ref::<DenseVector>() else {
1825 panic!("OrigIpoptNlp expects DenseVector for z_l");
1826 };
1827 let vals = dz.expanded_values();
1828 for (k, &var_idx) in cls.x_l_map.iter().enumerate() {
1829 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
1830 full[full_idx] = vals[k];
1831 }
1832 full
1833 }
1834
1835 fn pack_z_u_for_user(&self, z_u: &dyn Vector) -> Vec<Number> {
1836 let cls = self.adapter.borrow().classification().clone();
1837 let mut full = vec![0.0; cls.n_full_x as usize];
1838 if z_u.dim() == 0 {
1839 return full;
1840 }
1841 let Some(dz) = z_u.as_any().downcast_ref::<DenseVector>() else {
1842 panic!("OrigIpoptNlp expects DenseVector for z_u");
1843 };
1844 let vals = dz.expanded_values();
1845 for (k, &var_idx) in cls.x_u_map.iter().enumerate() {
1846 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
1847 full[full_idx] = vals[k];
1848 }
1849 full
1850 }
1851
1852 fn finalize_solution_lambda(&self, y_c: &dyn Vector, y_d: &dyn Vector) -> Vec<Number> {
1853 OrigIpoptNlp::finalize_solution_lambda(self, y_c, y_d)
1854 }
1855
1856 fn finalize_solution_z_l(&self, z_l: &dyn Vector) -> Vec<Number> {
1857 OrigIpoptNlp::finalize_solution_z_l(self, z_l)
1858 }
1859
1860 fn finalize_solution_z_u(&self, z_u: &dyn Vector) -> Vec<Number> {
1861 OrigIpoptNlp::finalize_solution_z_u(self, z_u)
1862 }
1863
1864 fn full_x_to_var_x(&self, full_idx: Index) -> Option<Index> {
1865 let cls = self.adapter.borrow();
1866 let cls = cls.classification();
1867 let f = full_idx as usize;
1868 if f >= cls.full_to_var.len() {
1869 return None;
1870 }
1871 let v = cls.full_to_var[f];
1872 if v < 0 {
1873 None
1874 } else {
1875 Some(v)
1876 }
1877 }
1878
1879 fn full_g_to_c_block(&self, full_idx: Index) -> Option<Index> {
1880 let cls = self.adapter.borrow();
1881 let cls = cls.classification();
1882 cls.c_map
1883 .iter()
1884 .position(|&g_idx| g_idx == full_idx)
1885 .map(|p| p as Index)
1886 }
1887
1888 fn var_x_to_full_x(&self, var_idx: Index) -> Index {
1889 let cls = self.adapter.borrow();
1890 let cls = cls.classification();
1891 cls.x_not_fixed_map[var_idx as usize]
1892 }
1893}
1894
1895#[cfg(test)]
1898mod tests {
1899 use super::*;
1900 use crate::tnlp::{
1901 BoundsInfo, IndexStyle, IpoptCq, IpoptData, NlpInfo, Solution, SparsityRequest,
1902 StartingPoint, TNLP,
1903 };
1904
1905 #[derive(Default)]
1910 struct Hs071 {
1911 eval_f_calls: usize,
1912 eval_grad_f_calls: usize,
1913 eval_g_calls: usize,
1914 eval_jac_g_value_calls: usize,
1915 eval_h_value_calls: usize,
1916 }
1917
1918 impl TNLP for Hs071 {
1919 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
1920 Some(NlpInfo {
1921 n: 4,
1922 m: 2,
1923 nnz_jac_g: 8,
1924 nnz_h_lag: 10,
1925 index_style: IndexStyle::C,
1926 })
1927 }
1928 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
1929 b.x_l.copy_from_slice(&[1.0; 4]);
1930 b.x_u.copy_from_slice(&[5.0; 4]);
1931 b.g_l.copy_from_slice(&[25.0, 40.0]);
1934 b.g_u.copy_from_slice(&[2.0e19, 40.0]);
1935 true
1936 }
1937 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
1938 sp.x.copy_from_slice(&[1.0, 5.0, 5.0, 1.0]);
1939 true
1940 }
1941 fn eval_f(&mut self, x: &[Number], _new_x: bool) -> Option<Number> {
1942 self.eval_f_calls += 1;
1943 Some(x[0] * x[3] * (x[0] + x[1] + x[2]) + x[2])
1944 }
1945 fn eval_grad_f(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
1946 self.eval_grad_f_calls += 1;
1947 g[0] = x[3] * (2.0 * x[0] + x[1] + x[2]);
1952 g[1] = x[0] * x[3];
1953 g[2] = x[0] * x[3] + 1.0;
1954 g[3] = x[0] * (x[0] + x[1] + x[2]);
1955 true
1956 }
1957 fn eval_g(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
1958 self.eval_g_calls += 1;
1959 g[0] = x[0] * x[1] * x[2] * x[3];
1962 g[1] = x[0] * x[0] + x[1] * x[1] + x[2] * x[2] + x[3] * x[3];
1963 true
1964 }
1965 fn eval_jac_g(
1966 &mut self,
1967 x: Option<&[Number]>,
1968 _new_x: bool,
1969 mode: SparsityRequest<'_>,
1970 ) -> bool {
1971 match mode {
1972 SparsityRequest::Structure { irow, jcol } => {
1973 irow.copy_from_slice(&[0, 0, 0, 0, 1, 1, 1, 1]);
1975 jcol.copy_from_slice(&[0, 1, 2, 3, 0, 1, 2, 3]);
1976 }
1977 SparsityRequest::Values { values } => {
1978 self.eval_jac_g_value_calls += 1;
1979 let x = x.expect("eval_jac_g(Values) without x");
1980 values[0] = x[1] * x[2] * x[3];
1982 values[1] = x[0] * x[2] * x[3];
1983 values[2] = x[0] * x[1] * x[3];
1984 values[3] = x[0] * x[1] * x[2];
1985 values[4] = 2.0 * x[0];
1987 values[5] = 2.0 * x[1];
1988 values[6] = 2.0 * x[2];
1989 values[7] = 2.0 * x[3];
1990 }
1991 }
1992 true
1993 }
1994 fn eval_h(
1995 &mut self,
1996 x: Option<&[Number]>,
1997 _new_x: bool,
1998 obj_factor: Number,
1999 lambda: Option<&[Number]>,
2000 _new_lambda: bool,
2001 mode: SparsityRequest<'_>,
2002 ) -> bool {
2003 match mode {
2006 SparsityRequest::Structure { irow, jcol } => {
2007 irow.copy_from_slice(&[0, 1, 1, 2, 2, 2, 3, 3, 3, 3]);
2008 jcol.copy_from_slice(&[0, 0, 1, 0, 1, 2, 0, 1, 2, 3]);
2009 }
2010 SparsityRequest::Values { values } => {
2011 self.eval_h_value_calls += 1;
2012 let x = x.expect("eval_h(Values) without x");
2013 let lam = lambda.expect("eval_h(Values) without lambda");
2014 let of = obj_factor;
2015 let l0 = lam[0];
2025 let l1 = lam[1];
2026 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; }
2037 }
2038 true
2039 }
2040 fn finalize_solution(&mut self, _sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {}
2041 }
2042
2043 fn build_orig_nlp() -> (Rc<RefCell<TNLPAdapter>>, OrigIpoptNlp) {
2044 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071::default()));
2045 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2046 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2047 (adapter, nlp)
2048 }
2049
2050 fn dense_x(values: &[Number], space: &Rc<DenseVectorSpace>) -> DenseVector {
2051 let mut v = space.make_new_dense();
2052 v.values_mut().copy_from_slice(values);
2053 v
2054 }
2055
2056 #[test]
2057 fn dimensions_match_classification() {
2058 let (_, nlp) = build_orig_nlp();
2059 assert_eq!(nlp.n(), 4);
2061 assert_eq!(nlp.m_eq(), 1);
2062 assert_eq!(nlp.m_ineq(), 1);
2063 assert_eq!(nlp.jac_c_space().nonzeros(), 4);
2065 assert_eq!(nlp.jac_d_space().nonzeros(), 4);
2066 assert_eq!(nlp.h_space().unwrap().nonzeros(), 10);
2068 assert_eq!(nlp.x_l().dim(), 4);
2070 assert_eq!(nlp.x_u().dim(), 4);
2071 assert_eq!(nlp.d_l().dim(), 1);
2072 assert_eq!(nlp.d_u().dim(), 0);
2073 }
2074
2075 #[test]
2076 fn eval_f_at_starting_point() {
2077 let (_, mut nlp) = build_orig_nlp();
2078 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2079 assert_eq!(nlp.eval_f(&x), 16.0);
2081 assert_eq!(nlp.f_evals(), 1);
2082 }
2083
2084 #[test]
2085 fn eval_grad_f_at_starting_point() {
2086 let (_, mut nlp) = build_orig_nlp();
2087 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2088 let mut g = nlp.x_space().make_new_dense();
2089 nlp.eval_grad_f(&x, &mut g);
2090 assert_eq!(g.values(), &[12.0, 1.0, 2.0, 11.0]);
2095 assert_eq!(nlp.grad_f_evals(), 1);
2096 }
2097
2098 #[test]
2099 fn eval_c_returns_equality_residual() {
2100 let (_, mut nlp) = build_orig_nlp();
2101 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2102 let mut c = nlp.c_space().make_new_dense();
2103 nlp.eval_c(&x, &mut c);
2104 assert_eq!(c.values(), &[12.0]);
2106 assert_eq!(nlp.c_evals(), 1);
2107 }
2108
2109 #[test]
2110 fn eval_d_returns_inequality_value_unshifted() {
2111 let (_, mut nlp) = build_orig_nlp();
2112 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2113 let mut d = nlp.d_space().make_new_dense();
2114 nlp.eval_d(&x, &mut d);
2115 assert_eq!(d.values(), &[25.0]);
2117 assert_eq!(nlp.d_evals(), 1);
2118 }
2119
2120 #[test]
2121 fn cache_returns_without_re_eval() {
2122 let (_, mut nlp) = build_orig_nlp();
2123 let mut x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2124 let f1 = nlp.eval_f(&x);
2125 let f2 = nlp.eval_f(&x);
2126 assert_eq!(f1, f2);
2127 assert_eq!(nlp.f_evals(), 1, "second call must be served from cache");
2128 x.values_mut()[0] = 1.0; let _ = nlp.eval_f(&x);
2131 assert_eq!(nlp.f_evals(), 2);
2132 }
2133
2134 #[test]
2135 fn jac_c_picks_only_equality_rows() {
2136 let (_, mut nlp) = build_orig_nlp();
2137 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2138 let m = nlp.eval_jac_c(&x);
2139 let g = m
2140 .as_any()
2141 .downcast_ref::<GenTMatrix>()
2142 .expect("jac_c is a GenTMatrix");
2143 assert_eq!(g.values(), &[2.0, 10.0, 10.0, 2.0]);
2145 assert_eq!(g.irows(), &[1, 1, 1, 1]);
2147 assert_eq!(g.jcols(), &[1, 2, 3, 4]);
2148 }
2149
2150 #[test]
2151 fn jac_d_picks_only_inequality_rows() {
2152 let (_, mut nlp) = build_orig_nlp();
2153 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2154 let m = nlp.eval_jac_d(&x);
2155 let g = m
2156 .as_any()
2157 .downcast_ref::<GenTMatrix>()
2158 .expect("jac_d is a GenTMatrix");
2159 assert_eq!(g.values(), &[25.0, 5.0, 5.0, 25.0]);
2162 }
2163
2164 #[test]
2165 fn starting_point_is_compressed_into_x_var() {
2166 let (_, mut nlp) = build_orig_nlp();
2167 let mut x = nlp.x_space().make_new_dense();
2168 let mut yc = nlp.c_space().make_new_dense();
2169 let mut yd = nlp.d_space().make_new_dense();
2170 let mut zl = nlp.x_l_space().make_new_dense();
2171 let mut zu = nlp.x_u_space().make_new_dense();
2172 let ok = nlp.initialize_starting_point(
2173 &mut x, true, &mut yc, false, &mut yd, false, &mut zl, false, &mut zu, false,
2174 );
2175 assert!(ok);
2176 assert_eq!(x.values(), &[1.0, 5.0, 5.0, 1.0]);
2177 }
2178
2179 struct OneFixedOneFree;
2184 impl TNLP for OneFixedOneFree {
2185 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2186 Some(NlpInfo {
2187 n: 2,
2188 m: 1,
2189 nnz_jac_g: 1,
2190 nnz_h_lag: 0,
2191 index_style: IndexStyle::C,
2192 })
2193 }
2194 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2195 b.x_l[0] = 7.0;
2196 b.x_u[0] = 7.0; b.x_l[1] = -1.0e19;
2198 b.x_u[1] = 1.0e19;
2199 b.g_l[0] = 0.0;
2200 b.g_u[0] = 0.0; true
2202 }
2203 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2204 sp.x[0] = 7.0;
2205 sp.x[1] = 0.5;
2206 true
2207 }
2208 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
2209 Some(x[1])
2210 }
2211 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
2212 g[0] = 0.0;
2213 g[1] = 1.0;
2214 true
2215 }
2216 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
2217 g[0] = x[1];
2218 true
2219 }
2220 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
2221 match m {
2222 SparsityRequest::Structure { irow, jcol } => {
2223 irow[0] = 0;
2224 jcol[0] = 1;
2225 }
2226 SparsityRequest::Values { values } => values[0] = 1.0,
2227 }
2228 true
2229 }
2230 fn eval_h(
2231 &mut self,
2232 _: Option<&[Number]>,
2233 _: bool,
2234 _: Number,
2235 _: Option<&[Number]>,
2236 _: bool,
2237 _: SparsityRequest<'_>,
2238 ) -> bool {
2239 true
2240 }
2241 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
2242 }
2243
2244 #[test]
2245 fn ipopt_nlp_index_mapping_methods_handle_fixed_var() {
2246 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneFixedOneFree));
2247 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2248 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2249
2250 assert_eq!(nlp.n_full_x(), 2);
2252 assert_eq!(nlp.n(), 1);
2253
2254 let nlp_dyn: &dyn crate::ipopt_nlp::IpoptNlp = &nlp;
2256 assert_eq!(nlp_dyn.full_x_to_var_x(0), None);
2257 assert_eq!(nlp_dyn.full_x_to_var_x(1), Some(0));
2258
2259 assert_eq!(nlp_dyn.var_x_to_full_x(0), 1);
2261
2262 assert_eq!(nlp_dyn.full_g_to_c_block(0), Some(0));
2264
2265 let mut x_var = nlp.x_space().make_new_dense();
2267 x_var.values_mut()[0] = 0.5;
2268 let lifted = nlp_dyn.lift_x_to_full(&x_var);
2269 assert_eq!(lifted, vec![7.0, 0.5]);
2270 }
2271
2272 struct FixedOnlyHess;
2278 impl TNLP for FixedOnlyHess {
2279 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2280 Some(NlpInfo {
2281 n: 2,
2282 m: 1,
2283 nnz_jac_g: 1,
2284 nnz_h_lag: 1,
2285 index_style: IndexStyle::C,
2286 })
2287 }
2288 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2289 b.x_l[0] = 7.0;
2290 b.x_u[0] = 7.0; b.x_l[1] = -1.0e19;
2292 b.x_u[1] = 1.0e19;
2293 b.g_l[0] = 0.0;
2294 b.g_u[0] = 0.0;
2295 true
2296 }
2297 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2298 sp.x[0] = 7.0;
2299 sp.x[1] = 0.5;
2300 true
2301 }
2302 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
2303 Some(0.5 * x[0] * x[0] + x[1])
2304 }
2305 fn eval_grad_f(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
2306 g[0] = x[0];
2307 g[1] = 1.0;
2308 true
2309 }
2310 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
2311 g[0] = x[1];
2312 true
2313 }
2314 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
2315 match m {
2316 SparsityRequest::Structure { irow, jcol } => {
2317 irow[0] = 0;
2318 jcol[0] = 1;
2319 }
2320 SparsityRequest::Values { values } => values[0] = 1.0,
2321 }
2322 true
2323 }
2324 fn eval_h(
2325 &mut self,
2326 _: Option<&[Number]>,
2327 _: bool,
2328 obj_factor: Number,
2329 _: Option<&[Number]>,
2330 _: bool,
2331 m: SparsityRequest<'_>,
2332 ) -> bool {
2333 match m {
2334 SparsityRequest::Structure { irow, jcol } => {
2335 irow[0] = 0;
2336 jcol[0] = 0;
2337 }
2338 SparsityRequest::Values { values } => values[0] = obj_factor,
2339 }
2340 true
2341 }
2342 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
2343 }
2344
2345 struct OneIneqLargeOffset;
2354 impl TNLP for OneIneqLargeOffset {
2355 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2356 Some(NlpInfo {
2357 n: 1,
2358 m: 1,
2359 nnz_jac_g: 1,
2360 nnz_h_lag: 0,
2361 index_style: IndexStyle::C,
2362 })
2363 }
2364 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2365 b.x_l[0] = -1.0e19;
2366 b.x_u[0] = 1.0e19;
2367 b.g_l[0] = 4.0e6;
2368 b.g_u[0] = 2.0e19;
2369 true
2370 }
2371 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2372 sp.x[0] = 5000.0;
2373 true
2374 }
2375 fn eval_f(&mut self, _: &[Number], _: bool) -> Option<Number> {
2376 Some(0.0)
2377 }
2378 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
2379 g[0] = 0.0;
2380 true
2381 }
2382 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
2383 g[0] = 1000.0 * x[0];
2384 true
2385 }
2386 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
2387 match m {
2388 SparsityRequest::Structure { irow, jcol } => {
2389 irow[0] = 0;
2390 jcol[0] = 0;
2391 }
2392 SparsityRequest::Values { values } => values[0] = 1000.0,
2393 }
2394 true
2395 }
2396 fn eval_h(
2397 &mut self,
2398 _: Option<&[Number]>,
2399 _: bool,
2400 _: Number,
2401 _: Option<&[Number]>,
2402 _: bool,
2403 _: SparsityRequest<'_>,
2404 ) -> bool {
2405 true
2406 }
2407 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
2408 }
2409
2410 #[test]
2411 fn gradient_based_scaling_scales_d_l_and_d_u() {
2412 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqLargeOffset));
2413 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2414 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2415
2416 assert_eq!(nlp.d_l().dim(), 1);
2418 let pre = nlp
2419 .d_l()
2420 .as_any()
2421 .downcast_ref::<DenseVector>()
2422 .unwrap()
2423 .values()[0];
2424 assert_eq!(pre, 4.0e6);
2425
2426 nlp.determine_scaling_from_starting_point(
2427 ScalingMethod::GradientBased,
2428 100.0,
2429 1e-8,
2430 0.0,
2431 0.0,
2432 );
2433
2434 let post = nlp
2436 .d_l()
2437 .as_any()
2438 .downcast_ref::<DenseVector>()
2439 .unwrap()
2440 .values()[0];
2441 assert!(
2442 (post - 4.0e5).abs() < 1e-9,
2443 "d_l should be scaled by d_scale=0.1; got {}",
2444 post
2445 );
2446
2447 let x = dense_x(&[5000.0], nlp.x_space());
2450 let mut d = nlp.d_space().make_new_dense();
2451 nlp.eval_d(&x, &mut d);
2452 assert!(
2453 (d.values()[0] - 5.0e5).abs() < 1e-6,
2454 "scaled d(x) mismatch; got {}",
2455 d.values()[0]
2456 );
2457 assert!(
2458 d.values()[0] >= post,
2459 "starting point must be feasible in scaled space"
2460 );
2461 }
2462
2463 struct OneIneqWithObj;
2469 impl TNLP for OneIneqWithObj {
2470 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2471 Some(NlpInfo {
2472 n: 1,
2473 m: 1,
2474 nnz_jac_g: 1,
2475 nnz_h_lag: 0,
2476 index_style: IndexStyle::C,
2477 })
2478 }
2479 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2480 b.x_l[0] = -1.0e19;
2481 b.x_u[0] = 1.0e19;
2482 b.g_l[0] = 4.0e6;
2483 b.g_u[0] = 2.0e19;
2484 true
2485 }
2486 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2487 sp.x[0] = 5000.0;
2488 true
2489 }
2490 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
2491 Some(10.0 * x[0])
2492 }
2493 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
2494 g[0] = 10.0;
2495 true
2496 }
2497 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
2498 g[0] = 1000.0 * x[0];
2499 true
2500 }
2501 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
2502 match m {
2503 SparsityRequest::Structure { irow, jcol } => {
2504 irow[0] = 0;
2505 jcol[0] = 0;
2506 }
2507 SparsityRequest::Values { values } => values[0] = 1000.0,
2508 }
2509 true
2510 }
2511 fn eval_h(
2512 &mut self,
2513 _: Option<&[Number]>,
2514 _: bool,
2515 _: Number,
2516 _: Option<&[Number]>,
2517 _: bool,
2518 _: SparsityRequest<'_>,
2519 ) -> bool {
2520 true
2521 }
2522 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
2523 }
2524
2525 #[test]
2526 fn obj_target_gradient_pins_obj_scale() {
2527 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqWithObj));
2530 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2531 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2532 nlp.determine_scaling_from_starting_point(
2533 ScalingMethod::GradientBased,
2534 100.0,
2535 1e-8,
2536 0.0, 0.0,
2538 );
2539 assert!(
2540 (nlp.obj_scale_factor() - 1.0).abs() < 1e-12,
2541 "no-target path leaves df=1 when grad < cutoff; got {}",
2542 nlp.obj_scale_factor()
2543 );
2544
2545 let tnlp2: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqWithObj));
2548 let adapter2 = Rc::new(RefCell::new(TNLPAdapter::new(tnlp2).unwrap()));
2549 let mut nlp2 = OrigIpoptNlp::new(Rc::clone(&adapter2), Rc::new(NoScaling)).unwrap();
2550 nlp2.determine_scaling_from_starting_point(
2551 ScalingMethod::GradientBased,
2552 100.0,
2553 1e-8,
2554 1.0,
2555 0.0,
2556 );
2557 assert!(
2558 (nlp2.obj_scale_factor() - 0.1).abs() < 1e-12,
2559 "target_gradient=1, max_grad_f=10 → df=0.1; got {}",
2560 nlp2.obj_scale_factor()
2561 );
2562 }
2563
2564 #[test]
2565 fn constr_target_gradient_overrides_cutoff_and_clamp() {
2566 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneIneqLargeOffset));
2571 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2572 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2573 nlp.determine_scaling_from_starting_point(
2574 ScalingMethod::GradientBased,
2575 100.0,
2576 1e-8,
2577 0.0,
2578 50.0,
2579 );
2580 let x = dense_x(&[5000.0], nlp.x_space());
2581 let mut d = nlp.d_space().make_new_dense();
2582 nlp.eval_d(&x, &mut d);
2583 assert!(
2585 (d.values()[0] - 2.5e5).abs() < 1e-6,
2586 "constr target=50 → dd=0.05; scaled d(5000)=2.5e5, got {}",
2587 d.values()[0]
2588 );
2589 }
2590
2591 struct Hs071UserScaled;
2596 impl TNLP for Hs071UserScaled {
2597 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2598 Hs071::default().get_nlp_info()
2599 }
2600 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2601 Hs071::default().get_bounds_info(b)
2602 }
2603 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2604 Hs071::default().get_starting_point(sp)
2605 }
2606 fn eval_f(&mut self, x: &[Number], new_x: bool) -> Option<Number> {
2607 Hs071::default().eval_f(x, new_x)
2608 }
2609 fn eval_grad_f(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
2610 Hs071::default().eval_grad_f(x, new_x, g)
2611 }
2612 fn eval_g(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
2613 Hs071::default().eval_g(x, new_x, g)
2614 }
2615 fn eval_jac_g(
2616 &mut self,
2617 x: Option<&[Number]>,
2618 new_x: bool,
2619 mode: SparsityRequest<'_>,
2620 ) -> bool {
2621 Hs071::default().eval_jac_g(x, new_x, mode)
2622 }
2623 fn eval_h(
2624 &mut self,
2625 x: Option<&[Number]>,
2626 new_x: bool,
2627 obj_factor: Number,
2628 lambda: Option<&[Number]>,
2629 new_lambda: bool,
2630 mode: SparsityRequest<'_>,
2631 ) -> bool {
2632 Hs071::default().eval_h(x, new_x, obj_factor, lambda, new_lambda, mode)
2633 }
2634 fn get_scaling_parameters(&mut self, req: ScalingRequest<'_>) -> bool {
2635 *req.obj_scaling = 2.0;
2636 *req.use_x_scaling = false;
2637 *req.use_g_scaling = true;
2638 req.g_scaling[0] = 0.5;
2640 req.g_scaling[1] = 0.25;
2641 true
2642 }
2643 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
2644 }
2645
2646 #[test]
2647 fn user_scaling_dispatch_applies_obj_and_g_scaling() {
2648 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071UserScaled));
2649 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2650 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2651 nlp.determine_scaling_from_starting_point(
2652 ScalingMethod::UserScaling,
2653 100.0,
2654 1e-8,
2655 0.0,
2656 0.0,
2657 );
2658
2659 assert!(
2662 (nlp.obj_scale_factor() - 2.0).abs() < 1e-12,
2663 "user obj_scaling=2.0 should be installed; got {}",
2664 nlp.obj_scale_factor()
2665 );
2666
2667 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2671 let mut c = nlp.c_space().make_new_dense();
2672 nlp.eval_c(&x, &mut c);
2673 assert!(
2676 (c.values()[0] - 3.0).abs() < 1e-9,
2677 "user g_scaling=0.25 on equality → c=3.0; got {}",
2678 c.values()[0]
2679 );
2680
2681 let mut d = nlp.d_space().make_new_dense();
2684 nlp.eval_d(&x, &mut d);
2685 assert!(
2686 (d.values()[0] - 12.5).abs() < 1e-9,
2687 "user g_scaling=0.5 on inequality → d=12.5; got {}",
2688 d.values()[0]
2689 );
2690
2691 let post_d_l = nlp
2694 .d_l()
2695 .as_any()
2696 .downcast_ref::<DenseVector>()
2697 .unwrap()
2698 .values()[0];
2699 assert!(
2700 (post_d_l - 12.5).abs() < 1e-9,
2701 "d_l scaled in step: got {}",
2702 post_d_l
2703 );
2704 }
2705
2706 struct Hs071DeclinesScaling;
2710 impl TNLP for Hs071DeclinesScaling {
2711 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2712 Hs071::default().get_nlp_info()
2713 }
2714 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2715 Hs071::default().get_bounds_info(b)
2716 }
2717 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2718 Hs071::default().get_starting_point(sp)
2719 }
2720 fn eval_f(&mut self, x: &[Number], new_x: bool) -> Option<Number> {
2721 Hs071::default().eval_f(x, new_x)
2722 }
2723 fn eval_grad_f(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
2724 Hs071::default().eval_grad_f(x, new_x, g)
2725 }
2726 fn eval_g(&mut self, x: &[Number], new_x: bool, g: &mut [Number]) -> bool {
2727 Hs071::default().eval_g(x, new_x, g)
2728 }
2729 fn eval_jac_g(
2730 &mut self,
2731 x: Option<&[Number]>,
2732 new_x: bool,
2733 mode: SparsityRequest<'_>,
2734 ) -> bool {
2735 Hs071::default().eval_jac_g(x, new_x, mode)
2736 }
2737 fn eval_h(
2738 &mut self,
2739 x: Option<&[Number]>,
2740 new_x: bool,
2741 obj_factor: Number,
2742 lambda: Option<&[Number]>,
2743 new_lambda: bool,
2744 mode: SparsityRequest<'_>,
2745 ) -> bool {
2746 Hs071::default().eval_h(x, new_x, obj_factor, lambda, new_lambda, mode)
2747 }
2748 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
2749 }
2750
2751 #[test]
2752 fn user_scaling_falls_back_when_tnlp_declines() {
2753 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071DeclinesScaling));
2754 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2755 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2756 nlp.determine_scaling_from_starting_point(
2757 ScalingMethod::UserScaling,
2758 100.0,
2759 1e-8,
2760 0.0,
2761 0.0,
2762 );
2763 assert!((nlp.obj_scale_factor() - 1.0).abs() < 1e-12);
2766 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
2767 let mut c = nlp.c_space().make_new_dense();
2768 nlp.eval_c(&x, &mut c);
2769 assert_eq!(c.values(), &[12.0], "unscaled equality residual");
2770 }
2771
2772 #[test]
2773 fn eval_h_with_all_entries_on_fixed_var_does_not_panic() {
2774 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(FixedOnlyHess));
2775 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2776 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2777
2778 assert_eq!(nlp.h_space().unwrap().nonzeros(), 0);
2781
2782 let x = dense_x(&[0.5], &nlp.x_space().clone());
2783 let yc = dense_x(&[0.0], &nlp.c_space().clone());
2784 let yd = nlp.d_space().make_new_dense();
2785 let h = nlp.eval_h(&x, 1.0, &yc, &yd);
2786 assert_eq!(h.n_rows(), 1);
2787 }
2788}