1use crate::ipopt_nlp::{IpoptNlp, Nlp};
61use crate::tnlp::{NlpInfo, 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}
109
110pub struct OrigIpoptNlp {
113 adapter: Rc<RefCell<TNLPAdapter>>,
115 scaling: Rc<dyn NlpScaling>,
120
121 obj_scale_factor: Cell<Number>,
124 c_scale: RefCell<Option<Vec<Number>>>,
128 d_scale: RefCell<Option<Vec<Number>>>,
130
131 x_space: Rc<DenseVectorSpace>,
133 c_space: Rc<DenseVectorSpace>,
134 d_space: Rc<DenseVectorSpace>,
135 x_l_space: Rc<DenseVectorSpace>,
136 x_u_space: Rc<DenseVectorSpace>,
137 d_l_space: Rc<DenseVectorSpace>,
138 d_u_space: Rc<DenseVectorSpace>,
139 px_l_space: Rc<ExpansionMatrixSpace>,
140 px_u_space: Rc<ExpansionMatrixSpace>,
141 pd_l_space: Rc<ExpansionMatrixSpace>,
142 pd_u_space: Rc<ExpansionMatrixSpace>,
143 jac_c_space: Rc<GenTMatrixSpace>,
144 jac_d_space: Rc<GenTMatrixSpace>,
145 h_space: Option<Rc<SymTMatrixSpace>>,
148
149 x_l: Rc<DenseVector>,
151 x_u: Rc<DenseVector>,
152 d_l: Rc<DenseVector>,
153 d_u: Rc<DenseVector>,
154
155 px_l: Rc<dyn Matrix>,
157 px_u: Rc<dyn Matrix>,
158 pd_l: Rc<dyn Matrix>,
159 pd_u: Rc<dyn Matrix>,
160
161 jac_c_entry_in_g: Vec<Index>,
165 jac_d_entry_in_g: Vec<Index>,
167 nnz_jac_g_full: Index,
169
170 nnz_h_lag_full: Index,
174 h_entry_in_full: Vec<Index>,
179
180 f_cache: RefCell<Cache<Number>>,
182 grad_f_cache: RefCell<Cache<Rc<dyn Vector>>>,
183 c_cache: RefCell<Cache<Rc<dyn Vector>>>,
184 d_cache: RefCell<Cache<Rc<dyn Vector>>>,
185 jac_c_cache: RefCell<Cache<Rc<dyn Matrix>>>,
186 jac_d_cache: RefCell<Cache<Rc<dyn Matrix>>>,
187 h_cache: RefCell<Cache<Rc<dyn SymMatrix>>>,
188
189 f_evals: RefCell<Index>,
191 grad_f_evals: RefCell<Index>,
192 c_evals: RefCell<Index>,
193 d_evals: RefCell<Index>,
194 jac_c_evals: RefCell<Index>,
195 jac_d_evals: RefCell<Index>,
196 h_evals: RefCell<Index>,
197
198 info: NlpInfo,
201
202 timing: RefCell<Option<Rc<TimingStatistics>>>,
207}
208
209impl std::fmt::Debug for OrigIpoptNlp {
210 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 f.debug_struct("OrigIpoptNlp")
212 .field("info", &self.info)
213 .field("f_evals", &*self.f_evals.borrow())
214 .field("grad_f_evals", &*self.grad_f_evals.borrow())
215 .field("c_evals", &*self.c_evals.borrow())
216 .field("d_evals", &*self.d_evals.borrow())
217 .field("jac_c_evals", &*self.jac_c_evals.borrow())
218 .field("jac_d_evals", &*self.jac_d_evals.borrow())
219 .field("h_evals", &*self.h_evals.borrow())
220 .finish_non_exhaustive()
221 }
222}
223
224impl OrigIpoptNlp {
225 pub fn new(
231 adapter: Rc<RefCell<TNLPAdapter>>,
232 scaling: Rc<dyn NlpScaling>,
233 ) -> Result<Self, String> {
234 let (info, classification) = {
236 let a = adapter.borrow();
237 (*a.nlp_info(), a.classification().clone())
238 };
239
240 let n_x_var = classification.n_x_var();
242 let x_space = DenseVectorSpace::new(n_x_var);
243 let c_space = DenseVectorSpace::new(classification.n_c);
244 let d_space = DenseVectorSpace::new(classification.n_d);
245 let x_l_space = DenseVectorSpace::new(classification.n_x_l());
246 let x_u_space = DenseVectorSpace::new(classification.n_x_u());
247 let d_l_space = DenseVectorSpace::new(classification.n_d_l());
248 let d_u_space = DenseVectorSpace::new(classification.n_d_u());
249
250 let px_l_space =
252 ExpansionMatrixSpace::new(n_x_var, classification.n_x_l(), &classification.x_l_map, 0);
253 let px_u_space =
254 ExpansionMatrixSpace::new(n_x_var, classification.n_x_u(), &classification.x_u_map, 0);
255 let pd_l_space = ExpansionMatrixSpace::new(
256 classification.n_d,
257 classification.n_d_l(),
258 &classification.d_l_map,
259 0,
260 );
261 let pd_u_space = ExpansionMatrixSpace::new(
262 classification.n_d,
263 classification.n_d_u(),
264 &classification.d_u_map,
265 0,
266 );
267 let px_l: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&px_l_space)));
268 let px_u: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&px_u_space)));
269 let pd_l: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&pd_l_space)));
270 let pd_u: Rc<dyn Matrix> = Rc::new(ExpansionMatrix::new(Rc::clone(&pd_u_space)));
271
272 let n_full_x = classification.n_full_x as usize;
276 let n_full_g = classification.n_full_g as usize;
277 let mut full_x_l = vec![0.0; n_full_x];
278 let mut full_x_u = vec![0.0; n_full_x];
279 let mut full_g_l = vec![0.0; n_full_g];
280 let mut full_g_u = vec![0.0; n_full_g];
281 {
282 let a = adapter.borrow();
283 let mut t = a.tnlp().borrow_mut();
284 let ok = t.get_bounds_info(crate::tnlp::BoundsInfo {
285 x_l: &mut full_x_l,
286 x_u: &mut full_x_u,
287 g_l: &mut full_g_l,
288 g_u: &mut full_g_u,
289 });
290 if !ok {
291 return Err("TNLP::get_bounds_info returned false on second call".into());
292 }
293 }
294
295 let x_l = make_dense_from(&x_l_space, |i| {
296 let var_idx = classification.x_l_map[i] as usize;
298 let full_idx = classification.x_not_fixed_map[var_idx] as usize;
299 full_x_l[full_idx]
300 });
301 let x_u = make_dense_from(&x_u_space, |i| {
302 let var_idx = classification.x_u_map[i] as usize;
303 let full_idx = classification.x_not_fixed_map[var_idx] as usize;
304 full_x_u[full_idx]
305 });
306 let d_l = make_dense_from(&d_l_space, |i| {
307 let d_idx = classification.d_l_map[i] as usize;
309 let full_g_idx = classification.d_map[d_idx] as usize;
310 full_g_l[full_g_idx]
311 });
312 let d_u = make_dense_from(&d_u_space, |i| {
313 let d_idx = classification.d_u_map[i] as usize;
314 let full_g_idx = classification.d_map[d_idx] as usize;
315 full_g_u[full_g_idx]
316 });
317
318 let mut full_irow = vec![0 as Index; info.nnz_jac_g as usize];
328 let mut full_jcol = vec![0 as Index; info.nnz_jac_g as usize];
329 {
330 let a = adapter.borrow();
331 let mut t = a.tnlp().borrow_mut();
332 let ok = t.eval_jac_g(
333 None,
334 false,
335 SparsityRequest::Structure {
336 irow: &mut full_irow,
337 jcol: &mut full_jcol,
338 },
339 );
340 if !ok {
341 return Err("TNLP::eval_jac_g(Structure) returned false".into());
342 }
343 }
344
345 let mut g_to_c = vec![-1 as Index; n_full_g];
347 for (c_idx, &g_idx) in classification.c_map.iter().enumerate() {
348 g_to_c[g_idx as usize] = c_idx as Index;
349 }
350 let mut g_to_d = vec![-1 as Index; n_full_g];
351 for (d_idx, &g_idx) in classification.d_map.iter().enumerate() {
352 g_to_d[g_idx as usize] = d_idx as Index;
353 }
354
355 let style_offset = match info.index_style {
356 crate::tnlp::IndexStyle::C => 0 as Index,
357 crate::tnlp::IndexStyle::Fortran => 1 as Index,
358 };
359
360 let mut jac_c_irow_1based = Vec::new();
361 let mut jac_c_jcol_1based = Vec::new();
362 let mut jac_c_entry_in_g = Vec::new();
363 let mut jac_d_irow_1based = Vec::new();
364 let mut jac_d_jcol_1based = Vec::new();
365 let mut jac_d_entry_in_g = Vec::new();
366
367 let full_to_var = &classification.full_to_var;
371 for k in 0..info.nnz_jac_g as usize {
372 let g_row_0 = (full_irow[k] - style_offset) as usize;
373 let x_col_0 = (full_jcol[k] - style_offset) as usize;
374 let var_col = full_to_var[x_col_0];
375 if var_col < 0 {
376 continue;
377 }
378 let col_1based = var_col + 1;
380 let c_row = g_to_c[g_row_0];
381 if c_row >= 0 {
382 jac_c_irow_1based.push(c_row + 1);
383 jac_c_jcol_1based.push(col_1based);
384 jac_c_entry_in_g.push(k as Index);
385 } else {
386 let d_row = g_to_d[g_row_0];
387 debug_assert!(d_row >= 0, "g row {g_row_0} is neither in c_map nor d_map");
388 jac_d_irow_1based.push(d_row + 1);
389 jac_d_jcol_1based.push(col_1based);
390 jac_d_entry_in_g.push(k as Index);
391 }
392 }
393
394 let jac_c_space = GenTMatrixSpace::new(
395 classification.n_c,
396 n_x_var,
397 jac_c_irow_1based,
398 jac_c_jcol_1based,
399 );
400 let jac_d_space = GenTMatrixSpace::new(
401 classification.n_d,
402 n_x_var,
403 jac_d_irow_1based,
404 jac_d_jcol_1based,
405 );
406
407 let nnz_h_lag_full = info.nnz_h_lag;
412 let mut h_entry_in_full: Vec<Index> = Vec::new();
413 let h_space = if info.nnz_h_lag > 0 {
414 let mut h_irow = vec![0 as Index; info.nnz_h_lag as usize];
415 let mut h_jcol = vec![0 as Index; info.nnz_h_lag as usize];
416 let supports_h = {
417 let a = adapter.borrow();
418 let mut t = a.tnlp().borrow_mut();
419 t.eval_h(
420 None,
421 false,
422 1.0,
423 None,
424 false,
425 SparsityRequest::Structure {
426 irow: &mut h_irow,
427 jcol: &mut h_jcol,
428 },
429 )
430 };
431 if supports_h {
432 let mut h_irow_1: Vec<Index> = Vec::with_capacity(h_irow.len());
438 let mut h_jcol_1: Vec<Index> = Vec::with_capacity(h_jcol.len());
439 for k in 0..h_irow.len() {
440 let i_full = (h_irow[k] - style_offset) as usize;
441 let j_full = (h_jcol[k] - style_offset) as usize;
442 let i_var = full_to_var[i_full];
443 let j_var = full_to_var[j_full];
444 if i_var < 0 || j_var < 0 {
445 continue;
446 }
447 h_irow_1.push(i_var + 1);
448 h_jcol_1.push(j_var + 1);
449 h_entry_in_full.push(k as Index);
450 }
451 Some(SymTMatrixSpace::new(n_x_var, h_irow_1, h_jcol_1))
452 } else {
453 None
455 }
456 } else {
457 Some(SymTMatrixSpace::new(n_x_var, Vec::new(), Vec::new()))
461 };
462
463 Ok(Self {
464 adapter,
465 scaling,
466 obj_scale_factor: Cell::new(1.0),
467 c_scale: RefCell::new(None),
468 d_scale: RefCell::new(None),
469 x_space,
470 c_space,
471 d_space,
472 x_l_space,
473 x_u_space,
474 d_l_space,
475 d_u_space,
476 px_l_space,
477 px_u_space,
478 pd_l_space,
479 pd_u_space,
480 jac_c_space,
481 jac_d_space,
482 h_space,
483 x_l: Rc::new(x_l),
484 x_u: Rc::new(x_u),
485 d_l: Rc::new(d_l),
486 d_u: Rc::new(d_u),
487 px_l,
488 px_u,
489 pd_l,
490 pd_u,
491 jac_c_entry_in_g,
492 jac_d_entry_in_g,
493 nnz_jac_g_full: info.nnz_jac_g,
494 nnz_h_lag_full,
495 h_entry_in_full,
496 f_cache: RefCell::new(Cache::new(1)),
497 grad_f_cache: RefCell::new(Cache::new(1)),
498 c_cache: RefCell::new(Cache::new(1)),
499 d_cache: RefCell::new(Cache::new(1)),
500 jac_c_cache: RefCell::new(Cache::new(1)),
501 jac_d_cache: RefCell::new(Cache::new(1)),
502 h_cache: RefCell::new(Cache::new(1)),
503 f_evals: RefCell::new(0),
504 grad_f_evals: RefCell::new(0),
505 c_evals: RefCell::new(0),
506 d_evals: RefCell::new(0),
507 jac_c_evals: RefCell::new(0),
508 jac_d_evals: RefCell::new(0),
509 h_evals: RefCell::new(0),
510 info,
511 timing: RefCell::new(None),
512 })
513 }
514
515 pub fn set_timing_stats(&self, t: Rc<TimingStatistics>) {
521 *self.timing.borrow_mut() = Some(t);
522 }
523
524 fn timed_eval<R, F>(&self, pick: fn(&TimingStatistics) -> &pounce_common::TimedTask, f: F) -> R
529 where
530 F: FnOnce() -> R,
531 {
532 let guard = self.timing.borrow();
533 match guard.as_deref() {
534 Some(t) => {
535 let task = pick(t);
536 task.start();
537 t.total_function_evaluation_time.start();
538 let r = f();
539 t.total_function_evaluation_time.end();
540 task.end();
541 r
542 }
543 None => {
544 drop(guard);
545 f()
546 }
547 }
548 }
549
550 pub fn nlp_info(&self) -> &NlpInfo {
553 &self.info
554 }
555 pub fn classification_n_x_var(&self) -> Index {
556 self.x_space.dim()
557 }
558 pub fn x_space(&self) -> &Rc<DenseVectorSpace> {
559 &self.x_space
560 }
561 pub fn c_space(&self) -> &Rc<DenseVectorSpace> {
562 &self.c_space
563 }
564 pub fn d_space(&self) -> &Rc<DenseVectorSpace> {
565 &self.d_space
566 }
567 pub fn x_l_space(&self) -> &Rc<DenseVectorSpace> {
568 &self.x_l_space
569 }
570 pub fn x_u_space(&self) -> &Rc<DenseVectorSpace> {
571 &self.x_u_space
572 }
573 pub fn d_l_space(&self) -> &Rc<DenseVectorSpace> {
574 &self.d_l_space
575 }
576 pub fn d_u_space(&self) -> &Rc<DenseVectorSpace> {
577 &self.d_u_space
578 }
579 pub fn px_l_space(&self) -> &Rc<ExpansionMatrixSpace> {
580 &self.px_l_space
581 }
582 pub fn px_u_space(&self) -> &Rc<ExpansionMatrixSpace> {
583 &self.px_u_space
584 }
585 pub fn pd_l_space(&self) -> &Rc<ExpansionMatrixSpace> {
586 &self.pd_l_space
587 }
588 pub fn pd_u_space(&self) -> &Rc<ExpansionMatrixSpace> {
589 &self.pd_u_space
590 }
591 pub fn jac_c_space(&self) -> &Rc<GenTMatrixSpace> {
592 &self.jac_c_space
593 }
594 pub fn jac_d_space(&self) -> &Rc<GenTMatrixSpace> {
595 &self.jac_d_space
596 }
597 pub fn h_space(&self) -> Option<&Rc<SymTMatrixSpace>> {
598 self.h_space.as_ref()
599 }
600
601 pub fn obj_scale_factor(&self) -> Number {
604 self.obj_scale_factor.get()
605 }
606
607 pub fn relax_bounds(&mut self, bound_relax_factor: Number, constr_viol_tol: Number) {
619 if bound_relax_factor <= 0.0 {
620 return;
621 }
622 let relax = bound_relax_factor.abs();
623 let cap = constr_viol_tol;
624 let apply = |v: &mut DenseVector, sign: Number| {
625 let xs = v.values_mut();
626 for x in xs.iter_mut() {
627 let delta = (relax * x.abs().max(1.0)).min(cap);
628 *x += sign * delta;
629 }
630 };
631 if let Some(x_l) = Rc::get_mut(&mut self.x_l) {
632 apply(x_l, -1.0);
633 }
634 if let Some(x_u) = Rc::get_mut(&mut self.x_u) {
635 apply(x_u, 1.0);
636 }
637 if let Some(d_l) = Rc::get_mut(&mut self.d_l) {
638 apply(d_l, -1.0);
639 }
640 if let Some(d_u) = Rc::get_mut(&mut self.d_u) {
641 apply(d_u, 1.0);
642 }
643 }
644
645 pub fn determine_scaling_from_starting_point(
656 &self,
657 method: ScalingMethod,
658 max_gradient: Number,
659 min_value: Number,
660 ) {
661 let user_obj_factor = self.scaling.obj_scaling();
664 if matches!(method, ScalingMethod::None) {
665 self.obj_scale_factor.set(user_obj_factor);
666 *self.c_scale.borrow_mut() = None;
667 *self.d_scale.borrow_mut() = None;
668 self.invalidate_eval_caches();
669 return;
670 }
671
672 let cls = self.adapter.borrow().classification().clone();
674 let n_full_x = cls.n_full_x as usize;
675 let n_full_g = cls.n_full_g as usize;
676 let mut full_x = vec![0.0; n_full_x];
677 let mut full_z_l = vec![0.0; n_full_x];
678 let mut full_z_u = vec![0.0; n_full_x];
679 let mut full_lambda = vec![0.0; n_full_g];
680 let starting_ok = {
681 let a = self.adapter.borrow();
682 let mut t = a.tnlp().borrow_mut();
683 t.get_starting_point(StartingPoint {
684 init_x: true,
685 x: &mut full_x,
686 init_z: false,
687 z_l: &mut full_z_l,
688 z_u: &mut full_z_u,
689 init_lambda: false,
690 lambda: &mut full_lambda,
691 })
692 };
693 if !starting_ok {
694 self.obj_scale_factor.set(user_obj_factor);
696 *self.c_scale.borrow_mut() = None;
697 *self.d_scale.borrow_mut() = None;
698 self.invalidate_eval_caches();
699 return;
700 }
701
702 let mut full_grad_f = vec![0.0; n_full_x];
704 let grad_ok = {
705 let a = self.adapter.borrow();
706 let mut t = a.tnlp().borrow_mut();
707 t.eval_grad_f(&full_x, true, &mut full_grad_f)
708 };
709 let mut df = 1.0;
710 if grad_ok {
711 let mut max_grad_f: Number = 0.0;
714 for &full_idx in cls.x_not_fixed_map.iter() {
715 let v = full_grad_f[full_idx as usize].abs();
716 if v > max_grad_f {
717 max_grad_f = v;
718 }
719 }
720 if max_grad_f > max_gradient {
721 df = max_gradient / max_grad_f;
722 }
723 if df < min_value {
724 df = min_value;
725 }
726 }
727 self.obj_scale_factor.set(df * user_obj_factor);
728
729 if cls.n_full_g > 0 {
731 let mut full_jac_vals = vec![0.0; self.nnz_jac_g_full as usize];
733 let jac_ok = {
734 let a = self.adapter.borrow();
735 let mut t = a.tnlp().borrow_mut();
736 t.eval_jac_g(
737 Some(&full_x),
738 true,
739 SparsityRequest::Values {
740 values: &mut full_jac_vals,
741 },
742 )
743 };
744 if jac_ok {
745 let mut full_irow = vec![0 as Index; self.nnz_jac_g_full as usize];
747 let mut full_jcol = vec![0 as Index; self.nnz_jac_g_full as usize];
748 let _ = {
749 let a = self.adapter.borrow();
750 let mut t = a.tnlp().borrow_mut();
751 t.eval_jac_g(
752 None,
753 false,
754 SparsityRequest::Structure {
755 irow: &mut full_irow,
756 jcol: &mut full_jcol,
757 },
758 )
759 };
760 let style_offset: Index = match self.info.index_style {
761 crate::tnlp::IndexStyle::C => 0,
762 crate::tnlp::IndexStyle::Fortran => 1,
763 };
764 let mut g_to_c = vec![-1 as Index; n_full_g];
766 for (c_idx, &g_idx) in cls.c_map.iter().enumerate() {
767 g_to_c[g_idx as usize] = c_idx as Index;
768 }
769 let mut g_to_d = vec![-1 as Index; n_full_g];
770 for (d_idx, &g_idx) in cls.d_map.iter().enumerate() {
771 g_to_d[g_idx as usize] = d_idx as Index;
772 }
773 let n_c = cls.n_c as usize;
774 let n_d = cls.n_d as usize;
775 let dbl_min = Number::MIN_POSITIVE;
777 let mut c_row_max: Vec<Number> = vec![dbl_min; n_c];
778 let mut d_row_max: Vec<Number> = vec![dbl_min; n_d];
779 for k in 0..self.nnz_jac_g_full as usize {
780 let g_row_0 = (full_irow[k] - style_offset) as usize;
781 let v = full_jac_vals[k].abs();
782 let cr = g_to_c[g_row_0];
783 if cr >= 0 {
784 let row = cr as usize;
785 if v > c_row_max[row] {
786 c_row_max[row] = v;
787 }
788 } else {
789 let dr = g_to_d[g_row_0];
790 if dr >= 0 {
791 let row = dr as usize;
792 if v > d_row_max[row] {
793 d_row_max[row] = v;
794 }
795 }
796 }
797 }
798
799 if n_c > 0 {
801 let mut any_above = false;
802 for &v in c_row_max.iter() {
803 if v > max_gradient {
804 any_above = true;
805 break;
806 }
807 }
808 if any_above {
809 let mut dc = vec![0.0; n_c];
810 for i in 0..n_c {
811 let mut s = max_gradient / c_row_max[i];
814 if s > 1.0 {
815 s = 1.0;
816 }
817 if s < min_value {
818 s = min_value;
819 }
820 dc[i] = s;
821 }
822 *self.c_scale.borrow_mut() = Some(dc);
823 } else {
824 *self.c_scale.borrow_mut() = None;
825 }
826 } else {
827 *self.c_scale.borrow_mut() = None;
828 }
829
830 if n_d > 0 {
831 let mut any_above = false;
832 for &v in d_row_max.iter() {
833 if v > max_gradient {
834 any_above = true;
835 break;
836 }
837 }
838 if any_above {
839 let mut dd = vec![0.0; n_d];
840 for i in 0..n_d {
841 let mut s = max_gradient / d_row_max[i];
842 if s > 1.0 {
843 s = 1.0;
844 }
845 if s < min_value {
846 s = min_value;
847 }
848 dd[i] = s;
849 }
850 *self.d_scale.borrow_mut() = Some(dd);
851 } else {
852 *self.d_scale.borrow_mut() = None;
853 }
854 } else {
855 *self.d_scale.borrow_mut() = None;
856 }
857 }
858 }
859
860 self.invalidate_eval_caches();
863 }
864
865 fn invalidate_eval_caches(&self) {
866 self.f_cache.borrow_mut().clear();
867 self.grad_f_cache.borrow_mut().clear();
868 self.c_cache.borrow_mut().clear();
869 self.d_cache.borrow_mut().clear();
870 self.jac_c_cache.borrow_mut().clear();
871 self.jac_d_cache.borrow_mut().clear();
872 self.h_cache.borrow_mut().clear();
873 }
874
875 pub fn f_evals(&self) -> Index {
876 *self.f_evals.borrow()
877 }
878 pub fn grad_f_evals(&self) -> Index {
879 *self.grad_f_evals.borrow()
880 }
881 pub fn c_evals(&self) -> Index {
882 *self.c_evals.borrow()
883 }
884 pub fn d_evals(&self) -> Index {
885 *self.d_evals.borrow()
886 }
887 pub fn jac_c_evals(&self) -> Index {
888 *self.jac_c_evals.borrow()
889 }
890 pub fn jac_d_evals(&self) -> Index {
891 *self.jac_d_evals.borrow()
892 }
893 pub fn h_evals(&self) -> Index {
894 *self.h_evals.borrow()
895 }
896
897 pub fn lift_x_to_full(&self, x: &dyn Vector) -> Vec<Number> {
903 let Some(dx) = x.as_any().downcast_ref::<DenseVector>() else {
904 panic!("OrigIpoptNlp expects DenseVector for x");
905 };
906 let a = self.adapter.borrow();
907 let cls = a.classification();
908 let mut full = vec![0.0; cls.n_full_x as usize];
909 let vals = dx.expanded_values();
910 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
911 full[full_idx as usize] = vals[var_idx];
912 }
913 for (i, &full_idx) in cls.x_fixed_map.iter().enumerate() {
914 full[full_idx as usize] = cls.x_fixed_vals[i];
915 }
916 full
917 }
918
919 pub fn finalize_solution_lambda(&self, y_c: &dyn Vector, y_d: &dyn Vector) -> Vec<Number> {
931 let cls = self.adapter.borrow().classification().clone();
932 let mut lambda = self.pack_lambda_for_user(y_c, y_d, &cls);
933 let obj_scal = self.obj_scale_factor.get();
934 if obj_scal != 0.0 && obj_scal != 1.0 {
935 let inv = 1.0 / obj_scal;
936 for v in lambda.iter_mut() {
937 *v *= inv;
938 }
939 }
940 lambda
941 }
942
943 pub fn finalize_solution_z_l(&self, z_l: &dyn Vector) -> Vec<Number> {
951 let cls = self.adapter.borrow().classification().clone();
952 let n_full_x = cls.n_full_x as usize;
953 let mut full_z_l = vec![0.0; n_full_x];
954 let n_x_l = self.x_l.dim() as usize;
955 if n_x_l == 0 {
956 return full_z_l;
957 }
958 let Some(dz) = z_l.as_any().downcast_ref::<DenseVector>() else {
959 panic!("OrigIpoptNlp::finalize_solution_z_l expects DenseVector");
960 };
961 let vals = dz.expanded_values();
962 let obj_scal = self.obj_scale_factor.get();
963 let inv = if obj_scal == 0.0 { 1.0 } else { 1.0 / obj_scal };
964 for i in 0..n_x_l {
965 let var_idx = cls.x_l_map[i] as usize;
966 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
967 full_z_l[full_idx] = vals[i] * inv;
968 }
969 full_z_l
970 }
971
972 pub fn finalize_solution_z_u(&self, z_u: &dyn Vector) -> Vec<Number> {
975 let cls = self.adapter.borrow().classification().clone();
976 let n_full_x = cls.n_full_x as usize;
977 let mut full_z_u = vec![0.0; n_full_x];
978 let n_x_u = self.x_u.dim() as usize;
979 if n_x_u == 0 {
980 return full_z_u;
981 }
982 let Some(dz) = z_u.as_any().downcast_ref::<DenseVector>() else {
983 panic!("OrigIpoptNlp::finalize_solution_z_u expects DenseVector");
984 };
985 let vals = dz.expanded_values();
986 let obj_scal = self.obj_scale_factor.get();
987 let inv = if obj_scal == 0.0 { 1.0 } else { 1.0 / obj_scal };
988 for i in 0..n_x_u {
989 let var_idx = cls.x_u_map[i] as usize;
990 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
991 full_z_u[full_idx] = vals[i] * inv;
992 }
993 full_z_u
994 }
995
996 pub fn pack_lambda_for_user(
1006 &self,
1007 y_c: &dyn Vector,
1008 y_d: &dyn Vector,
1009 cls: &BoundClassification,
1010 ) -> Vec<Number> {
1011 let mut lambda = vec![0.0; cls.n_full_g as usize];
1012 if cls.n_c > 0 {
1013 let Some(dy) = y_c.as_any().downcast_ref::<DenseVector>() else {
1014 panic!("OrigIpoptNlp expects DenseVector for y_c");
1015 };
1016 let vals = dy.expanded_values();
1017 let cs = self.c_scale.borrow();
1018 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1019 lambda[g_idx as usize] = match cs.as_ref() {
1020 Some(v) => vals[i] * v[i],
1021 None => vals[i],
1022 };
1023 }
1024 }
1025 if cls.n_d > 0 {
1026 let Some(dy) = y_d.as_any().downcast_ref::<DenseVector>() else {
1027 panic!("OrigIpoptNlp expects DenseVector for y_d");
1028 };
1029 let vals = dy.expanded_values();
1030 let ds = self.d_scale.borrow();
1031 for (i, &g_idx) in cls.d_map.iter().enumerate() {
1032 lambda[g_idx as usize] = match ds.as_ref() {
1033 Some(v) => vals[i] * v[i],
1034 None => vals[i],
1035 };
1036 }
1037 }
1038 lambda
1039 }
1040
1041 #[allow(clippy::too_many_arguments)]
1051 pub fn initialize_starting_point(
1052 &mut self,
1053 x: &mut DenseVector,
1054 init_x: bool,
1055 y_c: &mut DenseVector,
1056 init_y_c: bool,
1057 y_d: &mut DenseVector,
1058 init_y_d: bool,
1059 z_l: &mut DenseVector,
1060 init_z_l: bool,
1061 z_u: &mut DenseVector,
1062 init_z_u: bool,
1063 ) -> bool {
1064 let n_full_x = self.adapter.borrow().classification().n_full_x as usize;
1065 let n_full_g = self.adapter.borrow().classification().n_full_g as usize;
1066 let n_x_l = self.x_l.dim() as usize;
1067 let n_x_u = self.x_u.dim() as usize;
1068
1069 let mut full_x = vec![0.0; n_full_x];
1070 let mut full_z_l = vec![0.0; n_full_x];
1071 let mut full_z_u = vec![0.0; n_full_x];
1072 let mut full_lambda = vec![0.0; n_full_g];
1073
1074 let ok = {
1075 let a = self.adapter.borrow();
1076 let mut t = a.tnlp().borrow_mut();
1077 t.get_starting_point(StartingPoint {
1078 init_x,
1079 x: &mut full_x,
1080 init_z: init_z_l || init_z_u,
1081 z_l: &mut full_z_l,
1082 z_u: &mut full_z_u,
1083 init_lambda: init_y_c || init_y_d,
1084 lambda: &mut full_lambda,
1085 })
1086 };
1087 if !ok {
1088 return false;
1089 }
1090
1091 let cls = self.adapter.borrow().classification().clone();
1092 let obj_scal = self.obj_scale_factor.get();
1093 let c_scale = self.c_scale.borrow();
1094 let d_scale = self.d_scale.borrow();
1095
1096 if init_x {
1098 let xs = x.values_mut();
1099 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
1100 xs[var_idx] = full_x[full_idx as usize];
1101 }
1102 }
1103 if init_y_c && cls.n_c > 0 {
1109 let yc = y_c.values_mut();
1110 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1111 let cs = c_scale.as_ref().map(|v| v[i]).unwrap_or(1.0);
1112 yc[i] = full_lambda[g_idx as usize] / cs * obj_scal;
1113 }
1114 }
1115 if init_y_d && cls.n_d > 0 {
1116 let yd = y_d.values_mut();
1117 for (i, &g_idx) in cls.d_map.iter().enumerate() {
1118 let ds = d_scale.as_ref().map(|v| v[i]).unwrap_or(1.0);
1119 yd[i] = full_lambda[g_idx as usize] / ds * obj_scal;
1120 }
1121 }
1122 if init_z_l && n_x_l > 0 {
1124 let zl = z_l.values_mut();
1125 for (i, slot) in zl.iter_mut().enumerate().take(n_x_l) {
1126 let var_idx = cls.x_l_map[i] as usize;
1127 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1128 *slot = full_z_l[full_idx] * obj_scal;
1129 }
1130 }
1131 if init_z_u && n_x_u > 0 {
1132 let zu = z_u.values_mut();
1133 for (i, slot) in zu.iter_mut().enumerate().take(n_x_u) {
1134 let var_idx = cls.x_u_map[i] as usize;
1135 let full_idx = cls.x_not_fixed_map[var_idx] as usize;
1136 *slot = full_z_u[full_idx] * obj_scal;
1137 }
1138 }
1139 true
1140 }
1141
1142 fn eval_f_internal(&self, x: &dyn Vector) -> Number {
1145 if let Some(v) = self.f_cache.borrow().get_1dep(x.as_tagged()) {
1146 return v;
1147 }
1148 *self.f_evals.borrow_mut() += 1;
1149 let full_x = self.lift_x_to_full(x);
1150 let unscaled = {
1151 let a = self.adapter.borrow();
1152 let mut t = a.tnlp().borrow_mut();
1153 t.eval_f(&full_x, true).unwrap_or(f64::NAN)
1158 };
1159 let scaled = unscaled * self.obj_scale_factor.get();
1160 self.f_cache.borrow_mut().add_1dep(scaled, x.as_tagged());
1161 scaled
1162 }
1163
1164 fn eval_grad_f_internal(&self, x: &dyn Vector) -> Rc<dyn Vector> {
1165 if let Some(v) = self.grad_f_cache.borrow().get_1dep(x.as_tagged()) {
1166 return v;
1167 }
1168 *self.grad_f_evals.borrow_mut() += 1;
1169 let full_x = self.lift_x_to_full(x);
1170 let mut full_g = vec![0.0; full_x.len()];
1171 let ok = {
1172 let a = self.adapter.borrow();
1173 let mut t = a.tnlp().borrow_mut();
1174 t.eval_grad_f(&full_x, true, &mut full_g)
1175 };
1176 if !ok {
1179 full_g.fill(f64::NAN);
1180 }
1181 let cls = self.adapter.borrow().classification().clone();
1183 let mut g_compressed = self.x_space.make_new_dense();
1184 let obj_scal = self.obj_scale_factor.get();
1185 {
1186 let gv = g_compressed.values_mut();
1187 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
1188 gv[var_idx] = full_g[full_idx as usize] * obj_scal;
1189 }
1190 }
1191 let result: Rc<dyn Vector> = Rc::new(g_compressed);
1192 self.grad_f_cache
1193 .borrow_mut()
1194 .add_1dep(Rc::clone(&result), x.as_tagged());
1195 result
1196 }
1197
1198 fn eval_c_internal(&self, x: &dyn Vector) -> Rc<dyn Vector> {
1199 let cls = self.adapter.borrow().classification().clone();
1200 if cls.n_c == 0 {
1201 if let Some(v) = self.c_cache.borrow().get_1dep(x.as_tagged()) {
1203 return v;
1204 }
1205 let v = self.c_space.make_new_dense();
1206 let result: Rc<dyn Vector> = Rc::new(v);
1207 self.c_cache
1208 .borrow_mut()
1209 .add_1dep(Rc::clone(&result), x.as_tagged());
1210 return result;
1211 }
1212 if let Some(v) = self.c_cache.borrow().get_1dep(x.as_tagged()) {
1213 return v;
1214 }
1215 *self.c_evals.borrow_mut() += 1;
1216 let full_x = self.lift_x_to_full(x);
1217 let mut full_g = vec![0.0; cls.n_full_g as usize];
1218 let ok = {
1219 let a = self.adapter.borrow();
1220 let mut t = a.tnlp().borrow_mut();
1221 t.eval_g(&full_x, true, &mut full_g)
1222 };
1223 if !ok {
1226 full_g.fill(f64::NAN);
1227 }
1228 let mut c = self.c_space.make_new_dense();
1229 let n_full_g = cls.n_full_g as usize;
1235 let mut full_g_l = vec![0.0; n_full_g];
1236 let mut full_g_u = vec![0.0; n_full_g];
1237 {
1238 let mut tmp_x_l = vec![0.0; cls.n_full_x as usize];
1239 let mut tmp_x_u = vec![0.0; cls.n_full_x as usize];
1240 let a = self.adapter.borrow();
1241 let mut t = a.tnlp().borrow_mut();
1242 t.get_bounds_info(crate::tnlp::BoundsInfo {
1243 x_l: &mut tmp_x_l,
1244 x_u: &mut tmp_x_u,
1245 g_l: &mut full_g_l,
1246 g_u: &mut full_g_u,
1247 });
1248 }
1249 {
1250 let cv = c.values_mut();
1251 let cs = self.c_scale.borrow();
1252 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1253 let raw = full_g[g_idx as usize] - full_g_l[g_idx as usize];
1254 cv[i] = match cs.as_ref() {
1255 Some(v) => raw * v[i],
1256 None => raw,
1257 };
1258 }
1259 }
1260 let result: Rc<dyn Vector> = Rc::new(c);
1261 self.c_cache
1262 .borrow_mut()
1263 .add_1dep(Rc::clone(&result), x.as_tagged());
1264 result
1265 }
1266
1267 fn eval_d_internal(&self, x: &dyn Vector) -> Rc<dyn Vector> {
1268 let cls = self.adapter.borrow().classification().clone();
1269 if cls.n_d == 0 {
1270 if let Some(v) = self.d_cache.borrow().get_1dep(x.as_tagged()) {
1271 return v;
1272 }
1273 let v = self.d_space.make_new_dense();
1274 let result: Rc<dyn Vector> = Rc::new(v);
1275 self.d_cache
1276 .borrow_mut()
1277 .add_1dep(Rc::clone(&result), x.as_tagged());
1278 return result;
1279 }
1280 if let Some(v) = self.d_cache.borrow().get_1dep(x.as_tagged()) {
1281 return v;
1282 }
1283 *self.d_evals.borrow_mut() += 1;
1284 let full_x = self.lift_x_to_full(x);
1285 let mut full_g = vec![0.0; cls.n_full_g as usize];
1286 let ok = {
1287 let a = self.adapter.borrow();
1288 let mut t = a.tnlp().borrow_mut();
1289 t.eval_g(&full_x, true, &mut full_g)
1290 };
1291 if !ok {
1292 full_g.fill(f64::NAN);
1293 }
1294 let mut d = self.d_space.make_new_dense();
1295 {
1296 let dv = d.values_mut();
1297 let ds = self.d_scale.borrow();
1298 for (i, &g_idx) in cls.d_map.iter().enumerate() {
1299 let raw = full_g[g_idx as usize];
1300 dv[i] = match ds.as_ref() {
1301 Some(v) => raw * v[i],
1302 None => raw,
1303 };
1304 }
1305 }
1306 let result: Rc<dyn Vector> = Rc::new(d);
1307 self.d_cache
1308 .borrow_mut()
1309 .add_1dep(Rc::clone(&result), x.as_tagged());
1310 result
1311 }
1312
1313 fn eval_jac_c_internal(&self, x: &dyn Vector) -> Rc<dyn Matrix> {
1314 if let Some(m) = self.jac_c_cache.borrow().get_1dep(x.as_tagged()) {
1315 return m;
1316 }
1317 *self.jac_c_evals.borrow_mut() += 1;
1318 let mut full_vals = vec![0.0; self.nnz_jac_g_full as usize];
1319 let full_x = self.lift_x_to_full(x);
1320 let ok = {
1321 let a = self.adapter.borrow();
1322 let mut t = a.tnlp().borrow_mut();
1323 t.eval_jac_g(
1324 Some(&full_x),
1325 true,
1326 SparsityRequest::Values {
1327 values: &mut full_vals,
1328 },
1329 )
1330 };
1331 if !ok {
1332 full_vals.fill(f64::NAN);
1333 }
1334 let mut jac_c = GenTMatrix::new(Rc::clone(&self.jac_c_space));
1335 {
1336 let cs = self.c_scale.borrow();
1337 let irows = self.jac_c_space.irows().to_vec();
1338 let vs = jac_c.values_mut();
1339 for (k, &src) in self.jac_c_entry_in_g.iter().enumerate() {
1340 let raw = full_vals[src as usize];
1341 vs[k] = match cs.as_ref() {
1342 Some(v) => raw * v[(irows[k] - 1) as usize],
1344 None => raw,
1345 };
1346 }
1347 }
1348 let result: Rc<dyn Matrix> = Rc::new(jac_c);
1349 self.jac_c_cache
1350 .borrow_mut()
1351 .add_1dep(Rc::clone(&result), x.as_tagged());
1352 result
1353 }
1354
1355 fn eval_jac_d_internal(&self, x: &dyn Vector) -> Rc<dyn Matrix> {
1356 if let Some(m) = self.jac_d_cache.borrow().get_1dep(x.as_tagged()) {
1357 return m;
1358 }
1359 *self.jac_d_evals.borrow_mut() += 1;
1360 let mut full_vals = vec![0.0; self.nnz_jac_g_full as usize];
1361 let full_x = self.lift_x_to_full(x);
1362 let ok = {
1363 let a = self.adapter.borrow();
1364 let mut t = a.tnlp().borrow_mut();
1365 t.eval_jac_g(
1366 Some(&full_x),
1367 true,
1368 SparsityRequest::Values {
1369 values: &mut full_vals,
1370 },
1371 )
1372 };
1373 if !ok {
1374 full_vals.fill(f64::NAN);
1375 }
1376 let mut jac_d = GenTMatrix::new(Rc::clone(&self.jac_d_space));
1377 {
1378 let ds = self.d_scale.borrow();
1379 let irows = self.jac_d_space.irows().to_vec();
1380 let vs = jac_d.values_mut();
1381 for (k, &src) in self.jac_d_entry_in_g.iter().enumerate() {
1382 let raw = full_vals[src as usize];
1383 vs[k] = match ds.as_ref() {
1384 Some(v) => raw * v[(irows[k] - 1) as usize],
1385 None => raw,
1386 };
1387 }
1388 }
1389 let result: Rc<dyn Matrix> = Rc::new(jac_d);
1390 self.jac_d_cache
1391 .borrow_mut()
1392 .add_1dep(Rc::clone(&result), x.as_tagged());
1393 result
1394 }
1395
1396 fn eval_h_internal(
1397 &self,
1398 x: &dyn Vector,
1399 obj_factor: Number,
1400 y_c: &dyn Vector,
1401 y_d: &dyn Vector,
1402 ) -> Rc<dyn SymMatrix> {
1403 if let Some(m) = self.h_cache.borrow().get(
1406 &[x.as_tagged(), y_c.as_tagged(), y_d.as_tagged()],
1407 &[obj_factor],
1408 ) {
1409 return m;
1410 }
1411 *self.h_evals.borrow_mut() += 1;
1412 let Some(h_space) = self.h_space.as_ref() else {
1413 panic!(
1414 "OrigIpoptNlp::eval_h called but the TNLP did not provide \
1415 eval_h sparsity. The L-BFGS path lands in Phase 8."
1416 );
1417 };
1418 let cls = self.adapter.borrow().classification().clone();
1419 let full_x = self.lift_x_to_full(x);
1420 let full_lambda = self.pack_lambda_for_user(y_c, y_d, &cls);
1428 let scaled_obj_factor = obj_factor * self.obj_scale_factor.get();
1429
1430 let mut full_vals = vec![0.0; self.nnz_h_lag_full as usize];
1435 let ok = {
1436 let a = self.adapter.borrow();
1437 let mut t = a.tnlp().borrow_mut();
1438 t.eval_h(
1439 Some(&full_x),
1440 true,
1441 scaled_obj_factor,
1442 Some(&full_lambda),
1443 true,
1444 SparsityRequest::Values {
1445 values: &mut full_vals,
1446 },
1447 )
1448 };
1449 if !ok {
1450 full_vals.fill(f64::NAN);
1451 }
1452 let mut h = SymTMatrix::new(Rc::clone(h_space));
1453 let kept = h_space.nonzeros() as usize;
1454 let h_vals = h.values_mut();
1455 debug_assert_eq!(kept, self.h_entry_in_full.len());
1458 for (k, &src) in self.h_entry_in_full.iter().enumerate() {
1459 h_vals[k] = full_vals[src as usize];
1460 }
1461 let result: Rc<dyn SymMatrix> = Rc::new(h);
1462 self.h_cache.borrow_mut().add(
1463 Rc::clone(&result),
1464 &[x.as_tagged(), y_c.as_tagged(), y_d.as_tagged()],
1465 &[obj_factor],
1466 );
1467 result
1468 }
1469}
1470
1471fn make_dense_from(
1474 space: &Rc<DenseVectorSpace>,
1475 mut f: impl FnMut(usize) -> Number,
1476) -> DenseVector {
1477 let mut v = space.make_new_dense();
1478 let dim = space.dim() as usize;
1479 if dim > 0 {
1480 let vs = v.values_mut();
1481 for (i, slot) in vs.iter_mut().enumerate().take(dim) {
1482 *slot = f(i);
1483 }
1484 }
1485 v
1486}
1487
1488impl Nlp for OrigIpoptNlp {
1491 fn n(&self) -> Index {
1492 self.x_space.dim()
1493 }
1494 fn m_eq(&self) -> Index {
1495 self.c_space.dim()
1496 }
1497 fn m_ineq(&self) -> Index {
1498 self.d_space.dim()
1499 }
1500
1501 fn eval_f(&mut self, x: &dyn Vector) -> Number {
1502 self.timed_eval(|t| &t.eval_obj, || self.eval_f_internal(x))
1503 }
1504 fn eval_grad_f(&mut self, x: &dyn Vector, g: &mut dyn Vector) {
1505 let result = self.timed_eval(|t| &t.eval_grad_obj, || self.eval_grad_f_internal(x));
1506 g.copy(&*result);
1507 }
1508 fn eval_c(&mut self, x: &dyn Vector, c: &mut dyn Vector) {
1509 let result = self.timed_eval(|t| &t.eval_constr, || self.eval_c_internal(x));
1510 c.copy(&*result);
1511 }
1512 fn eval_d(&mut self, x: &dyn Vector, d: &mut dyn Vector) {
1513 let result = self.timed_eval(|t| &t.eval_constr, || self.eval_d_internal(x));
1514 d.copy(&*result);
1515 }
1516 fn eval_jac_c(&mut self, x: &dyn Vector) -> Rc<dyn Matrix> {
1517 self.timed_eval(|t| &t.eval_constr_jac, || self.eval_jac_c_internal(x))
1518 }
1519 fn eval_jac_d(&mut self, x: &dyn Vector) -> Rc<dyn Matrix> {
1520 self.timed_eval(|t| &t.eval_constr_jac, || self.eval_jac_d_internal(x))
1521 }
1522 fn eval_h(
1523 &mut self,
1524 x: &dyn Vector,
1525 obj_factor: Number,
1526 y_c: &dyn Vector,
1527 y_d: &dyn Vector,
1528 ) -> Rc<dyn SymMatrix> {
1529 self.timed_eval(
1530 |t| &t.eval_lag_hess,
1531 || self.eval_h_internal(x, obj_factor, y_c, y_d),
1532 )
1533 }
1534}
1535
1536impl IpoptNlp for OrigIpoptNlp {
1537 fn x_l(&self) -> &dyn Vector {
1538 &*self.x_l
1539 }
1540 fn x_u(&self) -> &dyn Vector {
1541 &*self.x_u
1542 }
1543 fn d_l(&self) -> &dyn Vector {
1544 &*self.d_l
1545 }
1546 fn d_u(&self) -> &dyn Vector {
1547 &*self.d_u
1548 }
1549 fn px_l(&self) -> Rc<dyn Matrix> {
1550 Rc::clone(&self.px_l)
1551 }
1552 fn px_u(&self) -> Rc<dyn Matrix> {
1553 Rc::clone(&self.px_u)
1554 }
1555 fn pd_l(&self) -> Rc<dyn Matrix> {
1556 Rc::clone(&self.pd_l)
1557 }
1558 fn pd_u(&self) -> Rc<dyn Matrix> {
1559 Rc::clone(&self.pd_u)
1560 }
1561
1562 fn obj_scaling_factor(&self) -> Number {
1563 self.obj_scale_factor.get()
1564 }
1565
1566 fn get_starting_x(&mut self, x: &mut dyn Vector) -> bool {
1570 let cls = self.adapter.borrow().classification().clone();
1571 let n_full_x = cls.n_full_x as usize;
1572 let n_full_g = cls.n_full_g as usize;
1573 let mut full_x = vec![0.0; n_full_x];
1574 let mut full_z_l = vec![0.0; n_full_x];
1575 let mut full_z_u = vec![0.0; n_full_x];
1576 let mut full_lambda = vec![0.0; n_full_g];
1577 let ok = {
1578 let a = self.adapter.borrow();
1579 let mut t = a.tnlp().borrow_mut();
1580 t.get_starting_point(StartingPoint {
1581 init_x: true,
1582 x: &mut full_x,
1583 init_z: false,
1584 z_l: &mut full_z_l,
1585 z_u: &mut full_z_u,
1586 init_lambda: false,
1587 lambda: &mut full_lambda,
1588 })
1589 };
1590 if !ok {
1591 return false;
1592 }
1593 let Some(dx) = x.as_any_mut().downcast_mut::<DenseVector>() else {
1594 return false;
1595 };
1596 let xs = dx.values_mut();
1597 for (var_idx, &full_idx) in cls.x_not_fixed_map.iter().enumerate() {
1598 xs[var_idx] = full_x[full_idx as usize];
1599 }
1600 true
1601 }
1602
1603 fn lift_x_to_full(&self, x: &dyn Vector) -> Vec<Number> {
1604 OrigIpoptNlp::lift_x_to_full(self, x)
1605 }
1606
1607 fn n_full_x(&self) -> Index {
1608 self.adapter.borrow().classification().n_full_x
1609 }
1610
1611 fn n_full_g(&self) -> Index {
1612 self.adapter.borrow().classification().n_full_g
1613 }
1614
1615 fn pack_lambda_for_user(&self, y_c: &dyn Vector, y_d: &dyn Vector) -> Vec<Number> {
1616 let cls = self.adapter.borrow().classification().clone();
1617 OrigIpoptNlp::pack_lambda_for_user(self, y_c, y_d, &cls)
1618 }
1619
1620 fn pack_g_for_user(&self, c: &dyn Vector, d: &dyn Vector) -> Vec<Number> {
1621 let cls = self.adapter.borrow().classification().clone();
1622 let mut g = vec![0.0; cls.n_full_g as usize];
1623 if cls.n_c > 0 {
1624 let Some(dc) = c.as_any().downcast_ref::<DenseVector>() else {
1625 panic!("OrigIpoptNlp expects DenseVector for c");
1626 };
1627 let cs = self.c_scale.borrow();
1628 for (i, &g_idx) in cls.c_map.iter().enumerate() {
1629 let v = dc.expanded_values()[i];
1630 g[g_idx as usize] = match cs.as_ref() {
1631 Some(s) => v / s[i],
1632 None => v,
1633 };
1634 }
1635 }
1636 if cls.n_d > 0 {
1637 let Some(dd) = d.as_any().downcast_ref::<DenseVector>() else {
1638 panic!("OrigIpoptNlp expects DenseVector for d");
1639 };
1640 let ds = self.d_scale.borrow();
1641 for (i, &g_idx) in cls.d_map.iter().enumerate() {
1642 let v = dd.expanded_values()[i];
1643 g[g_idx as usize] = match ds.as_ref() {
1644 Some(s) => v / s[i],
1645 None => v,
1646 };
1647 }
1648 }
1649 g
1650 }
1651
1652 fn pack_z_l_for_user(&self, z_l: &dyn Vector) -> Vec<Number> {
1653 let cls = self.adapter.borrow().classification().clone();
1654 let mut full = vec![0.0; cls.n_full_x as usize];
1655 if z_l.dim() == 0 {
1656 return full;
1657 }
1658 let Some(dz) = z_l.as_any().downcast_ref::<DenseVector>() else {
1659 panic!("OrigIpoptNlp expects DenseVector for z_l");
1660 };
1661 let vals = dz.expanded_values();
1662 for (k, &var_idx) in cls.x_l_map.iter().enumerate() {
1663 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
1664 full[full_idx] = vals[k];
1665 }
1666 full
1667 }
1668
1669 fn pack_z_u_for_user(&self, z_u: &dyn Vector) -> Vec<Number> {
1670 let cls = self.adapter.borrow().classification().clone();
1671 let mut full = vec![0.0; cls.n_full_x as usize];
1672 if z_u.dim() == 0 {
1673 return full;
1674 }
1675 let Some(dz) = z_u.as_any().downcast_ref::<DenseVector>() else {
1676 panic!("OrigIpoptNlp expects DenseVector for z_u");
1677 };
1678 let vals = dz.expanded_values();
1679 for (k, &var_idx) in cls.x_u_map.iter().enumerate() {
1680 let full_idx = cls.x_not_fixed_map[var_idx as usize] as usize;
1681 full[full_idx] = vals[k];
1682 }
1683 full
1684 }
1685
1686 fn finalize_solution_lambda(&self, y_c: &dyn Vector, y_d: &dyn Vector) -> Vec<Number> {
1687 OrigIpoptNlp::finalize_solution_lambda(self, y_c, y_d)
1688 }
1689
1690 fn finalize_solution_z_l(&self, z_l: &dyn Vector) -> Vec<Number> {
1691 OrigIpoptNlp::finalize_solution_z_l(self, z_l)
1692 }
1693
1694 fn finalize_solution_z_u(&self, z_u: &dyn Vector) -> Vec<Number> {
1695 OrigIpoptNlp::finalize_solution_z_u(self, z_u)
1696 }
1697
1698 fn full_x_to_var_x(&self, full_idx: Index) -> Option<Index> {
1699 let cls = self.adapter.borrow();
1700 let cls = cls.classification();
1701 let f = full_idx as usize;
1702 if f >= cls.full_to_var.len() {
1703 return None;
1704 }
1705 let v = cls.full_to_var[f];
1706 if v < 0 {
1707 None
1708 } else {
1709 Some(v)
1710 }
1711 }
1712
1713 fn full_g_to_c_block(&self, full_idx: Index) -> Option<Index> {
1714 let cls = self.adapter.borrow();
1715 let cls = cls.classification();
1716 cls.c_map
1717 .iter()
1718 .position(|&g_idx| g_idx == full_idx)
1719 .map(|p| p as Index)
1720 }
1721
1722 fn var_x_to_full_x(&self, var_idx: Index) -> Index {
1723 let cls = self.adapter.borrow();
1724 let cls = cls.classification();
1725 cls.x_not_fixed_map[var_idx as usize]
1726 }
1727}
1728
1729#[cfg(test)]
1732mod tests {
1733 use super::*;
1734 use crate::tnlp::{
1735 BoundsInfo, IndexStyle, IpoptCq, IpoptData, NlpInfo, Solution, SparsityRequest,
1736 StartingPoint, TNLP,
1737 };
1738
1739 #[derive(Default)]
1744 struct Hs071 {
1745 eval_f_calls: usize,
1746 eval_grad_f_calls: usize,
1747 eval_g_calls: usize,
1748 eval_jac_g_value_calls: usize,
1749 eval_h_value_calls: usize,
1750 }
1751
1752 impl TNLP for Hs071 {
1753 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
1754 Some(NlpInfo {
1755 n: 4,
1756 m: 2,
1757 nnz_jac_g: 8,
1758 nnz_h_lag: 10,
1759 index_style: IndexStyle::C,
1760 })
1761 }
1762 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
1763 b.x_l.copy_from_slice(&[1.0; 4]);
1764 b.x_u.copy_from_slice(&[5.0; 4]);
1765 b.g_l.copy_from_slice(&[25.0, 40.0]);
1768 b.g_u.copy_from_slice(&[2.0e19, 40.0]);
1769 true
1770 }
1771 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
1772 sp.x.copy_from_slice(&[1.0, 5.0, 5.0, 1.0]);
1773 true
1774 }
1775 fn eval_f(&mut self, x: &[Number], _new_x: bool) -> Option<Number> {
1776 self.eval_f_calls += 1;
1777 Some(x[0] * x[3] * (x[0] + x[1] + x[2]) + x[2])
1778 }
1779 fn eval_grad_f(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
1780 self.eval_grad_f_calls += 1;
1781 g[0] = x[3] * (2.0 * x[0] + x[1] + x[2]);
1786 g[1] = x[0] * x[3];
1787 g[2] = x[0] * x[3] + 1.0;
1788 g[3] = x[0] * (x[0] + x[1] + x[2]);
1789 true
1790 }
1791 fn eval_g(&mut self, x: &[Number], _new_x: bool, g: &mut [Number]) -> bool {
1792 self.eval_g_calls += 1;
1793 g[0] = x[0] * x[1] * x[2] * x[3];
1796 g[1] = x[0] * x[0] + x[1] * x[1] + x[2] * x[2] + x[3] * x[3];
1797 true
1798 }
1799 fn eval_jac_g(
1800 &mut self,
1801 x: Option<&[Number]>,
1802 _new_x: bool,
1803 mode: SparsityRequest<'_>,
1804 ) -> bool {
1805 match mode {
1806 SparsityRequest::Structure { irow, jcol } => {
1807 irow.copy_from_slice(&[0, 0, 0, 0, 1, 1, 1, 1]);
1809 jcol.copy_from_slice(&[0, 1, 2, 3, 0, 1, 2, 3]);
1810 }
1811 SparsityRequest::Values { values } => {
1812 self.eval_jac_g_value_calls += 1;
1813 let x = x.expect("eval_jac_g(Values) without x");
1814 values[0] = x[1] * x[2] * x[3];
1816 values[1] = x[0] * x[2] * x[3];
1817 values[2] = x[0] * x[1] * x[3];
1818 values[3] = x[0] * x[1] * x[2];
1819 values[4] = 2.0 * x[0];
1821 values[5] = 2.0 * x[1];
1822 values[6] = 2.0 * x[2];
1823 values[7] = 2.0 * x[3];
1824 }
1825 }
1826 true
1827 }
1828 fn eval_h(
1829 &mut self,
1830 x: Option<&[Number]>,
1831 _new_x: bool,
1832 obj_factor: Number,
1833 lambda: Option<&[Number]>,
1834 _new_lambda: bool,
1835 mode: SparsityRequest<'_>,
1836 ) -> bool {
1837 match mode {
1840 SparsityRequest::Structure { irow, jcol } => {
1841 irow.copy_from_slice(&[0, 1, 1, 2, 2, 2, 3, 3, 3, 3]);
1842 jcol.copy_from_slice(&[0, 0, 1, 0, 1, 2, 0, 1, 2, 3]);
1843 }
1844 SparsityRequest::Values { values } => {
1845 self.eval_h_value_calls += 1;
1846 let x = x.expect("eval_h(Values) without x");
1847 let lam = lambda.expect("eval_h(Values) without lambda");
1848 let of = obj_factor;
1849 let l0 = lam[0];
1859 let l1 = lam[1];
1860 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; }
1871 }
1872 true
1873 }
1874 fn finalize_solution(&mut self, _sol: Solution<'_>, _d: &IpoptData, _q: &IpoptCq) {}
1875 }
1876
1877 fn build_orig_nlp() -> (Rc<RefCell<TNLPAdapter>>, OrigIpoptNlp) {
1878 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(Hs071::default()));
1879 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
1880 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
1881 (adapter, nlp)
1882 }
1883
1884 fn dense_x(values: &[Number], space: &Rc<DenseVectorSpace>) -> DenseVector {
1885 let mut v = space.make_new_dense();
1886 v.values_mut().copy_from_slice(values);
1887 v
1888 }
1889
1890 #[test]
1891 fn dimensions_match_classification() {
1892 let (_, nlp) = build_orig_nlp();
1893 assert_eq!(nlp.n(), 4);
1895 assert_eq!(nlp.m_eq(), 1);
1896 assert_eq!(nlp.m_ineq(), 1);
1897 assert_eq!(nlp.jac_c_space().nonzeros(), 4);
1899 assert_eq!(nlp.jac_d_space().nonzeros(), 4);
1900 assert_eq!(nlp.h_space().unwrap().nonzeros(), 10);
1902 assert_eq!(nlp.x_l().dim(), 4);
1904 assert_eq!(nlp.x_u().dim(), 4);
1905 assert_eq!(nlp.d_l().dim(), 1);
1906 assert_eq!(nlp.d_u().dim(), 0);
1907 }
1908
1909 #[test]
1910 fn eval_f_at_starting_point() {
1911 let (_, mut nlp) = build_orig_nlp();
1912 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
1913 assert_eq!(nlp.eval_f(&x), 16.0);
1915 assert_eq!(nlp.f_evals(), 1);
1916 }
1917
1918 #[test]
1919 fn eval_grad_f_at_starting_point() {
1920 let (_, mut nlp) = build_orig_nlp();
1921 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
1922 let mut g = nlp.x_space().make_new_dense();
1923 nlp.eval_grad_f(&x, &mut g);
1924 assert_eq!(g.values(), &[12.0, 1.0, 2.0, 11.0]);
1929 assert_eq!(nlp.grad_f_evals(), 1);
1930 }
1931
1932 #[test]
1933 fn eval_c_returns_equality_residual() {
1934 let (_, mut nlp) = build_orig_nlp();
1935 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
1936 let mut c = nlp.c_space().make_new_dense();
1937 nlp.eval_c(&x, &mut c);
1938 assert_eq!(c.values(), &[12.0]);
1940 assert_eq!(nlp.c_evals(), 1);
1941 }
1942
1943 #[test]
1944 fn eval_d_returns_inequality_value_unshifted() {
1945 let (_, mut nlp) = build_orig_nlp();
1946 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
1947 let mut d = nlp.d_space().make_new_dense();
1948 nlp.eval_d(&x, &mut d);
1949 assert_eq!(d.values(), &[25.0]);
1951 assert_eq!(nlp.d_evals(), 1);
1952 }
1953
1954 #[test]
1955 fn cache_returns_without_re_eval() {
1956 let (_, mut nlp) = build_orig_nlp();
1957 let mut x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
1958 let f1 = nlp.eval_f(&x);
1959 let f2 = nlp.eval_f(&x);
1960 assert_eq!(f1, f2);
1961 assert_eq!(nlp.f_evals(), 1, "second call must be served from cache");
1962 x.values_mut()[0] = 1.0; let _ = nlp.eval_f(&x);
1965 assert_eq!(nlp.f_evals(), 2);
1966 }
1967
1968 #[test]
1969 fn jac_c_picks_only_equality_rows() {
1970 let (_, mut nlp) = build_orig_nlp();
1971 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
1972 let m = nlp.eval_jac_c(&x);
1973 let g = m
1974 .as_any()
1975 .downcast_ref::<GenTMatrix>()
1976 .expect("jac_c is a GenTMatrix");
1977 assert_eq!(g.values(), &[2.0, 10.0, 10.0, 2.0]);
1979 assert_eq!(g.irows(), &[1, 1, 1, 1]);
1981 assert_eq!(g.jcols(), &[1, 2, 3, 4]);
1982 }
1983
1984 #[test]
1985 fn jac_d_picks_only_inequality_rows() {
1986 let (_, mut nlp) = build_orig_nlp();
1987 let x = dense_x(&[1.0, 5.0, 5.0, 1.0], nlp.x_space());
1988 let m = nlp.eval_jac_d(&x);
1989 let g = m
1990 .as_any()
1991 .downcast_ref::<GenTMatrix>()
1992 .expect("jac_d is a GenTMatrix");
1993 assert_eq!(g.values(), &[25.0, 5.0, 5.0, 25.0]);
1996 }
1997
1998 #[test]
1999 fn starting_point_is_compressed_into_x_var() {
2000 let (_, mut nlp) = build_orig_nlp();
2001 let mut x = nlp.x_space().make_new_dense();
2002 let mut yc = nlp.c_space().make_new_dense();
2003 let mut yd = nlp.d_space().make_new_dense();
2004 let mut zl = nlp.x_l_space().make_new_dense();
2005 let mut zu = nlp.x_u_space().make_new_dense();
2006 let ok = nlp.initialize_starting_point(
2007 &mut x, true, &mut yc, false, &mut yd, false, &mut zl, false, &mut zu, false,
2008 );
2009 assert!(ok);
2010 assert_eq!(x.values(), &[1.0, 5.0, 5.0, 1.0]);
2011 }
2012
2013 struct OneFixedOneFree;
2018 impl TNLP for OneFixedOneFree {
2019 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2020 Some(NlpInfo {
2021 n: 2,
2022 m: 1,
2023 nnz_jac_g: 1,
2024 nnz_h_lag: 0,
2025 index_style: IndexStyle::C,
2026 })
2027 }
2028 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2029 b.x_l[0] = 7.0;
2030 b.x_u[0] = 7.0; b.x_l[1] = -1.0e19;
2032 b.x_u[1] = 1.0e19;
2033 b.g_l[0] = 0.0;
2034 b.g_u[0] = 0.0; true
2036 }
2037 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2038 sp.x[0] = 7.0;
2039 sp.x[1] = 0.5;
2040 true
2041 }
2042 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
2043 Some(x[1])
2044 }
2045 fn eval_grad_f(&mut self, _: &[Number], _: bool, g: &mut [Number]) -> bool {
2046 g[0] = 0.0;
2047 g[1] = 1.0;
2048 true
2049 }
2050 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
2051 g[0] = x[1];
2052 true
2053 }
2054 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
2055 match m {
2056 SparsityRequest::Structure { irow, jcol } => {
2057 irow[0] = 0;
2058 jcol[0] = 1;
2059 }
2060 SparsityRequest::Values { values } => values[0] = 1.0,
2061 }
2062 true
2063 }
2064 fn eval_h(
2065 &mut self,
2066 _: Option<&[Number]>,
2067 _: bool,
2068 _: Number,
2069 _: Option<&[Number]>,
2070 _: bool,
2071 _: SparsityRequest<'_>,
2072 ) -> bool {
2073 true
2074 }
2075 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
2076 }
2077
2078 #[test]
2079 fn ipopt_nlp_index_mapping_methods_handle_fixed_var() {
2080 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(OneFixedOneFree));
2081 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2082 let nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2083
2084 assert_eq!(nlp.n_full_x(), 2);
2086 assert_eq!(nlp.n(), 1);
2087
2088 let nlp_dyn: &dyn crate::ipopt_nlp::IpoptNlp = &nlp;
2090 assert_eq!(nlp_dyn.full_x_to_var_x(0), None);
2091 assert_eq!(nlp_dyn.full_x_to_var_x(1), Some(0));
2092
2093 assert_eq!(nlp_dyn.var_x_to_full_x(0), 1);
2095
2096 assert_eq!(nlp_dyn.full_g_to_c_block(0), Some(0));
2098
2099 let mut x_var = nlp.x_space().make_new_dense();
2101 x_var.values_mut()[0] = 0.5;
2102 let lifted = nlp_dyn.lift_x_to_full(&x_var);
2103 assert_eq!(lifted, vec![7.0, 0.5]);
2104 }
2105
2106 struct FixedOnlyHess;
2112 impl TNLP for FixedOnlyHess {
2113 fn get_nlp_info(&mut self) -> Option<NlpInfo> {
2114 Some(NlpInfo {
2115 n: 2,
2116 m: 1,
2117 nnz_jac_g: 1,
2118 nnz_h_lag: 1,
2119 index_style: IndexStyle::C,
2120 })
2121 }
2122 fn get_bounds_info(&mut self, b: BoundsInfo<'_>) -> bool {
2123 b.x_l[0] = 7.0;
2124 b.x_u[0] = 7.0; b.x_l[1] = -1.0e19;
2126 b.x_u[1] = 1.0e19;
2127 b.g_l[0] = 0.0;
2128 b.g_u[0] = 0.0;
2129 true
2130 }
2131 fn get_starting_point(&mut self, sp: StartingPoint<'_>) -> bool {
2132 sp.x[0] = 7.0;
2133 sp.x[1] = 0.5;
2134 true
2135 }
2136 fn eval_f(&mut self, x: &[Number], _: bool) -> Option<Number> {
2137 Some(0.5 * x[0] * x[0] + x[1])
2138 }
2139 fn eval_grad_f(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
2140 g[0] = x[0];
2141 g[1] = 1.0;
2142 true
2143 }
2144 fn eval_g(&mut self, x: &[Number], _: bool, g: &mut [Number]) -> bool {
2145 g[0] = x[1];
2146 true
2147 }
2148 fn eval_jac_g(&mut self, _: Option<&[Number]>, _: bool, m: SparsityRequest<'_>) -> bool {
2149 match m {
2150 SparsityRequest::Structure { irow, jcol } => {
2151 irow[0] = 0;
2152 jcol[0] = 1;
2153 }
2154 SparsityRequest::Values { values } => values[0] = 1.0,
2155 }
2156 true
2157 }
2158 fn eval_h(
2159 &mut self,
2160 _: Option<&[Number]>,
2161 _: bool,
2162 obj_factor: Number,
2163 _: Option<&[Number]>,
2164 _: bool,
2165 m: SparsityRequest<'_>,
2166 ) -> bool {
2167 match m {
2168 SparsityRequest::Structure { irow, jcol } => {
2169 irow[0] = 0;
2170 jcol[0] = 0;
2171 }
2172 SparsityRequest::Values { values } => values[0] = obj_factor,
2173 }
2174 true
2175 }
2176 fn finalize_solution(&mut self, _: Solution<'_>, _: &IpoptData, _: &IpoptCq) {}
2177 }
2178
2179 #[test]
2180 fn eval_h_with_all_entries_on_fixed_var_does_not_panic() {
2181 let tnlp: Rc<RefCell<dyn TNLP>> = Rc::new(RefCell::new(FixedOnlyHess));
2182 let adapter = Rc::new(RefCell::new(TNLPAdapter::new(tnlp).unwrap()));
2183 let mut nlp = OrigIpoptNlp::new(Rc::clone(&adapter), Rc::new(NoScaling)).unwrap();
2184
2185 assert_eq!(nlp.h_space().unwrap().nonzeros(), 0);
2188
2189 let x = dense_x(&[0.5], &nlp.x_space().clone());
2190 let yc = dense_x(&[0.0], &nlp.c_space().clone());
2191 let yd = nlp.d_space().make_new_dense();
2192 let h = nlp.eval_h(&x, 1.0, &yc, &yd);
2193 assert_eq!(h.n_rows(), 1);
2194 }
2195}