coco_rs/
problem.rs

1//! COCO problem instance.
2
3use coco_sys::coco_problem_t;
4use std::{ffi::CStr, marker::PhantomData, ops::RangeInclusive};
5
6use crate::{
7    suite::{self, Suite},
8    Observer,
9};
10
11/// A specific problem instance.
12///
13/// Instances can be optained using [Suite::next_problem]
14/// and [Suite::problem_by_function_dimension_instance].
15pub struct Problem<'suite> {
16    pub(crate) inner: *mut coco_problem_t,
17    _phantom: PhantomData<&'suite Suite>,
18}
19
20unsafe impl Send for Problem<'_> {}
21
22impl<'suite> Problem<'suite> {
23    pub(crate) fn new(inner: *mut coco_problem_t, _suite: &'suite Suite) -> Self {
24        Problem {
25            inner,
26            _phantom: PhantomData,
27        }
28    }
29}
30
31impl Problem<'_> {
32    /// Returns the ID of the problem.
33    ///
34    /// For the `toy` suite this is
35    /// - `{function-name}_d{dimension}`
36    ///
37    /// For `bbob` it is
38    /// - bbob_f{function-index}_i{instance}_d{dimension}
39    pub fn id(&self) -> &str {
40        unsafe {
41            CStr::from_ptr(coco_sys::coco_problem_get_id(self.inner))
42                .to_str()
43                .unwrap()
44        }
45    }
46
47    /// Returns the name of the problem.
48    pub fn name(&self) -> &str {
49        unsafe {
50            CStr::from_ptr(coco_sys::coco_problem_get_name(self.inner))
51                .to_str()
52                .unwrap()
53        }
54    }
55
56    /// Returns the type of the problem.
57    pub fn typ(&self) -> &str {
58        unsafe {
59            CStr::from_ptr(coco_sys::coco_problem_get_type(self.inner))
60                .to_str()
61                .unwrap()
62        }
63    }
64
65    /// Adds an observer to the given problem.
66    pub fn add_observer(&mut self, observer: &Observer) {
67        // The Python bindings also mutate the problem instead of returning a new one.
68        self.inner = unsafe { coco_sys::coco_problem_add_observer(self.inner, observer.inner) };
69
70        assert!(!self.inner.is_null())
71    }
72
73    /// Removes an observer to the given problem.
74    pub fn remove_observer(&mut self, observer: &Observer) {
75        // The Python bindings also mutate the problem instead of returning a new one.
76        self.inner = unsafe { coco_sys::coco_problem_remove_observer(self.inner, observer.inner) };
77
78        assert!(!self.inner.is_null())
79    }
80
81    /// Returns the problem index of the problem in its current suite.
82    pub fn suite_index(&self) -> suite::ProblemIdx {
83        let idx = unsafe { coco_sys::coco_problem_get_suite_dep_index(self.inner) };
84
85        suite::ProblemIdx(idx)
86    }
87
88    /// Evaluates the problem at `x` and returns the result in `y`.
89    ///
90    /// The length of `x` must match [Problem::dimension] and the
91    /// length of `y` must match [Problem::number_of_objectives].
92    pub fn evaluate_function(&mut self, x: &[f64], y: &mut [f64]) {
93        assert_eq!(self.dimension(), x.len());
94        assert_eq!(self.number_of_objectives(), y.len());
95
96        unsafe {
97            coco_sys::coco_evaluate_function(self.inner, x.as_ptr(), y.as_mut_ptr());
98        }
99    }
100
101    /// Evaluates the problem constraints in point x and save the result in y.
102    ///
103    /// The length of `x` must match [Problem::dimension] and the
104    /// length of `y` must match [Problem::number_of_constraints].
105    pub fn evaluate_constraint(&mut self, x: &[f64], y: &mut [f64]) {
106        assert_eq!(self.dimension(), x.len());
107        assert_eq!(self.number_of_constraints(), y.len());
108
109        unsafe {
110            coco_sys::coco_evaluate_constraint(self.inner, x.as_ptr(), y.as_mut_ptr());
111        }
112    }
113
114    /// Returns true if a previous evaluation hit the target value.
115    pub fn final_target_hit(&self) -> bool {
116        unsafe { coco_sys::coco_problem_final_target_hit(self.inner) == 1 }
117    }
118
119    /// Returns the optimal function value + delta of the problem
120    pub fn final_target_value(&self) -> f64 {
121        unsafe { coco_sys::coco_problem_get_final_target_fvalue1(self.inner) }
122    }
123
124    /// Returns the optimal function value of the problem
125    ///
126    /// To check whether the target has been reached use [[Problem::final_target_value]]
127    /// or [[Problem::final_target_hit]] instead.
128    pub fn best_value(&self) -> f64 {
129        unsafe { coco_sys::coco_problem_get_best_value(self.inner) }
130    }
131
132    /// Returns the best observed value for the first objective.
133    pub fn best_observed_value(&self) -> f64 {
134        unsafe { coco_sys::coco_problem_get_best_observed_fvalue1(self.inner) }
135    }
136
137    /// Returns the dimension of the problem.
138    pub fn dimension(&self) -> usize {
139        unsafe {
140            coco_sys::coco_problem_get_dimension(self.inner)
141                .try_into()
142                .unwrap()
143        }
144    }
145
146    /// Returns the number of objectives of the problem.
147    pub fn number_of_objectives(&self) -> usize {
148        unsafe {
149            coco_sys::coco_problem_get_number_of_objectives(self.inner)
150                .try_into()
151                .unwrap()
152        }
153    }
154
155    /// Returns the number of constraints of the problem.
156    pub fn number_of_constraints(&self) -> usize {
157        unsafe {
158            coco_sys::coco_problem_get_number_of_constraints(self.inner)
159                .try_into()
160                .unwrap()
161        }
162    }
163
164    /// Returns the numver of integer variables of the problem.
165    ///
166    /// The first `n` variables will be integers then.
167    /// Returns `0` if all variables are continuous.
168    pub fn number_of_integer_variables(&self) -> usize {
169        unsafe {
170            coco_sys::coco_problem_get_number_of_integer_variables(self.inner)
171                .try_into()
172                .unwrap()
173        }
174    }
175
176    /// Returns the upper and lover bounds of the problem.
177    pub fn ranges_of_interest(&self) -> Vec<RangeInclusive<f64>> {
178        let dimension = self.dimension() as isize;
179        unsafe {
180            let smallest = coco_sys::coco_problem_get_smallest_values_of_interest(self.inner);
181            let largest = coco_sys::coco_problem_get_largest_values_of_interest(self.inner);
182
183            (0..dimension)
184                .map(|i| (*smallest.offset(i))..=(*largest.offset(i)))
185                .collect()
186        }
187    }
188
189    /// Returns how often this instance has been evaluated.
190    pub fn evaluations(&self) -> u64 {
191        unsafe {
192            #[allow(clippy::useless_conversion)]
193            coco_sys::coco_problem_get_evaluations(self.inner)
194                .try_into()
195                .unwrap()
196        }
197    }
198
199    /// Returns how often this instances constrants have been evaluated.
200    pub fn evaluations_constraints(&self) -> u64 {
201        unsafe {
202            #[allow(clippy::useless_conversion)]
203            coco_sys::coco_problem_get_evaluations_constraints(self.inner)
204                .try_into()
205                .unwrap()
206        }
207    }
208
209    /// Writes a feasible initial solution into `x`.
210    ///
211    /// If the problem does not provide a specific solution,
212    /// it will be the center of the problem's region of interest.
213    pub fn initial_solution(&self, x: &mut [f64]) {
214        assert_eq!(self.dimension(), x.len());
215        unsafe {
216            coco_sys::coco_problem_get_initial_solution(self.inner, x.as_mut_ptr());
217        }
218    }
219}
220
221impl Drop for Problem<'_> {
222    fn drop(&mut self) {
223        unsafe {
224            coco_sys::coco_problem_free(self.inner);
225        }
226    }
227}