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)
[<Parameter
 {name: 'sigma',
  value: 3.166 Ang,
  unit: 'Ang',
  fixed: False,
  constraints: None,
  interactions_name: 'Dispersion',
  functions_name: 'LennardJones',
  tied: False}>, <Parameter
 {name: '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}>, <Parameter
 {name: '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}>, <Parameter
 {name: 'charge',
  value: -0.8476 e,
  unit: 'e',
  fixed: False,
  constraints: None,
  interactions_name: 'Coulombic',
  functions_name: 'Coulomb',
  tied: False}>, <Parameter
 {name: 'epsilon',
  value: 0.6502 kJ / mol,
  unit: 'kJ / mol',
  fixed: False,
  constraints: None,
  interactions_name: 'Dispersion',
  functions_name: 'LennardJones',
  tied: False}>, <Parameter
 {name: 'equilibrium_state',
  value: 109.47 deg,
  unit: 'deg',
  fixed: False,
  constraints: None,
  interactions_name: 'BondAngle',
  functions_name: 'HarmonicPotential',
  tied: False}>, <Parameter
 {name: 'equilibrium_state',
  value: 1.0 Ang,
  unit: 'Ang',
  fixed: False,
  constraints: None,
  interactions_name: 'Bond',
  functions_name: 'HarmonicPotential',
  tied: False}>, <Parameter
 {name: 'charge',
  value: 0.4238 e,
  unit: 'e',
  fixed: False,
  constraints: None,
  interactions_name: 'Coulombic',
  functions_name: 'Coulomb',
  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.list)
 |  Parameters(iterable=(), /)
 |
 |  A `list-like` object where every element is a ``Parameter``, which contains
 |  a number of helper methods for filtering
 |
 |  Method resolution order:
 |      Parameters
 |      builtins.list
 |      builtins.object
 |
 |  Methods defined here:
 |
 |  __getitem__(self, key)
 |      x.__getitem__(y) <==> x[y]
 |
 |  filter(self, predicate)
 |      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, value)
 |      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)
 |      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)
 |      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)
 |      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)
 |      Filters based on the name of the ``StructuralUnit`` to which each
 |      ``Parameter`` applies
 |
 |      Parameters
 |      ----------
 |      structure_name : str
 |          The name of a ``StructuralUnit``.
 |
 |      Returns
 |      -------
 |      Parameters
 |          The ``Parameter`` objects which are applied to a ``StructuralUnit``
 |          which has the specified ``zstructure_name``
 |
 |  filter_value(self, comparison, value)
 |      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
 |
 |  ----------------------------------------------------------------------
 |  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.list:
 |
 |  __add__(self, value, /)
 |      Return self+value.
 |
 |  __contains__(self, key, /)
 |      Return key in self.
 |
 |  __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.
 |
 |  __iadd__(self, value, /)
 |      Implement self+=value.
 |
 |  __imul__(self, value, /)
 |      Implement self*=value.
 |
 |  __init__(self, /, *args, **kwargs)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |
 |  __iter__(self, /)
 |      Implement iter(self).
 |
 |  __le__(self, value, /)
 |      Return self<=value.
 |
 |  __len__(self, /)
 |      Return len(self).
 |
 |  __lt__(self, value, /)
 |      Return self<value.
 |
 |  __mul__(self, value, /)
 |      Return self*value.
 |
 |  __ne__(self, value, /)
 |      Return self!=value.
 |
 |  __repr__(self, /)
 |      Return repr(self).
 |
 |  __reversed__(self, /)
 |      Return a reverse iterator over the list.
 |
 |  __rmul__(self, value, /)
 |      Return value*self.
 |
 |  __setitem__(self, key, value, /)
 |      Set self[key] to value.
 |
 |  __sizeof__(self, /)
 |      Return the size of the list in memory, in bytes.
 |
 |  append(self, object, /)
 |      Append object to the end of the list.
 |
 |  clear(self, /)
 |      Remove all items from list.
 |
 |  copy(self, /)
 |      Return a shallow copy of the list.
 |
 |  count(self, value, /)
 |      Return number of occurrences of value.
 |
 |  extend(self, iterable, /)
 |      Extend list by appending elements from the iterable.
 |
 |  index(self, value, start=0, stop=9223372036854775807, /)
 |      Return first index of value.
 |
 |      Raises ValueError if the value is not present.
 |
 |  insert(self, index, object, /)
 |      Insert object before index.
 |
 |  pop(self, index=-1, /)
 |      Remove and return item at index (default last).
 |
 |      Raises IndexError if list is empty or index is out of range.
 |
 |  remove(self, value, /)
 |      Remove first occurrence of value.
 |
 |      Raises ValueError if the value is not present.
 |
 |  reverse(self, /)
 |      Reverse *IN PLACE*.
 |
 |  sort(self, /, *, key=None, reverse=False)
 |      Sort the list in ascending order and return None.
 |
 |      The sort is in-place (i.e. the list itself is modified) and stable (i.e. the
 |      order of two equal elements is maintained).
 |
 |      If a key function is given, apply it once to each list item and sort them,
 |      ascending or descending, according to their function values.
 |
 |      The reverse flag can be set to sort in descending order.
 |
 |  ----------------------------------------------------------------------
 |  Class methods inherited from builtins.list:
 |
 |  __class_getitem__(...) from builtins.type
 |      See PEP 585
 |
 |  ----------------------------------------------------------------------
 |  Static methods inherited from builtins.list:
 |
 |  __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.list:
 |
 |  __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)))
[<Parameter
 {name: 'charge',
  value: -0.8476 e,
  unit: 'e',
  fixed: False,
  constraints: None,
  interactions_name: 'Coulombic',
  functions_name: 'Coulomb',
  tied: False}>, <Parameter
 {name: '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_strenghts = parameters.filter_name('potential_strength').filter_interaction('Bond')
print(bond_potential_strenghts)

# These operations are commutative
print('\nThe order these methods are'
      ' applied does not matter: {}'.format(bond_potential_strenghts
                                             == parameters.filter_interaction('Bond').filter_name('potential_strength')))
[<Parameter
 {name: '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)
[<Parameter
 {name: '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}>, <Parameter
 {name: '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}>, <Parameter
 {name: 'equilibrium_state',
  value: 109.47 deg,
  unit: 'deg',
  fixed: False,
  constraints: None,
  interactions_name: 'BondAngle',
  functions_name: 'HarmonicPotential',
  tied: False}>, <Parameter
 {name: 'equilibrium_state',
  value: 1.0 Ang,
  unit: 'Ang',
  fixed: False,
  constraints: None,
  interactions_name: 'Bond',
  functions_name: 'HarmonicPotential',
  tied: False}>, <Parameter
 {name: '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))
[<Parameter
 {name: 'sigma',
  value: 3.166 Ang,
  unit: 'Ang',
  fixed: False,
  constraints: None,
  interactions_name: 'Dispersion',
  functions_name: 'LennardJones',
  tied: False}>, <Parameter
 {name: 'equilibrium_state',
  value: 1.0 Ang,
  unit: 'Ang',
  fixed: False,
  constraints: None,
  interactions_name: 'Bond',
  functions_name: 'HarmonicPotential',
  tied: False}>]

The same filter can be achieved using lambdas: True