Source code for dnachisel.DnaOptimizationProblem.CircularDnaOptimizationProblem

"""Attempt at a special subclass of DnaOptimizationProblem for circular
sequences. Needs more docs. See example in the examples folder.
"""
from ..reports.optimization_reports import (
    write_optimization_report,
    write_no_solution_report,
)
from .DnaOptimizationProblem import DnaOptimizationProblem
from .NoSolutionError import NoSolutionError
from ..Location import Location


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

    def _replace_sequence(self, new_sequence):
        L = len(new_sequence) // 3

        def return_the_loony(a, b, c):
            """Return the element of a, b, c, which is unlike the other 2"""
            if a == b:
                return c
            elif a == c:
                return b
            else:
                return a

        self.sequence = 3 * "".join(
            [
                return_the_loony(
                    new_sequence[i],
                    new_sequence[i + L],
                    new_sequence[i + 2 * L],
                )
                for i in range(L)
            ]
        )


[docs]class CircularDnaOptimizationProblem(DnaOptimizationProblem): """Class for solving circular DNA optimization problems. The high-level interface is the same as for DnaOptimizationProblem: initialization, resolve_constraints(), and optimize() work the same way. """ def _circularized_specs(self, specs, central_specs_only=False): L = len(self.sequence) new_specs = [] for spec in specs: loc = spec.location if (loc.start, loc.end) == (0, L): new_location = Location(0, 3 * L, strand=loc.strand) new_specs.append( spec.copy_with_changes( location=new_location, derived_from=spec ) ) else: new_location = loc + L new_specs += [spec.shifted(i) for i in [0, L, 2 * L]] central_loc = Location(L, 2 * L) if central_specs_only: new_specs = [ spec for spec in new_specs if spec.location.overlap_region(central_loc) is not None ] return new_specs def _circularized_view( self, with_objectives=False, with_constraints=False, central_specs_only=False, ): return CircularViewProblem( sequence=3 * self.sequence, constraints=self._circularized_specs( self.constraints, central_specs_only=central_specs_only ) if with_constraints else [], objectives=self._circularized_specs( self.objectives, central_specs_only=central_specs_only ) if with_objectives else [], logger=self.logger, ) def _recentered_evaluations(self, evals): L = len(self.sequence) central_loc = Location(L, 2 * L) for e in list(evals.evaluations): e.specification = e.specification.derived_from if e.locations is not None: e.locations = [ (loc - L) for loc in e.locations if loc.overlap_region(central_loc) is not None ] e.message = e.default_message return evals
[docs] def constraints_evaluations(self, autopass=True): """Return the evaluation of constraints. The "autopass_constraints" enables to just assume that constraints enforced by the mutation space are verified. """ circularized = self._circularized_view( with_constraints=True, central_specs_only=False ) evals = circularized.constraints_evaluations(autopass=autopass) return self._recentered_evaluations(evals)
[docs] def all_constraints_pass(self, autopass=True): """Return whether the current problem sequence passes all constraints. """ evals = self.constraints_evaluations(autopass=autopass) return evals.all_evaluations_pass()
[docs] def objectives_evaluations(self): circularized = self._circularized_view( with_objectives=True, central_specs_only=False ) evals = circularized.objectives_evaluations() return self._recentered_evaluations(evals)
[docs] def resolve_constraints(self, final_check=True): problem = self._circularized_view(with_constraints=True) L = len(self.sequence) central_loc = Location(L, 2 * L) for c in problem.constraints: if c.location.overlap_region(central_loc) is not None: problem.resolve_constraint(c) self.sequence = problem.sequence[L : 2 * L] if final_check: self.perform_final_constraints_check()
[docs] def optimize(self): problem = self._circularized_view( with_constraints=True, with_objectives=True ) problem.optimize() L = len(self.sequence) self.sequence = problem.sequence[L : 2 * L]
[docs] def optimize_with_report( self, target, project_name="My project", file_path=None, file_content=None, ): """Resolve constraints, optimize objectives, write a multi-file report. WARNING: in case of optimization failure, the report generated will show a "pseudo-circular" sequence formed by concatenating the sequence with itself three times. TODO: fix the warning above, at some point? The report's content may vary depending on the optimization's success. Parameters ---------- target Either a path to a folder that will contain the report, or a path to a zip archive, or "@memory" to return raw data of a zip archive containing the report. project_name Project name to write on PDF reports Returns ------- (success, message, zip_data) Triplet where success is True/False, message is a one-line string summary indication whether some clash was found, or some solution, or maybe no solution was found because the random searches were too short """ self.logger(message="Solving constraints") try: self.resolve_constraints() except NoSolutionError as error: problem = self._circularized_view( with_constraints=True, with_objectives=True ) self.logger(message="No solution found: making report") data = write_no_solution_report( target, problem, error, file_path=file_path, file_content=file_content, ) start, end, s = error.location.to_tuple() message = "No solution found in zone [%d, %d]: %s." % ( start, end, str(error), ) return False, message, data self.logger(message="Now optimizing the sequence") self.optimize() self.logger(message="Success! Generating report.") data = write_optimization_report( target, self, project_name=project_name, file_path=file_path, file_content=file_content, ) return True, "Optimization successful.", data