# %load script.py
from pyomo.environ import *
from pyomo.opt import SolverFactory, TerminationCondition

def create_model():
    model = ConcreteModel()
    model.x = Var()
    model.o = Objective(expr=model.x)
    model.c = Constraint(expr=model.x >= 1)
    model.x.set_value(1.0)
    return model

if __name__ == "__main__":

    with SolverFactory("ipopt") as opt:
        model = create_model()
        results = opt.solve(model, load_solutions=False)
        if results.solver.termination_condition != TerminationCondition.optimal:
            raise RuntimeError('Solver did not report optimality:\n%s'
                               % (results.solver))
        model.solutions.load_from(results)
        print("Objective: %s" % (model.o()))


"""
Explanation: Loading ASL Results into a Model
Summary
In this scripting example we break apart the work flow that occurs when a Pyomo model is solved using the ASL solver plugin. The ASL solver plugin is a generic interface designed for solvers that utilize the AMPL Solver Library. This library takes model input in the form of an NL file and provides a solver solution in the form of an SOL file. As such, it provides a single unifying framework for interacting with a wide array of optimization solvers.
Pyomo includes separate tools for writing NL files and reading SOL files. In this example, we will show how to use these tools directly, as an alternative to calling the ASL solver plugin. In particular, we show how to save information about the symbol map created by the NL writer to a file so that it can be recovered at a later time. The symbol map that is recovered can be used to load a solution from the SOL file reader into any Pyomo model with component names that match those on the model used by the NL writer.
Solving With ASL
Consider the case below where we solve a simple Pyomo model using Ipopt through the ASL solver plugin and then verify that the solver termination condition is optimal before loading the solution into the model. Note that this example assumes Pyomo version 4.1 or later is installed. Since Pyomo 4.1, the load_solutions keyword must be assigned a value of False when calling the solve method on a solver plugin in order to prevent the solution from being automatically loaded into the model. This allows us to check the solver termination condition before manually loading the solution via the call to model.solutions.load_from.
End of explanation
"""


# %load write.py
import pyomo.environ
from pyomo.core import ComponentUID
from pyomo.opt import ProblemFormat
# use fast version of pickle (python 2 or 3)
from six.moves import cPickle as pickle

def write_nl(model, nl_filename, **kwds):
    """
    Writes a Pyomo model in NL file format and stores
    information about the symbol map that allows it to be
    recovered at a later time for a Pyomo model with
    matching component names.
    """
    symbol_map_filename = nl_filename+".symbol_map.pickle"

    # write the model and obtain the symbol_map
    _, smap_id = model.write(nl_filename,
                             format=ProblemFormat.nl,
                             io_options=kwds)
    symbol_map = model.solutions.symbol_map[smap_id]

    # save a persistent form of the symbol_map (using pickle) by
    # storing the NL file label with a ComponentUID, which is
    # an efficient lookup code for model components (created
    # by John Siirola)
    tmp_buffer = {} # this makes the process faster
    symbol_cuid_pairs = tuple(
        (symbol, ComponentUID(var_weakref(), cuid_buffer=tmp_buffer))
        for symbol, var_weakref in symbol_map.bySymbol.items())
    with open(symbol_map_filename, "wb") as f:
        pickle.dump(symbol_cuid_pairs, f)

    return symbol_map_filename

if __name__ == "__main__":
    from script import create_model

    model = create_model()
    nl_filename = "example.nl"
    symbol_map_filename = write_nl(model, nl_filename)
    print("        NL File: %s" % (nl_filename))
    print("Symbol Map File: %s" % (symbol_map_filename))


"""
Explanation: The basic work flow that takes place above can be summarized as:
 1. Create an ASL solver plugin that uses the ipopt executable appearing in the shell search PATH.
 2. Construct a Pyomo model.
 3. Solve the Pyomo model.
   1. Output the Pyomo model as an NL file.
   2. Invoke the solver (which produces an SOL file).
   3. Read the SOL file into a Pyomo results object.
 4. Check the solver termination condition stored in the results object.
 5. Load the solution stored in the results object into the Pyomo model.
The remainder of this example shows how to implement step 3 without the use of the ASL solver plugin.
A note about using the with statement
In the code provided with this example we make use of Python's with statement when dealing with objects returned from Pyomo Factory functions such as SolverFactory and ReaderFactory. Pyomo makes use of a Plugin system to instantiate these objects. As a result, they must be deactivated before going out of scope in order to prevent a memory leak. Deactivation of Plugins is managed automatically by the with statement, but can also be done by calling the deactivate method directly on the Plugin object.
Writing the NL File
The code block below defines the function write_nl that outputs a Pyomo model as an NL file and saves the pertinent symbol map data to a file using pickle. This symbol map data will allow a solution stored in an SOL file to be loaded into any Pyomo model with matching component names. The last section of this code block shows how this function can be used with a small example model.
End of explanation
"""


%%bash
ipopt -s example.nl

"""
Explanation: The first argument to this function is the Pyomo model. The second argument is the name to use for the NL file. Along with the NL file, another file with the suffix ".symbol_map.pickle" will be created that contains information that can be used to efficiently rebuild the symbol map for any Pyomo model with component names matching those used to build the NL file. Additional options can be passed to the NL writer as keywords to this function. These include:
* show_section_timing: Print timing after writing major sections of the NL file. (default=False)           
 skip_trivial_constraints: Skip writing constraints whose body section is fixed (i.e., no variables). (default=False)
* file_determinism: Sets the level of effort placed on ensuring the NL file is written deterministically. The value of this keyword will affect the row and column ordering assigned to Pyomo constraints and variables in the NLP matrix, respectively.
  *  0: declaration order only 
  *  1: sort index sets of indexed components after declaration order (default)
  *  2: sort component names (overriding declaration order) as well as index sets
* symbolic_solver_labels: Generate .row and .col files identifying constraint and variable indices in the NLP matrix. (default=False)
* include_all_variable_bounds: Include all variables that are on active blocks of the Pyomo model in the bounds section of the NL file. This includes variables that do not appear in any objective or constraint expressions. (default=False)
* output_fixed_variable_bounds*: Allow variables that are fixed to appear in the body of preprocessed expressions. Fixing takes place by using a variable's current value as the upper and lower bound in the bounds section of the NL file. This option is experimental. (default=False)
The symbolic_solver_labels option, when set to True, outputs files containing similar information to what is output by this function to recover the symbol map. The difference is that this function outputs component lookup codes (the ComponentUID class) that are meant to allow efficient recovery of components on models that make use of index Sets and/or Blocks. The .row and .col files are meant as debugging tools and use human readable names that are not efficient for recovering model components.
Invoking the Solver
The solver can be invoked directly from the command shell or by using Python's built-in utilities for executing shell commands. For most ASL-based solvers, we need to use an additional command-line option such as "-s" (before the input file) or "-AMPL" (after the input file) in order to tell the AMPL Solver Library we want it to store the solution into an SOL file. The code block below issues a bash command that uses the ipopt executable to solve our example model and generate an SOL file. This command requires that the ipopt executable can be found in the shell search PATH and that the code block from the previous section has been executed.
End of explanation
"""


# %load read.py
import pyomo.environ
from pyomo.core import SymbolMap
from pyomo.opt import (ReaderFactory,
                       ResultsFormat)
# use fast version of pickle (python 2 or 3)
from six.moves import cPickle as pickle

def read_sol(model, sol_filename, symbol_map_filename, suffixes=[".*"]):
    """
    Reads the solution from the SOL file and generates a
    results object with an appropriate symbol map for
    loading it into the given Pyomo model. By default all
    suffixes found in the NL file will be extracted. This
    can be overridden using the suffixes keyword, which
    should be a list of suffix names or regular expressions
    (or None).
    """
    if suffixes is None:
        suffixes = []

    # parse the SOL file
    with ReaderFactory(ResultsFormat.sol) as reader:
        results = reader(sol_filename, suffixes=suffixes)

    # regenerate the symbol_map for this model
    with open(symbol_map_filename, "rb") as f:
        symbol_cuid_pairs = pickle.load(f)
    symbol_map = SymbolMap()
    symbol_map.addSymbols((cuid.find_component(model), symbol)
                          for symbol, cuid in symbol_cuid_pairs)

    # tag the results object with the symbol_map
    results._smap = symbol_map

    return results

if __name__ == "__main__":
    from pyomo.opt import TerminationCondition
    from script import create_model

    model = create_model()
    sol_filename = "example.sol"
    symbol_map_filename = "example.nl.symbol_map.pickle"
    results = read_sol(model, sol_filename, symbol_map_filename)
    if results.solver.termination_condition != \
       TerminationCondition.optimal:
        raise RuntimeError("Solver did not terminate with status = optimal")
    model.solutions.load_from(results)
    print("Objective: %s" % (model.o()))


"""
Explanation: Loading the SOL File
The code block below defines the function read_sol that produces a results object that can be loaded into a Pyomo model from a given SOL file. The function first calls Pyomo's built-in SOL file reader to produce a bare results object. Symbols used by the SOL file reader take the form < type character >< index >, where < type character > is one of 'o' (objective), 'v' (variable), or 'c' (constraint) and < index > is the row / column index for constraints / variables in the NLP matrix and 0 for the objective (e.g., 'o0', 'v3', 'c1'). These symbols are mapped to component identifiers in the symbol map file created by the write_nl function from above. The results object returned from the read_sol function can be loaded into a Pyomo model just like that returned from the solve method on a Pyomo solver plugin when the load_solutions keyword is set to False. The last section of this code block shows how this function can be used to load a solution into a copy of the model used in the section on writing the NL file. It assumes the code blocks in the previous two sections have been executed.
End of explanation
"""


