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

Edinburgh-Genome-Foundry / DnaChisel / 14225166311

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

push

github

veghp
Switch to Sphinx

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

90.24
/dnachisel/DnaOptimizationProblem/CircularDnaOptimizationProblem.py
1
"""Attempt at a special subclass of DnaOptimizationProblem for circular
2
sequences. Needs more docs. See example in the examples folder.
3
"""
4

5
from ..reports.optimization_reports import (
1✔
6
    write_optimization_report,
7
    write_no_solution_report,
8
)
9
from .DnaOptimizationProblem import DnaOptimizationProblem
1✔
10
from .NoSolutionError import NoSolutionError
1✔
11
from ..Location import Location
1✔
12

13

14
class CircularViewProblem(DnaOptimizationProblem):
1✔
15
    """Class representing an optimization problem as the concatenation of 3
16
    times the same sequence and specifications, in order to model the
17
    circularity of the DNA sequence"""
18

19
    def _replace_sequence(self, new_sequence):
1✔
20
        L = len(new_sequence) // 3
1✔
21

22
        def return_the_loony(a, b, c):
1✔
23
            """Return the element of a, b, c, which is unlike the other 2"""
24
            if a == b:
1✔
25
                return c
1✔
26
            elif a == c:
1✔
27
                return b
1✔
28
            else:
29
                return a
1✔
30

31
        self.sequence = 3 * "".join(
1✔
32
            [
33
                return_the_loony(
34
                    new_sequence[i],
35
                    new_sequence[i + L],
36
                    new_sequence[i + 2 * L],
37
                )
38
                for i in range(L)
39
            ]
40
        )
41

42

43
class CircularDnaOptimizationProblem(DnaOptimizationProblem):
1✔
44
    """Class for solving circular DNA optimization problems.
45

46
    The high-level interface is the same as for DnaOptimizationProblem:
47
    initialization, resolve_constraints(), and optimize() work the same way.
48
    """
49

50
    def _circularized_specs(self, specs, central_specs_only=False):
1✔
51
        L = len(self.sequence)
1✔
52
        new_specs = []
1✔
53
        for spec in specs:
1✔
54
            loc = spec.location
1✔
55
            if (loc.start, loc.end) == (0, L):
1✔
56
                new_location = Location(0, 3 * L, strand=loc.strand)
1✔
57
                new_specs.append(
1✔
58
                    spec.copy_with_changes(location=new_location, derived_from=spec)
59
                )
60
            else:
61
                new_location = loc + L
1✔
62
                new_specs += [spec.shifted(i) for i in [0, L, 2 * L]]
1✔
63
        central_loc = Location(L, 2 * L)
1✔
64
        if central_specs_only:
1✔
65
            new_specs = [
×
66
                spec
67
                for spec in new_specs
68
                if spec.location.overlap_region(central_loc) is not None
69
            ]
70
        return new_specs
1✔
71

72
    def _circularized_view(
1✔
73
        self,
74
        with_objectives=False,
75
        with_constraints=False,
76
        central_specs_only=False,
77
    ):
78

79
        return CircularViewProblem(
1✔
80
            sequence=3 * self.sequence,
81
            constraints=(
82
                self._circularized_specs(
83
                    self.constraints, central_specs_only=central_specs_only
84
                )
85
                if with_constraints
86
                else []
87
            ),
88
            objectives=(
89
                self._circularized_specs(
90
                    self.objectives, central_specs_only=central_specs_only
91
                )
92
                if with_objectives
93
                else []
94
            ),
95
            logger=self.logger,
96
        )
97

98
    def _recentered_evaluations(self, evals):
1✔
99
        L = len(self.sequence)
1✔
100
        central_loc = Location(L, 2 * L)
1✔
101
        for e in list(evals.evaluations):
1✔
102
            e.specification = e.specification.derived_from
1✔
103
            if e.locations is not None:
1✔
104
                e.locations = [
1✔
105
                    (loc - L)
106
                    for loc in e.locations
107
                    if loc.overlap_region(central_loc) is not None
108
                ]
109
                e.message = e.default_message
1✔
110
        return evals
1✔
111

112
    def constraints_evaluations(self, autopass=True):
1✔
113
        """Return the evaluation of constraints.
114

115
        The "autopass_constraints" enables to just assume that constraints
116
        enforced by the mutation space are verified.
117

118
        """
119
        circularized = self._circularized_view(
1✔
120
            with_constraints=True, central_specs_only=False
121
        )
122
        evals = circularized.constraints_evaluations(autopass=autopass)
1✔
123
        return self._recentered_evaluations(evals)
1✔
124

125
    def all_constraints_pass(self, autopass=True):
1✔
126
        """Return whether the current problem sequence passes all constraints."""
127
        evals = self.constraints_evaluations(autopass=autopass)
1✔
128
        return evals.all_evaluations_pass()
1✔
129

130
    def objectives_evaluations(self):
1✔
131
        circularized = self._circularized_view(
1✔
132
            with_objectives=True, central_specs_only=False
133
        )
134
        evals = circularized.objectives_evaluations()
1✔
135
        return self._recentered_evaluations(evals)
1✔
136

137
    def resolve_constraints(self, final_check=True):
1✔
138
        problem = self._circularized_view(with_constraints=True)
1✔
139
        L = len(self.sequence)
1✔
140
        central_loc = Location(L, 2 * L)
1✔
141
        for c in problem.constraints:
1✔
142
            if c.location.overlap_region(central_loc) is not None:
1✔
143
                problem.resolve_constraint(c)
1✔
144

145
        self.sequence = problem.sequence[L : 2 * L]
1✔
146
        if final_check:
1✔
147
            self.perform_final_constraints_check()
1✔
148

149
    def optimize(self):
1✔
150
        problem = self._circularized_view(with_constraints=True, with_objectives=True)
1✔
151
        problem.optimize()
1✔
152
        L = len(self.sequence)
1✔
153
        self.sequence = problem.sequence[L : 2 * L]
1✔
154

155
    def optimize_with_report(
1✔
156
        self,
157
        target,
158
        project_name="My project",
159
        file_path=None,
160
        file_content=None,
161
    ):
162
        """Resolve constraints, optimize objectives, write a multi-file report.
163

164
        WARNING: in case of optimization failure, the report generated will
165
        show a "pseudo-circular" sequence formed by concatenating the sequence
166
        with itself three times.
167

168
        TODO: fix the warning above, at some point?
169

170
        The report's content may vary depending on the optimization's success.
171

172
        Parameters
173
        ----------
174

175
        target
176
          Either a path to a folder that will contain the report, or a path to
177
          a zip archive, or "@memory" to return raw data of a zip archive
178
          containing the report.
179

180
        project_name
181
          Project name to write on PDF reports
182

183
        Returns
184
        -------
185

186
        (success, message, zip_data)
187
          Triplet where success is True/False, message is a one-line string
188
          summary indication whether some clash was found, or some solution, or
189
          maybe no solution was found because the random searches were too
190
          short
191
        """
192
        self.logger(message="Solving constraints")
1✔
193
        try:
1✔
194
            self.resolve_constraints()
1✔
195
        except NoSolutionError as error:
×
196
            problem = self._circularized_view(
×
197
                with_constraints=True, with_objectives=True
198
            )
199
            self.logger(message="No solution found: making report")
×
200
            data = write_no_solution_report(
×
201
                target,
202
                problem,
203
                error,
204
                file_path=file_path,
205
                file_content=file_content,
206
            )
207
            start, end, s = error.location.to_tuple()
×
208
            message = "No solution found in zone [%d, %d]: %s." % (
×
209
                start,
210
                end,
211
                str(error),
212
            )
213
            return False, message, data
×
214
        self.logger(message="Now optimizing the sequence")
1✔
215
        self.optimize()
1✔
216
        self.logger(message="Success! Generating report.")
1✔
217
        data = write_optimization_report(
1✔
218
            target,
219
            self,
220
            project_name=project_name,
221
            file_path=file_path,
222
            file_content=file_content,
223
        )
224
        return True, "Optimization successful.", data
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