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

Edinburgh-Genome-Foundry / DnaChisel / 5190565251

pending completion
5190565251

push

github

veghp
Bump to v3.2.11

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

2966 of 3299 relevant lines covered (89.91%)

0.9 hits per line

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

92.78
/dnachisel/builtin_specifications/EnforcePatternOccurence.py
1
"""Implement EnforcePatternOccurence"""
2

3
from ..MutationSpace import MutationSpace
1✔
4
from ..SequencePattern import SequencePattern, DnaNotationPattern
1✔
5
from ..Location import Location
1✔
6
from ..biotools import reverse_complement
1✔
7
from ..DnaOptimizationProblem.DnaOptimizationProblem import DnaOptimizationProblem
1✔
8
from ..DnaOptimizationProblem.NoSolutionError import NoSolutionError
1✔
9
from ..Specification import Specification, SpecEvaluation
1✔
10

11
from .EnforceSequence import EnforceSequence
1✔
12

13

14
class EnforcePatternOccurence(Specification):
1✔
15
    """Enforce a number of occurrences of the given pattern in the sequence.
16

17
    Shorthand for annotations: "insert" (although this specification can be
18
    used to both insert new occurences of a pattern, or destroy supernumerary
19
    patterns)
20

21
    Parameters
22
    ----------
23
    pattern
24
      A SequencePattern or DnaNotationPattern or a string such as "AATTG",
25
      "BsmBI_site", etc. See SequencePattern documentation for more details.
26

27
    occurences
28
      Desired number of occurrences of the pattern.
29

30
    location
31
      Location of the DNA segment on which to enforce the pattern e.g.
32
      ``Location(10, 45, 1)``. Default ``None`` means the whole sequence.
33

34
    center
35
      If True, new inserted patterns will prioritize locations at the center
36
      of the specification's location. Else the insertion will happen at
37
      the beginning of the location.
38

39
    strand
40
      Alternative way to set the strand, meant to be used in two cases only:
41
      (1) in a Genbank annotation by setting ``strand='both'`` to indicate that
42
      the pattern could be on both strands (otherwise, only the
43
      feature's strand will be considered).
44
      (2) if you want to create a specification without preset location, but
45
      with a set strand: ``EnforcePatternOccurence('BsmBI_site', strand=1)``.
46
      The default 'from_location' uses the strand specified in ``location``, or
47
      if that is ``None``, it sets both strands.
48
    """
49

50
    best_possible_score = 0
1✔
51
    priority = -1
1✔
52
    shorthand_name = "insert"
1✔
53

54
    def __init__(
1✔
55
        self,
56
        pattern=None,
57
        occurences=1,
58
        location=None,
59
        strand="from_location",
60
        center=True,
61
        boost=1.0,
62
    ):
63
        """Initialize."""
64
        if isinstance(pattern, str):
1✔
65
            pattern = SequencePattern.from_string(pattern)
1✔
66
        self.pattern = pattern
1✔
67
        self.location = Location.from_data(location)
1✔
68
        if strand == "from_location":
1✔
69
            if self.location is None:
1✔
70
                self.strand = 0
1✔
71
            else:
72
                self.strand = self.location.strand
1✔
73
        elif strand == "both":
1✔
74
            self.strand = 0
1✔
75
        elif strand in [-1, 0, 1]:
1✔
76
            self.strand = strand
1✔
77
        else:
78
            raise ValueError("unknown strand: %s" % strand)
×
79
        self.occurences = occurences
1✔
80
        self.center = center
1✔
81
        self.boost = boost
1✔
82

83
    def initialized_on_problem(self, problem, role=None):
1✔
84
        copy_of_constraint = self._copy_with_full_span_if_no_location(problem)
1✔
85
        copy_of_constraint.location.strand = self.strand
1✔
86
        return copy_of_constraint
1✔
87

88
    def evaluate(self, problem):
1✔
89
        """Score the difference between expected and observed n_occurences."""
90
        matches = self.pattern.find_matches(
1✔
91
            problem.sequence,
92
            self.location,
93
        )
94
        score = -abs(len(matches) - self.occurences)
1✔
95

96
        if score == 0:
1✔
97
            message = "Passed. Pattern found at positions %s" % matches
1✔
98
        else:
99
            if self.occurences == 0:
1✔
100
                message = "Failed. Pattern not found."
×
101
            else:
102
                message = (
1✔
103
                    "Failed. Pattern found %d times instead of %d"
104
                    " wanted, at locations %s"
105
                ) % (len(matches), self.occurences, matches)
106
        return SpecEvaluation(
1✔
107
            self,
108
            problem,
109
            score,
110
            message=message,
111
            locations=[self.location],
112
            data=dict(matches=matches),
113
        )
114

115
    def localized(self, location, problem=None):
1✔
116
        """Localize the evaluation."""
117
        new_location = self.location.overlap_region(location)
1✔
118
        if new_location is None:
1✔
119
            return None
×
120
        # VoidSpecification(parent_specification=self)
121
        else:
122
            return self
1✔
123

124
    def insert_pattern_in_problem(self, problem, reverse=False):
1✔
125
        """Insert the pattern in the problem's sequence by successive tries.
126

127
        This heuristic is attempted to get the number of occurrences in the
128
        pattern from 0 to some number.
129
        """
130
        sequence_to_insert = self.pattern.sequence
1✔
131
        if reverse:
1✔
132
            sequence_to_insert = reverse_complement(sequence_to_insert)
1✔
133
        L = self.pattern.size
1✔
134
        starts = range(self.location.start, self.location.end - L)
1✔
135
        if self.center:
1✔
136
            center = 0.5 * (self.location.start + self.location.end)
1✔
137
            starts = sorted(starts, key=lambda s: abs(s - center))
1✔
138
        for start in starts:
1✔
139
            new_location = Location(start, start + L, self.location.strand)
1✔
140
            new_constraint = EnforceSequence(
1✔
141
                sequence=sequence_to_insert, location=new_location
142
            )
143
            new_space = MutationSpace.from_optimization_problem(
1✔
144
                problem, new_constraints=[new_constraint]
145
            )
146
            if len(new_space.unsolvable_segments) > 0:
1✔
147
                continue
1✔
148
            new_sequence = new_space.constrain_sequence(problem.sequence)
1✔
149
            new_constraints = problem.constraints + [new_constraint]
1✔
150
            new_problem = DnaOptimizationProblem(
1✔
151
                sequence=new_sequence,
152
                constraints=new_constraints,
153
                mutation_space=new_space,
154
                logger=None,
155
            )
156
            if self.evaluate(new_problem).passes:
1✔
157
                try:
1✔
158
                    new_problem.resolve_constraints()
1✔
159
                    problem.sequence = new_problem.sequence
1✔
160
                    return
1✔
161
                except NoSolutionError:
×
162
                    pass
×
163
        if (not reverse) and (not self.pattern.is_palyndromic):
1✔
164
            self.insert_pattern_in_problem(problem, reverse=True)
1✔
165
            return
1✔
166
        raise NoSolutionError(
×
167
            problem=problem,
168
            location=self.location,
169
            message="Insertion of pattern %s in %s failed"
170
            % (self.pattern.sequence, self.location),
171
        )
172

173
    def resolution_heuristic(self, problem):
1✔
174
        """Resolve using custom insertion if possible."""
175
        if isinstance(self.pattern, DnaNotationPattern):
1✔
176
            evaluation = self.evaluate(problem)
1✔
177
            if evaluation.passes:
1✔
178
                return
×
179
            n_matches = len(evaluation.data["matches"])
1✔
180
            if n_matches < self.occurences:
1✔
181
                other_constraints = [c for c in problem.constraints if c is not self]
1✔
182
                new_problem = problem
1✔
183
                for i in range(self.occurences - n_matches):
1✔
184
                    new_occurence_cst = self.copy_with_changes(
1✔
185
                        occurences=n_matches + i + 1
186
                    )
187
                    new_problem = DnaOptimizationProblem(
1✔
188
                        sequence=new_problem.sequence,
189
                        constraints=other_constraints + [new_occurence_cst],
190
                        mutation_space=problem.mutation_space,
191
                    )
192
                    new_occurence_cst.insert_pattern_in_problem(new_problem)
1✔
193
                problem.sequence = new_problem.sequence
1✔
194
                return
1✔
195
        problem.resolve_constraints_locally()  # default resolution method
1✔
196

197
    def label_parameters(self):
1✔
198
        # result = [('enzyme', self.enzyme) if (self.enzyme is not None)
199
        #           else (self.pattern.sequence
200
        #                 if hasattr(self.pattern, 'sequence')
201
        #                 else str(self.pattern))]
202
        result = [str(self.pattern)]
1✔
203
        if self.occurences != 1:
1✔
204
            result += ["occurence", str(self.occurences)]
1✔
205
        return result
1✔
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

© 2025 Coveralls, Inc