• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Edinburgh-Genome-Foundry / DnaChisel / 14225235949

02 Apr 2025 04:56PM UTC coverage: 90.508% (+0.5%) from 90.054%
14225235949

push

github

veghp
Bump to v3.2.14

1 of 1 new or added line in 1 file covered. (100.0%)

101 existing lines in 35 files now uncovered.

2994 of 3308 relevant lines covered (90.51%)

0.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.59
/dnachisel/builtin_specifications/AvoidChanges.py
1
"""Implementation of AvoidChanges."""
2

3
import numpy as np
1✔
4

5
from ..Specification import Specification, SpecEvaluation
1✔
6

7
# from .VoidSpecification import VoidSpecification
8
from ..biotools import (
1✔
9
    sequences_differences_array,
10
    group_nearby_indices,
11
)
12
from ..Location import Location
1✔
13

14

15
class AvoidChanges(Specification):
1✔
16
    """Specify that some locations of the sequence should not be changed.
17

18
    Shorthand for annotations: "change".
19

20
    Parameters
21
    ----------
22
    location
23
      Location object indicating the position of the segment that must be
24
      left unchanged. Alternatively,
25
      indices can be provided. If neither is provided, the assumed location
26
      is the whole sequence.
27

28
    indices
29
      List of indices that must be left unchanged.
30

31
    target_sequence
32
      At the moment, this is rather an internal variable. Do not use unless
33
      you're not afraid of side effects.
34

35
    """
36

37
    localization_interval_length = 6  # used when optimizing the minimize_diffs
1✔
38
    best_possible_score = 0
1✔
39
    enforced_by_nucleotide_restrictions = True
1✔
40
    shorthand_name = "keep"
1✔
41
    priority = -1000
1✔
42

43
    def __init__(
1✔
44
        self,
45
        max_edits=0,
46
        max_edits_percent=None,
47
        location=None,
48
        indices=None,
49
        target_sequence=None,
50
        boost=1.0,
51
    ):
52
        """Initialize."""
53
        if location is None and (indices is not None):
1✔
54
            location = (min(indices), max(indices) + 1)
1✔
55
        self.location = Location.from_data(location)
1✔
56
        if (self.location is not None) and self.location.strand == -1:
1✔
UNCOV
57
            self.location.strand = 1
×
58
        self.indices = np.array(indices) if (indices is not None) else None
1✔
59
        self.target_sequence = target_sequence
1✔
60
        self.max_edits = max_edits
1✔
61
        self.max_edits_percent = max_edits_percent
1✔
62
        self.boost = boost
1✔
63

64
    def extract_subsequence(self, sequence):
1✔
65
        """Extract a subsequence from the location or indices.
66

67
        Used to initialize the function when the sequence is provided.
68

69
        """
70
        if (self.location is None) and (self.indices is None):
1✔
71
            return sequence
1✔
72
        elif self.indices is not None:
1✔
73
            return "".join(np.array(list(sequence))[self.indices])
1✔
74
        else:  # self.location is not None:
75
            return self.location.extract_sequence(sequence)
1✔
76

77
    def initialized_on_problem(self, problem, role=None):
1✔
78
        """Find out what sequence it is that we are supposed to conserve."""
79
        result = self._copy_with_full_span_if_no_location(problem)
1✔
80
        L = len(result.location if result.indices is None else result.indices)
1✔
81
        if result.max_edits_percent is not None:
1✔
UNCOV
82
            result.max_edits = np.floor(result.max_edits_percent * L / 100.0)
×
83

84
        result.enforced_by_nucleotide_restrictions = result.max_edits == 0
1✔
85

86
        # Initialize the "target_sequence" in two cases:
87
        # - Always at the very beginning
88
        # - When the new sequence is bigger than the previous one
89
        #   (used in CircularDnaOptimizationProblem)
90
        if result.target_sequence is None or (
1✔
91
            len(result.target_sequence) < len(self.location)
92
        ):
93
            result = result.copy_with_changes()
1✔
94
            result.target_sequence = self.extract_subsequence(problem.sequence)
1✔
95
        return result
1✔
96

97
    def evaluate(self, problem):
1✔
98
        """Return a score equal to -number_of modifications.
99

100
        Locations are "binned" modifications regions. Each bin has a length
101
        in nucleotides equal to ``localization_interval_length`.`
102
        """
103
        target = self.target_sequence
1✔
104
        sequence = self.extract_subsequence(problem.sequence)
1✔
105
        differing_indices = np.nonzero(sequences_differences_array(sequence, target))[0]
1✔
106

107
        if self.indices is not None:
1✔
108
            differing_indices = self.indices[differing_indices]
1✔
109
        elif self.location is not None:
1✔
110
            if self.location.strand == -1:
1✔
UNCOV
111
                differing_indices = self.location.end - differing_indices
×
112
            else:
113
                differing_indices = differing_indices + self.location.start
1✔
114

115
        intervals = [
1✔
116
            (r[0], r[-1] + 1)
117
            for r in group_nearby_indices(
118
                differing_indices,
119
                max_group_spread=self.localization_interval_length,
120
            )
121
        ]
122
        locations = [Location(start, end, 1) for start, end in intervals]
1✔
123
        score = self.max_edits - len(differing_indices)
1✔
124
        return SpecEvaluation(self, problem, score=score, locations=locations)
1✔
125

126
    def localized(self, location, problem=None, with_righthand=False):
1✔
127
        """Localize the spec to the overlap of its location and the new."""
128

129
        if self.max_edits != 0:
1✔
130
            return self
1✔
131

132
        start, end = location.start, location.end
1✔
133
        if self.indices is not None:
1✔
134
            pos = ((start <= self.indices) & (self.indices < end)).nonzero()[0]
1✔
135
            new_indices = self.indices[pos]
1✔
136
            new_target = "".join(np.array(list(self.target_sequence))[pos])
1✔
137
            return self.copy_with_changes(
1✔
138
                indices=new_indices, target_sequence=new_target
139
            )
140
        else:
141
            new_location = self.location.overlap_region(location)
1✔
142
            if new_location is None:
1✔
143
                return None
1✔
144
            else:
145
                new_constraint = self.copy_with_changes(location=new_location)
1✔
146
                relative_location = new_location + (-self.location.start)
1✔
147
                new_constraint.target_sequence = relative_location.extract_sequence(
1✔
148
                    self.target_sequence
149
                )
150
                return new_constraint
1✔
151

152
    def restrict_nucleotides(self, sequence, location=None):
1✔
153
        """When localizing, forbid any nucleotide but the one already there."""
154
        if self.max_edits or self.max_edits_percent:
1✔
155
            return []
1✔
156
        if location is not None:
1✔
UNCOV
157
            start = max(location.start, self.location.start)
×
UNCOV
158
            end = min(location.end, self.location.end)
×
159
        else:
160
            start, end = self.location.start, self.location.end
1✔
161

162
        if self.indices is not None:
1✔
163
            return [
1✔
164
                ((i, i + 1), set([sequence[i : i + 1]]))
165
                for i in self.indices
166
                if start <= i < end
167
            ]
168
        else:
169
            return [((start, end), set([sequence[start:end]]))]
1✔
170

171
    def short_label(self):
1✔
172
        return "keep"
1✔
173

174
    def breach_label(self):
1✔
UNCOV
175
        return "edits"
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc