Skip to content

Commit f39557d

Browse files
committed
remove coordinate shifting; closes #1404
1 parent 09f97ea commit f39557d

7 files changed

Lines changed: 191 additions & 78 deletions

File tree

CHANGELOG.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,28 @@
22
[0.2.1] - 2023-XX-XX
33
--------------------
44

5+
**Bug fixes**:
6+
7+
- Updates to SLiM support: updated the `active` flag in the SLiM code to be integer.`
8+
59
**Breaking changes**:
610

11+
- The `time_units` attribute of tree sequence metadata is now set to "generations"
12+
for SLiM output, avoiding the "time units mismatch warning".
13+
(:user:`nspope`, :pr:`1567`)
14+
15+
- Previously, Contigs specified with `left` and `right` arguments produced
16+
simulations having coordinates shifted so they were relative to `left`. This
17+
is no longer the case: coordinates in resulting simulations retain the
18+
original chromosome's coordinates. As a result, regions outside `[left,
19+
right)` will now have missing data. To produce simulation output with the
20+
previous behavior, use the `.trim()` method of the resulting TreeSequence.
21+
So, the Contig's `.original_coordinates` attribute is now called
22+
`.coordinates`, and several methods (e.g., `Contig.basic_contig()`) now take
23+
`left` and `right` arguments as well as `length`. Finally, arguments such as
24+
`relative_coordinates=True/False` to `Contig.dfe_breakpoints()` are no longer
25+
necessary and are deprecated. (:user:`petrelharp`, :pr:`1570`)
26+
727
- To add the possibility for a relationship between dominance and selection
828
coefficient, now each stdpopsim MutationType might have more than one
929
underlying SLiM mutation type; so, where this is recorded in top-level

stdpopsim/cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -813,9 +813,9 @@ def run_simulation(args):
813813
intervals_summary_str = f"[{left}, {right})"
814814
if intervals_summary_str is None:
815815
# case where no intervals specified but we have a DFE
816-
_, left, right = contig.original_coordinates
816+
_, left, right = contig.coordinates
817817
intervals = np.array([[left, right]])
818-
intervals_summary_str = f"[{intervals[0][0]}, {intervals[0][1]})"
818+
intervals_summary_str = f"[{left}, {right})"
819819

820820
dfe = species.get_dfe(args.dfe)
821821
contig.add_dfe(

stdpopsim/genomes.py

Lines changed: 63 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,25 @@
1414
logger = logging.getLogger(__name__)
1515

1616

17+
def _uniform_rate_map(left, right, length, rate):
18+
"""
19+
Makes a RateMap that is
20+
"""
21+
pos = [
22+
0.0,
23+
]
24+
x = []
25+
if left > 0:
26+
pos.append(left)
27+
x.append(np.nan)
28+
pos.append(right)
29+
x.append(rate)
30+
if right < length:
31+
pos.append(length)
32+
x.append(np.nan)
33+
return msprime.RateMap(position=pos, rate=x)
34+
35+
1736
@attr.s
1837
class Genome:
1938
"""
@@ -225,9 +244,6 @@ class Contig:
225244
and ``interval_list``. These must be of the same length, and the k-th DFE
226245
applies to the k-th interval; see :meth:`.add_dfe` for more information.
227246
228-
The contig may be a segment of a named chromosome. If so, the original
229-
coordinate system is used for :meth:`.add_dfe` and :meth:`add_single_site`.
230-
231247
:ivar mutation_rate: The rate of mutation per base per generation.
232248
:vartype mutation_rate: float
233249
:ivar ploidy: The ploidy of the contig. Defaults to 2 (diploid).
@@ -267,11 +283,11 @@ class Contig:
267283
:ivar interval_list: A list of :class:`np.array` objects containing integers.
268284
By default, the inital interval list spans the whole chromosome with the
269285
neutral DFE.
270-
:ivar original_coordinates: The location of the contig on a named chromosome,
286+
:ivar coordinates: The location of the contig on a named chromosome,
271287
as a tuple of the form `(chromosome, left, right)`. If `None`, the contig
272288
is assumed to be generic (i.e. it does not inherit a coordinate system
273289
from a larger chromosome).
274-
:vartype original_coordinates: tuple
290+
:vartype coordinates: tuple
275291
276292
.. note::
277293
To run stdpopsim simulations with alternative, user-specified mutation,
@@ -300,14 +316,14 @@ class Contig:
300316
exclusion_mask = attr.ib(default=None)
301317
dfe_list = attr.ib(factory=list)
302318
interval_list = attr.ib(factory=list)
303-
original_coordinates = attr.ib(default=None, type=tuple)
319+
coordinates = attr.ib(default=None, type=tuple)
304320

305321
def __attrs_post_init__(self):
306-
if self.original_coordinates is None:
307-
self.original_coordinates = (None, 0, int(self.length))
308-
_, left, right = self.original_coordinates
322+
if self.coordinates is None:
323+
self.coordinates = (None, 0, int(self.length))
324+
_, left, right = self.coordinates
309325
self.add_dfe(
310-
[[left, right]],
326+
np.array([[left, right]]),
311327
stdpopsim.dfe.neutral_dfe(),
312328
)
313329

@@ -491,20 +507,24 @@ def species_contig(
491507
logger.debug(
492508
f"Making flat chromosome {length_multiplier} * {chrom.id}"
493509
)
510+
recomb_map = msprime.RateMap.uniform(
511+
round(length_multiplier * chrom.length),
512+
chrom.recombination_rate,
513+
)
494514
else:
495515
logger.debug(
496-
f"Making flat contig of length {right - left} from {chrom.id}"
516+
f"Making flat contig from {left} to {right} for {chrom.id}"
517+
)
518+
recomb_map = _uniform_rate_map(
519+
left, right, chrom.length, chrom.recombination_rate
497520
)
498-
recomb_map = msprime.RateMap.uniform(
499-
right - left, chrom.recombination_rate
500-
)
501521
else:
502522
if length_multiplier != 1:
503523
raise ValueError("Cannot use length multiplier with empirical maps")
504524
logger.debug(f"Getting map for {chrom.id} from {genetic_map}")
505525
gm = species.get_genetic_map(genetic_map)
506526
recomb_map = gm.get_chromosome_map(chrom.id)
507-
recomb_map = recomb_map.slice(left=left, right=right, trim=True)
527+
recomb_map = recomb_map.slice(left=left, right=right)
508528

509529
inclusion_intervals = None
510530
exclusion_intervals = None
@@ -517,7 +537,7 @@ def species_contig(
517537
)
518538
else:
519539
inclusion_intervals = inclusion_mask
520-
inclusion_intervals = stdpopsim.utils.clip_and_shift_intervals(
540+
inclusion_intervals = stdpopsim.utils.clip_intervals(
521541
inclusion_intervals, left, right
522542
)
523543
if exclusion_mask is not None:
@@ -529,7 +549,7 @@ def species_contig(
529549
)
530550
else:
531551
exclusion_intervals = exclusion_mask
532-
exclusion_intervals = stdpopsim.utils.clip_and_shift_intervals(
552+
exclusion_intervals = stdpopsim.utils.clip_intervals(
533553
exclusion_intervals, left, right
534554
)
535555

@@ -562,7 +582,7 @@ def species_contig(
562582
gene_conversion_length=gene_conversion_length,
563583
inclusion_mask=inclusion_intervals,
564584
exclusion_mask=exclusion_intervals,
565-
original_coordinates=(chromosome, left, right),
585+
coordinates=(chromosome, left, right),
566586
ploidy=ploidy,
567587
)
568588

@@ -572,19 +592,30 @@ def species_contig(
572592
def length(self):
573593
return self.recombination_map.sequence_length
574594

595+
@property
596+
def original_coordinates(self):
597+
warnings.warn(
598+
stdpopsim.DeprecatedFeatureWarning(
599+
"The original_coordinates property is deprecated, "
600+
"since contigs are no longer shifted to be relative to their "
601+
"left endpoint: use Contig.coordinates instead."
602+
)
603+
)
604+
return self.coordinates
605+
575606
@property
576607
def origin(self):
577608
"""
578609
The location of the contig on a named chromosome as a string with format,
579610
"chromosome:left-right"; or None if a generic contig.
580611
"""
581-
chromosome, left, right = self.original_coordinates
612+
chromosome, left, right = self.coordinates
582613
if chromosome is None:
583614
return None
584615
else:
585616
return f"{chromosome}:{left}-{right}"
586617

587-
def dfe_breakpoints(self, relative_coordinates=True):
618+
def dfe_breakpoints(self, *, relative_coordinates=None):
588619
"""
589620
Returns two things: the sorted vector of endpoints of all intervals across
590621
all DFEs in the contig, and a vector of integer labels for these intervals,
@@ -607,21 +638,23 @@ def dfe_breakpoints(self, relative_coordinates=True):
607638
``breaks[k]``. Some intervals may not be covered by a DFE, in which
608639
case they will have the label ``-1`` (beware of python indexing!).
609640
610-
:param bool relative_coordinates: If True, the returned breakpoints
611-
will be relative to the start of the contig, rather than to the
612-
start of the chromosome to which to contig belongs.
613-
614641
:return: A tuple (breaks, dfe_labels).
615642
"""
643+
if relative_coordinates is not None:
644+
warnings.warn(
645+
stdpopsim.DeprecatedFeatureWarning(
646+
"The relative_coordinates argument is deprecated "
647+
"(and no longer needed),"
648+
"because contigs are no longer shifted to be relative to their "
649+
"left endpoint."
650+
)
651+
)
616652
breaks = np.unique(
617653
np.vstack(self.interval_list + [[[0, int(self.length)]]]) # also sorted
618654
)
619655
dfe_labels = np.full(len(breaks) - 1, -1, dtype="int")
620656
for j, intervals in enumerate(self.interval_list):
621657
dfe_labels[np.isin(breaks[:-1], intervals[:, 0], assume_unique=True)] = j
622-
if not relative_coordinates:
623-
_, left, _ = self.original_coordinates
624-
breaks += left
625658
return breaks, dfe_labels
626659

627660
def clear_dfes(self):
@@ -659,9 +692,9 @@ def add_dfe(self, intervals, DFE):
659692
:param array intervals: A valid set of intervals.
660693
:param DFE dfe: A DFE object.
661694
"""
662-
_, left, right = self.original_coordinates
663-
intervals = stdpopsim.utils.clip_and_shift_intervals(intervals, left, right)
664-
stdpopsim.utils._check_intervals_validity(intervals, start=0, end=self.length)
695+
_, left, right = self.coordinates
696+
intervals = stdpopsim.utils.clip_intervals(intervals, left, right)
697+
stdpopsim.utils._check_intervals_validity(intervals, start=left, end=right)
665698
for j, ints in enumerate(self.interval_list):
666699
self.interval_list[j] = stdpopsim.utils.mask_intervals(ints, intervals)
667700
self.dfe_list.append(DFE)

stdpopsim/species.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,17 @@ def get_contig(
184184
is to be simulated based on empirical information for a given species
185185
and chromosome.
186186
187+
*Coordinates:* If a particular chunk of a given chromosome is obtained
188+
(by specifying a `chromosome` ID and `left` and `right` coordinates),
189+
the resulting genomes will have coordinates matching the original
190+
(full) genome, and portions of the genome outside of `[left, right)`
191+
will be masked (and so contain missing data). So, coordinates of
192+
`genetic_map` and masks should be in coordinates of the original genome
193+
(so, not shifted to be relative to `left`). If it is desired to have
194+
output coordinates relative to `left`, use the
195+
:meth:`tskit.TreeSequence.trim` method on the result (for instance,
196+
`ts_shifted = ts.trim()`).
197+
187198
:param str chromosome: The ID of the chromosome to simulate.
188199
A complete list of chromosome IDs for each species can be found in the
189200
"Genome" subsection for the species in the :ref:`sec_catalog`.
@@ -218,11 +229,11 @@ def get_contig(
218229
and recombination rates are equal to the genome-wide average across all
219230
autosomal chromosomes.
220231
:param float left: The left coordinate (inclusive) of the region to
221-
keep on the chromosome. Defaults to 0. Genetic maps and masks are
222-
clipped to this boundary.
232+
keep on the chromosome. Defaults to 0. Remaining regions will have
233+
missing data when simulated.
223234
:param float right: The right coordinate (exclusive) of the region to
224235
keep on the chromosome. Defaults to the length of the chromosome.
225-
Genetic maps and masks are clipped to this boundary.
236+
Remaining regions will have missing data when simulated.
226237
:rtype: :class:`.Contig`
227238
:return: A :class:`.Contig` describing the section of the genome.
228239
"""

stdpopsim/utils.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -254,12 +254,11 @@ def mask_intervals(intervals, mask):
254254
return out
255255

256256

257-
def clip_and_shift_intervals(intervals, left, right):
257+
def clip_intervals(intervals, left, right):
258258
"""
259259
Intersect each interval in ``intervals`` with ``[left, right]``.
260260
``intervals`` should be a numpy array with two columns that are the
261-
beginning and end coordinates, respectively. The coordinates are then
262-
shifted such that ``left`` becomes 0.
261+
beginning and end coordinates, respectively.
263262
"""
264263
assert 0 <= left < right
265264
intervals = np.array(intervals).astype(int)
@@ -268,7 +267,6 @@ def clip_and_shift_intervals(intervals, left, right):
268267
intervals = np.clip(intervals[~out_of_bounds], left, right)
269268
if intervals.shape[0] == 0:
270269
warnings.warn(f"No intervals remain after clipping to [{left}, {right}]")
271-
intervals -= left
272270
return intervals
273271

274272

0 commit comments

Comments
 (0)