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}