Skip to content

Commit 91d7267

Browse files
committed
Store function constraint values
1 parent dbe98ec commit 91d7267

File tree

7 files changed

+163
-59
lines changed

7 files changed

+163
-59
lines changed

crates/ego/src/egor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ impl<O: GroupFunc, C: CstrFn, SB: SurrogateBuilder + DeserializeOwned> Egor<O, C
250250
};
251251

252252
info!("{}", result);
253-
let (x_data, y_data) = result.state().clone().take_data().unwrap();
253+
let (x_data, y_data, _c_data) = result.state().clone().take_data().unwrap();
254254

255255
let res = if !self.solver.config.discrete() {
256256
info!("Data: \n{}", concatenate![Axis(1), x_data, y_data]);

crates/ego/src/solver/egor_impl.rs

Lines changed: 115 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ impl<SB: SurrogateBuilder + DeserializeOwned, C: CstrFn> EgorSolver<SB, C> {
5555
&self,
5656
x_data: &ArrayBase<impl Data<Elem = f64>, Ix2>,
5757
y_data: &ArrayBase<impl Data<Elem = f64>, Ix2>,
58-
_cstr_funcs: &[&(dyn ObjFn<InfillObjData<f64>> + Sync)],
5958
) -> Array2<f64> {
6059
let rng = self.rng.clone();
6160
let sampling = Lhs::new(&self.xlimits).with_rng(rng).kind(LhsKind::Maximin);
@@ -66,19 +65,26 @@ impl<SB: SurrogateBuilder + DeserializeOwned, C: CstrFn> EgorSolver<SB, C> {
6665
.cstr_tol
6766
.clone()
6867
.unwrap_or(Array1::from_elem(self.config.n_cstr, DEFAULT_CSTR_TOL));
69-
let (x_dat, _, _, _) = self.next_points(
68+
69+
// TODO: Manage fonction constraints
70+
let fcstrs = Vec::<Cstr>::new();
71+
// TODO: c_data has to be passed as argument or better computed using fcstrs(x_data)
72+
let c_data = Array2::zeros((x_data.nrows(), 0));
73+
74+
let (x_dat, _, _, _, _) = self.next_points(
7075
true,
7176
0,
7277
false, // done anyway
7378
&mut clusterings,
7479
&mut theta_tunings,
7580
x_data,
7681
y_data,
82+
&c_data,
7783
&cstr_tol,
7884
&sampling,
7985
None,
8086
find_best_result_index(y_data, &cstr_tol),
81-
&Vec::<Cstr>::new(),
87+
&fcstrs,
8288
);
8389
x_dat
8490
}
@@ -249,7 +255,7 @@ where
249255
PotentialBug,
250256
"EgorSolver: No sampling!"
251257
))?;
252-
let (mut x_data, mut y_data) = new_state
258+
let (mut x_data, mut y_data, mut c_data) = new_state
253259
.take_data()
254260
.ok_or_else(argmin_error_closure!(PotentialBug, "EgorSolver: No data!"))?;
255261

@@ -270,14 +276,15 @@ where
270276

271277
let problem = fobj.take_problem().unwrap();
272278
let fcstrs = problem.fn_constraints();
273-
let (x_dat, y_dat, infill_value, infill_data) = self.next_points(
279+
let (x_dat, y_dat, c_dat, infill_value, infill_data) = self.next_points(
274280
init,
275281
state.get_iter(),
276282
recluster,
277283
&mut clusterings,
278284
&mut theta_inits,
279285
&x_data,
280286
&y_data,
287+
&c_data,
281288
&state.cstr_tol,
282289
&sampling,
283290
lhs_optim_seed,
@@ -287,12 +294,19 @@ where
287294
fobj.problem = Some(problem);
288295

289296
debug!("Try adding {}", x_dat);
290-
let added_indices = update_data(&mut x_data, &mut y_data, &x_dat, &y_dat);
297+
let added_indices = update_data(
298+
&mut x_data,
299+
&mut y_data,
300+
&mut c_data,
301+
&x_dat,
302+
&y_dat,
303+
&c_dat,
304+
);
291305

292306
new_state = new_state
293307
.clusterings(clusterings.clone())
294308
.theta_inits(theta_inits.clone())
295-
.data((x_data.clone(), y_data.clone()))
309+
.data((x_data.clone(), y_data.clone(), c_data.clone()))
296310
.infill_value(infill_value)
297311
.sampling(sampling.clone())
298312
.param(x_dat.row(0).to_owned())
@@ -365,7 +379,7 @@ where
365379
);
366380
new_state.prev_best_index = state.best_index;
367381
new_state.best_index = Some(best_index);
368-
new_state = new_state.data((x_data.clone(), y_data.clone()));
382+
new_state = new_state.data((x_data.clone(), y_data.clone(), c_data.clone()));
369383

370384
Ok((new_state, infill_data, best_index))
371385
}
@@ -384,15 +398,23 @@ where
384398
theta_inits: &mut [Option<Array2<f64>>],
385399
x_data: &ArrayBase<impl Data<Elem = f64>, Ix2>,
386400
y_data: &ArrayBase<impl Data<Elem = f64>, Ix2>,
401+
c_data: &ArrayBase<impl Data<Elem = f64>, Ix2>,
387402
cstr_tol: &Array1<f64>,
388403
sampling: &Lhs<f64, Xoshiro256Plus>,
389404
lhs_optim: Option<u64>,
390405
best_index: usize,
391406
cstr_funcs: &[impl CstrFn],
392-
) -> (Array2<f64>, Array2<f64>, f64, InfillObjData<f64>) {
407+
) -> (
408+
Array2<f64>,
409+
Array2<f64>,
410+
Array2<f64>,
411+
f64,
412+
InfillObjData<f64>,
413+
) {
393414
debug!("Make surrogate with {}", x_data);
394415
let mut x_dat = Array2::zeros((0, x_data.ncols()));
395416
let mut y_dat = Array2::zeros((0, y_data.ncols()));
417+
let mut c_dat = Array2::zeros((0, c_data.ncols()));
396418
let mut infill_val = f64::INFINITY;
397419
let mut infill_data = Default::default();
398420
for i in 0..self.config.q_points {
@@ -458,14 +480,42 @@ where
458480
scale_wb2,
459481
};
460482

483+
let cstr_funcs = cstr_funcs
484+
.iter()
485+
.map(|cstr| {
486+
|x: &[f64],
487+
gradient: Option<&mut [f64]>,
488+
params: &mut InfillObjData<f64>|
489+
-> f64 {
490+
let x = if self.config.discrete() {
491+
let xary = Array2::from_shape_vec((1, x.len()), x.to_vec()).unwrap();
492+
// We have to cast x to folded space as EgorSolver
493+
// works internally in the continuous space while
494+
// the constraint function expects discrete variable in folded space
495+
to_discrete_space(&self.config.xtypes, &xary)
496+
.row(0)
497+
.into_owned();
498+
&xary.into_iter().collect::<Vec<_>>()
499+
} else {
500+
x
501+
};
502+
cstr(x, gradient, params)
503+
}
504+
})
505+
.collect::<Vec<_>>();
506+
let cstr_funcs = cstr_funcs
507+
.iter()
508+
.map(|cstr| cstr as &(dyn ObjFn<InfillObjData<f64>> + Sync))
509+
.collect::<Vec<_>>();
510+
461511
match self.find_best_point(
462512
sampling,
463513
obj_model.as_ref(),
464514
cstr_models,
465515
cstr_tol,
466516
lhs_optim,
467517
&infill_data,
468-
cstr_funcs,
518+
&cstr_funcs,
469519
) {
470520
Ok((infill_obj, xk)) => {
471521
match self.get_virtual_point(&xk, y_data, obj_model.as_ref(), cstr_models) {
@@ -475,7 +525,19 @@ where
475525
y_dat,
476526
Array2::from_shape_vec((1, 1 + self.config.n_cstr), yk).unwrap()
477527
];
528+
529+
let ck = cstr_funcs
530+
.iter()
531+
.map(|cstr| cstr(&xk.to_vec(), None, &mut infill_data))
532+
.collect::<Vec<_>>();
533+
c_dat = concatenate![
534+
Axis(0),
535+
c_dat,
536+
Array2::from_shape_vec((1, cstr_funcs.len()), ck).unwrap()
537+
];
538+
478539
x_dat = concatenate![Axis(0), x_dat, xk.insert_axis(Axis(0))];
540+
479541
// infill objective was minimized while infill criterion itself
480542
// is expected to be maximized hence the negative sign here
481543
infill_val = -infill_obj;
@@ -494,7 +556,7 @@ where
494556
}
495557
}
496558
}
497-
(x_dat, y_dat, infill_val, infill_data)
559+
(x_dat, y_dat, c_dat, infill_val, infill_data)
498560
}
499561

500562
pub(crate) fn compute_scaling(
@@ -550,7 +612,7 @@ where
550612
cstr_tol: &Array1<f64>,
551613
lhs_optim_seed: Option<u64>,
552614
infill_data: &InfillObjData<f64>,
553-
cstr_funcs: &[impl CstrFn],
615+
cstr_funcs: &[&(dyn ObjFn<InfillObjData<f64>> + Sync)],
554616
) -> Result<(f64, Array1<f64>)> {
555617
let fmin = infill_data.fmin;
556618

@@ -626,30 +688,8 @@ where
626688
})
627689
.collect();
628690
let mut cstr_refs: Vec<_> = cstrs.iter().map(|c| c.as_ref()).collect();
629-
let cstr_funcs = cstr_funcs
630-
.iter()
631-
.map(|cstr| {
632-
|x: &[f64], gradient: Option<&mut [f64]>, params: &mut InfillObjData<f64>| -> f64 {
633-
let x = if self.config.discrete() {
634-
let xary = Array2::from_shape_vec((1, x.len()), x.to_vec()).unwrap();
635-
// We have to cast x to folded space as EgorSolver
636-
// works internally in the continuous space while
637-
// the constraint function expects discrete variable in folded space
638-
to_discrete_space(&self.config.xtypes, &xary)
639-
.row(0)
640-
.into_owned();
641-
&xary.into_iter().collect::<Vec<_>>()
642-
} else {
643-
x
644-
};
645-
cstr(x, gradient, params)
646-
}
647-
})
648-
.collect::<Vec<_>>();
649-
let cstr_funcs = cstr_funcs
650-
.iter()
651-
.map(|cstr| cstr as &(dyn ObjFn<InfillObjData<f64>> + Sync));
652691
cstr_refs.extend(cstr_funcs);
692+
653693
info!("Optimize infill criterion...");
654694
while !success && n_optim <= n_max_optim {
655695
let x_start = sampling.sample(self.config.n_start);
@@ -818,15 +858,53 @@ where
818858
pb: &mut Problem<O>,
819859
x: &Array2<f64>,
820860
) -> Array2<f64> {
821-
let params = if self.config.discrete() {
861+
let x = if self.config.discrete() {
822862
// We have to cast x to folded space as EgorSolver
823863
// works internally in the continuous space while
824864
// the objective function expects discrete variable in folded space
825865
to_discrete_space(&self.config.xtypes, x)
826866
} else {
827867
x.to_owned()
828868
};
829-
pb.problem("cost_count", |problem| problem.cost(&params))
869+
pb.problem("cost_count", |problem| problem.cost(&x))
830870
.expect("Objective evaluation")
831871
}
872+
873+
pub fn eval_fcstrs<O: DomainConstraints<C>>(
874+
&self,
875+
pb: &mut Problem<O>,
876+
x: &Array2<f64>,
877+
) -> Array2<f64> {
878+
let problem = pb.take_problem().unwrap();
879+
let fcstrs = problem.fn_constraints();
880+
881+
let mut unused = InfillObjData::default();
882+
883+
let mut res = Array2::zeros((x.nrows(), fcstrs.len()));
884+
Zip::from(res.rows_mut())
885+
.and(x.rows())
886+
.for_each(|mut r, xi| {
887+
let cstr_vals = &fcstrs
888+
.iter()
889+
.map(|cstr| {
890+
let xuser = if self.config.discrete() {
891+
let xary = xi.to_owned().insert_axis(Axis(0));
892+
// We have to cast x to folded space as EgorSolver
893+
// works internally in the continuous space while
894+
// the constraint function expects discrete variable in folded space
895+
to_discrete_space(&self.config.xtypes, &xary)
896+
.row(0)
897+
.into_owned();
898+
&xary.into_iter().collect::<Vec<_>>()
899+
} else {
900+
xi.as_slice().unwrap()
901+
};
902+
cstr(xuser, None, &mut unused)
903+
})
904+
.collect::<Array1<_>>();
905+
r.assign(cstr_vals)
906+
});
907+
pb.problem = Some(problem);
908+
res
909+
}
832910
}

crates/ego/src/solver/egor_service.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,7 @@ impl<SB: SurrogateBuilder + DeserializeOwned, C: CstrFn> EgorServiceApi<SB, C> {
138138
) -> Array2<f64> {
139139
let xtypes = &self.solver.config.xtypes;
140140
let x_data = to_continuous_space(xtypes, x_data);
141-
// TODO: cstr_funcs not managed
142-
let x = self.solver.suggest(&x_data, y_data, &[]);
141+
let x = self.solver.suggest(&x_data, y_data);
143142
to_discrete_space(xtypes, &x).to_owned()
144143
}
145144
}

crates/ego/src/solver/egor_solver.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,8 +233,10 @@ where
233233
let theta_inits = vec![None; self.config.n_cstr + 1];
234234
let no_point_added_retries = MAX_POINT_ADDITION_RETRY;
235235

236+
let c_data = self.eval_fcstrs(problem, &x_data);
237+
236238
let mut initial_state = state
237-
.data((x_data, y_data.clone()))
239+
.data((x_data, y_data.clone(), c_data))
238240
.clusterings(clusterings)
239241
.theta_inits(theta_inits)
240242
.sampling(sampling);
@@ -274,7 +276,7 @@ where
274276
} else {
275277
self.ego_iteration(fobj, state)?
276278
};
277-
let (x_data, y_data) = res.0.data.clone().unwrap();
279+
let (x_data, y_data, _c_data) = res.0.data.clone().unwrap();
278280

279281
if self.config.outdir.is_some() {
280282
let doe = concatenate![Axis(1), x_data, y_data];
@@ -340,7 +342,7 @@ where
340342
state: EgorState<f64>,
341343
) -> std::result::Result<(EgorState<f64>, Option<KV>), argmin::core::Error> {
342344
let rho = |sigma| sigma * sigma;
343-
let (_, y_data) = state.data.as_ref().unwrap(); // initialized in init
345+
let (_, y_data, _) = state.data.as_ref().unwrap(); // initialized in init
344346
let best = state.best_index.unwrap(); // initialized in init
345347
let prev_best = state.prev_best_index.unwrap(); // initialized in init
346348

crates/ego/src/solver/egor_state.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ pub struct EgorState<F: Float> {
7676
/// ThetaTunings controlled by n_optmod configuration triggering
7777
/// GP surrogate models hyperparameters optimization or reusing previous ones
7878
pub theta_inits: Option<Vec<Option<Array2<F>>>>,
79-
/// Historic data (params, objective and constraints)
80-
pub data: Option<(Array2<F>, Array2<F>)>,
79+
/// Historic data (params, objective and constraints values, function constraints)
80+
pub data: Option<(Array2<F>, Array2<F>, Array2<F>)>,
8181
/// Previous index of best result in data
8282
pub prev_best_index: Option<usize>,
8383
/// index of best result in data
@@ -216,15 +216,17 @@ where
216216

217217
/// Set the current data points as training points for the surrogate models
218218
/// These points are gradually selected by the EGO algorithm regarding an infill criterion.
219-
/// Data is expressed as a couple (xdata, ydata) where xdata is a (p, nx matrix)
220-
/// and ydata is a (p, 1 + nb of cstr) matrix and ydata_i = fcost(xdata_i) for i in [1, p].
221-
pub fn data(mut self, data: (Array2<F>, Array2<F>)) -> Self {
219+
/// Data is expressed as a triple (xdata, ydata, cdata) where :
220+
/// * xdata is a (p, nx matrix),
221+
/// * ydata is a (p, 1 + nb of cstr) matrix and ydata_i = fcost(xdata_i) for i in [1, p],
222+
/// * cdata is a (p, nb of fcstr) matrix and cdata_i = fcstr_j(xdata_i) for i in [1, p], j in [1, nb of fcstr]
223+
pub fn data(mut self, data: (Array2<F>, Array2<F>, Array2<F>)) -> Self {
222224
self.data = Some(data);
223225
self
224226
}
225227

226228
/// Moves the current data out and replaces it internally with `None`.
227-
pub fn take_data(&mut self) -> Option<(Array2<F>, Array2<F>)> {
229+
pub fn take_data(&mut self) -> Option<(Array2<F>, Array2<F>, Array2<F>)> {
228230
self.data.take()
229231
}
230232

@@ -390,7 +392,7 @@ where
390392
/// let mut state: EgorState<f64> = EgorState::new();
391393
///
392394
/// // Simulating a new, better parameter vector
393-
/// let mut state = state.data((array![[1.0f64], [2.0f64], [3.0]], array![[10.0], [5.0], [0.5]]));
395+
/// let mut state = state.data((array![[1.0f64], [2.0f64], [3.0]], array![[10.0], [5.0], [0.5]], array![[], [], []]));
394396
/// state.iter = 2;
395397
/// state.prev_best_index = Some(0);
396398
/// state.best_index = Some(2);
@@ -406,9 +408,10 @@ where
406408
/// assert!(state.is_best());
407409
/// ```
408410
fn update(&mut self) {
409-
if let Some((x_data, y_data)) = self.data.as_ref() {
411+
if let Some((x_data, y_data, _c_data)) = self.data.as_ref() {
410412
let best_index = self
411413
.best_index
414+
// TODO: use cdata in find_best_result_index
412415
.unwrap_or_else(|| find_best_result_index(y_data, &self.cstr_tol));
413416

414417
let param = x_data.row(best_index).to_owned();

0 commit comments

Comments
 (0)