Ejemplo n.º 1
0
class ObservablesTestCase(object):
    """
    We use this class to run regressive tests

    ======================= ==============================================================================================
    Attribute               Description
    ======================= ==============================================================================================
    `title`                 A string describing the test. For regressive tests, should be same as example's name.
    `old_dir`               A directory path containing the chem_annotated.inp and species_dictionary.txt of the old model
    `new_dir`               A directory path containing the chem_annotated.inp and species_dictionary.txt of the new model
    `conditions`            A list of the :class: 'CanteraCondition' objects describing reaction conditions
    `observables`           A dictionary of observables
                            key: 'species', value: a list of the "class" 'Species' that correspond with species mole fraction observables
                            key: 'variable', value: a list of state variable observables, i.e. ['Temperature'] or ['Temperature','Pressure']
                            key: 'ignitionDelay', value: a tuple containing (ignition metric, y_var)
                                                         for example: ('maxDerivative','P')
                                                                      ('maxHalfConcentration', '[OH]')
                                                                      ('maxSpeciesConcentrations',['[CH2]','[O]'])
                                                        see find_ignition_delay function for more details
    'expt_data'             An array of GenericData objects
    'ck2tci'                Indicates whether to convert chemkin to cti mechanism.  If set to False, RMG will convert the species
                            and reaction objects to Cantera objects internally
    ======================= ==============================================================================================


    """
    def __init__(self,
                 title='',
                 old_dir='',
                 new_dir='',
                 observables=None,
                 expt_data=None,
                 ck2cti=True):
        self.title = title
        self.new_dir = new_dir
        self.old_dir = old_dir
        self.conditions = None
        self.expt_data = expt_data if expt_data else []
        self.observables = observables if observables else {}

        # Detect if the transport file exists
        old_transport_path = None
        if os.path.exists(os.path.join(old_dir, 'tran.dat')):
            old_transport_path = os.path.join(old_dir, 'tran.dat')
        new_transport_path = None
        if os.path.exists(os.path.join(new_dir, 'tran.dat')):
            new_transport_path = os.path.join(new_dir, 'tran.dat')

        # load the species and reactions from each model
        old_species_list, old_reaction_list = load_chemkin_file(
            os.path.join(old_dir, 'chem_annotated.inp'),
            os.path.join(old_dir, 'species_dictionary.txt'),
            old_transport_path)

        new_species_list, new_reaction_list = load_chemkin_file(
            os.path.join(new_dir, 'chem_annotated.inp'),
            os.path.join(new_dir, 'species_dictionary.txt'),
            new_transport_path)

        self.old_sim = Cantera(species_list=old_species_list,
                               reaction_list=old_reaction_list,
                               output_directory=old_dir)
        self.new_sim = Cantera(species_list=new_species_list,
                               reaction_list=new_reaction_list,
                               output_directory=new_dir)

        # load each chemkin file into the cantera model
        if not ck2cti:
            self.old_sim.load_model()
            self.new_sim.load_model()
        else:
            self.old_sim.load_chemkin_model(os.path.join(
                old_dir, 'chem_annotated.inp'),
                                            transport_file=old_transport_path,
                                            quiet=True)
            self.new_sim.load_chemkin_model(os.path.join(
                new_dir, 'chem_annotated.inp'),
                                            transport_file=new_transport_path,
                                            quiet=True)

    def __str__(self):
        """
        Return a string representation of this test case, using its title'.
        """
        return 'Observables Test Case: {0}'.format(self.title)

    def generate_conditions(self,
                            reactor_type_list,
                            reaction_time_list,
                            mol_frac_list,
                            Tlist=None,
                            Plist=None,
                            Vlist=None):
        """
        Creates a list of conditions from from the lists provided. 
        
        ======================= ====================================================
        Argument                Description
        ======================= ====================================================
        `reactor_type_list`     A list of strings of the cantera reactor type. List of supported types below:
            IdealGasReactor: A constant volume, zero-dimensional reactor for ideal gas mixtures
            IdealGasConstPressureReactor: A homogeneous, constant pressure, zero-dimensional reactor for ideal gas mixtures

        `reaction_time_list`    A tuple object giving the ([list of reaction times], units)
        `mol_frac_list`         A list of molfrac dictionaries with species object keys
                                and mole fraction values
        To specify the system for an ideal gas, you must define 2 of the following 3 parameters:
        `T0List`                A tuple giving the ([list of initial temperatures], units)
        'P0List'                A tuple giving the ([list of initial pressures], units)
        'V0List'                A tuple giving the ([list of initial specific volumes], units)
        
        This saves all the reaction conditions into both the old and new cantera jobs.
        """
        # Store the conditions in the observables test case, for bookkeeping
        self.conditions = generate_cantera_conditions(reactor_type_list,
                                                      reaction_time_list,
                                                      mol_frac_list,
                                                      Tlist=Tlist,
                                                      Plist=Plist,
                                                      Vlist=Vlist)

        # Map the mole fractions dictionaries to species objects from the old and new models
        old_mol_frac_list = []
        new_mol_frac_list = []

        for mol_frac in mol_frac_list:
            old_condition = {}
            new_condition = {}
            old_species_dict = get_rmg_species_from_user_species(
                list(mol_frac.keys()), self.old_sim.species_list)
            new_species_dict = get_rmg_species_from_user_species(
                list(mol_frac.keys()), self.new_sim.species_list)
            for smiles, molfrac in mol_frac.items():
                if old_species_dict[smiles] is None:
                    raise Exception(
                        'SMILES {0} was not found in the old model!'.format(
                            smiles))
                if new_species_dict[smiles] is None:
                    raise Exception(
                        'SMILES {0} was not found in the new model!'.format(
                            smiles))

                old_condition[old_species_dict[smiles]] = molfrac
                new_condition[new_species_dict[smiles]] = molfrac
            old_mol_frac_list.append(old_condition)
            new_mol_frac_list.append(new_condition)

        # Generate the conditions in each simulation
        self.old_sim.generate_conditions(reactor_type_list,
                                         reaction_time_list,
                                         old_mol_frac_list,
                                         Tlist=Tlist,
                                         Plist=Plist,
                                         Vlist=Vlist)
        self.new_sim.generate_conditions(reactor_type_list,
                                         reaction_time_list,
                                         new_mol_frac_list,
                                         Tlist=Tlist,
                                         Plist=Plist,
                                         Vlist=Vlist)

    def compare(self, tol, plot=False):
        """
        Compare the old and new model
        'tol':  average error acceptable between old and new model for variables
        `plot`: if set to True, it will comparison plots of the two models comparing their species.

        Returns a list of variables failed in a list of tuples in the format:
        
        (CanteraCondition, variable label, variable_old, variable_new)

        """
        # Ignore Inerts
        inert_list = ['[Ar]', '[He]', '[N#N]', '[Ne]']

        old_condition_data, new_condition_data = self.run_simulations()

        conditions_broken = []
        variables_failed = []

        print('')
        print('{0} Comparison'.format(self))
        print('================')
        # Check the species profile observables
        if 'species' in self.observables:
            old_species_dict = get_rmg_species_from_user_species(
                self.observables['species'], self.old_sim.species_list)
            new_species_dict = get_rmg_species_from_user_species(
                self.observables['species'], self.new_sim.species_list)

        # Check state variable observables
        implemented_variables = ['temperature', 'pressure']
        if 'variable' in self.observables:
            for item in self.observables['variable']:
                if item.lower() not in implemented_variables:
                    print('Observable variable {0} not yet implemented'.format(
                        item))

        fail_header = '\nThe following observables did not match:\n'
        fail_header_printed = False
        for i in range(len(old_condition_data)):
            time_old, data_list_old, reaction_sensitivity_data_old = old_condition_data[
                i]
            time_new, data_list_new, reaction_sensitivity_data_new = new_condition_data[
                i]

            # Compare species observables
            if 'species' in self.observables:
                smiles_list = [
                ]  # This is to make sure we don't have species with duplicate smiles
                multiplicity_list = ['', '(S)', '(D)', '(T)',
                                     '(Q)']  # list ot add multiplcity
                for species in self.observables['species']:

                    smiles = species.molecule[0].to_smiles(
                    )  # For purpose of naming the plot only
                    if smiles in smiles_list:
                        smiles = smiles + multiplicity_list[
                            species.molecule[0].multiplicity]
                    smiles_list.append(smiles)

                    fail = False
                    old_rmg_species = old_species_dict[species]
                    new_rmg_species = new_species_dict[species]

                    if old_rmg_species:
                        variable_old = next(
                            (data for data in data_list_old
                             if data.species == old_rmg_species), None)
                    else:
                        print(
                            'No RMG species found for observable species {0} in old model.'
                            .format(smiles))
                        fail = True
                    if new_rmg_species:
                        variable_new = next(
                            (data for data in data_list_new
                             if data.species == new_rmg_species), None)
                    else:
                        print(
                            'No RMG species found for observable species {0} in new model.'
                            .format(smiles))
                        fail = True

                    if fail is False:
                        if not curves_similar(time_old.data, variable_old.data,
                                              time_new.data, variable_new.data,
                                              tol):
                            fail = True

                        # Try plotting only when species are found in both models
                        if plot:
                            old_species_plot = SimulationPlot(
                                x_var=time_old, y_var=variable_old)
                            new_species_plot = SimulationPlot(
                                x_var=time_new, y_var=variable_new)
                            old_species_plot.compare_plot(
                                new_species_plot,
                                title='Observable Species {0} Comparison'.
                                format(smiles),
                                ylabel='Mole Fraction',
                                filename='condition_{0}_species_{1}.png'.
                                format(i + 1, smiles))

                    # Append to failed variables or conditions if this test failed
                    if fail:
                        if not fail_header_printed:
                            print(fail_header)
                            fail_header_printed = True
                        if i not in conditions_broken:
                            conditions_broken.append(i)
                        print(
                            "Observable species {0} varied by more than {1:.3f} on average between old model {2} and "
                            "new model {3} in condition {4:d}.".format(
                                smiles, tol, variable_old.label,
                                variable_new.label, i + 1))
                        variables_failed.append((self.conditions[i], smiles,
                                                 variable_old, variable_new))

            # Compare state variable observables
            if 'variable' in self.observables:
                for varName in self.observables['variable']:
                    variable_old = next(
                        (data
                         for data in data_list_old if data.label == varName),
                        None)
                    variable_new = next(
                        (data
                         for data in data_list_new if data.label == varName),
                        None)
                    if not curves_similar(time_old.data, variable_old.data,
                                          time_new.data, variable_new.data,
                                          0.05):
                        if i not in conditions_broken:
                            conditions_broken.append(i)
                        if not fail_header_printed:
                            fail_header_printed = True
                            print(fail_header)

                        print(
                            "Observable variable {0} varied by more than {1:.3f} on average between old model and "
                            "new model in condition {2:d}.".format(
                                variable_old.label, tol, i + 1))
                        variables_failed.append((self.conditions[i], varName,
                                                 variable_old, variable_new))

                    if plot:
                        old_var_plot = GenericPlot(x_var=time_old,
                                                   y_var=variable_old)
                        new_var_plot = GenericPlot(x_var=time_new,
                                                   y_var=variable_new)
                        old_var_plot.compare_plot(
                            new_var_plot,
                            title='Observable Variable {0} Comparison'.format(
                                varName),
                            filename='condition_{0}_variable_{1}.png'.format(
                                i + 1, varName))

            # Compare ignition delay observables
            if 'ignitionDelay' in self.observables:
                print(
                    'Ignition delay observable comparison not implemented yet.'
                )

        if fail_header_printed:
            print('')
            print(
                'The following reaction conditions were had some discrepancies:'
            )
            print('')
            for index in conditions_broken:
                print("Condition {0:d}:".format(index + 1))
                print(str(self.conditions[index]))
                print('')

            return variables_failed
        else:
            print('')
            print(
                'All Observables varied by less than {0:.3f} on average between old model and '
                'new model in all conditions!'.format(tol))
            print('')

    def run_simulations(self):
        """
        Run a selection of conditions in Cantera and return
        generic data objects containing the time, pressure, temperature,
        and mole fractions from the simulations.

        Returns (old_condition_data, new_condition_data)
        where conditionData is a list of of tuples: (time, dataList) for each condition in the same order as conditions
        time is a GenericData object which gives the time domain for each profile
        dataList is a list of GenericData objects for the temperature, profile, and mole fraction of major species
        """
        old_condition_data = self.old_sim.simulate()
        new_condition_data = self.new_sim.simulate()
        return (old_condition_data, new_condition_data)