hsc/
subclone.rs

1use crate::genotype::NeutralMutationPoisson;
2use crate::Probs;
3use crate::{stemcell::StemCell, write2file, MAX_SUBCLONES};
4use anyhow::{ensure, Context};
5use log::{debug, trace};
6use rand::seq::IteratorRandom;
7use rand::Rng;
8use rand_distr::{Bernoulli, Distribution, Gamma};
9use sosa::ReactionRates;
10use std::num::NonZeroUsize;
11use std::path::Path;
12
13/// Distribution probabilities for the simulations upon cell division.
14#[derive(Debug, Default, Clone)]
15pub struct Distributions {
16    /// arrival rate of fit mutations per division per cell
17    pub u: f32,
18    /// neutral mutations
19    pub neutral_poisson: NeutralMutationPoisson,
20}
21
22impl Distributions {
23    pub fn new(probs: Probs) -> Self {
24        let (u, background, division) = match probs {
25            Probs::Symmetric { u, probs_per_year } => {
26                (u, probs_per_year.mu_background, probs_per_year.mu_division)
27            }
28            Probs::Asymmetric {
29                u, probs_per_year, ..
30            } => (u, probs_per_year.mu_background, probs_per_year.mu_division),
31        };
32        debug!(
33                "creating distributions with u: {u}, lambda_background: {background}, lambda_division: {division}"
34            );
35
36        Self {
37            u,
38            neutral_poisson: NeutralMutationPoisson::new(division, background).unwrap(),
39        }
40    }
41}
42
43pub fn from_mean_std_to_shape_scale(mean: f32, std: f32) -> (f32, f32) {
44    (mean.powf(2.) / std.powf(2.), std.powf(2.) / mean)
45}
46
47/// Fitness models implemented so far.
48#[derive(Clone, Copy, Debug)]
49pub enum Fitness {
50    /// According to the `Neutral` fitness model, all subclones have the same
51    /// birth-rates `b0`, where `b0` is the birth-rate of the wild-type neutral
52    /// clone.
53    Neutral,
54    /// According to the `Fixed` fitness model, the birth-rates for the
55    /// non-wildtype clones are `b0(1+s)`, where `b0` is the birth-rate of the
56    /// wild-type neutral clone.
57    /// That is, all the subclones have the same proliferate rates except the
58    /// wild-type one.
59    Fixed { s: f32 },
60    /// According to the `GammaSampled` fitness model, the birth-rates for the
61    /// non-wildtype clones are `b0(1+s_i)`, where `b0` is the birth-rate of the
62    /// wild-type neutral clone and `s_i` are sampled from the Gamma
63    /// distribution.
64    /// In this case, all subclones can have different proliferate rates.
65    GammaSampled { shape: f32, scale: f32 },
66}
67
68impl Fitness {
69    pub fn get_mean_std(&self) -> (f32, f32) {
70        match self {
71            Fitness::Neutral => (0., 0.),
72            Fitness::Fixed { s } => (*s, 0.),
73            Fitness::GammaSampled { shape, scale } => {
74                (shape * scale, f32::sqrt(shape * scale.powi(2)))
75            }
76        }
77    }
78}
79
80/// Id of the [`SubClone`]s.
81pub type CloneId = usize;
82
83#[derive(Debug, Clone)]
84/// A group of cells sharing the same genetic background with a specific
85/// proliferation rate.
86///
87/// The main loop of the simulation delegates the proliferation of cells to
88/// this structure, meaning that the `SubClone` will randomly pick one of its
89/// cells and make it proliferate.
90/// Upon proliferation, the cell can be assigned to a new clone with
91/// probability `p` (see [`crate::process::CellDivisionProbabilities`]).
92pub struct SubClone {
93    cells: Vec<StemCell>,
94    pub id: CloneId,
95}
96
97impl Iterator for SubClone {
98    type Item = StemCell;
99
100    fn next(&mut self) -> Option<Self::Item> {
101        self.cells.pop()
102    }
103}
104
105impl SubClone {
106    pub fn new(id: CloneId, cell_capacity: usize) -> SubClone {
107        SubClone {
108            cells: Vec::with_capacity(cell_capacity),
109            id,
110        }
111    }
112
113    pub fn with_capacity(id: usize, capacity: usize) -> SubClone {
114        SubClone {
115            cells: Vec::with_capacity(capacity),
116            id,
117        }
118    }
119
120    pub fn empty_with_id(id: usize) -> SubClone {
121        SubClone { cells: vec![], id }
122    }
123
124    pub fn get_mut_cells(&mut self) -> &mut [StemCell] {
125        &mut self.cells
126    }
127
128    pub fn get_cells(&self) -> &[StemCell] {
129        &self.cells
130    }
131
132    pub fn get_cells_subclones_idx(&self) -> Vec<(&StemCell, usize)> {
133        self.cells.iter().map(|cell| (cell, self.id)).collect()
134    }
135
136    pub fn is_empty(&self) -> bool {
137        self.get_cells().is_empty()
138    }
139
140    pub fn assign_cell(&mut self, cell: StemCell) {
141        self.cells.push(cell);
142    }
143
144    pub fn random_cell(&mut self, rng: &mut impl Rng) -> anyhow::Result<StemCell> {
145        ensure!(!self.cells.is_empty());
146        Ok(self
147            .cells
148            .swap_remove(rng.random_range(0..self.cells.len())))
149    }
150
151    pub fn cell_count(&self) -> u64 {
152        self.cells.len() as u64
153    }
154}
155
156pub fn next_clone(
157    subclones: &SubClones,
158    old_subclone_id: CloneId,
159    p: f64,
160    rng: &mut impl Rng,
161) -> CloneId {
162    //! Returns a [`CloneId`] to which the stem cell must be assigned by
163    //! sampling a fit variant.
164    //!
165    //! This id can be either a new id if a new variant has been arisen, else
166    //! it will be the old clone.
167    // this will panic when u is not small and the cell hasn't divided much,
168    // i.e. when p > 1
169    if Bernoulli::new(p).unwrap().sample(rng) {
170        let mut rnd_clone_id = rng.random_range(0..MAX_SUBCLONES);
171        let mut counter = 0;
172        // the new random clone cannot have `subclone_id` id and must be empty
173        while (rnd_clone_id == old_subclone_id
174            || !subclones.get_clone(rnd_clone_id).unwrap().is_empty())
175            && counter <= MAX_SUBCLONES
176        {
177            rnd_clone_id = rng.random_range(0..MAX_SUBCLONES);
178            counter += 1;
179        }
180        assert!(counter <= MAX_SUBCLONES, "max number of clones reached");
181        debug!("new fit variant: assign cell to clone {rnd_clone_id}");
182        return rnd_clone_id;
183    }
184    debug!("no new fit variants with p {p}",);
185    old_subclone_id
186}
187
188/// A collection of subclones each having their proliferative advantage.
189/// This collection contains the neutral clone as well.
190#[derive(Clone, Debug)]
191pub struct SubClones([SubClone; MAX_SUBCLONES]);
192
193impl SubClones {
194    pub fn new(cells: Vec<StemCell>, capacity: usize) -> Self {
195        //! Returns all the newly initiated subclones by assigning all `cells`
196        //! to the neutral clone.
197        //!
198        //! All clones have their own `capacity`.
199        // initial state
200        let mut subclones: [SubClone; MAX_SUBCLONES] =
201            std::array::from_fn(|i| SubClone::new(i, capacity));
202        let tot_cells = cells.len();
203        debug!("assigning {tot_cells} cells to the wild-type clone");
204        for cell in cells {
205            subclones[0].assign_cell(cell);
206        }
207        assert_eq!(subclones[0].cells.len(), tot_cells);
208
209        Self(subclones)
210    }
211
212    pub fn new_empty() -> Self {
213        SubClones(std::array::from_fn(SubClone::empty_with_id))
214    }
215
216    pub fn with_capacity(capacity: usize) -> Self {
217        SubClones(std::array::from_fn(|id| {
218            SubClone::with_capacity(id, capacity)
219        }))
220    }
221
222    pub fn compute_tot_cells(&self) -> u64 {
223        self.0.iter().map(|subclone| subclone.cell_count()).sum()
224    }
225
226    pub(crate) fn get_clone(&self, id: usize) -> Option<&SubClone> {
227        self.0.get(id)
228    }
229
230    pub(crate) fn get_mut_clone_unchecked(&mut self, id: usize) -> &mut SubClone {
231        &mut self.0[id]
232    }
233
234    pub fn get_neutral_clone(&self) -> &SubClone {
235        &self.0[0]
236    }
237
238    pub fn array_of_gillespie_reactions(&self) -> [CloneId; MAX_SUBCLONES] {
239        core::array::from_fn(|i| i)
240    }
241
242    pub(crate) fn into_subsampled(self, nb_cells: NonZeroUsize, rng: &mut impl Rng) -> Self {
243        //! Take a subsample of the population that is stored in these subclones.
244        let mut subclones = Self::new_empty();
245
246        for (cell, clone_id) in self
247            .0
248            .into_iter()
249            .flat_map(|subclone| {
250                // for all subclones get all the cells with the its subclone_id.
251                // This will return all the population of cells, as a vec of
252                // tuple with the first entry being a stem cell and the second
253                // entry the id of the clone to which the cell belongs.
254                // Then subsample this population, by getting a `nb_cells`
255                // ammount of tuples (cell, id).
256                subclone
257                    .cells
258                    .into_iter()
259                    .map(|cell| (cell, subclone.id))
260                    .collect::<Vec<(StemCell, usize)>>()
261            })
262            .choose_multiple(rng, nb_cells.get())
263        {
264            subclones.0[clone_id].cells.push(cell);
265        }
266        subclones
267    }
268
269    pub fn get_mut_cells(&mut self) -> Vec<&mut StemCell> {
270        self.0
271            .iter_mut()
272            .flat_map(|subclone| subclone.get_mut_cells())
273            .collect()
274    }
275
276    pub fn get_cells(&self) -> Vec<&StemCell> {
277        self.0
278            .iter()
279            .flat_map(|subclone| subclone.get_cells())
280            .collect()
281    }
282
283    pub(crate) fn get_cells_with_clones_idx(&self) -> Vec<(&StemCell, usize)> {
284        self.0
285            .iter()
286            .flat_map(|subclone| subclone.get_cells_subclones_idx())
287            .collect()
288    }
289
290    pub(crate) fn get_cells_subsampled_with_clones_idx(
291        &self,
292        nb_cells: usize,
293        rng: &mut impl Rng,
294    ) -> Vec<(&StemCell, usize)> {
295        self.0
296            .iter()
297            .flat_map(|subclone| subclone.get_cells_subclones_idx())
298            .choose_multiple(rng, nb_cells)
299    }
300
301    pub fn gillespie_rates(
302        &self,
303        fitness: &Fitness,
304        b0: f32,
305        rng: &mut impl Rng,
306    ) -> ReactionRates<MAX_SUBCLONES> {
307        //! Create the Gillespie reaction rates according to the `fitness`
308        //! model with `b0` being the proliferative rate of the wild-type clone.
309        match fitness {
310            Fitness::Fixed { s } => ReactionRates(core::array::from_fn(|i| {
311                if i == 0 {
312                    b0
313                } else {
314                    b0 * (1. + s)
315                }
316            })),
317            &Fitness::GammaSampled { shape, scale } => {
318                let gamma = Gamma::new(shape, scale).unwrap();
319                ReactionRates(core::array::from_fn(|i| {
320                    if i == 0 {
321                        b0
322                    } else {
323                        b0 * (1. + gamma.sample(rng))
324                    }
325                }))
326            }
327            &Fitness::Neutral => ReactionRates(core::array::from_fn(|_| b0)),
328        }
329    }
330}
331
332impl Default for SubClones {
333    fn default() -> Self {
334        //! Create new subclones each having one cell (this creates also the cells).
335        let subclones = std::array::from_fn(|i| {
336            let mut subclone = SubClone::new(i, 2);
337            subclone.assign_cell(StemCell::new());
338            subclone
339        });
340        Self(subclones)
341    }
342}
343
344impl From<Vec<(StemCell, usize)>> for SubClones {
345    fn from(cells: Vec<(StemCell, usize)>) -> Self {
346        let mut subclones = SubClones::new_empty();
347        for (cell, id) in cells.into_iter() {
348            subclones.get_mut_clone_unchecked(id).assign_cell(cell);
349        }
350        subclones
351    }
352}
353
354/// Number of cells in subclones.
355pub struct Variants {}
356
357impl Variants {
358    pub fn variant_counts(subclones: &SubClones) -> [u64; MAX_SUBCLONES] {
359        //! The total variant count is the number of cells in all subclones.
360        //! ```
361        //! use hsc::MAX_SUBCLONES;
362        //! # use hsc::{stemcell::StemCell, process::{Moran}, subclone::Variants};
363        //! // create a process with one cell in each `MAX_SUBCLONES` subclones
364        //! let mut hsc = Moran::default();
365        //!
366        //! assert_eq!(Variants::variant_counts(&hsc.subclones), [1; MAX_SUBCLONES]);
367        //! ```
368        std::array::from_fn(|i| subclones.0[i].cell_count())
369    }
370
371    pub fn variant_fractions(subclones: &SubClones) -> Vec<f32> {
372        //! The proportion of cells in all subclones.
373        //!
374        //! ```
375        //! use hsc::MAX_SUBCLONES;
376        //! use hsc::subclone::SubClones;
377        //! # use hsc::{stemcell::StemCell, process::{Moran}, subclone::Variants};
378        //! // create a process with one cell in each `MAX_SUBCLONES` subclones
379        //! let mut hsc = Moran::default();
380        //!
381        //! assert_eq!(
382        //!     Variants::variant_fractions(&hsc.subclones),
383        //!     [1. / MAX_SUBCLONES as f32; MAX_SUBCLONES]
384        //! );
385        //!
386        //! let subclones = SubClones::new(vec![StemCell::new()], MAX_SUBCLONES);
387        //! let mut variant_fraction = [0.; MAX_SUBCLONES];
388        //! variant_fraction[0] = 1.;
389        //!
390        //! assert_eq!(
391        //!     Variants::variant_fractions(&subclones),
392        //!     variant_fraction
393        //! );
394        //! ```
395        let tot_cells = subclones.compute_tot_cells();
396        Variants::variant_counts(subclones)
397            .into_iter()
398            .map(|frac| frac as f32 / tot_cells as f32)
399            .collect()
400    }
401}
402
403pub fn save_variant_fraction(subclones: &SubClones, path2file: &Path) -> anyhow::Result<()> {
404    let path2file = path2file.with_extension("csv");
405    let total_variant_frac = Variants::variant_fractions(subclones);
406    debug!("total variant fraction in {path2file:#?}");
407    write2file(&total_variant_frac, &path2file, None, false)?;
408    Ok(())
409}
410
411pub fn proliferating_cell(
412    subclones: &mut SubClones,
413    subclone_id: CloneId,
414    rng: &mut impl Rng,
415) -> StemCell {
416    //! Determine which cells will proliferate by randomly selecting a cell
417    //! from the subclone with id `subclone_id`.
418    //! This will also remove the random selected cell from the subclone,
419    //! hence subclones are borrowed mutably.
420    trace!(
421        "a cell from clone {:#?} will divide",
422        subclones.0[subclone_id]
423    );
424    subclones.0[subclone_id]
425        .random_cell(rng)
426        .with_context(|| "found empty subclone")
427        .unwrap()
428}
429
430#[cfg(test)]
431mod tests {
432    use std::num::NonZeroU8;
433
434    use crate::tests::LambdaFromNonZeroU8;
435
436    use super::*;
437    use quickcheck_macros::quickcheck;
438    use rand::{rngs::SmallRng, SeedableRng};
439
440    #[quickcheck]
441    fn assign_cell_test(id: usize) -> bool {
442        let mut neutral_clone = SubClone { cells: vec![], id };
443        let cell = StemCell::new();
444        assert!(neutral_clone.cells.is_empty());
445
446        neutral_clone.assign_cell(cell);
447        !neutral_clone.cells.is_empty()
448    }
449
450    #[quickcheck]
451    fn proliferating_cell_test(seed: u64, mut subclone_id: u8) -> bool {
452        let mut rng = SmallRng::seed_from_u64(seed);
453        let mut subclones = SubClones::default();
454        if subclone_id >= subclones.0.len() as u8 {
455            subclone_id = 1;
456        }
457        let number_of_cells = subclones.0[subclone_id as usize].get_cells().len();
458        let tot_cells = subclones.compute_tot_cells();
459
460        let _ = proliferating_cell(&mut subclones, subclone_id as usize, &mut rng);
461
462        let subclones_have_lost_cell =
463            Variants::variant_counts(&subclones).iter().sum::<u64>() < tot_cells;
464        let subclone_has_lost_cell =
465            subclones.0[subclone_id as usize].get_cells().len() == number_of_cells - 1;
466        subclones_have_lost_cell && subclone_has_lost_cell
467    }
468
469    #[test]
470    fn variant_fraction() {
471        let subclones = SubClones::default();
472        assert_eq!(
473            Variants::variant_fractions(&subclones),
474            &[1. / MAX_SUBCLONES as f32; MAX_SUBCLONES]
475        );
476    }
477
478    #[quickcheck]
479    fn division_no_new_clone(seed: u64, cells_present: NonZeroU8) -> bool {
480        let mut rng = SmallRng::seed_from_u64(seed);
481        let mut cell = StemCell::new();
482        cell.set_last_division_time(1.1).unwrap();
483
484        let cells = vec![cell; cells_present.get() as usize];
485        let subclones = SubClones::new(cells, cells_present.get() as usize + 1);
486
487        let old_id = 0;
488        let id = next_clone(&subclones, old_id, 0., &mut rng);
489        id == old_id
490    }
491
492    #[quickcheck]
493    fn division_new_clone(seed: u64, cells_present: NonZeroU8) -> bool {
494        let mut rng = SmallRng::seed_from_u64(seed);
495        let mut cell = StemCell::new();
496        cell.set_last_division_time(1.1).unwrap();
497
498        let cells = vec![cell; cells_present.get() as usize];
499        let before_assignment = cells.len();
500        let subclones = SubClones::new(cells, cells_present.get() as usize + 1);
501
502        next_clone(&subclones, 0, 1., &mut rng);
503        subclones.0[0].cell_count() as usize == before_assignment
504    }
505
506    #[test]
507    #[should_panic]
508    fn assign_all_clones_occupied() {
509        let mut rng = SmallRng::seed_from_u64(26);
510
511        let subclones = SubClones::default();
512        let mut cell = StemCell::new();
513        cell.set_last_division_time(1.).unwrap();
514
515        next_clone(&subclones, 0, 1., &mut rng);
516    }
517
518    #[quickcheck]
519    fn new_distribution_test(
520        lambda_division: LambdaFromNonZeroU8,
521        lambda_background: LambdaFromNonZeroU8,
522    ) -> bool {
523        let probs = Probs::new(lambda_background.0, lambda_division.0, 0.01, 0., 10);
524        let distrs = Distributions::new(probs);
525        distrs.neutral_poisson.eq(&NeutralMutationPoisson::new(
526            lambda_division.0,
527            lambda_background.0,
528        )
529        .unwrap())
530    }
531}