Exemplo n.º 1
0
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 "
            )
Exemplo n.º 2
0
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 "
            )
Exemplo n.º 3
0
    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[:]
Exemplo n.º 4
0
    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, :]
Exemplo n.º 5
0
def func_set_system():
    ##############################################
    #% initial conditions
    P_0           = depth*9.8*2600.           #;      % initial chamber pressure (Pa)
    T_0           = 1200            #;       % initial chamber temperature (K)
    eps_g0        = 0.04            #;       % initial gas volume fraction
    rho_m0        = 2600            #;       % initial melt density (kg/m^3)
    rho_x0        = 3065            #;       % initial crystal density (kg/m^3)
    a             = 2000.            #;       % initial radius of the chamber (m)
    V_0           = (4.*np.pi/3.)*a**3.  #; % initial volume of the chamber (m^3)

    ##############################################
    ##############################################
    IC = np.array([P_0, T_0, eps_g0, V_0, rho_m0, rho_x0])  # % store initial conditions
    ## Gas (eps_g = zero), eps_x is zero, too many crystals, 50 % crystallinity,eruption (yes/no)
    sw0 = [False,False,False,False,False]

    ##############################################
    #% error tolerances used in ode method
    dt = 3e7*10.
    begin_time = 0  # ; % initialize time
    N  = int(round((end_time-begin_time)/dt))
    ##############################################

    #Define an Assimulo problem
    exp_mod = Chamber_Problem(depth=depth,t0=begin_time,y0=IC,sw0=sw0)
    exp_mod.param['T_S'] = 500.#+273.
    exp_mod.param['T_in'] = 1200.
    exp_mod.param['eps_g_in'] = 0.0    # Gas fraction of incoming melt - gas phase ..
    exp_mod.param['m_eq_in'] = 0.03    # Volatile fraction of incoming melt
    exp_mod.param['Mdot_in']    = mdot
    exp_mod.param['eta_x_max'] = 0.55                                     # Locking fraction
    exp_mod.param['delta_Pc']   = 20e6
    exp_mod.allow_diffusion_init = True
    exp_mod.radius = a
    exp_mod.permeability = perm_val
    exp_mod.R_steps = 1500
    exp_mod.dt_init = dt
    inp_func1 = inp.Input_functions_Degruyer()
    exp_mod.set_input_functions(inp_func1)
    exp_mod.get_constants()
    exp_mod.set_init_crust(material = 'Westerly_Granite')
    #################
    begin_time = exp_mod.set_init_crust_profile(T_0)
    exp_mod.tcurrent = begin_time
    P_0 = exp_mod.plith
    exp_mod.t0 = begin_time
    exp_mod.param['heat_cond'] = 1.                                             # Turn on/off heat conduction
    exp_mod.param['visc_relax'] = 1.                                          # Turn on/off viscous relaxation
    exp_mod.param['press_relax'] = 1.                                          ## Turn on/off pressure diffusion
    exp_mod.param['frac_rad_Temp'] =0.75
    exp_mod.param['vol_degass'] = 1.
    exp_mod.param['outflow_model'] = None  # 'huppert'
    IC = np.array([P_0, T_0, eps_g0, V_0, rho_m0, rho_x0])  # % store initial conditions
    exp_mod.y0 = IC

    #Define an explicit solver
    exp_sim = CVode(exp_mod) #Create a CVode solver
    # exp_sim = LSODAR(exp_mod) #Create a CVode solver

    #Sets the parameters
    exp_sim.store_event_points = True
    #exp_sim.iter = 'Newton'
    #exp_sim.discr = 'BDF'
    exp_sim.inith = 1e2
    #exp_sim.display_progress = True
    exp_sim.rtol = 1.e-7
    #exp_sim.maxh = 3e8*5. # 10 years
    exp_sim.atol = 1e-7
    #exp_sim.sensmethod = 'SIMULTANEOUS' #Defines the sensitvity method used
    #exp_sim.suppress_sens = True       #Dont suppress the sensitivity variables in the error test.
    #exp_sim.usesens = True
    #exp_sim.report_continuously = True
    return exp_mod,exp_sim,N
Exemplo n.º 6
0
mod_pen = Explicit_Problem(rhs, y0, t0)
mod_pen.name = 'Elastic Pendulum with CVode'
"""
Run simulation
"""
#Create solver object
sim_pen = CVode(mod_pen)

#Simulation parameters
#atol = 1.e-6*ones(shape(y0))
atol = rtol * N.array([1, 1, 1, 1])  #default 1e-06
sim_pen.atol = atol
sim_pen.rtol = rtol
sim_pen.maxh = hmax
sim_pen.maxord = maxord
sim_pen.inith = h0

#Simulate
t, y = sim_pen.simulate(tf)
"""
Plot results
"""
#Plot
#P.plot(t,y)
#P.legend(['$x$','$y$','$\dot{x}$','$\dot{y}$'])
#P.title('Elastic pendulum with k = ' + str(k) + ', stretch = ' + str(stretch))
#P.xlabel('time')
#P.ylabel('state')
#show()
P.plot(y[:, 0], y[:, 1])
P.plot(y[0, 0], y[0, 1], 'ro')
Exemplo n.º 7
0
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 "
            )