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

"""
***********************************************************************************
                           tutorial_opencs_dae_1.py
                DAE Tools: pyOpenCS 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__ = """
Reimplementation of IDAS idasAkzoNob_dns example.
The chemical kinetics problem with 6 non-linear diff. equations::
    
    dy1_dt + 2*r1 - r2 + r3 + r4 = 0
    dy2_dt + 0.5*r1 + r4 + 0.5*r5 - Fin = 0
    dy3_dt - r1 + r2 - r3 = 0
    dy4_dt + r2 - r3 + 2*r4 = 0
    dy5_dt - r2 + r3 - r5 = 0
             Ks*y1*y4 - y6 = 0

where::
    
    r1  = k1 * pow(y1,4) * sqrt(y2)
    r2  = k2 * y3 * y4
    r3  = k2/K * y1 * y5
    r4  = k3 * y1 * y4 * y4
    r5  = k4 * y6 * y6 * sqrt(y2)
    Fin = klA * (pCO2/H - y2)

The system is stiff.
The original results are in tutorial_opencs_dae_1.csv file.
"""

import os, sys, json, numpy
from daetools.solvers.opencs import csModelBuilder_t, csNumber_t, csSimulate
from daetools.examples.tutorial_opencs_aux import compareResults

# ChemicalKinetics class provides information for the OpenCS model:
#  - Variable names
#  - Initial conditions
#  - Model equations
# The same class can be used for specification of both OpenCS and DAE Tools models.
k1   =  18.70
k2   =   0.58
k3   =   0.09
k4   =   0.42
K    =  34.40
klA  =   3.30
Ks   = 115.83
pCO2 =   0.90
H    = 737.00
class ChemicalKinetics:
    def __init__(self):
        self.Nequations = 6

    def GetInitialConditions(self):
        y0 = [0.0] * self.Nequations
        y0[0] = 0.444
        y0[1] = 0.00123
        y0[2] = 0.00
        y0[3] = 0.007
        y0[4] = 0.0
        y0[5] = Ks * y0[0] * y0[3]
        return y0

    def GetVariableNames(self):
        return ['y1', 'y2', 'y3', 'y4', 'y5', 'y6']

    def CreateEquations(self, y, dydt):
        # y is a list of csNumber_t objects representing model variables
        # dydt is a list of csNumber_t objects representing time derivatives of model variables
        y1,y2,y3,y4,y5,y6 = y
        dy1_dt,dy2_dt,dy3_dt,dy4_dt,dy5_dt,dy6_dt = dydt

        r1  = k1 * numpy.power(y1,4) * numpy.sqrt(y2)
        r2  = k2 * y3 * y4
        r3  = k2/K * y1 * y5
        r4  = k3 * y1 * y4 * y4
        r5  = k4 * y6 * y6 * numpy.sqrt(y2)
        Fin = klA * ( pCO2/H - y2 )

        equations = [None] * self.Nequations
        equations[0] = dy1_dt + 2*r1 - r2 + r3 + r4
        equations[1] = dy2_dt + 0.5*r1 + r4 + 0.5*r5 - Fin
        equations[2] = dy3_dt - r1 + r2 - r3
        equations[3] = dy4_dt + r2 - r3 + 2*r4
        equations[4] = dy5_dt - r2 + r3 - r5
        equations[5] = Ks*y1*y4 - y6

        return equations

def run(**kwargs):
    inputFilesDirectory = kwargs.get('inputFilesDirectory', os.path.splitext(os.path.basename(__file__))[0])
    
    # Instantiate the model being simulated.
    model = ChemicalKinetics()
    
    # 1. Initialise the DAE system with the number of variables and other inputs.
    mb = csModelBuilder_t()
    mb.Initialize_DAE_System(model.Nequations, 0, defaultAbsoluteTolerance = 1e-10)
    
    # 2. Specify the OpenCS model.
    # Create and set model equations using the provided time/variable/timeDerivative/dof objects.
    # The DAE system is defined as:
    #     F(x',x,y,t) = 0
    # where x' are derivatives of state variables, x are state variables,
    # y are fixed variables (degrees of freedom) and t is the current simulation time.
    time            = mb.Time             # Current simulation time (t)
    variables       = mb.Variables        # State variables (x)
    timeDerivatives = mb.TimeDerivatives  # Derivatives of state variables (x')
    dofs            = mb.DegreesOfFreedom # Fixed variables (y)
    mb.ModelEquations = model.CreateEquations(variables, timeDerivatives)    
    
    # Set initial conditions.
    mb.VariableValues = model.GetInitialConditions()
    
    # Set variable names.
    mb.VariableNames  = model.GetVariableNames()
    
    # 3. Generate a model for single CPU simulations.    
    # Set simulation options (specified as a string in JSON format).
    # The default options are returned by SimulationOptions function as a string in JSON format.
    # The TimeHorizon and the ReportingInterval must be set.
    options = mb.SimulationOptions
    options['Simulation']['OutputDirectory']             = 'results'
    options['Simulation']['TimeHorizon']                 = 180.0
    options['Simulation']['ReportingInterval']           =   1.0
    options['Solver']['Parameters']['RelativeTolerance'] =  1e-8
    # Data reporter options
    #options['Simulation']['DataReporter']['Name']                       = 'CSV'
    #options['Simulation']['DataReporter']['Parameters']['precision']    = 14
    #options['Simulation']['DataReporter']['Parameters']['delimiter']    = ';'
    #options['Simulation']['DataReporter']['Parameters']['outputFormat'] = 'scientific'
    #options['Simulation']['DataReporter']['Name']                              = 'HDF5'
    #options['Simulation']['DataReporter']['Parameters']['fileNameResults']     = 'results.hdf5'
    #options['Simulation']['DataReporter']['Parameters']['fileNameDerivatives'] = 'derivatives.hdf5'
    mb.SimulationOptions = options
    
    # Partition the system to create the OpenCS model for a single CPU simulation.
    # In this case (Npe = 1) the graph partitioner is not required.
    Npe = 1
    graphPartitioner = None
    cs_models = mb.PartitionSystem(Npe, graphPartitioner)
    csModelBuilder_t.ExportModels(cs_models, inputFilesDirectory, mb.SimulationOptions)
    print("OpenCS model generated successfully!")

    # 4. Run simulation using the exported model from the specified directory.
    csSimulate(inputFilesDirectory)
    
    # Compare OpenCS and the original model results.
    compareResults(inputFilesDirectory, ['y1', 'y2', 'y3', 'y4', 'y5', 'y6'])
           
if __name__ == "__main__":
    inputFilesDirectory = 'tutorial_opencs_dae_1'
    run(inputFilesDirectory = inputFilesDirectory)