Skip to main content

genoxide/gp/
mutations.rs

1//! Point, hoist, shrink and constant mutation, and a mix of tree mutations.
2
3use super::operators::SubtreeMutation;
4use super::primitives::{Constants, PrimitiveSet};
5use super::representation::Gp;
6use super::tree::{Node, Tree, subtree_end};
7use crate::operator::Mutate;
8use crate::operator::mutate::{Mode, changed_gene, reflect};
9use crate::{Error, Result, StreamRng};
10use std::fmt::Debug;
11
12/// Point mutation (Poli, Langdon and McPhee 2008, sec. 5.2.2): nodes replaced by other
13/// primitives of the same signature, each node with a probability, or `n` nodes.
14///
15/// A function is replaced by another function with the same argument types and return type, so
16/// its children stay; a leaf by another terminal of its type or, if its type has
17/// [`Constants`], a new constant (a constant by one of a different value). Nodes with no
18/// replacement are never picked, so a picked node always changes, and the tree keeps its shape,
19/// types and limits.
20///
21/// A per-node mutation changes no node with probability `(1 − rate)^n`; in a genetic algorithm,
22/// such a child is a copy that inherits its parent's fitness without an evaluation.
23///
24/// ```
25/// use genoxide::gp::{Gp, PointMutation, PrimitiveSet};
26/// use genoxide::prelude::*;
27///
28/// #[derive(Clone, Copy, Debug)]
29/// enum Op {
30///     Add,
31///     Mul,
32///     X,
33/// }
34///
35/// let mut set = PrimitiveSet::builder();
36/// let real = set.new_type("real");
37/// set.function("add", Op::Add, [real, real], real)
38///     .function("mul", Op::Mul, [real, real], real)
39///     .terminal("x", Op::X, real);
40/// let gp = Gp::builder(set.build(real)?).build()?;
41/// let mut tree = gp.primitives().parse("add(x, mul(x, x))")?;
42/// PointMutation::count(1)?.mutate(&gp, &mut tree, &mut StreamRng::seed_from_u64(1));
43/// // `x` has no replacement: one of the functions changed
44/// let text = tree.display(gp.primitives()).to_string();
45/// assert!(text == "mul(x, mul(x, x))" || text == "add(x, add(x, x))");
46/// # Ok::<(), genoxide::Error>(())
47/// ```
48#[derive(Clone, Copy, Debug, PartialEq)]
49#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
50pub struct PointMutation {
51    mode: Mode,
52}
53
54impl PointMutation {
55    /// Replaces each node that has a replacement independently with probability `rate` (greater
56    /// than 0 and at most 1).
57    ///
58    /// # Errors
59    ///
60    /// [`Error::InvalidSetting`] (setting `point_mutation_rate`) for a rate outside (0, 1].
61    pub fn per_node(rate: f64) -> Result<Self> {
62        Ok(Self {
63            mode: Mode::per_gene("point_mutation_rate", rate)?,
64        })
65    }
66
67    /// Replaces `count` distinct random nodes that have a replacement, `count` at least 1; all of
68    /// them if there are fewer.
69    ///
70    /// # Errors
71    ///
72    /// [`Error::InvalidSetting`] (setting `point_mutation_count`) for a count of 0.
73    pub fn count(count: usize) -> Result<Self> {
74        Ok(Self {
75            mode: Mode::count("point_mutation_count", count)?,
76        })
77    }
78
79    // mutates the tree; false if no node has a replacement (then the tree is unchanged)
80    pub(crate) fn apply<P: Copy>(&self, gp: &Gp<P>, tree: &mut Tree, rng: &mut StreamRng) -> bool {
81        let set = gp.primitives();
82        let candidates: Vec<usize> = tree
83            .nodes()
84            .iter()
85            .enumerate()
86            .filter(|(_, node)| replacements(set, node) > 0)
87            .map(|(position, _)| position)
88            .collect();
89        if candidates.is_empty() {
90            return false;
91        }
92        let nodes = tree.nodes_mut();
93        self.mode.apply(
94            candidates.len(),
95            |candidate| candidates[candidate],
96            rng,
97            |position, rng| nodes[position] = replace(set, nodes[position], rng),
98        );
99        true
100    }
101}
102
103impl<P: Copy + Debug + Send + Sync> Mutate<Gp<P>> for PointMutation {
104    fn mutate(&self, gp: &Gp<P>, tree: &mut Tree, rng: &mut StreamRng) {
105        self.apply(gp, tree, rng);
106    }
107}
108
109// the number of nodes that can replace `node`: the other primitives of its signature, and for a
110// leaf of a type with constants, a new constant (of another value, for a constant)
111fn replacements<P: Copy>(set: &PrimitiveSet<P>, node: &Node) -> usize {
112    match *node {
113        Node::Primitive(index) => {
114            let peers = set.same_signature(index).len() - 1;
115            let constant = set.arity(index) == 0 && set.constants(set.node_type(node)).is_some();
116            peers + usize::from(constant)
117        }
118        Node::Constant { ty, value } => {
119            let constant = set.constants(ty).is_some_and(|c| c.has_other(value));
120            set.terminals_of(ty).len() + usize::from(constant)
121        }
122    }
123}
124
125// one of the replacements of `node`, uniformly
126fn replace<P: Copy>(set: &PrimitiveSet<P>, node: Node, rng: &mut StreamRng) -> Node {
127    let choice = rng.below(replacements(set, &node));
128    let ty = set.node_type(&node);
129    let constants = || set.constants(ty).expect("constants");
130    match node {
131        Node::Primitive(index) => {
132            let peers = set.same_signature(index);
133            if choice + 1 < peers.len() {
134                // the peers other than `index`, in order
135                let own = peers.binary_search(&index).expect("its own signature");
136                Node::Primitive(peers[choice + usize::from(choice >= own)])
137            } else {
138                Node::Constant {
139                    ty,
140                    value: constants().sample(rng),
141                }
142            }
143        }
144        Node::Constant { value, .. } => {
145            let terminals = set.terminals_of(ty);
146            if choice < terminals.len() {
147                Node::Primitive(terminals[choice])
148            } else {
149                Node::Constant {
150                    ty,
151                    value: constants().other(value, rng),
152                }
153            }
154        }
155    }
156}
157
158/// Hoist mutation (Kinnear 1993; Poli, Langdon and McPhee 2008, sec. 5.2.2): the tree is
159/// replaced by the subtree of one of its function nodes, chosen uniformly among those of the
160/// root's type other than the root.
161///
162/// The child is always smaller than its parent, which makes hoist a move against bloat. A tree
163/// without such a function node (a single node, or a function of leaves) is left unchanged.
164///
165/// ```
166/// use genoxide::gp::{Gp, HoistMutation, PrimitiveSet};
167/// use genoxide::prelude::*;
168///
169/// #[derive(Clone, Copy, Debug)]
170/// enum Op {
171///     Add,
172///     X,
173/// }
174///
175/// let mut set = PrimitiveSet::builder();
176/// let real = set.new_type("real");
177/// set.function("add", Op::Add, [real, real], real).terminal("x", Op::X, real);
178/// let gp = Gp::builder(set.build(real)?).build()?;
179/// let mut tree = gp.primitives().parse("add(x, add(x, x))")?;
180/// HoistMutation.mutate(&gp, &mut tree, &mut StreamRng::seed_from_u64(1));
181/// assert_eq!(tree, gp.primitives().parse("add(x, x)")?); // the only function below the root
182/// # Ok::<(), genoxide::Error>(())
183/// ```
184#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
185#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
186pub struct HoistMutation;
187
188impl HoistMutation {
189    // mutates the tree; false if it has no function node of the root's type below the root (then
190    // unchanged)
191    pub(crate) fn apply<P: Copy>(&self, gp: &Gp<P>, tree: &mut Tree, rng: &mut StreamRng) -> bool {
192        let set = gp.primitives();
193        let Some(root) = tree.nodes().first() else {
194            return false;
195        };
196        let ty = set.node_type(root);
197        let hoistable = |node: &Node| set.node_arity(node) > 0 && set.node_type(node) == ty;
198        let count = tree.nodes()[1..].iter().filter(|n| hoistable(n)).count();
199        if count == 0 {
200            return false;
201        }
202        let k = rng.below(count);
203        let (start, _) = tree.nodes()[1..]
204            .iter()
205            .enumerate()
206            .filter(|(_, n)| hoistable(n))
207            .nth(k)
208            .expect("a subtree");
209        let start = start + 1;
210        let end = subtree_end(set, tree.nodes(), start);
211        let nodes = tree.nodes_mut();
212        nodes.truncate(end);
213        nodes.drain(..start);
214        true
215    }
216}
217
218impl<P: Copy + Debug + Send + Sync> Mutate<Gp<P>> for HoistMutation {
219    fn mutate(&self, gp: &Gp<P>, tree: &mut Tree, rng: &mut StreamRng) {
220        self.apply(gp, tree, rng);
221    }
222}
223
224/// Shrink mutation (Poli, Langdon and McPhee 2008, sec. 5.2.2, after Angeline 1996): a subtree
225/// is replaced by a random terminal of its type: one of the type's terminals, or a new constant
226/// if the type has [`Constants`], each choice with the same probability.
227///
228/// The subtree is chosen uniformly among those whose root is a function of a type with leaves,
229/// so the child is always smaller than its parent. A tree without such a subtree is left
230/// unchanged.
231///
232/// ```
233/// use genoxide::gp::{Gp, PrimitiveSet, ShrinkMutation};
234/// use genoxide::prelude::*;
235///
236/// #[derive(Clone, Copy, Debug)]
237/// enum Op {
238///     Add,
239///     X,
240/// }
241///
242/// let mut set = PrimitiveSet::builder();
243/// let real = set.new_type("real");
244/// set.function("add", Op::Add, [real, real], real).terminal("x", Op::X, real);
245/// let gp = Gp::builder(set.build(real)?).build()?;
246/// let mut tree = gp.primitives().parse("add(x, add(x, x))")?;
247/// ShrinkMutation.mutate(&gp, &mut tree, &mut StreamRng::seed_from_u64(1));
248/// let text = tree.display(gp.primitives()).to_string();
249/// assert!(text == "x" || text == "add(x, x)");
250/// # Ok::<(), genoxide::Error>(())
251/// ```
252#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
253#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
254pub struct ShrinkMutation;
255
256impl ShrinkMutation {
257    // mutates the tree; false if no function's type has leaves (then unchanged)
258    pub(crate) fn apply<P: Copy>(&self, gp: &Gp<P>, tree: &mut Tree, rng: &mut StreamRng) -> bool {
259        let set = gp.primitives();
260        let leaves = |node: &Node| {
261            let ty = set.node_type(node);
262            set.terminals_of(ty).len() + usize::from(set.constants(ty).is_some())
263        };
264        let shrinkable = |node: &Node| set.node_arity(node) > 0 && leaves(node) > 0;
265        let count = tree.nodes().iter().filter(|n| shrinkable(n)).count();
266        if count == 0 {
267            return false;
268        }
269        let k = rng.below(count);
270        let (start, node) = tree
271            .nodes()
272            .iter()
273            .enumerate()
274            .filter(|(_, n)| shrinkable(n))
275            .nth(k)
276            .expect("a subtree");
277        let ty = set.node_type(node);
278        let choice = rng.below(leaves(node));
279        let terminals = set.terminals_of(ty);
280        let leaf = if choice < terminals.len() {
281            Node::Primitive(terminals[choice])
282        } else {
283            Node::Constant {
284                ty,
285                value: set.constants(ty).expect("constants").sample(rng),
286            }
287        };
288        let end = subtree_end(set, tree.nodes(), start);
289        tree.nodes_mut().splice(start..end, [leaf]);
290        true
291    }
292}
293
294impl<P: Copy + Debug + Send + Sync> Mutate<Gp<P>> for ShrinkMutation {
295    fn mutate(&self, gp: &Gp<P>, tree: &mut Tree, rng: &mut StreamRng) {
296        self.apply(gp, tree, rng);
297    }
298}
299
300/// Constant mutation (Poli, Langdon and McPhee 2008, sec. 5.2.2, after Schoenauer et al. 1996):
301/// one constant, chosen uniformly, is perturbed by normal noise of standard deviation `sigma`
302/// times the width of its type's range, mirrored at the ends of the range. Each call changes one
303/// constant, as each change is a separate mutation there.
304///
305/// It applies to constants of [`Constants::uniform`] and [`Constants::integers`] with more than
306/// one value (integers are rounded), and of [`Constants::normal`], whose noise is `sigma` times
307/// the deviation, without ends to mirror at; the constants of a [`Constants::choice`] have no range, and
308/// [`PointMutation`] draws them anew. The constant always changes: the noise is drawn again
309/// while it leaves the value as it is (for integers, while it rounds to the same value), up to 64
310/// times, then the value is drawn uniformly from the others. A tree without such a constant is
311/// left unchanged. The cheap tuning of constants: it keeps the tree's shape and moves a constant
312/// a little.
313///
314/// ```
315/// use genoxide::gp::{Constants, ConstantMutation, Gp, PrimitiveSet};
316/// use genoxide::prelude::*;
317///
318/// #[derive(Clone, Copy, Debug)]
319/// enum Op {
320///     Mul,
321///     X,
322/// }
323///
324/// let mut set = PrimitiveSet::builder();
325/// let real = set.new_type("real");
326/// set.function("mul", Op::Mul, [real, real], real)
327///     .terminal("x", Op::X, real)
328///     .constants(real, Constants::uniform(-1.0..=1.0)?);
329/// let gp = Gp::builder(set.build(real)?).build()?;
330/// let mut tree = gp.primitives().parse("mul(x, 0.5)")?;
331/// ConstantMutation::gaussian(0.05)?.mutate(&gp, &mut tree, &mut StreamRng::seed_from_u64(1));
332/// assert_ne!(tree, gp.primitives().parse("mul(x, 0.5)")?);
333/// assert!(gp.validate(&tree).is_ok()); // still within [-1, 1]
334/// # Ok::<(), genoxide::Error>(())
335/// ```
336#[derive(Clone, Copy, Debug, PartialEq)]
337#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
338pub struct ConstantMutation {
339    sigma: f64,
340}
341
342impl ConstantMutation {
343    /// Normal noise of standard deviation `sigma` (positive and finite) times the width of the
344    /// constant's range, or the deviation of [`Constants::normal`] constants, e.g. 0.1.
345    ///
346    /// # Errors
347    ///
348    /// [`Error::InvalidSetting`] (setting `constant_sigma`) for a `sigma` that isn't positive
349    /// and finite.
350    pub fn gaussian(sigma: f64) -> Result<Self> {
351        if !(sigma > 0.0 && sigma.is_finite()) {
352            return Err(Error::InvalidSetting {
353                setting: "constant_sigma",
354                reason: format!("must be positive and finite, got {sigma}"),
355            });
356        }
357        Ok(Self { sigma })
358    }
359
360    /// The standard deviation of the noise, as a fraction of a constant's range.
361    pub fn sigma(&self) -> f64 {
362        self.sigma
363    }
364
365    // mutates the tree; false if it has no constant with a range (then unchanged)
366    pub(crate) fn apply<P: Copy>(&self, gp: &Gp<P>, tree: &mut Tree, rng: &mut StreamRng) -> bool {
367        let set = gp.primitives();
368        let ranged = |node: &Node| match *node {
369            Node::Constant { ty, .. } => {
370                matches!(
371                    set.constants(ty),
372                    Some(Constants::Uniform { low, high }) if low < high
373                ) || matches!(
374                    set.constants(ty),
375                    Some(Constants::Integers { low, high }) if low < high
376                ) || matches!(set.constants(ty), Some(Constants::Normal { .. }))
377            }
378            Node::Primitive(_) => false,
379        };
380        let count = tree.nodes().iter().filter(|n| ranged(n)).count();
381        if count == 0 {
382            return false;
383        }
384        let k = rng.below(count);
385        let (position, _) = tree
386            .nodes()
387            .iter()
388            .enumerate()
389            .filter(|(_, n)| ranged(n))
390            .nth(k)
391            .expect("a constant");
392        let Node::Constant { ty, value } = tree.nodes()[position] else {
393            unreachable!("a constant");
394        };
395        let sigma = self.sigma;
396        let value = match *set.constants(ty).expect("constants") {
397            Constants::Uniform { low, high } => {
398                let range = low..=high;
399                let scale = sigma * (high - low);
400                changed_gene(&range, value, rng, |rng| {
401                    reflect(value + scale * rng.normal(), &range)
402                })
403            }
404            Constants::Integers { low, high } => {
405                let range = low as f64..=high as f64;
406                let scale = sigma * (high - low) as f64;
407                let mut changed = None;
408                for _ in 0..64 {
409                    let proposal = reflect(value + scale * rng.normal(), &range).round();
410                    if proposal != value && range.contains(&proposal) {
411                        changed = Some(proposal);
412                        break;
413                    }
414                }
415                changed.unwrap_or_else(|| {
416                    crate::genome::integer::random_other_in(&(low..=high), value as i64, rng) as f64
417                })
418            }
419            Constants::Normal { deviation, .. } => {
420                let mut changed = None;
421                for _ in 0..64 {
422                    let proposal = value + sigma * deviation * rng.normal();
423                    if proposal != value && proposal.is_finite() {
424                        changed = Some(proposal);
425                        break;
426                    }
427                }
428                changed.unwrap_or_else(|| set.constants(ty).expect("constants").other(value, rng))
429            }
430            Constants::Choice(_) => unreachable!("a constant with a range"),
431        };
432        tree.nodes_mut()[position] = Node::Constant { ty, value };
433        true
434    }
435}
436
437impl<P: Copy + Debug + Send + Sync> Mutate<Gp<P>> for ConstantMutation {
438    fn mutate(&self, gp: &Gp<P>, tree: &mut Tree, rng: &mut StreamRng) {
439        self.apply(gp, tree, rng);
440    }
441}
442
443/// One of the tree mutations, for [`Mutations`].
444#[derive(Clone, Debug, PartialEq)]
445#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
446pub enum TreeMutation {
447    /// [`SubtreeMutation`].
448    Subtree(SubtreeMutation),
449    /// [`PointMutation`].
450    Point(PointMutation),
451    /// [`HoistMutation`].
452    Hoist(HoistMutation),
453    /// [`ShrinkMutation`].
454    Shrink(ShrinkMutation),
455    /// [`ConstantMutation`].
456    Constant(ConstantMutation),
457}
458
459impl TreeMutation {
460    // mutates the tree; false if this mutation can't change it (then unchanged)
461    fn apply<P: Copy>(&self, gp: &Gp<P>, tree: &mut Tree, rng: &mut StreamRng) -> bool {
462        match self {
463            TreeMutation::Subtree(mutation) => mutation.apply(gp, tree, rng),
464            TreeMutation::Point(mutation) => mutation.apply(gp, tree, rng),
465            TreeMutation::Hoist(mutation) => mutation.apply(gp, tree, rng),
466            TreeMutation::Shrink(mutation) => mutation.apply(gp, tree, rng),
467            TreeMutation::Constant(mutation) => mutation.apply(gp, tree, rng),
468        }
469    }
470}
471
472impl<P: Copy + Debug + Send + Sync> Mutate<Gp<P>> for TreeMutation {
473    fn mutate(&self, gp: &Gp<P>, tree: &mut Tree, rng: &mut StreamRng) {
474        self.apply(gp, tree, rng);
475    }
476}
477
478macro_rules! tree_mutation {
479    ($($variant:ident($mutation:ty)),*) => {
480        $(
481            impl From<$mutation> for TreeMutation {
482                fn from(mutation: $mutation) -> Self {
483                    TreeMutation::$variant(mutation)
484                }
485            }
486        )*
487    };
488}
489
490tree_mutation!(
491    Subtree(SubtreeMutation),
492    Point(PointMutation),
493    Hoist(HoistMutation),
494    Shrink(ShrinkMutation),
495    Constant(ConstantMutation)
496);
497
498/// A mix of tree mutations: each call applies one of them, chosen by weight.
499///
500/// If the chosen mutation can't change the tree (hoist of a single node, constant mutation of a
501/// tree without constants), another is chosen by weight among the rest, so a tree changes
502/// whenever one of the mutations can change it. The weights needn't add up to 1.
503///
504/// ```
505/// use genoxide::gp::{Gp, Mutations, PrimitiveSet, SubtreeCrossover};
506/// use genoxide::prelude::*;
507///
508/// #[derive(Clone, Copy, Debug)]
509/// enum Op {
510///     Add,
511///     Mul,
512///     X,
513/// }
514///
515/// let mut set = PrimitiveSet::builder();
516/// let real = set.new_type("real");
517/// set.function("add", Op::Add, [real, real], real)
518///     .function("mul", Op::Mul, [real, real], real)
519///     .terminal("x", Op::X, real);
520/// let gp = Gp::builder(set.build(real)?).build()?;
521/// let ga = Ga::builder(gp)
522///     .population_size(100)
523///     .select(DoubleTournament::new(7, 1.4)?)
524///     .crossover(SubtreeCrossover::new())
525///     .mutate(Mutations::builder().subtree(0.6).point(0.2).hoist(0.1).shrink(0.1).build()?)
526///     .mutation_rate(0.2)
527///     .build()?;
528/// # Ok::<(), genoxide::Error>(())
529/// ```
530#[derive(Clone, Debug, PartialEq)]
531#[cfg_attr(feature = "serde", derive(serde::Serialize))]
532pub struct Mutations {
533    mutations: Vec<TreeMutation>,
534    weights: Vec<f64>,
535}
536
537/// Builds [`Mutations`]: see there.
538#[derive(Clone, Debug, Default, PartialEq)]
539pub struct MutationsBuilder {
540    mutations: Vec<TreeMutation>,
541    weights: Vec<f64>,
542}
543
544impl Mutations {
545    /// A builder: add mutations with their weights, then [`build`](MutationsBuilder::build).
546    pub fn builder() -> MutationsBuilder {
547        MutationsBuilder::default()
548    }
549
550    /// The mutations, in the order they were added.
551    pub fn mutations(&self) -> &[TreeMutation] {
552        &self.mutations
553    }
554
555    /// Their weights, in the same order.
556    pub fn weights(&self) -> &[f64] {
557        &self.weights
558    }
559
560    // mutates the tree with one of the mutations, by weight, trying the others by weight while
561    // the chosen one can't change the tree; false if none can
562    fn apply<P: Copy>(&self, gp: &Gp<P>, tree: &mut Tree, rng: &mut StreamRng) -> bool {
563        // the mutations that couldn't change the tree, allocated at the first
564        let mut tried: Vec<bool> = Vec::new();
565        let mut left: f64 = self.weights.iter().sum();
566        loop {
567            let point = rng.unit_f64() * left;
568            let mut sum = 0.0;
569            let mut chosen = None;
570            for (index, &weight) in self.weights.iter().enumerate() {
571                if weight == 0.0 || tried.get(index).copied().unwrap_or(false) {
572                    continue;
573                }
574                sum += weight;
575                chosen = Some(index);
576                if point < sum {
577                    break;
578                }
579            }
580            let Some(index) = chosen else {
581                return false;
582            };
583            if self.mutations[index].apply(gp, tree, rng) {
584                return true;
585            }
586            tried.resize(self.weights.len(), false);
587            tried[index] = true;
588            // what's left, added up again rather than subtracted, so no rounding remains
589            left = self
590                .weights
591                .iter()
592                .zip(&tried)
593                .filter(|&(_, &tried)| !tried)
594                .map(|(weight, _)| weight)
595                .sum();
596            if left <= 0.0 {
597                return false;
598            }
599        }
600    }
601}
602
603impl<P: Copy + Debug + Send + Sync> Mutate<Gp<P>> for Mutations {
604    fn mutate(&self, gp: &Gp<P>, tree: &mut Tree, rng: &mut StreamRng) {
605        self.apply(gp, tree, rng);
606    }
607}
608
609impl MutationsBuilder {
610    /// Adds [`SubtreeMutation::new`] with `weight`.
611    pub fn subtree(self, weight: f64) -> Self {
612        self.with(weight, SubtreeMutation::new())
613    }
614
615    /// Adds [`PointMutation::count`] of one node with `weight`.
616    pub fn point(self, weight: f64) -> Self {
617        self.with(
618            weight,
619            PointMutation {
620                mode: Mode::Count(1),
621            },
622        )
623    }
624
625    /// Adds [`HoistMutation`] with `weight`.
626    pub fn hoist(self, weight: f64) -> Self {
627        self.with(weight, HoistMutation)
628    }
629
630    /// Adds [`ShrinkMutation`] with `weight`.
631    pub fn shrink(self, weight: f64) -> Self {
632        self.with(weight, ShrinkMutation)
633    }
634
635    /// Adds any tree mutation with its settings, e.g.
636    /// `.with(0.1, ConstantMutation::gaussian(0.1)?)`.
637    pub fn with(mut self, weight: f64, mutation: impl Into<TreeMutation>) -> Self {
638        self.mutations.push(mutation.into());
639        self.weights.push(weight);
640        self
641    }
642
643    /// The mix.
644    ///
645    /// # Errors
646    ///
647    /// [`Error::InvalidSetting`] (setting `mutations`) for a weight that is negative or not
648    /// finite, a total weight that isn't positive and finite, or more than 2^24 mutations.
649    pub fn build(self) -> Result<Mutations> {
650        let invalid = |reason: String| {
651            Err(Error::InvalidSetting {
652                setting: "mutations",
653                reason,
654            })
655        };
656        crate::operator::check_size("mutations", self.weights.len())?;
657        if let Some(weight) = self
658            .weights
659            .iter()
660            .find(|weight| !(weight.is_finite() && **weight >= 0.0))
661        {
662            return invalid(format!(
663                "weights must be finite and at least 0, got {weight}"
664            ));
665        }
666        let total: f64 = self.weights.iter().sum();
667        if !(total > 0.0 && total.is_finite()) {
668            return invalid(format!(
669                "the weights must add up to a positive, finite total, got {total}"
670            ));
671        }
672        Ok(Mutations {
673            mutations: self.mutations,
674            weights: self.weights,
675        })
676    }
677}
678
679// deserialized like `build`
680#[cfg(feature = "serde")]
681impl<'de> serde::Deserialize<'de> for Mutations {
682    fn deserialize<D: serde::Deserializer<'de>>(
683        deserializer: D,
684    ) -> std::result::Result<Self, D::Error> {
685        #[derive(serde::Deserialize)]
686        #[serde(rename = "Mutations")]
687        struct Raw {
688            mutations: Vec<TreeMutation>,
689            weights: Vec<f64>,
690        }
691        let raw = Raw::deserialize(deserializer)?;
692        if raw.mutations.len() != raw.weights.len() {
693            return Err(serde::de::Error::custom(
694                "as many weights as mutations are needed",
695            ));
696        }
697        MutationsBuilder {
698            mutations: raw.mutations,
699            weights: raw.weights,
700        }
701        .build()
702        .map_err(serde::de::Error::custom)
703    }
704}