tantale_core/solution.rs
1//! # Solution
2//!
3//! This module defines the [`Solution`] trait and related types representing candidate solutions
4//! in the optimization process. Solutions are points sampled from a [`Searchspace`](crate::Searchspace)
5//! and form the fundamental data structure manipulated by optimizers and objective functions.
6//!
7//! ## Core Concepts
8//!
9//! ### Dual Domain Solutions
10//!
11//! Following Tantale's dual domain architecture, there are two types of solutions:
12//! - **Obj solutions**: In the objective function's domain
13//! - **Opt solutions**: In the optimizer's domain
14//!
15//! [`Twin`](Solution::twin) [`Solution`] are wrapped within a [`SolutionShape`] to maintain the relationship and bounds between both
16//! generated `Obj` and `Opt` [`Solution`]s.
17//!
18//! Each solution is statically typed by its corresponding [`Domain`].
19//!
20//! ### Solution Components
21//!
22//! A [`Solution`] consists of three essential components:
23//! 1. **Unique [`Id`]**: Identifies the solution and links [`twin`](Solution::twin) representations across domains
24//! 2. **[`Raw`](Solution::Raw) data**: The actual variable values in the domain's native type
25//! 3. **[`SolInfo`]**: Metadata associated with the solution (e.g., iteration info)
26//!
27//! ### Twin Solutions
28//!
29//! Solutions in different domains that represent the same point share an [`Id`], making them "twins":
30//! ```ignore
31//! let obj_solution = sp.sample_obj(&mut rng, info.clone());
32//! let paired = sp.onto_opt(obj_solution); // Creates twin in Opt domain
33//! assert_eq!(paired.get_obj().id(), paired.get_opt().id());
34//! ```
35//!
36//! ## Solution States
37//!
38//! Solutions progress through different states during optimization:
39//!
40//! - **[`Uncomputed`]**: Generated but not yet evaluated
41//! - **[`Computed`]**: Evaluated with an associated [`Codomain`] value
42//!
43//! The [`IntoComputed`] trait enables conversion from uncomputed to computed states.
44//!
45//! ## Multi-Fidelity Support
46//!
47//! For multi-fidelity optimization, solutions can track:
48//! - **[`Step`](crate::Step)** progress via [`HasStep`](crate::HasStep)
49//! - **[`Fidelity`]** level via [`HasFidelity`](crate::HasFidelity)
50//!
51//! This enables progressive evaluation and resource allocation strategies.
52//!
53//! ## Structured solutions
54//!
55//! Solutions can be arranged in different way:
56//! - [`Lone`]: Single-domain solution
57//! - [`Pair`]: Paired Obj/Opt solutions
58//! - [`Batch`]: Collections of [`SolutionShape`]
59//!
60//! ## Creation and Usage
61//!
62//! Solutions are typically created through [`Searchspace`](crate::Searchspace) methods:
63//! ```ignore
64//! // Sampling creates Uncomputed solutions
65//! let solution = sp.sample_opt(&mut rng, info);
66//!
67//! // Evaluation converts to Computed
68//! let outcome = objective_fn(solution.get_x());
69//! let computed = solution.into_computed(Arc::new(outcome));
70//! ```
71//!
72//! ## See Also
73//!
74//! - [`Searchspace`](crate::Searchspace) - Creates and manages solutions
75//! - [`Domain`] - Defines solution value types
76//! - [`Codomain`] - Defines evaluation result types
77//! - [`Id`] - Solution identifier types
78//! - [`BaseSol`] - Concrete solution implementations
79//! - [`Computed`] - Evaluated solution wrappers
80//!
81
82use crate::{
83 HasId, HasSolInfo, HasY, Outcome,
84 domain::{Domain, codomain::TypeCodom},
85 has_trait::HasX,
86};
87
88use serde::{Deserialize, Serialize};
89use std::{fmt::Debug, sync::Arc};
90
91/// Metadata associated with a solution during optimization.
92///
93/// [`SolInfo`] provides a way to attach auxiliary information to solutions, such as iteration
94/// numbers, timestamps, or optimizer-specific data. This information persists across checkpointing
95/// and is available to recorders.
96///
97/// # Associated Derive Macro
98///
99/// The `SolInfo` derive macro automatically implements the trait for any struct
100/// satisfying the required trait bounds.
101pub trait SolInfo: Debug + Serialize + for<'a> Deserialize<'a> {}
102
103/// Core trait representing a candidate solution in the optimization process.
104///
105/// [`Solution`] is the fundamental abstraction for points in the search space. It combines
106/// a unique identifier ([`Id`]), variable values ([`Raw`](Solution::Raw)), and metadata
107/// ([`SolInfo`]), with full serialization support for checkpointing.
108///
109/// # Associated Types
110///
111/// ## [`Raw`](Solution::Raw)
112///
113/// The native representation of variable values in this domain. The type depends on the [`Domain`]:
114/// - [`Real`](crate::Real): `f64`
115/// - [`Nat`](crate::Nat): `i64`
116/// - [`Cat`](crate::Cat): `String``
117/// - [`Mixed`](crate::Mixed): [`MixedTypeDom`](crate::MixedTypeDom) (heterogeneous values)
118///
119/// ## [`Twin`](Solution::Twin)
120///
121/// The solution type in an alternative domain `B`. Twin solutions share the same [`Id`]
122/// but have different [`Raw`](Solution::Raw) representations, enabling domain transformations while
123/// maintaining solution identity.
124///
125/// # Concrete Implementations
126///
127/// - [`BaseSol`] - Standard solution implementation
128/// - [`FidelitySol`] - With fidelity support
129/// - [`Computed`] - Wrapper for evaluated solutions
130pub trait Solution<SolId, Dom, SInfo>
131where
132 Self: Sized
133 + Debug
134 + HasX<Self::Raw>
135 + HasId<SolId>
136 + HasSolInfo<SInfo>
137 + Serialize
138 + for<'de> Deserialize<'de>,
139 SolId: Id,
140 SInfo: SolInfo,
141 Dom: Domain,
142{
143 /// The native type representing variable values in this domain.
144 ///
145 /// This type is determined by the [TypeDom](Domain::TypeDom) from the solution's [`Domain`].
146 type Raw: Clone + Debug + Serialize + for<'de> Deserialize<'de>;
147
148 /// Twin solutions share the same [`Id`] but exist in different domains,
149 /// enabling transformations between Obj and Opt representations.
150 type Twin<B: Domain>: Solution<SolId, B, SInfo, Twin<Dom> = Self>;
151
152 /// Creates a twin solution in a different domain with the same [`Id`].
153 ///
154 /// This method constructs a solution in domain `B` that shares the same identifier
155 /// as `self`, establishing a twin relationship. This is used internally by
156 /// [`Searchspace`](crate::Searchspace) when mapping between Obj and Opt domains.
157 ///
158 /// Twins are usually wrapped within a [`SolutionShape`] to facilitate access and manipulation of both representation.
159 fn twin<B: Domain>(
160 &self,
161 x: <Self::Twin<B> as Solution<SolId, B, SInfo>>::Raw,
162 ) -> Self::Twin<B>;
163
164 /// Creates a clone of this solution with the same values, metadata, and [`Id`], used
165 /// for [`Accumulator`](crate::domain::codomain::Accumulator).
166 fn _clone_sol(&self) -> Self;
167}
168
169/// Trait for solutions that have not yet been evaluated.
170///
171/// [`Uncomputed`] represents solutions generated by sampling or optimization but not yet
172/// evaluated by the objective function. These solutions can be converted to [`Computed`]
173/// solutions via the [`IntoComputed`] trait and a [`TypeCodom`](Codomain::TypeCodom).
174///
175/// # Type Parameters
176///
177/// * `SolId` - The unique identifier type
178/// * `Dom` - The domain defining variable types and constraints
179/// * `SInfo` - The solution metadata type
180///
181/// # Lifecycle
182///
183/// ```text
184/// Uncomputed --[evaluate]-> Computed
185/// ^ |
186/// | |
187/// TypeCodom <--+------[extract]--------+
188/// ```
189///
190/// # Builder methods
191///
192/// [`Uncomputed`] provides multiple ways to create solutions:
193/// - [`new`](Uncomputed::new) - From explicit values
194/// - [`default`](Uncomputed::default) - Placeholder with zero/default values
195/// - [`default_vec`](Uncomputed::default_vec) - Batch of placeholders
196pub trait Uncomputed<SolId, Dom, SInfo>
197where
198 Self: Solution<SolId, Dom, SInfo> + IntoComputed,
199 SolId: Id,
200 Dom: Domain,
201 SInfo: SolInfo,
202{
203 /// The uncomputed twin type in an alternative domain.
204 ///
205 /// Unlike [`Twin`](Solution::Twin), this always produces an [`Uncomputed`] solution,
206 /// preserving the uncomputed status across domain transformations.
207 type TwinUC<B: Domain>: Uncomputed<SolId, B, SInfo, Twin<Dom> = Self, TwinUC<Dom> = Self>;
208
209 /// Creates an uncomputed twin solution in a different domain.
210 ///
211 /// # Type Parameters
212 ///
213 /// * `B` - The target domain
214 ///
215 /// # Parameters
216 ///
217 /// * `x` - Variable values in the target domain
218 ///
219 /// # Returns
220 ///
221 /// An uncomputed solution in domain `B` with the same [`Id`] as `self`.
222 fn twin<B: Domain>(
223 &self,
224 x: <Self::TwinUC<B> as Solution<SolId, B, SInfo>>::Raw,
225 ) -> Self::TwinUC<B>;
226
227 /// Creates a new uncomputed solution with specified values.
228 ///
229 /// # Parameters
230 ///
231 /// * `id` - Unique identifier for this solution
232 /// * `x` - Variable values (convertible to [`Raw`](Solution::Raw))
233 /// * `info` - Solution metadata
234 ///
235 /// # Returns
236 ///
237 /// A new uncomputed solution.
238 fn new<T>(id: SolId, x: T, info: Arc<SInfo>) -> Self
239 where
240 T: Into<Self::Raw>;
241
242 /// Creates a placeholder uncomputed solution with default/zero values.
243 ///
244 /// Useful for initializing data structures before filling them with actual solutions.
245 ///
246 /// # Parameters
247 ///
248 /// * `info` - Solution metadata
249 /// * `size` - Number of variables (dimension)
250 ///
251 /// # Returns
252 ///
253 /// An uncomputed solution with default values.
254 fn default(info: Arc<SInfo>, size: usize) -> Self;
255
256 /// Creates a vector of placeholder uncomputed solutions.
257 ///
258 /// Batch version of [`default`](Uncomputed::default) for initializing collections.
259 ///
260 /// # Parameters
261 ///
262 /// * `info` - Shared solution metadata
263 /// * `size` - Number of variables per solution (dimension)
264 /// * `vsize` - Number of solutions to create
265 ///
266 /// # Returns
267 ///
268 /// A vector of `vsize` uncomputed solutions, each with `size` default values.
269 fn default_vec(info: Arc<SInfo>, size: usize, vsize: usize) -> Vec<Self>;
270}
271
272/// Trait for converting uncomputed solutions to computed solutions with objective values.
273///
274/// [`IntoComputed`] enables the transformation from unevaluated solutions to evaluated ones
275/// by associating an objective function output ([`TypeCodom`](Codomain::TypeCodom)). This trait
276/// also provides the reverse operation to extract the original solution and its value.
277///
278/// # Usage
279///
280/// ```text
281/// Uncomputed --[into_computed]--> Computed
282/// |
283/// v
284/// (Uncomputed, Y) --[extract]
285/// ```
286///
287/// # Type-Level Design
288///
289/// The [`Computed`](IntoComputed::Computed) associated type is generic over the codomain,
290/// allowing a single uncomputed solution to be converted into computed forms with different
291/// outcome types (single-objective, multi-objective, constrained, etc.).
292pub trait IntoComputed: Sized {
293 /// The computed type wrapping `Self` with an objective value.
294 ///
295 /// This type must implement [`HasY`] to provide access to the objective function output.
296 type Computed<Out: Outcome>: HasY<Out> + Serialize + for<'a> Deserialize<'a> + Debug;
297
298 /// Converts this uncomputed solution into a computed one with an objective value.
299 ///
300 /// # Type Parameters
301 ///
302 /// * `Cod` - The [`Codomain`] defining the output structure
303 /// * `Out` - The outcome type from the objective function
304 ///
305 /// # Parameters
306 ///
307 /// * `y` - The objective function output to associate with this solution
308 ///
309 /// # Returns
310 ///
311 /// A computed solution containing both `self` and `y`.
312 fn into_computed<Out: Outcome>(self, y: Arc<TypeCodom<Out>>) -> Self::Computed<Out>;
313
314 /// Extracts the uncomputed solution and objective value from a computed solution.
315 ///
316 /// This is the inverse operation of [`into_computed`](IntoComputed::into_computed),
317 /// decomposing a computed solution back into its constituent parts.
318 ///
319 /// # Type Parameters
320 ///
321 /// * `Cod` - The [`Codomain`] of the objective value
322 /// * `Out` - The [`Outcome`] type
323 ///
324 /// # Parameters
325 ///
326 /// * `comp` - The computed solution to decompose
327 ///
328 /// # Returns
329 ///
330 /// A tuple of `(uncomputed_solution, objective_value)`.
331 fn extract<Out: Outcome>(comp: Self::Computed<Out>) -> (Self, Arc<TypeCodom<Out>>);
332}
333
334pub trait IntoComputedShape<SolId, SInfo>: SolutionShape<SolId, SInfo>
335where
336 SolId: Id,
337 SInfo: SolInfo,
338 Self::SolObj: Uncomputed<SolId, Self::Obj, SInfo> + IntoComputed,
339 Self::SolOpt: Uncomputed<SolId, Self::Opt, SInfo> + IntoComputed,
340{
341 /// The computed type wrapping `Self` with an objective value.
342 ///
343 /// This type must implement [`HasY`] to provide access to the objective function output.
344 type Computed<Out: Outcome>: SolutionShape<
345 SolId,
346 SInfo,
347 Obj = Self::Obj,
348 Opt = Self::Opt,
349 SolObj = Computed<Self::SolObj, SolId, Self::Obj, Out, SInfo>,
350 SolOpt = Computed<Self::SolOpt, SolId, Self::Opt, Out, SInfo>,
351 > + HasY<Out>
352 + Serialize
353 + for<'a> Deserialize<'a>
354 + Debug;
355
356 /// Converts this uncomputed solution into a computed one with an objective value.
357 ///
358 /// # Type Parameters
359 ///
360 /// * `Cod` - The [`Codomain`] defining the output structure
361 /// * `Out` - The outcome type from the objective function
362 ///
363 /// # Parameters
364 ///
365 /// * `y` - The objective function output to associate with this solution
366 ///
367 /// # Returns
368 ///
369 /// A computed solution containing both `self` and `y`.
370 fn into_computed<Out: Outcome>(self, y: Arc<TypeCodom<Out>>) -> Self::Computed<Out>;
371
372 /// Extracts the uncomputed solution and objective value from a computed solution.
373 ///
374 /// This is the inverse operation of [`into_computed`](IntoComputed::into_computed),
375 /// decomposing a computed solution back into its constituent parts.
376 ///
377 /// # Type Parameters
378 ///
379 /// * `Cod` - The [`Codomain`] of the objective value
380 /// * `Out` - The [`Outcome`] type
381 ///
382 /// # Parameters
383 ///
384 /// * `comp` - The computed solution to decompose
385 ///
386 /// # Returns
387 ///
388 /// A tuple of `(uncomputed_solution, objective_value)`.
389 fn extract<Out: Outcome>(comp: Self::Computed<Out>) -> (Self, Arc<TypeCodom<Out>>);
390}
391
392/// Type alias for the twin solution type in an alternative domain.
393///
394/// [`SolTwin`] extracts the [`Twin`](Solution::Twin) associated type from a solution,
395/// providing a convenient way to refer to the solution type in domain `B` that is
396/// twin to a solution of type `S` in domain `A`.
397pub type SolTwin<S, SolId, A, B, SInfo> = <S as Solution<SolId, A, SInfo>>::Twin<B>;
398
399/// Type alias for the raw value type of a solution.
400///
401/// [`SolRaw`] extracts the [`Raw`](Solution::Raw) associated type from a solution,
402/// providing the native type used to represent variable values in the given domain.
403pub type SolRaw<S, SolId, Dom, Info> = <S as Solution<SolId, Dom, Info>>::Raw;
404
405pub mod id;
406pub use id::{Id, ParSId, SId, StepId, StepSId};
407
408pub mod partial;
409pub use partial::{BaseSol, Fidelity, FidelitySol};
410
411pub mod computed;
412pub use computed::{Computed, Xy};
413
414pub mod batchtype;
415pub use batchtype::{Batch, OutBatch};
416
417pub mod shape;
418pub use shape::{CompLone, Lone, Pair, SolutionShape};