Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functions to add mutations to populations. #764

Merged
merged 1 commit into from
Jun 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/misc/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ Bug fixes
{pr}`766`
{issue}`765`

New features

* Add {func}`fwdpy11.DiploidPopulation.add_mutation`.
* Add {class}`fwdpy11.NewMutationData`.

C++ back-end

* A population can now be checked that it is- or is not- being simulated.
Expand Down
3 changes: 3 additions & 0 deletions doc/pages/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,9 @@ print(fwdpy11.NOGROWTH)

.. autoattribute:: __init__

.. autoclass:: fwdpy11.NewMutationData
:members:

```


7 changes: 6 additions & 1 deletion fwdpy11/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ set (ARRAY_PROXY_SOURCES src/array_proxies/init.cc)
set (MUTATION_DOMINANCE_SOURCES src/mutation_dominance/init.cc
src/mutation_dominance/MutationDominance.cc)

set (FUNCTION_SOURCES src/functions/init.cc
src/functions/add_mutation.cc
src/functions/_add_mutation.cc)

set(ALL_SOURCES ${FWDPP_TYPES_SOURCES}
${FWDPY11_TYPES_SOURCES}
${REGION_SOURCES}
Expand All @@ -112,7 +116,8 @@ set(ALL_SOURCES ${FWDPP_TYPES_SOURCES}
${EVOLVE_POPULATION_SOURCES}
${DISCRETE_DEMOGRAPHY_SOURCES}
${ARRAY_PROXY_SOURCES}
${MUTATION_DOMINANCE_SOURCES})
${MUTATION_DOMINANCE_SOURCES}
${FUNCTION_SOURCES})

set(LTO_OPTIONS)
if(ENABLE_PROFILING OR DISABLE_LTO)
Expand Down
1 change: 1 addition & 0 deletions fwdpy11/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
DiploidPopulation,
TreeIterator,
VariantIterator,
NewMutationData,
) # NOQA
from ._types.demography_debugger import DemographyDebugger # NOQA
from ._types.model_params import ModelParams, MutationAndRecombinationRates # NOQA
Expand Down
1 change: 1 addition & 0 deletions fwdpy11/_types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@
from .data_matrix_iterator import DataMatrixIterator # NOQA
from .diploid_population import DiploidPopulation # NOQA
from .model_params import ModelParams # NOQA
from .new_mutation_data import NewMutationData # NOQA
82 changes: 82 additions & 0 deletions fwdpy11/_types/diploid_population.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from .._fwdpy11 import DiploidGenotype, DiploidMetadata, ll_DiploidPopulation
from .model_params import ModelParams
from .new_mutation_data import NewMutationData
from .population_mixin import PopulationMixin
from .table_collection import TableCollection

Expand Down Expand Up @@ -321,3 +322,84 @@ def sample_timepoints(
md.flags.writeable = False
nodes.flags.writeable = False
yield self.generation, nodes, md

def add_mutation(
self,
rng: fwdpy11.GSLrng,
*,
window: Tuple[float, float] = None,
ndescendants: int = 1,
deme: Optional[int] = None,
data: NewMutationData = None,
) -> Optional[int]:
"""
Add a new mutation to the population.

.. versionadded:: 0.16.0

:param rng: Random number generator
:type rng: fwdpy11.GSLrng

The following arguments are keyword-only:

:param window: A window [left, right) within which to place the mutation.
The default is `None`, meaning the window is the entire
genome.
:type window: tuple[float, float]
:param ndescendants: The number of alive nodes carrying the new mutation.
Default is `1`, implying that a singleton mutation
will be generated.
:type ndescendants: int
:param deme: The deme in which to place the new mutation
The default is `None`, meaning that alive node demes are
not considered.
:type deme: int
:type data: The details of the new mutation
:type data: fwdpy11.NewMutationData

:returns: The key of the new mutation.
(The index of the variant in :attr:`DiploidPopulation.mutations`.)

Implementation details:

* A set of nodes are found within `window` that are ancestral to
exactly `ndescendants` alive nodes.
* From this list of candidate nodes, one is chosen randomly
to be the node where we place the new mutation.
* This node's time is the origin time of the new mutation.
* If `deme` is None, any set of `ndescendants` will be considered.
* If `deme` is :math:`\geq 0`, all `ndescendants` alive nodes must be
from that deme.
* If the `deme`/`ndescendants` requirements cannot be satisified,
the function returns `None`.
* The genetic values of individuals are not updated by this function.
Such updates will happen when the population begins evolution
forwards in time.

Exceptions:

:raises ValueError: for bad input.
:raises RuntimeError: if this function is applied during a simulation
:raises RuntimeError: if internal checks fail.
"""
if window is None:
_window = (0.0, self.tables.genome_length)
else:
_window = window

if deme is None:
_deme = -1
else:
_deme = deme

if ndescendants is None:
raise ValueError(f"ndescendants must be > 0: got {ndescendants}")

from fwdpy11._fwdpy11 import _add_mutation

key = _add_mutation(
rng, _window[0], _window[1], ndescendants, _deme, data, self
)
if key == np.iinfo(np.uint64).max:
return None
return key
50 changes: 50 additions & 0 deletions fwdpy11/_types/new_mutation_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import typing

import attr

from .._fwdpy11 import ll_NewMutationData


@attr.s(auto_attribs=True, frozen=True, repr_ns="fwdpy11", kw_only=True)
class NewMutationData(ll_NewMutationData):
"""
Data object for :func:`fwdpy11.DiploidPopulation.add_mutation`

.. versionadded:: 0.16.0

This class has the following attributes, whose names
are also `kwargs` for intitialization. The attribute names
also determine the order of positional arguments:

:param effect_size: The fixed/univariate effect size.
Will fill in :attr:`fwdpy11.Mutation.s`
:type effect_size: float
:param dominance: Heterozygous effect of the mutation.
Will fill in :attr:`fwdpy11.Mutation.h`
:type dominance: float
:param esizes: Data to fill :attr:`fwdpy11.Mutation.esizes`.
Default is `None`.
:type esizes: list[float]
:param heffects: Data to fill :attr:`fwdpy11.Mutation.heffects`.
Default is `None`.
:type heffects: list[float]
:param label: Data for :attr:`fwdpy11.Mutation.label`.
Default is 0.
:type label: int
"""

effect_size: float
dominance: float
esizes: typing.Optional[typing.List[float]] = None
heffects: typing.Optional[typing.List[float]] = None
label: int = 0

def __attrs_post_init__(self):
if self.esizes is None and self.heffects is None:
super(NewMutationData, self).__init__(
self.effect_size, self.dominance, [], [], self.label
)
else:
super(NewMutationData, self).__init__(
self.effect_size, self.dominance, self.esizes, self.heffects, self.label
)
2 changes: 2 additions & 0 deletions fwdpy11/src/_fwdpy11.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ void init_ts(py::module &);
void init_evolution_functions(py::module &);
void init_discrete_demography(py::module &m);
void init_array_proxies(py::module &m);
void initialize_functions(py::module & m);

PYBIND11_MODULE(_fwdpy11, m)
{
Expand All @@ -37,6 +38,7 @@ PYBIND11_MODULE(_fwdpy11, m)
init_evolution_functions(m);
init_discrete_demography(m);
init_array_proxies(m);
initialize_functions(m);

py::register_exception<fwdpy11::GSLError>(m, "GSLError");

Expand Down
34 changes: 34 additions & 0 deletions fwdpy11/src/functions/_add_mutation.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// Copyright (C) 2021 Kevin Thornton <krthornt@uci.edu>
//
// This file is part of fwdpy11.
//
// fwdpy11 is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// fwdpy11 is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with fwdpy11. If not, see <http://www.gnu.org/licenses/>.
//
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include "add_mutation.hpp"

namespace py = pybind11;

void
init_add_mutation(py::module& m)
{
m.def("_add_mutation", &add_mutation);

py::class_<new_mutation_data>(m, "ll_NewMutationData")
.def(py::init<double, double, std::vector<double>, std::vector<double>,
decltype(fwdpy11::Mutation::xtra)>());
}