diff --git a/doc/long_vignettes/tskit_metadata_vignette.md b/doc/long_vignettes/tskit_metadata_vignette.md index bb0f1a98a..491f84483 100644 --- a/doc/long_vignettes/tskit_metadata_vignette.md +++ b/doc/long_vignettes/tskit_metadata_vignette.md @@ -164,7 +164,7 @@ fwdpy11.evolvets(rng, pop, params, simplification_interval=100, suppress_table_indexing=True) -ts = pop.dump_tables_to_tskit(demes_graph=graph) +ts = pop.dump_tables_to_tskit() ``` Now that we have some data, let's look at how the `fwdpy11` mutation and individual information got encoded as `tskit` metadata! diff --git a/doc/long_vignettes/tskitconvert_vignette.md b/doc/long_vignettes/tskitconvert_vignette.md index d75430f1d..7506a2abb 100644 --- a/doc/long_vignettes/tskitconvert_vignette.md +++ b/doc/long_vignettes/tskitconvert_vignette.md @@ -161,84 +161,6 @@ ts = pop.dump_tables_to_tskit(model_params=params) assert fwdpy11.ModelParams(**eval(ts.metadata["model_params"])) == params ``` -### Including a `demes` graph - -You may include a {class}`demes.Graph` in the top-level metadata. -If you also include a {class}`fwdpy11.ModelParams` (see above), including the `demes` graph gives redundant information. -However, including the graph may be useful if downstream analysis will involve other tools compatible with the `demes` specification. -With the graph as metadata, you can extract it and reconstruct the original `YAML` file, or send it to another Python package that understands it. - -The following hidden code block defines a function to return a {class}`demes.Graph` from `YAML` input stored in a string literal. - -```{code-cell} python ---- -tags: [ "hide-input" ] ---- -def gutenkunst(): - import demes - yaml = """ -description: The Gutenkunst et al. (2009) OOA model. -doi: -- https://doi.org/10.1371/journal.pgen.1000695 -time_units: years -generation_time: 25 - -demes: -- name: ancestral - description: Equilibrium/root population - epochs: - - {end_time: 220e3, start_size: 7300} -- name: AMH - description: Anatomically modern humans - ancestors: [ancestral] - epochs: - - {end_time: 140e3, start_size: 12300} -- name: OOA - description: Bottleneck out-of-Africa population - ancestors: [AMH] - epochs: - - {end_time: 21.2e3, start_size: 2100} -- name: YRI - description: Yoruba in Ibadan, Nigeria - ancestors: [AMH] - epochs: - - start_size: 12300 -- name: CEU - description: Utah Residents (CEPH) with Northern and Western European Ancestry - ancestors: [OOA] - epochs: - - {start_size: 1000, end_size: 29725} -- name: CHB - description: Han Chinese in Beijing, China - ancestors: [OOA] - epochs: - - {start_size: 510, end_size: 54090} - -migrations: -- {demes: [YRI, OOA], rate: 25e-5} -- {demes: [YRI, CEU], rate: 3e-5} -- {demes: [YRI, CHB], rate: 1.9e-5} -- {demes: [CEU, CHB], rate: 9.6e-5} -""" - return demes.loads(yaml) -``` - -```{code-cell} python -graph = gutenkunst() -ts = pop.dump_tables_to_tskit(demes_graph=graph) -``` - -```{code-cell} python -assert demes.Graph.fromdict(ts.metadata["demes_graph"]) == graph -``` - -Since this is an optional metadata field, accessing it will return `None` if no graph was provided: - -```{code-cell} python -ts = pop.dump_tables_to_tskit() -assert "demes_graph" not in ts.metadata -``` - ### User-defined metadata It may be useful to store arbitrary data in the output. @@ -279,51 +201,4 @@ Further, if the data are very large, then other output formats are likely more a ## Setting population table metadata -You can also set the population table metadata when exporting data. -Doing so requires a {class}`dict` mapping the integer label for each population to another {class}`dict`. - -For example, let's create an demographic model from the `demes` graph that used above: - -```{code-cell}python -model = fwdpy11.discrete_demography.from_demes(gutenkunst()) -type(model) -``` - -This object contains a mapping from integer labels to the deme names: - -```{code-cell} python -model.metadata['deme_labels'] -``` - -We can make the required `dict` like this: - -```{code-cell} python -pop_md = {} -for key, value in model.metadata['deme_labels'].items(): - pop_md[key] = {'name': value} -``` - -To actually illustrate the use of this metadata, we need to make sure that our `tskit` output actually has a population table: - -```{code-cell} python -# initialize a population with the right number of demes... -multideme_pop = fwdpy11.DiploidPopulation([100]*len(pop_md), 1.) -ts = multideme_pop.dump_tables_to_tskit(population_metadata=pop_md) -for pop in ts.populations(): - print(pop.metadata) -``` - -We could also easily add the deme description to the metadata from the `demes` graph: - -```{code-cell} python -graph = gutenkunst() -graph.demes -``` - -```{code-cell} python -pop_md = {} -for i,deme in enumerate(graph.demes): - pop_md[i] = {'name': deme.name, "description": deme.description} - -pop_md -``` +TODO diff --git a/doc/misc/changelog.md b/doc/misc/changelog.md index c9dde7fc2..b4b28056d 100644 --- a/doc/misc/changelog.md +++ b/doc/misc/changelog.md @@ -641,7 +641,7 @@ Bug fixes Breaking changes -* {func}`fwdpy11.TableCollection.fs` no longer accepts more than two sample sets. +* `fwdpy11.TableCollection.fs` no longer accepts more than two sample sets. This change allowed us to drop `sparse` as a dependency that was causing headaches when new Python point releases come out. PR {pr}`924`. Issues {issue}`876`, {issue}`919`. @@ -1055,7 +1055,7 @@ Fixes: This is a point release adding more documentation: * {ref}`Demes vignette ` updated. -* {func}`fwdpy11.TableCollection.fs` docstring updated regarding some perhaps unexpected behavior of `sparse.COO`. +* `fwdpy11.TableCollection.fs` docstring updated regarding some perhaps unexpected behavior of `sparse.COO`. ## 0.14.0 @@ -1494,7 +1494,7 @@ release candidates (see below) plus the following: {issue}`389` {issue}`390` {issue}`392` -* {func}`fwdpy11.TableCollection.fs` added. See `tablefs`. +* `fwdpy11.TableCollection.fs` added. See `tablefs`. PR {pr}`387` PR {pr}`399` * Creating populations from `msprime` input improved. diff --git a/doc/pages/functions.md b/doc/pages/functions.md index 7800500f2..15adf4398 100644 --- a/doc/pages/functions.md +++ b/doc/pages/functions.md @@ -14,10 +14,6 @@ :rtype: int ``` -```{eval-rst} -.. autofunction:: fwdpy11.simplify -``` - ```{eval-rst} .. autofunction:: fwdpy11.simplify_tables ``` diff --git a/doc/pages/genetic_values.md b/doc/pages/genetic_values.md index 1763a9be6..a2d96f492 100644 --- a/doc/pages/genetic_values.md +++ b/doc/pages/genetic_values.md @@ -23,8 +23,6 @@ For some background, see {ref}`definitions`. ``` ```{eval-rst} -.. autoclass:: fwdpy11.StrictAdditiveMultivariateEffects +.. autoclass:: fwdpy11.AdditivePleiotropy :members: asdict, fromdict, asblack, shape, genetic_values, maps_to_fitness, maps_to_trait_value ``` - - diff --git a/doc/short_vignettes/incomplete_sweep.md b/doc/short_vignettes/incomplete_sweep.md index e7aa5cc37..74e42521f 100644 --- a/doc/short_vignettes/incomplete_sweep.md +++ b/doc/short_vignettes/incomplete_sweep.md @@ -76,6 +76,7 @@ def setup(prune_selected=False): "rates": (0, 0, None), "prune_selected": False, "simlen": 200, + "demography": fwdpy11.ForwardDemesGraph.tubes([pop.N], burnin=10) } params = fwdpy11.ModelParams(**pdict) diff --git a/doc/short_vignettes/selective_sweep.md b/doc/short_vignettes/selective_sweep.md index dc7d5947d..9e0ffd1fe 100644 --- a/doc/short_vignettes/selective_sweep.md +++ b/doc/short_vignettes/selective_sweep.md @@ -59,6 +59,7 @@ def setup(prune_selected=False): "rates": (0, 0, None), "prune_selected": False, "simlen": 200, + "demography": fwdpy11.ForwardDemesGraph.tubes([pop.N], burnin=10) } params = fwdpy11.ModelParams(**pdict) diff --git a/doc/short_vignettes/trackmutationfates.md b/doc/short_vignettes/trackmutationfates.md index 36d15b9e1..1dd6b5c50 100644 --- a/doc/short_vignettes/trackmutationfates.md +++ b/doc/short_vignettes/trackmutationfates.md @@ -58,6 +58,7 @@ def setup(prune_selected=False): "rates": (0, 0, None), "prune_selected": False, "simlen": 200, + "demography": fwdpy11.ForwardDemesGraph.tubes([pop.N], burnin=10) } params = fwdpy11.ModelParams(**pdict) diff --git a/doc/short_vignettes/workingexample_fitness.md b/doc/short_vignettes/workingexample_fitness.md index 9c5f1eced..21483a12a 100644 --- a/doc/short_vignettes/workingexample_fitness.md +++ b/doc/short_vignettes/workingexample_fitness.md @@ -32,6 +32,7 @@ p = { "prune_selected": True, "demography": None, "simlen": 10 * N, + "demography": fwdpy11.ForwardDemesGraph.tubes([N], burnin=10) } params = fwdpy11.ModelParams(**p) ``` diff --git a/fwdpy11/__init__.py b/fwdpy11/__init__.py index 39c565540..960497548 100644 --- a/fwdpy11/__init__.py +++ b/fwdpy11/__init__.py @@ -49,16 +49,11 @@ PleiotropicOptima, Optimum, GaussianStabilizingSelection, - GSS, - GSSmo, - MultivariateGSS, - MultivariateGSSmo, NoNoise, GaussianNoise, Additive, Multiplicative, GBR, - StrictAdditiveMultivariateEffects, AdditivePleiotropy, PyDiploidGeneticValue, TimingError, @@ -84,8 +79,6 @@ from ._functions import ( # NOQA data_matrix_from_tables, infinite_sites, - make_data_matrix, - simplify, simplify_tables, _validate_regions, ) # NOQA diff --git a/fwdpy11/_evolvets.py b/fwdpy11/_evolvets.py index d9fe24868..c7d4b85ea 100644 --- a/fwdpy11/_evolvets.py +++ b/fwdpy11/_evolvets.py @@ -121,16 +121,7 @@ def evolvets( except Exception as e: raise e else: - # Build a default model of "tubes" - from fwdpy11 import ForwardDemesGraph - - sizes = pop.deme_sizes()[1].tolist() - msg = "Applying a default demographic model " - msg += f"where deme sizes are {sizes} " - msg += f"and the burn-in length is 10*{sum(sizes)}. " - msg += "This will raise an error in future releases." - warnings.warn(msg, DeprecationWarning, stacklevel=2) - demographic_model = ForwardDemesGraph.tubes(sizes, 10) + raise ValueError("params.demography cannot be None") for r in params.sregions: if isinstance(r, fwdpy11.mvDES): diff --git a/fwdpy11/_functions/__init__.py b/fwdpy11/_functions/__init__.py index 935d95521..297bbe0c7 100644 --- a/fwdpy11/_functions/__init__.py +++ b/fwdpy11/_functions/__init__.py @@ -1,6 +1,6 @@ -from .data_matrix_from_tables import data_matrix_from_tables, make_data_matrix # NOQA +from .data_matrix_from_tables import data_matrix_from_tables # NOQA from .import_demes import demography_from_demes # NOQa -from .simplify_tables import simplify, simplify_tables # NOQA +from .simplify_tables import simplify_tables # NOQA from fwdpy11._fwdpy11 import _infinite_sites from fwdpy11 import GSLrng, DiploidPopulation diff --git a/fwdpy11/_functions/data_matrix_from_tables.py b/fwdpy11/_functions/data_matrix_from_tables.py index fe5c7a558..e03189668 100644 --- a/fwdpy11/_functions/data_matrix_from_tables.py +++ b/fwdpy11/_functions/data_matrix_from_tables.py @@ -25,39 +25,6 @@ from .._types import DataMatrix, DiploidPopulation, TableCollection -def make_data_matrix( - pop: DiploidPopulation, - samples: Union[List, np.ndarray], - record_neutral: bool, - record_selected: bool, -) -> DataMatrix: - """ - Create a :class:`fwdpy11.DataMatrix` from a table collection. - - :param pop: A population - :type pop: :class:`fwdpy11.PopulationBase` - :param samples: A list of sample nodes - :type samples: list - :param record_neutral: If True, generate data for neutral variants - :type record_neutral: bool - :param record_selected: If True, generate data for selected variants - :type record_selected: bool - - .. deprecated:: 0.3.0 - - Prefer :func:`fwdpy11.data_matrix_from_tables`. - - """ - from warnings import warn - - warn( - "fwdpy11.make_data_matrix is deprecated." - " Please use fwdpy11.data_matrix_from_tables.", - FutureWarning, - ) - return DataMatrix(_make_data_matrix(pop, samples, record_neutral, record_selected)) - - def data_matrix_from_tables( tables: TableCollection, samples: Union[List, np.ndarray], diff --git a/fwdpy11/_functions/simplify_tables.py b/fwdpy11/_functions/simplify_tables.py index 3aef395fd..48bce12a9 100644 --- a/fwdpy11/_functions/simplify_tables.py +++ b/fwdpy11/_functions/simplify_tables.py @@ -5,61 +5,6 @@ import numpy as np -def simplify(pop, samples): - """ - Simplify a TableCollection stored in a Population. - - :param pop: A :class:`fwdpy11.DiploidPopulation` - :param samples: A list of samples (node indexes). - - :return: The simplified tables and array mapping input sample - IDs to output IDS - - :rtype: tuple - - Note that the samples argument is agnostic with respect to the time of - the nodes in the input tables. Thus, you may do things like simplify - to a set of "currently-alive" nodes plus some or all ancient samples by - including some node IDs from - :attr:`fwdpy11.DiploidPopulation.ancient_sample_metadata`. - - If the input contains ancient samples, - and you wish to include them in the output, - then you need to include their IDs in the samples argument. - - .. note:: - - Due to node ID remapping, the metadata corresponding - to nodes becomes a bit more difficult to look up. - You need to use the output ID map, the original IDs, and - the population's metadata containers. - - .. deprecated:: 0.3.0 - - Prefer :func:`fwdpy11.simplify_tables` - - .. versionchanged:: 0.3.0 - - Ancient samples are no longer kept by default - - .. versionchanged:: 0.5.0 - - No longer requires a :class:`MutationVector` argument. - - """ - import warnings - - warnings.warn( - "This function is deprecated and will be removed soon." - "Please use fwdpy11.simplify_tables instead", - category=FutureWarning, - stacklevel=2, - ) - - ll_t, idmap = fwdpy11._fwdpy11._simplify(pop, samples) - return fwdpy11._types.TableCollection(ll_t), idmap - - def simplify_tables( tables: fwdpy11._types.TableCollection, samples: Union[List, np.ndarray] ) -> Tuple[fwdpy11._types.TableCollection, np.ndarray]: diff --git a/fwdpy11/_types/diploid_population.py b/fwdpy11/_types/diploid_population.py index 441d9d866..9ae29edbe 100644 --- a/fwdpy11/_types/diploid_population.py +++ b/fwdpy11/_types/diploid_population.py @@ -298,8 +298,7 @@ def deme_sizes(self, as_dict=False): def dump_tables_to_tskit( self, *, - model_params: Optional[Union[ModelParams, Dict[str, ModelParams]]] = None, - demes_graph: Optional[demes.Graph] = None, + model_params: Optional[ModelParams] = None, population_metadata: Optional[Dict[int, object]] = None, data: Optional[object] = None, seed: Optional[int] = None, @@ -312,10 +311,7 @@ def dump_tables_to_tskit( :param model_params: Model parameters to be stored as top-level metadata - :type model_params: :class:`fwdpy11.ModelParams` or :class:`dict` - - :param demes_graph: A demographic model specified via `demes`. - :type demes_graph: :class:`demes.Graph` + :type model_params: :class:`fwdpy11.ModelParams` :param population_metadata: A mapping from integer id of a deme/population to metadata @@ -381,11 +377,14 @@ def dump_tables_to_tskit( Removed deprecated `wrapped` keyword argument. + .. versionadded:: 0.23.0 + + Remove deprecated `demes_graph` argument and update type hints. + """ return fwdpy11.tskit_tools._dump_tables_to_tskit._dump_tables_to_tskit( self, model_params=model_params, - demes_graph=demes_graph, population_metadata=population_metadata, data=data, seed=seed, diff --git a/fwdpy11/_types/model_params.py b/fwdpy11/_types/model_params.py index 101ab9c6c..ac9f7b27e 100644 --- a/fwdpy11/_types/model_params.py +++ b/fwdpy11/_types/model_params.py @@ -332,12 +332,7 @@ def validate_gvalue(self, attribute, value): @demography.validator def validate_demography(self, attribute, value): if value is None: - warnings.warn( - "No demographic model specified." - " This will be a hard error in a future release.", - DeprecationWarning, - ) - return + raise ValueError("no demographic model specified") if isinstance(value, fwdpy11.ForwardDemesGraph): return diff --git a/fwdpy11/_types/table_collection.py b/fwdpy11/_types/table_collection.py index f8d444a40..27a2dcc5c 100644 --- a/fwdpy11/_types/table_collection.py +++ b/fwdpy11/_types/table_collection.py @@ -1,4 +1,3 @@ -from deprecated import deprecated from typing import Iterable import numpy as np @@ -10,76 +9,6 @@ NOT_A_SAMPLE = np.iinfo(np.int32).min -def _include_both(m): - return True - - -def _include_neutral(m): - return m.neutral is True - - -def _include_selected(m): - return m.neutral is False - - -def _validate_windows(windows, genome_length): - if windows != sorted(windows, key=lambda x: x[0]): - raise ValueError("windows must be sorted in increasing order") - for w in windows: - if w[0] >= w[1]: - raise ValueError("windows must be [a, b) and a < b") - for i in w: - if i < 0 or i > genome_length: - raise ValueError("window coordinates must be [0, genome_length)") - for i in range(1, len(windows), 2): - if windows[i - 1][0] < windows[i][1] and windows[i][0] < windows[i - 1][1]: - raise ValueError("windows cannot overlap") - - -def _handle_fs_marginalizing(fs, marginalize, nwindows, num_sample_groups): - if marginalize is False or num_sample_groups == 1: - return fs - if nwindows == 1: - temp = {} - for i in range(num_sample_groups): - axes = num_sample_groups - i - 1 - temp[i] = np.ma.array(np.asarray(fs.sum(axis=axes).flatten())[0]) - temp[i][0] = np.ma.masked - temp[i][-1] = np.ma.masked - return temp - - rv = [] - for f in fs: - temp = {} - for i in range(num_sample_groups): - axes = num_sample_groups - i - 1 - temp[i] = np.ma.array(np.asarray(f.sum(axis=axes).flatten())[0]) - temp[i][0] = np.ma.masked - temp[i][-1] = np.ma.masked - rv.append(temp) - return rv - - -def _update_window(windex, tree, windows): - while windex < len(windows) and tree.right > windows[windex][1]: - windex += 1 - return windex - - -def _position_in_window(pos, window): - return pos >= window[0] and pos < window[1] - - -def _simplify(tables, samples, simplify): - import fwdpy11 - - if simplify is False: - return tables, samples - - t, i = fwdpy11.simplify_tables(tables, samples) - return t, i[samples] - - class TableCollection(ll_TableCollection): def __init__(self, *args): super(TableCollection, self).__init__(*args) @@ -132,206 +61,6 @@ def build_indexes(self): """ self._build_indexes() - def _1dfs(self, samples, windows, include_function, simplify): - """ - Returns an array with the zero and fixed - bins masked out. The masking is for - consistency w/ndfs output. - """ - from . import TreeIterator - - t, s = _simplify(self, samples, simplify) - fs = [np.zeros(len(s) + 1, dtype=np.int32) for i in windows] - ti = TreeIterator(t, s) - windex = 0 - sites = np.array(t.sites, copy=False) - positions = sites["position"][:] - for tree in ti: - for m in tree.mutations(): - if include_function(m): - pos = positions[m.site] - while windex < len(windows) and windows[windex][1] < pos: - windex += 1 - if windex >= len(windows): - break - if _position_in_window(pos, windows[windex]): - c = np.uint32(tree.leaf_counts(m.node)) - fs[windex][c] += 1 - - rv = [] - for i in fs: - i = np.ma.array(i) - i[0] = np.ma.masked - i[-1] = np.ma.masked - rv.append(i) - return rv - - def _ndfs( - self, - samples, - sample_groups, - num_sample_groups, - windows, - include_function, - simplify, - ): - """ - For more than one sample, always work with the joint FS - at first. When the marginals are wanted, we extract them - later. - """ - from . import TreeIterator - import scipy.sparse # type: ignore - - shapes = tuple(len(i) + 1 for i in samples) - dok_JFS = [scipy.sparse.dok_matrix(shapes, dtype=np.int32) for i in windows] - - sample_list = np.where(sample_groups != NOT_A_SAMPLE)[0] - t, s = _simplify(self, sample_list, simplify) - ti = TreeIterator(t, s, update_samples=True) - counts = np.zeros(len(samples), dtype=np.int32) - windex = 0 - sites = np.array(t.sites, copy=False) - positions = sites["position"][:] - for tree in ti: - for m in tree.mutations(): - if include_function(m): - pos = positions[m.site] - while windex < len(windows) and windows[windex][1] < pos: - windex += 1 - if windex >= len(windows): - break - if _position_in_window(pos, windows[windex]): - counts[:] = 0 - d = tree.samples_below(m.node) - if len(d) > 0: - c = np.unique(sample_groups[d], return_counts=True) - counts[c[0]] += c[1] - dok_JFS[windex][tuple((i) for i in counts)] += 1 - return [scipy.sparse.coo_matrix(i) for i in dok_JFS] - - def _fs_implementation(self, samples, windows, include_function, simplify): - """ - self is a fwdpy11.TableCollection - samples is a list of 1d numpy.ndarray - """ - if len(samples) == 0: - raise ValueError("empty list of samples") - - # Don't figure out sample groups when - # there is only 1 - if len(samples) == 1: - return self._1dfs(samples[0], windows, include_function, simplify) - - sample_groups = np.array([NOT_A_SAMPLE] * len(self.nodes), dtype=np.int32) - for i, j in enumerate(samples): - if np.any(sample_groups[j] != NOT_A_SAMPLE): - raise ValueError( - "sample nodes cannot be part of multiple sample groups" - ) - sample_groups[j] = i - - num_sample_groups = i + 1 - return self._ndfs( - samples, - sample_groups, - num_sample_groups, - windows, - include_function, - simplify, - ) - - @deprecated(reason="dependency on scikit is a problem") - def fs( - self, - samples, - marginalize=False, - simplify=False, - windows=None, - separate_windows=False, - include_neutral=True, - include_selected=True, - ): - """ - :param samples: lists of numpy arrays of sample nodes - :param marginalize: For ``FS`` involving multiple samples, - extract out the marginal ``FS`` per sample. - :param simplify: If ``True``, simplify with respect to the sample - set prior to calculating the ``FS``. - :param windows: A list of non-overlapping intervals from which - the ``FS`` is calculated. - :param separate_windows: If ``True``, return ``FS`` separately for - each interval in ``windows``. - :param include_neutral: Include neutral mutations? - :param include_selected: Include selected mutations? - - :returns: The mutation frequency spectrum. The `dtype` is `int32`. - :rtype: object - - The details of the return value depend heavily on the options. - - * If a single sample list is provided, the return value is a - 1-d ndarray with the first and last bins masked. The unmasked - bins correspond to frequencies 1 to n-1, where n is the number - of sample nodes. - * If multiple sample lists are provided, the return value is a - :class:`scipy.sparse.coo_matrix`. For each dimension, the 0 and n - bins are included. - * If ``marginalize == True`` and more than one sample bin is provided, - the :class:`scipy.sparse.coo_matrix` matrix is converted into a dict - where the key is the index of the sample list and the - value is a dense 1-d array, - masked as described above. - * If multiple windows are provided and ``separate_windows == False``, - then the return value is a single frequency spectrum summed over - windows. - If ``separate_windows == True``, then a list of frequency spectra is - returned, indexed in the same order as the input windows. - - .. versionadded:: 0.6.0 - - Python implementation added - - .. versionchanged:: 0.18.0 - - Dropped `sparse` as a dependency, using types from `scipy.sparse` - instead. This change drops support for more than 2 sample lists. - - """ - for s in samples: - if len(s) == 0: - raise ValueError("samples lists cannot be empty") - if len(s) < 2: - raise ValueError("samples lists must have at least two nodes") - if np.any(s >= len(self.nodes)): - raise ValueError("invalid samples") - - if windows is None: - windows = [(0, self.genome_length)] - - _validate_windows(windows, self.genome_length) - - if include_neutral is False and include_selected is False: - raise ValueError( - "One or both of include_neutral " "and include_selected must be True" - ) - - include_function = _include_both - if include_neutral is False: - include_function = _include_selected - elif include_selected is False: - include_function = _include_neutral - - lfs = self._fs_implementation(samples, windows, include_function, simplify) - if separate_windows is True: - lfs = _handle_fs_marginalizing(lfs, marginalize, len(windows), len(samples)) - return lfs - fs = lfs[0] - for i in lfs[1:]: - fs += i - fs = _handle_fs_marginalizing(fs, marginalize, len(windows), len(samples)) - return fs - def preserved_nodes(self): raise AttributeError( "TableCollection.preserved_nodes was removed in 0.13.0 and is" diff --git a/fwdpy11/genetic_values.py b/fwdpy11/genetic_values.py index 4fac53898..e5545c95e 100644 --- a/fwdpy11/genetic_values.py +++ b/fwdpy11/genetic_values.py @@ -25,8 +25,6 @@ import demes import numpy as np -from deprecated import deprecated - from ._fwdpy11 import ( GeneticValueIsTrait, @@ -227,216 +225,6 @@ def validate_timings(self, deme: int, demography: ForwardDemesGraph) -> None: raise TimingError(msg) -@attr_add_asblack -@attr_class_to_from_dict_no_recurse -@attr.s(auto_attribs=True, frozen=True, repr_ns="fwdpy11") -class GSS(_ll_GSSmo): - """ - Gaussian stabilizing selection on a single trait. - - This class has the following attributes, whose names - are also `kwargs` for intitialization. The attribute names - also determine the order of positional arguments: - - :param optimum: The optimal trait value - :type optimum: float or fwdpy11.Optimum - :param VS: Inverse strength of stabilizing selection - :type VS: float or None - - .. note:: - - VS should be None if optimum is an instance - of :class:`fwdpy11.Optimum` - - .. versionchanged:: 0.7.1 - - Allow instances of fwdpy11.Optimum for intitialization - - .. versionchanged:: 0.8.0 - - Refactored to use attrs and inherit from - low-level C++ class - """ - - optimum: typing.Union[Optimum, float] - VS: typing.Optional[float] = None - - def __attrs_post_init__(self): - warnings.warn("use GaussianStabilizingSelection instead", DeprecationWarning) - if self.VS is None: - super(GSS, self).__init__( - [Optimum(optimum=self.optimum.optimum, VS=self.optimum.VS, when=0)] - ) - else: - super(GSS, self).__init__( - [Optimum(optimum=self.optimum, VS=self.VS, when=0)] - ) - - def __getstate__(self): - return self.asdict() - - def __setstate__(self, d): - self.__dict__.update(d) - if self.VS is None: - super(GSS, self).__init__( - [Optimum(optimum=self.optimum.optimum, VS=self.optimum.VS, when=0)] - ) - else: - super(GSS, self).__init__( - [Optimum(optimum=self.optimum, VS=self.VS, when=0)] - ) - - -@attr_add_asblack -@attr_class_pickle_with_super -@attr_class_to_from_dict_no_recurse -@attr.s(auto_attribs=True, frozen=True, repr_ns="fwdpy11") -class GSSmo(_ll_GSSmo): - """ - Gaussian stabilizing selection on a single trait with moving - optimum. - - This class has the following attributes, whose names - are also `kwargs` for intitialization. The attribute names - also determine the order of positional arguments: - - :param optima: The optimal trait values - :type optima: list[fwdpy11.Optimum] - - .. note:: - - Instances of fwdpy11.Optimum must have valid - values for `when`. - - .. versionchanged:: 0.8.0 - - Refactored to use attrs and inherit from - low-level C++ class - - """ - - optima: typing.List[Optimum] = attr.ib() - - @optima.validator - def validate_optima(self, attribute, value): - if len(value) == 0: - raise ValueError("list of optima cannot be empty") - for o in value: - if o.when is None: - raise ValueError("Optimum.when is None") - - def __attrs_post_init__(self): - warnings.warn("use GaussianStabilizingSelection instead", DeprecationWarning) - super(GSSmo, self).__init__(self.optima) - - -@attr_add_asblack -@attr_class_to_from_dict_no_recurse -@attr.s(auto_attribs=True, frozen=True, repr_ns="fwdpy11") -class MultivariateGSS(_ll_MultivariateGSSmo): - """ - Multivariate gaussian stablizing selection. - - Maps a multidimensional trait to fitness using the Euclidian - distance of a vector of trait values to a vector of optima. - - Essentially, this is Equation 1 of - - Simons, Yuval B., Kevin Bullaughey, Richard R. Hudson, and Guy Sella. 2018. - "A Population Genetic Interpretation of GWAS Findings - for Human Quantitative Traits." - PLoS Biology 16 (3): e2002985. - - For the case of moving optima, see :class:`fwdpy11.MultivariateGSSmo`. - This class has the following attributes, whose names - are also `kwargs` for intitialization. The attribute names - also determine the order of positional arguments: - - :param optima: The optimum value for each trait over time - :type optima: numpy.ndarray or list[fwdpy11.PleiotropicOptima] - :param VS: Inverse strength of stablizing selection - :type VS: float or None - - .. note:: - - `VS` should be `None` if `optima` is list[fwdpy11.PleiotropicOptima] - - `VS` is :math:`\\omega^2` in the Simons et al. notation - - .. versionchanged:: 0.7.1 - Allow initialization with list of fwdpy11.PleiotropicOptima - - .. versionchanged:: 0.8.0 - - Refactored to use attrs and inherit from - low-level C++ class - """ - - optima: typing.Union[PleiotropicOptima, typing.List[float]] - VS: typing.Optional[float] = None - - def __attrs_post_init__(self): - warnings.warn("use GaussianStabilizingSelection instead", DeprecationWarning) - if self.VS is None: - super(MultivariateGSS, self).__init__([self.optima]) - else: - super(MultivariateGSS, self).__init__(self._convert_to_list()) - - def __getstate__(self): - return self.asdict() - - def __setstate__(self, d): - self.__dict__.update(d) - if self.VS is None: - super(MultivariateGSS, self).__init__([self.optima]) - else: - super(MultivariateGSS, self).__init__(self._convert_to_list()) - - def _convert_to_list(self): - if self.VS is None: - raise ValueError("VS must not be None") - return [PleiotropicOptima(optima=self.optima, VS=self.VS, when=0)] - - -@attr_add_asblack -@attr_class_pickle_with_super -@attr_class_to_from_dict_no_recurse -@attr.s(auto_attribs=True, frozen=True, repr_ns="fwdpy11") -class MultivariateGSSmo(_ll_MultivariateGSSmo): - """ - Multivariate gaussian stablizing selection with moving optima - This class has the following attributes, whose names - are also `kwargs` for intitialization. The attribute names - also determine the order of positional arguments: - - :param optima: list of optima over time - :type optima: list[fwdpy11.PleiotropicOptima] - - .. versionchanged:: 0.7.1 - - Allow initialization with list of fwdpy11.PleiotropicOptima - - .. versionchanged:: 0.8.0 - - Refactored to use attrs and inherit from - low-level C++ class - """ - - optima: typing.List[PleiotropicOptima] = attr.ib() - - @optima.validator - def validate_optima(self, attribute, value): - if len(value) == 0: - raise ValueError("list of optima cannot be empty") - for o in value: - if o.when is None: - raise ValueError("PleiotropicOptima.when is None") - - def __attrs_post_init__(self): - warnings.warn("use GaussianStabilizingSelection instead", DeprecationWarning) - super(MultivariateGSSmo, self).__init__(self.optima) - - @attr_add_asblack @attr_class_pickle_with_super @attr_class_to_from_dict @@ -680,11 +468,6 @@ def validate_timings(self, deme: int, demography: ForwardDemesGraph) -> None: self.gvalue_to_fitness.validate_timings(deme, demography) -@deprecated(reason="Use AdditivePleiotropy instead.") -class StrictAdditiveMultivariateEffects(AdditivePleiotropy): - pass - - class PyDiploidGeneticValue(_PyDiploidGeneticValue): def __init__(self, ndim: int, gvalue_to_fitness=None, noise=None): self.gvalue_to_fitness = gvalue_to_fitness diff --git a/fwdpy11/tskit_tools/_dump_tables_to_tskit.py b/fwdpy11/tskit_tools/_dump_tables_to_tskit.py index 3274f4d9a..265f04580 100644 --- a/fwdpy11/tskit_tools/_dump_tables_to_tskit.py +++ b/fwdpy11/tskit_tools/_dump_tables_to_tskit.py @@ -129,7 +129,6 @@ def _dump_tables_to_tskit( self, *, model_params=None, - demes_graph=None, population_metadata=None, data=None, seed=None, @@ -138,11 +137,6 @@ def _dump_tables_to_tskit( ) -> tskit.TreeSequence: from .._fwdpy11 import gsl_version, pybind11_version - if demes_graph is not None: - msg = "The demes_graph option is deprecated." - msg += " The demographic details should be passed in with model_params" - warnings.warn(msg, DeprecationWarning, stacklevel=2) - environment = tskit.provenance.get_environment( extra_libs={ "gsl": {"version": gsl_version()["gsl_version"]}, @@ -176,19 +170,10 @@ def _dump_tables_to_tskit( top_level_metadata = {"generation": self.generation} if model_params is not None: - try: - top_level_metadata["model_params"] = str(model_params.asdict()) - except Exception: - msg = "Passing a dict for model_params is deprecated." - msg += " Pass an instance of fwdpy11.ModelParams instead." - warnings.warn(msg, DeprecationWarning, stacklevel=2) - mp = {} - for key, value in model_params.items(): - mp[key] = str(value.asdict()) - top_level_metadata["model_params"] = mp - - if demes_graph is not None: - top_level_metadata["demes_graph"] = demes_graph.asdict() + assert isinstance( + model_params, fwdpy11.ModelParams + ), "model_params must be instance of fwdpy11.ModelParams" + top_level_metadata["model_params"] = str(model_params.asdict()) if data is not None: top_level_metadata["data"] = data