def run_simulation(filename, save_output, start_time, temp, RH, RO2_indices, H2O, PInit, y_cond, input_dict, simulation_time, batch_step, plot_mass): from assimulo.solvers import RodasODE, CVode, RungeKutta4, LSODAR #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] """ dy_dt = numpy.zeros((total_length_y, 1), ) #pdb.set_trace() # Calculate time of day time_of_day_seconds = start_time + t #pdb.set_trace() # make sure the y array is not a list. Assimulo uses lists y_asnumpy = numpy.array(y) Model_temp = temp #pdb.set_trace() #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, Model_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[0:num_species - 1]) #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_gas = loss_gain_fortran(reactants) #pdb.set_trace() dy_dt[0:num_species - 1, 0] = dydt_gas # Change the saturation vapour pressure of water # Need to re-think the change of organic vapour pressures with temperature. # At the moment this is kept constant as re-calulation using UManSysProp very slow sat_vap_water = numpy.exp((-0.58002206E4 / Model_temp) + 0.13914993E1 - \ (0.48640239E-1 * Model_temp) + (0.41764768E-4 * (Model_temp**2.0E0))- \ (0.14452093E-7 * (Model_temp**3.0E0)) + (0.65459673E1 * numpy.log(Model_temp))) sat_vp[-1] = (numpy.log10(sat_vap_water * 9.86923E-6)) Psat = numpy.power(10.0, sat_vp) # Convert the concentration of each component in the gas phase into a partial pressure using the ideal gas law # Units are Pascals Pressure_gas = (y_asnumpy[0:num_species, ] / NA) * 8.314E+6 * Model_temp #[using R] core_mass_array = numpy.multiply(ycore_asnumpy / NA, core_molw_asnumpy) ####### Calculate the thermal conductivity of gases according to the new temperature ######## K_water_vapour = ( 5.69 + 0.017 * (Model_temp - 273.15)) * 1e-3 * 4.187 #[W/mK []has to be in W/m.K] # Use this value for all organics, for now. If you start using a non-zero enthalpy of # vapourisation, this needs to change. therm_cond_air = K_water_vapour #---------------------------------------------------------------------------- #F2c) Extract the current gas phase concentrations to be used in pressure difference calculations C_g_i_t = y_asnumpy[0:num_species, ] #Set the values for oxidants etc to 0 as will force no mass transfer #C_g_i_t[ignore_index]=0.0 C_g_i_t = C_g_i_t[include_index] #pdb.set_trace() total_SOA_mass,aw_array,size_array,dy_dt_calc = dydt_partition_fortran(y_asnumpy,ycore_asnumpy,core_dissociation, \ core_mass_array,y_density_array_asnumpy,core_density_array_asnumpy,ignore_index_fortran,y_mw,Psat, \ DStar_org_asnumpy,alpha_d_org_asnumpy,C_g_i_t,N_perbin,gamma_gas_asnumpy,Latent_heat_asnumpy,GRAV, \ Updraft,sigma,NA,kb,Rv,R_gas,Model_temp,cp,Ra,Lv_water_vapour) #pdb.set_trace() # Add the calculated gains/losses to the complete dy_dt array dy_dt[0:num_species + (num_species_condensed * num_bins), 0] += dy_dt_calc[:] #pdb.set_trace() #---------------------------------------------------------------------------- #F4) Now calculate the change in water vapour mixing ratio. #To do this we need to know what the index key for the very last element is #pdb.set_trace() #pdb.set_trace() #print "elapsed time=", elapsedTime dydt_func.total_SOA_mass = total_SOA_mass dydt_func.size_array = size_array dydt_func.temp = Model_temp dydt_func.RH = Pressure_gas[-1] / (Psat[-1] * 101325.0) dydt_func.water_activity = aw_array #---------------------------------------------------------------------------- return dy_dt #------------------------------------------------------------------------------------- #------------------------------------------------------------------------------------- #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 partition_f2py import dydt_partition as dydt_partition_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'] num_species = input_dict['num_species'] num_species_condensed = input_dict['num_species_condensed'] y_density_array_asnumpy = input_dict['y_density_array_asnumpy'] y_mw = input_dict['y_mw'] sat_vp = input_dict['sat_vp'] Delta_H = input_dict['Delta_H'] Latent_heat_asnumpy = input_dict['Latent_heat_asnumpy'] DStar_org_asnumpy = input_dict['DStar_org_asnumpy'] alpha_d_org_asnumpy = input_dict['alpha_d_org_asnumpy'] gamma_gas_asnumpy = input_dict['gamma_gas_asnumpy'] Updraft = input_dict['Updraft'] GRAV = input_dict['GRAV'] Rv = input_dict['Rv'] Ra = input_dict['Ra'] R_gas = input_dict['R_gas'] R_gas_other = input_dict['R_gas_other'] cp = input_dict['cp'] sigma = input_dict['sigma'] NA = input_dict['NA'] kb = input_dict['kb'] Lv_water_vapour = input_dict['Lv_water_vapour'] ignore_index = input_dict['ignore_index'] ignore_index_fortran = input_dict['ignore_index_fortran'] ycore_asnumpy = input_dict['ycore_asnumpy'] core_density_array_asnumpy = input_dict['core_density_array_asnumpy'] y_cond = input_dict['y_cond_initial'] num_bins = input_dict['num_bins'] core_molw_asnumpy = input_dict['core_molw_asnumpy'] core_dissociation = input_dict['core_dissociation'] N_perbin = input_dict['N_perbin'] include_index = input_dict['include_index'] # pdb.set_trace() #Specify some starting concentrations [ppt] Cfactor = 2.55e+10 #ppb-to-molecules/cc # Create variables required to initialise ODE y0 = [0] * (num_species + num_species_condensed * num_bins ) #Initial concentrations, set to 0 t0 = 0.0 #T0 # Define species concentrations in ppb fr the gas phase # 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(): if specie is not 'H2O': y0[species_dict2array[specie]] = species_initial_conc[ specie] * Cfactor #convert from pbb to molcules/cc elif specie is 'H2O': y0[species_dict2array[specie]] = species_initial_conc[specie] # Now add the initial condensed phase [including water] #pdb.set_trace() y0[num_species:num_species + ((num_bins) * num_species_condensed)] = y_cond[:] #pdb.set_trace() #Set the total_time of the simulation to 0 [havent done anything yet] total_time = 0.0 # Define a 'key' that represents the end of the composition variables to track total_length_y = len(y0) key = num_species + ((num_bins) * num_species) - 1 #pdb.set_trace() # 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=300.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))) # Also define arrays and matrices that hold information such as total SOA mass SOA_matrix = numpy.zeros((int(number_steps), 1)) size_matrix = numpy.zeros((int(number_steps), num_bins)) 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 # Define which ODE solver you want to use exp_sim = CVode(exp_mod) tol_list = [1.0e-2] * len(y0) exp_sim.atol = tol_list #Default 1e-6 exp_sim.rtol = 1.0e-4 #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 = False # 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, :] SOA_matrix[time_step, 0] = dydt_func.total_SOA_mass size_matrix[time_step, :] = dydt_func.size_array print("SOA [micrograms/m3] = ", dydt_func.total_SOA_mass) #now save this information into a matrix for later plotting. time_step += 1 if save_output is True: print( "Saving the model output as a pickled object for later retrieval") # save the dictionary to a file for later retrieval - have to do each seperately. with open(filename + '_y_output.pickle', 'wb') as handle: pickle.dump(y_matrix, handle, protocol=pickle.HIGHEST_PROTOCOL) with open(filename + '_t_output.pickle', 'wb') as handle: pickle.dump(t_array, handle, protocol=pickle.HIGHEST_PROTOCOL) with open(filename + '_SOA_output.pickle', 'wb') as handle: pickle.dump(SOA_matrix, handle, protocol=pickle.HIGHEST_PROTOCOL) with open(filename + '_size_output.pickle', 'wb') as handle: pickle.dump(size_matrix, handle, protocol=pickle.HIGHEST_PROTOCOL) with open(filename + 'include_index.pickle', 'wb') as handle: pickle.dump(include_index, handle, protocol=pickle.HIGHEST_PROTOCOL) #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 plot_mass is True: try: P.plot(t_array, SOA_matrix[:, 0], marker='o') P.title(exp_mod.name) P.ylabel("SOA mass [micrograms/m3]") P.xlabel("Time [seconds] since start of simulation") P.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 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 # 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 = 0.03 #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: P.plot(t_array, numpy.log10(y_matrix[:, species_dict2array['APINENE']]), marker='o', label="APINENE") P.plot(t_array, numpy.log10(y_matrix[:, species_dict2array['PINONIC']]), marker='o', label="PINONIC") P.title(exp_mod.name) P.legend(loc='upper left') P.ylabel("Concetration log10[molecules/cc]") P.xlabel("Time [seconds] since start of simulation") P.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 run_sim_ice(Y, YLIQ): def dy_dt_func(t, Y): dy_dt = np.zeros(len(Y)) svp = f.svp_liq(Y[ITEMP]) svp_ice = f.svp_ice(Y[ITEMP]) # vapour mixing ratio WV = c.eps * Y[IRH_ICE] * svp / (Y[IPRESS_ICE] - svp) # liquid mixing ratio WL = sum(YLIQ[IND1:IND2] * YLIQ[0:IND1]) # ice mixing ratio WI = sum(Y[IND1:IND2] * Y[0:IND1]) Cpm = c.CP + WV * c.CPV + WL * c.CPW + WI * c.CPI # RH with respect to ice RH_ICE = WV / (c.eps * svp_ice / (Y[IPRESS_ICE] - svp_ice)) # ------------------------- growth rate of ice -------------------------- RH_EQ = 1e0 # from ACPIM, FPARCELCOLD - MICROPHYSICS.f90 CAP = f.CAPACITANCE01(Y[0:IND1], np.exp(Y[IND2:IND3])) growth_rate = f.ICEGROWTHRATE(Y[ITEMP_ICE], Y[IPRESS_ICE], RH_ICE, RH_EQ, Y[0:IND1], np.exp(Y[IND2:IND3]), CAP) growth_rate[np.isnan(growth_rate)] = 0 # get rid of nans growth_rate = np.where(Y[IND1:IND2] < 1e-6, 0.0, growth_rate) # Mass of water condensing dy_dt[:IND1] = growth_rate #---------------------------aspect ratio--------------------------------------- DELTA_RHO = c.eps * svp / (Y[IPRESS_ICE] - svp) DELTA_RHOI = c.eps * svp_ice / (Y[IPRESS_ICE] - svp_ice) DELTA_RHO = Y[IRH_ICE] * DELTA_RHO - DELTA_RHOI DELTA_RHO = DELTA_RHO * Y[IPRESS_ICE] / Y[ITEMP_ICE] / c.RA RHO_DEP = f.DEP_DENSITY(DELTA_RHO, Y[ITEMP_ICE]) # this is the rate of change of LOG of the aspect ratio dy_dt[IND2:IND3] = (dy_dt[0:IND1] * ((f.INHERENTGROWTH(Y[ITEMP_ICE]) - 1) / (f.INHERENTGROWTH(Y[ITEMP_ICE]) + 2)) / (Y[0:IND1] * c.rhoi * RHO_DEP)) #------------------------------------------------------------------------------ # Change in vapour content dwv_dt = -1 * sum(Y[IND1:IND2] * dy_dt[0:IND1]) # change in water vapour mixing ratio DRI = -1 * dwv_dt dy_dt[ITEMP_ICE] = 0.0 #+c.LS/Cpm*DRI # if n.Simulation_type.lower() == 'parcel': # dy_dt[ITEMP_ICE]=dy_dt[ITEMP_ICE] + c.LS/Cpm*DRI #---------------------------RH change------------------------------------------ dy_dt[IRH_ICE] = (Y[IPRESS_ICE] - svp) * svp * dwv_dt dy_dt[IRH_ICE] = ( dy_dt[IRH_ICE] - WV * Y[IPRESS_ICE] * derivative(f.svp_liq, Y[ITEMP_ICE], dx=1.0) * dy_dt[ITEMP_ICE]) dy_dt[IRH_ICE] = dy_dt[IRH_ICE] / (c.eps * svp**2) #------------------------------------------------------------------------------ return dy_dt #--------------------- SET-UP solver -------------------------------------- y0 = Y t0 = 0.0 #define assimulo problem exp_mod = Explicit_Problem(dy_dt_func, y0, t0) # define an explicit solver exp_sim = CVode(exp_mod) exp_sim.iter = 'Newton' exp_sim.discr = 'BDF' # set tolerance for each dydt function tol_list = np.zeros_like(Y) tol_list[0:IND1] = 1e-30 # mass tol_list[IND1:IND2] = 10 # number tol_list[IND2:IND3] = 1e-30 # aspect ratio # tol_list[IND3:IRH_SV_ICE] = 1e-26 #tol_list[IRH_SV_ICE] = 1e-26 tol_list[IPRESS_ICE] = 10 tol_list[ITEMP_ICE] = 1e-4 tol_list[IRH_ICE] = 1e-8 exp_sim.atol = tol_list exp_sim.rtol = 1.0e-8 exp_sim.inith = 1.0e-2 # initial time step-size exp_sim.usejac = False exp_sim.maxncf = 100 # max number of convergence failures allowed by solver exp_sim.verbosity = 40 t_output, y_output = exp_sim.simulate(1) return y_output[-1, :]
def run_sim(Y, time, Y_AER1, YICE): def dy_dt_func(t, Y): dy_dt = np.zeros(len(Y)) # add condensed semi-vol mass into bins if n.SV_flag: MBIN2[-1 * n.n_sv:, :] = np.reshape(Y[INDSV1:INDSV2], [n.n_sv, nbins * nmodes]) # calculate saturation vapour pressure over liquid svp1 = f.svp_liq(Y[ITEMP]) # saturation ratio SL = svp1 * Y[IRH] / (Y[IPRESS] - svp1) SL = (SL * Y[IPRESS] / (1 + SL)) / svp1 # water vapour mixing ratio WV = c.eps * Y[IRH] * svp1 / (Y[IPRESS] - svp1) WL = np.sum(Y[IND1:IND2] * Y[:IND1]) # LIQUID MIXING RATIO WI = np.sum(YICE[IND1:IND2] * YICE[:IND1]) # ice mixing ratio RM = c.RA + WV * c.RV CPM = c.CP + WV * c.CPV + WL * c.CPW + WI * c.CPI if simulation_type.lower() == 'chamber': # CHAMBER MODEL - pressure change dy_dt[IPRESS] = -100 * PRESS1 * PRESS2 * np.exp(-PRESS2 * (time + t)) elif simulation_type.lower() == 'parcel': # adiabatic parcel dy_dt[IPRESS] = -Y[IPRESS] / RM / Y[ ITEMP] * c.g * w #! HYDROSTATIC EQUATION else: print('simulation type unknown') return # ----------------------------change in vapour content: ----------------------- # 1. equilibruim size of particles if n.kappa_flag: #if n.SV_flag: # need to recalc kappa taking into acount the condensed semi-vols Kappa = np.sum( (MBIN2[:, :] / RHOBIN2[:, :]) * KAPPABIN2[:, :], axis=0) / np.sum(MBIN2[:, :] / RHOBIN2[:, :], axis=0) # print(Kappa) # print(MBIN2/RHOBIN2) KK01 = f.kk01(Y[0:IND1], Y[ITEMP], MBIN2, RHOBIN2, Kappa) else: KK01 = f.K01(Y[0:IND1], Y[ITEMP], MBIN2, n.n_sv, RHOBIN2, NUBIN2, MOLWBIN2) # print(KK01[0]) Dw = KK01[2] # wet diameter RHOAT = KK01[1] # density of particles inc water and aerosol mass RH_EQ = KK01[0] # equilibrium RH # print(MBIN2/MOLWBIN2) # 2. growth rate of particles, Jacobson p455 # rate of change of radius growth_rate = f.DROPGROWTHRATE(Y[ITEMP], Y[IPRESS], SL, RH_EQ, RHOAT, Dw) growth_rate[np.isnan(growth_rate)] = 0 # get rid of nans growth_rate = np.where(Y[IND1:IND2] < 1e-9, 0.0, growth_rate) # 3. Mass of water condensing # change in mass of water per particle dy_dt[:IND1] = (np.pi * RHOAT * Dw**2) * growth_rate # 4. Change in vapour content # change in water vapour mixing ratio dwv_dt = -1 * np.sum( Y[IND1:IND2] * dy_dt[:IND1]) # change to np.sum for speed # mass # ----------------------------------------------------------------------------- if simulation_type.lower() == 'chamber': # CHAMBER MODEL - temperature change dy_dt[ITEMP] = -Temp1 * Temp2 * np.exp(-Temp2 * (time + t)) elif simulation_type.lower() == 'parcel': # adiabatic parcel dy_dt[ITEMP] = RM / Y[IPRESS] * dy_dt[IPRESS] * Y[ ITEMP] / CPM # TEMPERATURE CHANGE: EXPANSION dy_dt[ITEMP] = dy_dt[ITEMP] - c.LV / CPM * dwv_dt else: print('simulation type unknown') return # --------------------------------RH change------------------------------------ dy_dt[IRH] = svp1 * dwv_dt * (Y[IPRESS] - svp1) dy_dt[IRH] = dy_dt[IRH] + svp1 * WV * dy_dt[IPRESS] dy_dt[IRH] = ( dy_dt[IRH] - WV * Y[IPRESS] * derivative(f.svp_liq, Y[ITEMP], dx=1.0) * dy_dt[ITEMP]) dy_dt[IRH] = dy_dt[IRH] / (c.eps * svp1**2) # ----------------------------------------------------------------------------- # ------------------------------ SEMI-VOLATILES ------------------------------- if n.SV_flag: # SV_mass = np.reshape(Y[INDSV1:INDSV2],[n.n_sv,n.nmodes*n.nbins]) # SV_mass = np.where(SV_mass == 0.0,1e-30,SV_mass) # MBIN2[n.n_sv*-1:,:] = SV_mass RH_EQ_SV = f.K01SV(Y[:IND1], Y[ITEMP], MBIN2, n.n_sv, RHOBIN2, NUBIN2, MOLWBIN2) RH_EQ = RH_EQ_SV[0] RHOAT = RH_EQ_SV[1] DW = RH_EQ_SV[2] SVP_ORG = f.SVP_GASES(n.semi_vols, Y[ITEMP], n.n_sv) #C-C equation #RH_ORG = [x*Y[IPRESS]/c.RA/Y[ITEMP] for x in Y[IRH_SV]] RH_ORG = [x for x in Y[IRH_SV]] RH_ORG = [(x / c.aerosol_dict[key][0]) * c.R * Y[ITEMP] for x, key in zip(RH_ORG, n.semi_vols[:n.n_sv]) ] # just for n_sv keys in dictionary RH_ORG = [RH_ORG[x] / SVP_ORG[x] for x in range(n.n_sv)] dy_dt[INDSV1:INDSV2] = f.SVGROWTHRATE(Y[ITEMP], Y[IPRESS], SVP_ORG, RH_ORG, RH_EQ, DW, n.n_sv, n.nbins, n.nmodes, MOLWBIN2) dy_dt[IRH_SV] = -np.sum(np.reshape( dy_dt[INDSV1:INDSV2], [n.n_sv, IND1]) * Y[IND1:IND2], axis=1) #see line 137 return dy_dt #--------------------- SET-UP solver ------------------------------------------ y0 = Y t0 = 0.0 #define assimulo problem exp_mod = Explicit_Problem(dy_dt_func, y0, t0) # define an explicit solver exp_sim = CVode(exp_mod) exp_sim.iter = 'Newton' exp_sim.discr = 'BDF' #set parameters tol_list = np.zeros_like(Y) tol_list[0:IND1] = 1e-40 # this is now different to ACPIM (1e-25) tol_list[IND1:IND2] = 10 # number tol_list[IND2:IND3] = 1e-30 # capacitance tol_list[IND3:INDSV2] = 1e-26 #condendensed semi-vol mass tol_list[IRH_SV] = 1e-26 # RH of each semi-vol compound tol_list[IPRESS] = 10 tol_list[ITEMP] = 1e-4 tol_list[IRH] = 1e-8 # set tolerance for each dydt function exp_sim.atol = tol_list exp_sim.rtol = 1.0e-8 exp_sim.inith = 0 # initial time step-size exp_sim.usejac = False exp_sim.maxncf = 100 # max number of convergence failures allowed by solver exp_sim.verbosity = 40 t_output, y_output = exp_sim.simulate(1) return y_output[-1, :], t_output[:]
def run_simulation(filename, save_output, start_time, 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 # In the standard Python version [not using Numba] I use Sparse matrix operations in calculating loss/gain of each compound. # This function loads the matrix created at the beginning of the module. def load_sparse_csr(filename): loader = numpy.load('loss_gain_' + filename + '.npz') return csr_matrix( (loader['data'], loader['indices'], loader['indptr']), shape=loader['shape']) def load_sparse_csr_reactants(filename): loader = numpy.load('reactants_indices_sparse_' + filename + '.npz') return csr_matrix( (loader['data'], loader['indices'], loader['indptr']), shape=loader['shape']) #------------------------------------------------------------------------------------- # 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() #Here we use the pre-created Numba based functions to arrive at our value for dydt # 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) #pdb.set_trace() # reactants=numpy.zeros((equations),) #pdb.set_trace() #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 [to be changed in the full aerosol mode] # The time_of_day_seconds is used for photolysis rates - need to change this if want constant values #pdb.set_trace() rates = evaluate_rates(time_of_day_seconds, RO2, H2O, temp, numpy.zeros((equations)), numpy.zeros((63))) # Calculate product of all reactants and stochiometry for each reaction [A^a*B^b etc] reactants = reactant_product(y_asnumpy, equations, numpy.zeros((equations))) #Multiply product of reactants with rate coefficient to get reaction rate #pdb.set_trace() reactants = numpy.multiply(reactants, rates) # Now use reaction rates with the loss_gain information in a pre-created Numba file to calculate the final dydt for each compound dydt = dydt_eval(numpy.zeros((len(y_asnumpy))), reactants) #pdb.set_trace() ############ Development place-holder ############## # ---------------------------------------------------------------------------------- # The following demonstrates the same procedure but using only Numpy and pure python # For the full MCM this is too slow, but is useful for demonstrations and testing #Calculate reaction rate for each equation. ## rates=test(time_of_day_seconds,RO2,H2O,temp) # Calculate product of all reactants and stochiometry for each reaction [A^a*B^b etc] # Take the approach of using sparse matrix operations from a python perspective # This approach uses the rule of logarithms and sparse matrix multiplication ##temp_array=reactants_indices_sparse @ numpy.log(y_asnumpy) ##indices=numpy.where(temp_array > 0.0) ##reactants[indices]=numpy.exp(temp_array[indices]) #Multiply product of reactants with rate coefficient to get reaction rate ## reactants = numpy.multiply(reactants,rates) # 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=numpy.array(loss_gain @ reactants) # ---------------------------------------------------------------------------------- return dydt #------------------------------------------------------------------------------------- print( "Importing Numba modules [compiling if first import or clean build...please be patient]" ) #import Numba functions for use in ODE solver from Rate_coefficients_numba import evaluate_rates from Reactants_conc_numba import reactants as reactant_product from Loss_Gain_numba import dydt as dydt_eval # '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'] # Set dive by zero to ignore for use of any sparse matrix multiplication numpy.errstate(divide='ignore') # --- For Numpy and pure Python runs ---- # Load the sparse matrix used in calculating the reactant products and dydt function ## reactants_indices_sparse = load_sparse_csr_reactants(filename) ## loss_gain = load_sparse_csr(filename) #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 # seconds # batch_step=100.0 # seconds 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") #pdb.set_trace() while total_time < simulation_time: if total_time == 0.0: #Define an Assimulo problem #Define an explicit solver #pdb.set_trace() 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 # 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 = False # To be provided as an option in future update. See Fortran variant for use of Jacobian #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, :] #pdb.set_trace() #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: P.plot(t_array, numpy.log10(y_matrix[:, species_dict2array['APINENE']]), marker='o', label="APINENE") P.plot(t_array, numpy.log10(y_matrix[:, species_dict2array['PINONIC']]), marker='o', label="PINONIC") P.title(exp_mod.name) P.legend(loc='upper left') P.ylabel("Concetration log10[molecules/cc]") P.xlabel("Time [seconds] since start of simulation") P.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 " )