コード例 #1
0
    def _build_problem(self, essential_reactions,
                       use_nullspace_simplification):
        logger.debug("Starting to formulate OptKnock problem")

        self.essential_reactions = find_essential_reactions(self._model).union(
            self._model.exchanges)
        if essential_reactions:
            self.essential_reactions.update(
                set(
                    get_reaction_for(self._model, r)
                    for r in essential_reactions))

        reactions = set(self._model.reactions) - self.essential_reactions
        if use_nullspace_simplification:
            reactions = self._reduce_to_nullspace(reactions)
        else:
            self.reaction_groups = None

        self._make_dual()

        self._combine_primal_and_dual()
        logger.debug("Primal and dual successfully combined")

        y_vars = {}
        constrained_dual_vars = set()
        for reaction in reactions:
            if reaction not in self.essential_reactions and reaction.lower_bound <= 0 <= reaction.upper_bound:
                y_var, constrained_vars = self._add_knockout_constraints(
                    reaction)
                y_vars[y_var] = reaction
                constrained_dual_vars.update(constrained_vars)
        self._y_vars = y_vars

        primal_objective = self._model.solver.objective
        dual_objective = self._model.solver.interface.Objective.clone(
            self._dual_problem.objective, model=self._model.solver)

        reduced_expression = Add(*((c * v) for v, c in dual_objective.
                                   expression.as_coefficients_dict().items()
                                   if v not in constrained_dual_vars))
        dual_objective = self._model.solver.interface.Objective(
            reduced_expression, direction=dual_objective.direction)

        optimality_constraint = self._model.solver.interface.Constraint(
            primal_objective.expression - dual_objective.expression,
            lb=0,
            ub=0,
            name="inner_optimality")
        self._model.solver.add(optimality_constraint)
        logger.debug("Inner optimality constrained")

        logger.debug("Adding constraint for number of knockouts")
        knockout_number_constraint = self._model.solver.interface.Constraint(
            Add(*y_vars), lb=len(y_vars), ub=len(y_vars))
        self._model.solver.add(knockout_number_constraint)
        self._number_of_knockouts_constraint = knockout_number_constraint
コード例 #2
0
    def run(self, target=None, max_enforced_flux=0.9, number_of_results=10, exclude=(), simulation_method=fba,
            simulation_kwargs=None):
        """
        Performs a Flux Scanning based on Enforced Objective Flux (FSEOF) analysis.

        Parameters
        ----------
        target: str, Reaction, Metabolite
            The target for optimization.
        max_enforced_flux : float, optional
            The maximal flux of secondary_objective that will be enforced, relative to the theoretical maximum (
            defaults to 0.9).
        number_of_results : int, optional
            The number of enforced flux levels (defaults to 10).
        exclude : Iterable of reactions or reaction ids that will not be included in the output.

        Returns
        -------
        FseofResult
            An object containing the identified reactions and the used parameters.

        References
        ----------
        .. [1] H. S. Choi, S. Y. Lee, T. Y. Kim, and H. M. Woo, 'In silico identification of gene amplification targets
        for improvement of lycopene production.,' Appl Environ Microbiol, vol. 76, no. 10, pp. 3097–3105, May 2010.

        """
        model = self.model
        target = get_reaction_for(model, target)

        simulation_kwargs = simulation_kwargs if simulation_kwargs is not None else {}
        simulation_kwargs['objective'] = self.primary_objective

        if 'reference' not in simulation_kwargs:
            reference = simulation_kwargs['reference'] = pfba(model, **simulation_kwargs)
        else:
            reference = simulation_kwargs['reference']

        ndecimals = config.ndecimals

        # Exclude list
        exclude = list(exclude) + model.boundary
        exclude_ids = [target.id]
        for reaction in exclude:
            if isinstance(reaction, Reaction):
                exclude_ids.append(reaction.id)
            else:
                exclude_ids.append(reaction)

        with TimeMachine() as tm:

            tm(do=int, undo=partial(setattr, model, "objective", model.objective))
            tm(do=int, undo=partial(setattr, target, "lower_bound", target.lower_bound))
            tm(do=int, undo=partial(setattr, target, "upper_bound", target.upper_bound))

            # Find initial flux of enforced reaction
            initial_fluxes = reference.fluxes
            initial_flux = round(initial_fluxes[target.id], ndecimals)

            # Find theoretical maximum of enforced reaction
            max_theoretical_flux = round(fba(model, objective=target.id, reactions=[target.id]).fluxes[target.id],
                                         ndecimals)

            max_flux = max_theoretical_flux * max_enforced_flux

            # Calculate enforcement levels
            levels = [initial_flux + (i + 1) * (max_flux - initial_flux) / number_of_results for i in
                      range(number_of_results)]

            # FSEOF results
            results = {reaction.id: [] for reaction in model.reactions}

            for level in levels:
                target.lower_bound = level
                target.upper_bound = level
                solution = simulation_method(model, **simulation_kwargs)
                for reaction_id, flux in solution.fluxes.iteritems():
                    results[reaction_id].append(round(flux, ndecimals))

        # Test each reaction
        fseof_reactions = []
        for reaction_id, fluxes in results.items():
            if reaction_id not in exclude_ids \
                    and max(abs(max(fluxes)), abs(min(fluxes))) > abs(reference[reaction_id]) \
                    and min(fluxes) * max(fluxes) >= 0:
                fseof_reactions.append(model.reactions.get_by_id(reaction_id))

        results = {rea.id: results[rea.id] for rea in fseof_reactions}
        run_args = dict(max_enforced_flux=max_enforced_flux,
                        number_of_results=number_of_results,
                        solution_method=simulation_method,
                        simulation_kwargs=simulation_kwargs,
                        exclude=exclude)

        return FSEOFResult(fseof_reactions, target, model, self.primary_objective, levels, results, run_args, reference)
コード例 #3
0
    def run(self,
            target=None,
            biomass=None,
            substrate=None,
            max_knockouts=5,
            variable_size=True,
            simulation_method=fba,
            growth_coupled=False,
            max_evaluations=20000,
            population_size=200,
            max_results=50,
            use_nullspace_simplification=True,
            seed=None,
            **kwargs):
        """
        Parameters
        ----------
        target : str, Metabolite or Reaction
            The design target
        biomass : str, Metabolite or Reaction
            The biomass definition in the model
        substrate : str, Metabolite or Reaction
            The main carbon source
        max_knockouts : int
            Max number of knockouts allowed
        variable_size : bool
            If true, all candidates have the same size. Otherwise the candidate size can be from 1 to max_knockouts.
        simulation_method : function
            Any method from cameo.flux_analysis.simulation or equivalent
        growth_coupled : bool
            If true will use the minimum flux rate to compute the fitness
        max_evaluations : int
            Number of evaluations before stop
        population_size : int
            Number of individuals in each generation
        max_results : int
            Max number of different designs to return if found.
        kwargs : dict
            Arguments for the simulation method.
        seed : int
            A seed for random.
        use_nullspace_simplification : Boolean (default True)
            Use a basis for the nullspace to find groups of reactions whose fluxes are multiples of each other and dead
            end reactions. From each of these groups only 1 reaction will be included as a possible knockout.


        Returns
        -------
        OptGeneResult
        """

        target = get_reaction_for(self._model, target)
        biomass = get_reaction_for(self._model, biomass)
        substrate = get_reaction_for(self._model, substrate)

        if growth_coupled:
            objective_function = biomass_product_coupled_min_yield(
                biomass, target, substrate)
        else:
            objective_function = biomass_product_coupled_yield(
                biomass, target, substrate)
        if self.manipulation_type == "genes":
            optimization_algorithm = GeneKnockoutOptimization(
                model=self._model,
                heuristic_method=self._algorithm,
                essential_genes=self._essential_genes,
                plot=self.plot,
                objective_function=objective_function,
                use_nullspace_simplification=use_nullspace_simplification)
        elif self.manipulation_type == "reactions":
            optimization_algorithm = ReactionKnockoutOptimization(
                model=self._model,
                heuristic_method=self._algorithm,
                essential_reactions=self._essential_reactions,
                plot=self.plot,
                objective_function=objective_function,
                use_nullspace_simplification=use_nullspace_simplification)
        else:
            raise ValueError("Invalid manipulation type %s" %
                             self.manipulation_type)
        optimization_algorithm.simulation_kwargs = kwargs
        optimization_algorithm.simulation_method = simulation_method
        optimization_algorithm.archiver = ProductionStrainArchive()

        result = optimization_algorithm.run(max_evaluations=max_evaluations,
                                            pop_size=population_size,
                                            max_size=max_knockouts,
                                            variable_size=variable_size,
                                            maximize=True,
                                            max_archive_size=max_results,
                                            seed=seed,
                                            **kwargs)

        kwargs.update(optimization_algorithm.simulation_kwargs)

        return OptGeneResult(self._model, result, objective_function,
                             simulation_method, self.manipulation_type,
                             biomass, target, substrate, kwargs)
コード例 #4
0
    def run(self,
            target=None,
            biomass=None,
            substrate=None,
            max_swaps=5,
            variable_size=True,
            simulation_method=fba,
            growth_coupled=False,
            max_evaluations=20000,
            population_size=200,
            time_machine=None,
            max_results=50,
            seed=None,
            **kwargs):
        """
        Parameters
        ----------
        target : str, Metabolite or Reaction
            The design target.
        biomass : str, Metabolite or Reaction
            The biomass definition in the model.
        substrate : str, Metabolite or Reaction
            The main carbon source.
        max_swaps : int
            Max number of swaps allowed.
        variable_size : bool
            If true, all candidates have the same size. Otherwise the candidate size can be from 1 to max_knockouts.
        simulation_method : function
            Any method from cameo.flux_analysis.simulation or equivalent.
        growth_coupled : bool
            If true will use the minimum flux rate to compute the fitness.
        max_evaluations : int
            Number of evaluations before stop.
        population_size : int
            Number of individuals in each generation.
        time_machine : TimeMachine
            See TimeMachine.
        max_results : int
            Max number of different designs to return if found.
        kwargs : dict
            Arguments for the simulation method.
        seed : int
            A seed for random.


        Returns
        -------
        HeuristicOptSwapResult
        """

        target = get_reaction_for(self._model, target)
        biomass = get_reaction_for(self._model, biomass)
        substrate = get_reaction_for(self._model, substrate)

        if growth_coupled:
            objective_function = biomass_product_coupled_min_yield(
                biomass, target, substrate)
        else:
            objective_function = biomass_product_coupled_yield(
                biomass, target, substrate)

        optimization_algorithm = CofactorSwapOptimization(
            model=self._model,
            cofactor_id_swaps=self._cofactor_id_swaps,
            skip_reactions=self._skip_reactions,
            objective_function=objective_function)

        optimization_algorithm.simulation_kwargs = kwargs
        optimization_algorithm.simulation_method = simulation_method
        optimization_algorithm.archiver = ProductionStrainArchive()

        result = optimization_algorithm.run(max_evaluations=max_evaluations,
                                            pop_size=population_size,
                                            max_size=max_swaps,
                                            variable_size=variable_size,
                                            maximize=True,
                                            max_archive_size=max_results,
                                            seed=seed,
                                            **kwargs)

        kwargs.update(optimization_algorithm.simulation_kwargs)

        return HeuristicOptSwapResult(self._model, result, self._swap_pairs,
                                      objective_function, simulation_method,
                                      biomass, target, substrate, kwargs)
コード例 #5
0
def phenotypic_phase_plane(model,
                           variables,
                           objective=None,
                           source=None,
                           points=20,
                           view=None):
    """Phenotypic phase plane analysis [1].

    Implements a phenotypic phase plan analysis with interpretation same as
    that presented in [1] but calculated by optimizing the model for all
    steps of the indicated variables (instead of using shadow prices).

    Parameters
    ----------
    model: cobra.Model
    variables: str or reaction or iterable
        A reaction ID, reaction, or list of reactions to be varied.
    objective: str or reaction or optlang.Objective or Metabolite, optional
        An objective, a reaction's flux, or a metabolite's production to be minimized/maximized
        (defaults to the current model objective).
    source: Reaction or reaction identifier
        The reaction to use as the source when calculating mass and carbon yield. Set to the medium reaction with the
        highest input carbon flux if left None.
    points: int or iterable
        Number of points to be interspersed between the variable bounds.
        A list of same same dimensions as `variables` can be used to specify
        variable specific numbers of points.
    view: SequentialView or MultiprocessingView or ipython.cluster.DirectView
        A parallelization view.

    Returns
    -------
    PhenotypicPhasePlaneResult
        The phenotypic phase plane with flux, mol carbon input / mol carbon
        output, gram input / gram output

    References
    ----------
    [1] Edwards, J. S., Ramakrishna, R. and Palsson, B. O. (2002). Characterizing the metabolic phenotype: a phenotype
        phase plane analysis. Biotechnology and Bioengineering, 77(1), 27–36. doi:10.1002/bit.10047
    """

    if isinstance(variables, six.string_types):
        variables = [variables]
    elif isinstance(variables, Reaction):
        variables = [variables]
    variable_ids = [
        var if isinstance(var, six.string_types) else var.id
        for var in variables
    ]

    if view is None:
        view = config.default_view
    with model:
        if objective is not None:
            if isinstance(objective, Metabolite):
                try:
                    objective = model.reactions.get_by_id("DM_%s" %
                                                          objective.id)
                except KeyError:
                    objective = model.add_boundary(objective, type='demand')
            # try:
            #     objective = model.reaction_for(objective, time_machine=tm)
            # except KeyError:
            #     pass

            model.objective = objective

        if source is not None:
            source_reaction = get_reaction_for(model, source)
        else:
            source_reaction = get_c_source_reaction(model)

        variable_reactions = model.reactions.get_by_any(variables)
        variables_min_max = flux_variability_analysis(
            model, reactions=variable_reactions, view=SequentialView())
        grid = [
            numpy.linspace(lower_bound, upper_bound, points, endpoint=True)
            for reaction_id, lower_bound, upper_bound in
            variables_min_max.data_frame.loc[variable_ids].itertuples()
        ]
        grid_generator = itertools.product(*grid)
        chunks_of_points = partition(list(grid_generator), len(view))
        evaluator = _PhenotypicPhasePlaneChunkEvaluator(
            model, variable_reactions, source_reaction)
        chunk_results = view.map(evaluator, chunks_of_points)
        envelope = reduce(list.__add__, chunk_results)

    nice_variable_ids = [_nice_id(reaction) for reaction in variable_reactions]
    variable_reactions_ids = [reaction.id for reaction in variable_reactions]
    phase_plane = pandas.DataFrame(
        envelope,
        columns=(variable_reactions_ids + [
            'objective_lower_bound', 'objective_upper_bound',
            'c_yield_lower_bound', 'c_yield_upper_bound',
            'mass_yield_lower_bound', 'mass_yield_upper_bound'
        ]))

    if objective is None:
        objective = model.objective
    nice_objective_id = _nice_id(objective)

    objective = objective.id if isinstance(objective,
                                           Reaction) else str(objective)
    return PhenotypicPhasePlaneResult(
        phase_plane,
        variable_reactions_ids,
        objective,
        nice_variable_ids=nice_variable_ids,
        source_reaction=_nice_id(source_reaction),
        nice_objective_id=nice_objective_id)
コード例 #6
0
    def run(self,
            max_knockouts=5,
            biomass=None,
            target=None,
            max_results=1,
            *args,
            **kwargs):
        """
        Perform the OptKnock simulation

        Parameters
        ----------
        target: str, Metabolite or Reaction
            The design target
        biomass: str, Metabolite or Reaction
            The biomass definition in the model
        max_knockouts: int
            Max number of knockouts allowed
        max_results: int
            Max number of different designs to return if found

        Returns
        -------
        OptKnockResult
        """
        # TODO: why not required arguments?
        if biomass is None or target is None:
            raise ValueError('missing biomass and/or target reaction')
        target = get_reaction_for(self._model, target, add=False)
        biomass = get_reaction_for(self._model, biomass, add=False)

        knockout_list = []
        fluxes_list = []
        production_list = []
        biomass_list = []
        loader_id = ui.loading()
        with self._model:
            self._model.objective = target.id
            self._number_of_knockouts_constraint.lb = self._number_of_knockouts_constraint.ub - max_knockouts
            count = 0
            while count < max_results:
                try:
                    solution = self._model.optimize(raise_error=True)
                except OptimizationError as e:
                    logger.debug(
                        "Problem could not be solved. Terminating and returning "
                        + str(count) + " solutions")
                    logger.debug(str(e))
                    break

                knockouts = tuple(reaction
                                  for y, reaction in self._y_vars.items()
                                  if round(y.primal, 3) == 0)
                assert len(knockouts) <= max_knockouts

                if self.reaction_groups:
                    combinations = decompose_reaction_groups(
                        self.reaction_groups, knockouts)
                    for kos in combinations:
                        knockout_list.append({r.id for r in kos})
                        fluxes_list.append(solution.fluxes)
                        production_list.append(solution.f)
                        biomass_list.append(solution.fluxes[biomass.id])
                else:
                    knockout_list.append({r.id for r in knockouts})
                    fluxes_list.append(solution.fluxes)
                    production_list.append(solution.f)
                    biomass_list.append(solution.fluxes[biomass.id])

                # Add an integer cut
                y_vars_to_cut = [
                    y for y in self._y_vars if round(y.primal, 3) == 0
                ]
                integer_cut = self._model.solver.interface.Constraint(
                    Add(*y_vars_to_cut),
                    lb=1,
                    name="integer_cut_" + str(count))

                if len(knockouts) < max_knockouts:
                    self._number_of_knockouts_constraint.lb = self._number_of_knockouts_constraint.ub - len(
                        knockouts)
                self._model.add_cons_vars(integer_cut)
                count += 1

            ui.stop_loader(loader_id)

            return OptKnockResult(self._original_model, knockout_list,
                                  fluxes_list, production_list, biomass_list,
                                  target.id, biomass)
コード例 #7
0
    def run(self,
            target=None,
            max_enforced_flux=0.9,
            number_of_results=10,
            exclude=(),
            simulation_method=fba,
            simulation_kwargs=None):
        """
        Performs a Flux Scanning based on Enforced Objective Flux (FSEOF) analysis.

        Parameters
        ----------
        target: str, Reaction, Metabolite
            The target for optimization.
        max_enforced_flux : float, optional
            The maximal flux of secondary_objective that will be enforced, relative to the theoretical maximum (
            defaults to 0.9).
        number_of_results : int, optional
            The number of enforced flux levels (defaults to 10).
        exclude : Iterable of reactions or reaction ids that will not be included in the output.

        Returns
        -------
        FseofResult
            An object containing the identified reactions and the used parameters.

        References
        ----------
        .. [1] H. S. Choi, S. Y. Lee, T. Y. Kim, and H. M. Woo, 'In silico identification of gene amplification targets
        for improvement of lycopene production.,' Appl Environ Microbiol, vol. 76, no. 10, pp. 3097–3105, May 2010.

        """
        model = self.model
        target = get_reaction_for(model, target)

        simulation_kwargs = simulation_kwargs if simulation_kwargs is not None else {}
        simulation_kwargs['objective'] = self.primary_objective

        if 'reference' not in simulation_kwargs:
            reference = simulation_kwargs['reference'] = pfba(
                model, **simulation_kwargs)
        else:
            reference = simulation_kwargs['reference']

        ndecimals = config.ndecimals

        # Exclude list
        exclude = list(exclude) + model.exchanges
        exclude_ids = [target.id]
        for reaction in exclude:
            if isinstance(reaction, Reaction):
                exclude_ids.append(reaction.id)
            else:
                exclude_ids.append(reaction)

        with TimeMachine() as tm:

            tm(do=int,
               undo=partial(setattr, model, "objective", model.objective))
            tm(do=int,
               undo=partial(setattr, target, "lower_bound",
                            target.lower_bound))
            tm(do=int,
               undo=partial(setattr, target, "upper_bound",
                            target.upper_bound))

            # Find initial flux of enforced reaction
            initial_fluxes = reference.fluxes
            initial_flux = round(initial_fluxes[target.id], ndecimals)

            # Find theoretical maximum of enforced reaction
            max_theoretical_flux = round(
                fba(model, objective=target.id,
                    reactions=[target.id]).fluxes[target.id], ndecimals)

            max_flux = max_theoretical_flux * max_enforced_flux

            # Calculate enforcement levels
            levels = [
                initial_flux + (i + 1) *
                (max_flux - initial_flux) / number_of_results
                for i in range(number_of_results)
            ]

            # FSEOF results
            results = {reaction.id: [] for reaction in model.reactions}

            for level in levels:
                target.lower_bound = level
                target.upper_bound = level
                solution = simulation_method(model, **simulation_kwargs)
                for reaction_id, flux in solution.fluxes.iteritems():
                    results[reaction_id].append(round(flux, ndecimals))

        # Test each reaction
        fseof_reactions = []
        for reaction_id, fluxes in results.items():
            if reaction_id not in exclude_ids \
                    and max(abs(max(fluxes)), abs(min(fluxes))) > abs(reference[reaction_id]) \
                    and min(fluxes) * max(fluxes) >= 0:
                fseof_reactions.append(model.reactions.get_by_id(reaction_id))

        results = {rea.id: results[rea.id] for rea in fseof_reactions}
        run_args = dict(max_enforced_flux=max_enforced_flux,
                        number_of_results=number_of_results,
                        solution_method=simulation_method,
                        simulation_kwargs=simulation_kwargs,
                        exclude=exclude)

        return FSEOFResult(fseof_reactions, target, model,
                           self.primary_objective, levels, results, run_args,
                           reference)
コード例 #8
0
    def run(self, target=None, biomass=None, substrate=None, max_knockouts=5, variable_size=True,
            simulation_method=fba, growth_coupled=False, max_evaluations=20000, population_size=200,
            max_results=50, use_nullspace_simplification=True, seed=None, **kwargs):
        """
        Parameters
        ----------
        target : str, Metabolite or Reaction
            The design target
        biomass : str, Metabolite or Reaction
            The biomass definition in the model
        substrate : str, Metabolite or Reaction
            The main carbon source
        max_knockouts : int
            Max number of knockouts allowed
        variable_size : bool
            If true, all candidates have the same size. Otherwise the candidate size can be from 1 to max_knockouts.
        simulation_method : function
            Any method from cameo.flux_analysis.simulation or equivalent
        growth_coupled : bool
            If true will use the minimum flux rate to compute the fitness
        max_evaluations : int
            Number of evaluations before stop
        population_size : int
            Number of individuals in each generation
        max_results : int
            Max number of different designs to return if found.
        kwargs : dict
            Arguments for the simulation method.
        seed : int
            A seed for random.
        use_nullspace_simplification : Boolean (default True)
            Use a basis for the nullspace to find groups of reactions whose fluxes are multiples of each other and dead
            end reactions. From each of these groups only 1 reaction will be included as a possible knockout.


        Returns
        -------
        OptGeneResult
        """

        target = get_reaction_for(self._model, target)
        biomass = get_reaction_for(self._model, biomass)
        substrate = get_reaction_for(self._model, substrate)

        if growth_coupled:
            objective_function = biomass_product_coupled_min_yield(biomass, target, substrate)
        else:
            objective_function = biomass_product_coupled_yield(biomass, target, substrate)
        if self.manipulation_type == "genes":
            optimization_algorithm = GeneKnockoutOptimization(
                model=self._model,
                heuristic_method=self._algorithm,
                essential_genes=self._essential_genes,
                plot=self.plot,
                objective_function=objective_function,
                use_nullspace_simplification=use_nullspace_simplification)
        elif self.manipulation_type == "reactions":
            optimization_algorithm = ReactionKnockoutOptimization(
                model=self._model,
                heuristic_method=self._algorithm,
                essential_reactions=self._essential_reactions,
                plot=self.plot,
                objective_function=objective_function,
                use_nullspace_simplification=use_nullspace_simplification)
        else:
            raise ValueError("Invalid manipulation type %s" % self.manipulation_type)
        optimization_algorithm.simulation_kwargs = kwargs
        optimization_algorithm.simulation_method = simulation_method
        optimization_algorithm.archiver = ProductionStrainArchive()

        result = optimization_algorithm.run(max_evaluations=max_evaluations,
                                            pop_size=population_size,
                                            max_size=max_knockouts,
                                            variable_size=variable_size,
                                            maximize=True,
                                            max_archive_size=max_results,
                                            seed=seed,
                                            **kwargs)

        kwargs.update(optimization_algorithm.simulation_kwargs)

        return OptGeneResult(self._model, result, objective_function, simulation_method, self.manipulation_type,
                             biomass, target, substrate, kwargs)
コード例 #9
0
    def run(self, target=None, biomass=None, substrate=None, max_swaps=5, variable_size=True,
            simulation_method=fba, growth_coupled=False, max_evaluations=20000, population_size=200,
            time_machine=None, max_results=50, seed=None, **kwargs):
        """
        Parameters
        ----------
        target : str, Metabolite or Reaction
            The design target.
        biomass : str, Metabolite or Reaction
            The biomass definition in the model.
        substrate : str, Metabolite or Reaction
            The main carbon source.
        max_swaps : int
            Max number of swaps allowed.
        variable_size : bool
            If true, all candidates have the same size. Otherwise the candidate size can be from 1 to max_knockouts.
        simulation_method : function
            Any method from cameo.flux_analysis.simulation or equivalent.
        growth_coupled : bool
            If true will use the minimum flux rate to compute the fitness.
        max_evaluations : int
            Number of evaluations before stop.
        population_size : int
            Number of individuals in each generation.
        time_machine : TimeMachine
            See TimeMachine.
        max_results : int
            Max number of different designs to return if found.
        kwargs : dict
            Arguments for the simulation method.
        seed : int
            A seed for random.


        Returns
        -------
        HeuristicOptSwapResult
        """

        target = get_reaction_for(self._model, target)
        biomass = get_reaction_for(self._model, biomass)
        substrate = get_reaction_for(self._model, substrate)

        if growth_coupled:
            objective_function = biomass_product_coupled_min_yield(biomass, target, substrate)
        else:
            objective_function = biomass_product_coupled_yield(biomass, target, substrate)

        optimization_algorithm = CofactorSwapOptimization(model=self._model,
                                                          cofactor_id_swaps=self._cofactor_id_swaps,
                                                          skip_reactions=self._skip_reactions,
                                                          objective_function=objective_function)

        optimization_algorithm.simulation_kwargs = kwargs
        optimization_algorithm.simulation_method = simulation_method
        optimization_algorithm.archiver = ProductionStrainArchive()

        result = optimization_algorithm.run(max_evaluations=max_evaluations,
                                            pop_size=population_size,
                                            max_size=max_swaps,
                                            variable_size=variable_size,
                                            maximize=True,
                                            max_archive_size=max_results,
                                            seed=seed,
                                            **kwargs)

        kwargs.update(optimization_algorithm.simulation_kwargs)

        return HeuristicOptSwapResult(self._model, result, self._swap_pairs, objective_function,
                                      simulation_method, biomass, target, substrate, kwargs)