Exemple #1
0
 def test_clear(self):
     cmap = ComponentMap()
     self.assertEqual(len(cmap), 0)
     cmap.update(self._components)
     self.assertEqual(len(cmap), len(self._components))
     cmap.clear()
     self.assertEqual(len(cmap), 0)
Exemple #2
0
 def test_update(self):
     cmap = ComponentMap()
     self.assertEqual(len(cmap), 0)
     cmap.update(self._components)
     self.assertEqual(len(cmap), len(self._components))
     for c, val in self._components:
         self.assertEqual(cmap[c], val)
Exemple #3
0
 def test_len(self):
     cmap = ComponentMap()
     self.assertEqual(len(cmap), 0)
     cmap.update(self._components)
     self.assertEqual(len(cmap), len(self._components))
     cmap = ComponentMap(self._components)
     self.assertEqual(len(cmap), len(self._components))
     self.assertTrue(len(self._components) > 0)
Exemple #4
0
 def test_iter(self):
     cmap = ComponentMap()
     self.assertEqual(list(iter(cmap)), [])
     cmap.update(self._components)
     ids_seen = set()
     for c in cmap:
         ids_seen.add(id(c))
     self.assertEqual(ids_seen, set(id(c) for c, val in self._components))
Exemple #5
0
    def test_incidence_matrix(self):
        N = 5
        model = make_gas_expansion_model(N)
        all_vars = list(model.component_data_objects(pyo.Var))
        all_cons = list(model.component_data_objects(pyo.Constraint))
        imat = get_structural_incidence_matrix(all_vars, all_cons)
        n_var = 4 * (N + 1)
        n_con = 4 * N + 1
        self.assertEqual(imat.shape, (n_con, n_var))

        var_idx_map = ComponentMap((v, i) for i, v in enumerate(all_vars))
        con_idx_map = ComponentMap((c, i) for i, c in enumerate(all_cons))

        # Map constraints to the variables they contain.
        csr_map = ComponentMap()
        csr_map.update((model.mbal[i],
                        ComponentSet([
                            model.F[i],
                            model.F[i - 1],
                            model.rho[i],
                            model.rho[i - 1],
                        ])) for i in model.streams
                       if i != model.streams.first())
        csr_map.update((model.ebal[i],
                        ComponentSet([
                            model.F[i],
                            model.F[i - 1],
                            model.rho[i],
                            model.rho[i - 1],
                            model.T[i],
                            model.T[i - 1],
                        ])) for i in model.streams
                       if i != model.streams.first())
        csr_map.update((model.expansion[i],
                        ComponentSet([
                            model.rho[i],
                            model.rho[i - 1],
                            model.P[i],
                            model.P[i - 1],
                        ])) for i in model.streams
                       if i != model.streams.first())
        csr_map.update((model.ideal_gas[i],
                        ComponentSet([
                            model.P[i],
                            model.rho[i],
                            model.T[i],
                        ])) for i in model.streams)

        # Want to test that the columns have the rows we expect.
        i = model.streams.first()
        for i, j, e in zip(imat.row, imat.col, imat.data):
            con = all_cons[i]
            var = all_vars[j]
            self.assertIn(var, csr_map[con])
            csr_map[con].remove(var)
            self.assertEqual(e, 1.0)
        # And no additional rows
        for con in csr_map:
            self.assertEqual(len(csr_map[con]), 0)
Exemple #6
0
    def test_eq(self):
        cmap1 = ComponentMap()
        self.assertNotEqual(cmap1, set())
        self.assertFalse(cmap1 == set())
        self.assertNotEqual(cmap1, list())
        self.assertFalse(cmap1 == list())
        self.assertNotEqual(cmap1, tuple())
        self.assertFalse(cmap1 == tuple())
        self.assertEqual(cmap1, dict())
        self.assertTrue(cmap1 == dict())

        cmap1.update(self._components)
        self.assertNotEqual(cmap1, set())
        self.assertFalse(cmap1 == set())
        self.assertNotEqual(cmap1, list())
        self.assertFalse(cmap1 == list())
        self.assertNotEqual(cmap1, tuple())
        self.assertFalse(cmap1 == tuple())
        self.assertNotEqual(cmap1, dict())
        self.assertFalse(cmap1 == dict())

        self.assertTrue(cmap1 == cmap1)
        self.assertEqual(cmap1, cmap1)

        cmap2 = ComponentMap(self._components)
        self.assertTrue(cmap2 == cmap1)
        self.assertFalse(cmap2 != cmap1)
        self.assertEqual(cmap2, cmap1)
        self.assertTrue(cmap1 == cmap2)
        self.assertFalse(cmap1 != cmap2)
        self.assertEqual(cmap1, cmap2)

        del cmap2[self._components[0][0]]
        self.assertFalse(cmap2 == cmap1)
        self.assertTrue(cmap2 != cmap1)
        self.assertNotEqual(cmap2, cmap1)
        self.assertFalse(cmap1 == cmap2)
        self.assertTrue(cmap1 != cmap2)
        self.assertNotEqual(cmap1, cmap2)
Exemple #7
0
class MOSEKDirect(DirectSolver):
    """
    A class to provide a direct interface between pyomo and MOSEK's Optimizer API.
    Due to direct python bindings interacting with each other, there is no need for
    file IO.
    """
    def __init__(self, **kwds):
        kwds.setdefault('type', 'mosek_direct')
        DirectSolver.__init__(self, **kwds)
        self._pyomo_cone_to_solver_cone_map = dict()
        self._solver_cone_to_pyomo_cone_map = ComponentMap()
        self._name = None
        try:
            import mosek
            self._mosek = mosek
            self._mosek_env = self._mosek.Env()
            self._python_api_exists = True
            self._version = self._mosek_env.getversion()
            self._name = "MOSEK " + ".".join(str(i) for i in self._version)
        except ImportError:
            self._python_api_exists = False
        except Exception as e:
            print("Import of MOSEK failed - MOSEK message = " + str(e) + "\n")
            self._python_api_exists = False

        self._range_constraint = set()
        self._max_obj_degree = 2
        self._max_constraint_degree = 2
        self._termcode = None

        # Undefined capabilities default to None.
        self._capabilities.linear = True
        self._capabilities.quadratic_objective = True
        self._capabilities.quadratic_constraint = True
        self._capabilities.integer = True
        self._capabilities.conic_constraints = True
        self._capabilities.sos1 = False
        self._capabilities.sos2 = False

    def license_is_valid(self):
        """
        Runs a check for a valid MOSEK license. Returns False if MOSEK fails
        to run on a trivial test case.
        """
        try:
            import mosek
        except ImportError:
            return False
        try:
            mosek.Env().checkoutlicense(mosek.feature.pton)
            mosek.Env().checkinlicense(mosek.feature.pton)
        except mosek.Error:
            return False
        return True

    def _apply_solver(self):
        StaleFlagManager.mark_all_as_stale()

        if self._tee:

            def _process_stream(msg):
                sys.stdout.write(msg)
                sys.stdout.flush()

            self._solver_model.set_Stream(self._mosek.streamtype.log,
                                          _process_stream)

        if self._keepfiles:
            logger.info("Solver log file: {}".format(self._log_file))

        for key, option in self.options.items():
            try:
                param = key.split('.')
                if param[0] == 'mosek':
                    param.pop(0)
                param = getattr(self._mosek, param[0])(param[1])
                if 'sparam' in key.split('.'):
                    self._solver_model.putstrparam(param, option)
                elif 'dparam' in key.split('.'):
                    self._solver_model.putdouparam(param, option)
                elif 'iparam' in key.split('.'):
                    if isinstance(option, str):
                        option = option.split('.')
                        if option[0] == 'mosek':
                            option.pop('mosek')
                        option = getattr(self._mosek, option[0])(option[1])
                    else:
                        self._solver_model.putintparam(param, option)
            except (TypeError, AttributeError):
                raise
        try:
            self._termcode = self._solver_model.optimize()
            self._solver_model.solutionsummary(self._mosek.streamtype.msg)
        except self._mosek.Error as e:
            logger.error(e)
            raise
        return Bunch(rc=None, log=None)

    def _set_instance(self, model, kwds={}):
        self._range_constraints = set()
        super(MOSEKDirect, self)._set_instance(model, kwds)
        self._pyomo_cone_to_solver_cone_map = dict()
        self._solver_cone_to_pyomo_cone_map = ComponentMap()
        self._whichsol = getattr(self._mosek.soltype,
                                 kwds.pop('soltype', 'bas'))
        try:
            self._solver_model = self._mosek.Env().Task()
        except:
            err_msg = sys.exc_info()[1]
            logger.error("MOSEK task creation failed. " +
                         "Reason: {}".format(err_msg))
            raise
        self._add_block(model)

    def _get_cone_data(self, con):
        cone_type, cone_param, cone_members = None, 0, None
        if isinstance(con, quadratic):
            cone_type = self._mosek.conetype.quad
            cone_members = [con.r] + list(con.x)
        elif isinstance(con, rotated_quadratic):
            cone_type = self._mosek.conetype.rquad
            cone_members = [con.r1, con.r2] + list(con.x)
        elif self._version[0] >= 9:
            if isinstance(con, primal_exponential):
                cone_type = self._mosek.conetype.pexp
                cone_members = [con.r, con.x1, con.x2]
            elif isinstance(con, primal_power):
                cone_type = self._mosek.conetype.ppow
                cone_param = value(con.alpha)
                cone_members = [con.r1, con.r2] + list(con.x)
            elif isinstance(con, dual_exponential):
                cone_type = self._mosek.conetype.dexp
                cone_members = [con.r, con.x1, con.x2]
            elif isinstance(con, dual_power):
                cone_type = self._mosek.conetype.dpow
                cone_param = value(con.alpha)
                cone_members = [con.r1, con.r2] + list(con.x)
        return (cone_type, cone_param, ComponentSet(cone_members))

    def _get_expr_from_pyomo_repn(self, repn, max_degree=2):
        degree = repn.polynomial_degree()
        if (degree is None) or degree > max_degree:
            raise DegreeError(
                'MOSEK does not support expressions of degree {}.'.format(
                    degree))

        referenced_vars = ComponentSet(repn.linear_vars)
        indices = tuple(self._pyomo_var_to_solver_var_map[i]
                        for i in repn.linear_vars)
        mosek_arow = (indices, tuple(repn.linear_coefs), repn.constant)

        if len(repn.quadratic_vars) == 0:
            mosek_qexp = ((), (), ())
            return mosek_arow, mosek_qexp, referenced_vars
        else:
            q_vars = itertools.chain.from_iterable(repn.quadratic_vars)
            referenced_vars.update(q_vars)
            qsubi = tuple(self._pyomo_var_to_solver_var_map[i]
                          for i, j in repn.quadratic_vars)
            qsubj = tuple(self._pyomo_var_to_solver_var_map[j]
                          for i, j in repn.quadratic_vars)
            qvals = tuple(v * 2 if qsubi[i] is qsubj[i] else v
                          for i, v in enumerate(repn.quadratic_coefs))
            mosek_qexp = (qsubj, qsubi, qvals)
        return mosek_arow, mosek_qexp, referenced_vars

    def _get_expr_from_pyomo_expr(self, expr, max_degree=2):
        repn = generate_standard_repn(expr, quadratic=(max_degree == 2))
        try:
            mosek_arow, mosek_qexp, referenced_vars = self._get_expr_from_pyomo_repn(
                repn, max_degree)
        except DegreeError as e:
            msg = e.args[0]
            msg += '\nexpr: {}'.format(expr)
            logger.error(DegreeError(msg))
            raise e
        return mosek_arow, mosek_qexp, referenced_vars

    def _mosek_vartype_from_var(self, var):
        if var.is_integer():
            return self._mosek.variabletype.type_int
        return self._mosek.variabletype.type_cont

    def _mosek_bounds(self, lb, ub, fixed_bool):
        if fixed_bool:
            return self._mosek.boundkey.fx
        if lb == -inf:
            if ub == inf:
                return self._mosek.boundkey.fr
            else:
                return self._mosek.boundkey.up
        elif ub == inf:
            return self._mosek.boundkey.lo
        return self._mosek.boundkey.ra

    def _add_var(self, var):
        self._add_vars((var, ))

    def _add_vars(self, var_seq):
        if not var_seq:
            return
        var_num = self._solver_model.getnumvar()
        vnames = tuple(
            self._symbol_map.getSymbol(v, self._labeler) for v in var_seq)
        vtypes = tuple(map(self._mosek_vartype_from_var, var_seq))
        lbs = tuple(
            value(v) if v.fixed else -inf if value(v.lb) is None else value(v.
                                                                            lb)
            for v in var_seq)
        ubs = tuple(
            value(v) if v.fixed else inf if value(v.ub) is None else value(v.ub
                                                                           )
            for v in var_seq)
        fxs = tuple(v.is_fixed() for v in var_seq)
        bound_types = tuple(map(self._mosek_bounds, lbs, ubs, fxs))
        self._solver_model.appendvars(len(var_seq))
        var_ids = range(var_num, var_num + len(var_seq))
        _vnames = tuple(map(self._solver_model.putvarname, var_ids, vnames))
        self._solver_model.putvartypelist(var_ids, vtypes)
        self._solver_model.putvarboundlist(var_ids, bound_types, lbs, ubs)
        self._pyomo_var_to_solver_var_map.update(zip(var_seq, var_ids))
        self._solver_var_to_pyomo_var_map.update(zip(var_ids, var_seq))
        self._referenced_variables.update(zip(var_seq, [0] * len(var_seq)))

    def _add_constraint(self, con):
        self._add_constraints((con, ))

    def _add_constraints(self, con_seq):
        if not con_seq:
            return
        active_seq = tuple(filter(operator.attrgetter('active'), con_seq))
        if len(active_seq) != len(con_seq):
            logger.warning("Inactive constraints will be skipped.")
        con_seq = active_seq
        if self._skip_trivial_constraints:
            con_seq = tuple(
                filter(is_fixed(operator.attrgetter('body')), con_seq))

        lq = tuple(
            filter(operator.attrgetter("_linear_canonical_form"), con_seq))
        conic = tuple(filter(lambda x: isinstance(x, _ConicBase), con_seq))
        lq_ex = tuple(
            filterfalse(
                lambda x: isinstance(x, _ConicBase) or
                (x._linear_canonical_form), con_seq))
        lq_all = lq + lq_ex
        num_lq = len(lq) + len(lq_ex)
        num_cones = len(conic)
        if num_lq > 0:
            con_num = self._solver_model.getnumcon()
            lq_data = [
                self._get_expr_from_pyomo_repn(c.canonical_form()) for c in lq
            ]
            lq_data.extend(
                self._get_expr_from_pyomo_expr(c.body) for c in lq_ex)
            arow, qexp, referenced_vars = zip(*lq_data)
            q_is, q_js, q_vals = zip(*qexp)
            l_ids, l_coefs, constants = zip(*arow)
            lbs = tuple(-inf if value(lq_all[i].lower) is None else
                        value(lq_all[i].lower) - constants[i]
                        for i in range(num_lq))
            ubs = tuple(inf if value(lq_all[i].upper) is None else
                        value(lq_all[i].upper) - constants[i]
                        for i in range(num_lq))
            fxs = tuple(c.equality for c in lq_all)
            bound_types = tuple(map(self._mosek_bounds, lbs, ubs, fxs))
            sub = range(con_num, con_num + num_lq)
            sub_names = tuple(
                self._symbol_map.getSymbol(c, self._labeler) for c in lq_all)
            ptre = tuple(accumulate(list(map(len, l_ids))))
            ptrb = (0, ) + ptre[:-1]
            asubs = tuple(itertools.chain.from_iterable(l_ids))
            avals = tuple(itertools.chain.from_iterable(l_coefs))
            qcsubi = tuple(itertools.chain.from_iterable(q_is))
            qcsubj = tuple(itertools.chain.from_iterable(q_js))
            qcval = tuple(itertools.chain.from_iterable(q_vals))
            qcsubk = tuple(i for i in sub
                           for j in range(len(q_is[i - con_num])))
            self._solver_model.appendcons(num_lq)
            self._solver_model.putarowlist(sub, ptrb, ptre, asubs, avals)
            self._solver_model.putqcon(qcsubk, qcsubi, qcsubj, qcval)
            self._solver_model.putconboundlist(sub, bound_types, lbs, ubs)
            for i, s_n in enumerate(sub_names):
                self._solver_model.putconname(sub[i], s_n)
            self._pyomo_con_to_solver_con_map.update(zip(lq_all, sub))
            self._solver_con_to_pyomo_con_map.update(zip(sub, lq_all))

            for i, c in enumerate(lq_all):
                self._vars_referenced_by_con[c] = referenced_vars[i]
                for v in referenced_vars[i]:
                    self._referenced_variables[v] += 1

        if num_cones > 0:
            cone_num = self._solver_model.getnumcone()
            cone_indices = range(cone_num, cone_num + num_cones)
            cone_names = tuple(
                self._symbol_map.getSymbol(c, self._labeler) for c in conic)
            cone_type, cone_param, cone_members = zip(
                *map(self._get_cone_data, conic))
            for i in range(num_cones):
                members = tuple(self._pyomo_var_to_solver_var_map[c_m]
                                for c_m in cone_members[i])
                self._solver_model.appendcone(cone_type[i], cone_param[i],
                                              members)
                self._solver_model.putconename(cone_indices[i], cone_names[i])
            self._pyomo_cone_to_solver_cone_map.update(zip(
                conic, cone_indices))
            self._solver_cone_to_pyomo_cone_map.update(zip(
                cone_indices, conic))

            for i, c in enumerate(conic):
                self._vars_referenced_by_con[c] = cone_members[i]
                for v in cone_members[i]:
                    self._referenced_variables[v] += 1

    def _set_objective(self, obj):
        if self._objective is not None:
            for var in self._vars_referenced_by_obj:
                self._referenced_variables[var] -= 1
            self._vars_referenced_by_obj = ComponentSet()
            self._objective = None

        if obj.active is False:
            raise ValueError('Cannot add inactive objective to solver.')

        if obj.sense == minimize:
            self._solver_model.putobjsense(self._mosek.objsense.minimize)
        elif obj.sense == maximize:
            self._solver_model.putobjsense(self._mosek.objsense.maximize)
        else:
            raise ValueError("Objective sense not recognized.")

        mosek_arow, mosek_qexp, referenced_vars = self._get_expr_from_pyomo_expr(
            obj.expr, self._max_obj_degree)

        for var in referenced_vars:
            self._referenced_variables[var] += 1

        self._solver_model.putclist(mosek_arow[0], mosek_arow[1])
        self._solver_model.putqobj(mosek_qexp[0], mosek_qexp[1], mosek_qexp[2])
        self._solver_model.putcfix(mosek_arow[2])

        self._objective = obj
        self._vars_referenced_by_obj = referenced_vars

    def _add_block(self, block):
        """
        Overrides the _add_block method to utilize _add_vars/_add_constraints.

        This will keep any existing model components intact.

        Use this method when adding conic domains. The add_constraint method
        is compatible with conic-constraints, not conic-domains.

        Parameters
        ----------
        block: Block (scalar Block or single _BlockData)
        """
        var_seq = tuple(
            block.component_data_objects(ctype=pyomo.core.base.var.Var,
                                         descend_into=True,
                                         active=True,
                                         sort=True))
        self._add_vars(var_seq)
        for sub_block in block.block_data_objects(descend_into=True,
                                                  active=True):
            con_list = []
            for con in sub_block.component_data_objects(
                    ctype=pyomo.core.base.constraint.Constraint,
                    descend_into=False,
                    active=True,
                    sort=True):
                if (not con.has_lb()) and \
                   (not con.has_ub()):
                    assert not con.equality
                    continue  # non-binding, so skip
                con_list.append(con)
            self._add_constraints(con_list)

            for con in sub_block.component_data_objects(
                    ctype=pyomo.core.base.sos.SOSConstraint,
                    descend_into=False,
                    active=True,
                    sort=True):
                self._add_sos_constraint(con)

            obj_counter = 0
            for obj in sub_block.component_data_objects(
                    ctype=pyomo.core.base.objective.Objective,
                    descend_into=False,
                    active=True):
                obj_counter += 1
                if obj_counter > 1:
                    raise ValueError("Solver interface does not "
                                     "support multiple objectives.")
                self._set_objective(obj)

    def _postsolve(self):

        extract_duals = False
        extract_slacks = False
        extract_reduced_costs = False
        for suffix in self._suffixes:
            flag = False
            if re.match(suffix, "dual"):
                extract_duals = True
                flag = True
            if re.match(suffix, "slack"):
                extract_slacks = True
                flag = True
            if re.match(suffix, "rc"):
                extract_reduced_costs = True
                flag = True
            if not flag:
                raise RuntimeError(
                    "***MOSEK solver plugin cannot extract solution suffix = "
                    + suffix)

        msk_task = self._solver_model
        msk = self._mosek

        itr_soltypes = [
            msk.problemtype.qo, msk.problemtype.qcqo, msk.problemtype.conic
        ]

        if (msk_task.getnumintvar() >= 1):
            self._whichsol = msk.soltype.itg
            if extract_reduced_costs:
                logger.warning("Cannot get reduced costs for MIP.")
            if extract_duals:
                logger.warning("Cannot get duals for MIP.")
            extract_reduced_costs = False
            extract_duals = False
        elif (msk_task.getprobtype() in itr_soltypes):
            self._whichsol = msk.soltype.itr

        whichsol = self._whichsol
        sol_status = msk_task.getsolsta(whichsol)
        pro_status = msk_task.getprosta(whichsol)

        self.results = SolverResults()
        soln = Solution()

        self.results.solver.name = self._name
        self.results.solver.wallclock_time = msk_task.getdouinf(
            msk.dinfitem.optimizer_time)

        SOLSTA_MAP = {
            msk.solsta.unknown: 'unknown',
            msk.solsta.optimal: 'optimal',
            msk.solsta.prim_and_dual_feas: 'pd_feas',
            msk.solsta.prim_feas: 'p_feas',
            msk.solsta.dual_feas: 'd_feas',
            msk.solsta.prim_infeas_cer: 'p_infeas',
            msk.solsta.dual_infeas_cer: 'd_infeas',
            msk.solsta.prim_illposed_cer: 'p_illposed',
            msk.solsta.dual_illposed_cer: 'd_illposed',
            msk.solsta.integer_optimal: 'optimal'
        }
        PROSTA_MAP = {
            msk.prosta.unknown: 'unknown',
            msk.prosta.prim_and_dual_feas: 'pd_feas',
            msk.prosta.prim_feas: 'p_feas',
            msk.prosta.dual_feas: 'd_feas',
            msk.prosta.prim_infeas: 'p_infeas',
            msk.prosta.dual_infeas: 'd_infeas',
            msk.prosta.prim_and_dual_infeas: 'pd_infeas',
            msk.prosta.ill_posed: 'illposed',
            msk.prosta.prim_infeas_or_unbounded: 'p_inf_unb'
        }

        if self._version[0] < 9:
            SOLSTA_OLD = {
                msk.solsta.near_optimal: 'optimal',
                msk.solsta.near_integer_optimal: 'optimal',
                msk.solsta.near_prim_feas: 'p_feas',
                msk.solsta.near_dual_feas: 'd_feas',
                msk.solsta.near_prim_and_dual_feas: 'pd_feas',
                msk.solsta.near_prim_infeas_cer: 'p_infeas',
                msk.solsta.near_dual_infeas_cer: 'd_infeas'
            }
            PROSTA_OLD = {
                msk.prosta.near_prim_and_dual_feas: 'pd_feas',
                msk.prosta.near_prim_feas: 'p_feas',
                msk.prosta.near_dual_feas: 'd_feas'
            }
            SOLSTA_MAP.update(SOLSTA_OLD)
            PROSTA_MAP.update(PROSTA_OLD)

        if self._termcode == msk.rescode.ok:
            self.results.solver.status = SolverStatus.ok
            self.results.solver.termination_message = ""

        elif self._termcode == msk.rescode.trm_max_iterations:
            self.results.solver.status = SolverStatus.ok
            self.results.solver.termination_message = "Optimizer terminated at the maximum number of iterations."
            self.results.solver.termination_condition = TerminationCondition.maxIterations
            soln.status = SolutionStatus.stoppedByLimit

        elif self._termcode == msk.rescode.trm_max_time:
            self.results.solver.status = SolverStatus.ok
            self.results.solver.termination_message = "Optimizer terminated at the maximum amount of time."
            self.results.solver.termination_condition = TerminationCondition.maxTimeLimit
            soln.status = SolutionStatus.stoppedByLimit

        elif self._termcode == msk.rescode.trm_user_callback:
            self.results.solver.status = SolverStatus.aborted
            self.results.solver.termination_message = "Optimizer terminated due to the return of the "\
                "user-defined callback function."
            self.results.solver.termination_condition = TerminationCondition.userInterrupt
            soln.status = SolutionStatus.unknown

        elif self._termcode in [
                msk.rescode.trm_mio_num_relaxs,
                msk.rescode.trm_mio_num_branches,
                msk.rescode.trm_num_max_num_int_solutions
        ]:
            self.results.solver.status = SolverStatus.ok
            self.results.solver.termination_message = "The mixed-integer optimizer terminated as the maximum number "\
                "of relaxations/branches/feasible solutions was reached."
            self.results.solver.termination_condition = TerminationCondition.maxEvaluations
            soln.status = SolutionStatus.stoppedByLimit
        else:
            self.results.solver.termination_message = " Optimization terminated with {} response code." \
                "Check MOSEK response code documentation for more information.".format(
                    self._termcode)
            self.results.solver.termination_condition = TerminationCondition.unknown

        if SOLSTA_MAP[sol_status] == 'unknown':
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_message += " The solution status is unknown."
            self.results.solver.Message = self.results.solver.termination_message
            self.results.solver.termination_condition = TerminationCondition.unknown
            soln.status = SolutionStatus.unknown

        if PROSTA_MAP[pro_status] == 'd_infeas':
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_message += " Problem is dual infeasible"
            self.results.solver.Message = self.results.solver.termination_message
            self.results.solver.termination_condition = TerminationCondition.unbounded
            soln.status = SolutionStatus.unbounded

        elif PROSTA_MAP[pro_status] == 'p_infeas':
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_message += " Problem is primal infeasible."
            self.results.solver.Message = self.results.solver.termination_message
            self.results.solver.termination_condition = TerminationCondition.infeasible
            soln.status = SolutionStatus.infeasible

        elif PROSTA_MAP[pro_status] == 'pd_infeas':
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_message += " Problem is primal and dual infeasible."
            self.results.solver.Message = self.results.solver.termination_message
            self.results.solver.termination_condition = TerminationCondition.infeasible
            soln.status = SolutionStatus.infeasible

        elif PROSTA_MAP[pro_status] == 'p_inf_unb':
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_message += " Problem is either primal infeasible or unbounded."\
                " This may happen for MIPs."
            self.results.solver.Message = self.results.solver.termination_message
            self.results.solver.termination_condition = TerminationCondition.infeasibleOrUnbounded
            soln.status = SolutionStatus.unsure

        if SOLSTA_MAP[sol_status] == 'optimal':
            self.results.solver.status = SolverStatus.ok
            self.results.solver.termination_message += " Model was solved to optimality and an optimal solution is available."
            self.results.solver.termination_condition = TerminationCondition.optimal
            soln.status = SolutionStatus.optimal

        elif SOLSTA_MAP[sol_status] == 'pd_feas':
            self.results.solver.status = SolverStatus.ok
            self.results.solver.termination_message += " The solution is both primal and dual feasible."
            self.results.solver.termination_condition = TerminationCondition.feasible
            soln.status = SolutionStatus.feasible

        elif SOLSTA_MAP[sol_status] == 'p_feas':
            self.results.solver.status = SolverStatus.ok
            self.results.solver.termination_message += " The solution is primal feasible."
            self.results.solver.termination_condition = TerminationCondition.feasible
            soln.status = SolutionStatus.feasible

        elif SOLSTA_MAP[sol_status] == 'd_feas':
            self.results.solver.status = SolverStatus.ok
            self.results.solver.termination_message += " The solution is dual feasible."
            self.results.solver.termination_condition = TerminationCondition.feasible
            soln.status = SolutionStatus.feasible

        elif SOLSTA_MAP[sol_status] == 'd_infeas':
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_message += " The solution is a certificate of dual infeasibility."
            self.results.solver.Message = self.results.solver.termination_message
            self.results.solver.termination_condition = TerminationCondition.unbounded
            soln.status = SolutionStatus.infeasible

        elif SOLSTA_MAP[sol_status] == 'p_infeas':
            self.results.solver.status = SolverStatus.warning
            self.results.solver.termination_message += " The solution is a certificate of primal infeasibility."
            self.results.solver.Message = self.results.solver.termination_message
            self.results.solver.termination_condition = TerminationCondition.infeasible
            soln.status = SolutionStatus.infeasible

        self.results.problem.name = msk_task.gettaskname()

        if msk_task.getobjsense() == msk.objsense.minimize:
            self.results.problem.sense = minimize
        elif msk_task.getobjsense() == msk.objsense.maximize:
            self.results.problem.sense = maximize
        else:
            raise RuntimeError(
                'Unrecognized Mosek objective sense: {0}'.format(
                    msk_task.getobjname()))

        self.results.problem.upper_bound = None
        self.results.problem.lower_bound = None

        if msk_task.getnumintvar() == 0:
            try:
                if msk_task.getobjsense() == msk.objsense.minimize:
                    self.results.problem.upper_bound = msk_task.getprimalobj(
                        whichsol)
                    self.results.problem.lower_bound = msk_task.getdualobj(
                        whichsol)
                elif msk_task.getobjsense() == msk.objsense.maximize:
                    self.results.problem.upper_bound = msk_task.getprimalobj(
                        whichsol)
                    self.results.problem.lower_bound = msk_task.getdualobj(
                        whichsol)

            except (msk.MosekException, AttributeError):
                pass
        elif msk_task.getobjsense() == msk.objsense.minimize:  # minimizing
            try:
                self.results.problem.upper_bound = msk_task.getprimalobj(
                    whichsol)
            except (msk.MosekException, AttributeError):
                pass
            try:
                self.results.problem.lower_bound = msk_task.getdouinf(
                    msk.dinfitem.mio_obj_bound)
            except (msk.MosekException, AttributeError):
                pass
        elif msk_task.getobjsense() == msk.objsense.maximize:  # maximizing
            try:
                self.results.problem.upper_bound = msk_task.getdouinf(
                    msk.dinfitem.mio_obj_bound)
            except (msk.MosekException, AttributeError):
                pass
            try:
                self.results.problem.lower_bound = msk_task.getprimalobj(
                    whichsol)
            except (msk.MosekException, AttributeError):
                pass
        else:
            raise RuntimeError(
                'Unrecognized Mosek objective sense: {0}'.format(
                    msk_task.getobjsense()))

        try:
            soln.gap = self.results.problem.upper_bound - self.results.problem.lower_bound
        except TypeError:
            soln.gap = None

        self.results.problem.number_of_constraints = msk_task.getnumcon()
        self.results.problem.number_of_nonzeros = msk_task.getnumanz()
        self.results.problem.number_of_variables = msk_task.getnumvar()
        self.results.problem.number_of_integer_variables = msk_task.getnumintvar(
        )
        self.results.problem.number_of_continuous_variables = msk_task.getnumvar() - \
            msk_task.getnumintvar()
        self.results.problem.number_of_objectives = 1
        self.results.problem.number_of_solutions = 1

        if self._save_results:
            """
            This code in this if statement is only needed for backwards compatability. It is more efficient to set
            _save_results to False and use load_vars, load_duals, etc.
            """
            if self.results.problem.number_of_solutions > 0:
                soln_variables = soln.variable
                soln_constraints = soln.constraint

                mosek_vars = list(range(msk_task.getnumvar()))
                mosek_vars = list(
                    set(mosek_vars).intersection(
                        set(self._pyomo_var_to_solver_var_map.values())))
                var_vals = [0.0] * len(mosek_vars)
                self._solver_model.getxx(whichsol, var_vals)
                names = list(map(msk_task.getvarname, mosek_vars))

                for mosek_var, val, name in zip(mosek_vars, var_vals, names):
                    pyomo_var = self._solver_var_to_pyomo_var_map[mosek_var]
                    if self._referenced_variables[pyomo_var] > 0:
                        soln_variables[name] = {"Value": val}

                if extract_reduced_costs:
                    vals = [0.0] * len(mosek_vars)
                    msk_task.getreducedcosts(whichsol, 0, len(mosek_vars),
                                             vals)
                    for mosek_var, val, name in zip(mosek_vars, vals, names):
                        pyomo_var = self._solver_var_to_pyomo_var_map[
                            mosek_var]
                        if self._referenced_variables[pyomo_var] > 0:
                            soln_variables[name]["Rc"] = val

                if extract_duals or extract_slacks:
                    mosek_cons = list(range(msk_task.getnumcon()))
                    con_names = list(map(msk_task.getconname, mosek_cons))
                    for name in con_names:
                        soln_constraints[name] = {}
                    """TODO wrong length, needs to be getnumvars()
                    mosek_cones = list(range(msk_task.getnumcone()))
                    cone_names = []
                    for cone in mosek_cones:
                        cone_names.append(msk_task.getconename(cone))
                    for name in cone_names:
                        soln_constraints[name] = {}
                    """

                if extract_duals:
                    ncon = msk_task.getnumcon()
                    if ncon > 0:
                        vals = [0.0] * ncon
                        msk_task.gety(whichsol, vals)
                        for val, name in zip(vals, con_names):
                            soln_constraints[name]["Dual"] = val
                    """TODO: wrong length, needs to be getnumvars()
                    ncone = msk_task.getnumcone()
                    if ncone > 0:
                        vals = [0.0]*ncone
                        msk_task.getsnx(whichsol, vals)
                        for val, name in zip(vals, cone_names):
                            soln_constraints[name]["Dual"] = val
                    """

                if extract_slacks:
                    Ax = [0] * len(mosek_cons)
                    msk_task.getxc(self._whichsol, Ax)
                    for con, name in zip(mosek_cons, con_names):
                        Us = Ls = 0

                        bk, lb, ub = msk_task.getconbound(con)

                        if bk in [
                                msk.boundkey.fx, msk.boundkey.ra,
                                msk.boundkey.up
                        ]:
                            Us = ub - Ax[con]
                        if bk in [
                                msk.boundkey.fx, msk.boundkey.ra,
                                msk.boundkey.lo
                        ]:
                            Ls = Ax[con] - lb

                        if Us > Ls:
                            soln_constraints[name]["Slack"] = Us
                        else:
                            soln_constraints[name]["Slack"] = -Ls

        elif self._load_solutions:
            if self.results.problem.number_of_solutions > 0:

                self.load_vars()

                if extract_reduced_costs:
                    self._load_rc()

                if extract_duals:
                    self._load_duals()

                if extract_slacks:
                    self._load_slacks()

        self.results.solution.insert(soln)

        # finally, clean any temporary files registered with the temp file
        # manager, created populated *directly* by this plugin.
        TempfileManager.pop(remove=not self._keepfiles)

        return DirectOrPersistentSolver._postsolve(self)

    def warm_start_capable(self):
        return True

    def _warm_start(self):
        for pyomo_var, mosek_var in self._pyomo_var_to_solver_var_map.items():
            if pyomo_var.value is not None:
                for solType in self._mosek.soltype.values:
                    self._solver_model.putxxslice(solType, mosek_var,
                                                  mosek_var + 1,
                                                  [(pyomo_var.value)])

    def _load_vars(self, vars_to_load=None):
        var_map = self._pyomo_var_to_solver_var_map
        ref_vars = self._referenced_variables
        if vars_to_load is None:
            vars_to_load = var_map.keys()

        mosek_vars_to_load = [var_map[pyomo_var] for pyomo_var in vars_to_load]
        var_vals = [0.0] * len(mosek_vars_to_load)
        self._solver_model.getxx(self._whichsol, var_vals)

        for var, val in zip(vars_to_load, var_vals):
            if ref_vars[var] > 0:
                var.set_value(val, skip_validation=True)

    def _load_rc(self, vars_to_load=None):
        if not hasattr(self._pyomo_model, 'rc'):
            self._pyomo_model.rc = Suffix(direction=Suffix.IMPORT)
        var_map = self._pyomo_var_to_solver_var_map
        ref_vars = self._referenced_variables
        rc = self._pyomo_model.rc
        if vars_to_load is None:
            vars_to_load = var_map.keys()

        mosek_vars_to_load = [var_map[pyomo_var] for pyomo_var in vars_to_load]
        vals = [0.0] * len(mosek_vars_to_load)
        self._solver_model.getreducedcosts(self._whichsol, 0,
                                           len(mosek_vars_to_load), vals)

        for var, val in zip(vars_to_load, vals):
            if ref_vars[var] > 0:
                rc[var] = val

    def _load_duals(self, objs_to_load=None):
        if not hasattr(self._pyomo_model, 'dual'):
            self._pyomo_model.dual = Suffix(direction=Suffix.IMPORT)
        con_map = self._pyomo_con_to_solver_con_map
        reverse_con_map = self._solver_con_to_pyomo_con_map
        cone_map = self._pyomo_cone_to_solver_cone_map
        #reverse_cone_map = self._solver_cone_to_pyomo_cone_map
        dual = self._pyomo_model.dual

        if objs_to_load is None:
            # constraints
            mosek_cons_to_load = range(self._solver_model.getnumcon())
            vals = [0.0] * len(mosek_cons_to_load)
            self._solver_model.gety(self._whichsol, vals)
            for mosek_con, val in zip(mosek_cons_to_load, vals):
                pyomo_con = reverse_con_map[mosek_con]
                dual[pyomo_con] = val
            """TODO wrong length, needs to be getnumvars()
            # cones
            mosek_cones_to_load = range(self._solver_model.getnumcone())
            vals = [0.0]*len(mosek_cones_to_load)
            self._solver_model.getsnx(self._whichsol, vals)
            for mosek_cone, val in zip(mosek_cones_to_load, vals):
                pyomo_cone = reverse_cone_map[mosek_cone]
                dual[pyomo_cone] = val
            """
        else:
            mosek_cons_to_load = []
            mosek_cones_to_load = []
            for obj in objs_to_load:
                if obj in con_map:
                    mosek_cons_to_load.append(con_map[obj])
                else:
                    # assume it is a cone
                    mosek_cones_to_load.append(cone_map[obj])
            # constraints
            mosek_cons_first = min(mosek_cons_to_load)
            mosek_cons_last = max(mosek_cons_to_load)
            vals = [0.0] * (mosek_cons_last - mosek_cons_first + 1)
            self._solver_model.getyslice(self._whichsol, mosek_cons_first,
                                         mosek_cons_last, vals)
            for mosek_con in mosek_cons_to_load:
                slice_index = mosek_con - mosek_cons_first
                val = vals[slice_index]
                pyomo_con = reverse_con_map[mosek_con]
                dual[pyomo_con] = val
            """TODO wrong length, needs to be getnumvars()
            # cones
            mosek_cones_first = min(mosek_cones_to_load)
            mosek_cones_last = max(mosek_cones_to_load)
            vals = [0.0]*(mosek_cones_last - mosek_cones_first + 1)
            self._solver_model.getsnxslice(self._whichsol,
                                           mosek_cones_first,
                                           mosek_cones_last,
                                           vals)
            for mosek_cone in mosek_cones_to_load:
                slice_index = mosek_cone - mosek_cones_first
                val = vals[slice_index]
                pyomo_cone = reverse_cone_map[mosek_cone]
                dual[pyomo_cone] = val
            """

    def _load_slacks(self, cons_to_load=None):
        if not hasattr(self._pyomo_model, 'slack'):
            self._pyomo_model.slack = Suffix(direction=Suffix.IMPORT)
        con_map = self._pyomo_con_to_solver_con_map
        reverse_con_map = self._solver_con_to_pyomo_con_map
        slack = self._pyomo_model.slack
        msk = self._mosek

        if cons_to_load is None:
            mosek_cons_to_load = range(self._solver_model.getnumcon())
        else:
            mosek_cons_to_load = set(
                [con_map[pyomo_con] for pyomo_con in cons_to_load])

        Ax = [0] * len(mosek_cons_to_load)
        self._solver_model.getxc(self._whichsol, Ax)
        for con in mosek_cons_to_load:
            pyomo_con = reverse_con_map[con]
            Us = Ls = 0

            bk, lb, ub = self._solver_model.getconbound(con)

            if bk in [msk.boundkey.fx, msk.boundkey.ra, msk.boundkey.up]:
                Us = ub - Ax[con]
            if bk in [msk.boundkey.fx, msk.boundkey.ra, msk.boundkey.lo]:
                Ls = Ax[con] - lb

            if Us > Ls:
                slack[pyomo_con] = Us
            else:
                slack[pyomo_con] = -Ls

    def load_duals(self, cons_to_load=None):
        """
        Load the duals into the 'dual' suffix. The 'dual' suffix must live on the parent model.

        Parameters
        ----------
        cons_to_load: list of Constraint
        """
        self._load_duals(cons_to_load)

    def load_rc(self, vars_to_load):
        """
        Load the reduced costs into the 'rc' suffix. The 'rc' suffix must live on the parent model.

        Parameters
        ----------
        vars_to_load: list of Var
        """
        self._load_rc(vars_to_load)

    def load_slacks(self, cons_to_load=None):
        """
        Load the values of the slack variables into the 'slack' suffix. The 'slack' suffix must live on the parent
        model.

        Parameters
        ----------
        cons_to_load: list of Constraint
        """
        self._load_slacks(cons_to_load)
Exemple #8
0
    def test_incidence_matrix(self):
        N = 5
        model = make_gas_expansion_model(N)
        all_vars = list(model.component_data_objects(pyo.Var))
        all_cons = list(model.component_data_objects(pyo.Constraint))
        imat = get_numeric_incidence_matrix(all_vars, all_cons)
        n_var = 4 * (N + 1)
        n_con = 4 * N + 1
        self.assertEqual(imat.shape, (n_con, n_var))

        var_idx_map = ComponentMap((v, i) for i, v in enumerate(all_vars))
        con_idx_map = ComponentMap((c, i) for i, c in enumerate(all_cons))

        # Map constraints to the variables they contain.
        csr_map = ComponentMap()
        csr_map.update((model.mbal[i],
                        ComponentSet([
                            model.F[i],
                            model.F[i - 1],
                            model.rho[i],
                            model.rho[i - 1],
                        ])) for i in model.streams
                       if i != model.streams.first())
        csr_map.update((model.ebal[i],
                        ComponentSet([
                            model.F[i],
                            model.F[i - 1],
                            model.rho[i],
                            model.rho[i - 1],
                            model.T[i],
                            model.T[i - 1],
                        ])) for i in model.streams
                       if i != model.streams.first())
        csr_map.update((model.expansion[i],
                        ComponentSet([
                            model.rho[i],
                            model.rho[i - 1],
                            model.P[i],
                            model.P[i - 1],
                        ])) for i in model.streams
                       if i != model.streams.first())
        csr_map.update((model.ideal_gas[i],
                        ComponentSet([
                            model.P[i],
                            model.rho[i],
                            model.T[i],
                        ])) for i in model.streams)

        # Map constraint and variable indices to the values of the derivatives
        # Note that the derivative values calculated here depend on the model's
        # canonical form.
        deriv_lookup = {}
        m = model  # for convenience
        for s in model.streams:
            # Ideal gas:
            i = con_idx_map[model.ideal_gas[s]]
            j = var_idx_map[model.P[s]]
            deriv_lookup[i, j] = 1.0

            j = var_idx_map[model.rho[s]]
            deriv_lookup[i, j] = -model.R.value * model.T[s].value

            j = var_idx_map[model.T[s]]
            deriv_lookup[i, j] = -model.R.value * model.rho[s].value

            if s != model.streams.first():
                # Expansion:
                i = con_idx_map[model.expansion[s]]
                j = var_idx_map[model.P[s]]
                deriv_lookup[i, j] = 1 / model.P[s - 1].value

                j = var_idx_map[model.P[s - 1]]
                deriv_lookup[i, j] = -model.P[s].value / model.P[s - 1]**2

                j = var_idx_map[model.rho[s]]
                deriv_lookup[i, j] = pyo.value(
                    -m.gamma * (m.rho[s] / m.rho[s - 1])**(m.gamma - 1) /
                    m.rho[s - 1])

                j = var_idx_map[model.rho[s - 1]]
                deriv_lookup[i, j] = pyo.value(
                    -m.gamma * (m.rho[s] / m.rho[s - 1])**(m.gamma - 1) *
                    (-m.rho[s] / m.rho[s - 1]**2))

                # Energy balance:
                i = con_idx_map[m.ebal[s]]
                j = var_idx_map[m.rho[s - 1]]
                deriv_lookup[i, j] = pyo.value(m.F[s - 1] * m.T[s - 1])

                j = var_idx_map[m.F[s - 1]]
                deriv_lookup[i, j] = pyo.value(m.rho[s - 1] * m.T[s - 1])

                j = var_idx_map[m.T[s - 1]]
                deriv_lookup[i, j] = pyo.value(m.F[s - 1] * m.rho[s - 1])

                j = var_idx_map[m.rho[s]]
                deriv_lookup[i, j] = pyo.value(-m.F[s] * m.T[s])

                j = var_idx_map[m.F[s]]
                deriv_lookup[i, j] = pyo.value(-m.rho[s] * m.T[s])

                j = var_idx_map[m.T[s]]
                deriv_lookup[i, j] = pyo.value(-m.F[s] * m.rho[s])

                # Mass balance:
                i = con_idx_map[m.mbal[s]]
                j = var_idx_map[m.rho[s - 1]]
                deriv_lookup[i, j] = pyo.value(m.F[s - 1])

                j = var_idx_map[m.F[s - 1]]
                deriv_lookup[i, j] = pyo.value(m.rho[s - 1])

                j = var_idx_map[m.rho[s]]
                deriv_lookup[i, j] = pyo.value(-m.F[s])

                j = var_idx_map[m.F[s]]
                deriv_lookup[i, j] = pyo.value(-m.rho[s])

        # Want to test that the columns have the rows we expect.
        i = model.streams.first()
        for i, j, e in zip(imat.row, imat.col, imat.data):
            con = all_cons[i]
            var = all_vars[j]
            self.assertIn(var, csr_map[con])
            csr_map[con].remove(var)
            self.assertAlmostEqual(deriv_lookup[i, j], e, 8)
        # And no additional rows
        for con in csr_map:
            self.assertEqual(len(csr_map[con]), 0)
Exemple #9
0
def categorize_dae_variables_and_constraints(
    model,
    dae_vars,
    dae_cons,
    time,
    index=None,
    input_vars=None,
    disturbance_vars=None,
    input_cons=None,
    active_inequalities=None,
):
    # Index that we access when we need to work with a specific data
    # object. This would be less necessary if constructing CUIDs was
    # efficient, or if we could do the equivalent of `identify_variables`
    # in a templatized constraint.
    if index is not None:
        t1 = index
    else:
        # Use the first non-initial time point as a "representative
        # index." Don't use get_finite_elements so this will be valid
        # for general ordered sets.
        t1 = time.at(2)

    if input_vars is None:
        input_vars = []
    if input_cons is None:
        input_cons = []
    if disturbance_vars is None:
        disturbance_vars = []
    if active_inequalities is None:
        active_inequalities = []

    # We will check these sets to determine which components
    # are inputs and disturbances.
    #
    # NOTE: Specified input vars/cons and disturbance vars should be
    # in the form of components indexed only by time. The user can
    # accomplish this easily with the `Reference` function.
    #
    input_var_set = ComponentSet(inp[t1] for inp in input_vars)
    disturbance_var_set = ComponentSet(dist[t1] for dist in disturbance_vars)
    input_con_set = ComponentSet(inp[t1] for inp in input_cons)
    active_inequality_set = ComponentSet(con[t1]
                                         for con in active_inequalities)

    # Filter vars and cons for duplicates.
    #
    # Here we assume that if any two components refer to the same
    # data object at our "representative index" t1, they are
    # effectively "the same" components, and do not need to both
    # be included.
    #
    visited = set()
    filtered_vars = []
    duplicate_vars = []
    for var in dae_vars:
        _id = id(var[t1])
        if _id not in visited:
            visited.add(_id)
            filtered_vars.append(var)
        else:
            duplicate_vars.append(var)
    filtered_cons = []
    duplicate_cons = []
    for con in dae_cons:
        _id = id(con[t1])
        if _id not in visited:
            visited.add(_id)
            filtered_cons.append(con)
        else:
            duplicate_cons.append(con)
    dae_vars = filtered_vars
    dae_cons = filtered_cons

    # Filter out inputs and disturbances. These are "not variables"
    # for the sake of having a square DAE model.
    dae_vars = [
        var for var in dae_vars
        if var[t1] not in input_var_set and var[t1] not in disturbance_var_set
    ]
    dae_cons = [
        con for con in dae_cons if con[t1] not in input_con_set and (
            con[t1].equality or con[t1] in active_inequality_set)
    ]

    dae_map = ComponentMap()
    dae_map.update((var[t1], var) for var in dae_vars)
    dae_map.update((con[t1], con) for con in dae_cons)

    diff_eqn_map = ComponentMap()
    for con in dae_cons:
        condata = con[t1]
        is_diff, deriv = _identify_derivative_if_differential(condata, time)
        if is_diff:
            diff_eqn_map[deriv] = condata

    potential_deriv = []
    potential_diff_var = []
    potential_disc = []
    potential_diff_eqn = []
    for var in dae_vars:
        vardata = var[t1]
        if vardata in diff_eqn_map:
            # This check ensures that vardata is differential wrt time
            # and participates in exactly one non-discretization equation.
            # This equation is that derivative's "differential equation."
            diff_vardata = _get_state_vardata(vardata)
            if diff_vardata in dae_map:
                # May not be the case if diff_var is an input...
                potential_diff_var.append(dae_map[diff_vardata])
                potential_diff_eqn.append(dae_map[diff_eqn_map[vardata]])
                potential_deriv.append(var)
                potential_disc.append(dae_map[_get_disc_eq(vardata)])

    # PyNumero requires exactly one objective on the model.
    dummy_obj = False
    if len(list(model.component_objects(Objective, active=True))) == 0:
        dummy_obj = True
        model._temp_dummy_obj = Objective(expr=0)

    igraph = IncidenceGraphInterface()
    variables = [var[t1] for var in dae_vars]
    constraints = [con[t1] for con in dae_cons]

    present_cons = [con for con in constraints if con.active]
    active_var_set = ComponentSet(
        var for con in present_cons
        for var in identify_variables(con.expr, include_fixed=False))
    present_vars = [var for var in variables if var in active_var_set]

    # Filter out fixed vars and inactive constraints.
    # We could do this check earlier (before constructing igraph)
    # by just checking var.fixed and con.active...
    #_present_vars = [var for var in variables if not var.fixed]
    #_present_cons = [con for con in constraints if con.active]
    #present_vars = [var for var in variables if var in _nlp._vardata_to_idx]
    #present_cons = [con for con in constraints if con in _nlp._condata_to_idx]

    var_block_map, con_block_map = igraph.block_triangularize(
        present_vars,
        present_cons,
    )
    derivdatas = []
    diff_vardatas = []
    discdatas = []
    diff_condatas = []
    for deriv, disc, diff_var, diff_con in zip(potential_deriv, potential_disc,
                                               potential_diff_var,
                                               potential_diff_eqn):
        derivdata = deriv[t1]
        discdata = disc[t1]
        diff_vardata = diff_var[t1]
        diff_condata = diff_con[t1]
        # Check:
        if (
                # a. Variables are actually used (not fixed), and
                #    constraints are active
                derivdata in var_block_map and diff_vardata in var_block_map
                and discdata in con_block_map and diff_condata in con_block_map
                and
                # b. The diff var can be matched with the disc eqn and
                #    the deriv var can be matched with the diff eqn.
            (var_block_map[diff_vardata] == con_block_map[discdata])
                and (var_block_map[derivdata] == con_block_map[diff_condata])):
            # Under these conditions, assuming the Jacobian of the diff eqns
            # with respect to the derivatives is nonsingular, a sufficient
            # condition for nonsingularity (of the submodel with fixed inputs
            # at t1) is that the Jacobian of algebraic variables with respect
            # to algebraic equations is nonsingular.
            derivdatas.append(derivdata)
            diff_vardatas.append(diff_vardata)
            discdatas.append(discdata)
            diff_condatas.append(diff_condata)

    derivs = [dae_map[vardata] for vardata in derivdatas]
    diff_vars = [dae_map[vardata] for vardata in diff_vardatas]
    discs = [dae_map[condata] for condata in discdatas]
    diff_cons = [dae_map[condata] for condata in diff_condatas]

    not_alg_set = ComponentSet(derivdatas + diff_vardatas + discdatas +
                               diff_condatas)
    alg_vars = []
    unused_vars = []
    for vardata in variables:
        var = dae_map[vardata]
        if vardata not in var_block_map:
            unused_vars.append(var)
        elif vardata not in not_alg_set:
            alg_vars.append(var)
        # else var is differential, derivative, input, or disturbance
    alg_cons = []
    unused_cons = []
    for condata in constraints:
        con = dae_map[condata]
        if condata not in con_block_map:
            unused_cons.append(con)
        elif condata not in not_alg_set:
            alg_cons.append(con)
        # else con is differential or discretization (or a constraint
        # on inputs)

    if dummy_obj:
        model.del_component(model._temp_dummy_obj)

    var_category_dict = {
        VC.INPUT: input_vars,
        VC.DIFFERENTIAL: diff_vars,
        VC.DERIVATIVE: derivs,
        VC.ALGEBRAIC: alg_vars,
        VC.DISTURBANCE: disturbance_vars,
        VC.UNUSED: unused_vars,
    }
    con_category_dict = {
        CC.INPUT: input_cons,
        CC.DIFFERENTIAL: diff_cons,
        CC.DISCRETIZATION: discs,
        CC.ALGEBRAIC: alg_cons,
        CC.UNUSED: unused_cons,
    }
    return var_category_dict, con_category_dict
Exemple #10
0
 def test_str(self):
     cmap = ComponentMap()
     self.assertEqual(str(cmap), "ComponentMap({})")
     cmap.update(self._components)
     str(cmap)