Beispiel #1
0
def try_factorization_and_reallocation(kkt,
                                       linear_solver,
                                       reallocation_factor,
                                       max_iter,
                                       timer=None):
    if timer is None:
        timer = HierarchicalTimer()

    assert max_iter >= 1
    for count in range(max_iter):
        timer.start('symbolic')
        """
        Performance could be improved significantly by only performing 
        symbolic factorization once.

        However, we first have to make sure the nonzero structure 
        (and ordering of row and column arrays) of the KKT matrix never 
        changes. We have not had time to test this thoroughly, yet. 
        """
        res = linear_solver.do_symbolic_factorization(matrix=kkt,
                                                      raise_on_error=False)
        timer.stop('symbolic')
        if res.status == LinearSolverStatus.successful:
            timer.start('numeric')
            res = linear_solver.do_numeric_factorization(matrix=kkt,
                                                         raise_on_error=False)
            timer.stop('numeric')
        status = res.status
        if status == LinearSolverStatus.not_enough_memory:
            linear_solver.increase_memory_allocation(reallocation_factor)
        else:
            break
    return status, count
Beispiel #2
0
 def solve(self, model, timer: HierarchicalTimer = None):
     avail = self.available()
     if not avail:
         raise PyomoException(
             f'Solver {self.__class__} is not available ({avail}).')
     if self._last_results_object is not None:
         self._last_results_object.solution_loader.invalidate()
     if timer is None:
         timer = HierarchicalTimer()
     try:
         TempfileManager.push()
         if self.config.filename is None:
             self._filename = TempfileManager.create_tempfile()
         else:
             self._filename = self.config.filename
         TempfileManager.add_tempfile(self._filename + '.lp', exists=False)
         TempfileManager.add_tempfile(self._filename + '.log', exists=False)
         timer.start('write lp file')
         self._writer.write(model, self._filename + '.lp', timer=timer)
         timer.stop('write lp file')
         res = self._apply_solver(timer)
         self._last_results_object = res
         if self.config.report_timing:
             logger.info('\n' + str(timer))
         return res
     finally:
         # finally, clean any temporary files registered with the
         # temp file manager, created/populated *directly* by this
         # plugin.
         TempfileManager.pop(remove=not self.config.keepfiles)
         if not self.config.keepfiles:
             self._filename = None
Beispiel #3
0
 def solve(self, model, timer: HierarchicalTimer = None):
     self.available(exception_flag=True)
     if timer is None:
         timer = HierarchicalTimer()
     try:
         TempfileManager.push()
         if self.config.filename is None:
             self._filename = TempfileManager.create_tempfile()
         else:
             self._filename = self.config.filename
         TempfileManager.add_tempfile(self._filename + '.lp', exists=False)
         TempfileManager.add_tempfile(self._filename + '.soln', exists=False)
         TempfileManager.add_tempfile(self._filename + '.log', exists=False)
         timer.start('write lp file')
         self._writer.write(model, self._filename+'.lp', timer=timer)
         timer.stop('write lp file')
         res = self._apply_solver(timer)
         if self.config.report_timing:
             logger.info('\n' + str(timer))
         return res
     finally:
         # finally, clean any temporary files registered with the
         # temp file manager, created/populated *directly* by this
         # plugin.
         TempfileManager.pop(remove=not self.config.keepfiles)
         if not self.config.keepfiles:
             self._filename = None
         if self.config.report_timing:
             print(timer)
Beispiel #4
0
    def _apply_solver(self, timer: HierarchicalTimer):
        config = self.config

        if config.time_limit is not None:
            timeout = config.time_limit + min(max(1, 0.01 * config.time_limit), 100)
        else:
            timeout = None

        ostreams = [LogStream(level=self.config.log_level, logger=self.config.solver_output_logger)]
        if self.config.stream_solver:
            ostreams.append(sys.stdout)

        cmd = [str(config.executable),
               self._filename + '.nl',
               '-AMPL',
               'option_file_name=' + self._filename + '.opt']
        if 'option_file_name' in self.solver_options:
            raise ValueError('Use Ipopt.config.filename to specify the name of the options file. '
                             'Do not use Ipopt.solver_options["option_file_name"].')
        for k, v in self.solver_options.items():
            cmd.append(str(k) + '=' + str(v))

        with TeeStream(*ostreams) as t:
            timer.start('subprocess')
            cp = subprocess.run(cmd,
                                timeout=timeout,
                                stdout=t.STDOUT,
                                stderr=t.STDERR,
                                universal_newlines=True)
            timer.stop('subprocess')

        if cp.returncode != 0:
            if self.config.load_solution:
                raise RuntimeError('A feasible solution was not found, so no solution can be loaded.'
                                   'Please set opt.config.load_solution=False and check '
                                   'results.termination_condition and '
                                   'results.best_feasible_objective before loading a solution.')
            results = Results()
            results.termination_condition = TerminationCondition.error
            results.best_feasible_objective = None
            self._primal_sol = None
            self._dual_sol = None
        else:
            timer.start('parse solution')
            results = self._parse_sol()
            timer.stop('parse solution')

        if self._writer.get_active_objective() is None:
            results.best_objective_bound = None
        else:
            if self._writer.get_active_objective().sense == minimize:
                results.best_objective_bound = -math.inf
            else:
                results.best_objective_bound = math.inf

        return results
def try_factorization_and_reallocation(kkt, linear_solver: LinearSolverInterface, reallocation_factor, max_iter,
                                       symbolic_or_numeric, timer=None):
    if timer is None:
        timer = HierarchicalTimer()

    assert max_iter >= 1
    if symbolic_or_numeric == 'numeric':
        method = linear_solver.do_numeric_factorization
    else:
        assert symbolic_or_numeric == 'symbolic'
        method = linear_solver.do_symbolic_factorization
    for count in range(max_iter):
        res = method(matrix=kkt, raise_on_error=False, timer=timer)
        status = res.status
        if status == LinearSolverStatus.not_enough_memory:
            linear_solver.increase_memory_allocation(reallocation_factor)
        else:
            break
    return status, count
Beispiel #6
0
    def _apply_solver(self, timer: HierarchicalTimer):
        config = self.config

        timer.start('cplex read lp')
        self._cplex_model = cplex_model = self._cplex.Cplex()
        cplex_model.read(self._filename + '.lp')
        timer.stop('cplex read lp')

        log_stream = LogStream(level=self.config.log_level,
                               logger=self.config.solver_output_logger)
        if config.stream_solver:

            def _process_stream(arg):
                sys.stdout.write(arg)
                return arg

            cplex_model.set_results_stream(log_stream, _process_stream)
        else:
            cplex_model.set_results_stream(log_stream)

        for key, option in self.cplex_options.items():
            opt_cmd = cplex_model.parameters
            key_pieces = key.split('_')
            for key_piece in key_pieces:
                opt_cmd = getattr(opt_cmd, key_piece)
            opt_cmd.set(option)

        if config.time_limit is not None:
            cplex_model.parameters.timelimit.set(config.time_limit)
        if config.mip_gap is not None:
            cplex_model.parameters.mip.tolerances.mipgap.set(config.mip_gap)

        timer.start('cplex solve')
        t0 = time.time()
        cplex_model.solve()
        t1 = time.time()
        timer.stop('cplex solve')

        return self._postsolve(timer, t1 - t0)
def main(plot=True, n_points=200):
    import numpy as np

    # create a Pyomo model
    m = pe.ConcreteModel()
    m.x = pe.Var()
    m.y = pe.Var()
    m.p = pe.Param(initialize=1, mutable=True)

    m.obj = pe.Objective(expr=m.x**2 + m.y**2)
    m.c1 = pe.Constraint(expr=m.y >= (m.x + 1)**2)
    m.c2 = pe.Constraint(expr=m.y >= (m.x - m.p)**2)

    opt = appsi.solvers.Cplex()  # create an APPSI solver interface
    opt.config.load_solution = False  # modify the config options
    opt.update_config.check_for_new_or_removed_vars = False  # change how automatic updates are handled
    opt.update_config.update_vars = False

    # write a for loop to vary the value of parameter p from 1 to 10
    p_values = [float(i) for i in np.linspace(1, 10, n_points)]
    obj_values = list()
    x_values = list()
    timer = HierarchicalTimer()  # create a timer for some basic profiling
    timer.start('p loop')
    for p_val in p_values:
        m.p.value = p_val
        res = opt.solve(m, timer=timer)
        assert res.termination_condition == appsi.base.TerminationCondition.optimal
        obj_values.append(res.best_feasible_objective)
        opt.load_vars([m.x])
        x_values.append(m.x.value)
    timer.stop('p loop')
    print(timer)

    if plot:
        import matplotlib.pyplot as plt
        # plot the results
        fig, ax1 = plt.subplots()
        ax1.set_xlabel('p')
        ax1.set_ylabel('objective')
        ax1.plot(p_values, obj_values, ':k', label='objective')

        ax2 = ax1.twinx()
        ax2.set_ylabel('x')
        ax2.plot(p_values, x_values, '-b', label='x')

        fig.legend()
        plt.show()
    def do_back_solve(self, rhs, timer=None):
        """
        Performs a back solve with the factorized matrix. Should only be called after
        do_numeric_factorixation.

        Parameters
        ----------
        rhs: MPIBlockVector
        timer: HierarchicalTimer

        Returns
        -------
        result: MPIBlockVector
        """
        if timer is None:
            timer = HierarchicalTimer()
        timer.start('back_solve')

        schur_complement_rhs = np.zeros(rhs.get_block(self.block_dim - 1).size,
                                        dtype='d')
        for ndx in self.local_block_indices:
            A = self.block_matrix.get_block(self.block_dim - 1, ndx)
            contribution = self.subproblem_solvers[ndx].do_back_solve(
                rhs.get_block(ndx))
            schur_complement_rhs -= A.tocsr().dot(contribution.flatten())
        res = np.zeros(rhs.get_block(self.block_dim - 1).shape[0], dtype='d')
        comm.Allreduce(schur_complement_rhs, res)
        schur_complement_rhs = rhs.get_block(self.block_dim - 1) + res

        result = rhs.copy_structure()
        coupling = self.schur_complement_solver.do_back_solve(
            schur_complement_rhs)

        for ndx in self.local_block_indices:
            A = self.block_matrix.get_block(self.block_dim - 1, ndx)
            result.set_block(
                ndx, self.subproblem_solvers[ndx].do_back_solve(
                    rhs.get_block(ndx) -
                    A.tocsr().transpose().dot(coupling.flatten())))

        result.set_block(self.block_dim - 1, coupling)

        timer.stop('back_solve')

        return result
Beispiel #9
0
import pyomo.environ as pyo
from pyomo.dae.flatten import flatten_dae_components
from pyomo.contrib.incidence_analysis import (
    IncidenceGraphInterface,
    solve_strongly_connected_components,
)
from pyomo.core.expr.visitor import identify_variables
from pyomo.common.collections import ComponentSet
from pyomo.common.timing import HierarchicalTimer
from pyomo.util.subsystems import (
    TemporarySubsystemManager, )
"""
"""

TIMER = HierarchicalTimer()


def set_default_design_vars(m):
    design_vars = {
        "fs.MB.bed_diameter": 6.5,
        "fs.MB.bed_height": 5.0,
    }
    design_vars = fix_design_variables(m, design_vars)
    return design_vars


def set_default_inlet_conditions(m):
    time = m.fs.time
    dynamic_inputs = {
        "fs.MB.gas_inlet.flow_mol[*]": 128.20513,
Beispiel #10
0
    def test_HierarchicalTimer(self):
        RES = 1e-2 # resolution (seconds)

        timer = HierarchicalTimer()
        start_time = time.time()
        timer.start('all')
        time.sleep(0.02)
        for i in range(10):
            timer.start('a')
            time.sleep(0.01)
            for j in range(5):
                timer.start('aa')
                time.sleep(0.001)
                timer.stop('aa')
            timer.start('ab')
            timer.stop('ab')
            timer.stop('a')
        end_time = time.time()
        timer.stop('all')
        ref = \
"""Identifier        ncalls   cumtime   percall      %
---------------------------------------------------
all                    1     [0-9.]+ +[0-9.]+ +100.0
     ----------------------------------------------
     a                10     [0-9.]+ +[0-9.]+ +[0-9.]+
          -----------------------------------------
          aa          50     [0-9.]+ +[0-9.]+ +[0-9.]+
          ab          10     [0-9.]+ +[0-9.]+ +[0-9.]+
          other      n/a     [0-9.]+ +n/a +[0-9.]+
          =========================================
     other           n/a     [0-9.]+ +n/a +[0-9.]+
     ==============================================
===================================================
""".splitlines()
        for l, r in zip(str(timer).splitlines(), ref):
            self.assertRegex(l, r)

        self.assertEqual(1, timer.get_num_calls('all'))
        self.assertAlmostEqual(
            end_time - start_time, timer.get_total_time('all'), delta=RES)
        self.assertEqual(100., timer.get_relative_percent_time('all'))
        self.assertTrue(100. > timer.get_relative_percent_time('all.a'))
        self.assertTrue(50. < timer.get_relative_percent_time('all.a'))
Beispiel #11
0
 def write(self,
           model: _BlockData,
           filename: str,
           timer: HierarchicalTimer = None):
     if timer is None:
         timer = HierarchicalTimer()
     if model is not self._model:
         timer.start('set_instance')
         self.set_instance(model)
         timer.stop('set_instance')
     else:
         timer.start('update')
         self.update(timer=timer)
         for cv, v in self._solver_var_to_pyomo_var_map.items():
             if v.value is not None:
                 cv.value = v.value
         timer.stop('update')
     timer.start('write file')
     self._writer.write(filename)
     timer.stop('write file')
Beispiel #12
0
 def write(self,
           model: _BlockData,
           filename: str,
           timer: HierarchicalTimer = None):
     if timer is None:
         timer = HierarchicalTimer()
     if model is not self._model:
         timer.start('set_instance')
         self.set_instance(model)
         timer.stop('set_instance')
     else:
         timer.start('update')
         self.update(timer=timer)
         timer.stop('update')
     timer.start('write file')
     self._writer.write(filename)
     timer.stop('write file')
Beispiel #13
0
    def _apply_solver(self, timer: HierarchicalTimer):
        config = self.config

        if config.time_limit is not None:
            timeout = config.time_limit + min(max(1, 0.01 * config.time_limit), 100)
        else:
            timeout = None

        def _check_and_escape_options():
            for key, val in self.solver_options.items():
                tmp_k = str(key)
                _bad = ' ' in tmp_k

                tmp_v = str(val)
                if ' ' in tmp_v:
                    if '"' in tmp_v:
                        if "'" in tmp_v:
                            _bad = True
                        else:
                            tmp_v = "'" + tmp_v + "'"
                    else:
                        tmp_v = '"' + tmp_v + '"'

                if _bad:
                    raise ValueError("Unable to properly escape solver option:"
                                     "\n\t%s=%s" % (key, val) )
                yield tmp_k, tmp_v

        cmd = [str(config.executable)]
        action_options = list()
        if config.time_limit is not None:
            cmd.extend(['-sec', str(config.time_limit)])
            cmd.extend(['-timeMode', 'elapsed'])
        for key, val in _check_and_escape_options():
            if val.strip() != '':
                cmd.append('-'+key, val)
            else:
                action_options.append('-'+key)
        cmd.extend(['-printingOptions', 'all'])
        cmd.extend(['-import', self._filename + '.lp'])
        cmd.extend(action_options)
        cmd.extend(['-stat=1'])
        cmd.extend(['-solve'])
        cmd.extend(['-solu', self._filename + '.soln'])

        ostreams = [LogStream(level=self.config.log_level, logger=self.config.solver_output_logger)]
        if self.config.stream_solver:
            ostreams.append(sys.stdout)

        with TeeStream(*ostreams) as t:
            timer.start('subprocess')
            cp = subprocess.run(cmd,
                                timeout=timeout,
                                stdout=t.STDOUT,
                                stderr=t.STDERR,
                                universal_newlines=True)
            timer.stop('subprocess')

        if cp.returncode != 0:
            if self.config.load_solution:
                raise RuntimeError('A feasible solution was not found, so no solution can be loaded.'
                                   'Please set opt.config.load_solution=False and check '
                                   'results.termination_condition and '
                                   'results.best_feasible_objective before loading a solution.')
            results = Results()
            results.termination_condition = TerminationCondition.error
            results.best_feasible_objective = None
            self._primal_sol = None
            self._dual_sol = None
            self._reduced_costs = None
        else:
            timer.start('parse solution')
            results = self._parse_soln()
            timer.stop('parse solution')

        if self._writer.get_active_objective() is None:
            results.best_feasible_objective = None
            results.best_objective_bound = None
        else:
            if self._writer.get_active_objective().sense == minimize:
                results.best_objective_bound = -math.inf
            else:
                results.best_objective_bound = math.inf

        return results
Beispiel #14
0
    def test_HierarchicalTimer_longNames(self):
        RES = 0.01  # resolution (seconds)

        timer = HierarchicalTimer()
        start_time = time.perf_counter()
        timer.start('all' * 25)
        time.sleep(0.02)
        for i in range(10):
            timer.start('a' * 75)
            time.sleep(0.01)
            for j in range(5):
                timer.start('aa' * 20)
                time.sleep(0.001)
                timer.stop('aa' * 20)
            timer.start('ab' * 20)
            timer.stop('ab' * 20)
            timer.stop('a' * 75)
        end_time = time.perf_counter()
        timer.stop('all' * 25)
        ref = ("""Identifier%s   ncalls   cumtime   percall      %%
%s------------------------------------
%s%s        1     [0-9.]+ +[0-9.]+ +100.0
    %s------------------------------------
    %s%s       10     [0-9.]+ +[0-9.]+ +[0-9.]+
        %s------------------------------------
        %s%s       50     [0-9.]+ +[0-9.]+ +[0-9.]+
        %s%s       10     [0-9.]+ +[0-9.]+ +[0-9.]+
        other%s      n/a     [0-9.]+ +n/a +[0-9.]+
        %s====================================
    other%s      n/a     [0-9.]+ +n/a +[0-9.]+
    %s====================================
%s====================================
""" % (' ' * 69, '-' * 79, 'all' * 25, ' ' * 4, '-' * 75, 'a' * 75, '',
        '-' * 71, 'aa' * 20, ' ' * 31, 'ab' * 20, ' ' * 31, ' ' * 66, '=' * 71,
        ' ' * 70, '=' * 75, '=' * 79)).splitlines()
        for l, r in zip(str(timer).splitlines(), ref):
            self.assertRegex(l, r)
Beispiel #15
0
    def evaluate_primal_dual_kkt_rhs(self, timer=None):
        if timer is None:
            timer = HierarchicalTimer()
        timer.start('eval grad obj')
        grad_obj = self.get_obj_factor() * self.evaluate_grad_objective()
        timer.stop('eval grad obj')
        timer.start('eval jac')
        jac_eq = self._nlp.evaluate_jacobian_eq()
        jac_ineq = self._nlp.evaluate_jacobian_ineq()
        timer.stop('eval jac')
        timer.start('eval cons')
        eq_resid = self._nlp.evaluate_eq_constraints()
        ineq_resid = self._nlp.evaluate_ineq_constraints() - self._slacks
        timer.stop('eval cons')

        timer.start('grad_lag_primals')
        grad_lag_primals = (
            grad_obj + jac_eq.transpose() * self._nlp.get_duals_eq() +
            jac_ineq.transpose() * self._nlp.get_duals_ineq() - self._barrier /
            (self._nlp.get_primals() - self._nlp.primals_lb()) +
            self._barrier / (self._nlp.primals_ub() - self._nlp.get_primals()))
        timer.stop('grad_lag_primals')

        timer.start('grad_lag_slacks')
        grad_lag_slacks = (-self._nlp.get_duals_ineq() - self._barrier /
                           (self._slacks - self._nlp.ineq_lb()) +
                           self._barrier /
                           (self._nlp.ineq_ub() - self._slacks))
        timer.stop('grad_lag_slacks')

        rhs = BlockVector(4)
        rhs.set_block(0, grad_lag_primals)
        rhs.set_block(1, grad_lag_slacks)
        rhs.set_block(2, eq_resid)
        rhs.set_block(3, ineq_resid)
        rhs = -rhs
        return rhs
Beispiel #16
0
    def evaluate_primal_dual_kkt_matrix(self, timer=None):
        if timer is None:
            timer = HierarchicalTimer()
        timer.start('eval hess')
        hessian = self._nlp.evaluate_hessian_lag()
        timer.stop('eval hess')
        timer.start('eval jac')
        jac_eq = self._nlp.evaluate_jacobian_eq()
        jac_ineq = self._nlp.evaluate_jacobian_ineq()
        timer.stop('eval jac')

        duals_primals_lb = self._duals_primals_lb
        duals_primals_ub = self._duals_primals_ub
        duals_slacks_lb = self._duals_slacks_lb
        duals_slacks_ub = self._duals_slacks_ub
        primals = self._nlp.get_primals()

        timer.start('hess block')
        data = (duals_primals_lb / (primals - self._nlp.primals_lb()) +
                duals_primals_ub / (self._nlp.primals_ub() - primals))
        n = self._nlp.n_primals()
        indices = np.arange(n)
        hess_block = scipy.sparse.coo_matrix((data, (indices, indices)),
                                             shape=(n, n))
        hess_block += hessian
        timer.stop('hess block')

        timer.start('slack block')
        data = (duals_slacks_lb / (self._slacks - self._nlp.ineq_lb()) +
                duals_slacks_ub / (self._nlp.ineq_ub() - self._slacks))
        n = self._nlp.n_ineq_constraints()
        indices = np.arange(n)
        slack_block = scipy.sparse.coo_matrix((data, (indices, indices)),
                                              shape=(n, n))
        timer.stop('slack block')

        timer.start('set block')
        kkt = BlockMatrix(4, 4)
        kkt.set_block(0, 0, hess_block)
        kkt.set_block(1, 1, slack_block)
        kkt.set_block(2, 0, jac_eq)
        kkt.set_block(0, 2, jac_eq.transpose())
        kkt.set_block(3, 0, jac_ineq)
        kkt.set_block(0, 3, jac_ineq.transpose())
        kkt.set_block(
            3, 1, -scipy.sparse.identity(self._nlp.n_ineq_constraints(),
                                         format='coo'))
        kkt.set_block(
            1, 3, -scipy.sparse.identity(self._nlp.n_ineq_constraints(),
                                         format='coo'))
        timer.stop('set block')
        return kkt
Beispiel #17
0
    def _postsolve(self, timer: HierarchicalTimer, solve_time):
        config = self.config
        cpxprob = self._cplex_model

        results = CplexResults(solver=self)
        results.wallclock_time = solve_time
        status = cpxprob.solution.get_status()

        if status in [1, 101, 102]:
            results.termination_condition = TerminationCondition.optimal
        elif status in [2, 40, 118, 133, 134]:
            results.termination_condition = TerminationCondition.unbounded
        elif status in [4, 119, 134]:
            results.termination_condition = TerminationCondition.infeasibleOrUnbounded
        elif status in [3, 103]:
            results.termination_condition = TerminationCondition.infeasible
        elif status in [10]:
            results.termination_condition = TerminationCondition.maxIterations
        elif status in [11, 25, 107, 131]:
            results.termination_condition = TerminationCondition.maxTimeLimit
        else:
            results.termination_condition = TerminationCondition.unknown

        if self._writer.get_active_objective() is None:
            results.best_feasible_objective = None
            results.best_objective_bound = None
        else:
            if cpxprob.solution.get_solution_type(
            ) != cpxprob.solution.type.none:
                if (cpxprob.variables.get_num_binary() +
                        cpxprob.variables.get_num_integer()) == 0:
                    results.best_feasible_objective = cpxprob.solution.get_objective_value(
                    )
                    results.best_objective_bound = cpxprob.solution.get_objective_value(
                    )
                else:
                    results.best_feasible_objective = cpxprob.solution.get_objective_value(
                    )
                    results.best_objective_bound = cpxprob.solution.MIP.get_best_objective(
                    )
            else:
                results.best_feasible_objective = None
                if cpxprob.objective.get_sense(
                ) == cpxprob.objective.sense.minimize:
                    results.best_objective_bound = -math.inf
                else:
                    results.best_objective_bound = math.inf

        if config.load_solution:
            if cpxprob.solution.get_solution_type(
            ) == cpxprob.solution.type.none:
                raise RuntimeError(
                    'A feasible solution was not found, so no solution can be loades. '
                    'Please set opt.config.load_solution=False and check '
                    'results.termination_condition and '
                    'results.best_feasible_objective before loading a solution.'
                )
            else:
                if results.termination_condition != TerminationCondition.optimal:
                    logger.warning(
                        'Loading a feasible but suboptimal solution. '
                        'Please set load_solution=False and check '
                        'results.termination_condition before loading a solution.'
                    )
                timer.start('load solution')
                self.load_vars()
                timer.stop('load solution')

        return results
Beispiel #18
0
    def check_convergence(self, barrier, timer=None):
        """
        Parameters
        ----------
        barrier: float
        timer: HierarchicalTimer
    
        Returns
        -------
        primal_inf: float
        dual_inf: float
        complimentarity_inf: float
        """
        if timer is None:
            timer = HierarchicalTimer()

        interface = self.interface
        slacks = interface.get_slacks()
        timer.start('grad obj')
        grad_obj = interface.get_obj_factor() * \
                interface.evaluate_grad_objective()
        timer.stop('grad obj')
        timer.start('jac eq')
        jac_eq = interface.evaluate_jacobian_eq()
        timer.stop('jac eq')
        timer.start('jac ineq')
        jac_ineq = interface.evaluate_jacobian_ineq()
        timer.stop('jac ineq')
        timer.start('eq cons')
        eq_resid = interface.evaluate_eq_constraints()
        timer.stop('eq cons')
        timer.start('ineq cons')
        ineq_resid = interface.evaluate_ineq_constraints() - slacks
        timer.stop('ineq cons')
        primals = interface.get_primals()
        duals_eq = interface.get_duals_eq()
        duals_ineq = interface.get_duals_ineq()
        duals_primals_lb = interface.get_duals_primals_lb()
        duals_primals_ub = interface.get_duals_primals_ub()
        duals_slacks_lb = interface.get_duals_slacks_lb()
        duals_slacks_ub = interface.get_duals_slacks_ub()

        primals_lb = interface.primals_lb()
        primals_ub = interface.primals_ub()
        primals_lb_mod = primals_lb.copy()
        primals_ub_mod = primals_ub.copy()
        primals_lb_mod[np.isneginf(
            primals_lb)] = 0  # these entries get multiplied by 0
        primals_ub_mod[np.isinf(
            primals_ub)] = 0  # these entries get multiplied by 0

        ineq_lb = interface.ineq_lb()
        ineq_ub = interface.ineq_ub()
        ineq_lb_mod = ineq_lb.copy()
        ineq_ub_mod = ineq_ub.copy()
        ineq_lb_mod[np.isneginf(
            ineq_lb)] = 0  # these entries get multiplied by 0
        ineq_ub_mod[np.isinf(ineq_ub)] = 0  # these entries get multiplied by 0

        timer.start('grad_lag_primals')
        grad_lag_primals = grad_obj + jac_eq.transpose() * duals_eq
        grad_lag_primals += jac_ineq.transpose() * duals_ineq
        grad_lag_primals -= duals_primals_lb
        grad_lag_primals += duals_primals_ub
        timer.stop('grad_lag_primals')
        timer.start('grad_lag_slacks')
        grad_lag_slacks = (-duals_ineq - duals_slacks_lb + duals_slacks_ub)
        timer.stop('grad_lag_slacks')
        timer.start('bound resids')
        primals_lb_resid = (primals -
                            primals_lb_mod) * duals_primals_lb - barrier
        primals_ub_resid = (primals_ub_mod -
                            primals) * duals_primals_ub - barrier
        primals_lb_resid[np.isneginf(primals_lb)] = 0
        primals_ub_resid[np.isinf(primals_ub)] = 0
        slacks_lb_resid = (slacks - ineq_lb_mod) * duals_slacks_lb - barrier
        slacks_ub_resid = (ineq_ub_mod - slacks) * duals_slacks_ub - barrier
        slacks_lb_resid[np.isneginf(ineq_lb)] = 0
        slacks_ub_resid[np.isinf(ineq_ub)] = 0
        timer.stop('bound resids')

        if eq_resid.size == 0:
            max_eq_resid = 0
        else:
            max_eq_resid = np.max(np.abs(eq_resid))
        if ineq_resid.size == 0:
            max_ineq_resid = 0
        else:
            max_ineq_resid = np.max(np.abs(ineq_resid))
        primal_inf = max(max_eq_resid, max_ineq_resid)

        max_grad_lag_primals = np.max(np.abs(grad_lag_primals))
        if grad_lag_slacks.size == 0:
            max_grad_lag_slacks = 0
        else:
            max_grad_lag_slacks = np.max(np.abs(grad_lag_slacks))
        dual_inf = max(max_grad_lag_primals, max_grad_lag_slacks)

        if primals_lb_resid.size == 0:
            max_primals_lb_resid = 0
        else:
            max_primals_lb_resid = np.max(np.abs(primals_lb_resid))
        if primals_ub_resid.size == 0:
            max_primals_ub_resid = 0
        else:
            max_primals_ub_resid = np.max(np.abs(primals_ub_resid))
        if slacks_lb_resid.size == 0:
            max_slacks_lb_resid = 0
        else:
            max_slacks_lb_resid = np.max(np.abs(slacks_lb_resid))
        if slacks_ub_resid.size == 0:
            max_slacks_ub_resid = 0
        else:
            max_slacks_ub_resid = np.max(np.abs(slacks_ub_resid))
        complimentarity_inf = max(max_primals_lb_resid, max_primals_ub_resid,
                                  max_slacks_lb_resid, max_slacks_ub_resid)

        return primal_inf, dual_inf, complimentarity_inf
Beispiel #19
0
    def solve(self, interface, timer=None, report_timing=False):
        """
        Parameters
        ----------
        interface: pyomo.contrib.interior_point.interface.BaseInteriorPointInterface
            The interior point interface. This object handles the function evaluation, 
            building the KKT matrix, and building the KKT right hand side.
        timer: HierarchicalTimer
        report_timing: bool
        """
        linear_solver = self.linear_solver
        max_iter = self.max_iter
        tol = self.tol
        if timer is None:
            timer = HierarchicalTimer()

        timer.start('IP solve')
        timer.start('init')

        self._barrier_parameter = 0.1

        self.set_interface(interface)

        t0 = time.time()
        primals = interface.init_primals().copy()
        slacks = interface.init_slacks().copy()
        duals_eq = interface.init_duals_eq().copy()
        duals_ineq = interface.init_duals_ineq().copy()
        duals_primals_lb = interface.init_duals_primals_lb().copy()
        duals_primals_ub = interface.init_duals_primals_ub().copy()
        duals_slacks_lb = interface.init_duals_slacks_lb().copy()
        duals_slacks_ub = interface.init_duals_slacks_ub().copy()

        self.process_init(primals, interface.primals_lb(),
                          interface.primals_ub())
        self.process_init(slacks, interface.ineq_lb(), interface.ineq_ub())
        self.process_init_duals_lb(duals_primals_lb,
                                   self.interface.primals_lb())
        self.process_init_duals_ub(duals_primals_ub,
                                   self.interface.primals_ub())
        self.process_init_duals_lb(duals_slacks_lb, self.interface.ineq_lb())
        self.process_init_duals_ub(duals_slacks_ub, self.interface.ineq_ub())

        interface.set_barrier_parameter(self._barrier_parameter)

        alpha_primal_max = 1
        alpha_dual_max = 1

        self.logger.info('{_iter:<6}'
                         '{objective:<11}'
                         '{primal_inf:<11}'
                         '{dual_inf:<11}'
                         '{compl_inf:<11}'
                         '{barrier:<11}'
                         '{alpha_p:<11}'
                         '{alpha_d:<11}'
                         '{reg:<11}'
                         '{time:<7}'.format(_iter='Iter',
                                            objective='Objective',
                                            primal_inf='Prim Inf',
                                            dual_inf='Dual Inf',
                                            compl_inf='Comp Inf',
                                            barrier='Barrier',
                                            alpha_p='Prim Step',
                                            alpha_d='Dual Step',
                                            reg='Reg',
                                            time='Time'))

        reg_coef = 0

        timer.stop('init')
        status = InteriorPointStatus.error

        for _iter in range(max_iter):
            self._iter = _iter

            interface.set_primals(primals)
            interface.set_slacks(slacks)
            interface.set_duals_eq(duals_eq)
            interface.set_duals_ineq(duals_ineq)
            interface.set_duals_primals_lb(duals_primals_lb)
            interface.set_duals_primals_ub(duals_primals_ub)
            interface.set_duals_slacks_lb(duals_slacks_lb)
            interface.set_duals_slacks_ub(duals_slacks_ub)

            timer.start('convergence check')
            primal_inf, dual_inf, complimentarity_inf = \
                    self.check_convergence(barrier=0, timer=timer)
            timer.stop('convergence check')
            objective = interface.evaluate_objective()
            self.logger.info('{_iter:<6}'
                             '{objective:<11.2e}'
                             '{primal_inf:<11.2e}'
                             '{dual_inf:<11.2e}'
                             '{compl_inf:<11.2e}'
                             '{barrier:<11.2e}'
                             '{alpha_p:<11.2e}'
                             '{alpha_d:<11.2e}'
                             '{reg:<11.2e}'
                             '{time:<7.3f}'.format(
                                 _iter=_iter,
                                 objective=objective,
                                 primal_inf=primal_inf,
                                 dual_inf=dual_inf,
                                 compl_inf=complimentarity_inf,
                                 barrier=self._barrier_parameter,
                                 alpha_p=alpha_primal_max,
                                 alpha_d=alpha_dual_max,
                                 reg=reg_coef,
                                 time=time.time() - t0))

            if max(primal_inf, dual_inf, complimentarity_inf) <= tol:
                status = InteriorPointStatus.optimal
                break
            timer.start('convergence check')
            primal_inf, dual_inf, complimentarity_inf = \
                    self.check_convergence(
                            barrier=self._barrier_parameter,
                            timer=timer)
            timer.stop('convergence check')
            if max(primal_inf, dual_inf, complimentarity_inf) \
                    <= 0.1 * self._barrier_parameter:
                # This comparison is made with barrier problem infeasibility.
                # Sometimes have trouble getting dual infeasibility low enough
                self.update_barrier_parameter()

            interface.set_barrier_parameter(self._barrier_parameter)
            timer.start('eval')
            timer.start('eval kkt')
            kkt = interface.evaluate_primal_dual_kkt_matrix(timer=timer)
            timer.stop('eval kkt')
            timer.start('eval rhs')
            rhs = interface.evaluate_primal_dual_kkt_rhs(timer=timer)
            timer.stop('eval rhs')
            timer.stop('eval')

            # Factorize linear system
            timer.start('factorize')
            reg_coef = self.factorize(kkt=kkt, timer=timer)
            timer.stop('factorize')

            timer.start('back solve')
            with self.linear_solve_context:
                self.logger.info('Iter: %s' % self._iter)
                delta = linear_solver.do_back_solve(rhs)
            timer.stop('back solve')

            interface.set_primal_dual_kkt_solution(delta)
            timer.start('frac boundary')
            alpha_primal_max, alpha_dual_max = \
                    self.fraction_to_the_boundary()
            timer.stop('frac boundary')
            delta_primals = interface.get_delta_primals()
            delta_slacks = interface.get_delta_slacks()
            delta_duals_eq = interface.get_delta_duals_eq()
            delta_duals_ineq = interface.get_delta_duals_ineq()
            delta_duals_primals_lb = interface.get_delta_duals_primals_lb()
            delta_duals_primals_ub = interface.get_delta_duals_primals_ub()
            delta_duals_slacks_lb = interface.get_delta_duals_slacks_lb()
            delta_duals_slacks_ub = interface.get_delta_duals_slacks_ub()

            primals += alpha_primal_max * delta_primals
            slacks += alpha_primal_max * delta_slacks
            duals_eq += alpha_dual_max * delta_duals_eq
            duals_ineq += alpha_dual_max * delta_duals_ineq
            duals_primals_lb += alpha_dual_max * delta_duals_primals_lb
            duals_primals_ub += alpha_dual_max * delta_duals_primals_ub
            duals_slacks_lb += alpha_dual_max * delta_duals_slacks_lb
            duals_slacks_ub += alpha_dual_max * delta_duals_slacks_ub

        timer.stop('IP solve')
        if report_timing:
            print(timer)
        return status
Beispiel #20
0
    def update(self, timer: HierarchicalTimer = None):
        if timer is None:
            timer = HierarchicalTimer()
        config = self.update_config
        new_vars = list()
        old_vars = list()
        new_params = list()
        old_params = list()
        new_cons = list()
        old_cons = list()
        old_sos = list()
        new_sos = list()
        current_vars_dict = dict()
        current_cons_dict = dict()
        current_sos_dict = dict()
        timer.start('vars')
        if config.check_for_new_or_removed_vars or config.update_vars:
            current_vars_dict = {id(v): v for v in self._model.component_data_objects(Var, descend_into=True, sort=False)}
            for v_id, v in current_vars_dict.items():
                if v_id not in self._vars:
                    new_vars.append(v)
            for v_id, v_tuple in self._vars.items():
                if v_id not in current_vars_dict:
                    old_vars.append(v_tuple[0])
        timer.stop('vars')
        timer.start('params')
        if config.check_for_new_or_removed_params:
            current_params_dict = {id(p): p for p in self._model.component_data_objects(Param, descend_into=True, sort=False)}
            for p_id, p in current_params_dict.items():
                if p_id not in self._params:
                    new_params.append(p)
            for p_id, p in self._params.items():
                if p_id not in current_params_dict:
                    old_params.append(p)
        timer.stop('params')
        timer.start('cons')
        if config.check_for_new_or_removed_constraints or config.update_constraints:
            current_cons_dict = {c: None for c in self._model.component_data_objects(Constraint, descend_into=True, active=True, sort=False)}
            current_sos_dict = {c: None for c in self._model.component_data_objects(SOSConstraint, descend_into=True, active=True, sort=False)}
            for c in current_cons_dict.keys():
                if c not in self._vars_referenced_by_con:
                    new_cons.append(c)
            for c in current_sos_dict.keys():
                if c not in self._vars_referenced_by_con:
                    new_sos.append(c)
            for c in self._vars_referenced_by_con.keys():
                if c not in current_cons_dict and c not in current_sos_dict:
                    if (c.ctype is Constraint) or (c.ctype is None and isinstance(c, _GeneralConstraintData)):
                        old_cons.append(c)
                    else:
                        assert (c.ctype is SOSConstraint) or (c.ctype is None and isinstance(c, _SOSConstraintData))
                        old_sos.append(c)
        self.remove_constraints(old_cons)
        self.remove_sos_constraints(old_sos)
        timer.stop('cons')
        timer.start('vars')
        self.remove_variables(old_vars)
        timer.stop('vars')
        timer.start('params')
        self.remove_params(old_params)

        # sticking this between removal and addition
        # is important so that we don't do unnecessary work
        if config.update_params:
            self.update_params()

        self.add_params(new_params)
        timer.stop('params')
        timer.start('vars')
        self.add_variables(new_vars)
        timer.stop('vars')
        timer.start('cons')
        self.add_constraints(new_cons)
        self.add_sos_constraints(new_sos)
        new_cons_set = set(new_cons)
        new_sos_set = set(new_sos)
        new_vars_set = set(id(v) for v in new_vars)
        if config.update_constraints:
            cons_to_update = list()
            sos_to_update = list()
            for c in current_cons_dict.keys():
                if c not in new_cons_set:
                    cons_to_update.append(c)
            for c in current_sos_dict.keys():
                if c not in new_sos_set:
                    sos_to_update.append(c)
            cons_to_remove_and_add = list()
            for c in cons_to_update:
                lower, body, upper = self._active_constraints[c]
                if c.lower is not lower or c.body is not body or c.upper is not upper:
                    cons_to_remove_and_add.append(c)
            self.remove_constraints(cons_to_remove_and_add)
            self.add_constraints(cons_to_remove_and_add)
            self.remove_sos_constraints(sos_to_update)
            self.add_sos_constraints(sos_to_update)
        timer.stop('cons')
        timer.start('vars')
        if config.update_vars:
            vars_to_check = list()
            for v_id, v in current_vars_dict.items():
                if v_id not in new_vars_set:
                    vars_to_check.append(v)
            vars_to_update = list()
            for v in vars_to_check:
                _v, lb, ub, fixed, domain, value = self._vars[id(v)]
                if lb is not v.lb:
                    vars_to_update.append(v)
                elif ub is not v.ub:
                    vars_to_update.append(v)
                elif fixed is not v.is_fixed():
                    vars_to_update.append(v)
                elif domain is not v.domain:
                    vars_to_update.append(v)
                elif fixed and (value is not v.value):
                    vars_to_update.append(v)
            self.update_variables(vars_to_update)
        timer.stop('vars')
        timer.start('named expressions')
        if config.update_named_expressions:
            cons_to_update = list()
            for c, expr_list in self._named_expressions.items():
                if c in new_cons_set:
                    continue
                for named_expr, old_expr in expr_list:
                    if named_expr.expr is not old_expr:
                        cons_to_update.append(c)
                        break
            self.remove_constraints(cons_to_update)
            self.add_constraints(cons_to_update)
        timer.stop('named expressions')
        timer.start('objective')
        pyomo_obj = get_objective(self._model)
        need_to_set_objective = False
        if pyomo_obj is not self._objective:
            need_to_set_objective = True
        elif pyomo_obj is not None and pyomo_obj.expr is not self._objective_expr:
            need_to_set_objective = True
        elif pyomo_obj is not None and pyomo_obj.sense is not self._objective_sense:
            need_to_set_objective = True
        elif config.update_named_expressions:
            for named_expr, old_expr in self._obj_named_expressions:
                if named_expr.expr is not old_expr:
                    need_to_set_objective = True
                    break
        if need_to_set_objective:
            self.set_objective(pyomo_obj)
        timer.stop('objective')
Beispiel #21
0
def ip_solve(interface: BaseInteriorPointInterface,
             options: Optional[IPOptions] = None,
             timer: Optional[HierarchicalTimer] = None) -> InteriorPointStatus:
    """
    Parameters
    ----------
    interface: BaseInteriorPointInterface
        The interior point interface. This object handles the function evaluation,
        building the KKT matrix, and building the KKT right hand side.
    options: IPOptions
    timer: HierarchicalTimer
    """
    if options is None:
        options = IPOptions()

    if timer is None:
        timer = HierarchicalTimer()

    timer.start('IP solve')
    timer.start('init')

    barrier_parameter = options.init_barrier_parameter
    inertia_coef = options.inertia_correction.init_coef
    used_inertia_coef = 0

    t0 = time.time()
    primals = interface.init_primals().copy()
    slacks = interface.init_slacks().copy()
    duals_eq = interface.init_duals_eq().copy()
    duals_ineq = interface.init_duals_ineq().copy()
    duals_primals_lb = interface.init_duals_primals_lb().copy()
    duals_primals_ub = interface.init_duals_primals_ub().copy()
    duals_slacks_lb = interface.init_duals_slacks_lb().copy()
    duals_slacks_ub = interface.init_duals_slacks_ub().copy()

    process_init(primals, interface.primals_lb(), interface.primals_ub())
    process_init(slacks, interface.ineq_lb(), interface.ineq_ub())
    process_init_duals_lb(duals_primals_lb, interface.primals_lb())
    process_init_duals_ub(duals_primals_ub, interface.primals_ub())
    process_init_duals_lb(duals_slacks_lb, interface.ineq_lb())
    process_init_duals_ub(duals_slacks_ub, interface.ineq_ub())

    interface.set_barrier_parameter(barrier_parameter)

    alpha_primal_max = 1
    alpha_dual_max = 1

    logger.info('{_iter:<6}'
                '{objective:<11}'
                '{primal_inf:<11}'
                '{dual_inf:<11}'
                '{compl_inf:<11}'
                '{barrier:<11}'
                '{alpha_p:<11}'
                '{alpha_d:<11}'
                '{reg:<11}'
                '{time:<7}'.format(_iter='Iter',
                                   objective='Objective',
                                   primal_inf='Prim Inf',
                                   dual_inf='Dual Inf',
                                   compl_inf='Comp Inf',
                                   barrier='Barrier',
                                   alpha_p='Prim Step',
                                   alpha_d='Dual Step',
                                   reg='Reg',
                                   time='Time'))

    timer.stop('init')
    status = InteriorPointStatus.error

    for _iter in range(options.max_iter):
        interface.set_primals(primals)
        interface.set_slacks(slacks)
        interface.set_duals_eq(duals_eq)
        interface.set_duals_ineq(duals_ineq)
        interface.set_duals_primals_lb(duals_primals_lb)
        interface.set_duals_primals_ub(duals_primals_ub)
        interface.set_duals_slacks_lb(duals_slacks_lb)
        interface.set_duals_slacks_ub(duals_slacks_ub)

        timer.start('convergence check')
        primal_inf, dual_inf, complimentarity_inf = check_convergence(interface=interface, barrier=0, timer=timer)
        timer.stop('convergence check')
        objective = interface.evaluate_objective()
        logger.info('{_iter:<6}'
                    '{objective:<11.2e}'
                    '{primal_inf:<11.2e}'
                    '{dual_inf:<11.2e}'
                    '{compl_inf:<11.2e}'
                    '{barrier:<11.2e}'
                    '{alpha_p:<11.2e}'
                    '{alpha_d:<11.2e}'
                    '{reg:<11.2e}'
                    '{time:<7.3f}'.format(_iter=_iter,
                                          objective=objective,
                                          primal_inf=primal_inf,
                                          dual_inf=dual_inf,
                                          compl_inf=complimentarity_inf,
                                          barrier=barrier_parameter,
                                          alpha_p=alpha_primal_max,
                                          alpha_d=alpha_dual_max,
                                          reg=used_inertia_coef,
                                          time=time.time() - t0))

        if max(primal_inf, dual_inf, complimentarity_inf) <= options.tol:
            status = InteriorPointStatus.optimal
            break
        timer.start('convergence check')
        primal_inf, dual_inf, complimentarity_inf = check_convergence(interface=interface,
                                                                      barrier=barrier_parameter,
                                                                      timer=timer)
        timer.stop('convergence check')
        if max(primal_inf, dual_inf, complimentarity_inf) \
                <= 0.1 * barrier_parameter:
            barrier_parameter = max(options.minimum_barrier_parameter,
                                    min(0.5 * barrier_parameter, barrier_parameter ** 1.5))

        interface.set_barrier_parameter(barrier_parameter)
        timer.start('eval')
        timer.start('eval kkt')
        kkt = interface.evaluate_primal_dual_kkt_matrix(timer=timer)
        timer.stop('eval kkt')
        timer.start('eval rhs')
        rhs = interface.evaluate_primal_dual_kkt_rhs(timer=timer)
        timer.stop('eval rhs')
        timer.stop('eval')

        # Factorize linear system
        timer.start('factorize')
        if _iter == 0:
            timer.start('symbolic')
            sym_fact_status, sym_fact_iter = try_factorization_and_reallocation(kkt=kkt,
                                                                                linear_solver=options.linalg.solver,
                                                                                reallocation_factor=options.linalg.reallocation_factor,
                                                                                max_iter=options.linalg.max_num_reallocations,
                                                                                symbolic_or_numeric='symbolic',
                                                                                timer=timer)
            timer.stop('symbolic')
            if sym_fact_status != LinearSolverStatus.successful:
                raise RuntimeError('Could not factorize KKT system; linear solver status: ' + str(sym_fact_status))
        timer.start('numeric')
        used_inertia_coef = numeric_factorization(interface=interface,
                                                  kkt=kkt,
                                                  options=options,
                                                  inertia_coef=inertia_coef,
                                                  timer=timer)
        inertia_coef = used_inertia_coef * options.inertia_correction.factor_decrease
        if inertia_coef < options.inertia_correction.init_coef:
            inertia_coef = options.inertia_correction.init_coef
        timer.stop('numeric')
        timer.stop('factorize')

        timer.start('back solve')
        delta = options.linalg.solver.do_back_solve(rhs)
        timer.stop('back solve')

        interface.set_primal_dual_kkt_solution(delta)
        timer.start('frac boundary')
        alpha_primal_max, alpha_dual_max = fraction_to_the_boundary(interface=interface, tau=1 - barrier_parameter)
        timer.stop('frac boundary')
        delta_primals = interface.get_delta_primals()
        delta_slacks = interface.get_delta_slacks()
        delta_duals_eq = interface.get_delta_duals_eq()
        delta_duals_ineq = interface.get_delta_duals_ineq()
        delta_duals_primals_lb = interface.get_delta_duals_primals_lb()
        delta_duals_primals_ub = interface.get_delta_duals_primals_ub()
        delta_duals_slacks_lb = interface.get_delta_duals_slacks_lb()
        delta_duals_slacks_ub = interface.get_delta_duals_slacks_ub()

        primals += alpha_primal_max * delta_primals
        slacks += alpha_primal_max * delta_slacks
        duals_eq += alpha_dual_max * delta_duals_eq
        duals_ineq += alpha_dual_max * delta_duals_ineq
        duals_primals_lb += alpha_dual_max * delta_duals_primals_lb
        duals_primals_ub += alpha_dual_max * delta_duals_primals_ub
        duals_slacks_lb += alpha_dual_max * delta_duals_slacks_lb
        duals_slacks_ub += alpha_dual_max * delta_duals_slacks_ub

    timer.stop('IP solve')
    if options.report_timing:
        print(timer)
    return status
    def do_numeric_factorization(
            self,
            matrix: MPIBlockMatrix,
            raise_on_error: bool = True,
            timer: Optional[HierarchicalTimer] = None) -> LinearSolverResults:
        """
        Perform numeric factorization:
          * perform numeric factorization on each diagonal block
          * form and communicate the Schur-Complement
          * factorize the schur-complement

        This method should only be called after do_symbolic_factorization.

        Parameters
        ----------
        matrix: MPIBlockMatrix
            A Pynumero MPIBlockMatrix. This is the A matrix in Ax=b
        raise_on_error: bool
            If False, an error will not be raised if an error occurs during symbolic factorization. Instead the
            status attribute of the results object will indicate an error ocurred.
        timer: HierarchicalTimer
            A timer for profiling.

        Returns
        -------
        res: LinearSolverResults
            The results object
        """
        if timer is None:
            timer = HierarchicalTimer()

        self.block_matrix = block_matrix = matrix

        res = LinearSolverResults()
        res.status = LinearSolverStatus.successful
        timer.start('form SC')
        for ndx in self.local_block_indices:
            timer.start('factorize')
            sub_res = self.subproblem_solvers[ndx].do_numeric_factorization(
                matrix=block_matrix.get_block(ndx, ndx), raise_on_error=False)
            timer.stop('factorize')
            _process_sub_results(res, sub_res)
            if res.status not in {
                    LinearSolverStatus.successful, LinearSolverStatus.warning
            }:
                break
        res = _gather_results(res)
        if res.status not in {
                LinearSolverStatus.successful, LinearSolverStatus.warning
        }:
            if raise_on_error:
                raise RuntimeError(
                    'Numeric factorization unsuccessful; status: ' +
                    str(res.status))
            else:
                timer.stop('form SC')
                return res

        # in a scipy csr_matrix,
        #     data contains the values
        #     indices contains the column indices
        #     indptr contains the number of nonzeros in the row
        self.schur_complement.data = np.zeros(self.schur_complement.data.size,
                                              dtype=np.double)
        for ndx in self.local_block_indices:
            border_matrix: _BorderMatrix = self.border_matrices[ndx]
            A = border_matrix.csr
            _rhs = np.zeros(A.shape[1], dtype=np.double)
            solver = self.subproblem_solvers[ndx]
            for row_ndx in border_matrix.nonzero_rows:
                for indptr in range(A.indptr[row_ndx], A.indptr[row_ndx + 1]):
                    col = A.indices[indptr]
                    val = A.data[indptr]
                    _rhs[col] += val
                timer.start('back solve')
                contribution = solver.do_back_solve(_rhs)
                timer.stop('back solve')
                timer.start('dot product')
                contribution = A.dot(contribution)
                timer.stop('dot product')
                self.schur_complement.data[self.sc_data_slices[ndx][
                    row_ndx]] -= contribution[border_matrix.nonzero_rows]
                for indptr in range(A.indptr[row_ndx], A.indptr[row_ndx + 1]):
                    col = A.indices[indptr]
                    val = A.data[indptr]
                    _rhs[col] -= val

        timer.start('communicate')
        timer.start('zeros')
        sc = np.zeros(self.schur_complement.data.size, dtype=np.double)
        timer.stop('zeros')
        timer.start('Barrier')
        comm.Barrier()
        timer.stop('Barrier')
        timer.start('Allreduce')
        comm.Allreduce(self.schur_complement.data, sc)
        timer.stop('Allreduce')
        self.schur_complement.data = sc
        timer.start('add')
        sc = self.schur_complement + block_matrix.get_block(
            self.block_dim - 1, self.block_dim - 1).tocoo()
        timer.stop('add')
        timer.stop('communicate')
        timer.stop('form SC')

        timer.start('factor SC')
        sub_res = self.schur_complement_solver.do_symbolic_factorization(
            sc, raise_on_error=raise_on_error)
        _process_sub_results(res, sub_res)
        if res.status not in {
                LinearSolverStatus.successful, LinearSolverStatus.warning
        }:
            timer.stop('factor SC')
            return res
        sub_res = self.schur_complement_solver.do_numeric_factorization(sc)
        _process_sub_results(res, sub_res)
        timer.stop('factor SC')
        return res
    def do_symbolic_factorization(
            self,
            matrix: MPIBlockMatrix,
            raise_on_error: bool = True,
            timer: Optional[HierarchicalTimer] = None) -> LinearSolverResults:
        """
        Perform symbolic factorization. This performs symbolic factorization for each diagonal block and
        collects some information on the structure of the schur complement for sparse communication in
        the numeric factorization phase.

        Parameters
        ----------
        matrix: MPIBlockMatrix
            A Pynumero MPIBlockMatrix. This is the A matrix in Ax=b
        raise_on_error: bool
            If False, an error will not be raised if an error occurs during symbolic factorization. Instead the
            status attribute of the results object will indicate an error ocurred.
        timer: HierarchicalTimer
            A timer for profiling.

        Returns
        -------
        res: LinearSolverResults
            The results object
        """
        if timer is None:
            timer = HierarchicalTimer()

        block_matrix = matrix
        nbrows, nbcols = block_matrix.bshape
        if nbrows != nbcols:
            raise ValueError('The block matrix provided is not square.')
        self.block_dim = nbrows

        # split up the blocks between ranks
        self.local_block_indices = list()
        for ndx in range(self.block_dim - 1):
            if ((block_matrix.rank_ownership[ndx, ndx] == rank) or
                (block_matrix.rank_ownership[ndx, ndx] == -1 and rank == 0)):
                self.local_block_indices.append(ndx)

        res = LinearSolverResults()
        res.status = LinearSolverStatus.successful
        timer.start('factorize')
        for ndx in self.local_block_indices:
            sub_res = self.subproblem_solvers[ndx].do_symbolic_factorization(
                matrix=block_matrix.get_block(ndx, ndx), raise_on_error=False)
            _process_sub_results(res, sub_res)
            if res.status not in {
                    LinearSolverStatus.successful, LinearSolverStatus.warning
            }:
                break
        timer.stop('factorize')
        res = _gather_results(res)
        if res.status not in {
                LinearSolverStatus.successful, LinearSolverStatus.warning
        }:
            if raise_on_error:
                raise RuntimeError(
                    'Symbolic factorization unsuccessful; status: ' +
                    str(res.status))
            else:
                return res

        timer.start('sc_structure')
        self._get_sc_structure(block_matrix=block_matrix, timer=timer)
        timer.stop('sc_structure')

        return res