Skip to main content

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};