def to_dict(self): """Represents object as dictionary with JSON-accepted datatypes Returns ------- obj_dict : dict """ obj_dict = { 'class': str(self.__class__), 'name': self.name, 'trans_model': self.trans_model.to_dict(), 'vib_model': self.vib_model.to_dict(), 'rot_model': self.rot_model.to_dict(), 'elec_model': self.elec_model.to_dict(), 'nucl_model': self.nucl_model.to_dict(), 'smiles': self.smiles, 'notes': self.notes } if _is_iterable(self.mix_models): obj_dict['mix_models'] = \ [mix_model.to_dict() for mix_model in self.mix_models] else: obj_dict['mix_models'] = self.mix_models return obj_dict
def to_dict(self): """Represents object as dictionary with JSON-accepted datatypes Returns ------- obj_dict : dict """ obj_dict = { 'class': str(self.__class__), 'type': 'empiricalbase', 'name': self.name, 'phase': self.phase, 'elements': self.elements, 'notes': self.notes, 'smiles': self.smiles, } try: obj_dict['references'] = self.references.to_dict() except AttributeError: obj_dict['references'] = self.references try: obj_dict['statmech_model'] = self.statmech_model.to_dict() except AttributeError: obj_dict['statmech_model'] = self.statmech_model if _is_iterable(self.mix_models): obj_dict['mix_models'] = \ [mix_model.to_dict() for mix_model in self.mix_models] else: obj_dict['mix_models'] = self.mix_models return obj_dict
def __init__(self, name=None, phase=None, elements=None, statmech_model=None, references=None, mix_models=None, smiles=None, notes=None, **kwargs): self.name = name self.phase = phase self.elements = elements self.references = references self.smiles = smiles self.notes = notes # Assign self.statmech_model if inspect.isclass(statmech_model): # If you're passing a class. Note that the required # arguments will be guessed. self.statmech_model = statmech_model(**kwargs) else: # If it's an object that has already been initialized self.statmech_model = statmech_model # Assign mixing models # TODO Mixing models can not be initialized by passing the class # because all the models will have the same attributes. Figure out a # way to pass them. Perhaps have a dictionary that contains the # attributes separated by species if not _is_iterable(mix_models) and mix_models is not None: mix_models = [mix_models] self.mix_models = mix_models
def __init__(self, name=None, trans_model=EmptyMode(), vib_model=EmptyMode(), rot_model=EmptyMode(), elec_model=EmptyMode(), nucl_model=EmptyMode(), mix_models=None, smiles=None, notes=None, **kwargs): self.name = name self.smiles = smiles self.notes = notes # Translational modes if inspect.isclass(trans_model): self.trans_model = _pass_expected_arguments(trans_model, **kwargs) else: self.trans_model = trans_model # Vibrational modes if inspect.isclass(vib_model): self.vib_model = _pass_expected_arguments(vib_model, **kwargs) else: self.vib_model = vib_model # Rotational modes if inspect.isclass(rot_model): self.rot_model = _pass_expected_arguments(rot_model, **kwargs) else: self.rot_model = rot_model # Electronic modes if inspect.isclass(elec_model): self.elec_model = _pass_expected_arguments(elec_model, **kwargs) else: self.elec_model = elec_model # Nuclear modes if inspect.isclass(nucl_model): self.nucl_model = _pass_expected_arguments(nucl_model, **kwargs) else: self.nucl_model = nucl_model # Assign mixing models # TODO Mixing models can not be initialized by passing the class # because all the models will have the same attributes. Figure out # a way to pass them. Perhaps have a dictionary that contains the # attributes separated by species if not _is_iterable(mix_models) and mix_models is not None: mix_models = [mix_models] self.mix_models = mix_models
def get_CpoR(self, T, raise_error=True, raise_warning=True, **kwargs): """Calculate the dimensionless heat capacity Parameters ---------- T : float or (N,) `numpy.ndarray`_ Temperature(s) in K raise_error : bool, optional If True, raises an error if any of the modes do not have the quantity of interest. Default is True raise_warning : bool, optional Only relevant if raise_error is False. Raises a warning if any of the modes do not have the quantity of interest. Default is True kwargs : key-word arguments Arguments to calculate mixture model properties, if any Returns ------- CpoR : float or (N,) `numpy.ndarray`_ Dimensionless heat capacity .. _`numpy.ndarray`: https://docs.scipy.org/doc/numpy-1.14.0/reference/generated/numpy.ndarray.html """ # Convert T to 1D numpy format if not _is_iterable(T): T = [T] T = np.array(T) # Calculate pure properties CpoR = get_shomate_CpoR(a=self.a, T=T) # Calculate mixing properties for T_i in T: CpoR_mix = _get_mix_quantity(mix_models=self.mix_models, method_name='get_CpoR', raise_error=raise_error, raise_warning=raise_warning, default_value=0., T=T_i, **kwargs) # Add mixing quantity in appropriate format if len(T) == 1: CpoR += CpoR_mix[0] else: CpoR += CpoR_mix return CpoR
def get_SoR(self, T, raise_error=True, raise_warning=True, **kwargs): """Calculate the dimensionless entropy Parameters ---------- T : float or (N,) `numpy.ndarray`_ Temperature(s) in K raise_error : bool, optional If True, raises an error if any of the modes do not have the quantity of interest. Default is True raise_warning : bool, optional Only relevant if raise_error is False. Raises a warning if any of the modes do not have the quantity of interest. Default is True kwargs : key-word arguments Arguments to calculate mixture model properties, if any Returns ------- SoR : float or (N,) `numpy.ndarray`_ Dimensionless entropy .. _`numpy.ndarray`: https://docs.scipy.org/doc/numpy-1.14.0/reference/generated/numpy.ndarray.html """ if _is_iterable(T): SoR = np.zeros_like(T) for i, T_i in enumerate(T): a = self.get_a(T=T_i) SoR[i] = get_nasa_SoR(a=a, T=T_i) \ + np.sum(_get_mix_quantity(mix_models=self.mix_models, method_name='get_SoR', raise_error=raise_error, raise_warning=raise_warning, default_value=0., T=T_i, **kwargs)) else: a = self.get_a(T=T) SoR = get_nasa_SoR(a=a, T=T) \ + np.sum(_get_mix_quantity(mix_models=self.mix_models, method_name='get_SoR', raise_error=raise_error, raise_warning=raise_warning, default_value=0., T=T, **kwargs)) return SoR
def _shomate_CpoR(T, A, B, C, D, E): """ Helper function to fit shomate heat capacity. Paramters --------- T - float Temperature in K A, B, C, D, E - float Shomate parameters Returns ------- CpoR - float Dimensionless heat capacity """ a = np.array([A, B, C, D, E, 0., 0., 0.]) if not _is_iterable(T): T = [T] T = np.array(T) return get_shomate_CpoR(a=a, T=T)
def _fit_CpoR(T, CpoR, T_mid=None): """Fit a[0]-a[4] coefficients in a_low and a_high attributes given the dimensionless heat capacity data Parameters ---------- T : (N,) `numpy.ndarray`_ Temperatures in K CpoR : (N,) `numpy.ndarray`_ Dimensionless heat capacity T_mid : float or iterable of float, optional Guess for T_mid. If float, only uses that value for T_mid. If list, finds the best fit for each element in the list. If None, a range of T_mid values are screened between the lowest value and highest value of T. Returns ------- a_low : (7,) `numpy.ndarray`_ Lower coefficients of NASA polynomial a_high : (7,) `numpy.ndarray`_ Higher coefficients of NASA polynomial T_mid : float Temperature in K used to split the CpoR data .. _`numpy.ndarray`: https://docs.scipy.org/doc/numpy-1.14.0/reference/generated/numpy.ndarray.html """ # If the Cp/R does not vary with temperature (occurs when no # vibrational frequencies are listed), return default values if (np.isclose(np.mean(CpoR), 0.) and np.isnan(variation(CpoR))) \ or np.isclose(variation(CpoR), 0.) \ or any([np.isnan(x) for x in CpoR]): T_mid = T[int(len(T) / 2)] a_low = np.zeros(7) a_high = np.zeros(7) return a_low, a_high, T_mid # If T_mid not specified, generate range between 6th smallest data point # and 6th largest data point if T_mid is None: T_mid = T[5:-5] # If a single value for T_mid is chosen, convert to a tuple if not _is_iterable(T_mid): T_mid = (T_mid, ) # Initialize parameters for T_mid optimization mse_list = [] prev_mse = np.inf all_a_low = [] all_a_high = [] for T_m in T_mid: # Generate temperature data (mse, a_low, a_high) = _get_CpoR_MSE(T=T, CpoR=CpoR, T_mid=T_m) mse_list.append(mse) all_a_low.append(a_low) all_a_high.append(a_high) # Check if the optimum T_mid has been found by determining if the # fit MSE value for the current T_mid is higher than the previous # indicating that subsequent guesses will not improve the fit if mse > prev_mse: break prev_mse = mse # Select the optimum T_mid based on the highest fit R2 value min_mse = min(mse_list) min_i = np.where(min_mse == mse_list)[0][0] T_mid_out = T_mid[min_i] a_low_rev = all_a_low[min_i] a_high_rev = all_a_high[min_i] # Reverse array and append two zeros to end empty_arr = np.zeros(2) a_low_out = np.concatenate((a_low_rev[::-1], empty_arr)) a_high_out = np.concatenate((a_high_rev[::-1], empty_arr)) return a_low_out, a_high_out, T_mid_out
def from_statmech(cls, name, statmech_model, T_low, T_high, T_mid=None, references=None, elements=None, **kwargs): """Calculates the NASA polynomials using statistical mechanic models Parameters ---------- name : str Name of the species statmech_model : `pMuTT.statmech.StatMech` object or class Statistical Mechanics model to generate data T_low : float Lower limit temerature in K T_high : float Higher limit temperature in K T_mid : float or iterable of float, optional Guess for T_mid. If float, only uses that value for T_mid. If list, finds the best fit for each element in the list. If None, a range of T_mid values are screened between the 6th lowest and 6th highest value of T. references : `pMuTT.empirical.references.References` object Reference to adjust enthalpy elements : dict Composition of the species. Keys of dictionary are elements, values are stoichiometric values in a formula unit. e.g. CH3OH can be represented as: {'C': 1, 'H': 4, 'O': 1,}. kwargs : keyword arguments Used to initalize ``statmech_model`` or ``EmpiricalBase`` attributes to be stored. Returns ------- Nasa : Nasa object Nasa object with polynomial terms fitted to data. """ # Initialize the StatMech object if inspect.isclass(statmech_model): statmech_model = statmech_model(**kwargs) # Generate data T = np.linspace(T_low, T_high) if T_mid is not None: # Check to see if specified T_mid's are in T and, if not, # insert them into T. # If a single value for T_mid is chosen, convert to a tuple if not _is_iterable(T_mid): T_mid = (T_mid, ) for x in range(0, len(T_mid)): if np.where(T == T_mid[x])[0].size == 0: # Insert T_mid's into T and save position Ts_index = np.where(T > T_mid[x])[0][0] T = np.insert(T, Ts_index, T_mid[x]) CpoR = np.array([statmech_model.get_CpoR(T=T_i) for T_i in T]) T_ref = c.T0('K') HoRT_ref = statmech_model.get_HoRT(T=T_ref) # Add contribution of references if references is not None: descriptor_name = references.descriptor if descriptor_name == 'elements': descriptors = elements else: descriptors = kwargs[descriptor_name] HoRT_ref += references.get_HoRT_offset(descriptors=descriptors, T=T_ref) SoR_ref = statmech_model.get_SoR(T=T_ref) return cls.from_data(name=name, T=T, CpoR=CpoR, T_ref=T_ref, HoRT_ref=HoRT_ref, SoR_ref=SoR_ref, T_mid=T_mid, statmech_model=statmech_model, elements=elements, references=references, **kwargs)