Beispiel #1
0
 def to_rmg(self):
     return kinetics.ArrheniusEP(
         A=ScalarQuantity(self.a, self.a_units),
         n=self.n,
         alpha=self.alpha,
         E0=ScalarQuantity(self.e0, self.e0_units),
     )
Beispiel #2
0
 def load_yaml(self, path, species, pdep=False):
     """
     Load the all statMech data from the .yml file in `path` into `species`
     `pdep` is a boolean specifying whether or not jobList includes a pressureDependentJob.
     """
     logging.info('Loading statistical mechanics parameters for {0} from .yml file...'.format(species.label))
     with open(path, 'r') as f:
         data = yaml.safe_load(stream=f)
     try:
         if species.label != data['label']:
             logging.debug('Found different labels for species: {0} in input file, and {1} in the .yml file. '
                           'Using the label "{0}" for this species.'.format(species.label, data['label']))
     except KeyError:
         # Lacking label in the YAML file is strange, but accepted
         logging.debug('Did not find label for species {0} in .yml file.'.format(species.label))
     try:
         class_name = data['class']
     except KeyError:
         raise KeyError("Can only make objects if the `class` attribute in the dictionary is known")
     if class_name != 'ArkaneSpecies':
         raise KeyError("Expected a ArkaneSpecies object, but got {0}".format(class_name))
     del data['class']
     class_dict = {'ScalarQuantity': ScalarQuantity,
                   'ArrayQuantity': ArrayQuantity,
                   'Conformer': Conformer,
                   'LinearRotor': LinearRotor,
                   'NonlinearRotor': NonlinearRotor,
                   'KRotor': KRotor,
                   'SphericalTopRotor': SphericalTopRotor,
                   'HinderedRotor': HinderedRotor,
                   'FreeRotor': FreeRotor,
                   'IdealGasTranslation': IdealGasTranslation,
                   'HarmonicOscillator': HarmonicOscillator,
                   'TransportData': TransportData,
                   'SingleExponentialDown': SingleExponentialDown,
                   'Wilhoit': Wilhoit,
                   'NASA': NASA,
                   }
     freq_data = None
     if 'imaginary_frequency' in data:
         freq_data = data['imaginary_frequency']
         del data['imaginary_frequency']
     self.make_object(data=data, class_dict=class_dict)
     if freq_data is not None:
         self.imaginary_frequency = ScalarQuantity()
         self.imaginary_frequency.make_object(data=freq_data, class_dict=dict())
     self.adjacency_list = data['adjacency_list'] if 'adjacency_list' in data else None
     self.inchi = data['inchi'] if 'inchi' in data else None
     self.smiles = data['smiles'] if 'smiles' in data else None
     self.is_ts = data['is_ts'] if 'is_ts' in data else False
     if pdep and not self.is_ts and (self.transport_data is None or self.energy_transfer_model is None):
         raise ValueError('Transport data and an energy transfer model must be given if pressure-dependent '
                          'calculations are requested. Check file {0}'.format(path))
     if pdep and not self.is_ts and self.smiles is None and self.adjacency_list is None \
             and self.inchi is None and self.molecular_weight is None:
         raise ValueError('The molecular weight was not specified, and a structure was not given so it could '
                          'not be calculated. Specify either the molecular weight or structure if '
                          'pressure-dependent calculations are requested. Check file {0}'.format(path))
     logging.debug("Parsed all YAML objects")
Beispiel #3
0
 def to_rmg(self, min_temp, max_temp, min_pressure, max_pressure, *args):
     return kinetics.MultiArrhenius(
         arrhenius=[a.arrhenius.to_rmg() for a in self.arrhenius_set],
         Tmin=ScalarQuantity(min_temp, "K"),
         Tmax=ScalarQuantity(max_temp, "K"),
         Pmin=ScalarQuantity(min_pressure, "Pa"),
         Pmax=ScalarQuantity(max_pressure, "Pa"),
     )
Beispiel #4
0
 def to_rmg(self, min_temp, max_temp, min_pressure, max_pressure, *args):
     return kinetics.ThirdBody(
         arrheniusLow=self.low_arrhenius.to_rmg(),
         Tmin=ScalarQuantity(min_temp, "K"),
         Tmax=ScalarQuantity(max_temp, "K"),
         Pmin=ScalarQuantity(min_pressure, "Pa"),
         Pmax=ScalarQuantity(max_pressure, "Pa"),
     )
Beispiel #5
0
 def to_rmg(self, min_temp, max_temp, min_pressure, max_pressure, *args):
     return kinetics.Chebyshev(
         coeffs=self.coefficient_matrix,
         kunits=self.units,
         Tmin=ScalarQuantity(min_temp, "K"),
         Tmax=ScalarQuantity(max_temp, "K"),
         Pmin=ScalarQuantity(min_pressure, "Pa"),
         Pmax=ScalarQuantity(max_pressure, "Pa"),
     )
Beispiel #6
0
 def to_rmg(self, min_temp, max_temp, min_pressure, max_pressure, *args):
     return kinetics.PDepArrhenius(
         pressures=ArrayQuantity([p.pressure for p in self.pressure_set],
                                 "Pa"),
         arrhenius=[p.arrhenius.to_rmg() for p in self.pressure_set],
         Tmin=ScalarQuantity(min_temp, "K"),
         Tmax=ScalarQuantity(max_temp, "K"),
         Pmin=ScalarQuantity(min_pressure, "Pa"),
         Pmax=ScalarQuantity(max_pressure, "Pa"),
     )
Beispiel #7
0
 def to_rmg(self, min_temp, max_temp, min_pressure, max_pressure, *args):
     return kinetics.MultiPdepArrhenius(
         arrhenius=[
             p.arrhenius.to_rmg() for pda in self.pdep_arrhenius_set
             for p in pda.pressure_set
         ],
         Tmin=ScalarQuantity(min_temp, "K"),
         Tmax=ScalarQuantity(max_temp, "K"),
         Pmin=ScalarQuantity(min_pressure, "Pa"),
         Pmax=ScalarQuantity(max_pressure, "Pa"),
     )
Beispiel #8
0
    def __init__(self,
                 molecule,
                 low_level_hf298,
                 model_chemistry,
                 high_level_hf298=None,
                 source=None):
        """

        Args:
            molecule (Molecule): The RMG Molecule object with connectivity information
            low_level_hf298 (ScalarQuantity): evaluated using a lower level of theory (e.g. DFT)
            model_chemistry (str): Level of theory used to calculate the low level thermo
            high_level_hf298 (ScalarQuantity, optional): evaluated using experimental data
                or a high level of theory that is serving as the "reference" for the isodesmic calculation
            source (str): Literature source from which the high level data was taken
        """
        if isinstance(molecule, Molecule):
            self.molecule = molecule
        else:
            raise ValueError(
                f'ErrorCancelingSpecies molecule attribute must be an rmgpy Molecule object. Instead a '
                f'{type(molecule)} object was given')

        if isinstance(model_chemistry, str):
            self.model_chemistry = model_chemistry
        else:
            raise ValueError(
                f'The model chemistry string used to calculate the low level Hf298 must be provided '
                f'consistency checks. Instead, a {type(model_chemistry)} object was given'
            )

        if not isinstance(low_level_hf298, ScalarQuantity):
            if isinstance(low_level_hf298, tuple):
                low_level_hf298 = ScalarQuantity(*low_level_hf298)
            else:
                raise TypeError(
                    f'Low level Hf298 should be a ScalarQuantity object or its tuple representation, but '
                    f'received {low_level_hf298} instead.')
        self.low_level_hf298 = low_level_hf298

        # If the species is a reference species, then the high level data is already known
        if high_level_hf298 is not None and not isinstance(
                high_level_hf298, ScalarQuantity):
            if isinstance(high_level_hf298, tuple):
                high_level_hf298 = ScalarQuantity(*high_level_hf298)
            else:
                raise TypeError(
                    f'High level Hf298 should be a ScalarQuantity object or its tuple representation, but '
                    f'received {high_level_hf298} instead.')
        self.high_level_hf298 = high_level_hf298
        self.source = source
Beispiel #9
0
def draw_kinetics_plots(rxn_list, path=None, t_min=(300, 'K'), t_max=(3000, 'K'), t_count=50):
    """
    Draws plots of calculated rates and RMG's best values for reaction rates in rxn_list
    `rxn_list` has a .kinetics attribute calculated by ARC and an .rmg_reactions list with RMG rates
    """
    plt.style.use(str('seaborn-talk'))
    t_min = ScalarQuantity(value=t_min[0], units=str(t_min[1]))
    t_max = ScalarQuantity(value=t_max[0], units=str(t_max[1]))
    temperature = np.linspace(t_min.value_si, t_max.value_si, t_count)
    pressure = 1e7  # Pa  (=100 bar)

    pp = None
    if path is not None:
        path = os.path.join(path, str('rate_plots.pdf'))
        if os.path.exists(path):
            os.remove(path)
        pp = PdfPages(path)

    for rxn in rxn_list:
        if rxn.kinetics is not None:
            reaction_order = len(rxn.reactants)
            units = ''
            conversion_factor = {1: 1, 2: 1e6, 3: 1e12}
            if reaction_order == 1:
                units = r' (s$^-1$)'
            elif reaction_order == 2:
                units = r' (cm$^3$/(mol s))'
            elif reaction_order == 3:
                units = r' (cm$^6$/(mol$^2$ s))'
            arc_k = list()
            for t in temperature:
                arc_k.append(rxn.kinetics.getRateCoefficient(t, pressure) * conversion_factor[reaction_order])
            rmg_rxns = list()
            for rmg_rxn in rxn.rmg_reactions:
                rmg_rxn_dict = dict()
                rmg_rxn_dict['rmg_rxn'] = rmg_rxn
                rmg_rxn_dict['t_min'] = rmg_rxn.kinetics.Tmin if rmg_rxn.kinetics.Tmin is not None else t_min
                rmg_rxn_dict['t_max'] = rmg_rxn.kinetics.Tmax if rmg_rxn.kinetics.Tmax is not None else t_max
                k = list()
                temp = np.linspace(rmg_rxn_dict['t_min'].value_si, rmg_rxn_dict['t_max'].value_si, t_count)
                for t in temp:
                    k.append(rmg_rxn.kinetics.getRateCoefficient(t, pressure) * conversion_factor[reaction_order])
                rmg_rxn_dict['k'] = k
                rmg_rxn_dict['T'] = temp
                if rmg_rxn.kinetics.isPressureDependent():
                    rmg_rxn.comment += str(' (at {0} bar)'.format(int(pressure / 1e5)))
                rmg_rxn_dict['label'] = rmg_rxn.comment
                rmg_rxns.append(rmg_rxn_dict)
            _draw_kinetics_plots(rxn.label, arc_k, temperature, rmg_rxns, units, pp)
    pp.close()
Beispiel #10
0
    def calculate_target_enthalpy(self,
                                  n_reactions_max=20,
                                  milp_software=None):
        """
        Perform a multiple error canceling reactions search and calculate hf298 for the target species by taking the
        median hf298 value from among the error canceling reactions found

        Args:
            n_reactions_max (int, optional): The maximum number of found reactions that will returned, after which no
                further searching will occur even if there are possible subsets left in the queue.
            milp_software (list, optional): Solvers to try in order. Defaults to ['lpsolve'] or if pyomo is available
                defaults to ['lpsolve', 'pyomo']. lpsolve is usually faster.

        Returns:
            tuple(ScalarQuantity, list)
            - Standard heat of formation at 298 K calculated for the target species
            - reaction list containing all error canceling reactions found
        """
        reaction_list = self.multiple_error_canceling_reaction_search(
            n_reactions_max, milp_software)
        h298_list = np.zeros(len(reaction_list))

        for i, rxn in enumerate(reaction_list):
            h298_list[i] = rxn.calculate_target_thermo().value_si

        return ScalarQuantity(np.median(h298_list), 'J/mol'), reaction_list
Beispiel #11
0
    def to_rmg(self, min_temp, max_temp, min_pressure, max_pressure,
               efficiencies, *args):
        rmg_efficiencies = {
            e.species.to_rmg(): e.efficiency
            for e in self.efficiency_set.all()
        }

        return kinetics.Lindemann(
            arrheniusHigh=self.high_arrhenius.to_rmg(),
            arrheniusLow=self.low_arrhenius.to_rmg(),
            Tmin=ScalarQuantity(min_temp, "K"),
            Tmax=ScalarQuantity(max_temp, "K"),
            Pmin=ScalarQuantity(min_pressure, "Pa"),
            Pmax=ScalarQuantity(max_pressure, "Pa"),
            efficiencies=rmg_efficiencies,
        )
 def load_yaml(self, path, species, pdep=False):
     """
     Load the all statMech data from the .yml file in `path` into `species`
     `pdep` is a boolean specifying whether or not jobList includes a pressureDependentJob.
     """
     logging.info('Loading statistical mechanics parameters for {0} from .yml file...'.format(species.label))
     with open(path, 'r') as f:
         data = yaml.safe_load(stream=f)
     try:
         if species.label != data['label']:
             logging.debug('Found different labels for species: {0} in input file, and {1} in the .yml file. '
                           'Using the label "{0}" for this species.'.format(species.label, data['label']))
     except KeyError:
         # Lacking label in the YAML file is strange, but accepted
         logging.debug('Did not find label for species {0} in .yml file.'.format(species.label))
     try:
         class_name = data['class']
     except KeyError:
         raise KeyError("Can only make objects if the `class` attribute in the dictionary is known")
     if class_name != 'ArkaneSpecies':
         raise KeyError("Expected a ArkaneSpecies object, but got {0}".format(class_name))
     del data['class']
     class_dict = {'ScalarQuantity': ScalarQuantity,
                   'ArrayQuantity': ArrayQuantity,
                   'Conformer': Conformer,
                   'LinearRotor': LinearRotor,
                   'NonlinearRotor': NonlinearRotor,
                   'KRotor': KRotor,
                   'SphericalTopRotor': SphericalTopRotor,
                   'HinderedRotor': HinderedRotor,
                   'FreeRotor': FreeRotor,
                   'IdealGasTranslation': IdealGasTranslation,
                   'HarmonicOscillator': HarmonicOscillator,
                   'TransportData': TransportData,
                   'SingleExponentialDown': SingleExponentialDown,
                   'Wilhoit': Wilhoit,
                   'NASA': NASA,
                   }
     freq_data = None
     if 'imaginary_frequency' in data:
         freq_data = data['imaginary_frequency']
         del data['imaginary_frequency']
     self.make_object(data=data, class_dict=class_dict)
     if freq_data is not None:
         self.imaginary_frequency = ScalarQuantity()
         self.imaginary_frequency.make_object(data=freq_data, class_dict=dict())
     self.adjacency_list = data['adjacency_list'] if 'adjacency_list' in data else None
     self.inchi = data['inchi'] if 'inchi' in data else None
     self.smiles = data['smiles'] if 'smiles' in data else None
     self.is_ts = data['is_ts'] if 'is_ts' in data else False
     if pdep and not self.is_ts and (self.transport_data is None or self.energy_transfer_model is None):
         raise ValueError('Transport data and an energy transfer model must be given if pressure-dependent '
                          'calculations are requested. Check file {0}'.format(path))
     if pdep and not self.is_ts and self.smiles is None and self.adjacency_list is None\
             and self.inchi is None and self.molecular_weight is None:
         raise ValueError('The molecular weight was not specified, and a structure was not given so it could '
                          'not be calculated. Specify either the molecular weight or structure if '
                          'pressure-dependent calculations are requested. Check file {0}'.format(path))
     logging.debug("Parsed all YAML objects")
Beispiel #13
0
    def calculate_target_thermo(self):
        """
        Estimate the high level thermochemistry for the target species using the error canceling scheme

        Returns:
            rmgpy.quantity.ScalarQuantity: Hf298 in 'J/mol' estimated for the target species
        """
        low_level_h_rxn = sum(spec[0].low_level_hf298.value_si*spec[1] for spec in self.species.items()) - \
            self.target.low_level_hf298.value_si

        target_thermo = sum(spec[0].high_level_hf298.value_si*spec[1] for spec in self.species.items()) - \
            low_level_h_rxn
        return ScalarQuantity(target_thermo, 'J/mol')
Beispiel #14
0
    def _get_petersson_correction(
            self,
            bonds: Dict[str, int] = None,
            datapoint: BACDatapoint = None) -> ScalarQuantity:
        """
        Given the model_chemistry and a dictionary of bonds, return the
        total BAC.

        Args:
            bonds: Dictionary of bonds with the following format:
                bonds = {
                    'C-H': C-H_bond_count,
                    'C-C': C-C_bond_count,
                    'C=C': C=C_bond_count,
                    ...
                }
            datapoint: BACDatapoint instead of bonds.

        Returns:
            Petersson-type bond additivity correction.
        """
        if datapoint is not None:
            if bonds is None:
                bonds = datapoint.bonds
            else:
                logging.warning(
                    f'Species {datapoint.spc.label} will not be used because `bonds` was specified'
                )

        # Sum up corrections for all bonds
        bac = 0.0
        for symbol, count in bonds.items():
            if symbol in self.bacs:
                bac += count * self.bacs[symbol]
            else:
                symbol_flipped = ''.join(
                    re.findall('[a-zA-Z]+|[^a-zA-Z]+',
                               symbol)[::-1])  # Check reversed symbol
                if symbol_flipped in self.bacs:
                    bac += count * self.bacs[symbol_flipped]
                else:
                    logging.warning(
                        f'Bond correction not applied for unknown bond type {symbol}.'
                    )

        return ScalarQuantity(bac, 'kcal/mol')
Beispiel #15
0
    def to_rmg(self, min_temp, max_temp, min_pressure, max_pressure, *args):
        """
        Return an rmgpy.kinetics.Arrhenius object for this rate expression.
        """
        if self.a_delta:
            A = ScalarQuantity(self.a, self.a_units, self.a_delta)
        else:
            A = ScalarQuantity(self.a, self.a_units)
        if self.e_delta:
            Ea = ScalarQuantity(self.e, self.e_units, self.e_delta)
        else:
            Ea = ScalarQuantity(self.e, self.e_units)

        return kinetics.Arrhenius(
            A=A,
            n=self.n,
            Ea=Ea,
            Tmin=ScalarQuantity(min_temp, "K"),
            Tmax=ScalarQuantity(max_temp, "K"),
            Pmin=ScalarQuantity(min_pressure, "Pa"),
            Pmax=ScalarQuantity(max_pressure, "Pa"),
        )
Beispiel #16
0
class ArkaneSpecies(RMGObject):
    """
    A class for archiving an Arkane species including its statmech data into .yml files
    """
    def __init__(self,
                 species=None,
                 conformer=None,
                 author='',
                 level_of_theory='',
                 model_chemistry='',
                 frequency_scale_factor=None,
                 use_hindered_rotors=None,
                 use_bond_corrections=None,
                 atom_energies='',
                 chemkin_thermo_string='',
                 smiles=None,
                 adjacency_list=None,
                 inchi=None,
                 inchi_key=None,
                 xyz=None,
                 molecular_weight=None,
                 symmetry_number=None,
                 transport_data=None,
                 energy_transfer_model=None,
                 thermo=None,
                 thermo_data=None,
                 label=None,
                 datetime=None,
                 RMG_version=None,
                 reactants=None,
                 products=None,
                 reaction_label=None,
                 is_ts=None):
        # reactants/products/reaction_label need to be in the init() to avoid error when loading a TS YAML file,
        # but we don't use them
        if species is None and conformer is None:
            # Expecting to get a species or a TS when generating the object within Arkane,
            # or a conformer when parsing from YAML.
            raise ValueError(
                'No species (or TS) or conformer was passed to the ArkaneSpecies object'
            )
        if conformer is not None:
            self.conformer = conformer
        if label is None and species is not None:
            self.label = species.label
        else:
            self.label = label
        self.author = author
        self.level_of_theory = level_of_theory
        self.model_chemistry = model_chemistry
        self.frequency_scale_factor = frequency_scale_factor
        self.use_hindered_rotors = use_hindered_rotors
        self.use_bond_corrections = use_bond_corrections
        self.atom_energies = atom_energies
        self.xyz = xyz
        self.molecular_weight = molecular_weight
        self.symmetry_number = symmetry_number
        self.is_ts = is_ts if is_ts is not None else isinstance(
            species, TransitionState)
        if not self.is_ts:
            self.chemkin_thermo_string = chemkin_thermo_string
            self.smiles = smiles
            self.adjacency_list = adjacency_list
            self.inchi = inchi
            self.inchi_key = inchi_key
            self.transport_data = transport_data
            self.energy_transfer_model = energy_transfer_model
            self.thermo = thermo
            self.thermo_data = thermo_data
        else:
            # initialize TS-related attributes
            self.imaginary_frequency = None
            self.reaction_label = ''
            self.reactants = list()
            self.products = list()
        if species is not None:
            self.update_species_attributes(species)
        self.RMG_version = RMG_version if RMG_version is not None else __version__
        self.datetime = datetime if datetime is not None else time.strftime(
            "%Y-%m-%d %H:%M")

    def __repr__(self):
        """
        Return a string representation that can be used to reconstruct the object
        """
        result = '{0!r}'.format(self.__class__.__name__)
        result += '{'
        for key, value in self.as_dict().iteritems():
            if key != 'class':
                result += '{0!r}: {1!r}'.format(str(key), str(value))
        result += '}'
        return result

    def update_species_attributes(self, species=None):
        """
        Update the object with a new species/TS (while keeping non-species-dependent attributes unchanged)
        """
        if species is None:
            raise ValueError('No species was passed to ArkaneSpecies')
        self.label = species.label
        if isinstance(species, TransitionState):
            self.imaginary_frequency = species.frequency
            if species.conformer is not None:
                self.conformer = species.conformer
                self.xyz = self.update_xyz_string()
        elif species.molecule is not None and len(species.molecule) > 0:
            self.smiles = species.molecule[0].toSMILES()
            self.adjacency_list = species.molecule[0].toAdjacencyList()
            try:
                inchi = toInChI(species.molecule[0],
                                backend='try-all',
                                aug_level=0)
            except ValueError:
                inchi = ''
            try:
                inchi_key = toInChIKey(species.molecule[0],
                                       backend='try-all',
                                       aug_level=0)
            except ValueError:
                inchi_key = ''
            self.inchi = inchi
            self.inchi_key = inchi_key
            if species.conformer is not None:
                self.conformer = species.conformer
                self.xyz = self.update_xyz_string()
            self.molecular_weight = species.molecularWeight
            if species.symmetryNumber != -1:
                self.symmetry_number = species.symmetryNumber
            if species.transportData is not None:
                self.transport_data = species.transportData  # called `collisionModel` in Arkane
            if species.energyTransferModel is not None:
                self.energy_transfer_model = species.energyTransferModel
            if species.thermo is not None:
                self.thermo = species.thermo.as_dict()
                thermo_data = species.getThermoData()
                h298 = thermo_data.getEnthalpy(298) / 4184.
                s298 = thermo_data.getEntropy(298) / 4.184
                cp = dict()
                for t in [300, 400, 500, 600, 800, 1000, 1500, 2000, 2400]:
                    temp_str = '{0} K'.format(t)
                    cp[temp_str] = '{0:.2f}'.format(
                        thermo_data.getHeatCapacity(t) / 4.184)
                self.thermo_data = {
                    'H298': '{0:.2f} kcal/mol'.format(h298),
                    'S298': '{0:.2f} cal/mol*K'.format(s298),
                    'Cp (cal/mol*K)': cp
                }

    def update_xyz_string(self):
        if self.conformer is not None and self.conformer.number is not None:
            # generate the xyz-format string from the Conformer coordinates
            xyz_string = '{0}\n{1}'.format(len(self.conformer.number.value_si),
                                           self.label)
            for i, coorlist in enumerate(self.conformer.coordinates.value_si):
                for element in elementList:
                    if element.number == int(
                            self.conformer.number.value_si[i]):
                        element_symbol = element.symbol
                        break
                else:
                    raise ValueError(
                        'Could not find element symbol corresponding to atom number {0}'
                        .format(self.conformer.number.value_si[i]))
                xyz_string += '\n{0} {1} {2} {3}'.format(
                    element_symbol, coorlist[0], coorlist[1], coorlist[2])
        else:
            xyz_string = ''
        return xyz_string

    def save_yaml(self, path):
        """
        Save the species with all statMech data to a .yml file
        """
        if not os.path.exists(
                os.path.join(os.path.abspath(path), 'species', '')):
            os.mkdir(os.path.join(os.path.abspath(path), 'species', ''))
        valid_chars = "-_.()<=>+ %s%s" % (string.ascii_letters, string.digits)
        filename = os.path.join(
            'species',
            ''.join(c for c in self.label if c in valid_chars) + '.yml')
        full_path = os.path.join(path, filename)
        with open(full_path, 'w') as f:
            yaml.dump(data=self.as_dict(), stream=f)
        logging.debug('Dumping species {0} data as {1}'.format(
            self.label, filename))

    def load_yaml(self, path, species, pdep=False):
        """
        Load the all statMech data from the .yml file in `path` into `species`
        `pdep` is a boolean specifying whether or not jobList includes a pressureDependentJob.
        """
        logging.info(
            'Loading statistical mechanics parameters for {0} from .yml file...'
            .format(species.label))
        with open(path, 'r') as f:
            data = yaml.safe_load(stream=f)
        try:
            if species.label != data['label']:
                logging.debug(
                    'Found different labels for species: {0} in input file, and {1} in the .yml file. '
                    'Using the label "{0}" for this species.'.format(
                        species.label, data['label']))
        except KeyError:
            # Lacking label in the YAML file is strange, but accepted
            logging.debug(
                'Did not find label for species {0} in .yml file.'.format(
                    species.label))
        try:
            class_name = data['class']
        except KeyError:
            raise KeyError(
                "Can only make objects if the `class` attribute in the dictionary is known"
            )
        if class_name != 'ArkaneSpecies':
            raise KeyError(
                "Expected a ArkaneSpecies object, but got {0}".format(
                    class_name))
        del data['class']
        class_dict = {
            'ScalarQuantity': ScalarQuantity,
            'ArrayQuantity': ArrayQuantity,
            'Conformer': Conformer,
            'LinearRotor': LinearRotor,
            'NonlinearRotor': NonlinearRotor,
            'KRotor': KRotor,
            'SphericalTopRotor': SphericalTopRotor,
            'HinderedRotor': HinderedRotor,
            'FreeRotor': FreeRotor,
            'IdealGasTranslation': IdealGasTranslation,
            'HarmonicOscillator': HarmonicOscillator,
            'TransportData': TransportData,
            'SingleExponentialDown': SingleExponentialDown,
            'Wilhoit': Wilhoit,
            'NASA': NASA,
        }
        freq_data = None
        if 'imaginary_frequency' in data:
            freq_data = data['imaginary_frequency']
            del data['imaginary_frequency']
        self.make_object(data=data, class_dict=class_dict)
        if freq_data is not None:
            self.imaginary_frequency = ScalarQuantity()
            self.imaginary_frequency.make_object(data=freq_data,
                                                 class_dict=dict())
        self.adjacency_list = data[
            'adjacency_list'] if 'adjacency_list' in data else None
        self.inchi = data['inchi'] if 'inchi' in data else None
        self.smiles = data['smiles'] if 'smiles' in data else None
        self.is_ts = data['is_ts'] if 'is_ts' in data else False
        if pdep and not self.is_ts and (self.transport_data is None
                                        or self.energy_transfer_model is None):
            raise ValueError(
                'Transport data and an energy transfer model must be given if pressure-dependent '
                'calculations are requested. Check file {0}'.format(path))
        if pdep and not self.is_ts and self.smiles is None and self.adjacency_list is None\
                and self.inchi is None and self.molecular_weight is None:
            raise ValueError(
                'The molecular weight was not specified, and a structure was not given so it could '
                'not be calculated. Specify either the molecular weight or structure if '
                'pressure-dependent calculations are requested. Check file {0}'
                .format(path))
        logging.debug("Parsed all YAML objects")
Beispiel #17
0
    def load_yaml(self, path, label=None, pdep=False):
        """
        Load the all statMech data from the .yml file in `path` into `species`
        `pdep` is a boolean specifying whether or not job_list includes a pressureDependentJob.
        """
        yml_file = os.path.basename(path)
        if label:
            logging.info(
                'Loading statistical mechanics parameters for {0} from {1} file...'
                .format(label, yml_file))
        else:
            logging.info(
                'Loading statistical mechanics parameters from {0} file...'.
                format(yml_file))
        with open(path, 'r') as f:
            content = f.read()
        content = replace_yaml_syntax(content, label)
        data = yaml.safe_load(stream=content)
        if label:
            # First, warn the user if the label doesn't match
            try:
                if label != data['label']:
                    logging.debug(
                        'Found different labels for species: {0} in input file, and {1} in the .yml file. '
                        'Using the label "{0}" for this species.'.format(
                            label, data['label']))
            except KeyError:
                # Lacking label in the YAML file is strange, but accepted
                logging.debug(
                    'Did not find label for species {0} in .yml file.'.format(
                        label))

            # Then, set the ArkaneSpecies label to the user supplied label
            data['label'] = label
        try:
            class_name = data['class']
        except KeyError:
            raise KeyError(
                "Can only make objects if the `class` attribute in the dictionary is known"
            )
        if class_name != 'ArkaneSpecies':
            raise KeyError(
                "Expected a ArkaneSpecies object, but got {0}".format(
                    class_name))
        del data['class']
        freq_data = None
        if 'imaginary_frequency' in data:
            freq_data = data['imaginary_frequency']
            del data['imaginary_frequency']
        if not data['is_ts']:
            if 'smiles' in data:
                data['species'] = Species(smiles=data['smiles'])
            elif 'adjacency_list' in data:
                data['species'] = Species().from_adjacency_list(
                    data['adjacency_list'])
            elif 'inchi' in data:
                data['species'] = Species(inchi=data['inchi'])
            else:
                raise ValueError(
                    'Cannot load ArkaneSpecies from YAML file {0}. Either `smiles`, `adjacency_list`, or '
                    'InChI must be specified'.format(path))
            # Finally, set the species label so that the special attributes are updated properly
            data['species'].label = data['label']

        self.make_object(data=data, class_dict=ARKANE_CLASS_DICT)
        if freq_data is not None:
            self.imaginary_frequency = ScalarQuantity()
            self.imaginary_frequency.make_object(data=freq_data,
                                                 class_dict=ARKANE_CLASS_DICT)

        if pdep and not self.is_ts and self.smiles is None and self.adjacency_list is None \
                and self.inchi is None and self.molecular_weight is None:
            raise ValueError(
                'The molecular weight was not specified, and a structure was not given so it could '
                'not be calculated. Specify either the molecular weight or structure if '
                'pressure-dependent calculations are requested. Check file {0}'
                .format(path))
        logging.debug("Parsed all YAML objects")
Beispiel #18
0
class ArkaneSpecies(RMGObject):
    """
    A class for archiving an Arkane species including its statmech data into .yml files
    """
    def __init__(self,
                 species=None,
                 conformer=None,
                 author='',
                 level_of_theory='',
                 model_chemistry='',
                 frequency_scale_factor=None,
                 use_hindered_rotors=None,
                 use_bond_corrections=None,
                 atom_energies='',
                 chemkin_thermo_string='',
                 smiles=None,
                 adjacency_list=None,
                 inchi=None,
                 inchi_key=None,
                 xyz=None,
                 molecular_weight=None,
                 symmetry_number=None,
                 transport_data=None,
                 energy_transfer_model=None,
                 thermo=None,
                 thermo_data=None,
                 label=None,
                 datetime=None,
                 RMG_version=None,
                 reactants=None,
                 products=None,
                 reaction_label=None,
                 is_ts=None,
                 charge=None,
                 formula=None,
                 multiplicity=None):
        # reactants/products/reaction_label need to be in the init() to avoid error when loading a TS YAML file,
        # but we don't use them
        super(ArkaneSpecies, self).__init__()
        if species is None and conformer is None:
            # Expecting to get a species or a TS when generating the object within Arkane,
            # or a conformer when parsing from YAML.
            raise ValueError(
                'No species (or TS) or conformer was passed to the ArkaneSpecies object'
            )
        if conformer is not None:
            self.conformer = conformer
        if label is None and species is not None:
            self.label = species.label
        else:
            self.label = label
        self.author = author
        self.level_of_theory = level_of_theory
        self.model_chemistry = model_chemistry
        self.frequency_scale_factor = frequency_scale_factor
        self.use_hindered_rotors = use_hindered_rotors
        self.use_bond_corrections = use_bond_corrections
        self.atom_energies = atom_energies
        self.xyz = xyz
        self.molecular_weight = molecular_weight
        self.symmetry_number = symmetry_number
        self.charge = charge
        self.multiplicity = multiplicity
        self.is_ts = is_ts if is_ts is not None else isinstance(
            species, TransitionState)
        if not self.is_ts:
            self.chemkin_thermo_string = chemkin_thermo_string
            self.smiles = smiles
            self.adjacency_list = adjacency_list
            self.inchi = inchi
            self.inchi_key = inchi_key
            self.transport_data = transport_data
            self.energy_transfer_model = energy_transfer_model
            self.thermo = thermo
            self.thermo_data = thermo_data
            self.formula = formula
        else:
            # initialize TS-related attributes
            self.imaginary_frequency = None
            self.reaction_label = ''
            self.reactants = list()
            self.products = list()
        if species is not None:
            self.update_species_attributes(species)
        self.RMG_version = RMG_version if RMG_version is not None else __version__
        self.datetime = datetime if datetime is not None else time.strftime(
            "%Y-%m-%d %H:%M")

    def __repr__(self):
        """
        Return a string representation that can be used to reconstruct the object
        """
        result = '{0!r}'.format(self.__class__.__name__)
        result += '{'
        for key, value in self.as_dict().items():
            if key != 'class':
                result += '{0!r}: {1!r}'.format(str(key), str(value))
        result += '}'
        return result

    def update_species_attributes(self, species=None):
        """
        Update the object with a new species/TS (while keeping non-species-dependent attributes unchanged)
        """
        if species is None:
            raise ValueError('No species was passed to ArkaneSpecies')
        # Don't overwrite the label if it already exists
        self.label = self.label or species.label
        if isinstance(species, TransitionState):
            self.imaginary_frequency = species.frequency
            if species.conformer is not None:
                self.conformer = species.conformer
                self.xyz = self.update_xyz_string()
        elif species.molecule is not None and len(species.molecule) > 0:
            self.smiles = species.molecule[0].to_smiles()
            self.adjacency_list = species.molecule[0].to_adjacency_list()
            self.charge = species.molecule[0].get_net_charge()
            self.multiplicity = species.molecule[0].multiplicity
            self.formula = species.molecule[0].get_formula()
            try:
                inchi = to_inchi(species.molecule[0],
                                 backend='try-all',
                                 aug_level=0)
            except ValueError:
                inchi = ''
            try:
                inchi_key = to_inchi_key(species.molecule[0],
                                         backend='try-all',
                                         aug_level=0)
            except ValueError:
                inchi_key = ''
            self.inchi = inchi
            self.inchi_key = inchi_key
            if species.conformer is not None:
                self.conformer = species.conformer
                self.xyz = self.update_xyz_string()
            self.molecular_weight = species.molecular_weight
            if species.symmetry_number != -1:
                self.symmetry_number = species.symmetry_number
            if species.transport_data is not None:
                self.transport_data = species.transport_data  # called `collisionModel` in Arkane
            if species.energy_transfer_model is not None:
                self.energy_transfer_model = species.energy_transfer_model
            if species.thermo is not None:
                self.thermo = species.thermo.as_dict()
                data = species.get_thermo_data()
                h298 = data.get_enthalpy(298) / 4184.
                s298 = data.get_entropy(298) / 4.184
                temperatures = np.array(
                    [300, 400, 500, 600, 800, 1000, 1500, 2000, 2400])
                cp = []
                for t in temperatures:
                    cp.append(data.get_heat_capacity(t) / 4.184)

                self.thermo_data = ThermoData(
                    H298=(h298, 'kcal/mol'),
                    S298=(s298, 'cal/(mol*K)'),
                    Tdata=(temperatures, 'K'),
                    Cpdata=(cp, 'cal/(mol*K)'),
                )

    def update_xyz_string(self):
        """
        Generate an xyz string built from self.conformer, and standardize the result

        Returns:
            str: 3D coordinates in an XYZ format.
        """
        xyz_list = list()
        if self.conformer is not None and self.conformer.number is not None:
            # generate the xyz-format string from self.conformer.coordinates and self.conformer.number
            xyz_list.append(str(len(self.conformer.number.value_si)))
            xyz_list.append(self.label)
            for number, coordinate in zip(self.conformer.number.value_si,
                                          self.conformer.coordinates.value_si):
                element_symbol = get_element(int(number)).symbol
                row = '{0:4}'.format(element_symbol)
                row += '{0:14.8f}{1:14.8f}{2:14.8f}'.format(
                    *(coordinate * 1e10).tolist())  # convert m to Angstrom
                xyz_list.append(row)
        return '\n'.join(xyz_list)

    def save_yaml(self, path):
        """
        Save the species with all statMech data to a .yml file
        """
        if not os.path.exists(
                os.path.join(os.path.abspath(path), 'species', '')):
            os.mkdir(os.path.join(os.path.abspath(path), 'species', ''))
        valid_chars = "-_.()<=>+ %s%s" % (string.ascii_letters, string.digits)
        filename = os.path.join(
            'species',
            ''.join(c for c in self.label if c in valid_chars) + '.yml')
        full_path = os.path.join(path, filename)
        with open(full_path, 'w') as f:
            yaml.dump(data=self.as_dict(), stream=f)
        logging.debug('Dumping species {0} data as {1}'.format(
            self.label, filename))

    def load_yaml(self, path, label=None, pdep=False):
        """
        Load the all statMech data from the .yml file in `path` into `species`
        `pdep` is a boolean specifying whether or not job_list includes a pressureDependentJob.
        """
        yml_file = os.path.basename(path)
        if label:
            logging.info(
                'Loading statistical mechanics parameters for {0} from {1} file...'
                .format(label, yml_file))
        else:
            logging.info(
                'Loading statistical mechanics parameters from {0} file...'.
                format(yml_file))
        with open(path, 'r') as f:
            content = f.read()
        content = replace_yaml_syntax(content, label)
        data = yaml.safe_load(stream=content)
        if label:
            # First, warn the user if the label doesn't match
            try:
                if label != data['label']:
                    logging.debug(
                        'Found different labels for species: {0} in input file, and {1} in the .yml file. '
                        'Using the label "{0}" for this species.'.format(
                            label, data['label']))
            except KeyError:
                # Lacking label in the YAML file is strange, but accepted
                logging.debug(
                    'Did not find label for species {0} in .yml file.'.format(
                        label))

            # Then, set the ArkaneSpecies label to the user supplied label
            data['label'] = label
        try:
            class_name = data['class']
        except KeyError:
            raise KeyError(
                "Can only make objects if the `class` attribute in the dictionary is known"
            )
        if class_name != 'ArkaneSpecies':
            raise KeyError(
                "Expected a ArkaneSpecies object, but got {0}".format(
                    class_name))
        del data['class']
        freq_data = None
        if 'imaginary_frequency' in data:
            freq_data = data['imaginary_frequency']
            del data['imaginary_frequency']
        if not data['is_ts']:
            if 'smiles' in data:
                data['species'] = Species(smiles=data['smiles'])
            elif 'adjacency_list' in data:
                data['species'] = Species().from_adjacency_list(
                    data['adjacency_list'])
            elif 'inchi' in data:
                data['species'] = Species(inchi=data['inchi'])
            else:
                raise ValueError(
                    'Cannot load ArkaneSpecies from YAML file {0}. Either `smiles`, `adjacency_list`, or '
                    'InChI must be specified'.format(path))
            # Finally, set the species label so that the special attributes are updated properly
            data['species'].label = data['label']

        self.make_object(data=data, class_dict=ARKANE_CLASS_DICT)
        if freq_data is not None:
            self.imaginary_frequency = ScalarQuantity()
            self.imaginary_frequency.make_object(data=freq_data,
                                                 class_dict=ARKANE_CLASS_DICT)

        if pdep and not self.is_ts and self.smiles is None and self.adjacency_list is None \
                and self.inchi is None and self.molecular_weight is None:
            raise ValueError(
                'The molecular weight was not specified, and a structure was not given so it could '
                'not be calculated. Specify either the molecular weight or structure if '
                'pressure-dependent calculations are requested. Check file {0}'
                .format(path))
        logging.debug("Parsed all YAML objects")
class ArkaneSpecies(RMGObject):
    """
    A class for archiving an Arkane species including its statmech data into .yml files
    """
    def __init__(self, species=None, conformer=None, author='', level_of_theory='', model_chemistry='',
                 frequency_scale_factor=None, use_hindered_rotors=None, use_bond_corrections=None, atom_energies='',
                 chemkin_thermo_string='', smiles=None, adjacency_list=None, inchi=None, inchi_key=None, xyz=None,
                 molecular_weight=None, symmetry_number=None, transport_data=None, energy_transfer_model=None,
                 thermo=None, thermo_data=None, label=None, datetime=None, RMG_version=None, reactants=None,
                 products=None, reaction_label=None, is_ts=None):
        # reactants/products/reaction_label need to be in the init() to avoid error when loading a TS YAML file,
        # but we don't use them
        if species is None and conformer is None:
            # Expecting to get a species or a TS when generating the object within Arkane,
            # or a conformer when parsing from YAML.
            raise ValueError('No species (or TS) or conformer was passed to the ArkaneSpecies object')
        if conformer is not None:
            self.conformer = conformer
        if label is None and species is not None:
            self.label = species.label
        else:
            self.label = label
        self.author = author
        self.level_of_theory = level_of_theory
        self.model_chemistry = model_chemistry
        self.frequency_scale_factor = frequency_scale_factor
        self.use_hindered_rotors = use_hindered_rotors
        self.use_bond_corrections = use_bond_corrections
        self.atom_energies = atom_energies
        self.xyz = xyz
        self.molecular_weight = molecular_weight
        self.symmetry_number = symmetry_number
        self.is_ts = is_ts if is_ts is not None else isinstance(species, TransitionState)
        if not self.is_ts:
            self.chemkin_thermo_string = chemkin_thermo_string
            self.smiles = smiles
            self.adjacency_list = adjacency_list
            self.inchi = inchi
            self.inchi_key = inchi_key
            self.transport_data = transport_data
            self.energy_transfer_model = energy_transfer_model
            self.thermo = thermo
            self.thermo_data = thermo_data
        else:
            # initialize TS-related attributes
            self.imaginary_frequency = None
            self.reaction_label = ''
            self.reactants = list()
            self.products = list()
        if species is not None:
            self.update_species_attributes(species)
        self.RMG_version = RMG_version if RMG_version is not None else __version__
        self.datetime = datetime if datetime is not None else time.strftime("%Y-%m-%d %H:%M")

    def __repr__(self):
        """
        Return a string representation that can be used to reconstruct the object
        """
        result = '{0!r}'.format(self.__class__.__name__)
        result += '{'
        for key, value in self.as_dict().iteritems():
            if key != 'class':
                result += '{0!r}: {1!r}'.format(str(key), str(value))
        result += '}'
        return result

    def update_species_attributes(self, species=None):
        """
        Update the object with a new species/TS (while keeping non-species-dependent attributes unchanged)
        """
        if species is None:
            raise ValueError('No species was passed to ArkaneSpecies')
        self.label = species.label
        if isinstance(species, TransitionState):
            self.imaginary_frequency = species.frequency
            if species.conformer is not None:
                self.conformer = species.conformer
                self.xyz = self.update_xyz_string()
        elif species.molecule is not None and len(species.molecule) > 0:
            self.smiles = species.molecule[0].toSMILES()
            self.adjacency_list = species.molecule[0].toAdjacencyList()
            try:
                inchi = toInChI(species.molecule[0], backend='try-all', aug_level=0)
            except ValueError:
                inchi = ''
            try:
                inchi_key = toInChIKey(species.molecule[0], backend='try-all', aug_level=0)
            except ValueError:
                inchi_key = ''
            self.inchi = inchi
            self.inchi_key = inchi_key
            if species.conformer is not None:
                self.conformer = species.conformer
                self.xyz = self.update_xyz_string()
            self.molecular_weight = species.molecularWeight
            if species.symmetryNumber != -1:
                self.symmetry_number = species.symmetryNumber
            if species.transportData is not None:
                self.transport_data = species.transportData  # called `collisionModel` in Arkane
            if species.energyTransferModel is not None:
                self.energy_transfer_model = species.energyTransferModel
            if species.thermo is not None:
                self.thermo = species.thermo.as_dict()
                thermo_data = species.getThermoData()
                h298 = thermo_data.getEnthalpy(298) / 4184.
                s298 = thermo_data.getEntropy(298) / 4.184
                cp = dict()
                for t in [300,400,500,600,800,1000,1500,2000,2400]:
                    temp_str = '{0} K'.format(t)
                    cp[temp_str] = '{0:.2f}'.format(thermo_data.getHeatCapacity(t) / 4.184)
                self.thermo_data = {'H298': '{0:.2f} kcal/mol'.format(h298),
                                    'S298': '{0:.2f} cal/mol*K'.format(s298),
                                    'Cp (cal/mol*K)': cp}

    def update_xyz_string(self):
        if self.conformer is not None and self.conformer.number is not None:
            # generate the xyz-format string from the Conformer coordinates
            xyz_string = '{0}\n{1}'.format(len(self.conformer.number.value_si), self.label)
            for i, coorlist in enumerate(self.conformer.coordinates.value_si):
                for element in elementList:
                    if element.number == int(self.conformer.number.value_si[i]):
                        element_symbol = element.symbol
                        break
                else:
                    raise ValueError('Could not find element symbol corresponding to atom number {0}'.format(
                        self.conformer.number.value_si[i]))
                xyz_string += '\n{0} {1} {2} {3}'.format(element_symbol,
                                                         coorlist[0],
                                                         coorlist[1],
                                                         coorlist[2])
        else:
            xyz_string = ''
        return xyz_string

    def save_yaml(self, path):
        """
        Save the species with all statMech data to a .yml file
        """
        if not os.path.exists(os.path.join(os.path.abspath(path), 'species', '')):
            os.mkdir(os.path.join(os.path.abspath(path), 'species', ''))
        valid_chars = "-_.()<=>+ %s%s" % (string.ascii_letters, string.digits)
        filename = os.path.join('species',
                                ''.join(c for c in self.label if c in valid_chars) + '.yml')
        full_path = os.path.join(path, filename)
        with open(full_path, 'w') as f:
            yaml.dump(data=self.as_dict(), stream=f)
        logging.debug('Dumping species {0} data as {1}'.format(self.label, filename))

    def load_yaml(self, path, species, pdep=False):
        """
        Load the all statMech data from the .yml file in `path` into `species`
        `pdep` is a boolean specifying whether or not jobList includes a pressureDependentJob.
        """
        logging.info('Loading statistical mechanics parameters for {0} from .yml file...'.format(species.label))
        with open(path, 'r') as f:
            data = yaml.safe_load(stream=f)
        try:
            if species.label != data['label']:
                logging.debug('Found different labels for species: {0} in input file, and {1} in the .yml file. '
                              'Using the label "{0}" for this species.'.format(species.label, data['label']))
        except KeyError:
            # Lacking label in the YAML file is strange, but accepted
            logging.debug('Did not find label for species {0} in .yml file.'.format(species.label))
        try:
            class_name = data['class']
        except KeyError:
            raise KeyError("Can only make objects if the `class` attribute in the dictionary is known")
        if class_name != 'ArkaneSpecies':
            raise KeyError("Expected a ArkaneSpecies object, but got {0}".format(class_name))
        del data['class']
        class_dict = {'ScalarQuantity': ScalarQuantity,
                      'ArrayQuantity': ArrayQuantity,
                      'Conformer': Conformer,
                      'LinearRotor': LinearRotor,
                      'NonlinearRotor': NonlinearRotor,
                      'KRotor': KRotor,
                      'SphericalTopRotor': SphericalTopRotor,
                      'HinderedRotor': HinderedRotor,
                      'FreeRotor': FreeRotor,
                      'IdealGasTranslation': IdealGasTranslation,
                      'HarmonicOscillator': HarmonicOscillator,
                      'TransportData': TransportData,
                      'SingleExponentialDown': SingleExponentialDown,
                      'Wilhoit': Wilhoit,
                      'NASA': NASA,
                      }
        freq_data = None
        if 'imaginary_frequency' in data:
            freq_data = data['imaginary_frequency']
            del data['imaginary_frequency']
        self.make_object(data=data, class_dict=class_dict)
        if freq_data is not None:
            self.imaginary_frequency = ScalarQuantity()
            self.imaginary_frequency.make_object(data=freq_data, class_dict=dict())
        self.adjacency_list = data['adjacency_list'] if 'adjacency_list' in data else None
        self.inchi = data['inchi'] if 'inchi' in data else None
        self.smiles = data['smiles'] if 'smiles' in data else None
        self.is_ts = data['is_ts'] if 'is_ts' in data else False
        if pdep and not self.is_ts and (self.transport_data is None or self.energy_transfer_model is None):
            raise ValueError('Transport data and an energy transfer model must be given if pressure-dependent '
                             'calculations are requested. Check file {0}'.format(path))
        if pdep and not self.is_ts and self.smiles is None and self.adjacency_list is None\
                and self.inchi is None and self.molecular_weight is None:
            raise ValueError('The molecular weight was not specified, and a structure was not given so it could '
                             'not be calculated. Specify either the molecular weight or structure if '
                             'pressure-dependent calculations are requested. Check file {0}'.format(path))
        logging.debug("Parsed all YAML objects")
Beispiel #20
0
    def setUp(self):
        self.np_array = np.array([[1, 2], [3, 4]])
        self.np_dict = {'class': 'np_array', 'object': [[1, 2], [3, 4]]}

        self.array_quantity = ArrayQuantity(value=self.np_array,
                                            units='kJ/mol')
        self.array_dict = {
            'class': 'ArrayQuantity',
            'value': self.np_dict,
            'units': 'kJ/mol'
        }

        self.scalar_quantity = ScalarQuantity(value=500.0, units='K')
        self.scalar_dict = {
            'class': 'ScalarQuantity',
            'value': 500.0,
            'units': 'K'
        }

        self.highly_nested_object = PseudoRMGObject(
            a=PseudoRMGObject(a=PseudoRMGObject(
                b=self.np_array,
                c=PseudoRMGObject(c=self.array_quantity,
                                  d=PseudoRMGObject(a=self.scalar_quantity,
                                                    b=PseudoRMGObject())))),
            b=6)
        self.highly_nest_dictionary = {
            'class': 'PseudoRMGObject',
            'a': {
                'class': 'PseudoRMGObject',
                'a': {
                    'class': 'PseudoRMGObject',
                    'b': self.np_dict,
                    'c': {
                        'class': 'PseudoRMGObject',
                        'c': self.array_dict,
                        'd': {
                            'class': 'PseudoRMGObject',
                            'a': self.scalar_dict,
                            'b': {
                                'class': 'PseudoRMGObject'
                            }
                        }
                    }
                }
            },
            'b': 6
        }

        self.list_of_objects = [
            1, 2.0, 'abc', self.np_array, self.array_quantity,
            self.scalar_quantity, self.highly_nested_object
        ]
        self.list_dict = [
            1, 2.0, 'abc', self.np_dict, self.array_dict, self.scalar_dict,
            self.highly_nest_dictionary
        ]

        self.dictionary_of_objects = {
            'test_int': 1,
            'test_float': 2.0,
            'test_np': self.np_array,
            'test_array': self.array_quantity,
            'test_scalar': self.scalar_quantity,
            'test_nested': self.highly_nested_object
        }
        self.objects_dict = {
            'test_int': 1,
            'test_float': 2.0,
            'test_np': self.np_dict,
            'test_array': self.array_dict,
            'test_scalar': self.scalar_dict,
            'test_nested': self.highly_nest_dictionary
        }

        self.input_dict = {
            'class': 'PseudoRMGObject',
            'a': {
                'class': 'PseudoRMGObject',
                'b': self.np_dict
            }
        }
        self.final_obj_dict = {'a': PseudoRMGObject(b=self.np_array)}

        self.class_dictionary = {
            'np_array': np.array,
            'ScalarQuantity': ScalarQuantity,
            'ArrayQuantity': ArrayQuantity,
            'PseudoRMGObject': PseudoRMGObject
        }
Beispiel #21
0
def process_reactions(database,
                      libraries,
                      families,
                      compare_kinetics=True,
                      show_all=False,
                      filter_aromatic=True):
    """
    Main function to recreate library reactions from families and display the results.
    """
    master_dict = {}
    multiple_dict = {}

    for library_name in libraries:
        library = database.kinetics.libraries[library_name]
        reaction_dict = {}
        for index, entry in library.entries.items():
            lib_rxn = entry.item
            lib_rxn.kinetics = entry.data
            lib_rxn.index = index

            # Let's make RMG try to generate this reaction from the families!
            fam_rxn_list = database.kinetics.generate_reactions_from_families(
                reactants=lib_rxn.reactants,
                products=lib_rxn.products,
                only_families=None if families == 'all' else families,
                resonance=True,
            )

            # Filter by aromatic resonance structures if requested
            if filter_aromatic and len(fam_rxn_list) > 1:
                selected_rxns = []
                max_num_aromatic_reactants = 0
                for fam_rxn in fam_rxn_list:
                    num_aromatic_reactants = 0
                    reactants = fam_rxn.reactants if fam_rxn.is_forward else fam_rxn.products
                    for r in reactants:
                        num_aromatic_reactants += r.molecule[0].is_aromatic()
                    if num_aromatic_reactants > max_num_aromatic_reactants:
                        max_num_aromatic_reactants = num_aromatic_reactants
                        selected_rxns = [fam_rxn]
                    elif num_aromatic_reactants == max_num_aromatic_reactants:
                        selected_rxns.append(fam_rxn)
                    else:
                        continue
                if selected_rxns:
                    fam_rxn_list = selected_rxns

            if len(fam_rxn_list) == 1:
                fam_rxn = fam_rxn_list[0]

                forward = fam_rxn.is_forward

                # Find the labeled atoms using family and reactants & products from fam_rxn
                database.kinetics.families[
                    fam_rxn.family].add_atom_labels_for_reaction(fam_rxn)

                # Replace lib_rxn spcs with fam_rxn spcs to transfer atom labels
                if forward:
                    lib_rxn.reactants = fam_rxn.reactants
                    lib_rxn.products = fam_rxn.products
                    lib_rxn._degeneracy = fam_rxn.degeneracy
                else:
                    lib_rxn.reactants = fam_rxn.products
                    lib_rxn.products = fam_rxn.reactants

                if len(lib_rxn.reactants) == 1:
                    units = 's^-1'
                elif len(lib_rxn.reactants) == 2:
                    units = 'cm^3/(mol*s)'
                elif len(lib_rxn.reactants) == 3:
                    units = 'cm^6/(mol^2*s)'
                A = lib_rxn.kinetics.A
                lib_rxn.kinetics.A = ScalarQuantity(
                    value=A.value_si *
                    A.get_conversion_factor_from_si_to_cm_mol_s(),
                    units=units,
                    uncertainty_type=A.uncertainty_type,
                    uncertainty=A.uncertainty_si *
                    A.get_conversion_factor_from_si_to_cm_mol_s())

                if fam_rxn.family in reaction_dict:
                    reaction_dict[fam_rxn.family].append(lib_rxn)
                else:
                    reaction_dict[fam_rxn.family] = [lib_rxn]

                template = database.kinetics.families[
                    fam_rxn.family].retrieve_template(fam_rxn.template)

                if compare_kinetics:
                    # Check what the current kinetics for this template are
                    new_kinetics = lib_rxn.kinetics
                    old_kinetics = database.kinetics.families[
                        fam_rxn.family].get_kinetics_for_template(
                            template, degeneracy=fam_rxn.degeneracy)[0]
                    # Evaluate kinetics
                    tlistinv = np.linspace(1000 / 1500, 1000 / 300, num=10)
                    tlist = 1000 * np.reciprocal(tlistinv)
                    newklist = np.log10(
                        np.array([
                            new_kinetics.get_rate_coefficient(t) for t in tlist
                        ]))
                    oldklist = np.log10(
                        np.array([
                            old_kinetics.get_rate_coefficient(t) for t in tlist
                        ]))
                    # Create plot
                    plt.cla()
                    plt.plot(tlistinv, newklist, label='New')
                    plt.plot(tlistinv, oldklist, label='Current')
                    plt.legend()
                    plt.xlabel('1000/T')
                    plt.ylabel('log(k)')
                    fig = BytesIO()
                    plt.savefig(fig)
                    fig.seek(0)
                    figdata = b64encode(fig.getvalue()).decode()
                    fig.close()

                # Format output using html
                html = generate_header_html(1, fam_rxn, lib_rxn, library_name,
                                            families)
                html += generate_template_html(fam_rxn, template)
                if compare_kinetics:
                    if not forward:
                        html += [
                            '<tr><th colspan="{0}" style="color:red;text-align:center">'
                            'Note: Training reaction written in opposite direction from reaction family.'
                            '</th></tr>'.format(full)
                        ]
                    html += ['<tr>']
                    html += [
                        '<td colspan="{0}"><strong>New Kinetics:</strong><br>{1}<br><br>'
                        '<strong>Current Kinetics</strong><br>{2}</td>'.format(
                            half, new_kinetics, old_kinetics)
                    ]
                    html += [
                        '<td colspan="{0}"><img src="data:image/png;base64,{1}"></td>'
                        .format(half, figdata)
                    ]
                    html += ['</tr>']
                html += ['</table>']

                display(HTML(''.join(html)))
            elif len(fam_rxn_list) == 0:
                if show_all:
                    html = generate_header_html(0, None, lib_rxn, library_name,
                                                families)
                    html += ['</table>']

                    display(HTML(''.join(html)))
                else:
                    continue
            else:
                # Save results to allow further processing later
                if library_name in multiple_dict:
                    multiple_dict[library_name].append((lib_rxn, fam_rxn_list))
                else:
                    multiple_dict[library_name] = [(lib_rxn, fam_rxn_list)]

                if compare_kinetics:
                    old_kinetics = []

                for i, rxn in enumerate(fam_rxn_list):
                    forward = rxn.is_forward

                    template = database.kinetics.families[
                        rxn.family].retrieve_template(rxn.template)

                    if compare_kinetics:
                        old_kinetics.append(database.kinetics.families[
                            rxn.family].get_kinetics_for_template(
                                template, degeneracy=rxn.degeneracy)[0])

                    if i == 0:
                        html = generate_header_html(2, rxn, lib_rxn,
                                                    library_name, families)

                    html += ['<tr>']
                    html += [
                        '<th colspan="{0}">Match #{1} - For the following resonance form of the reaction:</th>'
                        .format(full, i + 1)
                    ]
                    html += ['</tr><tr>']
                    html += [
                        '<td colspan="{0}"><img src="data:image/png;base64,{1}"></td>'
                        .format(full,
                                b64encode(rxn._repr_png_()).decode())
                    ]
                    html += ['</tr>']
                    html += generate_template_html(rxn, template)

                if compare_kinetics:
                    new_kinetics = lib_rxn.kinetics
                    # Evaluate kinetics
                    tlistinv = np.linspace(1000 / 1500, 1000 / 300, num=10)
                    tlist = 1000 * np.reciprocal(tlistinv)
                    newklist = np.log10(
                        np.array([
                            new_kinetics.get_rate_coefficient(t) for t in tlist
                        ]))
                    oldklist = []
                    for kinetics in old_kinetics:
                        oldklist.append(
                            np.log10(
                                np.array([
                                    kinetics.get_rate_coefficient(t)
                                    for t in tlist
                                ])))
                    # Create plot
                    plt.cla()
                    plt.plot(tlistinv, newklist, label='New')
                    for i, k in enumerate(oldklist):
                        plt.plot(tlistinv, k, label='Match #{0}'.format(i + 1))
                    plt.legend()
                    plt.xlabel('1000/T')
                    plt.ylabel('log(k)')
                    fig = BytesIO()
                    plt.savefig(fig)
                    fig.seek(0)
                    figdata = b64encode(fig.getvalue()).decode()
                    fig.close()

                    if not forward:
                        html += [
                            '<tr><th colspan="{0}" style="color:red;text-align:center">'
                            'Note: Training reaction written in opposite direction from reaction family.'
                            '</tr></tr>'.format(full)
                        ]
                    html += ['<tr><td colspan="{0}">'.format(half)]
                    html += [
                        '<strong>New Kinetics:</strong><br>{0}'.format(
                            new_kinetics)
                    ]
                    for i, kinetics in enumerate(old_kinetics):
                        html += [
                            '<br><br><strong>Match #{0} Kinetics:</strong><br>{1}'
                            .format(i + 1, kinetics)
                        ]
                    html += [
                        '</td><td colspan="{0}"><img src="data:image/png;base64,{1}"></td>'
                        .format(half, figdata)
                    ]
                    html += ['</tr>']

                html += ['</table>']

                display(HTML(''.join(html)))

        # Save results for this library
        if reaction_dict:
            master_dict[library_name] = reaction_dict

    return master_dict, multiple_dict
Beispiel #22
0
    def _get_melius_correction(self,
                               coords: np.ndarray = None,
                               nums: Iterable[int] = None,
                               datapoint: BACDatapoint = None,
                               multiplicity: int = None,
                               params: Dict[str, Union[float, Dict[str, float]]] = None) -> ScalarQuantity:
        """
        Given the level of theory, molecular coordinates, atomic numbers,
        and dictionaries of BAC parameters, return the total BAC.

        Notes:
            A molecular correction term other than 0 destroys the size
            consistency of the quantum chemistry method. This correction
            also requires the multiplicity of the molecule.

            The negative of the total correction described in
            Anantharaman and Melius (JPCA 2005) is returned so that it
            can be added to the energy.

        Args:
            coords: Numpy array of Cartesian atomic coordinates.
            nums: Sequence of atomic numbers.
            datapoint: BACDatapoint instead of molecule.
            multiplicity: Multiplicity of the molecule (not necessary if using datapoint).
            params: Optionally provide parameters other than those stored in self.

        Returns:
            Melius-type bond additivity correction.
        """
        if params is None:
            params = self.bacs
        atom_corr = params['atom_corr']
        bond_corr_length = params['bond_corr_length']
        bond_corr_neighbor = params['bond_corr_neighbor']
        mol_corr = params.get('mol_corr', 0.0)

        # Get single-bonded RMG molecule
        mol = None
        if datapoint is not None:
            if nums is None or coords is None:
                mol = datapoint.to_mol(from_geo=True)
                multiplicity = datapoint.spc.multiplicity  # Use species multiplicity instead
            else:
                logging.warning(
                    f'Species {datapoint.spc.label} will not be used because `nums` and `coords` were specified'
                )
        if mol is None:
            mol = geo_to_mol(coords, nums=nums)

        # Molecular correction
        if mol_corr != 0 and multiplicity is None:
            raise BondAdditivityCorrectionError(f'Missing multiplicity for {mol}')
        bac_mol = mol_corr * self._get_mol_coeff(mol, multiplicity=multiplicity)

        # Atomic correction
        bac_atom = sum(count * atom_corr[symbol] for symbol, count in self._get_atom_counts(mol).items())

        # Bond correction
        bac_length = sum(
            coeff * (bond_corr_length[symbol[0]] * bond_corr_length[symbol[1]]) ** 0.5 if isinstance(symbol, tuple)
            else coeff * bond_corr_length[symbol]
            for symbol, coeff in self._get_length_coeffs(mol).items()
        )
        bac_neighbor = sum(count * bond_corr_neighbor[symbol] for
                           symbol, count in self._get_neighbor_coeffs(mol).items())
        bac_bond = bac_length + bac_neighbor

        # Note the minus sign
        return ScalarQuantity(-(bac_mol + bac_atom + bac_bond), 'kcal/mol')
Beispiel #23
0
    def load_yaml(self, path, label=None, pdep=False):
        """
        Load the all statMech data from the .yml file in `path` into `species`
        `pdep` is a boolean specifying whether or not jobList includes a pressureDependentJob.
        """
        yml_file = os.path.basename(path)
        if label:
            logging.info(
                'Loading statistical mechanics parameters for {0} from {1} file...'
                .format(label, yml_file))
        else:
            logging.info(
                'Loading statistical mechanics parameters from {0} file...'.
                format(yml_file))
        with open(path, 'r') as f:
            data = yaml.safe_load(stream=f)
        if label:
            # First, warn the user if the label doesn't match
            try:
                if label != data['label']:
                    logging.debug(
                        'Found different labels for species: {0} in input file, and {1} in the .yml file. '
                        'Using the label "{0}" for this species.'.format(
                            label, data['label']))
            except KeyError:
                # Lacking label in the YAML file is strange, but accepted
                logging.debug(
                    'Did not find label for species {0} in .yml file.'.format(
                        label))

            # Then, set the ArkaneSpecies label to the user supplied label
            data['label'] = label
        try:
            class_name = data['class']
        except KeyError:
            raise KeyError(
                "Can only make objects if the `class` attribute in the dictionary is known"
            )
        if class_name != 'ArkaneSpecies':
            raise KeyError(
                "Expected a ArkaneSpecies object, but got {0}".format(
                    class_name))
        del data['class']
        class_dict = {
            'ScalarQuantity': ScalarQuantity,
            'ArrayQuantity': ArrayQuantity,
            'Conformer': Conformer,
            'LinearRotor': LinearRotor,
            'NonlinearRotor': NonlinearRotor,
            'KRotor': KRotor,
            'SphericalTopRotor': SphericalTopRotor,
            'HinderedRotor': HinderedRotor,
            'FreeRotor': FreeRotor,
            'IdealGasTranslation': IdealGasTranslation,
            'HarmonicOscillator': HarmonicOscillator,
            'TransportData': TransportData,
            'SingleExponentialDown': SingleExponentialDown,
            'Wilhoit': Wilhoit,
            'NASA': NASA,
            'NASAPolynomial': NASAPolynomial,
            'ThermoData': ThermoData,
            'np_array': numpy.array,
        }
        freq_data = None
        if 'imaginary_frequency' in data:
            freq_data = data['imaginary_frequency']
            del data['imaginary_frequency']
        if not data['is_ts']:
            if 'smiles' in data:
                data['species'] = Species(SMILES=data['smiles'])
            elif 'adjacency_list' in data:
                data['species'] = Species().fromAdjacencyList(
                    data['adjacency_list'])
            elif 'inchi' in data:
                data['species'] = Species(InChI=data['inchi'])
            else:
                raise ValueError(
                    'Cannot load ArkaneSpecies from YAML file {0}. Either `smiles`, `adjacency_list`, or '
                    'InChI must be specified'.format(path))
            # Finally, set the species label so that the special attributes are updated properly
            data['species'].label = data['label']

        self.make_object(data=data, class_dict=class_dict)
        if freq_data is not None:
            self.imaginary_frequency = ScalarQuantity()
            self.imaginary_frequency.make_object(data=freq_data,
                                                 class_dict=class_dict)

        if pdep and not self.is_ts and (self.transport_data is None
                                        or self.energy_transfer_model is None):
            raise ValueError(
                'Transport data and an energy transfer model must be given if pressure-dependent '
                'calculations are requested. Check file {0}'.format(path))
        if pdep and not self.is_ts and self.smiles is None and self.adjacency_list is None \
                and self.inchi is None and self.molecular_weight is None:
            raise ValueError(
                'The molecular weight was not specified, and a structure was not given so it could '
                'not be calculated. Specify either the molecular weight or structure if '
                'pressure-dependent calculations are requested. Check file {0}'
                .format(path))
        logging.debug("Parsed all YAML objects")