#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
***********************************************************************************
                            opt_tutorial2.py
                DAE Tools: pyDAE module, www.daetools.com
                Copyright (C) Dragan Nikolic
***********************************************************************************
DAE Tools is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License version 3 as published by the Free Software
Foundation. DAE Tools is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with the
DAE Tools software; if not, see <http://www.gnu.org/licenses/>.
************************************************************************************
"""
__doc__ = """
This tutorial introduces Bonmin MINLP solver, its setup and options.
"""

import sys
from time import localtime, strftime
from daetools.pyDAE import *
from daetools.solvers.bonmin import pyBONMIN

class modTutorial(daeModel):
    def __init__(self, Name, Parent = None, Description = ""):
        daeModel.__init__(self, Name, Parent, Description)

        self.x  = daeVariable("x",  no_t, self)
        self.y1 = daeVariable("y1", no_t, self)
        self.y2 = daeVariable("y2", no_t, self)
        self.z  = daeVariable("z",  no_t, self)

        self.dummy = daeVariable("dummy", no_t, self, "A dummy variable to satisfy the condition that there should be at least one-state variable and one equation in a model")

    def DeclareEquations(self):
        daeModel.DeclareEquations(self)

        eq = self.CreateEquation("Dummy")
        eq.Residual = self.dummy()

class simTutorial(daeSimulation):
    def __init__(self):
        daeSimulation.__init__(self)
        self.m = modTutorial("opt_tutorial2")
        self.m.Description = __doc__

    def SetUpParametersAndDomains(self):
        pass

    def SetUpVariables(self):
        self.m.x.AssignValue(0)
        self.m.y1.AssignValue(0)
        self.m.y2.AssignValue(0)
        self.m.z.AssignValue(0)

    def SetUpOptimization(self):
        # Set the objective function (min)
        self.ObjectiveFunction.Residual = -self.m.x() - self.m.y1() - self.m.y2()

        # Set the constraints (inequality, equality)
        # Constraints are in the following form:
        #  - Inequality: g(i) <= 0
        #  - Equality: h(i) = 0
        c1 = self.CreateInequalityConstraint("Constraint 1")
        c1.Residual = (self.m.y1() - 0.5) ** 2 + (self.m.y2() - 0.5) ** 2 - 0.25
        # Or by using daetools Pow() function:
        #c1.Residual = Pow(self.m.y1() - 0.5, 2) + Pow(self.m.y2() - 0.5, 2) - 0.25

        c2 = self.CreateInequalityConstraint("Constraint 2")
        c2.Residual = self.m.x() - self.m.y1()

        c3 = self.CreateInequalityConstraint("Constraint 3")
        c3.Residual = self.m.x() + self.m.y2() + self.m.z() - 2

        # Set the optimization variables, their lower/upper bounds and the starting point
        self.SetBinaryOptimizationVariable(self.m.x, 0)
        self.SetContinuousOptimizationVariable(self.m.y1, 0, 2e19, 0)
        self.SetContinuousOptimizationVariable(self.m.y2, 0, 2e19, 0)
        self.SetIntegerOptimizationVariable(self.m.z, 0, 5, 0)

def setOptions(nlpsolver):
    # 1) Set the options manually
    nlpsolver.SetOption('bonmin.algorithm', 'B-Hyb')

    # 2) Load the options file (if file name is empty load the default: daetools/bonmin.cfg)
    #nlpsolver.LoadOptionsFile("")

    # Print options loaded at pyBonmin startup and the user set options:
    nlpsolver.PrintOptions()
    #nlpsolver.PrintUserOptions()

    # ClearOptions can clear all options:
    #nlpsolver.ClearOptions()

def run(**kwargs):
    simulation = simTutorial()
    # Achtung! Achtung! NLP solver options can only be set after optimization.Initialize()
    # Otherwise seg. fault occurs for some reasons.
    nlpsolver  = pyBONMIN.daeBONMIN()
    return daeActivity.optimize(simulation, reportingInterval       = 1, 
                                            timeHorizon             = 1,
                                            nlpsolver               = nlpsolver,
                                            nlpsolver_setoptions_fn = setOptions,
                                            reportSensitivities     = True,
                                            **kwargs)

if __name__ == "__main__":
    guiRun = False if (len(sys.argv) > 1 and sys.argv[1] == 'console') else True
    run(guiRun = guiRun)