feat(core): add data types and Rng alias
Plain-data structs and the seeded Rng alias from spec §7. Each lives in its own file under src/core/ with unit tests: - Direction, Objective, ObjectiveSpace (with as_minimization negating only Maximize axes) - Evaluation (is_feasible == constraint_violation <= 0.0) - Candidate<D>, Population<D> (concrete, public fields, From<Vec<...>>) - OptimizationResult<D> - type Rng = rand::rngs::StdRng + rng_from_seed, so no public trait is generic over the RNG (spec §2.5) All public types behind #[cfg_attr(feature = "serde", derive(...))] so the optional feature wires up without changing the default surface.
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
//! Objective directions, named objectives, and the objective space.
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Whether an objective should be minimized or maximized.
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Direction {
|
||||
/// Smaller objective values are better.
|
||||
Minimize,
|
||||
/// Larger objective values are better.
|
||||
Maximize,
|
||||
}
|
||||
|
||||
/// A named objective and its optimization direction.
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Objective {
|
||||
/// Human-readable name of the objective.
|
||||
pub name: String,
|
||||
/// Whether to minimize or maximize.
|
||||
pub direction: Direction,
|
||||
}
|
||||
|
||||
impl Objective {
|
||||
/// Create a minimize objective with the given name.
|
||||
pub fn minimize(name: impl Into<String>) -> Self {
|
||||
Self { name: name.into(), direction: Direction::Minimize }
|
||||
}
|
||||
|
||||
/// Create a maximize objective with the given name.
|
||||
pub fn maximize(name: impl Into<String>) -> Self {
|
||||
Self { name: name.into(), direction: Direction::Maximize }
|
||||
}
|
||||
}
|
||||
|
||||
/// The collection of objectives that define a problem's objective space.
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ObjectiveSpace {
|
||||
/// Objectives in declaration order.
|
||||
pub objectives: Vec<Objective>,
|
||||
}
|
||||
|
||||
impl ObjectiveSpace {
|
||||
/// Build an objective space from the given objectives.
|
||||
pub fn new(objectives: Vec<Objective>) -> Self {
|
||||
Self { objectives }
|
||||
}
|
||||
|
||||
/// Number of objectives.
|
||||
pub fn len(&self) -> usize {
|
||||
self.objectives.len()
|
||||
}
|
||||
|
||||
/// Returns `true` if there are zero objectives.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.objectives.is_empty()
|
||||
}
|
||||
|
||||
/// Returns `true` if there is exactly one objective.
|
||||
pub fn is_single_objective(&self) -> bool {
|
||||
self.objectives.len() == 1
|
||||
}
|
||||
|
||||
/// Returns `true` if there are two or more objectives.
|
||||
pub fn is_multi_objective(&self) -> bool {
|
||||
self.objectives.len() >= 2
|
||||
}
|
||||
|
||||
/// Convert objective values into minimization orientation.
|
||||
///
|
||||
/// Minimize objectives are returned unchanged; Maximize objectives are
|
||||
/// negated. In v1, this zips to the shorter of the two lengths.
|
||||
pub fn as_minimization(&self, values: &[f64]) -> Vec<f64> {
|
||||
debug_assert_eq!(
|
||||
values.len(),
|
||||
self.objectives.len(),
|
||||
"objective value count must match ObjectiveSpace length",
|
||||
);
|
||||
self.objectives
|
||||
.iter()
|
||||
.zip(values.iter())
|
||||
.map(|(obj, &v)| match obj.direction {
|
||||
Direction::Minimize => v,
|
||||
Direction::Maximize => -v,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn minimize_constructor_sets_direction() {
|
||||
let o = Objective::minimize("cost");
|
||||
assert_eq!(o.name, "cost");
|
||||
assert_eq!(o.direction, Direction::Minimize);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn maximize_constructor_sets_direction() {
|
||||
let o = Objective::maximize("accuracy");
|
||||
assert_eq!(o.name, "accuracy");
|
||||
assert_eq!(o.direction, Direction::Maximize);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_minimization_negates_maximize_only() {
|
||||
let space = ObjectiveSpace::new(vec![
|
||||
Objective::minimize("cost"),
|
||||
Objective::maximize("accuracy"),
|
||||
]);
|
||||
assert_eq!(space.as_minimization(&[10.0, 0.8]), vec![10.0, -0.8]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lengths_and_predicates() {
|
||||
let single = ObjectiveSpace::new(vec![Objective::minimize("f")]);
|
||||
assert!(single.is_single_objective());
|
||||
assert!(!single.is_multi_objective());
|
||||
assert!(!single.is_empty());
|
||||
assert_eq!(single.len(), 1);
|
||||
|
||||
let multi = ObjectiveSpace::new(vec![
|
||||
Objective::minimize("f1"),
|
||||
Objective::minimize("f2"),
|
||||
]);
|
||||
assert!(multi.is_multi_objective());
|
||||
assert!(!multi.is_single_objective());
|
||||
|
||||
let empty = ObjectiveSpace::new(Vec::new());
|
||||
assert!(empty.is_empty());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user