Selecting Fitting Parameters

As the number of fitting parameters increases it may be necessary to only refine a subset of the parameters. In MDMC this can be done in two ways:

Fixing a Parameter

Parameter objects can be fixed. This can either be set when they are initialised (created) or changed for an existing Parameter. By default, an initialised Parameter is not fixed.

[1]:
from MDMC.MD.parameters import Parameter
charge = Parameter(value=0.5, name='charge', fixed=True, unit='e')

# Trying to set the value of a fixed Parameter will result in a warning...
charge.value = 12
[2]:
# ... and the value will remain unchanged
print(charge.value)
0.5 e
[3]:
# Existing Parameters can also be fixed
sigma = Parameter(1.0, name='sigma', unit='Ang')
print('Is sigma fixed: {}'.format(sigma.fixed))
sigma.fixed = True
print('Is sigma fixed: {}'.format(sigma.fixed))
Is sigma fixed: False
Is sigma fixed: True

Passing a subset of Parameters to Control

Only parameters that are passed to Control (as fit_parameters) will be refined. While it is simplest to pass all parameters in a Universe to Control, it is also possible to filter out a subset. To demonstrate this, a universe filled with SPCE water molecules is read from Building a Universe.

The %%capture and %run commands below simply executes the Building a Universe notebook and captures the variables into this notebook. They are only valid if they are executed in the same folder as the Building a Universe notebook. Otherwise, please copy the last section of Building a Universe to set the same state.

[4]:
%%capture
# Run Building a universe notebook and hide output
%run "building-a-universe.ipynb"

There are 8 parameters in the universe:

[5]:
print(universe.parameters)
{'equilibrium_state (#20)': <Parameter
 {ID: 20,
  type: 'equilibrium_state',
  value: 1.0 Ang,
  unit: 'Ang',
  fixed: False,
  constraints: None,
  interactions_name: 'Bond',
  functions_name: 'HarmonicPotential',
  tied: False}>, 'potential_strength (#21)': <Parameter
 {ID: 21,
  type: 'potential_strength',
  value: 4637.0 kJ / mol Ang ^ 2,
  unit: 'kJ / mol Ang ^ 2',
  fixed: False,
  constraints: None,
  interactions_name: 'Bond',
  functions_name: 'HarmonicPotential',
  tied: False}>, 'equilibrium_state (#38)': <Parameter
 {ID: 38,
  type: 'equilibrium_state',
  value: 109.47 deg,
  unit: 'deg',
  fixed: False,
  constraints: None,
  interactions_name: 'BondAngle',
  functions_name: 'HarmonicPotential',
  tied: False}>, 'potential_strength (#39)': <Parameter
 {ID: 39,
  type: 'potential_strength',
  value: 383.0 kJ / mol rad ^ 2,
  unit: 'kJ / mol rad ^ 2',
  fixed: False,
  constraints: None,
  interactions_name: 'BondAngle',
  functions_name: 'HarmonicPotential',
  tied: False}>, 'charge (#40)': <Parameter
 {ID: 40,
  type: 'charge',
  value: -0.8476 e,
  unit: 'e',
  fixed: False,
  constraints: None,
  interactions_name: 'Coulombic',
  functions_name: 'Coulomb',
  tied: False}>, 'charge (#49)': <Parameter
 {ID: 49,
  type: 'charge',
  value: 0.4238 e,
  unit: 'e',
  fixed: False,
  constraints: None,
  interactions_name: 'Coulombic',
  functions_name: 'Coulomb',
  tied: False}>, 'epsilon (#26)': <Parameter
 {ID: 26,
  type: 'epsilon',
  value: 0.6502 kJ / mol,
  unit: 'kJ / mol',
  fixed: False,
  constraints: None,
  interactions_name: 'Dispersion',
  functions_name: 'LennardJones',
  tied: False}>, 'sigma (#27)': <Parameter
 {ID: 27,
  type: 'sigma',
  value: 3.166 Ang,
  unit: 'Ang',
  fixed: False,
  constraints: None,
  interactions_name: 'Dispersion',
  functions_name: 'LennardJones',
  tied: False}>}

So while all 8 parameters can be passed when initiliasing Control, they can also be filtered. Parameters objects have a number of convenience methods to assist with this:

[6]:
parameters = universe.parameters
help(parameters)
Help on Parameters in module MDMC.MD.parameters object:

class Parameters(builtins.dict)
 |  Parameters(init_parameters: "Optional[Union[Parameter, 'list[Parameter]']]" = None)
 |
 |  A `dict-like` object where every element is a ``Parameter`` indexed by name,
 |  which contains a number of helper methods for filtering.
 |
 |  Although ``Parameters`` is a `dict`, it should be treated like a `list` when writing to it;
 |  i.e. initialise it using a `list` and use `append` to add to it. These parameters can
 |  then be accessed by their name as a key.
 |
 |  In short; Parameters writes like a list and reads like a dict.
 |
 |  Parameters
 |  ----------
 |  init_parameters: ``Parameter`` or `list` of ``Parameter``s, optional, default None
 |      The initial ``Parameter`` objects that the ``Parameters`` object contains.
 |
 |  Attributes
 |  ----------
 |  array: np.ndarray
 |      An alphabetically-sorted numpy array of the ``Parameter``s stored in this object.
 |
 |  Method resolution order:
 |      Parameters
 |      builtins.dict
 |      builtins.object
 |
 |  Methods defined here:
 |
 |  __getitem__(self, key: 'str') -> "Union[Parameter, 'list[Parameter]']"
 |      x.__getitem__(y) <==> x[y]
 |
 |  __init__(self, init_parameters: "Optional[Union[Parameter, 'list[Parameter]']]" = None)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |
 |  __setitem__(self, key: 'str', value: 'Parameter') -> 'NoReturn'
 |      Set self[key] to value.
 |
 |  append(self, parameters: "Union['list[Parameter]', Parameter]") -> 'None'
 |      Appends a ``Parameter`` or list of ``Parameter``s to the dict,
 |      with the parameter name as its key.
 |
 |      Parameters
 |      ----------
 |      parameters: ``Parameter`` or `list` of ``Parameter``s
 |          The parameter(s) to be added to the dict.
 |
 |  filter(self, predicate: 'Callable[[Parameter], bool]') -> 'Parameters'
 |      Filters using a predicate
 |
 |      Parameters
 |      ----------
 |      predicate : function
 |          A function that returns a `bool` which takes a ``Parameter`` as an
 |          argument.
 |
 |      Returns
 |      -------
 |      Parameters
 |          The ``Parameter`` objects which meet the condition of the predicate
 |
 |  filter_atom_attribute(self, attribute: 'str', value: 'Union[str, float]') -> 'Parameters'
 |      Filters based on the attribute of ``Atom`` objects which have each
 |      ``Parameter`` applied to them
 |
 |
 |      Parameters
 |      ----------
 |      attribute : str
 |          An attribute of an ``Atom``. Attributes to match to must be either
 |          `float` or str.
 |      value : str, float
 |          The value of the ``Atom`` ``attribute``.
 |
 |      Returns
 |      -------
 |      Parameters
 |          The ``Parameter`` objects which are applied to an ``Atom`` object
 |          which has the specified ``value`` of the specified ``attribute``
 |
 |  filter_function(self, function_name: 'str') -> 'Parameters'
 |      Filters based on the name of the ``InteractionFunction`` of each
 |      ``Parameter``
 |
 |      Parameters
 |      ----------
 |      function_name : str
 |          The name of the ``InteractionFunction`` of ``Parameter`` objects to
 |          return, for example ``'LennardJones'`` or ``'HarmonicPotential'``.
 |
 |      Returns
 |      -------
 |      Parameters
 |          The ``Parameter`` objects which have a ``function`` with the
 |          specified ``function_name``
 |
 |  filter_interaction(self, interaction_name: 'str') -> 'Parameters'
 |      Filters based on the name of the ``Interaction`` of each ``Parameter``
 |
 |      Parameters
 |      ----------
 |      interaction_name : str
 |          The name of the ``Interaction`` of ``Parameter`` objects to return,
 |          for example ``'Bond'``.
 |
 |      Returns
 |      -------
 |      Parameters
 |          The ``Parameter`` objects which have an ``Interaction`` with the
 |          specified ``interaction_name``
 |
 |  filter_name(self, name: 'str') -> 'Parameters'
 |      Filters by ``name``
 |
 |      Parameters
 |      ----------
 |      name : str
 |          The ``name`` of the ``Parameter`` objects to return.
 |
 |      Returns
 |      -------
 |      Parameters
 |          The ``Parameter`` objects with ``name``
 |
 |  filter_structure(self, structure_name: 'str') -> 'Parameters'
 |      Filters based on the name of the ``Structure`` to which each
 |      ``Parameter`` applies
 |
 |      Parameters
 |      ----------
 |      structure_name : str
 |          The name of a ``Structure``.
 |
 |      Returns
 |      -------
 |      Parameters
 |          The ``Parameter`` objects which are applied to a ``Structure``
 |          which has the specified ``structure_name``
 |
 |  filter_value(self, comparison: 'str', value: 'float') -> 'Parameters'
 |      Filters by ``value``
 |
 |      Parameters
 |      ----------
 |      comparison : str
 |          A `str` representing a comparison operator, ``'>'``, ``'<'``,
 |          ``'>='``, ``'<='``, ``'=='``, ``'!='``.
 |      value : float
 |          A `float` with which ``Parameter`` values are compared, using the
 |          ``comparison`` operator.
 |
 |      Returns
 |      -------
 |      Parameters
 |          The ``Parameter`` objects which return a `True` when their values
 |          are compared with ``value`` using the ``comparison`` operator
 |
 |  log_parameters(self) -> 'None'
 |      Logs all Parameters by ID
 |
 |  ----------------------------------------------------------------------
 |  Readonly properties defined here:
 |
 |  as_array
 |      The parameters in the object as a sorted numpy array.
 |
 |      Returns
 |      -------
 |      np.ndarray
 |          An alphabetically-sorted array of parameter values in the object.
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |
 |  __dict__
 |      dictionary for instance variables (if defined)
 |
 |  __weakref__
 |      list of weak references to the object (if defined)
 |
 |  ----------------------------------------------------------------------
 |  Methods inherited from builtins.dict:
 |
 |  __contains__(self, key, /)
 |      True if the dictionary has the specified key, else False.
 |
 |  __delitem__(self, key, /)
 |      Delete self[key].
 |
 |  __eq__(self, value, /)
 |      Return self==value.
 |
 |  __ge__(self, value, /)
 |      Return self>=value.
 |
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |
 |  __gt__(self, value, /)
 |      Return self>value.
 |
 |  __ior__(self, value, /)
 |      Return self|=value.
 |
 |  __iter__(self, /)
 |      Implement iter(self).
 |
 |  __le__(self, value, /)
 |      Return self<=value.
 |
 |  __len__(self, /)
 |      Return len(self).
 |
 |  __lt__(self, value, /)
 |      Return self<value.
 |
 |  __ne__(self, value, /)
 |      Return self!=value.
 |
 |  __or__(self, value, /)
 |      Return self|value.
 |
 |  __repr__(self, /)
 |      Return repr(self).
 |
 |  __reversed__(self, /)
 |      Return a reverse iterator over the dict keys.
 |
 |  __ror__(self, value, /)
 |      Return value|self.
 |
 |  __sizeof__(...)
 |      D.__sizeof__() -> size of D in memory, in bytes
 |
 |  clear(...)
 |      D.clear() -> None.  Remove all items from D.
 |
 |  copy(...)
 |      D.copy() -> a shallow copy of D
 |
 |  get(self, key, default=None, /)
 |      Return the value for key if key is in the dictionary, else default.
 |
 |  items(...)
 |      D.items() -> a set-like object providing a view on D's items
 |
 |  keys(...)
 |      D.keys() -> a set-like object providing a view on D's keys
 |
 |  pop(...)
 |      D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
 |
 |      If the key is not found, return the default if given; otherwise,
 |      raise a KeyError.
 |
 |  popitem(self, /)
 |      Remove and return a (key, value) pair as a 2-tuple.
 |
 |      Pairs are returned in LIFO (last-in, first-out) order.
 |      Raises KeyError if the dict is empty.
 |
 |  setdefault(self, key, default=None, /)
 |      Insert key with a value of default if key is not in the dictionary.
 |
 |      Return the value for key if key is in the dictionary, else default.
 |
 |  update(...)
 |      D.update([E, ]**F) -> None.  Update D from dict/iterable E and F.
 |      If E is present and has a .keys() method, then does:  for k in E: D[k] = E[k]
 |      If E is present and lacks a .keys() method, then does:  for k, v in E: D[k] = v
 |      In either case, this is followed by: for k in F:  D[k] = F[k]
 |
 |  values(...)
 |      D.values() -> an object providing a view on D's values
 |
 |  ----------------------------------------------------------------------
 |  Class methods inherited from builtins.dict:
 |
 |  __class_getitem__(...) from builtins.type
 |      See PEP 585
 |
 |  fromkeys(iterable, value=None, /) from builtins.type
 |      Create a new dictionary with keys from iterable and values set to value.
 |
 |  ----------------------------------------------------------------------
 |  Static methods inherited from builtins.dict:
 |
 |  __new__(*args, **kwargs) from builtins.type
 |      Create and return a new object.  See help(type) for accurate signature.
 |
 |  ----------------------------------------------------------------------
 |  Data and other attributes inherited from builtins.dict:
 |
 |  __hash__ = None

For example, if only the charge parameters should be refined, parameters could be filtered by name:

[7]:
charges = parameters.filter_name('charge')
print(charges)

# charges is a Parameters object
print('\nThe class of charges is: {}'.format(type(charges)))
{'charge (#40)': <Parameter
 {ID: 40,
  type: 'charge',
  value: -0.8476 e,
  unit: 'e',
  fixed: False,
  constraints: None,
  interactions_name: 'Coulombic',
  functions_name: 'Coulomb',
  tied: False}>, 'charge (#49)': <Parameter
 {ID: 49,
  type: 'charge',
  value: 0.4238 e,
  unit: 'e',
  fixed: False,
  constraints: None,
  interactions_name: 'Coulombic',
  functions_name: 'Coulomb',
  tied: False}>}

The class of charges is: <class 'MDMC.MD.parameters.Parameters'>

As each filter returns a Parameters object, filters can be chained together. For example, to find the potential strengths of all bonds:

[8]:
bond_potential_strengths = parameters.filter_name('potential_strength').filter_interaction('Bond')
print(bond_potential_strengths)

# These operations are commutative
print('\nThe order these methods are'
      ' applied does not matter: {}'.format(bond_potential_strengths
                                             == parameters.filter_interaction('Bond').filter_name('potential_strength')))
{'potential_strength (#21)': <Parameter
 {ID: 21,
  type: 'potential_strength',
  value: 4637.0 kJ / mol Ang ^ 2,
  unit: 'kJ / mol Ang ^ 2',
  fixed: False,
  constraints: None,
  interactions_name: 'Bond',
  functions_name: 'HarmonicPotential',
  tied: False}>}

The order these methods are applied does not matter: True

It is also possible to filter parameters based on the properties of the atoms to which they apply. For instance, we can filter the SPCE parameters so that only parameters of interactions on H atoms are shown:

[9]:
H_parameters = parameters.filter_atom_attribute('name', 'H')
print(H_parameters)
{'equilibrium_state (#20)': <Parameter
 {ID: 20,
  type: 'equilibrium_state',
  value: 1.0 Ang,
  unit: 'Ang',
  fixed: False,
  constraints: None,
  interactions_name: 'Bond',
  functions_name: 'HarmonicPotential',
  tied: False}>, 'potential_strength (#21)': <Parameter
 {ID: 21,
  type: 'potential_strength',
  value: 4637.0 kJ / mol Ang ^ 2,
  unit: 'kJ / mol Ang ^ 2',
  fixed: False,
  constraints: None,
  interactions_name: 'Bond',
  functions_name: 'HarmonicPotential',
  tied: False}>, 'equilibrium_state (#38)': <Parameter
 {ID: 38,
  type: 'equilibrium_state',
  value: 109.47 deg,
  unit: 'deg',
  fixed: False,
  constraints: None,
  interactions_name: 'BondAngle',
  functions_name: 'HarmonicPotential',
  tied: False}>, 'potential_strength (#39)': <Parameter
 {ID: 39,
  type: 'potential_strength',
  value: 383.0 kJ / mol rad ^ 2,
  unit: 'kJ / mol rad ^ 2',
  fixed: False,
  constraints: None,
  interactions_name: 'BondAngle',
  functions_name: 'HarmonicPotential',
  tied: False}>, 'charge (#49)': <Parameter
 {ID: 49,
  type: 'charge',
  value: 0.4238 e,
  unit: 'e',
  fixed: False,
  constraints: None,
  interactions_name: 'Coulombic',
  functions_name: 'Coulomb',
  tied: False}>}

Finally there is also a more flexible method (Parameters.filter) which can be used in conjunction with any function to filter the parameters:

[10]:
def is_length(parameter):
    if parameter.unit == 'Ang':
        return True
    else:
        return False
length_parameters = parameters.filter(is_length)
print(length_parameters)

# For those more familiar with Python, this can also be done using a lambda
lambda_length_parameters = parameters.filter(lambda x: x.unit == 'Ang')
print('\nThe same filter can be achieved using lambdas: {}'.format(lambda_length_parameters == length_parameters))
{'equilibrium_state (#20)': <Parameter
 {ID: 20,
  type: 'equilibrium_state',
  value: 1.0 Ang,
  unit: 'Ang',
  fixed: False,
  constraints: None,
  interactions_name: 'Bond',
  functions_name: 'HarmonicPotential',
  tied: False}>, 'sigma (#27)': <Parameter
 {ID: 27,
  type: 'sigma',
  value: 3.166 Ang,
  unit: 'Ang',
  fixed: False,
  constraints: None,
  interactions_name: 'Dispersion',
  functions_name: 'LennardJones',
  tied: False}>}

The same filter can be achieved using lambdas: True