예제 #1
0
파일: __init__.py 프로젝트: alongd/pMuTT
    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['model'] = self.model.to_dict()
        except AttributeError:
            obj_dict['model'] = self.model

        if _is_iterable(self.misc_models):
            obj_dict['misc_models'] = \
                    [misc_model.to_dict() for misc_model in self.misc_models]
        else:
            obj_dict['misc_models'] = self.misc_models
        return obj_dict
예제 #2
0
파일: __init__.py 프로젝트: wittregr/pMuTT
    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.misc_models):
            obj_dict['misc_models'] = \
                    [mix_model.to_dict() for mix_model in self.misc_models]
        else:
            obj_dict['misc_models'] = self.misc_models

        try:
            obj_dict['references'] = self.references.to_dict()
        except AttributeError:
            obj_dict['references'] = self.references

        return obj_dict
예제 #3
0
    def __init__(self,
                 name=None,
                 phase=None,
                 elements=None,
                 statmech_model=None,
                 references=None,
                 misc_models=None,
                 smiles=None,
                 notes=None,
                 **kwargs):
        self.name = name
        self.phase = phase
        self.elements = elements
        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(misc_models) and misc_models is not None:
            misc_models = [misc_models]
        self.misc_models = misc_models
예제 #4
0
 def test_is_iterable(self):
     self.assertTrue(pmutt._is_iterable(list()))
     self.assertTrue(pmutt._is_iterable(tuple()))
     self.assertTrue(pmutt._is_iterable(set()))
     self.assertTrue(pmutt._is_iterable(dict()))
     self.assertFalse(pmutt._is_iterable(''))
     self.assertFalse(pmutt._is_iterable(1))
     self.assertFalse(pmutt._is_iterable(1.))
     self.assertFalse(pmutt._is_iterable(None))
예제 #5
0
    def __init__(self,
                 name=None,
                 trans_model=EmptyMode(),
                 vib_model=EmptyMode(),
                 rot_model=EmptyMode(),
                 elec_model=EmptyMode(),
                 nucl_model=EmptyMode(),
                 misc_models=None,
                 elements=None,
                 references=None,
                 smiles=None,
                 notes=None,
                 **kwargs):
        self.name = name
        self.smiles = smiles
        self.notes = notes
        self.elements = elements
        self.references = references

        # 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 misc models
        # TODO misc 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(misc_models) and misc_models is not None:
            misc_models = [misc_models]
        self.misc_models = misc_models
예제 #6
0
def _get_target_sets(target, species_delimiter='+'):
    target_sets = []
    if target is None:
        target_sets.append(set())
    else:
        if not _is_iterable(target):
            target = [target]
        for reaction_str in target:
            target_names, target_stoich = _parse_reaction_state(
                reaction_str=reaction_str, species_delimiter=species_delimiter)
            target_set = state_str_to_set(species_names=target_names,
                                          stoich=target_stoich)
            target_sets.append(target_set)
    return target_sets
예제 #7
0
파일: shomate.py 프로젝트: wittregr/pMuTT
    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/reference/generated/numpy.ndarray.html
        """
        # Convert T to 1D numpy format
        if not _is_iterable(T):
            T = [T]
        T = np.array(T)
        self._check_T(T)

        # Calculate pure properties
        CpoR = get_shomate_CpoR(a=self.a, T=T, units=self.units)
        # Calculate mixing properties
        for T_i in T:
            CpoR_mix = _get_mix_quantity(misc_models=self.misc_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
        CpoR = CpoR + CpoR_mix
        if len(T) == 1:
            CpoR = CpoR.item(0)
        return CpoR
예제 #8
0
파일: shomate.py 프로젝트: wittregr/pMuTT
def _shomate_CpoR(T, A, B, C, D, E, units):
    """
    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, units=units)
예제 #9
0
def write_yaml(reactor_type=None,
               mode=None,
               nodes=None,
               V=None,
               T=None,
               P=None,
               A=None,
               L=None,
               cat_abyv=None,
               flow_rate=None,
               residence_time=None,
               mass_flow_rate=None,
               end_time=None,
               transient=None,
               stepping=None,
               init_step=None,
               step_size=None,
               atol=None,
               rtol=None,
               full_SA=None,
               reactions_SA=None,
               species_SA=None,
               phases=None,
               reactor=None,
               inlet_gas=None,
               multi_T=None,
               multi_P=None,
               multi_flow_rate=None,
               output_format=None,
               solver=None,
               simulation=None,
               multi_input=None,
               misc=None,
               units=None,
               filename=None,
               yaml_options={
                   'default_flow_style': False,
                   'indent': 4
               },
               newline='\n'):
    """Writes the reactor options in a YAML file for OpenMKM. 
    
    Parameters
    ----------
        reactor_type : str
            Type of reactor. Supported options include:
            
            - pfr
            - pfr_0d
            - cstr
            - batch

            Value written to ``reactor.type``.
        mode : str
            Operation of reactor. Supported options include:

            - Isothermal
            - Adiabatic

            Value written to ``reactor.mode``.
        nodes : int
            Number of nodes to use if ``reactor_type`` is 'pfr_0d'. Value
            written to ``reactor.nodes``
        V : float or str
            Volume of reactor. Value written to ``reactor.volume``. Units of
            length^3. See Notes section regarding unit specification.
        T : float
            Temperature (in K) of reactor. Value written to 
            ``reactor.temperature``.
        P : float or str
            Pressure of reactor. Value written to ``reactor.pressure``. Units
            of pressure. See Notes section regarding unit specification.
        A : float or str
            Surface area of reactor. Value written to ``reactor.area``.
            Units of length^2. See Notes section regarding unit specification.
        L : float or str
            Length of reactor. Value written to ``reactor.length``.
            Units of length. See Notes section regarding unit specification.
        cat_abyv : float or str
            Catalyst surface area to volume ratio. Value written to
            ``reactor.cat_abyv``. Units of 1/length. See Notes section
            regarding unit specification.
        flow_rate : float or str
            Volumetric flow rate of inlet. Value written to
            ``inlet_gas.flow_rate``. Units of length^3/time.
            See Notes section regarding unit specification.
        residence_time : float or str
            Residence time of reactor. Value written to
            ``inlet_gas.residence_time``. Not required if ``flow_rate``
            or ``mass_flow_rate`` already specified. Units of time.
            See Notes section regarding unit specification.
        mass_flow_rate : float or str
            Mass flow rate of inlet. Value written to
            ``inlet_gas.mass_flow_rate``. Units of mass^3/time.
            See Notes section regarding unit specification.
        end_time : float or str
            Reactor simulation time. For continuous reactors, the system is
            assumed to reach steady state by this time. Value written to
            ``simulation.end_time``. Units of time. See Notes section regarding
            unit specification.
        transient : bool
            If True, transient operation results are saved. Otherwise,
            transient files are left blank. Value written to
            ``simulation.transient``.
        stepping : str
            Time steps taken to simulate reactor. Supported options include:

            - logarithmic
            - regular

            Value written to ``simulation.stepping``.
        init_step : float or str
            Initial step to take. Value written to ``simulation.init_step``.
        step_size : float or str
            If ``stepping`` is logarithmic, represents the ratio between the
            next step and the current step. If ``stepping`` is regular,
            represents the time between the next step and the current step in
            units of time. Value written to simulation.step_size. See Notes
            section regarding unit specification.
        atol : float
            Absolute tolerance for solver. Value written to
            ``simulation.solver.atol``.
        rtol : float
            Relative tolerance for solver. Value written to
            ``simulation.solver.rtol``.
        full_SA : bool
            If True, OpenMKM will do a full sensitivity analysis using the
            Fisher Information Matrix (FIM). Value written to
            ``simulation.sensitivity.full``.
        reactions_SA : list of str or list of :class:`~pmutt.omkm.reaction.SurfaceReaction` obj
            List of reactions to perturb using local sensitivity analysis (LSA).
            If a list of :class:`~pmutt.omkm.reaction.SurfaceReaction` is given,
            the ``id`` attribute will be used.
        species_SA : list of str or list of :class:`~pmutt.empirical.EmpiricalBase` obj
            List of species to perturb using local sensitivity analysis (LSA).
            If a list of :class:`~pmutt.empirical.EmpiricalBase` is given,
            the ``name`` attribute will be used.
        phases : list of ``Phase`` objects
            Phases present in reactor. Each phase should have the ``name``
            and ``initial_state`` attribute.
        reactor : dict
            Generic dictionary for reactor to specify values not supported by
            ``write_yaml``.
        inlet_gas : dict
            Generic dictionary for inlet_gas to specify values not supported by
            ``write_yaml``.
        multi_T : list of float
            Multiple temperatures (in K) of reactor. Value written to
            ``simulation.multi_input.temperature``.
        multi_P : list of float/str
            Multiple pressures of reactor. Value written to
            ``simulation.multi_input.pressure``. Units of pressure. See Notes
            section regarding unit specification.
        multi_flow_rate : list of float/str
            Multiple flow rates to run the model. Value written to
            ``simulation.multi_input.flow_rate``. Units of length3/time.
            See Notes section regarding unit specification.
        output_format : str
            Format for output files. Supported options include:
            
            - CSV
            - DAT

            Value written to ``simulation.output_format``.

        misc : dict
            Generic dictionary for any parameter specified at the top level.
        solver : dict
            Generic dictionary for solver to specify values not supported by
            ``write_yaml``.
        simulation : dict
            Generic dictionary for simultaion to specify values not supported by
            ``write_yaml``.            
        multi_input : dict
            Generic dictionary for multi_input to specify values not supported
            by ``write_yaml``.
        units : dict or :class:`~pmutt.omkm.units.Unit` object, optional
            Units used for file. If a dict is inputted, the key is the quantity
            and the value is the unit. If not specified, uses the default units
            of :class:`~pmutt.omkm.units.Unit`.
        filename: str, optional
            Filename for the input.cti file. If not specified, returns file
            as str.
        yaml_options : dict
            Options to pass when converting the parameters to YAML format. See
            `PyYAML documentation`_ for ``dump`` for available options.
    Returns
    -------
        lines_out : str
            If ``filename`` is None, CTI file is returned
    Notes
    -----
        **Units**
        If ``units`` is not specified, all values in file are assumed to be SI
        units. If ``units`` is specified, all values inputted are assumed to
        be in the units specified by ``units``. If a particular unit set is
        desired for a value, a str can be inputted with the form
        quantity="<<value>> <<desired units>>" where ``value`` is float-like
        and ``desired units`` is a string. For example, flow_rate="1 cm3/s".
        See https://vlachosgroup.github.io/openmkm/input for the most up-to-date
        supported values.

        **Generic Dictionaries**
        Also, values in generic dictionaries (i.e. ``reactor``, ``inlet_gas``,
        ``simulation``) will ben written preferentially over arguments passed.
        i.e. The value in ``reactor['temperature']`` will be written instead
        of ``T``.

    .. _`PyYAML Documentation`: https://pyyaml.org/wiki/PyYAMLDocumentation
    """
    lines = [
        _get_file_timestamp(comment_char='# '),
        '# See documentation for OpenMKM YAML file here:',
        '# https://vlachosgroup.github.io/openmkm/input'
    ]
    '''Initialize units'''
    if isinstance(units, dict):
        units = Units(**units)
    '''Organize reactor parameters'''
    if reactor is None:
        reactor = {}
    reactor_params = [
        _Param('type', reactor_type, None),
        _Param('mode', mode, None),
        _Param('nodes', nodes, None),
        _Param('volume', V, '_length3'),
        _Param('area', A, '_length2'),
        _Param('length', L, '_length'),
        _Param('cat_abyv', cat_abyv, '/_length')
    ]
    # Process temperature
    if T is not None:
        reactor_params.append(_Param('temperature', T, None))
    elif multi_T is not None:
        reactor_params.append(_Param('temperature', multi_T[0], None))
    # Process pressure
    if P is not None:
        reactor_params.append(_Param('pressure', P, '_pressure'))
    elif multi_P is not None:
        reactor_params.append(_Param('pressure', multi_P[0], '_pressure'))

    for parameter in reactor_params:
        _assign_yaml_val(parameter, reactor, units)

    if inlet_gas is None:
        inlet_gas = {}
    inlet_gas_params = [
        _Param('residence_time', residence_time, '_time'),
        _Param('mass_flow_rate', mass_flow_rate, '_mass/_time')
    ]
    # Process flow rate
    if flow_rate is not None:
        inlet_gas_params.append(
            _Param('flow_rate', flow_rate, '_length3/_time'))
    elif multi_flow_rate is not None:
        inlet_gas_params.append(
            _Param('flow_rate', multi_flow_rate[0], '_length3/_time'))
    '''Process inlet gas parameters'''
    for parameter in inlet_gas_params:
        _assign_yaml_val(parameter, inlet_gas, units)
    '''Organize solver parameters'''
    if solver is None:
        solver = {}
    solver_params = [_Param('atol', atol, None), _Param('rtol', rtol, None)]
    for parameter in solver_params:
        _assign_yaml_val(parameter, solver, units)
    '''Organize multi parameters'''
    if multi_input is None:
        multi_input = {}
    # Ensure multi quantities are in correct type
    if _is_iterable(multi_T):
        multi_T = list(multi_T)
    if _is_iterable(multi_P):
        multi_P = list(multi_P)
    if _is_iterable(multi_flow_rate):
        multi_flow_rate = list(multi_flow_rate)
    multi_input_params = [
        _Param('temperature', multi_T, None),
        _Param('pressure', multi_P, '_pressure'),
        _Param('flow_rate', multi_flow_rate, '_length3/_time')
    ]
    for parameter in multi_input_params:
        _assign_yaml_val(parameter, multi_input, units)
    '''Organize sensitivity parameters'''
    sensitivity = {}
    if reactions_SA is None:
        reactions_SA_names = None
    else:
        reactions_SA_names = []
        for reaction in reactions_SA:
            if isinstance(reaction, str):
                name = reaction
            else:
                try:
                    name = reaction.id
                except AttributeError:
                    err_msg = ('Unable to write reaction id to reactor YAML '
                               'file because object is invalid type ({}). '
                               'Expected str or '
                               'pmutt.omkm.reaction.SurfaceReaction type with '
                               'id attribute assigned.'
                               ''.format(type(reaction)))
                    raise TypeError(err_msg)
            reactions_SA_names.append(name)
    if species_SA is None:
        species_SA_names = None
    else:
        species_SA_names = []
        for ind_species in species_SA:
            if isinstance(ind_species, str):
                name = ind_species
            else:
                try:
                    name = ind_species.name
                except AttributeError:
                    err_msg = ('Unable to write species name to reactor YAML '
                               'file because object is invalid type ({}). '
                               'Expected str or '
                               'pmutt.empirical.EmpiricalBase type with '
                               'name attribute assigned.'
                               ''.format(type(ind_species)))
                    raise TypeError(err_msg)
            species_SA_names.append(name)
    sensitivity_params = (_Param('full', full_SA, None),
                          _Param('reactions', reactions_SA_names, None),
                          _Param('species', species_SA_names, None))
    for parameter in sensitivity_params:
        _assign_yaml_val(parameter, sensitivity, units)
    '''Organize simulation parameters'''
    if simulation is None:
        simulation = {}
    simulation_params = (_Param('end_time', end_time,
                                '_time'), _Param('transient', transient, None),
                         _Param('stepping', stepping,
                                None), _Param('step_size', step_size, None),
                         _Param('init_step', init_step, None),
                         _Param('output_format', output_format, None))
    for parameter in simulation_params:
        _assign_yaml_val(parameter, simulation, units)
    if len(solver) > 0:
        _assign_yaml_val(_Param('solver', solver, None), simulation, units)
    if len(multi_input) > 0:
        _assign_yaml_val(_Param('multi_input', multi_input, None), simulation,
                         units)
    if len(sensitivity) > 0:
        _assign_yaml_val(_Param('sensitivity', sensitivity, None), simulation,
                         units)
    '''Organize phase parameters'''
    if phases is not None:
        if isinstance(phases, dict):
            phases_dict = phases.copy()
        elif isinstance(phases, list):
            phases_dict = {}
            for phase in phases:
                phase_info = {'name': phase.name}

                # Assign intial state if available
                if phase.initial_state is not None:
                    initial_state_str = '"'
                    for species, mole_frac in phase.initial_state.items():
                        initial_state_str += '{}:{}, '.format(
                            species, mole_frac)
                    initial_state_str = '{}"'.format(initial_state_str[:-2])
                    phase_info['initial_state'] = initial_state_str

                # Assign phase type
                if isinstance(phase, IdealGas):
                    phase_type = 'gas'
                elif isinstance(phase, StoichSolid):
                    phase_type = 'bulk'
                elif isinstance(phase, InteractingInterface):
                    phase_type = 'surfaces'

                try:
                    phases_dict[phase_type].append(phase_info)
                except KeyError:
                    phases_dict[phase_type] = [phase_info]
        # If only one entry for phase type, reassign it.
        for phase_type, phases in phases_dict.items():
            if len(phases) == 1 and phase_type != 'surfaces':
                phases_dict[phase_type] = phases[0]
    '''Assign misc values'''
    if misc is None:
        misc = {}
    yaml_dict = misc.copy()
    '''Assign values to overall YAML dict'''
    headers = (('reactor', reactor), ('inlet_gas', inlet_gas),
               ('simulation', simulation), ('phases', phases_dict))
    for header in headers:
        if len(header[1]) > 0:
            yaml_dict[header[0]] = header[1]
    '''Convert dictionary to YAML str'''
    yaml_str = yaml.dump(yaml_dict, **yaml_options)
    # Remove redundant quotes
    yaml_str = yaml_str.replace('\'', '')
    lines.append(yaml_str)
    '''Write to file'''
    lines_out = '\n'.join(lines)
    if filename is not None:
        with open(filename, 'w', newline=newline) as f_ptr:
            f_ptr.write(lines_out)
    else:
        # Or return as string
        return lines_out
예제 #10
0
    def plot_coordinate_diagram(self,
                                source,
                                target,
                                method_name,
                                units=None,
                                cutoff=None,
                                max_energy_span=None,
                                max_paths=None,
                                pathway_numbers=None,
                                min_x_spacing=1.,
                                x_width=0.5,
                                x_scale_TS=0.5,
                                y_scale_TS=0.5,
                                x_label_offset=-0.1,
                                y_label_offset=0.1,
                                species_delimiter='+',
                                viewer='matplotlib',
                                show_state_table=True,
                                show_state_labels=True,
                                table_font_size=None,
                                table_width_ratio=[3, 1],
                                show_energy_span=False,
                                energy_span_format='.2f',
                                colors=None,
                                **kwargs):
        """Plots the reaction coordinate diagram

        Parameters
        ----------
            source : str
                Initial state as string. All pathways will start here.
            target : str or list of str
                Final state as string. All pathways will end here
            method_name : str
                Method to evaluate property of the states. Examples include:
                'get_H', 'get_HoRT', 'get_G', 'get_GoRT'
            units : str, optional
                Units to use to evaluate method_name. Must be specified if the
                `method_name` returns a dimensional property
            cutoff : int, optional
                Maximum number of states in the pathway. If not specified, all
                pathways are shown
            max_energy_span : float, optional
                If specified, pathways with larger energy spans are eliminated
            max_paths : int, optional
                If specified, the number of pathways plotted are limited
            pathway_numbers : list of int, optional
                If specified, only certain pathways are plotted            
            min_x_spacing : float, optional
                Minimum spacing between states. Default is 1.
            x_width : float, optional
                Spacing of stable states. Default is 0.5
            x_scale_TS : float, optional
                Value between 0 and 1 that controls curvature of transition
                state peaks. Higher values produce sharper peaks. Default is
                0.5
            y_scale_TS : float, optional
                Value between 0 and 1 that controls curvature of transition
                state peaks. Higher values produce sharper peaks. Default is
                0.5
            x_label_offset : float, optional
                Horizontal value to offset TS_label from the TS position. This
                value scales with the difference between major ticks. Negative
                values will shift the label leftward. Default is -0.1
            y_label_offset : float, optional
                Vertical value to offset TS_label from the TS position. This
                value scales with the difference between major ticks. Negative
                values will shift the label downwards. Default is 0.1
            species_delimiter : str, optional
                Delimiter that separate species for target and source.
                Leading and trailing spaces will be trimmed. Default is '+'
            viewer : str, optional
                Visualization package to use. Currently, the accepted options
                are: 

                - 'matplotlib' (default)
                - 'pygal'
            show_state_table : bool, optional
                Only applies if `viewer` = 'matplotlib'. If True, a table of
                the states is printed with the diagram. Default is True
            show_state_labels : bool, optional
                Only applies if `viewer` = 'matplotlib'. If True, numbers are
                added to the states which correspond to the entries in the
                table. Default is True
            table_font_size : int, optional
                Only applies if `viewer` = 'matplotlib'. Controls the text font
                size. If not specified, font rescales with figure size
            table_width_ratio : list of int, optional
                Only applies if `viewer` = 'matplotlib'. Controls the relative
                size of diagram to table. i.e. [2, 1] will make the diagram
                width twice as large as the table. Default is [3, 1]
            show_energy_span : bool, optional
                If True, adds energy span value to legend. Default is True
            energy_span_format : str, optional
                String format for energy span in legend. Default is 2 floating
                decimal points
            colors : list of str, optional
                Colors to use for reaction plots
            kwargs: keyword arguments
                Extra arguments that will be fed to evaluate reaction states
        Returns
        -------
            figure : `matplotlib.figure.Figure`_
                Figure
            axes : tuple of `matplotlib.axes.Axes.axis`_
                Axes of the plot.
        Raises
        ------
            ValueError : Raised when `viewer` is not supported.
        """
        # Get the y axis value for the axis label
        y_title = method_name.replace('get_', '')
        if units is not None:
            y_title = '{} ({})'.format(y_title, units)

        # Initialize plot using appropriate viewer
        if viewer == 'matplotlib':
            # Split graph into two axes if including the table
            if show_state_table:
                fig, axes = plt.subplots(
                    ncols=2, gridspec_kw={'width_ratios': table_width_ratio})
            else:
                fig, axes = plt.subplots()
                axes = [axes]
        elif viewer == 'pygal':
            # Use the tooltip x value to indicate what the y value indicates
            x_value_formatter = lambda x: y_title

            # Edit style sheet to have the same color for points and colors
            style = pygal.style.DefaultStyle
            new_colors = []
            if colors is None:
                colors = style.colors
            for color in colors:
                new_colors.extend([color] * 2)
            style.colors = new_colors

            # Initialize the graph
            graph = pygal.XY(x_title='Reaction Coordinate',
                             y_title=y_title,
                             pretty_print=True,
                             show_y_guides=False,
                             show_x_guides=False,
                             show_x_labels=False,
                             x_value_formatter=x_value_formatter,
                             style=style,
                             truncate_legend=-1)
        else:
            err_msg = (
                'Viewer {} not supported. Type help(pmutt.reaction.'
                'network.Network) for supported options.'.format(viewer))
            raise ValueError(err_msg)

        # If the pathway to plot was specified as an integer, convert to a list
        if pathway_numbers is not None and not _is_iterable(pathway_numbers):
            pathway_numbers = [pathway_numbers]

        # Encode inital and final node
        source_names, source_stoich = _parse_reaction_state(source)
        source_set = state_str_to_set(source_names, source_stoich)
        target_sets = _get_target_sets(target=target,
                                       species_delimiter=species_delimiter)

        # Get all the pathways and associated data for sorting
        paths = list(path for path in nx.all_simple_paths(
            self.graph, source=source_set, target=target_sets, cutoff=cutoff))
        path_lens = list(len(path) for path in paths)
        energy_spans = list(
            self.get_E_span(path, units, **kwargs) for path in paths)

        # Get n paths with smallest energy span
        if max_paths is not None:
            paths_data = [
                (span, path_len, path)
                for span, path_len, path in zip(energy_spans, path_lens, paths)
            ]
            paths_data_reduced = heapq.nsmallest(max_paths, paths_data)
            energy_spans, path_lens, paths = map(list,
                                                 zip(*paths_data_reduced))

        # Remove pathways greater than the limit if any
        if max_energy_span is not None:
            for i in range(len(paths) - 1, -1, -1):
                if energy_spans[i] > max_energy_span:
                    del paths[i], path_lens[i], energy_spans[i]

        # Sort in descending order of path length
        _, paths_sorted, energy_spans_sorted = zip(
            *sorted(zip(path_lens, paths, energy_spans), reverse=True))

        # Determine x values for each state
        x_vals = {}
        for path in paths_sorted:
            x_spacing = min_x_spacing
            # Find duplicates
            duplicates = tuple(state for state in path if state in x_vals)
            # If there are no duplicates, assign all the states to values and
            # move to next path
            if len(duplicates) == 0:
                for i, state in enumerate(path):
                    x_vals[state] = i * x_spacing
                continue
            # If there is only one duplicate
            if len(duplicates) == 1:
                # If it is the last index, then assign to the length of the
                # reaction.
                if duplicates[0] == path[-1]:
                    x_vals[duplicates[0]] = i * x_spacing * (
                        np.max(path_lens) - 1)
                else:
                    i = path.index(duplicates[0])
                    prev_state = path[i - 1]
                    next_state = path[i + 1]
                    x_vals[duplicates[0]] = np.mean(
                        [x_vals[prev_state], x_vals[next_state]])
                continue
            # Skip this path if all the states are duplicates
            if len(duplicates) == len(path):
                continue

            # Check adjacent duplicates for intermedient elements
            for duplicate_i, duplicate_j in zip(duplicates, duplicates[1:]):
                i = path.index(duplicate_i)
                j = path.index(duplicate_j)
                # Skip if adjacent duplicates are also adjacent in reaction path
                if j - i == 1:
                    continue
                # Calculate spacing
                x_spacing = (x_vals[duplicate_j] - x_vals[duplicate_i]) / (j -
                                                                           i)

                # Assign x positions for new states
                x_initial = x_vals[duplicate_i]
                for l, k in enumerate(range(i + 1, j), start=1):
                    x_vals[path[k]] = x_spacing * l + x_initial

        # Sort ascending order by energy span
        energy_spans_sorted, paths_sorted = zip(
            *sorted(zip(energy_spans_sorted, paths_sorted)))

        # Assign x, y values for plot
        labels_list = []
        labels_set = set()
        y_states = {}
        n_paths = len(paths_sorted)
        for i, (path, energy_span) in enumerate(zip(paths_sorted,
                                                    energy_spans_sorted),
                                                start=1):
            # If pathway_numbers set, skips pathways not specified
            if pathway_numbers is not None and i not in pathway_numbers:
                continue

            # Initialize x, y points for continuous line
            x_plot = []
            y_plot = []
            # Initialize x, y points for interactive points (when viewer is
            # pygal)
            x_points = []
            y_points = []

            # Generate legend for trend
            if show_energy_span:
                if units is None:
                    units_str = ''
                else:
                    units_str = units
                path_name = 'Pathway {:>3} ({:%s} {})' % energy_span_format
                path_name = path_name.format(i, energy_span, units_str)
            else:
                path_name = 'Pathway {:>3}'.format(i)
            point_name = 'Points {:>4}'.format(i)

            for j, state in enumerate(path):
                # If unique state found, add it to the label set
                if state not in labels_set:
                    labels_list.append(state)
                    labels_set.add(state)
                # Get x and y value
                x_state = x_vals[state]
                species = self.graph.nodes[state]['species']
                stoich = self.graph.nodes[state]['stoich']
                y_val = get_state_quantity(species=species,
                                           stoich=stoich,
                                           method_name=method_name,
                                           units=units,
                                           **kwargs)
                # Subtract the initial state's energy
                if j == 0:
                    y_ref = y_val
                y_state = y_val - y_ref
                y_states[state] = y_state

                # Generate continuous points for plot
                if self.graph.nodes[state]['is_transition_state']:
                    # Calculate product properties for y interpolation
                    products = self.graph.nodes[path[j + 1]]['species']
                    prod_stoich = self.graph.nodes[path[j + 1]]['stoich']
                    y_prod = get_state_quantity(species=products,
                                                stoich=prod_stoich,
                                                method_name=method_name,
                                                units=units,
                                                **kwargs) - y_ref
                    # Fit spline
                    delta_x = x_state - x_plot[-1]
                    delta_y = y_state - y_plot[-1]
                    x_fit = np.array([
                        x_plot[-1], x_state - delta_x * x_scale_TS, x_state,
                        x_state + delta_x * x_scale_TS, x_state + delta_x
                    ])
                    y_fit = np.array([
                        y_plot[-1], y_state - delta_y * y_scale_TS, y_state,
                        (y_state - y_prod) * y_scale_TS + y_prod, y_prod
                    ])
                    tck = interpolate.splrep(x_fit, y_fit, k=2)
                    # Calculate new x and y points from spline fit
                    x_spline = np.linspace(x_state - delta_x,
                                           x_state + delta_x, 100)
                    y_spline = interpolate.splev(x_spline, tck)

                    # Get x value corresponding to peak for pygal
                    max_i = np.argmax(y_spline)
                    x_points.append(x_spline[max_i])
                    y_points.append(y_spline[max_i])

                    # Add new data to the appropriate lists
                    x_plot.extend(x_spline)
                    y_plot.extend(y_spline)
                else:
                    # For intermediates, use a straight line
                    x_plot.extend([
                        x_state - x_width / 2., x_state, x_state + x_width / 2.
                    ])
                    y_plot.extend([y_state, y_state, y_state])
                    x_points.append(x_state)
                    y_points.append(y_state)
            # Add data to plot
            if viewer == 'matplotlib':
                axes[0].plot(x_plot,
                             y_plot,
                             label=path_name,
                             zorder=n_paths - i)
            elif viewer == 'pygal':
                # Add line
                line_data = [{
                    'value': (x, y),
                } for x, y in zip(x_plot, y_plot)]
                graph.add(path_name, line_data, show_dots=False)
                # Add interactive points
                point_data = [{
                    'value': (x, y),
                    'label': self.graph.nodes[state]['name'],
                } for x, y, state in zip(x_points, y_points, path)]
                graph.add(point_name, point_data, stroke=False)

        if viewer == 'matplotlib':
            # Add other misc labels
            axes[0].legend()
            axes[0].set_ylabel(y_title)
            axes[0].set_xlabel('Reaction coordinate')
            axes[0].tick_params(axis='x',
                                which='both',
                                bottom=False,
                                top=False,
                                labelbottom=False)
            # Add state labels
            if show_state_labels:
                for i, label in enumerate(labels_list, start=1):
                    axes[0].text(x=x_vals[label] + x_label_offset,
                                 y=y_states[label] + y_label_offset,
                                 s='{:^}'.format(i))
            # Add table
            if show_state_table:
                axes[1].axis('off')
                # Setting up table info
                columns = ('State', )
                rows = range(1, len(labels_list) + 1)
                cellText = tuple([self.graph.nodes[state]['name']]
                                 for state in labels_list)
                # Adding table
                table = axes[1].table(cellText=cellText,
                                      colLabels=columns,
                                      rowLabels=rows,
                                      loc='center')
                # Adjust font size
                if table_font_size is not None:
                    table.auto_set_font_size(False)
                    table.set_fontsize(table_font_size)
            return fig, axes
        else:
            return graph
예제 #11
0
파일: shomate.py 프로젝트: wittregr/pMuTT
    def from_model(cls,
                   model,
                   name=None,
                   T_low=None,
                   T_high=None,
                   elements=None,
                   n_T=50,
                   units='J/mol/K',
                   **kwargs):
        """Calculates the NASA polynomials using the model passed

        Parameters
        ----------
            model : Model object or class
                Model to generate data. Must contain the methods `get_CpoR`,
                `get_HoRT` and `get_SoR`
            name : str, optional
                Name of the species. If not passed, `model.name` will be used.
            T_low : float, optional
                Lower limit temerature in K. If not passed, `model.T_low` will
                be used.
            T_high : float, optional
                Higher limit temperature in K. If not passed, `model.T_high`
                will be used.
            elements : dict, optional
                Composition of the species. If not passed, `model.elements`
                will be used. 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,}.
            n_T : int, optional
                Number of data points between `T_low` and `T_high` for fitting
                heat capacity. Default is 50.
            units : str, optional
                Units used to fit the Shomate polynomial. Units should be
                supported by :class:`~pmutt.constants.R` (e.g. J/mol/K,
                cal/mol/K, eV/K). Default is J/mol/K.
            kwargs : keyword arguments
                Used to initalize model if a class is passed.
        Returns
        -------
            Shomate : Shomate object
                Shomate object with polynomial terms fitted to data.
        """
        # Initialize the model object
        if inspect.isclass(model):
            model = model(name=name, elements=elements, **kwargs)

        if name is None:
            try:
                name = model.name
            except AttributeError:
                err_msg = ('Name must either be passed to from_model directly '
                           'or be an attribute of model.')
                raise AttributeError(err_msg)
        if T_low is None:
            try:
                T_low = model.T_low
            except AttributeError:
                err_msg = ('T_low must either be passed to from_model '
                           'directly or be an attribute of model.')
                raise AttributeError(err_msg)
        if T_high is None:
            try:
                T_high = model.T_high
            except AttributeError:
                err_msg = ('T_high must either be passed to from_model '
                           'directly or be an attribute of model.')
                raise AttributeError(err_msg)
        if elements is None:
            try:
                elements = model.elements
            except AttributeError:
                pass
        # Check if inputted T_low and T_high are outside model's T_low and
        # T_high range
        try:
            model.T_low
        except AttributeError:
            pass
        else:
            if T_low < model.T_low:
                warn_msg = ('Inputted T_low ({} K) is lower than model '
                            'T_low ({} K). Fitted empirical object may not be '
                            'valid.'
                            ''.format(T_low, model.T_low))
                warn(warn_msg, UserWarning)


        try:
            if T_high > model.T_high:
                warn_msg = ('Inputted T_high ({} K) is higher than model '
                            'T_high ({} K). Fitted empirical object may not be '
                            'valid.'
                            ''.format(T_high, model.T_high))
                warn(warn_msg, UserWarning)
        except AttributeError:
            pass

        # Generate heat capacity data
        T = np.linspace(T_low, T_high, n_T)
        try:
            CpoR = model.get_CpoR(T=T)
        except ValueError:
            CpoR = np.array([model.get_CpoR(T=T_i) for T_i in T])
        else:
            if not _is_iterable(CpoR) or len(CpoR) != len(T):
                CpoR = np.array([model.get_CpoR(T=T_i) for T_i in T])

        # Generate enthalpy and entropy data
        T_mean = (T_low + T_high) / 2.
        HoRT_ref = model.get_HoRT(T=T_mean)
        SoR_ref = model.get_SoR(T=T_mean)
        return cls.from_data(name=name,
                             T=T,
                             CpoR=CpoR,
                             T_ref=T_mean,
                             HoRT_ref=HoRT_ref,
                             SoR_ref=SoR_ref,
                             model=model,
                             elements=elements,
                             units=units,
                             **kwargs)