def setUp(self): """ This sets up the test case. """ def f(t,y): eps = 1.e-6 my = 1./eps yd_0 = y[1] yd_1 = my*((1.-y[0]**2)*y[1]-y[0]) return N.array([yd_0,yd_1]) def jac(t,y): eps = 1.e-6 my = 1./eps J = N.zeros([2,2]) J[0,0]=0. J[0,1]=1. J[1,0]=my*(-2.*y[0]*y[1]-1.) J[1,1]=my*(1.-y[0]**2) return J def jac_sparse(t,y): eps = 1.e-6 my = 1./eps J = N.zeros([2,2]) J[0,0]=0. J[0,1]=1. J[1,0]=my*(-2.*y[0]*y[1]-1.) J[1,1]=my*(1.-y[0]**2) return sp.csc_matrix(J) #Define an Assimulo problem y0 = [2.0,-0.6] #Initial conditions exp_mod = Explicit_Problem(f,y0) exp_mod_t0 = Explicit_Problem(f,y0,1.0) exp_mod_sp = Explicit_Problem(f,y0) exp_mod.jac = jac exp_mod_sp.jac = jac_sparse self.mod = exp_mod #Define an explicit solver self.sim = Radau5ODE(exp_mod) #Create a Radau5 solve self.sim_t0 = Radau5ODE(exp_mod_t0) self.sim_sp = Radau5ODE(exp_mod_sp) #Sets the parameters self.sim.atol = 1e-4 #Default 1e-6 self.sim.rtol = 1e-4 #Default 1e-6 self.sim.inith = 1.e-4 #Initial step-size self.sim.usejac = False
def test_usejac_csc_matrix(self): """ This tests the functionality of the property usejac. """ f = lambda t, x: N.array([x[1], -9.82]) #Defines the rhs jac = lambda t, x: sp.csc_matrix(N.array([[0., 1.], [0., 0.]]) ) #Defines the jacobian exp_mod = Explicit_Problem(f, [1.0, 0.0]) exp_mod.jac = jac exp_sim = CVode(exp_mod) exp_sim.discr = 'BDF' exp_sim.iter = 'Newton' exp_sim.simulate(5., 100) assert exp_sim.statistics["nfcnjacs"] == 0 nose.tools.assert_almost_equal(exp_sim.y_sol[-1][0], -121.75000143, 4) exp_sim.reset() exp_sim.usejac = False exp_sim.simulate(5., 100) nose.tools.assert_almost_equal(exp_sim.y_sol[-1][0], -121.75000143, 4) assert exp_sim.statistics["nfcnjacs"] > 0
def setUp(self): #Define the rhs def f(t, y): eps = 1.e-6 my = 1. / eps yd_0 = y[1] yd_1 = my * ((1. - y[0]**2) * y[1] - y[0]) return N.array([yd_0, yd_1]) #Define the jacobian def jac(t, y): eps = 1.e-6 my = 1. / eps j = N.array( [[0.0, 1.0], [my * ((-2.0 * y[0]) * y[1] - 1.0), my * (1.0 - y[0]**2)]]) return j def jac_sparse(t, y): eps = 1.e-6 my = 1. / eps J = N.zeros([2, 2]) J[0, 0] = 0. J[0, 1] = 1. J[1, 0] = my * (-2. * y[0] * y[1] - 1.) J[1, 1] = my * (1. - y[0]**2) return sp.csc_matrix(J) y0 = [2.0, -0.6] #Initial conditions #Define an Assimulo problem exp_mod = Explicit_Problem(f, y0, name='Van der Pol (explicit)') exp_mod_sp = Explicit_Problem(f, y0, name='Van der Pol (explicit)') exp_mod.jac = jac exp_mod_sp.jac = jac_sparse self.mod = exp_mod self.mod_sp = exp_mod_sp
def run_example(with_plots=True): global t,y #Defines the rhs def f(t,y): yd_0 = y[1] yd_1 = -9.82 #print y, yd_0, yd_1 return N.array([yd_0,yd_1]) #Defines the jacobian def jac(t,y): j = N.array([[0,1.],[0,0]]) return j #Defines an Assimulo explicit problem y0 = [1.0,0.0] #Initial conditions exp_mod = Explicit_Problem(f,y0) exp_mod.jac = jac #Sets the jacobian exp_mod.name = 'Example using Jacobian' exp_sim = CVode(exp_mod) #Create a CVode solver #Set the parameters exp_sim.iter = 'Newton' #Default 'FixedPoint' exp_sim.discr = 'BDF' #Default 'Adams' exp_sim.atol = 1e-5 #Default 1e-6 exp_sim.rtol = 1e-5 #Default 1e-6 #Simulate t, y = exp_sim.simulate(5, 1000) #Simulate 5 seconds with 1000 communication points #Basic tests nose.tools.assert_almost_equal(y[-1][0],-121.75000000,4) nose.tools.assert_almost_equal(y[-1][1],-49.100000000) #Plot if with_plots: P.plot(t,y,linestyle="dashed",marker="o") #Plot the solution P.show()
def run_example(with_plots=True): global t, y #Defines the rhs def f(t, y): yd_0 = y[1] yd_1 = -9.82 #print y, yd_0, yd_1 return N.array([yd_0, yd_1]) #Defines the jacobian def jac(t, y): j = N.array([[0, 1.], [0, 0]]) return j #Defines an Assimulo explicit problem y0 = [1.0, 0.0] #Initial conditions exp_mod = Explicit_Problem(f, y0) exp_mod.jac = jac #Sets the jacobian exp_mod.name = 'Example using Jacobian' exp_sim = CVode(exp_mod) #Create a CVode solver #Set the parameters exp_sim.iter = 'Newton' #Default 'FixedPoint' exp_sim.discr = 'BDF' #Default 'Adams' exp_sim.atol = 1e-5 #Default 1e-6 exp_sim.rtol = 1e-5 #Default 1e-6 #Simulate t, y = exp_sim.simulate( 5, 1000) #Simulate 5 seconds with 1000 communication points #Basic tests nose.tools.assert_almost_equal(y[-1][0], -121.75000000, 4) nose.tools.assert_almost_equal(y[-1][1], -49.100000000) #Plot if with_plots: P.plot(t, y, linestyle="dashed", marker="o") #Plot the solution P.show()
def setUp(self): """ This sets up the test case. """ def f(t,y): eps = 1.e-6 my = 1./eps yd_0 = y[1] yd_1 = my*((1.-y[0]**2)*y[1]-y[0]) return N.array([yd_0,yd_1]) def jac(t,y): eps = 1.e-6 my = 1./eps J = N.zeros([2,2]) J[0,0]=0. J[0,1]=1. J[1,0]=my*(-2.*y[0]*y[1]-1.) J[1,1]=my*(1.-y[0]**2) return J #Define an Assimulo problem y0 = [2.0,-0.6] #Initial conditions exp_mod = Explicit_Problem(f,y0) exp_mod_t0 = Explicit_Problem(f,y0,1.0) exp_mod.jac = jac self.mod = exp_mod #Define an explicit solver self.sim = Radau5ODE(exp_mod) #Create a Radau5 solve self.sim_t0 = Radau5ODE(exp_mod_t0) #Sets the parameters self.sim.atol = 1e-4 #Default 1e-6 self.sim.rtol = 1e-4 #Default 1e-6 self.sim.inith = 1.e-4 #Initial step-size self.sim.usejac = False
def test_usejac(self): """ This tests the functionality of the property usejac. """ f = lambda t,x: N.array([x[1], -9.82]) #Defines the rhs jac = lambda t,x: N.array([[0.,1.],[0.,0.]]) #Defines the jacobian exp_mod = Explicit_Problem(f, [1.0,0.0]) exp_mod.jac = jac exp_sim = CVode(exp_mod) exp_sim.discr='BDF' exp_sim.iter='Newton' exp_sim.simulate(5.,100) assert exp_sim.statistics["nfevalsLS"] == 0 nose.tools.assert_almost_equal(exp_sim.y_sol[-1][0], -121.75000143, 4) exp_sim.reset() exp_sim.usejac=False exp_sim.simulate(5.,100) nose.tools.assert_almost_equal(exp_sim.y_sol[-1][0], -121.75000143, 4) assert exp_sim.statistics["nfevalsLS"] > 0
def run_example(with_plots=True): r""" Example for the use of the implicit Euler method to solve Van der Pol's equation .. math:: \dot y_1 &= y_2 \\ \dot y_2 &= \mu ((1.-y_1^2) y_2-y_1) with :math:`\mu=\frac{1}{5} 10^3`. on return: - :dfn:`exp_mod` problem instance - :dfn:`exp_sim` solver instance """ eps = 5.e-3 my = 1./eps #Define the rhs def f(t,y): yd_0 = y[1] yd_1 = my*((1.-y[0]**2)*y[1]-y[0]) return N.array([yd_0,yd_1]) #Define the Jacobian def jac(t,y): jd_00 = 0.0 jd_01 = 1.0 jd_10 = -1.0*my-2*y[0]*y[1]*my jd_11 = my*(1.-y[0]**2) return N.array([[jd_00,jd_01],[jd_10,jd_11]]) y0 = [2.0,-0.6] #Initial conditions #Define an Assimulo problem exp_mod = Explicit_Problem(f,y0, name = "ImplicitEuler: Van der Pol's equation (as explicit problem) ") exp_mod.jac = jac #Define an explicit solver exp_sim = ImplicitEuler(exp_mod) #Create a ImplicitEuler solver #Sets the parameters exp_sim.h = 1e-4 #Stepsize exp_sim.usejac = True #If the user defined jacobian should be used or not #Simulate t, y = exp_sim.simulate(2.0) #Simulate 2 seconds #Plot if with_plots: import pylab as P P.plot(t,y[:,0], marker='o') P.title(exp_mod.name) P.ylabel("State: $y_1$") P.xlabel("Time") P.show() #Basic test x1 = y[:,0] assert N.abs(x1[-1]-1.8601438) < 1e-1 #For test purpose return exp_mod, exp_sim
def run_example(with_plots=True): r""" Example for the use of RODAS to solve Van der Pol's equation .. math:: \dot y_1 &= y_2 \\ \dot y_2 &= \mu ((1.-y_1^2) y_2-y_1) with :math:`\mu=1 10^6`. on return: - :dfn:`exp_mod` problem instance - :dfn:`exp_sim` solver instance """ #Define the rhs def f(t,y): eps = 1.e-6 my = 1./eps yd_0 = y[1] yd_1 = my*((1.-y[0]**2)*y[1]-y[0]) return N.array([yd_0,yd_1]) #Define the jacobian def jac(t,y): eps = 1.e-6 my = 1./eps j = N.array([[0.0,1.0],[my*((-2.0*y[0])*y[1]-1.0), my*(1.0-y[0]**2)]]) return j y0 = [2.0,-0.6] #Initial conditions #Define an Assimulo problem exp_mod = Explicit_Problem(f,y0, name = 'Van der Pol (explicit)') exp_mod.jac = jac #Define an explicit solver exp_sim = RodasODE(exp_mod) #Create a Rodas solver #Sets the parameters exp_sim.atol = 1e-4 #Default 1e-6 exp_sim.rtol = 1e-4 #Default 1e-6 exp_sim.inith = 1.e-4 #Initial step-size exp_sim.usejac = True #Simulate t, y = exp_sim.simulate(2.) #Simulate 2 seconds #Plot if with_plots: P.plot(t,y[:,0], marker='o') P.title(exp_mod.name) P.ylabel("State: $y_1$") P.xlabel("Time") P.show() #Basic test x1 = y[:,0] assert N.abs(x1[-1]-1.706168035) < 1e-3 #For test purpose return exp_mod, exp_sim
def run_example(with_plots=True): r""" Example for demonstrating the use of a user supplied Jacobian ODE: .. math:: \dot y_1 &= y_2 \\ \dot y_2 &= -9.82 on return: - :dfn:`exp_mod` problem instance - :dfn:`exp_sim` solver instance """ #Defines the rhs def f(t, y): yd_0 = y[1] yd_1 = -9.82 return N.array([yd_0, yd_1]) #Defines the Jacobian def jac(t, y): j = N.array([[0, 1.], [0, 0]]) return j #Defines an Assimulo explicit problem y0 = [1.0, 0.0] #Initial conditions exp_mod = Explicit_Problem(f, y0, name='Example using analytic Jacobian') exp_mod.jac = jac #Sets the Jacobian exp_sim = CVode(exp_mod) #Create a CVode solver #Set the parameters exp_sim.iter = 'Newton' #Default 'FixedPoint' exp_sim.discr = 'BDF' #Default 'Adams' exp_sim.atol = 1e-5 #Default 1e-6 exp_sim.rtol = 1e-5 #Default 1e-6 #Simulate t, y = exp_sim.simulate( 5, 1000) #Simulate 5 seconds with 1000 communication points #Plot if with_plots: import pylab as P P.plot(t, y, linestyle="dashed", marker="o") #Plot the solution P.xlabel('Time') P.ylabel('State') P.title(exp_mod.name) P.show() #Basic tests nose.tools.assert_almost_equal(y[-1][0], -121.75000000, 4) nose.tools.assert_almost_equal(y[-1][1], -49.100000000) return exp_mod, exp_sim
def run_example(with_plots=True): r""" This is the same example from the Sundials package (cvsRoberts_FSA_dns.c) Its purpose is to demonstrate the use of parameters in the differential equation. This simple example problem for CVode, due to Robertson see http://www.dm.uniba.it/~testset/problems/rober.php, is from chemical kinetics, and consists of the system: .. math:: \dot y_1 &= -p_1 y_1 + p_2 y_2 y_3 \\ \dot y_2 &= p_1 y_1 - p_2 y_2 y_3 - p_3 y_2^2 \\ \dot y_3 &= p_3 y_ 2^2 on return: - :dfn:`exp_mod` problem instance - :dfn:`exp_sim` solver instance """ def f(t, y, p): yd_0 = -p[0]*y[0]+p[1]*y[1]*y[2] yd_1 = p[0]*y[0]-p[1]*y[1]*y[2]-p[2]*y[1]**2 yd_2 = p[2]*y[1]**2 return N.array([yd_0,yd_1,yd_2]) def jac(t,y, p): J = N.array([[-p[0], p[1]*y[2], p[1]*y[1]], [p[0], -p[1]*y[2]-2*p[2]*y[1], -p[1]*y[1]], [0.0, 2*p[2]*y[1],0.0]]) return J def fsens(t, y, s, p): J = N.array([[-p[0], p[1]*y[2], p[1]*y[1]], [p[0], -p[1]*y[2]-2*p[2]*y[1], -p[1]*y[1]], [0.0, 2*p[2]*y[1],0.0]]) P = N.array([[-y[0],y[1]*y[2],0], [y[0], -y[1]*y[2], -y[1]**2], [0,0,y[1]**2]]) return J.dot(s)+P #The initial conditions y0 = [1.0,0.0,0.0] #Initial conditions for y #Create an Assimulo explicit problem exp_mod = Explicit_Problem(f,y0, name='Robertson Chemical Kinetics Example') exp_mod.rhs_sens = fsens exp_mod.jac = jac #Sets the options to the problem exp_mod.p0 = [0.040, 1.0e4, 3.0e7] #Initial conditions for parameters exp_mod.pbar = [0.040, 1.0e4, 3.0e7] #Create an Assimulo explicit solver (CVode) exp_sim = CVode(exp_mod) #Sets the solver paramters exp_sim.iter = 'Newton' exp_sim.discr = 'BDF' exp_sim.rtol = 1.e-4 exp_sim.atol = N.array([1.0e-8, 1.0e-14, 1.0e-6]) exp_sim.sensmethod = 'SIMULTANEOUS' #Defines the sensitvity method used exp_sim.suppress_sens = False #Dont suppress the sensitivity variables in the error test. exp_sim.report_continuously = True #Simulate t, y = exp_sim.simulate(4,400) #Simulate 4 seconds with 400 communication points #Basic test nose.tools.assert_almost_equal(y[-1][0], 9.05518032e-01, 4) nose.tools.assert_almost_equal(y[-1][1], 2.24046805e-05, 4) nose.tools.assert_almost_equal(y[-1][2], 9.44595637e-02, 4) nose.tools.assert_almost_equal(exp_sim.p_sol[0][-1][0], -1.8761, 2) #Values taken from the example in Sundials nose.tools.assert_almost_equal(exp_sim.p_sol[1][-1][0], 2.9614e-06, 8) nose.tools.assert_almost_equal(exp_sim.p_sol[2][-1][0], -4.9334e-10, 12) #Plot if with_plots: P.plot(t, y) P.title(exp_mod.name) P.xlabel('Time') P.ylabel('State') P.show() return exp_mod, exp_sim
def run_example(with_plots=True): r""" Example for demonstrating the use of a user supplied Jacobian (sparse). Note that this will only work if Assimulo has been configured with Sundials + SuperLU. Based on the SUNDIALS example cvRoberts_sps.c ODE: .. math:: \dot y_1 &= -0.04y_1 + 1e4 y_2 y_3 \\ \dot y_2 &= - \dot y_1 - \dot y_3 \\ \dot y_3 &= 3e7 y_2^2 on return: - :dfn:`exp_mod` problem instance - :dfn:`exp_sim` solver instance """ #Defines the rhs def f(t, y): yd_0 = -0.04 * y[0] + 1e4 * y[1] * y[2] yd_2 = 3e7 * y[1] * y[1] yd_1 = -yd_0 - yd_2 return N.array([yd_0, yd_1, yd_2]) #Defines the Jacobian def jac(t, y): colptrs = [0, 3, 6, 9] rowvals = [0, 1, 2, 0, 1, 2, 0, 1, 2] data = [ -0.04, 0.04, 0.0, 1e4 * y[2], -1e4 * y[2] - 6e7 * y[1], 6e7 * y[1], 1e4 * y[1], -1e4 * y[1], 0.0 ] J = SP.csc_matrix((data, rowvals, colptrs)) return J #Defines an Assimulo explicit problem y0 = [1.0, 0.0, 0.0] #Initial conditions exp_mod = Explicit_Problem(f, y0, name='Example using analytic (sparse) Jacobian') exp_mod.jac = jac #Sets the Jacobian exp_mod.jac_nnz = 9 exp_sim = CVode(exp_mod) #Create a CVode solver #Set the parameters exp_sim.iter = 'Newton' #Default 'FixedPoint' exp_sim.discr = 'BDF' #Default 'Adams' exp_sim.atol = [1e-8, 1e-14, 1e-6] #Default 1e-6 exp_sim.rtol = 1e-4 #Default 1e-6 exp_sim.linear_solver = "sparse" #Simulate t, y = exp_sim.simulate(0.4) #Simulate 0.4 seconds #Basic tests nose.tools.assert_almost_equal(y[-1][0], 0.9851, 3) #Plot if with_plots: P.plot(t, y[:, 1], linestyle="dashed", marker="o") #Plot the solution P.xlabel('Time') P.ylabel('State') P.title(exp_mod.name) P.show() return exp_mod, exp_sim
def run_simulation(filename, start_time, save_output, temp, RH, RO2_indices, H2O, input_dict, simulation_time, batch_step): from assimulo.solvers import RodasODE, CVode #Choose solver accoring to your need. from assimulo.problem import Explicit_Problem # In this function, we import functions that have been pre-compiled for use in the ODE solver # The function that calculates the RHS of the ODE is also defined within this function, such # that it can be used by the Assimulo solvers # The variables passed to this function are defined as follows: #------------------------------------------------------------------------------------- # define the ODE function to be called def dydt_func(t, y): """ This function defines the right-hand side [RHS] of the ordinary differential equations [ODEs] to be solved input: • t - time variable [internal to solver] • y - array holding concentrations of all compounds in both gas and particulate [molecules/cc] output: dydt - the dy_dt of each compound in both gas and particulate phase [molecules/cc.sec] """ #pdb.set_trace() # Calculate time of day time_of_day_seconds = start_time + t # make sure the y array is not a list. Assimulo uses lists y_asnumpy = numpy.array(y) #Calculate the concentration of RO2 species, using an index file created during parsing RO2 = numpy.sum(y[RO2_indices]) #Calculate reaction rate for each equation. # Note that H2O will change in parcel mode # The time_of_day_seconds is used for photolysis rates - need to change this if want constant values rates = evaluate_rates_fortran(RO2, H2O, temp, time_of_day_seconds) #pdb.set_trace() # Calculate product of all reactants and stochiometry for each reaction [A^a*B^b etc] reactants = reactants_fortran(y_asnumpy) #pdb.set_trace() #Multiply product of reactants with rate coefficient to get reaction rate reactants = numpy.multiply(reactants, rates) #pdb.set_trace() # Now use reaction rates with the loss_gain matri to calculate the final dydt for each compound # With the assimulo solvers we need to output numpy arrays dydt = loss_gain_fortran(reactants) #pdb.set_trace() return dydt #------------------------------------------------------------------------------------- #------------------------------------------------------------------------------------- # define jacobian function to be called def jacobian(t, y): """ This function defines Jacobian of the ordinary differential equations [ODEs] to be solved input: • t - time variable [internal to solver] • y - array holding concentrations of all compounds in both gas and particulate [molecules/cc] output: dydt_dydt - the N_compounds x N_compounds matrix of Jacobian values """ # Different solvers might call jacobian at different stages, so we have to redo some calculations here # Calculate time of day time_of_day_seconds = start_time + t # make sure the y array is not a list. Assimulo uses lists y_asnumpy = numpy.array(y) #Calculate the concentration of RO2 species, using an index file created during parsing RO2 = numpy.sum(y[RO2_indices]) #Calculate reaction rate for each equation. # Note that H2O will change in parcel mode rates = evaluate_rates_fortran(RO2, H2O, temp, time_of_day_seconds) #pdb.set_trace() # Now use reaction rates with the loss_gain matrix to calculate the final dydt for each compound # With the assimulo solvers we need to output numpy arrays dydt_dydt = jacobian_fortran(rates, y_asnumpy) #pdb.set_trace() return dydt_dydt #------------------------------------------------------------------------------------- #import static compilation of Fortran functions for use in ODE solver print("Importing pre-compiled Fortran modules") from rate_coeff_f2py import evaluate_rates as evaluate_rates_fortran from reactants_conc_f2py import reactants as reactants_fortran from loss_gain_f2py import loss_gain as loss_gain_fortran from jacobian_f2py import jacobian as jacobian_fortran # 'Unpack' variables from input_dict species_dict = input_dict['species_dict'] species_dict2array = input_dict['species_dict2array'] species_initial_conc = input_dict['species_initial_conc'] equations = input_dict['equations'] #Specify some starting concentrations [ppt] Cfactor = 2.55e+10 #ppb-to-molecules/cc # Create variables required to initialise ODE num_species = len(species_dict.keys()) y0 = [0] * num_species #Initial concentrations, set to 0 t0 = 0.0 #T0 # Define species concentrations in ppb # You have already set this in the front end script, and now we populate the y array with those concentrations for specie in species_initial_conc.keys(): y0[species_dict2array[specie]] = species_initial_conc[ specie] * Cfactor #convert from pbb to molcules/cc #Set the total_time of the simulation to 0 [havent done anything yet] total_time = 0.0 # Now run through the simulation in batches. # I do this to enable testing of coupling processes. Some initial investigations with non-ideality in # the condensed phase indicated that even defining a maximum step was not enough for ODE solvers to # overshoot a stable region. It also helps with in-simulation debugging. Its up to you if you want to keep this. # To not run in batches, just define one batch as your total simulation time. This will reduce any overhead with # initialising the solvers # Set total simulation time and batch steps in seconds # Note also that the current module outputs solver information after each batch step. This can be turned off and the # the batch step change for increased speed #simulation_time= 3600.0 #batch_step=100.0 t_array = [] time_step = 0 number_steps = int( simulation_time / batch_step) # Just cycling through 3 steps to get to a solution # Define a matrix that stores values as outputs from the end of each batch step. Again, you can remove # the need to run in batches. You can tell the Assimulo solvers the frequency of outputs. y_matrix = numpy.zeros((int(number_steps), len(y0))) print("Starting simulation") # In the following, we can while total_time < simulation_time: if total_time == 0.0: #Define an Assimulo problem #Define an explicit solver exp_mod = Explicit_Problem(dydt_func, y0, t0, name=filename) else: y0 = y_output[ -1, :] # Take the output from the last batch as the start of this exp_mod = Explicit_Problem(dydt_func, y0, t0, name=filename) # Define ODE parameters. # Initial steps might be slower than mid-simulation. It varies. #exp_mod.jac = dydt_jac exp_mod.jac = jacobian # Define which ODE solver you want to use exp_sim = CVode(exp_mod) tol_list = [1.0e-3] * num_species exp_sim.atol = tol_list #Default 1e-6 exp_sim.rtol = 1e-6 #Default 1e-6 exp_sim.inith = 1.0e-6 #Initial step-size #exp_sim.discr = 'Adams' exp_sim.maxh = 100.0 # Use of a jacobian makes a big differece in simulation time. This is relatively # easy to define for a gas phase - not sure for an aerosol phase with composition # dependent processes. exp_sim.usejac = True # To be provided as an option in future update. #exp_sim.fac1 = 0.05 #exp_sim.fac2 = 50.0 exp_sim.report_continuously = True exp_sim.maxncf = 1000 #Sets the parameters t_output, y_output = exp_sim.simulate( batch_step) #Simulate 'batch' seconds total_time += batch_step t_array.append( total_time ) # Save the output from the end step, of the current batch, to a matrix y_matrix[time_step, :] = y_output[-1, :] #now save this information into a matrix for later plotting. time_step += 1 # Do you want to save the generated matrix of outputs? if save_output: numpy.save(filename + '_output', y_matrix) df = pd.DataFrame(y_matrix) df.to_csv(filename + "_output_matrix.csv") w = csv.writer(open(filename + "_output_names.csv", "w")) for specie, number in species_dict2array.items(): w.writerow([specie, number]) with_plots = True #pdb.set_trace() #Plot the change in concentration over time for a given specie. For the user to change / remove #In a future release I will add this as a seperate module if with_plots: try: plt.plot(t_array, numpy.log10(y_matrix[:, species_dict2array['APINENE']]), marker='o', label="APINENE") plt.plot(t_array, numpy.log10(y_matrix[:, species_dict2array['PINONIC']]), marker='o', label="PINONIC") plt.title(exp_mod.name) plt.legend(loc='upper left') plt.ylabel("Concetration log10[molecules/cc]") plt.xlabel("Time [seconds] since start of simulation") plt.show() except: print( "There is a problem using Matplotlib in your environment. If using this within a docker container, you will need to transfer the data to the host or configure your container to enable graphical displays. More information can be found at http://wiki.ros.org/docker/Tutorials/GUI " )
def ode_solv(y, integ_step, rindx, pindx, rstoi, pstoi, nreac, nprod, rrc, jac_stoi, njac, jac_den_indx, jac_indx, Cinfl_now, y_arr, y_rind, uni_y_rind, y_pind, uni_y_pind, reac_col, prod_col, rstoi_flat, pstoi_flat, rr_arr, rr_arr_p, rowvals, colptrs, num_comp, num_sb, wall_on, Psat, Cw, act_coeff, kw, jac_wall_indx, seedi, core_diss, kelv_fac, kimt, num_asb, jac_part_indx, rindx_aq, pindx_aq, rstoi_aq, pstoi_aq, nreac_aq, nprod_aq, jac_stoi_aq, njac_aq, jac_den_indx_aq, jac_indx_aq, y_arr_aq, y_rind_aq, uni_y_rind_aq, y_pind_aq, uni_y_pind_aq, reac_col_aq, prod_col_aq, rstoi_flat_aq, pstoi_flat_aq, rr_arr_aq, rr_arr_p_aq, eqn_num): # inputs: ------------------------------------- # y - initial concentrations (moleucles/cm3) # integ_step - the maximum integration time step (s) # rindx - index of reactants per equation # pindx - index of products per equation # rstoi - stoichiometry of reactants # pstoi - stoichiometry of products # nreac - number of reactants per equation # nprod - number of products per equation # rrc - reaction rate coefficient # jac_stoi - stoichiometries relevant to Jacobian # njac - number of Jacobian elements affected per equation # jac_den_indx - index of component denominators for Jacobian # jac_indx - index of Jacobian to place elements per equation (rows) # Cinfl_now - influx of components with constant influx # (molecules/cc/s) # y_arr - index for matrix used to arrange concentrations, # enabling calculation of reaction rate coefficients # y_rind - index of y relating to reactants for reaction rate # coefficient equation # uni_y_rind - unique index of reactants # y_pind - index of y relating to products # uni_y_pind - unique index of products # reac_col - column indices for sparse matrix of reaction losses # prod_col - column indices for sparse matrix of production gains # rstoi_flat - 1D array of reactant stoichiometries per equation # pstoi_flat - 1D array of product stoichiometries per equation # rr_arr - index for reaction rates to allow reactant loss # calculation # rr_arr_p - index for reaction rates to allow reactant loss # calculation # rowvals - row indices of Jacobian elements # colptrs - indices of rowvals corresponding to each column of the # Jacobian # num_comp - number of components # num_sb - number of size bins # wall_on - flag saying whether to include wall partitioning # Psat - pure component saturation vapour pressures (molecules/cc) # Cw - effective absorbing mass concentration of wall (molecules/cc) # act_coeff - activity coefficient of components # kw - mass transfer coefficient to wall (/s) # jac_wall_indx - index of inputs to Jacobian by wall partitioning # seedi - index of seed material # core_diss - dissociation constant of seed material # kelv_fac - kelvin factor for particles # kimt - mass transfer coefficient for gas-particle partitioning (s) # num_asb - number of actual size bins (excluding wall) # jac_part_indx - index for sparse Jacobian for particle influence # eqn_num - number of gas- and aqueous-phase reactions # --------------------------------------------- def dydt(t, y): # define the ODE(s) # empty array to hold rate of change per component dd = np.zeros((len(y))) # gas-phase reactions ------------------------- # empty array to hold relevant concentrations for # reaction rate coefficient calculation rrc_y = np.ones((rindx.shape[0] * rindx.shape[1])) rrc_y[y_arr] = y[y_rind] rrc_y = rrc_y.reshape(rindx.shape[0], rindx.shape[1], order='C') # reaction rate (molecules/cc/s) rr = rrc[0:rindx.shape[0]] * ((rrc_y**rstoi).prod(axis=1)) # loss of reactants data = rr[rr_arr] * rstoi_flat # prepare loss values # convert to sparse matrix loss = SP.csc_matrix((data, y_rind, reac_col)) # register loss of reactants dd[uni_y_rind] -= np.array((loss.sum(axis=1))[uni_y_rind])[:, 0] # gain of products data = rr[rr_arr_p] * pstoi_flat # prepare loss values # convert to sparse matrix loss = SP.csc_matrix((data, y_pind, prod_col)) # register gain of products dd[uni_y_pind] += np.array((loss.sum(axis=1))[uni_y_pind])[:, 0] # particle-phase reactions ------------------------- if (eqn_num[1] > 0): # empty array to hold relevant concentrations for # reaction rate coefficient calculation # tile aqueous-phase reaction rate coefficients rr_aq = np.tile(rrc[rindx.shape[0]::], num_asb) # prepare aqueous-phase concentrations rrc_y = np.ones((rindx_aq.shape[0] * rindx_aq.shape[1])) rrc_y[y_arr_aq] = y[y_rind_aq] rrc_y = rrc_y.reshape(rindx_aq.shape[0], rindx_aq.shape[1], order='C') # reaction rate (molecules/cc/s) rr = rr_aq * ((rrc_y**rstoi_aq).prod(axis=1)) # loss of reactants data = rr[rr_arr_aq] * rstoi_flat_aq # prepare loss values # convert to sparse matrix loss = SP.csc_matrix((data[0, :], y_rind_aq, reac_col_aq)) # register loss of reactants dd[uni_y_rind_aq] -= np.array((loss.sum(axis=1))[uni_y_rind_aq])[:, 0] # gain of products data = rr[rr_arr_p_aq] * pstoi_flat_aq # prepare loss values # convert to sparse matrix loss = SP.csc_matrix((data[0, :], y_pind_aq, prod_col_aq)) # register gain of products dd[uni_y_pind_aq] += np.array((loss.sum(axis=1))[uni_y_pind_aq])[:, 0] # gas-particle partitioning----------------- # transform particle phase concentrations into # size bins in rows, components in columns ymat = (y[num_comp:num_comp * (num_asb + 1)]).reshape( num_asb, num_comp) # total particle-phase concentration per size bin (molecules/cc (air)) csum = ((ymat.sum(axis=1) - ymat[:, seedi].sum(axis=1)) + ( (ymat[:, seedi] * core_diss).sum(axis=1)).reshape(-1)).reshape( -1, 1) # size bins with contents isb = (csum[:, 0] > 0.) # container for gas-phase concentrations at particle surface Csit = np.zeros((num_asb, num_comp)) # mole fraction of components at particle surface Csit[isb, :] = (ymat[isb, :] / csum[isb, :]) # gas-phase concentration of components at # particle surface (molecules/cc (air)) Csit[isb, :] = Csit[isb, :] * Psat[isb, :] * kelv_fac[isb] * act_coeff[ isb, :] # partitioning rate (molecules/cc/s) dd_all = kimt * (y[0:num_comp].reshape(1, -1) - Csit) # gas-phase change dd[0:num_comp] -= dd_all.sum(axis=0) # particle change dd[num_comp:num_comp * (num_asb + 1)] += (dd_all.flatten()) # gas-wall partitioning ---------------- # concentration on wall (molecules/cc (air)) Csit = y[num_comp * num_sb:num_comp * (num_sb + 1)] # saturation vapour pressure on wall (molecules/cc (air)) # note, just using the top rows of Psat and act_coeff # as do not need the repetitions over size bins if (Cw > 0.): Csit = Psat[0, :] * (Csit / Cw) * act_coeff[0, :] # rate of transfer (molecules/cc/s) dd_all = kw * (y[0:num_comp] - Csit) dd[0:num_comp] -= dd_all # gas-phase change dd[num_comp * num_sb:num_comp * (num_sb + 1)] += dd_all # wall change return (dd) def jac(t, y): # define the Jacobian # elements of sparse Jacobian matrix data = np.zeros((94)) for i in range(rindx.shape[0]): # gas-phase reaction loop # reaction rate (molecules/cc/s) rr = rrc[i] * (y[rindx[i, 0:nreac[i]]].prod()) # prepare Jacobian inputs jac_coeff = np.zeros((njac[i, 0])) # only fill Jacobian if reaction rate sufficient if (rr != 0.): jac_coeff = (rr * (jac_stoi[i, 0:njac[i, 0]]) / (y[jac_den_indx[i, 0:njac[i, 0]]])) data[jac_indx[i, 0:njac[i, 0]]] += jac_coeff n_aqr = nreac_aq.shape[0] # number of aqueous-phase reactions aqi = 0 # aqueous-phase reaction counter for i in range(rindx.shape[0], rrc.shape[0]): # aqueous-phase reaction loop # reaction rate (molecules/cc/s) rr = rrc[i] * (y[rindx_aq[aqi::n_aqr, 0:nreac_aq[aqi]]].prod(axis=1)) # spread along affected components rr = rr.reshape(-1, 1) rr = (np.tile(rr, int(njac_aq[aqi, 0] / (num_sb - wall_on)))).flatten(order='C') # prepare Jacobian inputs jac_coeff = np.zeros((njac_aq[aqi, 0])) nzi = (rr != 0) jac_coeff[nzi] = ( rr[nzi] * ((jac_stoi_aq[aqi, 0:njac_aq[aqi, 0]])[nzi]) / ((y[jac_den_indx_aq[aqi, 0:njac_aq[aqi, 0]]])[nzi])) # stack size bins jac_coeff = jac_coeff.reshape(int(num_sb - wall_on), int(njac_aq[aqi, 0] / (num_sb - wall_on)), order='C') data[jac_indx_aq[aqi::n_aqr, 0:(int(njac_aq[aqi, 0] / (num_sb - wall_on)))]] += jac_coeff aqi += 1 # gas-particle partitioning part_eff = np.zeros((56)) part_eff[0:24:3] = -kimt.sum(axis=0) # effect of gas on gas # transform particle phase concentrations into # size bins in rows, components in columns ymat = (y[num_comp:num_comp * (num_asb + 1)]).reshape( num_asb, num_comp) # total particle-phase concentration per size bin (molecules/cc (air)) csum = ymat.sum(axis=1) - ymat[:, seedi].sum( axis=1) + (ymat[:, seedi] * core_diss).sum(axis=1) # effect of particle on gas for isb in range(int(num_asb)): # size bin loop if csum[isb] > 0: # if particles present # effect of gas on particle part_eff[1 + isb:num_comp * (num_asb + 1):num_asb + 1] = +kimt[isb, :] # start and finish index sti = int((num_asb + 1) * num_comp + isb * (num_comp * 2)) fii = int(sti + (num_comp * 2.)) # diagonal index diag_indxg = sti + np.arange(0, num_comp * 2, 2).astype('int') diag_indxp = sti + np.arange(1, num_comp * 2, 2).astype('int') # prepare for diagonal (component effect on itself) diag = kimt[isb, :] * Psat[0, :] * act_coeff[0, :] * kelv_fac[ isb, 0] * (csum[isb] - ymat[isb, :]) / (csum[isb]**2.) # implement to part_eff part_eff[diag_indxg] = +diag part_eff[diag_indxp] = -diag data[jac_part_indx] += part_eff if (Cw > 0.): wall_eff = np.zeros((32)) wall_eff[0:16:2] = -kw # effect of gas on gas wall_eff[1:16:2] = +kw # effect of gas on wall # effect of wall on gas wall_eff[16:32:2] = +kw * (Psat[0, :] * act_coeff[0, :] / Cw) # effect of wall on wall wall_eff[16 + 1:32:2] = -kw * (Psat[0, :] * act_coeff[0, :] / Cw) data[jac_wall_indx] += wall_eff # create Jacobian j = SP.csc_matrix((data, rowvals, colptrs)) return (j) mod = Explicit_Problem(dydt, y) # instantiate solver mod.jac = jac # set the Jacobian mod_sim = CVode(mod) # define a solver instance # there is a general trend of an inverse relationship # between tolerances and computation time mod_sim.atol = 0.001 mod_sim.rtol = 0.0001 # integration approach (backward differentiation formula) mod_sim.discr = 'BDF' # call solver res_t, res = mod_sim.simulate(integ_step) # return concentrations following integration return (res, res_t)