Beispiel #1
0
    def test_clear_dir_k_aug(self):
        m = simple_model_1()
        sens = SensitivityInterface(m, clone_model=False)
        k_aug = K_augInterface()

        opt_ipopt.solve(m, tee=True)
        m.ptb = pyo.Param(mutable=True, initialize=1.5)

        cwd = os.getcwd()
        dir_contents = os.listdir(cwd)

        sens_param = [m.p]
        sens.setup_sensitivity(sens_param)
        
        k_aug.k_aug(m, tee=True)

        # We are back in our working directory
        self.assertEqual(cwd, os.getcwd())

        # The contents of this directory have not changed
        self.assertEqual(dir_contents, os.listdir(cwd))

        # In particular, the following files do not exist
        self.assertFalse(os.path.exists("dsdp_in_.in"))
        self.assertFalse(os.path.exists("conorder.txt"))
        self.assertFalse(os.path.exists("timings_k_aug_dsdp.txt"))

        # But they have been transferred to our k_aug interface's data
        # dict as strings.
        self.assertIsInstance(k_aug.data["dsdp_in_.in"], str)
        self.assertIsInstance(k_aug.data["conorder.txt"], str)
        self.assertIsInstance(k_aug.data["timings_k_aug_dsdp.txt"], str)
Beispiel #2
0
    def test_clear_dir_dot_sens(self):
        m = simple_model_1()
        sens = SensitivityInterface(m, clone_model=False)
        k_aug = K_augInterface()
        opt_ipopt.solve(m, tee=True)
        m.ptb = pyo.Param(mutable=True, initialize=1.5)

        cwd = os.getcwd()
        dir_contents = os.listdir(cwd)

        sens_param = [m.p]
        sens.setup_sensitivity(sens_param)
        
        # Call k_aug
        k_aug.k_aug(m, tee=True)
        self.assertIsInstance(k_aug.data["dsdp_in_.in"], str)

        sens.perturb_parameters([m.ptb])

        # Call dot_sens. In the process, we re-write dsdp_in_.in
        k_aug.dot_sens(m, tee=True)

        # Make sure we get the values we expect. This problem is easy enough
        # to solve by hand:
        # x = [1, 1, -2] = [v1, v2, dual]
        # Sensitivity system:
        # | 2 -2  1 |
        # |-2  2  1 | dx/dp = -[dL/dxdp, dc/dp]^T = -[0, 0, -1]^T
        # | 1  1  0 |
        # => dx/dp = [0.5, 0.5, 0]^T
        # Here, dp = [0.5]
        # => dx = [0.25, 0.25, 0]^T
        # => x_new = [1.25, 1.25, -2]
        self.assertAlmostEqual(m.v1.value, 1.25, 7)
        self.assertAlmostEqual(m.v2.value, 1.25, 7)

        # We are back in our working directory
        self.assertEqual(cwd, os.getcwd())

        # The contents of this directory have not changed
        self.assertEqual(dir_contents, os.listdir(cwd))
        self.assertFalse(os.path.exists("dsdp_in_.in"))
        self.assertFalse(os.path.exists("delta_p.out"))
        self.assertFalse(os.path.exists("dot_out.out"))
        self.assertFalse(os.path.exists("timings_dot_driver_dsdp.txt"))

        # And we have saved strings of the file contents.
        self.assertIsInstance(k_aug.data["dsdp_in_.in"], str)
        self.assertIsInstance(k_aug.data["delta_p.out"], str)
        self.assertIsInstance(k_aug.data["dot_out.out"], str)
        self.assertIsInstance(k_aug.data["timings_dot_driver_dsdp.txt"], str)
Beispiel #3
0
def get_dsdp(model, theta_names, theta, tee=False):
    """This function calculates gradient vector of the variables
        with respect to the parameters (theta_names).

    e.g) min f:  p1*x1+ p2*(x2^2) + p1*p2
         s.t  c1: x1 + x2 = p1
              c2: x2 + x3 = p2
              0 <= x1, x2, x3 <= 10
              p1 = 10
              p2 = 5
    the function retuns dx/dp and dp/dp, and column orders.

    The following terms are used to define the output dimensions:
    Ncon   = number of constraints
    Nvar   = number of variables (Nx + Ntheta)
    Nx     = number of decision (primal) variables
    Ntheta = number of uncertain parameters.

    Parameters
    ----------
    model: Pyomo ConcreteModel
        model should include an objective function
    theta_names: list of strings
        List of Var names
    theta: dict
        Estimated parameters e.g) from parmest
    tee: bool, optional
        Indicates that ef solver output should be teed

    Returns
    -------
    dsdp: scipy.sparse.csr.csr_matrix
        Ntheta by Nvar size sparse matrix. A Jacobian matrix of the
        (decision variables, parameters) with respect to parameters
        (theta_names). number of rows = len(theta_name), number of
        columns = len(col)
    col: list
        List of variable names
    """
    # Get parameters from names. In SensitivityInterface, we expect
    # these to be parameters on the original model.
    param_list = []
    for name in theta_names:
        comp = model.find_component(name)
        if comp is None:
            raise RuntimeError("Cannot find component %s on model" % name)
        if comp.ctype is Var:
            # If theta_names correspond to Vars in the model, these vars
            # need to be fixed.
            comp.fix()
        param_list.append(comp)

    sens = SensitivityInterface(model, clone_model=True)
    m = sens.model_instance

    # Setup model and calculate sensitivity matrix with k_aug
    sens.setup_sensitivity(param_list)
    k_aug = K_augInterface()
    k_aug.k_aug(m, tee=tee)

    # Write row and col files in a temp dir, then immediately
    # read into a Python data structure.
    nl_data = {}
    with InTempDir():
        base_fname = "col_row"
        nl_file = ".".join((base_fname, "nl"))
        row_file = ".".join((base_fname, "row"))
        col_file = ".".join((base_fname, "col"))
        m.write(nl_file, io_options={"symbolic_solver_labels": True})
        for fname in [nl_file, row_file, col_file]:
            with open(fname, "r") as fp:
                nl_data[fname] = fp.read()

    # Create more useful data structures from strings
    dsdp = np.fromstring(k_aug.data["dsdp_in_.in"], sep="\n\t")
    col = nl_data[col_file].strip("\n").split("\n")
    row = nl_data[row_file].strip("\n").split("\n")

    dsdp = dsdp.reshape((len(theta_names), int(len(dsdp) / len(theta_names))))
    dsdp = dsdp[:len(theta_names), :len(col)]

    col = [i for i in col if sens.get_default_block_name() not in i]
    dsdp_out = np.zeros((len(theta_names), len(col)))
    for i in range(len(theta_names)):
        for j in range(len(col)):
            if sens.get_default_block_name() not in col[j]:
                dsdp_out[
                    i,
                    j] = -dsdp[i, j]  # e.g) k_aug dsdp returns -dx1/dx1 = -1.0

    return scipy.sparse.csr_matrix(dsdp_out), col
Beispiel #4
0
def get_dfds_dcds(model, theta_names, tee=False, solver_options=None):
    """This function calculates gradient vector of the objective function 
       and constraints with respect to the variables and parameters.

    e.g) min f:  p1*x1+ p2*(x2^2) + p1*p2
         s.t  c1: x1 + x2 = p1
              c2: x2 + x3 = p2
              0 <= x1, x2, x3 <= 10
              p1 = 10
              p2 = 5
    - Variables = (x1, x2, x3, p1, p2)
    - Fix p1 and p2 with estimated values

    The following terms are used to define the output dimensions:
    Ncon   = number of constraints
    Nvar   = number of variables (Nx + Ntheta)
    Nx     = number of decision (primal) variables
    Ntheta = number of uncertain parameters.

    Parameters
    ----------
    model: Pyomo ConcreteModel
        model should include an objective function
    theta_names: list of strings
        List of Var names
    tee: bool, optional
        Indicates that ef solver output should be teed
    solver_options: dict, optional
        Provides options to the solver (also the name of an attribute)

    Returns
    -------
    gradient_f: numpy.ndarray
        Length Nvar array. A gradient vector of the objective function
        with respect to the (decision variables, parameters) at the optimal
        solution
    gradient_c: scipy.sparse.csr.csr_matrix
        Ncon by Nvar size sparse matrix. A Jacobian matrix of the
        constraints with respect to the (decision variables, parameters)
        at the optimal solution. Each row contains [column number,
        row number, and value], column order follows variable order in col
        and index starts from 1. Note that it follows k_aug.
        If no constraint exists, return []
    col: list
        Size Nvar list of variable names
    row: list
        Size Ncon+1 list of constraints and objective function names.
        The final element is the objective function name.
    line_dic: dict
        column numbers of the theta_names in the model. Index starts from 1

    Raises
    ------
    RuntimeError
        When ipopt or k_aug or dotsens is not available
    Exception
        When ipopt fails 
    """
    # Create the solver plugin using the ASL interface
    ipopt = SolverFactory('ipopt', solver_io='nl')
    if solver_options is not None:
        ipopt.options = solver_options
    k_aug = SolverFactory('k_aug', solver_io='nl')
    if not ipopt.available(False):
        raise RuntimeError('ipopt is not available')
    if not k_aug.available(False):
        raise RuntimeError('k_aug is not available')

    # Declare Suffixes
    _add_sensitivity_suffixes(model)

    # K_AUG SUFFIXES
    model.dof_v = Suffix(direction=Suffix.EXPORT)  #: SUFFIX FOR K_AUG
    model.rh_name = Suffix(
        direction=Suffix.IMPORT)  #: SUFFIX FOR K_AUG AS WELL
    k_aug.options["print_kkt"] = ""

    results = ipopt.solve(model, tee=tee)

    # Raise exception if ipopt fails
    if (results.solver.status == SolverStatus.warning):
        raise Exception(results.solver.Message)

    for o in model.component_objects(Objective, active=True):
        f_mean = value(o)
    model.ipopt_zL_in.update(model.ipopt_zL_out)
    model.ipopt_zU_in.update(model.ipopt_zU_out)

    # run k_aug
    k_aug_interface = K_augInterface(k_aug=k_aug)
    k_aug_interface.k_aug(model, tee=tee)  #: always call k_aug AFTER ipopt.

    nl_data = {}
    with InTempDir():
        base_fname = "col_row"
        nl_file = ".".join((base_fname, "nl"))
        row_file = ".".join((base_fname, "row"))
        col_file = ".".join((base_fname, "col"))
        model.write(nl_file, io_options={"symbolic_solver_labels": True})
        for fname in [nl_file, row_file, col_file]:
            with open(fname, "r") as fp:
                nl_data[fname] = fp.read()

    col = nl_data[col_file].strip("\n").split("\n")
    row = nl_data[row_file].strip("\n").split("\n")

    # get the column numbers of "parameters"
    line_dic = {name: col.index(name) for name in theta_names}

    grad_f_file = os.path.join("GJH", "gradient_f_print.txt")
    grad_f_string = k_aug_interface.data[grad_f_file]
    gradient_f = np.fromstring(grad_f_string, sep="\n\t")
    col = [
        i for i in col
        if SensitivityInterface.get_default_block_name() not in i
    ]

    grad_c_file = os.path.join("GJH", "A_print.txt")
    grad_c_string = k_aug_interface.data[grad_c_file]
    gradient_c = np.fromstring(grad_c_string, sep="\n\t")

    # Jacobian file is in "COO format," i.e. an nnz-by-3 array.
    # Reshape to a numpy array that matches this format.
    gradient_c = gradient_c.reshape((-1, 3))

    num_constraints = len(row) - 1  # Objective is included as a row
    if num_constraints > 0:
        row_idx = gradient_c[:, 1] - 1
        col_idx = gradient_c[:, 0] - 1
        data = gradient_c[:, 2]
        gradient_c = scipy.sparse.csr_matrix((data, (row_idx, col_idx)),
                                             shape=(num_constraints, len(col)))
    else:
        gradient_c = np.array([])

    return gradient_f, gradient_c, col, row, line_dic
Beispiel #5
0
def sensitivity_calculation(method,
                            instance,
                            paramList,
                            perturbList,
                            cloneModel=True,
                            tee=False,
                            keepfiles=False,
                            solver_options=None):
    """This function accepts a Pyomo ConcreteModel, a list of parameters, and
    their corresponding perturbation list. The model is then augmented with
    dummy constraints required to call sipopt or k_aug to get an approximation
    of the perturbed solution.
    
    Parameters
    ----------
    method: string
        'sipopt' or 'k_aug'
    instance: Block
        pyomo block or model object
    paramSubList: list
        list of mutable parameters or fixed variables
    perturbList: list
        list of perturbed parameter values
    cloneModel: bool, optional
        indicator to clone the model. If set to False, the original
        model will be altered
    tee: bool, optional
        indicator to stream solver log
    keepfiles: bool, optional
        preserve solver interface files
    solver_options: dict, optional
        Provides options to the solver (also the name of an attribute)
    
    Returns
    -------
    The model that was manipulated by the sensitivity interface

    """

    sens = SensitivityInterface(instance, clone_model=cloneModel)
    sens.setup_sensitivity(paramList)

    m = sens.model_instance

    if method not in {"k_aug", "sipopt"}:
        raise ValueError("Only methods 'k_aug' and 'sipopt' are supported'")

    if method == 'k_aug':
        k_aug = SolverFactory('k_aug', solver_io='nl')
        dot_sens = SolverFactory('dot_sens', solver_io='nl')
        ipopt = SolverFactory('ipopt', solver_io='nl')

        k_aug_interface = K_augInterface(k_aug=k_aug, dot_sens=dot_sens)

        ipopt.solve(m, tee=tee)
        m.ipopt_zL_in.update(m.ipopt_zL_out)  #: important!
        m.ipopt_zU_in.update(m.ipopt_zU_out)  #: important!

        k_aug.options['dsdp_mode'] = ""  #: sensitivity mode!
        k_aug_interface.k_aug(m, tee=tee)

    sens.perturb_parameters(perturbList)

    if method == 'sipopt':
        ipopt_sens = SolverFactory('ipopt_sens', solver_io='nl')
        ipopt_sens.options['run_sens'] = 'yes'

        # Send the model to ipopt_sens and collect the solution
        results = ipopt_sens.solve(m, keepfiles=keepfiles, tee=tee)

    elif method == 'k_aug':
        dot_sens.options["dsdp_mode"] = ""
        k_aug_interface.dot_sens(m, tee=tee)

    return m