diff --git a/.github/workflows/publish_release.yaml b/.github/workflows/publish_release.yaml new file mode 100644 index 0000000..6a4a3a8 --- /dev/null +++ b/.github/workflows/publish_release.yaml @@ -0,0 +1,20 @@ +name: Publish release +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+.[0-9]+" +jobs: + publish-release: + runs-on: ubuntu-latest + env: + VERSION: ${{ github.ref_name }} + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + - uses: actions/setup-python@v5 + with: + python-version-file: "pyproject.toml" + - run: uv build + - run: uv publish -u __token__ -p ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..34efe2a --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,68 @@ +name: Tests +on: + push: + branches: + - master + pull_request: + workflow_dispatch: +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + - uses: actions/setup-python@v5 + with: + python-version-file: "pyproject.toml" + - run: uv sync --all-extras --dev + - run: uv run ruff check src/ tests/ docs/source/ examples/ + mypy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + - uses: actions/setup-python@v5 + with: + python-version-file: "pyproject.toml" + - run: uv sync --all-extras --dev + - run: uv run mypy src/ tests/ docs/source/ examples/ + ruff-format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + - uses: actions/setup-python@v5 + with: + python-version-file: "pyproject.toml" + - run: uv sync --all-extras --dev + - run: uv run ruff format --check src/ tests/ docs/source/ examples/ + pytest: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + - uses: actions/setup-python@v5 + with: + python-version-file: "pyproject.toml" + - run: uv sync --all-extras --dev + - run: uv run pytest + doctest: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + - uses: actions/setup-python@v5 + with: + python-version-file: "pyproject.toml" + - run: uv sync --all-extras --dev + - run: uv run make -C docs doctest diff --git a/.gitignore b/.gitignore index c9be7f5..9301818 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,3 @@ -# Marcin add-ons -TEST.ipynb -tests/ -pywindow/shape.py -pywindow/graph.py -pywindow/postprocess.py - # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -94,3 +87,7 @@ ENV/ # Rope project settings .ropeproject + +.pytest_cache +.ruff_cache +.venv diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..138e1c3 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,16 @@ +version: 2 + +build: + os: ubuntu-lts-latest + tools: + python: "3.11" + +sphinx: + configuration: docs/source/conf.py + +python: + install: + - method: pip + path: . + extra_requirements: + - dev diff --git a/README.md b/README.md deleted file mode 100644 index 72324f6..0000000 --- a/README.md +++ /dev/null @@ -1,35 +0,0 @@ -![alt tag](docs/pyWINDOW_logo.png) -### Python package for the analysis of structural properties of molecular pores (*porous organic cages*, but also *MOFs* and *metalorganic cages* - see examples directory). - -### Documentation - -https://marcinmiklitz.github.io/pywindow/ - -### How to install `pywindow` - -Git clone this repository or download a zipped version. - -cd pywindow/ - -and run - -python setup.py install - -### Overview - -Structural parameters associated with porous organic molecules that are available -to calculate using `pywindow` software. - -* COM: centre of mass of a molecule. -* dmax: the maximum diameter of a molecule. -* davg: the average diameter of a molecule. -* dvoid: the intrinsic void diameter of a molecule. -* Vvoid: the intrinsic void volume of a molecule. -* dvoid_opt: the optimised intrinsic void diameter of a molecule. -* Vvoid_opt: the optimised intrinsic void volume of a molecule. -* dwindow: the circular diameter of an xth window of a molecule. - -Instructions and examples how to calculate these structural parameters are in form of Jupyter notebooks in the examples directory. - ---------------------------------------------------------------- -MIT License | Copyright (c) 2017 Marcin Miklitz, Jelfs Materials Group diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..8e1d742 --- /dev/null +++ b/README.rst @@ -0,0 +1,45 @@ +:maintainers: + `marcinmiklitz `_ + `andrewtarzia `_ +:documentation: https://pywindowx.readthedocs.io/en/latest/ + +.. figure:: docs/source/_static/pyWINDOW_logo.png + +Overview +======== + +``pywindow`` is a Python package for the analysis of structural properties of +molecular pores (*porous organic cages*, but also *MOFs* and +*metal-organic cages* - see examples directory). + +.. warning:: + This package is currently under development and differs + from the published + `pywindow `_ but + should work with Python >= 3.9. + +Installation +============ + +``pywindow`` can be installed with pip: + +.. code-block:: bash + + pip install pywindowx + +Developer Setup +--------------- + +To develop with ``pywindow``, you can clone the repo and use +`just `_ to setup the dev environment: + +.. code-block:: bash + + just setup + +How To Cite +=========== + +If you use ``pywindow`` please cite + + https://pubs.acs.org/doi/10.1021/acs.jcim.8b00490 diff --git a/bld.bat b/bld.bat deleted file mode 100644 index b9cd616..0000000 --- a/bld.bat +++ /dev/null @@ -1,2 +0,0 @@ -"%PYTHON%" setup.py install --single-version-externally-managed --record=record.txt -if errorlevel 1 exit 1 diff --git a/build.sh b/build.sh deleted file mode 100644 index 913c778..0000000 --- a/build.sh +++ /dev/null @@ -1 +0,0 @@ -$PYTHON setup.py install --record=record.txt # Python command to install the script. diff --git a/docs/.buildinfo b/docs/.buildinfo deleted file mode 100644 index 9bdcfdb..0000000 --- a/docs/.buildinfo +++ /dev/null @@ -1,4 +0,0 @@ -# Sphinx build info version 1 -# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 95d7e8f8dac6cf5135bad3e00f5e4a0c -tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/Makefile b/docs/Makefile index 98726b7..8b6275a 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,12 +1,12 @@ # Minimal makefile for Sphinx documentation # -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -SPHINXPROJ = pywindow -SOURCEDIR = ./source/ -BUILDDIR = .. +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= -W --keep-going +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: diff --git a/docs/_modules/index.html b/docs/_modules/index.html deleted file mode 100644 index af7efe6..0000000 --- a/docs/_modules/index.html +++ /dev/null @@ -1,188 +0,0 @@ - - - - - - - - - - - Overview: module code — pywindow documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - -
- - - - - - -
-
- - - - - - -
-
    -
  • Docs »
  • - -
  • Overview: module code
  • -
  • - - - -
  • -
-
-
-
-
- -

All modules for which code is available

- - -
-
-
- - -
- -
-

- © Copyright 2017, Marcin Miklitz, Jelfs Materials Group. - -

-
- Built with Sphinx using a theme provided by Read the Docs. - -
- -
-
- -
- -
- - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_modules/pywindow/io_tools.html b/docs/_modules/pywindow/io_tools.html deleted file mode 100644 index 354356d..0000000 --- a/docs/_modules/pywindow/io_tools.html +++ /dev/null @@ -1,581 +0,0 @@ - - - - - - - - - - - pywindow.io_tools — pywindow documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - -
- - - - - - -
-
- - - - - - -
- -
-
-
-
- -

Source code for pywindow.io_tools

-"""Module contains classes for input/output processing."""
-
-import os
-import json
-import numpy as np
-
-from .utilities import decipher_atom_key, unit_cell_to_lattice_array
-
-
-class _CorruptedPDBFile(Exception):
-    def __init__(self, message):
-        self.message = message
-
-
-class _CorruptedXYZFile(Exception):
-    def __init__(self, message):
-        self.message = message
-
-
-class _FileAlreadyExists(Exception):
-    def __init__(self, message):
-        self.message = message
-
-
-class _NotADictionary(Exception):
-    def __init__(self, message):
-        self.message = message
-
-
-class _FileTypeError(Exception):
-    def __init__(self, message):
-        self.message = message
-
-
-
[docs]class Input(object): - """Class used to load and process input files.""" - - def __init__(self): - self._load_funcs = { - '.xyz': self._read_xyz, - '.pdb': self._read_pdb, - '.mol': self._read_mol, - } - -
[docs] def load_file(self, filepath): - """ - This function opens any type of a readable file and decompose - the file object into a list, for each line, of lists containing - splitted line strings using space as a spacer. - - Parameters - ---------- - filepath : :class:`str` - The full path or a relative path to any type of file. - - Returns - ------- - :class:`dict` - Returns a dictionary containing the molecular information - extracted from the input files. This information will - vary with file type and information stored in it. - The data is sorted into lists that contain one feature - for example key atom_id: [atom_id_1, atom_id_2] - Over the process of analysis this dictionary will be updated - with new data. - """ - self.file_path = filepath - _, self.file_type = os.path.splitext(filepath) - _, self.file_name = os.path.split(filepath) - with open(filepath) as ffile: - self.file_content = ffile.readlines() - - return (self._load_funcs[self.file_type]())
- -
[docs] def load_rdkit_mol(self, mol): - """ - Return molecular data from :class:`rdkit.Chem.rdchem.Mol` object. - - Parameters - ---------- - mol : :class:`rdkit.Chem.rdchem.Mol` - A molecule object from RDKit. - - Returns - ------- - :class:`dict` - A dictionary with ``elements`` and ``coordinates`` as keys - containing molecular data extracted from - :class:`rdkit.Chem.rdchem.Mol` object. - - """ - self.system = { - 'elements': np.empty( - mol.GetNumAtoms(), dtype=str), - 'coordinates': np.empty((mol.GetNumAtoms(), 3)) - } - for atom in mol.GetAtoms(): - atom_id = atom.GetIdx() - atom_sym = atom.GetSymbol() - x, y, z = mol.GetConformer().GetAtomPosition(atom_id) - self.system['elements'][atom_id] = atom_sym - self.system['coordinates'][atom_id] = x, y, z - return self.system
- - def _read_xyz(self): - """""" - try: - self.system = dict() - self.file_remarks = self.file_content[1] - self.system['elements'] = np.array( - [i.split()[0] for i in self.file_content[2:]]) - self.system['coordinates'] = np.array( - [[float(j[0]), float(j[1]), float(j[2])] - for j in [i.split()[1:] for i in self.file_content[2:]]]) - return self.system - except IndexError: - raise _CorruptedXYZFile( - "The XYZ file is corrupted in some way. For example, an empty " - "line at the end etc. or it is a trajectory. If the latter is " - "the case, please use `trajectory` module, otherwise fix it.") - - def _read_pdb(self): - """""" - if sum([i.count('END ') for i in self.file_content]) > 1: - raise _CorruptedPDBFile( - "Multiple 'END' statements were found in this PDB file." - "If this is a trajectory, use a trajectory module, " - "Otherwise, fix it.") - self.system = dict() - self.system['remarks'] = [ - i for i in self.file_content if i[:6] == 'REMARK' - ] - self.system['unit_cell'] = np.array([ - float(x) - for i in self.file_content for x in - [i[6:15], i[15:24], i[24:33], i[33:40], i[40:47], i[47:54]] - if i[:6] == 'CRYST1' - ]) - if self.system['unit_cell'].any(): - self.system['lattice'] = unit_cell_to_lattice_array(self.system[ - 'unit_cell']) - self.system['atom_ids'] = np.array( - [ - i[12:16].strip() for i in self.file_content - if i[:6] == 'HETATM' or i[:6] == 'ATOM ' - ], - dtype='<U8') - self.system['elements'] = np.array( - [ - i[76:78].strip() for i in self.file_content - if i[:6] == 'HETATM' or i[:6] == 'ATOM ' - ], - dtype='<U8') - self.system['coordinates'] = np.array( - [[float(i[30:38]), float(i[38:46]), float(i[46:54])] - for i in self.file_content - if i[:6] == 'HETATM' or i[:6] == 'ATOM ']) - return self.system - - def _read_mol(self): - """-V3000""" - self.system = dict() - if self.file_content[2] != '\n': - self.system['remarks'] = self.file_content[2] - file_body = [i.split() for i in self.file_content] - elements = [] - coordinates = [] - atom_data = False - for line in file_body: - if len(line) > 2: - if line[2] == 'END' and line[3] == 'ATOM': - atom_data = False - if atom_data is True: - elements.append(line[3]) - coordinates.append(line[4:7]) - if line[2] == 'BEGIN' and line[3] == 'ATOM': - atom_data = True - self.system['elements'] = np.array(elements) - self.system['coordinates'] = np.array(coordinates, dtype=float) - return self.system
- - -
[docs]class Output(object): - """Class used to process and save output files.""" - - def __init__(self): - self.cwd = os.getcwd() - self._save_funcs = { - 'xyz': self._save_xyz, - 'pdb': self._save_pdb, - } - -
[docs] def dump2json(self, obj, filepath, override=False, **kwargs): - """ - Dump a dictionary into a JSON dictionary. - - Uses the json.dump() function. - - Parameters - ---------- - obj : :class:`dict` - A dictionary to be dumpped as JSON file. - - filepath : :class:`str` - The filepath for the dumped file. - - override : :class:`bool` - If True, any file in the filepath will be override. (default=False) - """ - # We make sure that the object passed by the user is a dictionary. - if isinstance(obj, dict): - pass - else: - raise _NotADictionary( - "This function only accepts dictionaries as input") - # We check if the filepath has a json extenstion, if not we add it. - if str(filepath[-4:]) == 'json': - pass - else: - filepath = ".".join((str(filepath), "json")) - # First we check if the file already exists. If yes and the override - # keyword is False (default), we will raise an exception. Otherwise - # the file will be overwritten. - if override is False: - if os.path.isfile(filepath): - raise _FileAlreadyExists( - "The file {0} already exists. Use a different filepath, " - "or set the 'override' kwarg to True.".format(filepath)) - # We dump the object to the json file. Additional kwargs can be passed. - with open(filepath, 'w+') as json_file: - json.dump(obj, json_file, **kwargs)
- -
[docs] def dump2file(self, obj, filepath, override=False, **kwargs): - """ - Dump a dictionary into a file. (Extensions: XYZ or PDB) - - Parameters - ---------- - obj : :class:`dict` - A dictionary containing molecular information. - - filepath : :class:`str` - The filepath for the dumped file. - - override : :class:`bool` - If True, any file in the filepath will be override. (default=False) - """ - # First we check if the file already exists. If yes and the override - # keyword is False (default), we will raise an exception. Otherwise - # the file will be overwritten. - if override is False: - if os.path.isfile(filepath): - raise _FileAlreadyExists( - "The file {0} already exists. Use a different filepath, " - "or set the 'override' kwarg to True.".format(filepath)) - if str(filepath[-3:]) not in self._save_funcs.keys(): - raise _FileTypeError( - "The {0} file extension is " - "not supported for dumping a MolecularSystem or a Molecule. " - "Please use XYZ or PDB.".format(str(filepath[-3:]))) - self._save_funcs[str(filepath[-3:])](obj, filepath, **kwargs)
- - def _save_xyz(self, system, filepath, **kwargs): - """""" - # Initial settings. - settings = { - 'elements': 'elements', - 'coordinates': 'coordinates', - 'remark': " ", - 'decipher': False, - 'forcefield': None, - } - settings.update(kwargs) - # Extract neccessary data. - elements = system['elements'] - coordinates = system['coordinates'] - if settings['decipher'] is True: - elements = np.array([ - decipher_atom_key( - key, forcefield=settings['forcefield']) for key in elements - ]) - string = '{0:0d}\n{1}\n'.format(len(elements), str(settings['remark'])) - for i, j in zip(elements, coordinates): - string += '{0} {1:.2f} {2:.2f} {3:.2f}\n'.format(i, *j) - with open(filepath, 'w+') as file_: - file_.write(string) - - def _save_pdb(self, system, filepath, **kwargs): - """""" - settings = { - 'atom_ids': 'atom_ids', - 'elements': 'elements', - 'coordinates': 'coordinates', - 'cryst': 'unit_cell', - 'connect': None, - 'remarks': None, - 'space_group': None, - 'resName': "MOL", - 'chainID': 'A', - 'resSeq': 1, - 'decipher': False, - 'forcefield': None, - } - settings.update(kwargs) - # We create initial string that we will gradually extend while we - # process the data and in the end it will be written into a pdb file. - string = "REMARK File generated using pyWINDOW." - # Number of items (atoms) in the provided system. - len_ = system[settings['atom_ids']].shape[0] - # We process the remarks, if any, given by the user (optional). - if isinstance(settings['remarks'], (list, tuple)): - # If a list or tuple of remarks each is written at a new line - # with the REMARK prefix not to have to long remark line. - for remark in settings['remarks']: - string = "\n".join([string, 'REMARK {0}'.format(remark)]) - else: - # Otherwise if it's a single string or an int/float we just write - # it under single remark line, otherwise nothing happens. - if isinstance(settings['remarks'], (str, int, float)): - remark = settings['remarks'] - string = "\n".join([string, 'REMARK {0}'.format(remark)]) - # If there is a unit cell (crystal data) provided we need to add it. - if settings['cryst'] in system.keys(): - if system[settings['cryst']].any(): - cryst_line = "CRYST1" - cryst = system[settings['cryst']] - # The user have to provide the crystal data as a list/array - # of six items containing unit cell edges lengths a, b and c - # in x, y and z directions and three angles, or it can be. - # Other options are not allowed for simplicity. It can convert - # from the lattice array using function from utilities. - for i in cryst[:3]: - cryst_line = "".join([cryst_line, "{0:9.3f}".format(i)]) - for i in cryst[3:]: - cryst_line = "".join([cryst_line, "{0:7.2f}".format(i)]) - # This is kind of messy, by default the data written in PDB - # file should be P1 symmetry group therefore containing all - # atom coordinates and not considering symmetry operations. - # But, user can still define a space group if he wishes to. - if settings['space_group'] is not None: - space_group = settings['space_group'] - else: - space_group = "{0}".format("P1") - cryst_line = " ".join([cryst_line, space_group]) - # We add the unit cell parameters to the main string. - string = "\n".join([string, cryst_line]) - # For the sake of code readability we extract interesting data from the - # system. Atom_ids are the atom ids written at the third column of a - # PDB file and the user has here the freedom to use the forcefield - # assigned ones. However, they have to specify it directly using the - # atom_ids key. Otherwise, the 'elements' array from system object - # will be used, that is also used for elements in the last column of - # a PDB file. Other parameters like residue name (resName), chain id - # (chainID) and residue sequence (resSeq) can be controlled by - # appropriate parameter keyword passed to this function, Otherwise - # the default values from settings dictionary are used. - atom_ids = system[settings['atom_ids']] - elements = system[settings['elements']] - # If the 'elements' array of the system need deciphering atom keys this - # is done if the user sets decipher to True. They can also provided - # forcefield, otherwise it's None which equals to DLF. - if settings['decipher'] is True: - elements = np.array([ - decipher_atom_key( - key, forcefield=settings['forcefield']) for key in elements - ]) - coordinates = system[settings['coordinates']] - for i in range(len_): - atom_line = "ATOM {0:5d}".format(i + 1) - atom_id = "{0:4}".format(atom_ids[i].center(4)) - resName = "{0:3}".format(settings['resName']) - chainID = settings['chainID'] - atom_line = " ".join([atom_line, atom_id, resName, chainID]) - resSeq = str(settings['resSeq']).rjust(4) - atom_line = "".join([atom_line, resSeq]) - coor = "{0:8.3f}{1:8.3f}{2:8.3f}".format( - coordinates[i][0], - coordinates[i][1], - coordinates[i][2], ) - atom_line = " ".join([atom_line, coor]) - big_space = "{0}".format(" ".center(22)) - element = "{0:2} ".format(elements[i].rjust(2)) - atom_line = "".join([atom_line, big_space, element]) - string = "\n".join([string, atom_line]) - # The connectivity part is to be written after a function calculating - # connectivity is finished - # "Everything that has a beginning has an end" by Neo. :) - string = "\n".join([string, 'END']) - # Check if .pdb extension is missing from filepath. - if filepath[-4:].lower() != '.pdb': - filepath = ".".join((filepath, 'pdb')) - # Write the string to a a PDB file. - with open(filepath, 'w+') as file: - file.write(string)
-
- -
-
-
- - -
- -
-

- © Copyright 2017, Marcin Miklitz, Jelfs Materials Group. - -

-
- Built with Sphinx using a theme provided by Read the Docs. - -
- -
-
- -
- -
- - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_modules/pywindow/molecular.html b/docs/_modules/pywindow/molecular.html deleted file mode 100644 index 3706090..0000000 --- a/docs/_modules/pywindow/molecular.html +++ /dev/null @@ -1,1350 +0,0 @@ - - - - - - - - - - - pywindow.molecular — pywindow documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - -
- - - - - - -
-
- - - - - - -
- -
-
-
-
- -

Source code for pywindow.molecular

-"""
-Defines :class:`MolecularSystem` and :class:`Molecule` classes.
-
-This module is the most important part of the ``pywindow`` package, as it is
-at the frontfront of the interaction with the user. The two main classes
-defined here: :class:`MolecularSystem` and :class:`Molecule` are used to
-store and analyse single molecules or assemblies of single molecules.
-
-The :class:`MolecularSystem` is used as a first step to the analysis. It allows
-to load data, to refine it (rebuild molecules in a periodic system, decipher
-force field atom ids) and to extract single molecules for analysis as
-:class:`Molecule` instances.
-
-To get started see :class:`MolecularSystem`.
-
-To get started with the analysis of Molecular Dynamic trajectories go to
-:mod:`pywindow.trajectory`.
-
-"""
-
-import os
-import numpy as np
-from copy import deepcopy
-from scipy.spatial import ConvexHull
-
-from .io_tools import Input, Output
-from .utilities import (discrete_molecules,
-                        decipher_atom_key,
-                        molecular_weight,
-                        center_of_mass,
-                        max_dim,
-                        pore_diameter,
-                        opt_pore_diameter,
-                        sphere_volume,
-                        find_windows,
-                        shift_com,
-                        create_supercell,
-                        is_inside_polyhedron,
-                        find_average_diameter,
-                        calculate_pore_shape,
-                        circumcircle,
-                        to_list,
-                        align_principal_ax,
-                        get_inertia_tensor,
-                        get_gyration_tensor,
-                        calc_asphericity,
-                        calc_acylidricity,
-                        calc_relative_shape_anisotropy,
-                        find_windows_new,
-                        calculate_window_diameter,
-                        get_window_com,
-                        window_shape)
-
-
-class _MolecularSystemError(Exception):
-    def __init__(self, message):
-        self.message = message
-
-
-class _NotAModularSystem(Exception):
-    def __init__(self, message):
-        self.message = message
-
-
-class _Shape:
-    """
-    Class containing shape descriptors.
-
-    This class allows other classes, such as :class:`Pore` and
-    :class:`Molecule`, inherit shape descriptors (applicable to any set of
-    points in a 3D Cartesian space, let it be a shape of an intrinsic pore or
-    shape of a molecule) such as asphericity, acylidricity and relative shape
-    anisotropy. This class should not be used by itself.
-
-    """
-
-    @property
-    def _asphericity(self):
-        """
-        Return asphericity of a shape.
-
-        The asphericity of a shape is weighted by the mass assigned to each
-        coordinate (associated with the element). In case if `elements` is
-        `None`, mass of each element = 1 and this returns a non-weighted value.
-
-        Returns
-        -------
-        :class:`float`
-           The asphericity of a shape.
-
-        """
-        return calc_asphericity(self.elements, self.coordinates)
-
-    @property
-    def _acylidricity(self):
-        """
-        Return acylidricity of a shape.
-
-        The acylidricity of a shape is weighted by the mass assigned to each
-        coordinate (associated with the element). In case if `elements` is
-        `None`, mass of each element = 1 and this returns a non-weighted value.
-
-        Returns
-        -------
-        :class:`float`
-           The acylidricity of a shape.
-
-        """
-        return calc_acylidricity(self.elements, self.coordinates)
-
-    @property
-    def _relative_shape_anisotropy(self):
-        """
-        Return relative shape anisotropy of a shape.
-
-        The relative shape anisotropy of a shape is weighted by the mass
-        assigned to each coordinate (associated with the element). In case if
-        `elements` is `None`, mass of each element = 1 and this returns a
-        non-weighted value.
-
-        Returns
-        -------
-        :class:`float`
-           The relative shape anisotropy of a shape.
-
-        """
-        return calc_relative_shape_anisotropy(
-            self.elements, self.coordinates
-            )
-
-    @property
-    def inertia_tensor(self):
-        """
-        Return inertia tensor of a shape.
-
-        The inertia tensor of a shape is weighted by the mass assigned to each
-        coordinate (associated with the element). In case if `elements` is
-        `None`, mass of each element = 1 and this returns a non-weighted value.
-
-        Returns
-        -------
-        :class:`numpy.array`
-           The inertia tensor of a shape.
-
-        """
-        return get_inertia_tensor(
-            self.elements, self.coordinates
-            )
-
-    @property
-    def gyration_tensor(self):
-        """
-        Return gyration tensor of a shape.
-
-        The gyration tensor of a shape is weighted by the mass assigned to each
-        coordinate (associated with the element). In case if `elements` is
-        `None`, mass of each element = 1 and this returns a non-weighted value.
-
-        Returns
-        -------
-        :class:`numpy.array`
-           The gyration tensor of a shape.
-
-        """
-        return get_gyration_tensor(self.elements, self.coordinates)
-
-
-class _Pore(_Shape):
-    """Under development."""
-
-    def __init__(self, elements, coordinates, shape=False, **kwargs):
-        self._elements, self._coordinates = elements, coordinates
-        self.diameter, self.closest_atom = pore_diameter(
-            elements, coordinates, **kwargs)
-        self.spherical_volume = sphere_volume(self.diameter / 2)
-        if 'com' in kwargs.keys():
-            self.centre_coordinates = kwargs['com']
-        else:
-            self.centre_coordinates = center_of_mass(elements, coordinates)
-        self.optimised = False
-
-    def optimise(self, **kwargs):
-        (self.diameter, self.closest_atom,
-         self.centre_coordinates) = opt_pore_diameter(
-             self._elements,
-             self._coordinates,
-             com=self.centre_coordinates,
-             **kwargs)
-        self.spherical_volume = sphere_volume(self.diameter / 2)
-        self.optimised = True
-
-    def get_shape(self):
-        super().__init__(calculate_pore_shape(self._coordinates))
-
-    def reset(self):
-        self.__init__(self._elements, self._coordinates)
-
-
-class _Window:
-    """Under development."""
-
-    def __init__(self, window, key, elements, coordinates, com_adjust):
-        self.raw_data = window
-        self.index = key
-        self.mol_coordinates = coordinates
-        self.mol_elements = elements
-        self.com_correction = com_adjust
-        self.shape = None
-        self.convexhull = None
-
-    def calculate_diameter(self, **kwargs):
-        diameter = calculate_window_diameter(
-            self.raw_data, self.mol_elements, self.mol_coordinates, **kwargs
-        )
-        return diameter
-
-    def calculate_centre_of_mass(self, **kwargs):
-        com = get_window_com(
-            self.raw_data, self.mol_elements, self.mol_coordinates,
-            self.com_correction, **kwargs
-        )
-        return com
-
-    def get_shape(self, **kwargs):
-        self.shape = window_shape(
-            self.raw_data, self.mol_elements, self.mol_coordinates
-        )
-        return self.shape
-
-    def get_convexhull(self):
-        hull = ConvexHull(self.shape)
-        verticesx = np.append(
-            self.shape[hull.vertices, 0], self.shape[hull.vertices, 0][0]
-        )
-        verticesy = np.append(
-            self.shape[hull.vertices, 1], self.shape[hull.vertices, 1][0]
-        )
-        self.convexhull = verticesx, verticesy
-        return self.convexhull
-
-
-
[docs]class Molecule(_Shape): - """ - Container for a single molecule. - - This class is meant for the analysis of single molecules, molecular pores - especially. The object passed to this class should therefore be a finite - and interconnected individuum. - - This class should not be initialised directly, but result from - :func:`MolecularSystem.system_to_molecule()` or - :func:`MolecularSystem.make_modular()`. - - Methods in :class:`Molecule` allow to calculate: - - 1. The maximum diameter of a molecule. - - 2. The average diameter of a molecule. - - 3. The intrinsic void diameter of a molecule. - - 4. The intrinsic void volume of a molecule. - - 5. The optimised intrinsic void diameter of a molecule. - - 6. The optimised intrinsic void volume of a molecule. - - 7. The circular diameter of a window of a molecule. - - Attributes - ---------- - mol : :class:`dict` - The :attr:`Molecular.System.system` dictionary passed to the - :class:`Molecule` which is esentially a container of the information - that compose a molecular entity, such as the coordinates and - atom ids and/or elements. - - no_of_atoms : :class:`int` - The number of atoms in the molecule. - - elements : :class:`numpy.array` - An array containing the elements, as strings, composing the molecule. - - atom_ids : :class:`numpy.array` (conditional) - If the :attr:`Molecule.mol` contains 'atom_ids' keyword, the force - field ids of the elements. - - coordinates : :class:`numpy.array` - The x, y and z atomic Cartesian coordinates of all elements. - - parent_system : :class:`str` - The :attr:`name` of :class:`MolecularSystem` passed to - :class:`Molecule`. - - molecule_id : :class:`any` - The molecule id passed when initialising :class:`Molecule`. - - properties : :class:`dict` - A dictionary that is populated by the output of - :class:`Molecule` methods. - - """ - - def __init__(self, mol, system_name, mol_id): - self._Output = Output() - self.mol = mol - self.no_of_atoms = len(mol['elements']) - self.elements = mol['elements'] - if 'atom_ids' in mol.keys(): - self.atom_ids = mol['atom_ids'] - self.coordinates = mol['coordinates'] - self.parent_system = system_name - self.molecule_id = mol_id - self.properties = {'no_of_atoms': self.no_of_atoms} - self._windows = None - - @classmethod - def _load_rdkit_mol(cls, mol, system_name='rdkit', mol_id=0): - """ - Create a :class:`Molecule` from :class:`rdkit.Chem.rdchem.Mol`. - - To be used only by expert users. - - Parameters - ---------- - mol : :class:`rdkit.Chem.rdchem.Mol` - An RDKit molecule object. - - Returns - ------- - :class:`pywindow.molecular.Molecule` - :class:`Molecule` - - """ - return cls(Input().load_rdkit_mol(mol), system_name, mol_id) - -
[docs] def full_analysis(self, ncpus=1, **kwargs): - """ - Perform a full structural analysis of a molecule. - - This invokes other methods: - - 1. :attr:`molecular_weight()` - - 2. :attr:`calculate_centre_of_mass()` - - 3. :attr:`calculate_maximum_diameter()` - - 4. :attr:`calculate_average_diameter()` - - 5. :attr:`calculate_pore_diameter()` - - 6. :attr:`calculate_pore_volume()` - - 7. :attr:`calculate_pore_diameter_opt()` - - 8. :attr:`calculate_pore_volume_opt()` - - 9. :attr:`calculate_pore_diameter_opt()` - - 10. :attr:`calculate_windows()` - - Parameters - ---------- - ncpus : :class:`int` - Number of CPUs used for the parallelised parts of - :func:`pywindow.utilities.find_windows()`. (default=1=serial) - - Returns - ------- - :attr:`Molecule.properties` - The updated :attr:`Molecule.properties` with returns of all - used methods. - - """ - self.molecular_weight() - self.calculate_centre_of_mass() - self.calculate_maximum_diameter() - self.calculate_average_diameter() - self.calculate_pore_diameter() - self.calculate_pore_volume() - self.calculate_pore_diameter_opt(**kwargs) - self.calculate_pore_volume_opt(**kwargs) - self.calculate_windows(ncpus=ncpus, **kwargs) - # self._circumcircle(**kwargs) - return self.properties
- - def _align_to_principal_axes(self, align_molsys=False): - if align_molsys: - self.coordinates[0] = align_principal_ax_all( - self.elements, self.coordinates - ) - else: - self.coordinates[0] = align_principal_ax( - self.elements, self.coordinates - ) - self.aligned_to_principal_axes = True - - def _get_pore(self): - return Pore(self.elements, self.coordinates) - - def _get_shape(self, **kwargs): - super().__init__(self.coordinates, elements=self.elements) - - def _get_windows(self, **kwargs): - windows = find_windows_new(self.elements, self.coordinates, **kwargs) - if windows: - self.windows = [ - Window(np.array(windows[0][window]), window, windows[1], - windows[2], windows[3]) - for window in windows[0] if window != -1 - ] - return self.windows - else: - return None - -
[docs] def calculate_centre_of_mass(self): - """ - Return the xyz coordinates of the centre of mass of a molecule. - - Returns - ------- - :class:`numpy.array` - The centre of mass of the molecule. - - """ - self.centre_of_mass = center_of_mass(self.elements, self.coordinates) - self.properties['centre_of_mass'] = self.centre_of_mass - return self.centre_of_mass
- -
[docs] def calculate_maximum_diameter(self): - """ - Return the maximum diamension of a molecule. - - Returns - ------- - :class:`float` - The maximum dimension of the molecule. - - """ - self.maxd_atom_1, self.maxd_atom_2, self.maximum_diameter = max_dim( - self.elements, self.coordinates) - self.properties['maximum_diameter'] = { - 'diameter': self.maximum_diameter, - 'atom_1': int(self.maxd_atom_1), - 'atom_2': int(self.maxd_atom_2), - } - return self.maximum_diameter
- -
[docs] def calculate_average_diameter(self, **kwargs): - """ - Return the average diamension of a molecule. - - Returns - ------- - :class:`float` - The average dimension of the molecule. - - """ - self.average_diameter = find_average_diameter( - self.elements, self.coordinates, **kwargs) - return self.average_diameter
- -
[docs] def calculate_pore_diameter(self): - """ - Return the intrinsic pore diameter. - - Returns - ------- - :class:`float` - The intrinsic pore diameter. - - """ - self.pore_diameter, self.pore_closest_atom = pore_diameter( - self.elements, self.coordinates) - self.properties['pore_diameter'] = { - 'diameter': self.pore_diameter, - 'atom': int(self.pore_closest_atom), - } - return self.pore_diameter
- -
[docs] def calculate_pore_volume(self): - """ - Return the intrinsic pore volume. - - Returns - ------- - :class:`float` - The intrinsic pore volume. - - """ - self.pore_volume = sphere_volume(self.calculate_pore_diameter() / 2) - self.properties['pore_volume'] = self.pore_volume - return self.pore_volume
- -
[docs] def calculate_pore_diameter_opt(self, **kwargs): - """ - Return the intrinsic pore diameter (for the optimised pore centre). - - Similarly to :func:`calculate_pore_diameter` this method returns the - the intrinsic pore diameter, however, first a better approximation - of the pore centre is found with optimisation. - - Returns - ------- - :class:`float` - The intrinsic pore diameter. - - """ - (self.pore_diameter_opt, self.pore_opt_closest_atom, - self.pore_opt_COM) = opt_pore_diameter(self.elements, - self.coordinates, **kwargs) - self.properties['pore_diameter_opt'] = { - 'diameter': self.pore_diameter_opt, - 'atom_1': int(self.pore_opt_closest_atom), - 'centre_of_mass': self.pore_opt_COM, - } - return self.pore_diameter_opt
- -
[docs] def calculate_pore_volume_opt(self, **kwargs): - """ - Return the intrinsic pore volume (for the optimised pore centre). - - Similarly to :func:`calculate_pore_volume` this method returns the - the volume intrinsic pore diameter, however, for the - :func:`calculate_pore_diameter_opt` returned value. - - Returns - ------- - :class:`float` - The intrinsic pore volume. - - """ - self.pore_volume_opt = sphere_volume( - self.calculate_pore_diameter_opt(**kwargs) / 2) - self.properties['pore_volume_opt'] = self.pore_volume_opt - return self.pore_volume_opt
- - def _calculate_pore_shape(self, filepath='shape.xyz', **kwargs): - shape = calculate_pore_shape(self.elements, self.coordinates, **kwargs) - shape_obj = {'elements': shape[0], 'coordinates': shape[1]} - Output()._save_xyz(shape_obj, filepath) - return 1 - -
[docs] def calculate_windows(self, **kwargs): - """ - Return the diameters of all windows in a molecule. - - This function first finds and then measures the diameters of all the - window in the molecule. - - Returns - ------- - :class:`numpy.array` - An array of windows' diameters. - - :class:`NoneType` - If no windows were found. - - """ - windows = find_windows(self.elements, self.coordinates, **kwargs) - if windows: - self.properties.update( - { - 'windows': { - 'diameters': windows[0], 'centre_of_mass': windows[1], - } - } - ) - return windows[0] - else: - self.properties.update( - {'windows': {'diameters': None, 'centre_of_mass': None, }} - ) - return None
- -
[docs] def shift_to_origin(self, **kwargs): - """ - Shift a molecule to Origin. - - This function takes the molecule's coordinates and adjust them so that - the centre of mass of the molecule coincides with the origin of the - coordinate system. - - Returns - ------- - None : :class:`NoneType` - - """ - self.coordinates = shift_com(self.elements, self.coordinates, **kwargs) - self._update()
- -
[docs] def molecular_weight(self): - """ - Return the molecular weight of a molecule. - - Returns - ------- - :class:`float` - The molecular weight of the molecule. - - """ - self.MW = molecular_weight(self.elements) - return self.MW
- -
[docs] def dump_properties_json(self, filepath=None, molecular=False, **kwargs): - """ - Dump content of :attr:`Molecule.properties` to a JSON dictionary. - - Parameters - ---------- - filepath : :class:`str` - The filepath for the dumped file. If :class:`None`, the file is - dumped localy with :attr:`molecule_id` as filename. - (defualt=None) - - molecular : :class:`bool` - If False, dump only the content of :attr:`Molecule.properties`, - if True, dump all the information about :class:`Molecule`. - - Returns - ------- - None : :class:`NoneType` - - """ - # We pass a copy of the properties dictionary. - dict_obj = deepcopy(self.properties) - # If molecular data is also required we update the dictionary. - if molecular is True: - dict_obj.update(self.mol) - # If no filepath is provided we create one. - if filepath is None: - filepath = "_".join( - (str(self.parent_system), str(self.molecule_id)) - ) - filepath = '/'.join((os.getcwd(), filepath)) - # Dump the dictionary to json file. - self._Output.dump2json(dict_obj, filepath, default=to_list, **kwargs)
- -
[docs] def dump_molecule(self, filepath=None, include_coms=False, **kwargs): - """ - Dump a :class:`Molecule` to a file (PDB or XYZ). - - Kwargs are passed to :func:`pywindow.io_tools.Output.dump2file()`. - - For validation purposes an overlay of window centres and COMs can also - be dumped as: - - He - for the centre of mass - - Ne - for the centre of the optimised cavity - - Ar - for the centres of each found window - - Parameters - ---------- - filepath : :class:`str` - The filepath for the dumped file. If :class:`None`, the file is - dumped localy with :attr:`molecule_id` as filename. - (defualt=None) - - include_coms : :class:`bool` - If True, dump also with an overlay of window centres and COMs. - (default=False) - - Returns - ------- - None : :class:`NoneType` - - """ - # If no filepath is provided we create one. - if filepath is None: - filepath = "_".join( - (str(self.parent_system), str(self.molecule_id))) - filepath = '/'.join((os.getcwd(), filepath)) - filepath = '.'.join((filepath, 'pdb')) - # Check if there is an 'atom_ids' keyword in the self.mol dict. - # Otherwise pass to the dump2file atom_ids='elements'. - if 'atom_ids' not in self.mol.keys(): - atom_ids = 'elements' - else: - atom_ids = 'atom_ids' - # Dump molecule into a file. - # If coms are to be included additional steps are required. - # First deepcopy the molecule - if include_coms is True: - mmol = deepcopy(self.mol) - # add centre of mass (centre of not optimised pore) as 'He'. - mmol['elements'] = np.concatenate( - (mmol['elements'], np.array(['He']))) - if 'atom_ids' not in self.mol.keys(): - pass - else: - mmol['atom_ids'] = np.concatenate( - (mmol['atom_ids'], np.array(['He']))) - mmol['coordinates'] = np.concatenate( - (mmol['coordinates'], - np.array([self.properties['centre_of_mass']]))) - # add centre of pore optimised as 'Ne'. - mmol['elements'] = np.concatenate( - (mmol['elements'], np.array(['Ne']))) - if 'atom_ids' not in self.mol.keys(): - pass - else: - mmol['atom_ids'] = np.concatenate( - (mmol['atom_ids'], np.array(['Ne']))) - mmol['coordinates'] = np.concatenate( - (mmol['coordinates'], np.array( - [self.properties['pore_diameter_opt']['centre_of_mass']]))) - # add centre of windows as 'Ar'. - if self.properties['windows']['centre_of_mass'] is not None: - range_ = range( - len(self.properties['windows']['centre_of_mass'])) - for com in range_: - mmol['elements'] = np.concatenate( - (mmol['elements'], np.array(['Ar']))) - if 'atom_ids' not in self.mol.keys(): - pass - else: - mmol['atom_ids'] = np.concatenate( - (mmol['atom_ids'], - np.array(['Ar{0}'.format(com + 1)]))) - mmol['coordinates'] = np.concatenate( - (mmol['coordinates'], np.array([ - self.properties['windows']['centre_of_mass'][com] - ]))) - self._Output.dump2file(mmol, filepath, atom_ids=atom_ids, **kwargs) - - else: - self._Output.dump2file( - self.mol, filepath, atom_ids=atom_ids, **kwargs)
- - def _update(self): - self.mol['coordinates'] = self.coordinates - self.calculate_centre_of_mass() - self.calculate_pore_diameter_opt() - - def _circumcircle(self, **kwargs): - windows = circumcircle(self.coordinates, kwargs['atom_sets']) - if 'output' in kwargs: - if kwargs['output'] == 'windows': - self.properties['circumcircle'] = {'diameter': windows, } - else: - if windows is not None: - self.properties['circumcircle'] = { - 'diameter': windows[0], - 'centre_of_mass': windows[1], - } - else: - self.properties['circumcircle'] = { - 'diameter': None, - 'centre_of_mass': None, - } - return windows
- - -
[docs]class MolecularSystem: - """ - Container for the molecular system. - - To load input and initialise :class:`MolecularSystem`, one of the - :class:`MolecularSystem` classmethods (:func:`load_file()`, - :func:`load_rdkit_mol()` or :func:`load_system()`) should be used. - :class:`MolecularSystem` **should not be initialised by itself.** - - Examples - -------- - 1. Using file as an input: - - .. code-block:: python - - pywindow.molecular.MolecularSystem.load_file(`filepath`) - - 2. Using RDKit molecule object as an input: - - .. code-block:: python - - pywindow.molecular.MolecularSystem.load_rdkit_mol(rdkit.Chem.rdchem.Mol) - - 3. Using a dictionary (or another :attr:`MoleculeSystem.system`) as input: - - .. code-block:: python - - pywindow.molecular.MolecularSystem.load_system({...}) - - Attributes - ---------- - system_id : :class:`str` or :class:`int` - The input filename or user defined. - - system : :class:`dict` - A dictionary containing all the information extracted from input. - - molecules : :class:`list` - A list containing all the returned :class:`Molecule` s after using - :func:`make_modular()`. - - """ - - def __init__(self): - self._Input = Input() - self._Output = Output() - self.system_id = 0 - -
[docs] @classmethod - def load_file(cls, filepath): - """ - Create a :class:`MolecularSystem` from an input file. - - Recognized input file formats: XYZ, PDB and MOL (V3000). - - Parameters - ---------- - filepath : :class:`str` - The input's filepath. - - Returns - ------- - :class:`pywindow.molecular.MolecularSystem` - :class:`MolecularSystem` - - """ - obj = cls() - obj.system = obj._Input.load_file(filepath) - obj.filename = os.path.basename(filepath) - obj.system_id = obj.filename.split(".")[0] - obj.name, ext = os.path.splitext(obj.filename) - return obj
- -
[docs] @classmethod - def load_rdkit_mol(cls, mol): - """ - Create a :class:`MolecularSystem` from :class:`rdkit.Chem.rdchem.Mol`. - - Parameters - ---------- - mol : :class:`rdkit.Chem.rdchem.Mol` - An RDKit molecule object. - - Returns - ------- - :class:`pywindow.molecular.MolecularSystem` - :class:`MolecularSystem` - - """ - obj = cls() - obj.system = obj._Input.load_rdkit_mol(mol) - return obj
- -
[docs] @classmethod - def load_system(cls, dict_, system_id='system'): - """ - Create a :class:`MolecularSystem` from a python :class:`dict`. - - As the loaded :class:`MolecularSystem` is storred as a :class:`dict` in - the :class:`MolecularSystem.system` it can also be loaded directly from - a :class:`dict` input. This feature is used by :mod:`trajectory` that - extracts trajectory frames as dictionaries and returns them - as :class:`MolecularSystem` objects through this classmethod. - - Parameters - ---------- - dict_ : :class:`dict` - A python dictionary. - - system_id : :class:`str` or :class:'int', optional - Inherited or user defined system id. (default='system') - - Returns - ------- - :class:`pywindow.molecular.MolecularSystem` - :class:`MolecularSystem` - - """ - obj = cls() - obj.system = dict_ - obj.system_id = system_id - return obj
- -
[docs] def rebuild_system(self, override=False, **kwargs): - """ - Rebuild molecules in molecular system. - - Parameters - ---------- - override : :class:`bool`, optional (default=False) - If False the rebuild molecular system is returned as a new - :class:`MolecularSystem`, if True, the current - :class:`MolecularSystem` is modified. - - """ - # First we create a 3x3x3 supercell with the initial unit cell in the - # centre and the 26 unit cell translations around to provide all the - # atom positions necessary for the molecules passing through periodic - # boundary reconstruction step. - supercell_333 = create_supercell(self.system, **kwargs) - # smolsys = self.load_system(supercell_333, self.system_id + '_311') - # smolsys.dump_system(override=True) - discrete = discrete_molecules(self.system, rebuild=supercell_333) - # This function overrides the initial data for 'coordinates', - # 'atom_ids', and 'elements' instances in the 'system' dictionary. - coordinates = np.array([], dtype=np.float64).reshape(0, 3) - atom_ids = np.array([]) - elements = np.array([]) - for i in discrete: - coordinates = np.concatenate( - [coordinates, i['coordinates']], axis=0 - ) - atom_ids = np.concatenate([atom_ids, i['atom_ids']], axis=0) - elements = np.concatenate([elements, i['elements']], axis=0) - rebuild_system = { - 'coordinates': coordinates, - 'atom_ids': atom_ids, - 'elements': elements - } - if override is True: - self.system.update(rebuild_system) - return None - else: - return self.load_system(rebuild_system)
- -
[docs] def swap_atom_keys(self, swap_dict, dict_key='atom_ids'): - """ - Swap a force field atom id for another user-defined value. - - This modified all values in :attr:`MolecularSystem.system['atom_ids']` - that match criteria. - - This function can be used to decipher a whole forcefield if an - appropriate dictionary is passed to the function. - - Example - ------- - In this example all atom ids 'he' will be exchanged to 'H'. - - .. code-block:: python - - pywindow.molecular.MolecularSystem.swap_atom_keys({'he': 'H'}) - - Parameters - ---------- - swap_dict: :class:`dict` - A dictionary containg force field atom ids (keys) to be swapped - with corresponding values (keys' arguments). - - dict_key: :class:`str` - A key in :attr:`MolecularSystem.system` dictionary to perform the - atom keys swapping operation on. (default='atom_ids') - - Returns - ------- - None : :class:`NoneType` - - """ - # Similar situation to the one from decipher_atom_keys function. - if 'atom_ids' not in self.system.keys(): - dict_key = 'elements' - for atom_key in range(len(self.system[dict_key])): - for key in swap_dict.keys(): - if self.system[dict_key][atom_key] == key: - self.system[dict_key][atom_key] = swap_dict[key]
- -
[docs] def decipher_atom_keys(self, forcefield='DLF', dict_key='atom_ids'): - """ - Decipher force field atom ids. - - This takes all values in :attr:`MolecularSystem.system['atom_ids']` - that match force field type criteria and creates - :attr:`MolecularSystem.system['elements']` with the corresponding - periodic table of elements equivalents. - - If a forcefield is not supported by this method, the - :func:`MolecularSystem.swap_atom_keys()` can be used instead. - - DLF stands for DL_F notation. - - See: C. W. Yong, Descriptions and Implementations of DL_F Notation: A - Natural Chemical Expression System of Atom Types for Molecular - Simulations, J. Chem. Inf. Model., 2016, 56, 1405–1409. - - Parameters - ---------- - forcefield : :class:`str` - The forcefield used to decipher atom ids. Allowed (not case - sensitive): 'OPLS', 'OPLS2005', 'OPLSAA', 'OPLS3', 'DLF', 'DL_F'. - (default='DLF') - - dict_key : :class:`str` - The :attr:`MolecularSystem.system` dictionary key to the array - containing the force field atom ids. (default='atom_ids') - - Returns - ------- - None : :class:`NoneType` - - """ - # In case there is no 'atom_ids' key we try 'elements'. This is for - # XYZ and MOL files mostly. But, we keep the dict_key keyword for - # someone who would want to decipher 'elements' even if 'atom_ids' key - # is present in the system's dictionary. - if 'atom_ids' not in self.system.keys(): - dict_key = 'elements' - # I do it on temporary object so that it only finishes when successful - temp = deepcopy(self.system[dict_key]) - for element in range(len(temp)): - temp[element] = "{0}".format( - decipher_atom_key( - temp[element], forcefield=forcefield)) - self.system['elements'] = temp
- -
[docs] def make_modular(self, rebuild=False): - """ - Find and return all :class:`Molecule` s in :class:`MolecularSystem`. - - This function populates :attr:`MolecularSystem.molecules` with - :class:`Molecule` s. - - Parameters - ---------- - rebuild : :class:`bool` - If True, run first the :func:`rebuild_system()`. (default=False) - - Returns - ------- - None : :class:`NoneType` - - """ - if rebuild is True: - supercell_333 = create_supercell(self.system) - else: - supercell_333 = None - dis = discrete_molecules(self.system, rebuild=supercell_333) - self.no_of_discrete_molecules = len(dis) - self.molecules = {} - for i in range(len(dis)): - self.molecules[i] = Molecule(dis[i], self.system_id, i)
- -
[docs] def system_to_molecule(self): - """ - Return :class:`MolecularSystem` as a :class:`Molecule` directly. - - Only to be used conditionally, when the :class:`MolecularSystem` is a - discrete molecule and no input pre-processing is required. - - Returns - ------- - :class:`pywindow.molecular.Molecule` - :class:`Molecule` - """ - return Molecule(self.system, self.system_id, 0)
- - def _get_pores(self, sampling_points): - """ Under development.""" - pores = [] - for point in sampling_points: - pores.append( - Pore( - self.system['elements'], - self.system['coordinates'], - com=point)) - return pores - -
[docs] def dump_system(self, filepath=None, modular=False, **kwargs): - """ - Dump a :class:`MolecularSystem` to a file (PDB or XYZ). - - Kwargs are passed to :func:`pywindow.io_tools.Output.dump2file()`. - - Parameters - ---------- - filepath : :class:`str` - The filepath for the dumped file. If :class:`None`, the file is - dumped localy with :attr:`system_id` as filename. - (defualt=None) - - modular : :class:`bool` - If False, dump the :class:`MolecularSystem` as in - :attr:`MolecularSystem.system`, if True, dump the - :class:`MolecularSystem` as catenated :class:Molecule objects - from :attr:`MolecularSystem.molecules` - - Returns - ------- - None : :class:`NoneType` - - """ - # If no filepath is provided we create one. - if filepath is None: - filepath = '/'.join((os.getcwd(), str(self.system_id))) - filepath = '.'.join((filepath, 'pdb')) - # If modular is True substitute the molecular data for modular one. - system_dict = deepcopy(self.system) - if modular is True: - elements = np.array([]) - atom_ids = np.array([]) - coor = np.array([]).reshape(0, 3) - for mol_ in self.molecules: - mol = self.molecules[mol_] - elements = np.concatenate((elements, mol.mol['elements'])) - atom_ids = np.concatenate((atom_ids, mol.mol['atom_ids'])) - coor = np.concatenate((coor, mol.mol['coordinates']), axis=0) - system_dict['elements'] = elements - system_dict['atom_ids'] = atom_ids - system_dict['coordinates'] = coor - # Check if there is an 'atom_ids' keyword in the self.mol dict. - # Otherwise pass to the dump2file atom_ids='elements'. - # This is mostly for XYZ files and not deciphered trajectories. - if 'atom_ids' not in system_dict.keys(): - atom_ids = 'elements' - else: - atom_ids = 'atom_ids' - # Dump system into a file. - self._Output.dump2file( - system_dict, filepath, atom_ids=atom_ids, **kwargs)
- -
[docs] def dump_system_json(self, filepath=None, modular=False, **kwargs): - """ - Dump a :class:`MolecularSystem` to a JSON dictionary. - - The dumped JSON dictionary, with :class:`MolecularSystem`, can then be - loaded through a JSON loader and then through :func:`load_system()` - to retrieve a :class:`MolecularSystem`. - - Kwargs are passed to :func:`pywindow.io_tools.Output.dump2json()`. - - Parameters - ---------- - filepath : :class:`str` - The filepath for the dumped file. If :class:`None`, the file is - dumped localy with :attr:`system_id` as filename. - (defualt=None) - - modular : :class:`bool` - If False, dump the :class:`MolecularSystem` as in - :attr:`MolecularSystem.system`, if True, dump the - :class:`MolecularSystem` as catenated :class:Molecule objects - from :attr:`MolecularSystem.molecules` - - Returns - ------- - None : :class:`NoneType` - - """ - # We pass a copy of the properties dictionary. - dict_obj = deepcopy(self.system) - # In case we want a modular system. - if modular is True: - try: - if self.molecules: - pass - except AttributeError: - raise _NotAModularSystem( - "This system is not modular. Please, run first the " - "make_modular() function of this class.") - dict_obj = {} - for molecule in self.molecules: - mol_ = self.molecules[molecule] - dict_obj[molecule] = mol_.mol - # If no filepath is provided we create one. - if filepath is None: - filepath = '/'.join((os.getcwd(), str(self.system_id))) - # Dump the dictionary to json file. - self._Output.dump2json(dict_obj, filepath, default=to_list, **kwargs)
-
- -
-
-
- - -
- -
-

- © Copyright 2017, Marcin Miklitz, Jelfs Materials Group. - -

-
- Built with Sphinx using a theme provided by Read the Docs. - -
- -
-
- -
- -
- - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_modules/pywindow/trajectory.html b/docs/_modules/pywindow/trajectory.html deleted file mode 100644 index de196df..0000000 --- a/docs/_modules/pywindow/trajectory.html +++ /dev/null @@ -1,1883 +0,0 @@ - - - - - - - - - - - pywindow.trajectory — pywindow documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - -
- - - - - - -
-
- - - - - - -
- -
-
-
-
- -

Source code for pywindow.trajectory

-"""
-Module intended for the analysis of molecular dynamics trajectories.
-
-The trajectory file (DL_POLY_C:HISTORY, PDB or XYZ) should be loaded with
-the one of the corresponding classes (DLPOLY, PDB or XYZ, respectively).
-
-Example
--------
-In this example a DL_POLY_C HISTORY trajectory file is loaded.
-
-.. code-block:: python
-
-    pywindow.trajectory.DLPOLY('path/to/HISTORY')
-
-Then, each of the trajectory frames can be extracted and returned as a
-:class:`pywindow.molecular.MolecularSystem` object for analysis. See
-:mod:`pywindow.molecular` docstring for more information.
-
-Alternatively, the analysis can be performed on a whole or a chunk of
-the trajectory with the :func:`analysis()` function. The benefit is
-that the analysis can be performed in parallel and the results stored as a
-single JSON dictionary in a straightforward way. Also, the deciphering of the
-force field atom ids and the rebuilding of molecules can be applied to each
-frame in a consitent and automated manner. The downfall is that at the
-moment it is not possible to choose the set of parameters that are being
-calculated in the :class:`pywindow.molecular.Molecule` as the
-:func:`pywindow.molecular.Molecule.full_analysis()` is invoked by default.
-However, the computational cost of calculating majority of the structural
-properties is miniscule and it is usually the
-:func:`pywindow.molecular.MolecularSystem.rebuild_system()` step that is the
-bottleneck.
-
-"""
-import os
-import numpy as np
-from copy import deepcopy
-from mmap import mmap, ACCESS_READ
-from contextlib import closing
-from multiprocessing import Pool
-
-from .io_tools import Input, Output
-from .utilities import (
-    is_number, create_supercell, lattice_array_to_unit_cell, to_list
-)
-from .molecular import MolecularSystem
-
-
-class _ParallelAnalysisError(Exception):
-    def __init__(self, message):
-        self.message = message
-
-
-class _TrajectoryError(Exception):
-    def __init__(self, message):
-        self.message = message
-
-
-class _FormatError(Exception):
-    def __init__(self, message):
-        self.message = message
-
-
-class _FunctionError(Exception):
-    def __init__(self, message):
-        self.message = message
-
-
-
[docs]def make_supercell(system, matrix, supercell=[1, 1, 1]): - """ - Return a supercell. - - This functions takes the input unitcell and creates a supercell of it that - is returned as a new :class:`pywindow.molecular.MolecularSystem`. - - Parameters - ---------- - system : :attr:`pywindow.molecular.MolecularSystem.system` - The unit cell for creation of the supercell - - matrix : :class:`numpy.array` - The unit cell parameters in form of a lattice. - - supercell : :class:`list`, optional - A list that specifies the size of the supercell in the a, b and c - direction. (default=[1, 1, 1]) - - Returns - ------- - :class:`pywindow.molecular.MolecularSystem` - Returns the created supercell as a new :class:`MolecularSystem`. - - """ - user_supercell = [[1, supercell[0]], [1, supercell[1]], [1, supercell[1]]] - system = create_supercell(system, matrix, supercell=user_supercell) - return MolecularSystem.load_system(system)
- - -
[docs]class DLPOLY(object): - """ - A container for a DL_POLY_C type trajectory (HISTORY). - - This function takes a DL_POLY_C trajectory file and maps it for the - binary points in the file where each frame starts/ends. This way the - process is fast, as it not require loading the trajectory into computer - memory. When a frame is being extracted, it is only this frame that gets - loaded to the memory. - - Frames can be accessed individually and loaded as an unmodified string, - returned as a :class:`pywindow.molecular.MolecularSystem` (and analysed), - dumped as PDB or XYZ or JSON (if dumped as a - :attr:`pywindow.molecular.MolecularSystem.system`) - - Attributes - ---------- - filepath : :class:`str` - The filepath. - - system_id : :class:`str` - The system id inherited from the filename. - - frames : :class:`dict` - A dictionary that is populated, on the fly, with the extracted frames. - - analysis_output : :class:`dict` - A dictionary that is populated, on the fly, with the analysis output. - - """ - def __init__(self, filepath): - # Image conventions - periodic boundary key. - self._imcon = { - 0: 'nonperiodic', - 1: 'cubic', - 2: 'orthorhombic', - 3: 'parallelepiped', - 4: 'truncated octahedral', - 5: 'rhombic dodecahedral', - 6: 'x-y parallelogram', - 7: 'hexagonal prism', - } - # Trajectory key - content type. - self._keytrj = { - 0: 'coordinates', - 1: 'coordinates and velocities', - 2: 'coordinates, velocities and forces', - } - self.filepath = filepath - self.system_id = os.path.basename(filepath) - self.frames = {} - self.analysis_output = {} - # Check the history file at init, if no errors, proceed to mapping. - self._check_HISTORY() - # Map the trajectory file at init. - self._map_HISTORY() - - def _map_HISTORY(self): - """ """ - self.trajectory_map = {} - with open(self.filepath, 'r') as trajectory_file: - with closing( - mmap( - trajectory_file.fileno(), 0, - access=ACCESS_READ)) as mapped_file: - progress = 0 - line = 0 - frame = 0 - cell_param_line = 0 - # We need to first process trajectory file's header. - header_flag = True - while progress <= len(mapped_file): - line = line + 1 - # We read a binary data from a mapped file. - bline = mapped_file.readline() - # If the bline length equals zero we terminate. - # We reached end of the file but still add the last frame! - if len(bline) == 0: - self.trajectory_map[frame] = [frame_start, progress] - frame = frame + 1 - break - # We need to decode byte line into an utf-8 string. - sline = bline.decode("utf-8").strip('\n').split() - # We extract map's byte coordinates for each frame - if header_flag is False: - if sline[0] == 'timestep': - self.trajectory_map[frame] = [ - frame_start, progress - ] - frame_start = progress - frame = frame + 1 - # Here we extract the map's byte coordinates for the header - # And also the periodic system type needed for later. - if header_flag is True: - if sline[0] == 'timestep': - self.trajectory_map['header'] = self._decode_head( - [0, progress]) - frame_start = progress - header_flag = False - progress = progress + len(bline) - self.no_of_frames = frame - - def _decode_head(self, header_coordinates): - start, end = header_coordinates - with open(self.filepath, 'r') as trajectory_file: - with closing( - mmap( - trajectory_file.fileno(), 0, - access=ACCESS_READ)) as mapped_file: - header = [ - i.split() - for i in mapped_file[start:end].decode("utf-8").split('\n') - ] - header = [int(i) for i in header[1]] - self.periodic_boundary = self._imcon[header[1]] - self.content_type = self._keytrj[header[0]] - self.no_of_atoms = header[2] - return header - -
[docs] def get_frames(self, frames='all', override=False, **kwargs): - """ - Extract frames from the trajectory file. - - Depending on the passed parameters a frame, a list of particular - frames, a range of frames (from, to), or all frames can be extracted - with this function. - - Parameters - ---------- - frames : :class:`int` or :class:`list` or :class:`touple` or :class:`str` - Specified frame (:class:`int`), or frames (:class:`list`), or - range (:class:`touple`), or `all`/`everything` (:class:`str`). - (default=`all`) - - override : :class:`bool` - If True, a frame already storred in :attr:`frames` can be override. - (default=False) - - extract_data : :class:`bool`, optional - If False, a frame is returned as a :class:`str` block as in the - trajectory file. Ohterwise, it is extracted and returned as - :class:`pywindow.molecular.MolecularSystem`. (default=True) - - swap_atoms : :class:`dict`, optional - If this kwarg is passed with an appropriate dictionary a - :func:`pywindow.molecular.MolecularSystem.swap_atom_keys()` will - be applied to the extracted frame. - - forcefield : :class:`str`, optional - If this kwarg is passed with appropriate forcefield keyword a - :func:`pywindow.molecular.MolecularSystem.decipher_atom_keys()` - will be applied to the extracted frame. - - Returns - ------- - :class:`pywindow.molecular.MolecularSystem` - If a single frame is extracted. - - None : :class:`NoneType` - If more than one frame is extracted, the frames are returned to - :attr:`frames` - - """ - if override is True: - self.frames = {} - if isinstance(frames, int): - frame = self._get_frame( - self.trajectory_map[frames], frames, **kwargs) - if frames not in self.frames.keys(): - self.frames[frames] = frame - return frame - if isinstance(frames, list): - for frame in frames: - if frame not in self.frames.keys(): - self.frames[frame] = self._get_frame( - self.trajectory_map[frame], frame, **kwargs) - if isinstance(frames, tuple): - for frame in range(frames[0], frames[1]): - if frame not in self.frames.keys(): - self.frames[frame] = self._get_frame( - self.trajectory_map[frame], frame, **kwargs) - if isinstance(frames, str): - if frames in ['all', 'everything']: - for frame in range(0, self.no_of_frames): - if frame not in self.frames.keys(): - self.frames[frame] = self._get_frame( - self.trajectory_map[frame], frame, **kwargs)
- - def _get_frame(self, frame_coordinates, frame_no, **kwargs): - kwargs_ = { - "extract_data": True - } - kwargs_.update(kwargs) - start, end = frame_coordinates - with open(self.filepath, 'r') as trajectory_file: - with closing( - mmap( - trajectory_file.fileno(), 0, - access=ACCESS_READ)) as mapped_file: - if kwargs_["extract_data"] is False: - return mapped_file[start:end].decode("utf-8") - else: - # [:-1] because the split results in last list empty. - frame = [ - i.split() - for i in mapped_file[start:end].decode("utf-8").split( - '\n') - ][:-1] - decoded_frame = self._decode_frame(frame) - molsys = MolecularSystem.load_system( - decoded_frame, - "_".join([self.system_id, str(frame_no)])) - if 'swap_atoms' in kwargs: - molsys.swap_atom_keys(kwargs['swap_atoms']) - if 'forcefield' in kwargs: - molsys.decipher_atom_keys(kwargs['forcefield']) - return molsys - - def _decode_frame(self, frame): - frame_data = { - 'frame_info': { - 'nstep': int(frame[0][1]), - 'natms': int(frame[0][2]), - 'keytrj': int(frame[0][3]), - 'imcon': int(frame[0][4]), - 'tstep': float(frame[0][5]) - } - } - start_line = 1 - if frame_data['frame_info']['imcon'] in [1, 2, 3]: - frame_data['lattice'] = np.array(frame[1:4], dtype=float).T - frame_data['unit_cell'] = lattice_array_to_unit_cell(frame_data[ - 'lattice']) - start_line = 4 - # Depending on what the trajectory key is (see __init__) we need - # to extract every second/ third/ fourth line for elements and coor. - elements = [] - coordinates = [] - velocities = [] - forces = [] - for i in range(len(frame[start_line:])): - i_ = i + start_line - if frame_data['frame_info']['keytrj'] == 0: - if i % 2 == 0: - elements.append(frame[i_][0]) - if i % 2 == 1: - coordinates.append(frame[i_]) - if frame_data['frame_info']['keytrj'] == 1: - if i % 3 == 0: - elements.append(frame[i_][0]) - if i % 3 == 1: - coordinates.append(frame[i_]) - if i % 3 == 2: - velocities.append(frame[i_]) - if frame_data['frame_info']['keytrj'] == 2: - if i % 4 == 0: - elements.append(frame[i_][0]) - if i % 4 == 1: - coordinates.append(frame[i_]) - if i % 4 == 2: - velocities.append(frame[i_]) - if i % 4 == 3: - forces.append(frame[i_]) - frame_data['atom_ids'] = np.array(elements) - frame_data['coordinates'] = np.array(coordinates, dtype=float) - if velocities: - frame_data['velocities'] = np.array(velocities, dtype=float) - if forces: - frame_data['forces'] = np.array(forces, dtype=float) - return frame_data - -
[docs] def analysis( - self, frames='all', ncpus=1, _ncpus=1, override=False, **kwargs - ): - """ - Perform structural analysis on a frame/ set of frames. - - Depending on the passed parameters a frame, a list of particular - frames, a range of frames (from, to), or all frames can be analysed - with this function. - - The analysis is performed on each frame and each discrete molecule in - that frame separately. The steps are as follows: - - 1. A frame is extracted and returned as a :class:`MolecularSystem`. - 2. If `swap_atoms` is set the atom ids are swapped. - 3. If `forcefield` is set the atom ids are deciphered. - 4. If `rebuild` is set the molecules in the system are rebuild. - 5. Each discrete molecule is extracted as :class:`Molecule` - 6. Each molecule is analysed with :func:`Molecule.full_analysis()` - 7. Analysis output populates the :attr:`analysis_output` dictionary. - - As the analysis of trajectories often have to be unique, many options - are conditional. - - A side effect of this function is that the analysed frames are also - returned to the :attr:`frames` mimicking the behaviour of the - :func:`get_frames()`. - - Parameters - ---------- - frames : :class:`int` or :class:`list` or :class:`touple` or :class:`str` - Specified frame (:class:`int`), or frames (:class:`list`), or - range (:class:`touple`), or `all`/`everything` (:class:`str`). - (default='all') - - override : :class:`bool` - If True, an output already storred in :attr:`analysis_output` can - be override. (default=False) - - swap_atoms : :class:`dict`, optional - If this kwarg is passed with an appropriate dictionary a - :func:`pywindow.molecular.MolecularSystem.swap_atom_keys()` will - be applied to the extracted frame. - - forcefield : :class:`str`, optional - If this kwarg is passed with appropriate forcefield keyword a - :func:`pywindow.molecular.MolecularSystem.decipher_atom_keys()` - will be applied to the extracted frame. - - modular : :class:`bool`, optional - If this kwarg is passed a - :func:`pywindow.molecular.MolecularSystem.make_modular()` - will be applied to the extracted frame. (default=False) - - rebuild : :class:`bool`, optional - If this kwarg is passed a `rebuild=True` is passed to - :func:`pywindow.molecular.MolecularSystem.make_modular()` that - will be applied to the extracted frame. (default=False) - - ncpus : :class:`int`, optional - If ncpus > 1, then the analysis is performed in parallel for the - specified number of parallel jobs. Otherwise, it runs in serial. - (default=1) - - Returns - ------- - None : :class:`NoneType` - The function returns `None`, the analysis output is - returned to :attr:`analysis_output` dictionary. - - """ - frames_for_analysis = [] - # First populate the frames_for_analysis list. - if isinstance(frames, int): - frames_for_analysis.append(frames) - if isinstance(frames, list): - for frame in frames: - if isinstance(frame, int): - frames_for_analysis.append(frame) - else: - raise _FunctionError( - "The list should be populated with integers only." - ) - if isinstance(frames, tuple): - if isinstance(frames[0], int) and isinstance(frames[1], int): - for frame in range(frames[0], frames[1]): - frames_for_analysis.append(frame) - else: - raise _FunctionError( - "The tuple should contain only two integers " - "for the begining and the end of the frames range." - ) - if isinstance(frames, str): - if frames in ['all', 'everything']: - for frame in range(0, self.no_of_frames): - frames_for_analysis.append(frame) - else: - raise _FunctionError( - "Didn't recognise the keyword. (see manual)" - ) - # The override keyword by default is False. So we check if any of the - # frames were already analysed and if so we delete them from the list. - # However, if the override is set to True, then we just proceed. - if override is False: - frames_for_analysis_new = [] - for frame in frames_for_analysis: - if frame not in self.analysis_output.keys(): - frames_for_analysis_new.append(frame) - frames_for_analysis = frames_for_analysis_new - if ncpus == 1: - for frame in frames_for_analysis: - analysed_frame = self._analysis_serial(frame, _ncpus, **kwargs) - self.analysis_output[frame] = analysed_frame - if ncpus > 1: - self._analysis_parallel(frames_for_analysis, ncpus, **kwargs)
- - def _analysis_serial(self, frame, _ncpus, **kwargs): - settings = { - 'rebuild': False, - 'modular': False, - } - settings.update(kwargs) - molecular_system = self._get_frame( - self.trajectory_map[frame], frame, extract_data=True, **kwargs - ) - if settings['modular'] is True: - molecular_system.make_modular(rebuild=settings['rebuild']) - molecules = molecular_system.molecules - else: - molecules = {'0': molecular_system.system_to_molecule()} - results = {} - for molecule in molecules: - mol = molecules[molecule] - if 'molsize' in settings: - molsize = settings['molsize'] - if isinstance(molsize, int): - if mol.no_of_atoms == molsize: - results[molecule] = mol.full_analysis( - _ncpus=_ncpus, **kwargs) - if isinstance(molsize, tuple) and isinstance(molsize[0], str): - if molsize[0] in ['bigger', 'greater', 'larger', 'more']: - if mol.no_of_atoms > molsize[1]: - results[molecule] = mol.full_analysis( - _ncpus=_ncpus, **kwargs) - if molsize[0] in ['smaller', 'less']: - if mol.no_of_atoms > molsize[1]: - results[molecule] = mol.full_analysis( - _ncpus=_ncpus, **kwargs) - if molsize[0] in ['not', 'isnot', 'notequal', 'different']: - if mol.no_of_atoms != molsize[1]: - results[molecule] = mol.full_analysis( - _ncpus=_ncpus, **kwargs) - if molsize[0] in ['is', 'equal', 'exactly']: - if mol.no_of_atoms == molsize[1]: - results[molecule] = mol.full_analysis( - _ncpus=_ncpus, **kwargs) - if molsize[0] in ['between', 'inbetween']: - if molsize[1] < mol.no_of_atoms < molsize[2]: - results[molecule] = mol.full_analysis( - _ncpus=_ncpus, **kwargs) - else: - results[molecule] = mol.full_analysis(_ncpus=_ncpus, **kwargs) - return results - - def _analysis_parallel_execute(self, frame, **kwargs): - settings = { - 'rebuild': False, - 'modular': False, - } - settings.update(kwargs) - molecular_system = self._get_frame( - self.trajectory_map[frame], frame, extract_data=True, **kwargs - ) - if settings['modular'] is True: - molecular_system.make_modular(rebuild=settings['rebuild']) - molecules = molecular_system.molecules - else: - molecules = {'0': molecular_system.system_to_molecule()} - results = {} - for molecule in molecules: - mol = molecules[molecule] - if 'molsize' in settings: - molsize = settings['molsize'] - if isinstance(molsize, int): - if mol.no_of_atoms == molsize: - results[molecule] = mol.full_analysis(**kwargs) - if isinstance(molsize, tuple) and isinstance(molsize[0], str): - if molsize[0] in ['bigger', 'greater', 'larger', 'more']: - if mol.no_of_atoms > molsize[1]: - results[molecule] = mol.full_analysis(**kwargs) - if molsize[0] in ['smaller', 'less']: - if mol.no_of_atoms > molsize[1]: - results[molecule] = mol.full_analysis(**kwargs) - if molsize[0] in ['not', 'isnot', 'notequal', 'different']: - if mol.no_of_atoms != molsize[1]: - results[molecule] = mol.full_analysis(**kwargs) - if molsize[0] in ['is', 'equal', 'exactly']: - if mol.no_of_atoms == molsize[1]: - results[molecule] = mol.full_analysis(**kwargs) - if molsize[0] in ['between', 'inbetween']: - if molsize[1] < mol.no_of_atoms < molsize[2]: - results[molecule] = mol.full_analysis(**kwargs) - else: - results[molecule] = mol.full_analysis(**kwargs) - return frame, results - - def _analysis_parallel(self, frames, ncpus, **kwargs): - try: - pool = Pool(processes=ncpus) - parallel = [ - pool.apply_async( - self._analysis_parallel_execute, - args=(frame, ), - kwds=kwargs) for frame in frames - ] - results = [p.get() for p in parallel if p.get()] - pool.terminate() - for i in results: - self.analysis_output[i[0]] = i[1] - except TypeError: - pool.terminate() - raise _ParallelAnalysisError("Parallel analysis failed.") - - def _check_HISTORY(self): - """ - """ - self.check_log = "" - line = 0 - binary_step = 0 - timestep = 0 - timestep_flag = 'timestep' - progress = 0 - - warning_1 = "No comment line is present as the file header.\n" - warning_2 = " ".join( - ( - "Second header line is missing from the file", - "that contains information on the system's periodicity", - "and the type of the trajectory file.\n" - ) - ) - warning_3 = " ".join( - ( - "Comment line encountered in the middle of", - "the trajectory file.\n" - ) - ) - - error_1 = "The trajectory is discontinous.\n" - error_2 = "The file contains an empty line.\n" - - with open(self.filepath, 'r') as trajectory_file: - # We open the HISTORY trajectory file - with closing( - mmap( - trajectory_file.fileno(), 0, - access=ACCESS_READ)) as file_binary_map: - # We use this binary mapping feature that instead of loading - # the full file into memory beforehand it only - # maps the content. Especially useful with enormous files - while binary_step < len(file_binary_map): - line += 1 - binary_line = file_binary_map.readline() - binary_step = binary_step + len(binary_line) - progress_old = progress - progress = round(binary_step * 100 / len(file_binary_map), - 0) - string_line = binary_line.decode("utf-8").strip( - '\n').split() - - # Warning 1 - if line == 1: - if string_line[0] != 'DLFIELD': - self.check_log = " ".join( - (self.check_log, "Line {0}:".format(line), - warning_1) - ) - - # Warning 2 - if line == 2: - if len(string_line) != 3: - self.check_log = " ".join( - (self.check_log, "Line {0}:".format(line), - warning_2) - ) - - # Error 1 - if string_line: - if string_line[0] == timestep_flag: - old_timestep = timestep - timestep = int(string_line[1]) - if old_timestep > timestep: - error = " ".join( - "Line {0}:".format(line), error_1 - ) - raise _TrajectoryError(error) - - # Error 2 - if len(string_line) == 0: - error = " ".join( - "Line {0}:".format(line), error_2 - ) - raise _TrajectoryError(error) - -
[docs] def save_analysis(self, filepath=None, **kwargs): - """ - Dump the content of :attr:`analysis_output` as JSON dictionary. - - Parameters - ---------- - filepath : :class:`str` - The filepath for the JSON file. - - Returns - ------- - None : :class:`NoneType` - """ - # We pass a copy of the analysis attribute dictionary. - dict_obj = deepcopy(self.analysis_output) - # If no filepath is provided we create one. - if filepath is None: - filepath = "_".join( - (str(self.system_id), "pywindow_analysis") - ) - filepath = '/'.join((os.getcwd(), filepath)) - # Dump the dictionary to json file. - Output().dump2json(dict_obj, filepath, default=to_list, **kwargs) - return
- -
[docs] def save_frames(self, frames, filepath=None, filetype='pdb', **kwargs): - settings = { - "pdb": Output()._save_pdb, - "xyz": Output()._save_xyz, - "decipher": True, - "forcefield": None, - } - settings.update(kwargs) - if filetype.lower() not in settings.keys(): - raise _FormatError("The '{0}' file format is not supported".format( - filetype)) - frames_to_get = [] - if isinstance(frames, int): - frames_to_get.append(frames) - if isinstance(frames, list): - frames_to_get = frames - if isinstance(frames, tuple): - for frame in range(frames[0], frames[1]): - frames_to_get.append(frame) - if isinstance(frames, str): - if frames in ['all', 'everything']: - for frame in range(0, self.no_of_frames): - frames_to_get.append(frame) - for frame in frames_to_get: - if frame not in self.frames.keys(): - _ = self.get_frames(frame) - # If no filepath is provided we create one. - if filepath is None: - filepath = '/'.join((os.getcwd(), str(self.system_id))) - for frame in frames_to_get: - frame_molsys = self.frames[frame] - if settings[ - 'decipher'] is True and settings['forcefield'] is not None: - if "swap_atoms" in settings.keys(): - if isinstance(settings["swap_atoms"], dict): - frame_molsys.swap_atom_keys(settings["swap_atoms"]) - else: - raise _FunctionError( - "The swap_atom_keys function only accepts " - "'swap_atoms' argument in form of a dictionary.") - frame_molsys.decipher_atom_keys(settings["forcefield"]) - ffilepath = '_'.join((filepath, str(frame))) - if 'elements' not in frame_molsys.system.keys(): - raise _FunctionError( - "The frame (MolecularSystem object) needs to have " - "'elements' attribute within the system dictionary. " - "It is, therefore, neccessary that you set a decipher " - "keyword to True. (see manual)") - settings[filetype.lower()](frame_molsys.system, ffilepath, ** - kwargs)
- - -
[docs]class XYZ(object): - """ - A container for an XYZ type trajectory. - - This function takes an XYZ trajectory file and maps it for the - binary points in the file where each frame starts/ends. This way the - process is fast, as it not require loading the trajectory into computer - memory. When a frame is being extracted, it is only this frame that gets - loaded to the memory. - - Frames can be accessed individually and loaded as an unmodified string, - returned as a :class:`pywindow.molecular.MolecularSystem` (and analysed), - dumped as PDB or XYZ or JSON (if dumped as a - :attr:`pywindow.molecular.MolecularSystem.system`) - - Attributes - ---------- - filepath : :class:`str` - The filepath. - - filename : :class:`str` - The filename. - - system_id : :class:`str` - The system id inherited from the filename. - - frames : :class:`dict` - A dictionary that is populated, on the fly, with the extracted frames. - - analysis_output : :class:`dict` - A dictionary that is populated, on the fly, with the analysis output. - - """ - def __init__(self, filepath): - self.filepath = filepath - self.filename = os.path.basename(filepath) - self.system_id = self.filename.split(".")[0] - self.frames = {} - self.analysis_output = {} - # Map the trajectory file at init. - self._map_trajectory() - - def _map_trajectory(self): - """ Return filepath as a class attribute""" - self.trajectory_map = {} - with open(self.filepath, 'r') as trajectory_file: - with closing( - mmap( - trajectory_file.fileno(), 0, - access=ACCESS_READ)) as mapped_file: - progress = 0 - line = 0 - frame = -1 - frame_start = 0 - while progress <= len(mapped_file): - line = line + 1 - # We read a binary data from a mapped file. - bline = mapped_file.readline() - # If the bline length equals zero we terminate. - # We reached end of the file but still add the last frame! - if len(bline) == 0: - frame = frame + 1 - self.trajectory_map[frame] = [frame_start, progress] - break - # We need to decode byte line into an utf-8 string. - sline = bline.decode("utf-8").strip('\n').split() - # We extract map's byte coordinates for each frame - if (len(sline) == 1 and is_number(sline[0]) and - progress > 0): - frame = frame + 1 - self.trajectory_map[frame] = [frame_start, progress] - frame_start = progress - # Here we extract the map's byte coordinates for the header - # And also the periodic system type needed for later. - progress = progress + len(bline) - self.no_of_frames = frame + 1 - -
[docs] def get_frames(self, frames='all', override=False, **kwargs): - """ - Extract frames from the trajectory file. - - Depending on the passed parameters a frame, a list of particular - frames, a range of frames (from, to), or all frames can be extracted - with this function. - - Parameters - ---------- - frames : :class:`int` or :class:`list` or :class:`touple` or :class:`str` - Specified frame (:class:`int`), or frames (:class:`list`), or - range (:class:`touple`), or `all`/`everything` (:class:`str`). - (default=`all`) - - override : :class:`bool` - If True, a frame already storred in :attr:`frames` can be override. - (default=False) - - extract_data : :class:`bool`, optional - If False, a frame is returned as a :class:`str` block as in the - trajectory file. Ohterwise, it is extracted and returned as - :class:`pywindow.molecular.MolecularSystem`. (default=True) - - swap_atoms : :class:`dict`, optional - If this kwarg is passed with an appropriate dictionary a - :func:`pywindow.molecular.MolecularSystem.swap_atom_keys()` will - be applied to the extracted frame. - - forcefield : :class:`str`, optional - If this kwarg is passed with appropriate forcefield keyword a - :func:`pywindow.molecular.MolecularSystem.decipher_atom_keys()` - will be applied to the extracted frame. - - Returns - ------- - :class:`pywindow.molecular.MolecularSystem` - If a single frame is extracted. - - None : :class:`NoneType` - If more than one frame is extracted, the frames are returned to - :attr:`frames` - - """ - if override is True: - self.frames = {} - if isinstance(frames, int): - frame = self._get_frame( - self.trajectory_map[frames], frames, **kwargs) - if frames not in self.frames.keys(): - self.frames[frames] = frame - return frame - if isinstance(frames, list): - for frame in frames: - if frame not in self.frames.keys(): - self.frames[frame] = self._get_frame( - self.trajectory_map[frame], frame, **kwargs) - if isinstance(frames, tuple): - for frame in range(frames[0], frames[1]): - if frame not in self.frames.keys(): - self.frames[frame] = self._get_frame( - self.trajectory_map[frame], frame, **kwargs) - if isinstance(frames, str): - if frames in ['all', 'everything']: - for frame in range(0, self.no_of_frames): - if frame not in self.frames.keys(): - self.frames[frame] = self._get_frame( - self.trajectory_map[frame], frame, **kwargs)
- - def _get_frame(self, frame_coordinates, frame_no, **kwargs): - kwargs_ = { - "extract_data": True, - } - kwargs_.update(kwargs) - start, end = frame_coordinates - with open(self.filepath, 'r') as trajectory_file: - with closing( - mmap( - trajectory_file.fileno(), 0, - access=ACCESS_READ)) as mapped_file: - if kwargs_["extract_data"] is False: - return mapped_file[start:end].decode("utf-8") - else: - # [:-1] because the split results in last list empty. - frame = [ - i.split() - for i in mapped_file[start:end].decode("utf-8").split( - '\n') - ][:-1] - decoded_frame = self._decode_frame(frame) - molsys = MolecularSystem.load_system( - decoded_frame, - "_".join([self.system_id, str(frame_no)])) - if 'swap_atoms' in kwargs: - molsys.swap_atom_keys(kwargs['swap_atoms']) - if 'forcefield' in kwargs: - molsys.decipher_atom_keys(kwargs['forcefield']) - return molsys - - def _decode_frame(self, frame): - frame_data = { - 'frame_info': { - 'natms': int(frame[0][0]), - 'remarks': " ".join([*frame[1]]), - } - } - start_line = 2 - elements = [] - coordinates = [] - for i in range(start_line, len(frame)): - elements.append(frame[i][0]) - coordinates.append(frame[i][1:]) - frame_data['atom_ids'] = np.array(elements) - frame_data['coordinates'] = np.array(coordinates, dtype=float) - return frame_data - -
[docs] def analysis(self, frames='all', ncpus=1, override=False, **kwargs): - """ - Perform structural analysis on a frame/ set of frames. - - Depending on the passed parameters a frame, a list of particular - frames, a range of frames (from, to), or all frames can be analysed - with this function. - - The analysis is performed on each frame and each discrete molecule in - that frame separately. The steps are as follows: - - 1. A frame is extracted and returned as a :class:`MolecularSystem`. - 2. If `swap_atoms` is set the atom ids are swapped. - 3. If `forcefield` is set the atom ids are deciphered. - 4. If `rebuild` is set the molecules in the system are rebuild. - 5. Each discrete molecule is extracted as :class:`Molecule` - 6. Each molecule is analysed with :func:`Molecule.full_analysis()` - 7. Analysis output populates the :attr:`analysis_output` dictionary. - - As the analysis of trajectories often have to be unique, many options - are conditional. - - A side effect of this function is that the analysed frames are also - returned to the :attr:`frames` mimicking the behaviour of the - :func:`get_frames()`. - - Parameters - ---------- - frames : :class:`int` or :class:`list` or :class:`touple` or :class:`str` - Specified frame (:class:`int`), or frames (:class:`list`), or - range (:class:`touple`), or `all`/`everything` (:class:`str`). - (default='all') - - override : :class:`bool` - If True, an output already storred in :attr:`analysis_output` can - be override. (default=False) - - swap_atoms : :class:`dict`, optional - If this kwarg is passed with an appropriate dictionary a - :func:`pywindow.molecular.MolecularSystem.swap_atom_keys()` will - be applied to the extracted frame. - - forcefield : :class:`str`, optional - If this kwarg is passed with appropriate forcefield keyword a - :func:`pywindow.molecular.MolecularSystem.decipher_atom_keys()` - will be applied to the extracted frame. - - modular : :class:`bool`, optional - If this kwarg is passed a - :func:`pywindow.molecular.MolecularSystem.make_modular()` - will be applied to the extracted frame. (default=False) - - rebuild : :class:`bool`, optional - If this kwarg is passed a `rebuild=True` is passed to - :func:`pywindow.molecular.MolecularSystem.make_modular()` that - will be applied to the extracted frame. (default=False) - - ncpus : :class:`int`, optional - If ncpus > 1, then the analysis is performed in parallel for the - specified number of parallel jobs. Otherwise, it runs in serial. - (default=1) - - Returns - ------- - None : :class:`NoneType` - The function returns `None`, the analysis output is - returned to :attr:`analysis_output` dictionary. - - """ - if override is True: - self.analysis_output = {} - if isinstance(frames, int): - analysed_frame = self._analysis_serial(frames, ncpus, **kwargs) - if frames not in self.analysis_output.keys(): - self.analysis_output[frames] = analysed_frame - return analysed_frame - else: - frames_for_analysis = [] - if isinstance(frames, list): - for frame in frames: - if frame not in self.analysis_output.keys(): - frames_for_analysis.append(frame) - if isinstance(frames, tuple): - for frame in range(frames[0], frames[1]): - if frame not in self.analysis_output.keys(): - frames_for_analysis.append(frame) - if isinstance(frames, str): - if frames in ['all', 'everything']: - for frame in range(0, self.no_of_frames): - if frame not in self.analysis_output.keys(): - frames_for_analysis.append(frame) - self._analysis_parallel(frames_for_analysis, ncpus, **kwargs)
- - def _analysis_serial(self, frame, ncpus, **kwargs): - settings = { - 'rebuild': False, - 'modular': False, - } - settings.update(kwargs) - molecular_system = self._get_frame( - self.trajectory_map[frame], frame, extract_data=True, **kwargs - ) - if settings['modular'] is True: - molecular_system.make_modular(rebuild=settings['rebuild']) - molecules = molecular_system.molecules - else: - molecules = {'0': molecular_system.system_to_molecule()} - results = {} - for molecule in molecules: - mol = molecules[molecule] - if 'molsize' in settings: - molsize = settings['molsize'] - if isinstance(molsize, int): - if mol.no_of_atoms == molsize: - results[molecule] = mol.full_analysis( - ncpus=ncpus, **kwargs) - if isinstance(molsize, tuple) and isinstance(molsize[0], str): - if molsize[0] in ['bigger', 'greater', 'larger', 'more']: - if mol.no_of_atoms > molsize[1]: - results[molecule] = mol.full_analysis( - ncpus=ncpus, **kwargs) - if molsize[0] in ['smaller', 'less']: - if mol.no_of_atoms > molsize[1]: - results[molecule] = mol.full_analysis( - ncpus=ncpus, **kwargs) - if molsize[0] in ['not', 'isnot', 'notequal', 'different']: - if mol.no_of_atoms != molsize[1]: - results[molecule] = mol.full_analysis( - ncpus=ncpus, **kwargs) - if molsize[0] in ['is', 'equal', 'exactly']: - if mol.no_of_atoms == molsize[1]: - results[molecule] = mol.full_analysis( - ncpus=ncpus, **kwargs) - if molsize[0] in ['between', 'inbetween']: - if molsize[1] < mol.no_of_atoms < molsize[2]: - results[molecule] = mol.full_analysis( - ncpus=ncpus, **kwargs) - else: - results[molecule] = mol.full_analysis(ncpus=ncpus, **kwargs) - return results - - def _analysis_parallel_execute(self, frame, **kwargs): - settings = { - 'rebuild': False, - 'modular': False, - } - settings.update(kwargs) - molecular_system = self._get_frame( - self.trajectory_map[frame], frame, extract_data=True, **kwargs - ) - if settings['modular'] is True: - molecular_system.make_modular(rebuild=settings['rebuild']) - molecules = molecular_system.molecules - else: - molecules = {'0': molecular_system.system_to_molecule()} - results = {} - for molecule in molecules: - mol = molecules[molecule] - if 'molsize' in settings: - molsize = settings['molsize'] - if isinstance(molsize, int): - if mol.no_of_atoms == molsize: - results[molecule] = mol.full_analysis(**kwargs) - if isinstance(molsize, tuple) and isinstance(molsize[0], str): - if molsize[0] in ['bigger', 'greater', 'larger', 'more']: - if mol.no_of_atoms > molsize[1]: - results[molecule] = mol.full_analysis(**kwargs) - if molsize[0] in ['smaller', 'less']: - if mol.no_of_atoms > molsize[1]: - results[molecule] = mol.full_analysis(**kwargs) - if molsize[0] in ['not', 'isnot', 'notequal', 'different']: - if mol.no_of_atoms != molsize[1]: - results[molecule] = mol.full_analysis(**kwargs) - if molsize[0] in ['is', 'equal', 'exactly']: - if mol.no_of_atoms == molsize[1]: - results[molecule] = mol.full_analysis(**kwargs) - if molsize[0] in ['between', 'inbetween']: - if molsize[1] < mol.no_of_atoms < molsize[2]: - results[molecule] = mol.full_analysis(**kwargs) - else: - results[molecule] = mol.full_analysis(**kwargs) - return frame, results - - def _analysis_parallel(self, frames, ncpus, **kwargs): - try: - pool = Pool(processes=ncpus) - parallel = [ - pool.apply_async( - self._analysis_parallel_execute, - args=(frame, ), - kwds=kwargs) for frame in frames - ] - results = [p.get() for p in parallel if p.get()[1] is not None] - pool.terminate() - for i in results: - self.analysis_output[i[0]] = i[1] - except TypeError: - pool.terminate() - raise _ParallelAnalysisError("Parallel analysis failed.") - -
[docs] def save_analysis(self, filepath=None, **kwargs): - """ - Dump the content of :attr:`analysis_output` as JSON dictionary. - - Parameters - ---------- - filepath : :class:`str` - The filepath for the JSON file. - - Returns - ------- - None : :class:`NoneType` - """ - # We pass a copy of the analysis attribute dictionary. - dict_obj = deepcopy(self.analysis_output) - # If no filepath is provided we create one. - if filepath is None: - filepath = "_".join( - (str(self.system_id), "pywindow_analysis") - ) - filepath = '/'.join((os.getcwd(), filepath)) - # Dump the dictionary to json file. - Output().dump2json(dict_obj, filepath, default=to_list, **kwargs) - return
- -
[docs] def save_frames(self, frames, filepath=None, filetype='pdb', **kwargs): - settings = { - "pdb": Output()._save_pdb, - "xyz": Output()._save_xyz, - "decipher": True, - "forcefield": None, - } - settings.update(kwargs) - if filetype.lower() not in settings.keys(): - raise _FormatError("The '{0}' file format is not supported".format( - filetype)) - frames_to_get = [] - if isinstance(frames, int): - frames_to_get.append(frames) - if isinstance(frames, list): - frames_to_get = frames - if isinstance(frames, tuple): - for frame in range(frames[0], frames[1]): - frames_to_get.append(frame) - if isinstance(frames, str): - if frames in ['all', 'everything']: - for frame in range(0, self.no_of_frames): - frames_to_get.append(frame) - for frame in frames_to_get: - if frame not in self.frames.keys(): - _ = self.get_frames(frame) - # If no filepath is provided we create one. - if filepath is None: - filepath = '/'.join((os.getcwd(), str(self.system_id))) - for frame in frames_to_get: - frame_molsys = self.frames[frame] - if settings[ - 'decipher'] is True and settings['forcefield'] is not None: - if "swap_atoms" in settings.keys(): - if isinstance(settings["swap_atoms"], dict): - frame_molsys.swap_atom_keys(settings["swap_atoms"]) - else: - raise _FunctionError( - "The swap_atom_keys function only accepts " - "'swap_atoms' argument in form of a dictionary.") - frame_molsys.decipher_atom_keys(settings["forcefield"]) - ffilepath = '_'.join((filepath, str(frame))) - if 'elements' not in frame_molsys.system.keys(): - raise _FunctionError( - "The frame (MolecularSystem object) needs to have " - "'elements' attribute within the system dictionary. " - "It is, therefore, neccessary that you set a decipher " - "keyword to True. (see manual)") - settings[filetype.lower()](frame_molsys.system, ffilepath, ** - kwargs)
- - -
[docs]class PDB(object): - def __init__(self, filepath): - """ - A container for an PDB type trajectory. - - This function takes an PDB trajectory file and maps it for the - binary points in the file where each frame starts/ends. This way the - process is fast, as it not require loading the trajectory into computer - memory. When a frame is being extracted, it is only this frame that gets - loaded to the memory. - - Frames can be accessed individually and loaded as an unmodified string, - returned as a :class:`pywindow.molecular.MolecularSystem` (and analysed), - dumped as PDB or XYZ or JSON (if dumped as a - :attr:`pywindow.molecular.MolecularSystem.system`) - - Attributes - ---------- - filepath : :class:`str` - The filepath. - - filename : :class:`str` - The filename. - - system_id : :class:`str` - The system id inherited from the filename. - - frames : :class:`dict` - A dictionary that is populated, on the fly, with the extracted frames. - - analysis_output : :class:`dict` - A dictionary that is populated, on the fly, with the analysis output. - - """ - self.filepath = filepath - self.filename = os.path.basename(filepath) - self.system_id = self.filename.split(".")[0] - self.frames = {} - self.analysis_output = {} - # Map the trajectory file at init. - self._map_trajectory() - - def _map_trajectory(self): - """ Return filepath as a class attribute""" - self.trajectory_map = {} - with open(self.filepath, 'r') as trajectory_file: - with closing( - mmap( - trajectory_file.fileno(), 0, - access=ACCESS_READ)) as mapped_file: - progress = 0 - line = 0 - frame = -1 - frame_start = 0 - while progress <= len(mapped_file): - line = line + 1 - # We read a binary data from a mapped file. - bline = mapped_file.readline() - # If the bline length equals zero we terminate. - # We reached end of the file but still add the last frame! - if len(bline) == 0: - frame = frame + 1 - if progress - frame_start > 10: - self.trajectory_map[frame] = [ - frame_start, progress - ] - break - # We need to decode byte line into an utf-8 string. - sline = bline.decode("utf-8").strip('\n').split() - # We extract map's byte coordinates for each frame - if len(sline) == 1 and sline[0] == 'END': - frame = frame + 1 - self.trajectory_map[frame] = [frame_start, progress] - frame_start = progress - # Here we extract the map's byte coordinates for the header - # And also the periodic system type needed for later. - progress = progress + len(bline) - self.no_of_frames = frame - -
[docs] def get_frames(self, frames='all', override=False, **kwargs): - """ - Extract frames from the trajectory file. - - Depending on the passed parameters a frame, a list of particular - frames, a range of frames (from, to), or all frames can be extracted - with this function. - - Parameters - ---------- - frames : :class:`int` or :class:`list` or :class:`touple` or :class:`str` - Specified frame (:class:`int`), or frames (:class:`list`), or - range (:class:`touple`), or `all`/`everything` (:class:`str`). - (default=`all`) - - override : :class:`bool` - If True, a frame already storred in :attr:`frames` can be override. - (default=False) - - extract_data : :class:`bool`, optional - If False, a frame is returned as a :class:`str` block as in the - trajectory file. Ohterwise, it is extracted and returned as - :class:`pywindow.molecular.MolecularSystem`. (default=True) - - swap_atoms : :class:`dict`, optional - If this kwarg is passed with an appropriate dictionary a - :func:`pywindow.molecular.MolecularSystem.swap_atom_keys()` will - be applied to the extracted frame. - - forcefield : :class:`str`, optional - If this kwarg is passed with appropriate forcefield keyword a - :func:`pywindow.molecular.MolecularSystem.decipher_atom_keys()` - will be applied to the extracted frame. - - Returns - ------- - :class:`pywindow.molecular.MolecularSystem` - If a single frame is extracted. - - None : :class:`NoneType` - If more than one frame is extracted, the frames are returned to - :attr:`frames` - - """ - if override is True: - self.frames = {} - if isinstance(frames, int): - frame = self._get_frame( - self.trajectory_map[frames], frames, **kwargs) - if frames not in self.frames.keys(): - self.frames[frames] = frame - return frame - if isinstance(frames, list): - for frame in frames: - if frame not in self.frames.keys(): - self.frames[frame] = self._get_frame( - self.trajectory_map[frame], frame, **kwargs) - if isinstance(frames, tuple): - for frame in range(frames[0], frames[1]): - if frame not in self.frames.keys(): - self.frames[frame] = self._get_frame( - self.trajectory_map[frame], frame, **kwargs) - if isinstance(frames, str): - if frames in ['all', 'everything']: - for frame in range(0, self.no_of_frames): - if frame not in self.frames.keys(): - self.frames[frame] = self._get_frame( - self.trajectory_map[frame], frame, **kwargs)
- - def _get_frame(self, frame_coordinates, frame_no, **kwargs): - kwargs_ = { - "extract_data": True - } - kwargs_.update(kwargs) - start, end = frame_coordinates - with open(self.filepath, 'r') as trajectory_file: - with closing( - mmap( - trajectory_file.fileno(), 0, - access=ACCESS_READ)) as mapped_file: - if kwargs_["extract_data"] is False: - return mapped_file[start:end].decode("utf-8") - else: - # In case of PDB we do not split lines! - frame = mapped_file[start:end].decode("utf-8").split('\n') - decoded_frame = self._decode_frame(frame) - molsys = MolecularSystem.load_system( - decoded_frame, - "_".join([self.system_id, str(frame_no)])) - if 'swap_atoms' in kwargs: - molsys.swap_atom_keys(kwargs['swap_atoms']) - if 'forcefield' in kwargs: - molsys.decipher_atom_keys(kwargs['forcefield']) - return molsys - - def _decode_frame(self, frame): - frame_data = {} - elements = [] - coordinates = [] - for i in range(len(frame)): - if frame[i][:6] == 'REMARK': - if 'REMARKS' not in frame_data.keys(): - frame_data['REMARKS'] = [] - frame_data['REMARKS'].append(frame[i][6:]) - if frame[i][:6] == 'CRYST1': - cryst = np.array( - [ - frame[i][6:15], frame[i][15:24], frame[i][24:33], - frame[i][33:40], frame[i][40:47], frame[i][47:54] - ], - dtype=float) - # This is in case of nonperiodic systems, often they have - # a,b,c unit cell parameters as 0,0,0. - if sum(cryst[0:3]) != 0: - frame_data['CRYST1'] = cryst - if frame[i][:6] in ['HETATM', 'ATOM ']: - elements.append(frame[i][12:16].strip()) - coordinates.append( - [frame[i][30:38], frame[i][38:46], frame[i][46:54]]) - frame_data['atom_ids'] = np.array(elements, dtype='<U8') - frame_data['coordinates'] = np.array(coordinates, dtype=float) - return frame_data - -
[docs] def analysis(self, frames='all', ncpus=1, override=False, **kwargs): - """ - Perform structural analysis on a frame/ set of frames. - - Depending on the passed parameters a frame, a list of particular - frames, a range of frames (from, to), or all frames can be analysed - with this function. - - The analysis is performed on each frame and each discrete molecule in - that frame separately. The steps are as follows: - - 1. A frame is extracted and returned as a :class:`MolecularSystem`. - 2. If `swap_atoms` is set the atom ids are swapped. - 3. If `forcefield` is set the atom ids are deciphered. - 4. If `rebuild` is set the molecules in the system are rebuild. - 5. Each discrete molecule is extracted as :class:`Molecule` - 6. Each molecule is analysed with :func:`Molecule.full_analysis()` - 7. Analysis output populates the :attr:`analysis_output` dictionary. - - As the analysis of trajectories often have to be unique, many options - are conditional. - - A side effect of this function is that the analysed frames are also - returned to the :attr:`frames` mimicking the behaviour of the - :func:`get_frames()`. - - Parameters - ---------- - frames : :class:`int` or :class:`list` or :class:`touple` or :class:`str` - Specified frame (:class:`int`), or frames (:class:`list`), or - range (:class:`touple`), or `all`/`everything` (:class:`str`). - (default='all') - - override : :class:`bool` - If True, an output already storred in :attr:`analysis_output` can - be override. (default=False) - - swap_atoms : :class:`dict`, optional - If this kwarg is passed with an appropriate dictionary a - :func:`pywindow.molecular.MolecularSystem.swap_atom_keys()` will - be applied to the extracted frame. - - forcefield : :class:`str`, optional - If this kwarg is passed with appropriate forcefield keyword a - :func:`pywindow.molecular.MolecularSystem.decipher_atom_keys()` - will be applied to the extracted frame. - - modular : :class:`bool`, optional - If this kwarg is passed a - :func:`pywindow.molecular.MolecularSystem.make_modular()` - will be applied to the extracted frame. (default=False) - - rebuild : :class:`bool`, optional - If this kwarg is passed a `rebuild=True` is passed to - :func:`pywindow.molecular.MolecularSystem.make_modular()` that - will be applied to the extracted frame. (default=False) - - ncpus : :class:`int`, optional - If ncpus > 1, then the analysis is performed in parallel for the - specified number of parallel jobs. Otherwise, it runs in serial. - (default=1) - - Returns - ------- - None : :class:`NoneType` - The function returns `None`, the analysis output is - returned to :attr:`analysis_output` dictionary. - - """ - if override is True: - self.analysis_output = {} - if isinstance(frames, int): - analysed_frame = self._analysis_serial(frames, ncpus, **kwargs) - if frames not in self.analysis_output.keys(): - self.analysis_output[frames] = analysed_frame - return analysed_frame - else: - frames_for_analysis = [] - if isinstance(frames, list): - for frame in frames: - if frame not in self.analysis_output.keys(): - frames_for_analysis.append(frame) - if isinstance(frames, tuple): - for frame in range(frames[0], frames[1]): - if frame not in self.analysis_output.keys(): - frames_for_analysis.append(frame) - if isinstance(frames, str): - if frames in ['all', 'everything']: - for frame in range(0, self.no_of_frames): - if frame not in self.analysis_output.keys(): - frames_for_analysis.append(frame) - self._analysis_parallel(frames_for_analysis, ncpus, **kwargs)
- - def _analysis_serial(self, frame, ncpus, **kwargs): - settings = { - 'rebuild': False, - 'modular': False, - } - settings.update(kwargs) - molecular_system = self._get_frame( - self.trajectory_map[frame], frame, extract_data=True, **kwargs - ) - if settings['modular'] is True: - molecular_system.make_modular(rebuild=settings['rebuild']) - molecules = molecular_system.molecules - else: - molecules = {'0': molecular_system.system_to_molecule()} - results = {} - for molecule in molecules: - mol = molecules[molecule] - if 'molsize' in settings: - molsize = settings['molsize'] - if isinstance(molsize, int): - if mol.no_of_atoms == molsize: - results[molecule] = mol.full_analysis( - ncpus=ncpus, **kwargs) - if isinstance(molsize, tuple) and isinstance(molsize[0], str): - if molsize[0] in ['bigger', 'greater', 'larger', 'more']: - if mol.no_of_atoms > molsize[1]: - results[molecule] = mol.full_analysis( - ncpus=ncpus, **kwargs) - if molsize[0] in ['smaller', 'less']: - if mol.no_of_atoms > molsize[1]: - results[molecule] = mol.full_analysis( - ncpus=ncpus, **kwargs) - if molsize[0] in ['not', 'isnot', 'notequal', 'different']: - if mol.no_of_atoms != molsize[1]: - results[molecule] = mol.full_analysis( - ncpus=ncpus, **kwargs) - if molsize[0] in ['is', 'equal', 'exactly']: - if mol.no_of_atoms == molsize[1]: - results[molecule] = mol.full_analysis( - ncpus=ncpus, **kwargs) - if molsize[0] in ['between', 'inbetween']: - if molsize[1] < mol.no_of_atoms < molsize[2]: - results[molecule] = mol.full_analysis( - ncpus=ncpus, **kwargs) - else: - results[molecule] = mol.full_analysis(ncpus=ncpus, **kwargs) - return results - - def _analysis_parallel_execute(self, frame, **kwargs): - settings = { - 'rebuild': False, - 'modular': False, - } - settings.update(kwargs) - molecular_system = self._get_frame( - self.trajectory_map[frame], frame, extract_data=True, **kwargs - ) - if settings['modular'] is True: - molecular_system.make_modular(rebuild=settings['rebuild']) - molecules = molecular_system.molecules - else: - molecules = {'0': molecular_system.system_to_molecule()} - results = {} - for molecule in molecules: - mol = molecules[molecule] - if 'molsize' in settings: - molsize = settings['molsize'] - if isinstance(molsize, int): - if mol.no_of_atoms == molsize: - results[molecule] = mol.full_analysis(**kwargs) - if isinstance(molsize, tuple) and isinstance(molsize[0], str): - if molsize[0] in ['bigger', 'greater', 'larger', 'more']: - if mol.no_of_atoms > molsize[1]: - results[molecule] = mol.full_analysis(**kwargs) - if molsize[0] in ['smaller', 'less']: - if mol.no_of_atoms > molsize[1]: - results[molecule] = mol.full_analysis(**kwargs) - if molsize[0] in ['not', 'isnot', 'notequal', 'different']: - if mol.no_of_atoms != molsize[1]: - results[molecule] = mol.full_analysis(**kwargs) - if molsize[0] in ['is', 'equal', 'exactly']: - if mol.no_of_atoms == molsize[1]: - results[molecule] = mol.full_analysis(**kwargs) - if molsize[0] in ['between', 'inbetween']: - if molsize[1] < mol.no_of_atoms < molsize[2]: - results[molecule] = mol.full_analysis(**kwargs) - else: - results[molecule] = mol.full_analysis(**kwargs) - return frame, results - - def _analysis_parallel(self, frames, ncpus, **kwargs): - try: - pool = Pool(processes=ncpus) - parallel = [ - pool.apply_async( - self._analysis_parallel_execute, - args=(frame, ), - kwds=kwargs) for frame in frames - ] - results = [p.get() for p in parallel if p.get()[1] is not None] - pool.terminate() - for i in results: - self.analysis_output[i[0]] = i[1] - except TypeError: - pool.terminate() - raise _ParallelAnalysisError("Parallel analysis failed.") - -
[docs] def save_analysis(self, filepath=None, **kwargs): - """ - Dump the content of :attr:`analysis_output` as JSON dictionary. - - Parameters - ---------- - filepath : :class:`str` - The filepath for the JSON file. - - Returns - ------- - None : :class:`NoneType` - """ - # We pass a copy of the analysis attribute dictionary. - dict_obj = deepcopy(self.analysis_output) - # If no filepath is provided we create one. - if filepath is None: - filepath = "_".join( - (str(self.system_id), "pywindow_analysis") - ) - filepath = '/'.join((os.getcwd(), filepath)) - # Dump the dictionary to json file. - Output().dump2json(dict_obj, filepath, default=to_list, **kwargs) - return
- -
[docs] def save_frames(self, frames, filepath=None, filetype='pdb', **kwargs): - settings = { - "pdb": Output()._save_pdb, - "xyz": Output()._save_xyz, - "decipher": True, - "forcefield": None, - } - settings.update(kwargs) - if filetype.lower() not in settings.keys(): - raise _FormatError("The '{0}' file format is not supported".format( - filetype)) - frames_to_get = [] - if isinstance(frames, int): - frames_to_get.append(frames) - if isinstance(frames, list): - frames_to_get = frames - if isinstance(frames, tuple): - for frame in range(frames[0], frames[1]): - frames_to_get.append(frame) - if isinstance(frames, str): - if frames in ['all', 'everything']: - for frame in range(0, self.no_of_frames): - frames_to_get.append(frame) - for frame in frames_to_get: - if frame not in self.frames.keys(): - _ = self.get_frames(frame) - # If no filepath is provided we create one. - if filepath is None: - filepath = '/'.join((os.getcwd(), str(self.system_id))) - for frame in frames_to_get: - frame_molsys = self.frames[frame] - if settings[ - 'decipher'] is True and settings['forcefield'] is not None: - if "swap_atoms" in settings.keys(): - if isinstance(settings["swap_atoms"], dict): - frame_molsys.swap_atom_keys(settings["swap_atoms"]) - else: - raise _FunctionError( - "The swap_atom_keys function only accepts " - "'swap_atoms' argument in form of a dictionary.") - frame_molsys.decipher_atom_keys(settings["forcefield"]) - ffilepath = '_'.join((filepath, str(frame))) - if 'elements' not in frame_molsys.system.keys(): - raise _FunctionError( - "The frame (MolecularSystem object) needs to have " - "'elements' attribute within the system dictionary. " - "It is, therefore, neccessary that you set a decipher " - "keyword to True. (see manual)") - settings[filetype.lower()](frame_molsys.system, ffilepath, ** - kwargs)
-
- -
-
-
- - -
- -
-

- © Copyright 2017, Marcin Miklitz, Jelfs Materials Group. - -

-
- Built with Sphinx using a theme provided by Read the Docs. - -
- -
-
- -
- -
- - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_modules/pywindow/utilities.html b/docs/_modules/pywindow/utilities.html deleted file mode 100644 index d7be114..0000000 --- a/docs/_modules/pywindow/utilities.html +++ /dev/null @@ -1,2178 +0,0 @@ - - - - - - - - - - - pywindow.utilities — pywindow documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - -
- - - - - - -
-
- - - - - - -
- -
-
-
-
- -

Source code for pywindow.utilities

-"""
-Module containing all general purpose functions shared by other modules.
-
-This module is not intended for the direct use by a User. Therefore, I will
-only docstring functions if I see fit to do so.
-
-LOG
----
-11/07/18
-    Changed the way vector path is analysed. Now, the initial analysis is
-    done with the geometrical formula for line-sphere intersection. Only
-    the remaining vestors that do not intersect any van der Waals spheres are
-    then analysed in the old way.
-
-27/07/17
-    Fixed the cartesian coordinates -> fractional coordinates -> cartesian
-    coordinates conversion related functions, creation of lattice array
-    from unit cell parameters (triclinic system: so applicable to any)
-    and conversion back to unit cell parameters. WORKS! inspiration from:
-    http://www.ruppweb.org/Xray/tutorial/Coordinate%20system%20transformation.htm
-
-26/07/17
-    Changed the way bonds are determined. Now, rather then fixed value
-    a formula and covalent radii are used as explained in the Elemental_Radii
-    spreadsheet (see tables module).
-
-TO DO LIST
-----------
-
-- Fix and validate calculating shape descriptors: asphericity, acylindricity
-  and the realtive shape anisotropy. (Not working at the moment)
-
-- In the find_windows() function, maybe change the way the EPS value for
-  the DBSCAN() is estimates. Need to look how the distances change with the
-  increase in size of the sampling sphere. (validate this with the MongoDB)
-"""
-
-import numpy as np
-from copy import deepcopy
-from multiprocessing import Pool
-from scipy.optimize import brute, fmin, minimize
-from sklearn.cluster import DBSCAN
-from sklearn.metrics.pairwise import euclidean_distances
-from sklearn.neighbors import KDTree
-
-from .tables import (
-    atomic_mass, atomic_vdw_radius, opls_atom_keys, atomic_covalent_radius
-    )
-
-
-class _AtomKeyError(Exception):
-    def __init__(self, message):
-        self.message = message
-
-
-class _AtomKeyConflict(Exception):
-    def __init__(self, message):
-        self.message = message
-
-
-class _ForceFieldError(Exception):
-    def __init__(self, message):
-        self.message = message
-
-
-class _FunctionError(Exception):
-    def __init__(self, message):
-        self.message = message
-
-
-
[docs]def is_number(number): - """ - Return True if an object is a number - can be converted into a float. - - Parameters - ---------- - number : any - - Returns - ------- - bool - True if input is a float convertable (a number), False otherwise. - - """ - try: - float(number) - return True - except ValueError: - return False
- - -
[docs]def unique(input_list): - """ - Return a list of unique items (similar to set functionality). - - Parameters - ---------- - input_list : list - A list containg some items that can occur more than once. - - Returns - ------- - list - A list with only unique occurances of an item. - - """ - output = [] - for item in input_list: - if item not in output: - output.append(item) - return output
- - -
[docs]def to_list(obj): - """ """ - if isinstance(obj, np.ndarray): - return obj.tolist() - raise TypeError('Not serializable')
- - -
[docs]def distance(a, b): - """ - Return the distance between two vectors (points) a and b. - - Parameters - ---------- - a : numpy.ndarray - First vector. - b : numpy.ndarray - Second vector. - - Returns - ------- - numpy.float64 - A distance between two vectors (points). - - """ - return (np.sum((a - b)**2))**0.5
- - -
[docs]def molecular_weight(elements): - """ - Return molecular weight of a molecule. - - Parameters - ---------- - elements : numpy.ndarray - An array of all elements (type: str) in a molecule. - - Returns - ------- - numpy.float64 - A molecular weight of a molecule. - - """ - return (np.array([atomic_mass[i.upper()] for i in elements]).sum())
- - -
[docs]def center_of_coor(coordinates): - """ - Return the centre of coordinates. - - Parameters - ---------- - coordinates : numpy.ndarray - An array containing molecule's coordinates. - - Returns - ------- - numpy.ndarray - An 1d array with coordinates of the centre of coordinates excluding - elements' masses. - - """ - return (np.sum(coordinates, axis=0) / coordinates.shape[0])
- - -
[docs]def center_of_mass(elements, coordinates): - """ - Return the centre of mass (COM). - - Parameters - ---------- - elements : numpy.ndarray - An array of all elements (type: str) in a molecule. - - coordinates : numpy.ndarray - An array containing molecule's coordinates. - - Returns - ------- - numpy.ndarray - An 1d array with coordinates of the centre of mass including elements' - masses. - - """ - mass = molecular_weight(elements) - mass_array = np.array([[atomic_mass[i.upper()]] * 3 for i in elements]) - mass_coordinates = coordinates * mass_array - return (np.sum(mass_coordinates, axis=0) / np.array([mass, mass, mass]))
- - -
[docs]def compose_atom_list(*args): - """ - Return an `atom list` from elements and/or atom ids and coordinates. - - An `atom list` is a special object that some pywindowfunctions uses. - It is a nested list of lists with each individual list containing: - - 1. [[element, coordinates (x, y, z)], ...] - - 2. [[element, atom key, coordinates (x, y, z)], ...] - - They work better for molecular re-building than two separate arrays for - elements and coordinates do. - - Parameters - ---------- - elements : :class:`numpy.ndarray` - An array of all elements (type: str) in a molecule. - - coordinates : :class:`numpy.ndarray` - An array containing molecule's coordinates. - - atom_ids : :class:`numpy.ndarray`, optional - An array of all forcfield dependent atom keys (type:str) in a molecule. - - - Returns - ------- - list - Version 1 or version 2 atom list depending on input parameters. - - Raises - ------ - _FunctionError : :class:`Exception` - Raised when wrong number of parameters is passed to the function. - - """ - if len(args) == 2: - atom_list = [[ - i[0], - round(float(i[1]), 8), - round(float(i[2]), 8), - round(float(i[3]), 8), - ] for i in np.concatenate( - (args[0].reshape(-1, 1), args[1]), axis=1)] - elif len(args) == 3: - atom_list = [ - [ - i[0], - i[1], - round(float(i[2]), 8), - round(float(i[3]), 8), - round(float(i[4]), 8), - ] - for i in np.concatenate( - (np.concatenate( - (args[0].reshape(-1, 1), args[1].reshape(-1, 1) - ), axis=1), args[2]), - axis=1) - ] - else: - raise _FunctionError( - "The compose_atom_list() function accepts only 2 or 3 arguments.") - return atom_list
- - -
[docs]def decompose_atom_list(atom_list): - """ - Return elements and/or atom ids and coordinates from an `atom list`. - - Depending on input type of an atom list (version 1 or 2) - - 1. [[element, coordinates (x, y, z)], ...] - 2. [[element, atom key, coordinates (x, y, z)], ...] - - the function reverses what pywindow.utilities.compose_atom_list() do. - - Parameters - ---------- - atom_list : list - A nested list of lists (version 1 or 2) - - Returns - ------- - touple - A touple of elements and coordinates arrays, or if input contained - atom ideas, also atom ids array. - - """ - transpose = list(zip(*atom_list)) - if len(transpose) == 4: - elements = np.array(transpose[0]) - array_a = np.array(transpose[1]).reshape(-1, 1) - array_b = np.array(transpose[2]).reshape(-1, 1) - array_c = np.array(transpose[3]).reshape(-1, 1) - array_ab = np.concatenate((array_a, array_b), axis=1) - coordinates = np.concatenate((array_ab, array_c), axis=1) - return elements, coordinates - elif len(transpose) == 5: - elements = np.array(transpose[0]) - atom_ids = np.array(transpose[1]) - array_a = np.array(transpose[2]).reshape(-1, 1) - array_b = np.array(transpose[3]).reshape(-1, 1) - array_c = np.array(transpose[4]).reshape(-1, 1) - array_ab = np.concatenate((array_a, array_b), axis=1) - coordinates = np.concatenate((array_ab, array_c), axis=1) - return elements, atom_ids, coordinates - else: - raise _FunctionError( - "The decompose_atom_list() function accepts only list of lists " - " with only 4 or 5 items per sublist.")
- - -
[docs]def dlf_notation(atom_key): - """Return element for atom key using DL_F notation.""" - split = list(atom_key) - element = '' - number = False - count = 0 - while number is False: - element = "".join((element, split[count])) - count += 1 - if is_number(split[count]) is True: - number = True - # In case of for example Material Studio output, integers can also be - # in the beginning of the string. As the dlf_notation decipher function - # is very general in use, we have to make sure these integers are deleted. - # In standard DL_F notation the string will never start with integer so it - # will not affect the functionality towards it. - # EDIT2: also the '?' atoms, you can delete them manually or somewhere else - element = "".join(i for i in element if not is_number(i)) - element = "".join(i for i in element if i != '?') - return element
- - -
[docs]def opls_notation(atom_key): - """Return element for OPLS forcefield atom key.""" - # warning for Ne, He, Na types overlap - conflicts = ['ne', 'he', 'na'] - if atom_key in conflicts: - raise _AtomKeyConflict(( - "One of the OPLS conflicting " - "atom_keys has occured '{0}'. " - "For how to solve this issue see the manual or " - "MolecularSystem._atom_key_swap() doc string.").format(atom_key)) - for element in opls_atom_keys: - if atom_key in opls_atom_keys[element]: - return element - # In case if atom_key was not found in the OPLS keys dictionary - raise _AtomKeyError(( - "OPLS atom key {0} was not found in OPLS keys dictionary.").format( - atom_key))
- - -
[docs]def decipher_atom_key(atom_key, forcefield): - """ - Return element for deciphered atom key. - - This functions checks if the forcfield specified by user is supported - and passes the atom key to the appropriate function for deciphering. - - Parameters - ---------- - atom_key : str - The atom key which is to be deciphered. - - forcefield : str - The forcefield to which the atom key belongs to. - - Returns - ------- - str - A string that is the periodic table element equvalent of forcefield - atom key. - - """ - load_funcs = { - 'DLF': dlf_notation, - 'DL_F': dlf_notation, - 'OPLS': opls_notation, - 'OPLSAA': opls_notation, - 'OPLS2005': opls_notation, - 'OPLS3': opls_notation, - } - if forcefield.upper() in load_funcs.keys(): - return load_funcs[forcefield.upper()](atom_key) - else: - raise _ForceFieldError( - ("Unfortunetely, '{0}' forcefield is not supported by pyWINDOW." - " For list of supported forcefields see User's Manual or " - "MolecularSystem._decipher_atom_keys() function doc string." - ).format(forcefield))
- - -
[docs]def shift_com(elements, coordinates, com_adjust=np.zeros(3)): - """ - Return coordinates translated by some vector. - - Parameters - ---------- - elements : numpy.ndarray - An array of all elements (type: str) in a molecule. - - coordinates : numpy.ndarray - An array containing molecule's coordinates. - - com_adjust : numpy.ndarray (default = [0, 0, 0]) - - Returns - ------- - numpy.ndarray - Translated array of molecule's coordinates. - - """ - com = center_of_mass(elements, coordinates) - com = np.array([com - com_adjust] * coordinates.shape[0]) - return coordinates - com
- - -
[docs]def max_dim(elements, coordinates): - """ - Return the maximum diameter of a molecule. - - Parameters - ---------- - elements : numpy.ndarray - An array of all elements (type: str) in a molecule. - - coordinates : numpy.ndarray - An array containing molecule's coordinates. - - Returns - ------- - - """ - atom_vdw_vertical = np.matrix( - [[atomic_vdw_radius[i.upper()]] for i in elements]) - atom_vdw_horizontal = np.matrix( - [atomic_vdw_radius[i.upper()] for i in elements]) - dist_matrix = euclidean_distances(coordinates, coordinates) - vdw_matrix = atom_vdw_vertical + atom_vdw_horizontal - re_dist_matrix = dist_matrix + vdw_matrix - final_matrix = np.triu(re_dist_matrix) - i1, i2 = np.unravel_index(final_matrix.argmax(), final_matrix.shape) - maxdim = final_matrix[i1, i2] - return i1, i2, maxdim
- - -
[docs]def pore_diameter(elements, coordinates, com=None): - """Return pore diameter of a molecule.""" - if com is None: - com = center_of_mass(elements, coordinates) - atom_vdw = np.array([[atomic_vdw_radius[x.upper()]] for x in elements]) - dist_matrix = euclidean_distances(coordinates, com.reshape(1, -1)) - re_dist_matrix = dist_matrix - atom_vdw - index = np.argmin(re_dist_matrix) - pored = re_dist_matrix[index][0] * 2 - return (pored, index)
- - -
[docs]def correct_pore_diameter(com, *params): - """Return negative of a pore diameter. (optimisation function).""" - elements, coordinates = params - return (-pore_diameter(elements, coordinates, com)[0])
- - -
[docs]def opt_pore_diameter(elements, coordinates, bounds=None, com=None, **kwargs): - """Return optimised pore diameter and it's COM.""" - args = elements, coordinates - if com is not None: - pass - else: - com = center_of_mass(elements, coordinates) - if bounds is None: - pore_r = pore_diameter(elements, coordinates, com=com)[0] / 2 - bounds = ( - (com[0]-pore_r, com[0]+pore_r), - (com[1]-pore_r, com[1]+pore_r), - (com[2]-pore_r, com[2]+pore_r) - ) - minimisation = minimize( - correct_pore_diameter, x0=com, args=args, bounds=bounds) - pored = pore_diameter(elements, coordinates, com=minimisation.x) - return (pored[0], pored[1], minimisation.x)
- - -
[docs]def sphere_volume(sphere_radius): - """Return volume of a sphere.""" - return (4 / 3 * np.pi * sphere_radius**3)
- - -
[docs]def asphericity(S): - return (S[0] - (S[1] + S[2]) / 2)
- - -
[docs]def acylidricity(S): - return (S[1] - S[2])
- - -
[docs]def relative_shape_anisotropy(S): - return (1 - 3 * ( - (S[0] * S[1] + S[0] * S[2] + S[1] * S[2]) / (np.sum(S))**2))
- - -
[docs]def get_tensor_eigenvalues(T, sort=False): - if sort: - return (sorted(np.linalg.eigvals(T), reverse=True)) - else: - return (np.linalg.eigvals(T))
- - -
[docs]def get_gyration_tensor(elements, coordinates): - """ - Return the gyration tensor of a molecule. - - The gyration tensor should be invariant to the molecule's position. - The known formulas for the gyration tensor have the correction for the - centre of mass of the molecule, therefore, the coordinates are first - corrected for the centre of mass and essentially shifted to the origin. - - Parameters - ---------- - elements : numpy.ndarray - The array containing the molecule's elemental data. - - coordinates : numpy.ndarray - The array containing the Cartesian coordinates of the molecule. - - Returns - ------- - numpy.ndarray - The gyration tensor of a molecule invariant to the molecule's position. - - """ - # First calculate COM for correction. - com = centre_of_mass(elements, coordinates) - # Correct the coordinates for the COM. - coordinates = coordinates - com - # Calculate diagonal and then other values of the matrix. - diag = np.sum(coordinates**2, axis=0) - xy = np.sum(coordinates[:, 0] * coordinates[:, 1]) - xz = np.sum(coordinates[:, 0] * coordinates[:, 2]) - yz = np.sum(coordinates[:, 1] * coordinates[:, 2]) - S = np.array([[diag[0], xy, xz], [xy, diag[1], yz], - [xz, yz, diag[2]]]) / coordinates.shape[0] - return (S)
- - -
[docs]def get_inertia_tensor(elements, coordinates): - """ - Return the tensor of inertia a molecule. - - Parameters - ---------- - elements : numpy.ndarray - The array containing the molecule's elemental data. - - coordinates : numpy.ndarray - The array containing the Cartesian coordinates of the molecule. - - Returns - ------- - numpy.ndarray - The tensor of inertia of a molecule. - - """ - pow2 = coordinates**2 - molecular_weight = np.array( - [[atomic_mass[e.upper()]] for e in elements]) - - diag_1 = np.sum(molecular_weight * (pow2[:, 1] + pow2[:, 2])) - diag_2 = np.sum(molecular_weight * (pow2[:, 0] + pow2[:, 2])) - diag_3 = np.sum(molecular_weight * (pow2[:, 0] + pow2[:, 1])) - - mxy = np.sum(-molecular_weight * coordinates[:, 0] * coordinates[:, 1]) - mxz = np.sum(-molecular_weight * coordinates[:, 0] * coordinates[:, 2]) - myz = np.sum(-molecular_weight * coordinates[:, 1] * coordinates[:, 2]) - - inertia_tensor = np.array([[diag_1, mxy, mxz], [mxy, diag_2, myz], - [mxz, myz, diag_3]]) / coordinates.shape[0] - return (inertia_tensor)
- - -
[docs]def principal_axes(elements, coordinates): - return (np.linalg.eig(get_inertia_tensor(elements, coordinates))[1].T)
- - -
[docs]def normalize_vector(vector): - """ - Normalize a vector. - - A new vector is returned, the original vector is not modified. - - Parameters - ---------- - vector : np.array - The vector to be normalized. - - Returns - ------- - np.array - The normalized vector. - - """ - v = np.divide(vector, np.linalg.norm(vector)) - return np.round(v, decimals=4)
- - -
[docs]def rotation_matrix_arbitrary_axis(angle, axis): - """ - Return a rotation matrix of `angle` radians about `axis`. - - Parameters - ---------- - angle : int or float - The size of the rotation in radians. - - axis : numpy.array - A 3 element aray which represents a vector. The vector is the - axis about which the rotation is carried out. - - Returns - ------- - numpy.array - A 3x3 array representing a rotation matrix. - - """ - axis = normalize_vector(axis) - - a = np.cos(angle / 2) - b, c, d = axis * np.sin(angle / 2) - - e11 = np.square(a) + np.square(b) - np.square(c) - np.square(d) - e12 = 2 * (b * c - a * d) - e13 = 2 * (b * d + a * c) - - e21 = 2 * (b * c + a * d) - e22 = np.square(a) + np.square(c) - np.square(b) - np.square(d) - e23 = 2 * (c * d - a * b) - - e31 = 2 * (b * d - a * c) - e32 = 2 * (c * d + a * b) - e33 = np.square(a) + np.square(d) - np.square(b) - np.square(c) - - return np.array([[e11, e12, e13], [e21, e22, e23], [e31, e32, e33]])
- - -
[docs]def align_principal_ax(elements, coordinates): - """ """ - coor = deepcopy(coordinates) - new_coor = [] - rot = [] - for i, j in zip([2, 1, 0], [[1, 0, 0], [0, 1, 0], [0, 0, 1]]): - p_axes = principal_axes(elements, coordinates) - - r_vec = np.cross(p_axes[i], np.array(j)) - sin = np.linalg.norm(r_vec) - cos = np.dot(p_axes[i], np.array(j)) - ang = np.arctan2(sin, cos) - - R_mat = np.matrix(rotation_matrix_arbitrary_axis(ang, r_vec)) - rot.append(R_mat) - - for i in coor: - new_coord = R_mat * i.reshape(-1, 1) - new_coor.append(np.array(new_coord.reshape(1, -1))[0]) - new_coor = np.array(new_coor) - coor = new_coor - new_coor = [] - return (coor, rot)
- - -
[docs]def calc_asphericity(elements, coordinates): - inertia_tensor = get_inertia_tensor(elements, coordinates) - tensor_eigenvalues = get_tensor_eigenvalues(inertia_tensor, sort=True) - return asphericity(tensor_eigenvalues)
- - -
[docs]def calc_acylidricity(elements, coordinates): - inertia_tensor = get_inertia_tensor(elements, coordinates) - tensor_eigenvalues = get_tensor_eigenvalues(inertia_tensor, sort=True) - return acylidricity(tensor_eigenvalues)
- - -
[docs]def calc_relative_shape_anisotropy(elements, coordinates): - inertia_tensor = get_inertia_tensor(elements, coordinates) - tensor_eigenvalues = get_tensor_eigenvalues(inertia_tensor, sort=True) - return relative_shape_anisotropy(tensor_eigenvalues)
- - -
[docs]def unit_cell_to_lattice_array(cryst): - """Return parallelpiped unit cell lattice matrix.""" - a_, b_, c_, alpha, beta, gamma = cryst - # Convert angles from degrees to radians. - r_alpha = np.deg2rad(alpha) - r_beta = np.deg2rad(beta) - r_gamma = np.deg2rad(gamma) - # Calculate unit cell volume that is neccessary. - volume = a_ * b_ * c_ * ( - 1 - np.cos(r_alpha)**2 - np.cos(r_beta)**2 - np.cos(r_gamma)**2 + 2 * - np.cos(r_alpha) * np.cos(r_beta) * np.cos(r_gamma))**0.5 - # Create the orthogonalisation Matrix (M^-1) - lattice matrix - a_x = a_ - a_y = b_ * np.cos(r_gamma) - a_z = c_ * np.cos(r_beta) - b_x = 0 - b_y = b_ * np.sin(r_gamma) - b_z = c_ * ( - np.cos(r_alpha) - np.cos(r_beta) * np.cos(r_gamma)) / np.sin(r_gamma) - c_x = 0 - c_y = 0 - c_z = volume / (a_ * b_ * np.sin(r_gamma)) - lattice_array = np.array( - [[a_x, a_y, a_z], [b_x, b_y, b_z], [c_x, c_y, c_z]]) - return lattice_array
- - -
[docs]def lattice_array_to_unit_cell(lattice_array): - """Return crystallographic param. from unit cell lattice matrix.""" - cell_lengths = np.sqrt(np.sum(lattice_array**2, axis=0)) - gamma_r = np.arccos(lattice_array[0][1] / cell_lengths[1]) - beta_r = np.arccos(lattice_array[0][2] / cell_lengths[2]) - alpha_r = np.arccos( - lattice_array[1][2] * np.sin(gamma_r) / cell_lengths[2] - + np.cos(beta_r) * np.cos(gamma_r) - ) - cell_angles = [ - np.rad2deg(alpha_r), np.rad2deg(beta_r), np.rad2deg(gamma_r) - ] - return np.append(cell_lengths, cell_angles)
- - -
[docs]def volume_from_lattice_array(lattice_array): - """Return unit cell's volume from lattice matrix.""" - return np.linalg.det(lattice_array)
- - -
[docs]def volume_from_cell_parameters(cryst): - """Return unit cell's volume from crystallographic parameters.""" - return volume_from_lattice_array(unit_cell_to_lattice_array(cryst))
- - -
[docs]def fractional_from_cartesian(coordinate, lattice_array): - """Return a fractional coordinate from a cartesian one.""" - deorthogonalisation_M = np.matrix(np.linalg.inv(lattice_array)) - fractional = deorthogonalisation_M * coordinate.reshape(-1, 1) - return np.array(fractional.reshape(1, -1))
- - -
[docs]def cartisian_from_fractional(coordinate, lattice_array): - """Return cartesian coordinate from a fractional one.""" - orthogonalisation_M = np.matrix(lattice_array) - orthogonal = orthogonalisation_M * coordinate.reshape(-1, 1) - return np.array(orthogonal.reshape(1, -1))
- - -
[docs]def cart2frac_all(coordinates, lattice_array): - """Convert all cartesian coordinates to fractional.""" - frac_coordinates = deepcopy(coordinates) - for coord in range(frac_coordinates.shape[0]): - frac_coordinates[coord] = fractional_from_cartesian( - frac_coordinates[coord], lattice_array) - return frac_coordinates
- - -
[docs]def frac2cart_all(frac_coordinates, lattice_array): - """Convert all fractional coordinates to cartesian.""" - coordinates = deepcopy(frac_coordinates) - for coord in range(coordinates.shape[0]): - coordinates[coord] = cartisian_from_fractional(coordinates[coord], - lattice_array) - return coordinates
- - -
[docs]def create_supercell(system, supercell=[[-1, 1], [-1, 1], [-1, 1]]): - """Create a supercell.""" - if 'lattice' not in system.keys(): - matrix = unit_cell_to_lattice_array(system['unit_cell']) - else: - matrix = system['lattice'] - coordinates = deepcopy(system['coordinates']) - multiplication_matrices = [] - for a_ in range(supercell[0][0], supercell[0][1] + 1): - for b_ in range(supercell[1][0], supercell[1][1] + 1): - for c_ in range(supercell[2][0], supercell[2][1] + 1): - mult_matrix = np.array([[a_, b_, c_]]) - mult_matrix = np.repeat( - mult_matrix, coordinates.shape[0], axis=0) - multiplication_matrices.append(mult_matrix) - frac_coordinates = cart2frac_all(coordinates, matrix) - updated_coordinates = [] - for mat in multiplication_matrices: - updated_coor = frac_coordinates + mat - updated_coordinates.append(updated_coor) - supercell_frac_coordinates = np.concatenate(updated_coordinates, axis=0) - supercell_coordinates = frac2cart_all(supercell_frac_coordinates, matrix) - # Now for each new cell in the supercell we need to repeat the - # elements array so that it maches - new_elements = deepcopy(system['elements']) - new_ids = deepcopy(system['atom_ids']) - for i in range(len(updated_coordinates) - 1): - new_elements = np.concatenate((new_elements, system['elements'])) - new_ids = np.concatenate((new_ids, system['atom_ids'])) - cryst = lattice_array_to_unit_cell(matrix) - supercell_system = { - 'elements': new_elements, - 'atom_ids': new_ids, - 'coordinates': supercell_coordinates, - 'unit_cell': cryst, - 'lattice': matrix, - } - return supercell_system
- - -
[docs]def is_inside_polyhedron(point, polyhedron): - if polyhedron.shape == (1, 6): - matrix = unit_cell_to_lattice_array(polyhedron) - if polyhedron.shape == (3, 3): - matrix = polyhedron - - frac_coord = pw.utilities.fractional_from_cartesian(point, matrix)[0] - - if 0 <= frac_coord[0] <= 1.000 and 0 <= frac_coord[ - 1] <= 1.000 and 0 <= frac_coord[2] <= 1.000: - return True - else: - return False
- - -
[docs]def normal_vector(origin, vectors): - """Return normal vector for two vectors with same origin.""" - return np.cross(vectors[0] - origin, vectors[1] - origin)
- - -
[docs]def discrete_molecules(system, rebuild=None, tol=0.4): - """ - Decompose molecular system into individual discreet molecules. - - Note - ---- - New formula for bonds: (26/07/17) - The two atoms, x and y, are considered bonded if the distance between - them, calculated with distance matrix, is within the ranges: - .. :math: - - Rcov(x) + Rcov(y) - t < R(x,y) < Rcov(x) + Rcov(y) + t - - where Rcov is the covalent radius and the tolarenace (t) is set to - 0.4 Angstrom. - - """ - # First we check which operation mode we use. - # 1) Non-periodic MolecularSystem. - # 2) Periodic MolecularSystem without rebuilding. - # 3) Periodic Molecular system with rebuilding (supercell provided). - if rebuild is not None: - mode = 3 - else: - if 'unit_cell' in system.keys(): - if system['unit_cell'].shape == (6,): - mode = 2 - else: - mode = 1 - elif 'lattice' in system.keys(): - if system['lattice'].shape == (3, 3): - mode = 2 - else: - mode = 1 - else: - mode = 1 - # We create a list containing all atoms, theirs periodic elements and - # coordinates. As this process is quite complicated, we need a list - # which we will gradually be reducing. - try: - elements = system['elements'] - coordinates = system['coordinates'] - except KeyError: - raise _FunctionError( - "The 'elements' key is missing in the 'system' dictionary " - "attribute of the MolecularSystem object. Which means, you need to" - " decipher the forcefield based atom keys first (see manual)." - ) - coordinates = system['coordinates'] - args = (elements, coordinates) - adj = 0 - # If there are forcefield 'atom ids' as well we will retain them. - if 'atom_ids' in system.keys(): - atom_ids = system['atom_ids'] - args = (elements, atom_ids, coordinates) - adj = 1 - atom_list = compose_atom_list(*args) - atom_coor = decompose_atom_list(atom_list)[1 + adj] - # Scenario 1: We load a non-periodic MolecularSystem. - # We will not have 'unit_cell' nor 'lattice' keywords in the dictionary - # and also we do not do any re-building. - # Scenario 2: We load a periodic MolecularSystem. We want to only Extract - # complete molecules that do not have been affected by the periodic - # boundary. - # Scenario 3: We load a periodic Molecular System. We want it to be rebuild - # therefore, we also provide a supercell. - # Scenarios 2 and 3 require a lattice and also their origin is at origin. - # Scenario 1 should have the origin at the center of mass of the system. - # EDIT 09-04-18: All origins/pseudo_origin had to be skewed towards some - # direction (x + 0.01) so that there would be no ambiguity in periodic - # ang highly symmetric systems where the choice of the closest atom would - # be random from a set of equally far choices - bug found in the testing - # this way rebuild system should always look the same from the same input - # and on different machines. - if mode == 2 or mode == 3: - # Scenarios 2 or 3. - origin = np.array([0.01, 0., 0.]) - if 'lattice' not in system.keys(): - matrix = unit_cell_to_lattice_array(system['unit_cell']) - else: - matrix = system['lattice'] - pseudo_origin_frac = np.array([0.26, 0.25, 0.25]) - pseudo_origin = cartisian_from_fractional(pseudo_origin_frac, matrix) - # If a supercell is also provided that encloses the unit cell for the - # reconstruction of the molecules through the periodic boundary. - if rebuild is not None: - selements = rebuild['elements'] - sids = rebuild['atom_ids'] - scoordinates = rebuild['coordinates'] - satom_list = compose_atom_list(selements, sids, scoordinates) - satom_coor = decompose_atom_list(satom_list)[1 + adj] - # There is one more step. We need to sort out for all the - # reconstructed molecules, which are the ones that belong to the - # unit cell. As we did the reconstruction to every chunk in the unit - # cell we have now some molecules that belong to neighbouring cells. - # The screening is simple. If the COM of a molecule translated to - # fractional coordinates (so that it works for parallelpiped) is - # within the unit cell boundaries <0, 1> then it's it. There is - # an exception, for the trajectories, very often the unit cell - # is centered at origin. Therefore we need to use <-0.5, 0.5> - # boundary. We will simply decide which is the case by calculating - # the centre of mass of the whole system. - system_com = center_of_mass(elements, coordinates) - if np.allclose(system_com, origin, atol=1e-00): - boundary = np.array([-0.5, 0.5]) - else: - boundary = np.array([0., 1.]) - else: - # Scenario 1. - pseudo_origin = center_of_mass( - elements, coordinates) + np.array([0.01, 0., 0.]) - # Here the final discrete molecules will be stored. - molecules = [] - # Exceptions. Usually end-point atoms that create single bonds or - # just a separate atoms in the system. - exceptions = ['H', 'CL', 'BR', 'F', 'HE', 'AR', 'NE', 'KR', 'XE', 'RN'] - # The upper limit for distances analysed for bonds will be assigned for - # a given system (to save time). We take set('elements') and then find - # the largest R(cov) in the system and set the max_dist as a double - # of it plus the 150% tolerance (tol). - set_of_elements = set(system['elements']) - max_r_cov = max([ - atomic_covalent_radius[i.upper()] for i in set_of_elements]) - max_dist = 2 * max_r_cov + tol - # We continue untill all items in the list have been analysed and popped. - while atom_list: - inside_atoms_heavy = [ - i for i in atom_list if i[0].upper() not in exceptions - ] - if inside_atoms_heavy: - # Now we create an array of atom coordinates. It does seem - # somehow counter-intuitive as this is what we started with - # and made it into a list. But, in my opinion it's the only - # way to do it. It's hard to control and delete items in two - # separate arrays that we started with and we don't want - # atoms already assigned in our array for distance matrix. - inside_atoms_coord_heavy = decompose_atom_list(inside_atoms_heavy)[ - 1 + adj] - dist_matrix = euclidean_distances(inside_atoms_coord_heavy, - pseudo_origin.reshape(1, -1)) - atom_index_x, _ = np.unravel_index(dist_matrix.argmin(), - dist_matrix.shape) - # Added this so that lone atoms (even if heavy) close to the - # periodic boundary are not analysed, as they surely have matching - # symmetry equivalence that bind to a bigger atom cluster inside - # the unit_cell. - potential_starting_point = inside_atoms_heavy[atom_index_x] - pot_arr = np.array(potential_starting_point[1 + adj:]) - dist_matrix = euclidean_distances( - atom_coor, pot_arr.reshape(1, -1) - ) - idx = (dist_matrix > 0.1) * (dist_matrix < max_dist) - if len(idx) < 1: - pass - else: - working_list = [potential_starting_point] - else: - # Safety check. - break - final_molecule = [] - while working_list: - working_list_temp = [] - try: - atom_coor = decompose_atom_list(atom_list)[1 + adj] - except _FunctionError: - atom_coor = None - for i in working_list: - if i[0].upper() not in exceptions: - # It's of GREATEST importance that the i_arr variable - # is assigned here before entering the atom_coor loop.! - # Otherwise it will not be re-asigned when the satom_list - # still iterates, but the atom_list is already empty... - i_arr = np.array(i[1 + adj:]) - if atom_coor is not None: - dist_matrix = euclidean_distances( - atom_coor, i_arr.reshape(1, -1) - ) - idx = (dist_matrix > 0.1) * (dist_matrix < max_dist) - neighbours_indexes = np.where(idx)[0] - for j in neighbours_indexes: - j_arr = np.array(atom_coor[j]) - r_i_j = distance(i_arr, j_arr) - r_cov_i_j = atomic_covalent_radius[ - i[0].upper()] + atomic_covalent_radius[ - atom_list[j][0].upper()] - if r_cov_i_j - tol < r_i_j < r_cov_i_j + tol: - working_list_temp.append(atom_list[j]) - if rebuild is not None: - sdist_matrix = euclidean_distances( - satom_coor, i_arr.reshape(1, -1)) - sidx = (sdist_matrix > 0.1) * (sdist_matrix < max_dist) - sneighbours_indexes = np.where(sidx)[0] - for j in sneighbours_indexes: - if satom_list[j] in atom_list: - pass - else: - j_arr = np.array(satom_coor[j]) - r_i_j = distance(i_arr, j_arr) - r_cov_i_j = atomic_covalent_radius[ - i[0].upper() - ] + atomic_covalent_radius[ - satom_list[j][0].upper()] - if r_cov_i_j - tol < r_i_j < r_cov_i_j + tol: - working_list_temp.append(satom_list[j]) - final_molecule.append(i) - else: - final_molecule.append(i) - for i in working_list: - try: - atom_list.remove(i) - except ValueError: - pass - # We empty the working list as all the items were analysed - # and moved to the final_molecule list. - working_list = [] - # We make sure there are no duplicates in the working_list_temp. - working_list_temp = unique(working_list_temp) - # Now we move the entries from the temporary working list - # to the working list for looping analysys. - for i in working_list_temp: - # We make sure that only new and unassigned atoms are - # being transfered. - if i not in final_molecule: - working_list.append(i) - final_molecule_dict = {} - final_molecule_dict['elements'] = np.array( - [x[0] for x in final_molecule], dtype='str') - final_molecule_dict['coordinates'] = np.array( - [[*xyz[1 + adj:]] for xyz in final_molecule]) - if adj == 1: - final_molecule_dict['atom_ids'] = np.array( - [x[1] for x in final_molecule], dtype='str') - # In general we always want the molecule so the initial bool_ is True. - bool_ = True - # But, for periodic only if the molecule is in the initial unit cell. - if rebuild is not None: - com = center_of_mass(final_molecule_dict['elements'], - final_molecule_dict['coordinates']) - com_frac = fractional_from_cartesian(com, matrix)[0] - # If we don't round the numerical errors will come up. - com_frac_round = np.around(com_frac, decimals=8) - bool_ = np.all(np.logical_and(com_frac_round >= boundary[0], - com_frac_round < boundary[1]), - axis=0) - if bool(bool_) is True: - molecules.append(final_molecule_dict) - return molecules
- - -
[docs]def angle_between_vectors(x, y): - """Calculate the angle between two vectors x and y.""" - first_step = abs(x[0] * y[0] + x[1] * y[1] + x[2] * y[2]) / ( - np.sqrt(x[0]**2 + x[1]**2 + x[2]**2) * - np.sqrt(y[0]**2 + y[1]**2 + y[2]**2)) - second_step = np.arccos(first_step) - return (second_step)
- - -
[docs]def vector_analysis(vector, coordinates, elements_vdw, increment=1.0): - """Analyse a sampling vector's path for window analysis purpose.""" - # Calculate number of chunks if vector length is divided by increment. - chunks = int(np.linalg.norm(vector) // increment) - # Create a single chunk. - chunk = vector / chunks - # Calculate set of points on vector's path every increment. - vector_pathway = np.array([chunk * i for i in range(chunks + 1)]) - analysed_vector = np.array([ - np.amin( - euclidean_distances(coordinates, i.reshape(1, -1)) - elements_vdw) - for i in vector_pathway - ]) - if all(i > 0 for i in analysed_vector): - pos = np.argmin(analysed_vector) - # As first argument we need to give the distance from the origin. - dist = np.linalg.norm(chunk * pos) - return np.array( - [dist, analysed_vector[pos] * 2, *chunk * pos, *vector])
- - -
[docs]def vector_preanalysis(vector, coordinates, elements_vdw, increment=1.0): - norm_vec = vector/np.linalg.norm(vector) - intersections = [] - origin = center_of_coor(coordinates) - L = coordinates - origin - t_ca = np.dot(L, norm_vec) - d = np.sqrt(np.einsum('ij,ij->i', L, L) - t_ca**2) - under_sqrt = elements_vdw**2 - d**2 - diag = under_sqrt.diagonal() - positions = np.argwhere(diag > 0) - for pos in positions: - t_hc = np.sqrt(diag[pos[0]]) - t_0 = t_ca[pos][0] - t_hc - t_1 = t_ca[pos][0] + t_hc - - P_0 = origin + np.dot(t_0, norm_vec) - P_1 = origin + np.dot(t_1, norm_vec) - # print(np.linalg.norm(P_0), np.linalg.norm(P_1)) - if np.linalg.norm(P_0) < np.linalg.norm(P_1): - intersections.append(1) - else: - intersections.append(0) - if sum(intersections) == 0: - return vector_analysis(vector, coordinates, elements_vdw, increment)
- - -
[docs]def optimise_xy(xy, *args): - """Return negative pore diameter for x and y coordinates optimisation.""" - z, elements, coordinates = args - window_com = np.array([xy[0], xy[1], z]) - return -pore_diameter(elements, coordinates, com=window_com)[0]
- - -
[docs]def optimise_z(z, *args): - """Return pore diameter for coordinates optimisation in z direction.""" - x, y, elements, coordinates = args - window_com = np.array([x, y, z]) - return pore_diameter(elements, coordinates, com=window_com)[0]
- - -
[docs]def window_analysis(window, - elements, - coordinates, - elements_vdw, - increment2=0.1, - z_bounds=[None, None], - lb_z=True, - z_second_mini=False, - **kwargs): - """ - Return window diameter and window's centre. - - Parameters - ---------- - widnow: list - - elements: numpy.array - - coordinates: numpy.array - - elements_vdw: numpy.array - - step: float - - """ - # Copy the coordinates as we will manipulate them. - coordinates = deepcopy(coordinates) - # Find the vector with the largest window sampling diameter from the pool. - vector_ = window[window.argmax(axis=0)[1]][5:8] - vector_analysed = vector_analysis( - vector_, coordinates, elements_vdw, increment=increment2) - # A safety check, if the refined analysis give None we end the function. - if vector_analysed is not None: - pass - else: - return None - vector = vector_analysed[5:8] - # Unit vectors. - vec_a = [1, 0, 0] - vec_b = [0, 1, 0] - vec_c = [0, 0, 1] - # Angles needed for rotation (in radians) to rotate and translate the - # molecule for the vector to become the Z-axis. - angle_1 = angle_between_vectors(np.array([vector[0], vector[1], 0]), vec_a) - angle_2 = angle_between_vectors(vector, vec_c) - # Depending in which cartesian coordinate system area the vector is - # We need a rotation into a different direction and by different value. - if vector[0] >= 0 and vector[1] >= 0 and vector[2] >= 0: - angle_1 = -angle_1 - angle_2 = -angle_2 - if vector[0] < 0 and vector[1] >= 0 and vector[2] >= 0: - angle_1 = np.pi * 2 + angle_1 - angle_2 = angle_2 - if vector[0] >= 0 and vector[1] < 0 and vector[2] >= 0: - angle_1 = angle_1 - angle_2 = -angle_2 - if vector[0] < 0 and vector[1] < 0 and vector[2] >= 0: - angle_1 = np.pi * 2 - angle_1 - if vector[0] >= 0 and vector[1] >= 0 and vector[2] < 0: - angle_1 = -angle_1 - angle_2 = np.pi + angle_2 - if vector[0] < 0 and vector[1] >= 0 and vector[2] < 0: - angle_2 = np.pi - angle_2 - if vector[0] >= 0 and vector[1] < 0 and vector[2] < 0: - angle_2 = angle_2 + np.pi - if vector[0] < 0 and vector[1] < 0 and vector[2] < 0: - angle_1 = -angle_1 - angle_2 = np.pi - angle_2 - # Rotation matrix for rotation around Z-axis with angle_1. - rotation_around_z = np.array([[np.cos(angle_1), -np.sin(angle_1), 0], - [np.sin(angle_1), np.cos(angle_1), 0], - [0, 0, 1]]) - # Rotate the whole molecule around with rotation_around_z. - coordinates = np.array([np.dot(rotation_around_z, i) for i in coordinates]) - # Rotation matrix for rotation around Y-axis with angle_2 - rotation_around_y = np.array([[np.cos(angle_2), 0, np.sin(angle_2)], - [0, 1, 0], - [-np.sin(angle_2), 0, np.cos(angle_2)]]) - # Rotate the whole molecule around with rotation_around_y. - coordinates = np.array([np.dot(rotation_around_y, i) for i in coordinates]) - # Third step is translation. We are now at [0, 0, -z]. - # We shift the molecule so that center of the window is at the origin. - # The `z` is from original vector analysis. It is the point on the vector - # where the largest sampling sphere was (vector_analysed[0]). - new_z = vector_analysed[0] - # Translate the whole molecule to shift window's center to origin. - coordinates = coordinates - np.array([[0, 0, new_z]] * - coordinates.shape[0]) - # !!!Here the window center (xy and z) optimisation take place!!! - window_com = np.array([0, 0, 0], dtype=float) - # The lb_z parameter is 'lower bound equal to z' which means, - # that we set the lower bound for the z optimisation to be equal - # to the -new_z as in some cages it's the COM - pore that is the - # limiting diameter. But, no lower than new_z because we don't want to - # move into the other direction. - if lb_z: - z_bounds[0] = -new_z - window_diameter, _ = pore_diameter(elements, coordinates, com=window_com) - # SciPy minimisation on z coordinate. - z_args = (window_com[0], window_com[1], elements, coordinates) - z_optimisation = minimize( - optimise_z, x0=window_com[2], args=z_args, bounds=[z_bounds]) - # Substitute the z coordinate for a minimised one. - window_com[2] = z_optimisation.x[0] - # SciPy brute optimisation on x and y coordinates in window plane. - xy_args = (window_com[2], elements, coordinates) - xy_bounds = ((-window_diameter / 2, window_diameter / 2), - (-window_diameter / 2, window_diameter / 2)) - xy_optimisation = brute( - optimise_xy, xy_bounds, args=xy_args, full_output=True, finish=fmin) - # Substitute the x and y coordinates for the optimised ones. - window_com[0] = xy_optimisation[0][0] - window_com[1] = xy_optimisation[0][1] - # Additional SciPy minimisation on z coordinate. Added on 18 May 2017. - # We can argue which aproach is best. Whether z opt and then xy opt - # or like now z opt -> xy opt -> additional z opt etc. I have also tested - # a loop of optimisations until some convergence and optimisation of - # xyz coordinates at the same time by optimising these two optimisations. - # In the end. I think this approach is best for cages. - # Update 20 October 2017: I made this optional and turned off by default - # In many cases that worsen the quality of the results and should be used - # with caution. - if z_second_mini is not False: - z_args = (window_com[0], window_com[1], elements, coordinates) - # The z_bounds should be passed in kwargs. - z_optimisation = minimize( - optimise_z, x0=window_com[2], args=z_args, bounds=[z_bounds]) - # Substitute the z coordinate for a minimised one. - window_com[2] = z_optimisation.x[0] - # Calculate the new window diameter. - window_diameter, _ = pore_diameter(elements, coordinates, com=window_com) - # To get the window true centre of mass we need to revere the rotation and - # translation operations on the window com. - # Reverse the translation by substracting the new_z. - window_com[2] = window_com[2] + new_z - angle_2_1 = -angle_2 - reverse_around_y = np.array([[np.cos(angle_2_1), 0, np.sin(angle_2_1)], - [0, 1, 0], - [-np.sin(angle_2_1), 0, np.cos(angle_2_1)]]) - # Reversing the second rotation around Y-axis. - window_com = np.dot(reverse_around_y, window_com) - angle_1_1 = -angle_1 - reverse_around_z = np.array([[np.cos(angle_1_1), -np.sin(angle_1_1), 0], - [np.sin(angle_1_1), np.cos(angle_1_1), 0], - [0, 0, 1]]) - # Reversing the first rotation around Z-axis. - window_com = np.dot(reverse_around_z, window_com) - return (window_diameter, window_com)
- - -
[docs]def find_windows(elements, - coordinates, - processes=None, - mol_size=None, - adjust=1, - pore_opt=True, - increment=1.0, - **kwargs): - """Return windows diameters and center of masses for a molecule.""" - # Copy the coordinates as will perform many opertaions on them - coordinates = deepcopy(coordinates) - # Center of our cartesian system is always at origin - origin = np.array([0, 0, 0]) - # Initial center of mass to reverse translation at the end - initial_com = center_of_mass(elements, coordinates) - # Shift the cage to the origin using either the standard center of mass - # or if pore_opt flag is True, the optimised pore center as center of mass - if pore_opt is True: - # Normally the pore is calculated from the COM of a molecule. - # So, esentially the molecule's COM is the pore center. - # To shift the molecule so that the center of the optimised pore - # is at the origin of the system and not the center of the not - # optimised one, we need to adjust the shift. We also have to update - # the initial com. - com_adjust = initial_com - opt_pore_diameter(elements, coordinates, ** - kwargs)[2] - initial_com = initial_com - com_adjust - coordinates = shift_com(elements, coordinates, com_adjust=com_adjust) - else: - # Otherwise, we just shift the cage to the origin. - coordinates = shift_com(elements, coordinates) - # We create an array of vdw radii of elements. - elements_vdw = np.array([[atomic_vdw_radius[x.upper()]] for x in elements]) - # We calculate maximum diameter of a molecule to determine the radius - # of a sampling sphere neccessary to enclose the whole molecule. - shpere_radius = max_dim(elements, coordinates)[2] / 2 - sphere_surface_area = 4 * np.pi * shpere_radius**2 - # Here we determine the number of sampling points necessary for a fine - # sampling. Smaller molecules require more finner density of sampling - # points on the sampling sphere's surface, whereas largen require less. - # This formula was created so that larger molecule do not take much longer - # to analyse, as number_sampling_points*length_of_sampling_vectors - # results in quadratic increase of sampling time. The 250 factor was - # specificly determined to produce close to 1 sampling point /Angstrom^2 - # for a sphere of radius ~ 24 Angstrom. We can adjust how fine is the - # sampling by changing the adjust factor. - number_of_points = int(np.log10(sphere_surface_area) * 250 * adjust) - # Here I use code by Alexandre Devert for spreading points on a sphere: - # http://blog.marmakoide.org/?p=1 - golden_angle = np.pi * (3 - np.sqrt(5)) - theta = golden_angle * np.arange(number_of_points) - z = np.linspace(1 - 1.0 / number_of_points, 1.0 / number_of_points - 1.0, - number_of_points) - radius = np.sqrt(1 - z * z) - points = np.zeros((number_of_points, 3)) - points[:, 0] = radius * np.cos(theta) * shpere_radius - points[:, 1] = radius * np.sin(theta) * shpere_radius - points[:, 2] = z * shpere_radius - # Here we will compute the eps parameter for the sklearn.cluster.DBSCAN - # (3-dimensional spatial clustering algorithm) which is the mean distance - # to the closest point of all points. - values = [] - tree = KDTree(points) - for i in points: - dist, ind = tree.query(i.reshape(1, -1), k=10) - values.extend(dist) - mean_distance = np.mean(values) - # The best eps is parametrized when adding the mean distance and it's root. - eps = mean_distance + mean_distance**0.5 - # Here we either run the sampling points vectors analysis in serial - # or parallel. The vectors that go through molecular pores return - # as analysed list with the increment at vector's path with largest - # included sphere, coordinates for this narrow channel point. vectors - # that find molecule on theirs path are return as NoneType object. - # Parralel analysis on user's defined number of CPUs. - if processes: - pool = Pool(processes=processes) - parallel = [ - pool.apply_async( - vector_preanalysis, - args=( - point, - coordinates, - elements_vdw, ), - kwds={'increment': increment}) for point in points - ] - results = [p.get() for p in parallel if p.get() is not None] - pool.terminate() - # Dataset is an array of sampling points coordinates. - dataset = np.array([x[5:8] for x in results]) - else: - results = [ - vector_preanalysis( - point, coordinates, elements_vdw, increment=increment) - for point in points - ] - results = [x for x in results if x is not None] - dataset = np.array([x[5:8] for x in results]) - # If not a single vector was returned from the analysis it mean that - # no molecular channels (what we call windows here) connects the - # molecule's interior with the surroungsings (exterior space). - # The number of windows in that case equals zero and zero is returned. - # Otherwise we continue our search for windows. - if len(results) == 0: - return None - else: - # Perfomr DBSCAN to cluster the sampling points vectors. - # the n_jobs will be developed later. - # db = DBSCAN(eps=eps, n_jobs=_ncpus).fit(dataset) - db = DBSCAN(eps=eps).fit(dataset) - core_samples_mask = np.zeros_like(db.labels_, dtype=bool) - core_samples_mask[db.core_sample_indices_] = True - labels = set(db.labels_) - # Assing cluster label to a sampling point. - clusters = [[i, j] for i, j in zip(results, db.labels_)] - clustered_results = {label: [] for label in labels} - # Create a dictionary of clusters with points listed. - [clustered_results[i[1]].append(i[0]) for i in clusters] - # No for the sampling point vector in each cluster that had - # the widest channel's 'neck' is assumed to pass the closest - # to the window's center and therefore will be passed to - # window analysis function. - # We also pass user defined settings for window analysis. - # Again either in serlia or in parallel. - # Noisy points get a cluster label -1, therefore we have to exclude it. - if processes: - pool = Pool(processes=processes) - parallel = [ - pool.apply_async( - window_analysis, - args=(np.array(clustered_results[cluster]), elements, - coordinates, elements_vdw), - kwds=kwargs) for cluster in clustered_results - if cluster != -1 - ] - window_results = [p.get() for p in parallel if p.get() is not None] - pool.terminate() - else: - window_results = [ - window_analysis( - np.array(clustered_results[cluster]), elements, - coordinates, elements_vdw, **kwargs) - for cluster in clustered_results if cluster != -1 - ] - # The function returns two numpy arrays, one with windows diameters - # in Angstrom, second with corresponding windows center's coordinates - windows = np.array([result[0] for result in window_results - if result is not None]) - windows_coms = np.array( - [np.add(result[1], initial_com) for result in window_results - if result is not None]) - # Safety measures, if one of the windows is None or negative a warning - # should be raised. - for result in window_results: - if result is None: - msg_ = " ".join( - ['Warning. One of the analysed windows has', - 'returned as None. See manual.'] - ) - # print(msg_) - elif result[0] < 0: - msg_ = " ".join( - ['Warning. One of the analysed windows has a vdW', - 'corrected diameter smaller than 0. See manual.'] - ) - # print(msg_) - return (windows, windows_coms)
- - -
[docs]def window_shape(window, - elements, - coordinates, - increment2=0.1, - z_bounds=[None, None], - lb_z=True, - z_second_mini=False, - **kwargs): - """ - Return window diameter and window's centre. - - Parameters - ---------- - widnow: list - - elements: numpy.array - - coordinates: numpy.array - - elements_vdw: numpy.array - - step: float - - """ - # Copy the coordinates as we will manipulate them. - coordinates = deepcopy(coordinates) - # We create an array of vdw radii of elements. - elements_vdw = np.array([[atomic_vdw_radius[x.upper()]] for x in elements]) - # Find the vector with the largest window sampling diameter from the pool. - vector_ = window[window.argmax(axis=0)[1]][5:8] - vector_analysed = vector_analysis( - vector_, coordinates, elements_vdw, increment=increment2) - # A safety check, if the refined analysis give None we end the function. - if vector_analysed is not None: - pass - else: - return None - vector = vector_analysed[5:8] - # Unit vectors. - vec_a = [1, 0, 0] - vec_b = [0, 1, 0] - vec_c = [0, 0, 1] - # Angles needed for rotation (in radians) to rotate and translate the - # molecule for the vector to become the Z-axis. - angle_1 = angle_between_vectors(np.array([vector[0], vector[1], 0]), vec_a) - angle_2 = angle_between_vectors(vector, vec_c) - # Depending in which cartesian coordinate system area the vector is - # We need a rotation into a different direction and by different value. - if vector[0] >= 0 and vector[1] >= 0 and vector[2] >= 0: - angle_1 = -angle_1 - angle_2 = -angle_2 - if vector[0] < 0 and vector[1] >= 0 and vector[2] >= 0: - angle_1 = np.pi * 2 + angle_1 - angle_2 = angle_2 - if vector[0] >= 0 and vector[1] < 0 and vector[2] >= 0: - angle_1 = angle_1 - angle_2 = -angle_2 - if vector[0] < 0 and vector[1] < 0 and vector[2] >= 0: - angle_1 = np.pi * 2 - angle_1 - if vector[0] >= 0 and vector[1] >= 0 and vector[2] < 0: - angle_1 = -angle_1 - angle_2 = np.pi + angle_2 - if vector[0] < 0 and vector[1] >= 0 and vector[2] < 0: - angle_2 = np.pi - angle_2 - if vector[0] >= 0 and vector[1] < 0 and vector[2] < 0: - angle_2 = angle_2 + np.pi - if vector[0] < 0 and vector[1] < 0 and vector[2] < 0: - angle_1 = -angle_1 - angle_2 = np.pi - angle_2 - # Rotation matrix for rotation around Z-axis with angle_1. - rotation_around_z = np.array([[np.cos(angle_1), -np.sin(angle_1), 0], - [np.sin(angle_1), np.cos(angle_1), 0], - [0, 0, 1]]) - # Rotate the whole molecule around with rotation_around_z. - coordinates = np.array([np.dot(rotation_around_z, i) for i in coordinates]) - # Rotation matrix for rotation around Y-axis with angle_2 - rotation_around_y = np.array([[np.cos(angle_2), 0, np.sin(angle_2)], - [0, 1, 0], - [-np.sin(angle_2), 0, np.cos(angle_2)]]) - # Rotate the whole molecule around with rotation_around_y. - coordinates = np.array([np.dot(rotation_around_y, i) for i in coordinates]) - # Third step is translation. We are now at [0, 0, -z]. - # We shift the molecule so that center of the window is at the origin. - # The `z` is from original vector analysis. It is the point on the vector - # where the largest sampling sphere was (vector_analysed[0]). - new_z = vector_analysed[0] - # Translate the whole molecule to shift window's center to origin. - coordinates = coordinates - np.array([[0, 0, new_z]] * - coordinates.shape[0]) - # !!!Here the window center (xy and z) optimisation take place!!! - window_com = np.array([0, 0, 0], dtype=float) - # The lb_z parameter is 'lower bound equal to z' which means, - # that we set the lower bound for the z optimisation to be equal - # to the -new_z as in some cages it's the COM - pore that is the - # limiting diameter. But, no lower than new_z because we don't want to - # move into the other direction. - if lb_z: - z_bounds[0] = -new_z - window_diameter, _ = pore_diameter(elements, coordinates, com=window_com) - # SciPy minimisation on z coordinate. - z_args = (window_com[0], window_com[1], elements, coordinates) - z_optimisation = minimize( - optimise_z, x0=window_com[2], args=z_args, bounds=[z_bounds]) - # Substitute the z coordinate for a minimised one. - window_com[2] = z_optimisation.x[0] - # SciPy brute optimisation on x and y coordinates in window plane. - xy_args = (window_com[2], elements, coordinates) - xy_bounds = ((-window_diameter / 2, window_diameter / 2), - (-window_diameter / 2, window_diameter / 2)) - xy_optimisation = brute( - optimise_xy, xy_bounds, args=xy_args, full_output=True, finish=fmin) - # Substitute the x and y coordinates for the optimised ones. - window_com[0] = xy_optimisation[0][0] - window_com[1] = xy_optimisation[0][1] - # Additional SciPy minimisation on z coordinate. Added on 18 May 2017. - # We can argue which aproach is best. Whether z opt and then xy opt - # or like now z opt -> xy opt -> additional z opt etc. I have also tested - # a loop of optimisations until some convergence and optimisation of - # xyz coordinates at the same time by optimising these two optimisations. - # In the end. I think this approach is best for cages. - # Update 20 October 2017: I made this optional and turned off by default - # In many cases that worsen the quality of the results and should be used - # with caution. - if z_second_mini is not False: - z_args = (window_com[0], window_com[1], elements, coordinates) - # The z_bounds should be passed in kwargs. - z_optimisation = minimize( - optimise_z, x0=window_com[2], args=z_args, bounds=[z_bounds]) - # Substitute the z coordinate for a minimised one. - window_com[2] = z_optimisation.x[0] - # Getting the 2D plane crosssection of a window in XY plain. (10-04-18) - # First translation around Z axis. - vectors_translated = [ - [ - np.dot(rotation_around_z, i[5:])[0], - np.dot(rotation_around_z, i[5:])[1], - np.dot(rotation_around_z, i[5:])[2], - ] for i in window - ] - # Second rotation around Y axis. - vectors_translated = [ - [ - np.dot(rotation_around_y, i)[0], - np.dot(rotation_around_y, i)[1], - np.dot(rotation_around_y, i)[2] - ] for i in vectors_translated - ] - ref_distance = (new_z - window_com[2]) / np.linalg.norm(vector) - # Cutting the XY plane. - XY_plane = np.array( - [ - [i[0] * ref_distance, i[1] * ref_distance] - for i in vectors_translated - ] - ) - return XY_plane
- - -
[docs]def find_windows_new(elements, - coordinates, - processes=None, - mol_size=None, - adjust=1, - pore_opt=True, - increment=1.0, - **kwargs): - """Return windows diameters and center of masses for a molecule.""" - # Copy the coordinates as will perform many opertaions on them - coordinates = deepcopy(coordinates) - # Center of our cartesian system is always at origin - origin = np.array([0, 0, 0]) - # Initial center of mass to reverse translation at the end - initial_com = center_of_mass(elements, coordinates) - # Shift the cage to the origin using either the standard center of mass - # or if pore_opt flag is True, the optimised pore center as center of mass - if pore_opt is True: - # Normally the pore is calculated from the COM of a molecule. - # So, esentially the molecule's COM is the pore center. - # To shift the molecule so that the center of the optimised pore - # is at the origin of the system and not the center of the not - # optimised one, we need to adjust the shift. We also have to update - # the initial com. - com_adjust = initial_com - opt_pore_diameter(elements, coordinates, ** - kwargs)[2] - initial_com = initial_com - com_adjust - coordinates = shift_com(elements, coordinates, com_adjust=com_adjust) - else: - # Otherwise, we just shift the cage to the origin. - coordinates = shift_com(elements, coordinates) - # We create an array of vdw radii of elements. - elements_vdw = np.array([[atomic_vdw_radius[x.upper()]] for x in elements]) - # We calculate maximum diameter of a molecule to determine the radius - # of a sampling sphere neccessary to enclose the whole molecule. - shpere_radius = max_dim(elements, coordinates)[2] / 2 - sphere_surface_area = 4 * np.pi * shpere_radius**2 - # Here we determine the number of sampling points necessary for a fine - # sampling. Smaller molecules require more finner density of sampling - # points on the sampling sphere's surface, whereas largen require less. - # This formula was created so that larger molecule do not take much longer - # to analyse, as number_sampling_points*length_of_sampling_vectors - # results in quadratic increase of sampling time. The 250 factor was - # specificly determined to produce close to 1 sampling point /Angstrom^2 - # for a sphere of radius ~ 24 Angstrom. We can adjust how fine is the - # sampling by changing the adjust factor. - number_of_points = int(np.log10(sphere_surface_area) * 250 * adjust) - # Here I use code by Alexandre Devert for spreading points on a sphere: - # http://blog.marmakoide.org/?p=1 - golden_angle = np.pi * (3 - np.sqrt(5)) - theta = golden_angle * np.arange(number_of_points) - z = np.linspace(1 - 1.0 / number_of_points, 1.0 / number_of_points - 1.0, - number_of_points) - radius = np.sqrt(1 - z * z) - points = np.zeros((number_of_points, 3)) - points[:, 0] = radius * np.cos(theta) * shpere_radius - points[:, 1] = radius * np.sin(theta) * shpere_radius - points[:, 2] = z * shpere_radius - # Here we will compute the eps parameter for the sklearn.cluster.DBSCAN - # (3-dimensional spatial clustering algorithm) which is the mean distance - # to the closest point of all points. - values = [] - tree = KDTree(points) - for i in points: - dist, ind = tree.query(i.reshape(1, -1), k=10) - values.extend(dist) - mean_distance = np.mean(values) - # The best eps is parametrized when adding the mean distance and it's root. - eps = mean_distance + mean_distance**0.5 - # Here we either run the sampling points vectors analysis in serial - # or parallel. The vectors that go through molecular pores return - # as analysed list with the increment at vector's path with largest - # included sphere, coordinates for this narrow channel point. vectors - # that find molecule on theirs path are return as NoneType object. - # Parralel analysis on user's defined number of CPUs. - if processes: - pool = Pool(processes=processes) - parallel = [ - pool.apply_async( - vector_preanalysis, - args=( - point, - coordinates, - elements_vdw, ), - kwds={'increment': increment}) for point in points - ] - results = [p.get() for p in parallel if p.get() is not None] - pool.terminate() - # Dataset is an array of sampling points coordinates. - dataset = np.array([x[5:8] for x in results]) - else: - results = [ - vector_preanalysis( - point, coordinates, elements_vdw, increment=increment) - for point in points - ] - results = [x for x in results if x is not None] - dataset = np.array([x[5:8] for x in results]) - # If not a single vector was returned from the analysis it mean that - # no molecular channels (what we call windows here) connects the - # molecule's interior with the surroungsings (exterior space). - # The number of windows in that case equals zero and zero is returned. - # Otherwise we continue our search for windows. - if len(results) == 0: - return None - else: - # Perfomr DBSCAN to cluster the sampling points vectors. - # the n_jobs will be developed later. - # db = DBSCAN(eps=eps, n_jobs=_ncpus).fit(dataset) - db = DBSCAN(eps=eps).fit(dataset) - core_samples_mask = np.zeros_like(db.labels_, dtype=bool) - core_samples_mask[db.core_sample_indices_] = True - labels = set(db.labels_) - # Assing cluster label to a sampling point. - clusters = [[i, j] for i, j in zip(results, db.labels_)] - clustered_results = {label: [] for label in labels} - # Create a dictionary of clusters with points listed. - [clustered_results[i[1]].append(i[0]) for i in clusters] - return clustered_results, elements, coordinates, initial_com
- - -
[docs]def calculate_window_diameter(window, elements, coordinates, **kwargs): - elements_vdw = np.array( - [[atomic_vdw_radius[x.upper()]] for x in elements] - ) - window_results = window_analysis( - np.array(window), elements, coordinates, elements_vdw, **kwargs - ) - # The function returns two numpy arrays, one with windows diameters - # in Angstrom, second with corresponding windows center's coordinates - if window_results: - return window_results[0] - else: - return None
- - -
[docs]def get_window_com(window, elements, coordinates, initial_com, **kwargs): - elements_vdw = np.array( - [[atomic_vdw_radius[x.upper()]] for x in elements] - ) - window_results = window_analysis( - np.array(window), elements, coordinates, elements_vdw, **kwargs - ) - # The function returns two numpy arrays, one with windows diameters - # in Angstrom, second with corresponding windows center's coordinates - if window_results: - # I correct the COM of window for the initial COM of the cage - return np.add(window_results[1], initial_com) - else: - return None
- - -
[docs]def vector_analysis_reversed(vector, coordinates, elements_vdw): - norm_vec = vector/np.linalg.norm(vector) - intersections = [] - origin = center_of_coor(coordinates) - L = coordinates - origin - t_ca = np.dot(L, norm_vec) - d = np.sqrt(np.einsum('ij,ij->i', L, L) - t_ca**2) - under_sqrt = elements_vdw**2 - d**2 - diag = under_sqrt.diagonal() - positions = np.argwhere(diag > 0) - for pos in positions: - t_hc = np.sqrt(diag[pos[0]]) - t_0 = t_ca[pos][0] - t_hc - t_1 = t_ca[pos][0] + t_hc - - P_0 = origin + np.dot(t_0, norm_vec) - P_1 = origin + np.dot(t_1, norm_vec) - if np.linalg.norm(P_0) < np.linalg.norm(P_1): - intersections.append([np.linalg.norm(P_1), P_1]) - if intersections: - intersection = sorted(intersections, reverse=True)[0][1] - dist_origin = np.linalg.norm(intersection) - return [dist_origin, intersection]
- - -
[docs]def find_average_diameter(elements, coordinates, adjust=1, increment=0.1, - processes=None, **kwargs): - """Return average diameter for a molecule.""" - # Copy the coordinates as will perform many opertaions on them - coordinates = deepcopy(coordinates) - # Center of our cartesian system is always at origin - origin = np.array([0, 0, 0]) - # Initial center of mass to reverse translation at the end - initial_com = center_of_mass(elements, coordinates) - # We just shift the cage to the origin. - coordinates = shift_com(elements, coordinates) - # We create an array of vdw radii of elements. - elements_vdw = np.array([[atomic_vdw_radius[x.upper()]] for x in elements]) - # We calculate maximum diameter of a molecule to determine the radius - # of a sampling sphere neccessary to enclose the whole molecule. - shpere_radius = max_dim(elements, coordinates)[2] - sphere_surface_area = 4 * np.pi * shpere_radius**2 - # Here we determine the number of sampling points necessary for a fine - # sampling. Smaller molecules require more finner density of sampling - # points on the sampling sphere's surface, whereas largen require less. - # This formula was created so that larger molecule do not take much longer - # to analyse, as number_sampling_points*length_of_sampling_vectors - # results in quadratic increase of sampling time. The 250 factor was - # specificly determined to produce close to 1 sampling point /Angstrom^2 - # for a sphere of radius ~ 24 Angstrom. We can adjust how fine is the - # sampling by changing the adjust factor. - number_of_points = int(np.log10(sphere_surface_area) * 250 * adjust) - # Here I use code by Alexandre Devert for spreading points on a sphere: - # http://blog.marmakoide.org/?p=1 - golden_angle = np.pi * (3 - np.sqrt(5)) - theta = golden_angle * np.arange(number_of_points) - z = np.linspace(1 - 1.0 / number_of_points, 1.0 / number_of_points - 1.0, - number_of_points) - radius = np.sqrt(1 - z * z) - points = np.zeros((number_of_points, 3)) - points[:, 0] = radius * np.cos(theta) * shpere_radius - points[:, 1] = radius * np.sin(theta) * shpere_radius - points[:, 2] = z * shpere_radius - # Here we analyse the vectors and retain the ones that create the molecule - # outline. - if processes: - pool = Pool(processes=processes) - parallel = [ - pool.apply_async( - vector_analysis_reversed, - args=( - point, coordinates, elements_vdw) - ) for point in points - ] - results = [p.get() for p in parallel if p.get() is not None] - pool.terminate() - else: - results = [ - vector_analysis_reversed( - point, coordinates, elements_vdw) - for point in points - ] - results_cleaned = [x[0] for x in results if x is not None] - return np.mean(results_cleaned)*2
- - -
[docs]def vector_analysis_pore_shape(vector, coordinates, elements_vdw): - norm_vec = vector/np.linalg.norm(vector) - intersections = [] - origin = center_of_coor(coordinates) - L = coordinates - origin - t_ca = np.dot(L, norm_vec) - d = np.sqrt(np.einsum('ij,ij->i', L, L) - t_ca**2) - under_sqrt = elements_vdw**2 - d**2 - diag = under_sqrt.diagonal() - positions = np.argwhere(diag > 0) - for pos in positions: - t_hc = np.sqrt(diag[pos[0]]) - t_0 = t_ca[pos][0] - t_hc - t_1 = t_ca[pos][0] + t_hc - - P_0 = origin + np.dot(t_0, norm_vec) - P_1 = origin + np.dot(t_1, norm_vec) - # print(np.linalg.norm(P_0), np.linalg.norm(P_1)) - if np.linalg.norm(P_0) < np.linalg.norm(P_1): - intersections.append([np.linalg.norm(P_0), P_0]) - if intersections: - return sorted(intersections)[0][1]
- - -
[docs]def calculate_pore_shape(elements, coordinates, adjust=1, increment=0.1, - **kwargs): - """Return average diameter for a molecule.""" - # Copy the coordinates as will perform many opertaions on them - coordinates = deepcopy(coordinates) - # Center of our cartesian system is always at origin - origin = np.array([0, 0, 0]) - # Initial center of mass to reverse translation at the end - initial_com = center_of_mass(elements, coordinates) - # We just shift the cage to the origin. - coordinates = shift_com(elements, coordinates) - # We create an array of vdw radii of elements. - elements_vdw = np.array([[atomic_vdw_radius[x.upper()]] for x in elements]) - # We calculate maximum diameter of a molecule to determine the radius - # of a sampling sphere neccessary to enclose the whole molecule. - shpere_radius = max_dim(elements, coordinates)[2]/2 - sphere_surface_area = 4 * np.pi * shpere_radius**2 - # Here we determine the number of sampling points necessary for a fine - # sampling. Smaller molecules require more finner density of sampling - # points on the sampling sphere's surface, whereas largen require less. - # This formula was created so that larger molecule do not take much longer - # to analyse, as number_sampling_points*length_of_sampling_vectors - # results in quadratic increase of sampling time. The 250 factor was - # specificly determined to produce close to 1 sampling point /Angstrom^2 - # for a sphere of radius ~ 24 Angstrom. We can adjust how fine is the - # sampling by changing the adjust factor. - number_of_points = int(np.log10(sphere_surface_area) * 250 * adjust) - # Here I use code by Alexandre Devert for spreading points on a sphere: - # http://blog.marmakoide.org/?p=1 - golden_angle = np.pi * (3 - np.sqrt(5)) - theta = golden_angle * np.arange(number_of_points) - z = np.linspace(1 - 1.0 / number_of_points, 1.0 / number_of_points - 1.0, - number_of_points) - radius = np.sqrt(1 - z * z) - points = np.zeros((number_of_points, 3)) - points[:, 0] = radius * np.cos(theta) * shpere_radius - points[:, 1] = radius * np.sin(theta) * shpere_radius - points[:, 2] = z * shpere_radius - # Here we will compute the eps parameter for the sklearn.cluster.DBSCAN - # (3-dimensional spatial clustering algorithm) which is the mean distance - # to the closest point of all points. - values = [] - tree = KDTree(points) - for i in points: - dist, ind = tree.query(i.reshape(1, -1), k=10) - values.extend(dist) - mean_distance = np.mean(values) - # The best eps is parametrized when adding the mean distance and it's root. - eps = mean_distance + mean_distance**0.5 - # Here we either run the sampling points vectors analysis in serial - # or parallel. The vectors that go through molecular voids return - # as analysed list with the increment at vector's path with largest - # included sphere, coordinates for this narrow channel point. vectors - # that find molecule on theirs path are return as NoneType object. - results = [ - vector_analysis_pore_shape(point, coordinates, elements_vdw) - for point in points - ] - results_cleaned = [x for x in results if x is not None] - ele = np.array(['X'] * len(results_cleaned)) - coor = np.array(results_cleaned) - return coor
- - -
[docs]def circumcircle_window(coordinates, atom_set): - # Calculating circumcircle - A = np.array(coordinates[int(atom_set[0])]) - B = np.array(coordinates[int(atom_set[1])]) - C = np.array(coordinates[int(atom_set[2])]) - a = np.linalg.norm(C - B) - b = np.linalg.norm(C - A) - c = np.linalg.norm(B - A) - s = (a + b + c) / 2 - # Holden et al. method is intended to only work with triads of carbons, - # therefore I substract the vdW radii for a carbon. - # These equation calculaties the window's radius. - R = a*b*c / 4 / np.sqrt(s * (s - a) * (s - b) * (s - c)) - 1.70 - # This steps are used to calculate the window's COM. - b1 = a*a * (b*b + c*c - a*a) - b2 = b*b * (a*a + c*c - b*b) - b3 = c*c * (a*a + b*b - c*c) - COM = np.column_stack((A, B, C)).dot(np.hstack((b1, b2, b3))) - # The window's COM. - COM /= b1 + b2 + b3 - return R, COM
- - -
[docs]def circumcircle(coordinates, atom_sets): - pld_diameter_list = [] - pld_com_list = [] - iter_ = 0 - while iter_ < len(atom_sets): - R, COM = circumcircle_window(coordinates, atom_sets[iter_]) - pld_diameter_list.append(R*2) - pld_com_list.append(COM) - iter_ += 1 - return pld_diameter_list, pld_com_list
-
- -
-
-
- - -
- -
-

- © Copyright 2017, Marcin Miklitz, Jelfs Materials Group. - -

-
- Built with Sphinx using a theme provided by Read the Docs. - -
- -
-
- -
- -
- - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_sources/index.rst.txt b/docs/_sources/index.rst.txt deleted file mode 100644 index 855a8b6..0000000 --- a/docs/_sources/index.rst.txt +++ /dev/null @@ -1,115 +0,0 @@ -.. pywindow documentation master file, created by - sphinx-quickstart on Thu Jul 19 13:12:27 2018. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to ``pywindow``'s documentation! -======================================== - -GitHub: - - https://github.com/JelfsMaterialsGroup/pywindow - stable release - - https://github.com/marcinmiklitz/pywindow - most up-to-date release - -Overview --------- - -``pywindow`` is a Python 3 library for the structural analysis of molecular pores. - -For quick start head to :ref:`modindex` and see :class:`pywindow.molecular.MolecularSystem`. - -.. toctree:: - :maxdepth: 2 - :caption: Contents: - -Installation ------------- - -Git clone the pywindow repository or download a zipped version. - - .. code-block:: bash - - cd pywindow/ - - python setup.py install - -Examples --------- - -For the specific examples of ``pywindow`` usage see Examples/ directory in the -``pywindow`` Github repository. - -Loading input -............. - -1. Using file as an input: - - .. code-block:: python - - import pywindow as pw - - molsys = pw.MolecularSystem.load_file(`data/input/PUDXES.xyz`) - -2. Using RDKit molecule object as an input: - - .. code-block:: python - - import pywindow as pw - from rdkit import Chem - - rdkit_mol = Chem.MolFromMol2File("data/input/PUDXES.mol2") - - molsys = pw.MolecularSystem.load_rdkit_mol(rdkit_mol) - -3. Using a dictionary (or another :attr:`MoleculeSystem.system`) as input: - - .. code-block:: python - - import pywindow as pw - - molsys = pw.MolecularSystem.load_file(`data/input/PUDXES.xyz`) - - molsys2 = pw.MolecularSystem.load_system(molsys.system) - -Pre-processing -.............. - -If our input requires pre-processing (rebuilding molecules through periodic -boundary and/or force field atom ids deciphering) the following methods allow -that: - -1. Rebuilding a periodic system - -.. code-block:: python - - rebuild_molsys = molsys.rebuild_system() - -2. Deciphering force field atom ids - -.. code-block:: python - - molsys.decipher_atom_keys('OPLS') - -If the force field is not supported by ``pywindow`` we can use -:func:`pywindow.MolecularSystem.decipher_atom_keys()` to generate a custom -force field deciphering tool. - -.. code-block:: python - - some_forcefield = { - 'ca': 'C', - 'ni': 'N', - 'hc': 'H', - 'ha': 'H' - } - - molsys.swap_atom_keys(some_forcefield) - - -Indices and tables ------------------- - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/_sources/modules.rst.txt b/docs/_sources/modules.rst.txt deleted file mode 100644 index 9d246ea..0000000 --- a/docs/_sources/modules.rst.txt +++ /dev/null @@ -1,7 +0,0 @@ -pywindow -======== - -.. toctree:: - :maxdepth: 4 - - pywindow diff --git a/docs/_sources/pywindow.rst.txt b/docs/_sources/pywindow.rst.txt deleted file mode 100644 index 880d1d5..0000000 --- a/docs/_sources/pywindow.rst.txt +++ /dev/null @@ -1,54 +0,0 @@ -pywindow package -================ - -Submodules ----------- - -pywindow.io\_tools module -------------------------- - -.. automodule:: pywindow.io_tools - :members: - :undoc-members: - :show-inheritance: - -pywindow.molecular module -------------------------- - -.. automodule:: pywindow.molecular - :members: - :undoc-members: - :show-inheritance: - -pywindow.tables module ----------------------- - -.. automodule:: pywindow.tables - :members: - :undoc-members: - :show-inheritance: - -pywindow.trajectory module --------------------------- - -.. automodule:: pywindow.trajectory - :members: - :undoc-members: - :show-inheritance: - -pywindow.utilities module -------------------------- - -.. automodule:: pywindow.utilities - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: pywindow - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/_static/ajax-loader.gif b/docs/_static/ajax-loader.gif deleted file mode 100644 index 61faf8c..0000000 Binary files a/docs/_static/ajax-loader.gif and /dev/null differ diff --git a/docs/_static/basic.css b/docs/_static/basic.css deleted file mode 100644 index 6df76b0..0000000 --- a/docs/_static/basic.css +++ /dev/null @@ -1,639 +0,0 @@ -/* - * basic.css - * ~~~~~~~~~ - * - * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/* -- main layout ----------------------------------------------------------- */ - -div.clearer { - clear: both; -} - -/* -- relbar ---------------------------------------------------------------- */ - -div.related { - width: 100%; - font-size: 90%; -} - -div.related h3 { - display: none; -} - -div.related ul { - margin: 0; - padding: 0 0 0 10px; - list-style: none; -} - -div.related li { - display: inline; -} - -div.related li.right { - float: right; - margin-right: 5px; -} - -/* -- sidebar --------------------------------------------------------------- */ - -div.sphinxsidebarwrapper { - padding: 10px 5px 0 10px; -} - -div.sphinxsidebar { - float: left; - width: 230px; - margin-left: -100%; - font-size: 90%; - word-wrap: break-word; - overflow-wrap : break-word; -} - -div.sphinxsidebar ul { - list-style: none; -} - -div.sphinxsidebar ul ul, -div.sphinxsidebar ul.want-points { - margin-left: 20px; - list-style: square; -} - -div.sphinxsidebar ul ul { - margin-top: 0; - margin-bottom: 0; -} - -div.sphinxsidebar form { - margin-top: 10px; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - -div.sphinxsidebar #searchbox input[type="text"] { - width: 170px; -} - -img { - border: 0; - max-width: 100%; -} - -/* -- search page ----------------------------------------------------------- */ - -ul.search { - margin: 10px 0 0 20px; - padding: 0; -} - -ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; -} - -ul.search li a { - font-weight: bold; -} - -ul.search li div.context { - color: #888; - margin: 2px 0 0 30px; - text-align: left; -} - -ul.keywordmatches li.goodmatch a { - font-weight: bold; -} - -/* -- index page ------------------------------------------------------------ */ - -table.contentstable { - width: 90%; - margin-left: auto; - margin-right: auto; -} - -table.contentstable p.biglink { - line-height: 150%; -} - -a.biglink { - font-size: 1.3em; -} - -span.linkdescr { - font-style: italic; - padding-top: 5px; - font-size: 90%; -} - -/* -- general index --------------------------------------------------------- */ - -table.indextable { - width: 100%; -} - -table.indextable td { - text-align: left; - vertical-align: top; -} - -table.indextable ul { - margin-top: 0; - margin-bottom: 0; - list-style-type: none; -} - -table.indextable > tbody > tr > td > ul { - padding-left: 0em; -} - -table.indextable tr.pcap { - height: 10px; -} - -table.indextable tr.cap { - margin-top: 10px; - background-color: #f2f2f2; -} - -img.toggler { - margin-right: 3px; - margin-top: 3px; - cursor: pointer; -} - -div.modindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -div.genindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -/* -- domain module index --------------------------------------------------- */ - -table.modindextable td { - padding: 2px; - border-collapse: collapse; -} - -/* -- general body styles --------------------------------------------------- */ - -div.body p, div.body dd, div.body li, div.body blockquote { - -moz-hyphens: auto; - -ms-hyphens: auto; - -webkit-hyphens: auto; - hyphens: auto; -} - -a.headerlink { - visibility: hidden; -} - -h1:hover > a.headerlink, -h2:hover > a.headerlink, -h3:hover > a.headerlink, -h4:hover > a.headerlink, -h5:hover > a.headerlink, -h6:hover > a.headerlink, -dt:hover > a.headerlink, -caption:hover > a.headerlink, -p.caption:hover > a.headerlink, -div.code-block-caption:hover > a.headerlink { - visibility: visible; -} - -div.body p.caption { - text-align: inherit; -} - -div.body td { - text-align: left; -} - -.first { - margin-top: 0 !important; -} - -p.rubric { - margin-top: 30px; - font-weight: bold; -} - -img.align-left, .figure.align-left, object.align-left { - clear: left; - float: left; - margin-right: 1em; -} - -img.align-right, .figure.align-right, object.align-right { - clear: right; - float: right; - margin-left: 1em; -} - -img.align-center, .figure.align-center, object.align-center { - display: block; - margin-left: auto; - margin-right: auto; -} - -.align-left { - text-align: left; -} - -.align-center { - text-align: center; -} - -.align-right { - text-align: right; -} - -/* -- sidebars -------------------------------------------------------------- */ - -div.sidebar { - margin: 0 0 0.5em 1em; - border: 1px solid #ddb; - padding: 7px 7px 0 7px; - background-color: #ffe; - width: 40%; - float: right; -} - -p.sidebar-title { - font-weight: bold; -} - -/* -- topics ---------------------------------------------------------------- */ - -div.topic { - border: 1px solid #ccc; - padding: 7px 7px 0 7px; - margin: 10px 0 10px 0; -} - -p.topic-title { - font-size: 1.1em; - font-weight: bold; - margin-top: 10px; -} - -/* -- admonitions ----------------------------------------------------------- */ - -div.admonition { - margin-top: 10px; - margin-bottom: 10px; - padding: 7px; -} - -div.admonition dt { - font-weight: bold; -} - -div.admonition dl { - margin-bottom: 0; -} - -p.admonition-title { - margin: 0px 10px 5px 0px; - font-weight: bold; -} - -div.body p.centered { - text-align: center; - margin-top: 25px; -} - -/* -- tables ---------------------------------------------------------------- */ - -table.docutils { - border: 0; - border-collapse: collapse; -} - -table caption span.caption-number { - font-style: italic; -} - -table caption span.caption-text { -} - -table.docutils td, table.docutils th { - padding: 1px 8px 1px 5px; - border-top: 0; - border-left: 0; - border-right: 0; - border-bottom: 1px solid #aaa; -} - -table.footnote td, table.footnote th { - border: 0 !important; -} - -th { - text-align: left; - padding-right: 5px; -} - -table.citation { - border-left: solid 1px gray; - margin-left: 1px; -} - -table.citation td { - border-bottom: none; -} - -/* -- figures --------------------------------------------------------------- */ - -div.figure { - margin: 0.5em; - padding: 0.5em; -} - -div.figure p.caption { - padding: 0.3em; -} - -div.figure p.caption span.caption-number { - font-style: italic; -} - -div.figure p.caption span.caption-text { -} - -/* -- field list styles ----------------------------------------------------- */ - -table.field-list td, table.field-list th { - border: 0 !important; -} - -.field-list ul { - margin: 0; - padding-left: 1em; -} - -.field-list p { - margin: 0; -} - -.field-name { - -moz-hyphens: manual; - -ms-hyphens: manual; - -webkit-hyphens: manual; - hyphens: manual; -} - -/* -- other body styles ----------------------------------------------------- */ - -ol.arabic { - list-style: decimal; -} - -ol.loweralpha { - list-style: lower-alpha; -} - -ol.upperalpha { - list-style: upper-alpha; -} - -ol.lowerroman { - list-style: lower-roman; -} - -ol.upperroman { - list-style: upper-roman; -} - -dl { - margin-bottom: 15px; -} - -dd p { - margin-top: 0px; -} - -dd ul, dd table { - margin-bottom: 10px; -} - -dd { - margin-top: 3px; - margin-bottom: 10px; - margin-left: 30px; -} - -dt:target, .highlighted { - background-color: #fbe54e; -} - -dl.glossary dt { - font-weight: bold; - font-size: 1.1em; -} - -.optional { - font-size: 1.3em; -} - -.sig-paren { - font-size: larger; -} - -.versionmodified { - font-style: italic; -} - -.system-message { - background-color: #fda; - padding: 5px; - border: 3px solid red; -} - -.footnote:target { - background-color: #ffa; -} - -.line-block { - display: block; - margin-top: 1em; - margin-bottom: 1em; -} - -.line-block .line-block { - margin-top: 0; - margin-bottom: 0; - margin-left: 1.5em; -} - -.guilabel, .menuselection { - font-family: sans-serif; -} - -.accelerator { - text-decoration: underline; -} - -.classifier { - font-style: oblique; -} - -abbr, acronym { - border-bottom: dotted 1px; - cursor: help; -} - -/* -- code displays --------------------------------------------------------- */ - -pre { - overflow: auto; - overflow-y: hidden; /* fixes display issues on Chrome browsers */ -} - -span.pre { - -moz-hyphens: none; - -ms-hyphens: none; - -webkit-hyphens: none; - hyphens: none; -} - -td.linenos pre { - padding: 5px 0px; - border: 0; - background-color: transparent; - color: #aaa; -} - -table.highlighttable { - margin-left: 0.5em; -} - -table.highlighttable td { - padding: 0 0.5em 0 0.5em; -} - -div.code-block-caption { - padding: 2px 5px; - font-size: small; -} - -div.code-block-caption code { - background-color: transparent; -} - -div.code-block-caption + div > div.highlight > pre { - margin-top: 0; -} - -div.code-block-caption span.caption-number { - padding: 0.1em 0.3em; - font-style: italic; -} - -div.code-block-caption span.caption-text { -} - -div.literal-block-wrapper { - padding: 1em 1em 0; -} - -div.literal-block-wrapper div.highlight { - margin: 0; -} - -code.descname { - background-color: transparent; - font-weight: bold; - font-size: 1.2em; -} - -code.descclassname { - background-color: transparent; -} - -code.xref, a code { - background-color: transparent; - font-weight: bold; -} - -h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { - background-color: transparent; -} - -.viewcode-link { - float: right; -} - -.viewcode-back { - float: right; - font-family: sans-serif; -} - -div.viewcode-block:target { - margin: -1px -10px; - padding: 0 10px; -} - -/* -- math display ---------------------------------------------------------- */ - -img.math { - vertical-align: middle; -} - -div.body div.math p { - text-align: center; -} - -span.eqno { - float: right; -} - -span.eqno a.headerlink { - position: relative; - left: 0px; - z-index: 1; -} - -div.math:hover a.headerlink { - visibility: visible; -} - -/* -- printout stylesheet --------------------------------------------------- */ - -@media print { - div.document, - div.documentwrapper, - div.bodywrapper { - margin: 0 !important; - width: 100%; - } - - div.sphinxsidebar, - div.related, - div.footer, - #top-link { - display: none; - } -} \ No newline at end of file diff --git a/docs/_static/comment-bright.png b/docs/_static/comment-bright.png deleted file mode 100644 index 15e27ed..0000000 Binary files a/docs/_static/comment-bright.png and /dev/null differ diff --git a/docs/_static/comment-close.png b/docs/_static/comment-close.png deleted file mode 100644 index 4d91bcf..0000000 Binary files a/docs/_static/comment-close.png and /dev/null differ diff --git a/docs/_static/comment.png b/docs/_static/comment.png deleted file mode 100644 index dfbc0cb..0000000 Binary files a/docs/_static/comment.png and /dev/null differ diff --git a/docs/_static/css/badge_only.css b/docs/_static/css/badge_only.css deleted file mode 100644 index 7e17fb1..0000000 --- a/docs/_static/css/badge_only.css +++ /dev/null @@ -1,2 +0,0 @@ -.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-weight:normal;font-style:normal;src:url("../font/fontawesome_webfont.eot");src:url("../font/fontawesome_webfont.eot?#iefix") format("embedded-opentype"),url("../font/fontawesome_webfont.woff") format("woff"),url("../font/fontawesome_webfont.ttf") format("truetype"),url("../font/fontawesome_webfont.svg#FontAwesome") format("svg")}.fa:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa{display:inline-block;text-decoration:inherit}li .fa{display:inline-block}li .fa-large:before,li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-0.8em}ul.fas li .fa{width:0.8em}ul.fas li .fa-large:before,ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before{content:""}.icon-book:before{content:""}.fa-caret-down:before{content:""}.icon-caret-down:before{content:""}.fa-caret-up:before{content:""}.icon-caret-up:before{content:""}.fa-caret-left:before{content:""}.icon-caret-left:before{content:""}.fa-caret-right:before{content:""}.icon-caret-right:before{content:""}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;border-top:solid 10px #343131;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980B9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27AE60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#E74C3C;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#F1C40F;color:#000}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}img{width:100%;height:auto}} -/*# sourceMappingURL=badge_only.css.map */ diff --git a/docs/_static/css/theme.css b/docs/_static/css/theme.css deleted file mode 100644 index 7be9339..0000000 --- a/docs/_static/css/theme.css +++ /dev/null @@ -1,5 +0,0 @@ -*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}[hidden]{display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:hover,a:active{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;color:#000;text-decoration:none}mark{background:#ff0;color:#000;font-style:italic;font-weight:bold}pre,code,.rst-content tt,.rst-content code,kbd,samp{font-family:monospace,serif;_font-family:"courier new",monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:before,q:after{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}ul,ol,dl{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:0;margin:0;padding:0}label{cursor:pointer}legend{border:0;*margin-left:-7px;padding:0;white-space:normal}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*width:13px;*height:13px}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top;resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:0.2em 0;background:#ccc;color:#000;padding:0.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none !important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{html,body,section{background:none !important}*{box-shadow:none !important;text-shadow:none !important;filter:none !important;-ms-filter:none !important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,.rst-content .toctree-wrapper p.caption,h3{orphans:3;widows:3}h2,.rst-content .toctree-wrapper p.caption,h3{page-break-after:avoid}}.fa:before,.wy-menu-vertical li span.toctree-expand:before,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li.current>a span.toctree-expand:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso,.rst-content .admonition-todo,.btn,input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"],select,textarea,.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a,.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a,.wy-nav-top a{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}/*! - * Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:'FontAwesome';src:url("../fonts/fontawesome-webfont.eot?v=4.2.0");src:url("../fonts/fontawesome-webfont.eot?#iefix&v=4.2.0") format("embedded-opentype"),url("../fonts/fontawesome-webfont.woff?v=4.2.0") format("woff"),url("../fonts/fontawesome-webfont.ttf?v=4.2.0") format("truetype"),url("../fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular") format("svg");font-weight:normal;font-style:normal}.fa,.wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.rst-content p.caption .headerlink,.rst-content tt.download span:first-child,.rst-content code.download span:first-child,.icon{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:0.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:0.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:solid 0.08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.wy-menu-vertical li span.pull-left.toctree-expand,.wy-menu-vertical li.on a span.pull-left.toctree-expand,.wy-menu-vertical li.current>a span.pull-left.toctree-expand,.rst-content .pull-left.admonition-title,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content dl dt .pull-left.headerlink,.rst-content p.caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.rst-content code.download span.pull-left:first-child,.pull-left.icon{margin-right:.3em}.fa.pull-right,.wy-menu-vertical li span.pull-right.toctree-expand,.wy-menu-vertical li.on a span.pull-right.toctree-expand,.wy-menu-vertical li.current>a span.pull-right.toctree-expand,.rst-content .pull-right.admonition-title,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content dl dt .pull-right.headerlink,.rst-content p.caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.rst-content code.download span.pull-right:first-child,.pull-right.icon{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-remove:before,.fa-close:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-gear:before,.fa-cog:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-rotate-right:before,.fa-repeat:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.rst-content .admonition-title:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-warning:before,.fa-exclamation-triangle:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-gears:before,.fa-cogs:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-save:before,.fa-floppy-o:before{content:""}.fa-square:before{content:""}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.wy-dropdown .caret:before,.icon-caret-down:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-unsorted:before,.fa-sort:before{content:""}.fa-sort-down:before,.fa-sort-desc:before{content:""}.fa-sort-up:before,.fa-sort-asc:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-legal:before,.fa-gavel:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-flash:before,.fa-bolt:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-paste:before,.fa-clipboard:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-unlink:before,.fa-chain-broken:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li.current>a span.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:""}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:""}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:""}.fa-euro:before,.fa-eur:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-rupee:before,.fa-inr:before{content:""}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:""}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:""}.fa-won:before,.fa-krw:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-turkish-lira:before,.fa-try:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li span.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-institution:before,.fa-bank:before,.fa-university:before{content:""}.fa-mortar-board:before,.fa-graduation-cap:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:""}.fa-file-zip-o:before,.fa-file-archive-o:before{content:""}.fa-file-sound-o:before,.fa-file-audio-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before{content:""}.fa-ge:before,.fa-empire:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-send:before,.fa-paper-plane:before{content:""}.fa-send-o:before,.fa-paper-plane-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:""}.fa-meanpath:before{content:""}.fa,.wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.rst-content p.caption .headerlink,.rst-content tt.download span:first-child,.rst-content code.download span:first-child,.icon,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context{font-family:inherit}.fa:before,.wy-menu-vertical li span.toctree-expand:before,.wy-menu-vertical li.on a span.toctree-expand:before,.wy-menu-vertical li.current>a span.toctree-expand:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before{font-family:"FontAwesome";display:inline-block;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa,a .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li a span.toctree-expand,.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand,a .rst-content .admonition-title,.rst-content a .admonition-title,a .rst-content h1 .headerlink,.rst-content h1 a .headerlink,a .rst-content h2 .headerlink,.rst-content h2 a .headerlink,a .rst-content h3 .headerlink,.rst-content h3 a .headerlink,a .rst-content h4 .headerlink,.rst-content h4 a .headerlink,a .rst-content h5 .headerlink,.rst-content h5 a .headerlink,a .rst-content h6 .headerlink,.rst-content h6 a .headerlink,a .rst-content dl dt .headerlink,.rst-content dl dt a .headerlink,a .rst-content p.caption .headerlink,.rst-content p.caption a .headerlink,a .rst-content tt.download span:first-child,.rst-content tt.download a span:first-child,a .rst-content code.download span:first-child,.rst-content code.download a span:first-child,a .icon{display:inline-block;text-decoration:inherit}.btn .fa,.btn .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li .btn span.toctree-expand,.btn .wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.on a .btn span.toctree-expand,.btn .wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.current>a .btn span.toctree-expand,.btn .rst-content .admonition-title,.rst-content .btn .admonition-title,.btn .rst-content h1 .headerlink,.rst-content h1 .btn .headerlink,.btn .rst-content h2 .headerlink,.rst-content h2 .btn .headerlink,.btn .rst-content h3 .headerlink,.rst-content h3 .btn .headerlink,.btn .rst-content h4 .headerlink,.rst-content h4 .btn .headerlink,.btn .rst-content h5 .headerlink,.rst-content h5 .btn .headerlink,.btn .rst-content h6 .headerlink,.rst-content h6 .btn .headerlink,.btn .rst-content dl dt .headerlink,.rst-content dl dt .btn .headerlink,.btn .rst-content p.caption .headerlink,.rst-content p.caption .btn .headerlink,.btn .rst-content tt.download span:first-child,.rst-content tt.download .btn span:first-child,.btn .rst-content code.download span:first-child,.rst-content code.download .btn span:first-child,.btn .icon,.nav .fa,.nav .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li .nav span.toctree-expand,.nav .wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.on a .nav span.toctree-expand,.nav .wy-menu-vertical li.current>a span.toctree-expand,.wy-menu-vertical li.current>a .nav span.toctree-expand,.nav .rst-content .admonition-title,.rst-content .nav .admonition-title,.nav .rst-content h1 .headerlink,.rst-content h1 .nav .headerlink,.nav .rst-content h2 .headerlink,.rst-content h2 .nav .headerlink,.nav .rst-content h3 .headerlink,.rst-content h3 .nav .headerlink,.nav .rst-content h4 .headerlink,.rst-content h4 .nav .headerlink,.nav .rst-content h5 .headerlink,.rst-content h5 .nav .headerlink,.nav .rst-content h6 .headerlink,.rst-content h6 .nav .headerlink,.nav .rst-content dl dt .headerlink,.rst-content dl dt .nav .headerlink,.nav .rst-content p.caption .headerlink,.rst-content p.caption .nav .headerlink,.nav .rst-content tt.download span:first-child,.rst-content tt.download .nav span:first-child,.nav .rst-content code.download span:first-child,.rst-content code.download .nav span:first-child,.nav .icon{display:inline}.btn .fa.fa-large,.btn .wy-menu-vertical li span.fa-large.toctree-expand,.wy-menu-vertical li .btn span.fa-large.toctree-expand,.btn .rst-content .fa-large.admonition-title,.rst-content .btn .fa-large.admonition-title,.btn .rst-content h1 .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.btn .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .btn .fa-large.headerlink,.btn .rst-content p.caption .fa-large.headerlink,.rst-content p.caption .btn .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.rst-content tt.download .btn span.fa-large:first-child,.btn .rst-content code.download span.fa-large:first-child,.rst-content code.download .btn span.fa-large:first-child,.btn .fa-large.icon,.nav .fa.fa-large,.nav .wy-menu-vertical li span.fa-large.toctree-expand,.wy-menu-vertical li .nav span.fa-large.toctree-expand,.nav .rst-content .fa-large.admonition-title,.rst-content .nav .fa-large.admonition-title,.nav .rst-content h1 .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.nav .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.nav .rst-content p.caption .fa-large.headerlink,.rst-content p.caption .nav .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.nav .rst-content code.download span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.nav .fa-large.icon{line-height:0.9em}.btn .fa.fa-spin,.btn .wy-menu-vertical li span.fa-spin.toctree-expand,.wy-menu-vertical li .btn span.fa-spin.toctree-expand,.btn .rst-content .fa-spin.admonition-title,.rst-content .btn .fa-spin.admonition-title,.btn .rst-content h1 .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.btn .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .btn .fa-spin.headerlink,.btn .rst-content p.caption .fa-spin.headerlink,.rst-content p.caption .btn .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.rst-content tt.download .btn span.fa-spin:first-child,.btn .rst-content code.download span.fa-spin:first-child,.rst-content code.download .btn span.fa-spin:first-child,.btn .fa-spin.icon,.nav .fa.fa-spin,.nav .wy-menu-vertical li span.fa-spin.toctree-expand,.wy-menu-vertical li .nav span.fa-spin.toctree-expand,.nav .rst-content .fa-spin.admonition-title,.rst-content .nav .fa-spin.admonition-title,.nav .rst-content h1 .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.nav .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.nav .rst-content p.caption .fa-spin.headerlink,.rst-content p.caption .nav .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.nav .rst-content code.download span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.nav .fa-spin.icon{display:inline-block}.btn.fa:before,.wy-menu-vertical li span.btn.toctree-expand:before,.rst-content .btn.admonition-title:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content dl dt .btn.headerlink:before,.rst-content p.caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.rst-content code.download span.btn:first-child:before,.btn.icon:before{opacity:0.5;-webkit-transition:opacity 0.05s ease-in;-moz-transition:opacity 0.05s ease-in;transition:opacity 0.05s ease-in}.btn.fa:hover:before,.wy-menu-vertical li span.btn.toctree-expand:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content p.caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.rst-content code.download span.btn:first-child:hover:before,.btn.icon:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .wy-menu-vertical li span.toctree-expand:before,.wy-menu-vertical li .btn-mini span.toctree-expand:before,.btn-mini .rst-content .admonition-title:before,.rst-content .btn-mini .admonition-title:before,.btn-mini .rst-content h1 .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.btn-mini .rst-content dl dt .headerlink:before,.rst-content dl dt .btn-mini .headerlink:before,.btn-mini .rst-content p.caption .headerlink:before,.rst-content p.caption .btn-mini .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.rst-content tt.download .btn-mini span:first-child:before,.btn-mini .rst-content code.download span:first-child:before,.rst-content code.download .btn-mini span:first-child:before,.btn-mini .icon:before{font-size:14px;vertical-align:-15%}.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso,.rst-content .admonition-todo{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.wy-alert-title,.rst-content .admonition-title{color:#fff;font-weight:bold;display:block;color:#fff;background:#6ab0de;margin:-12px;padding:6px 12px;margin-bottom:12px}.wy-alert.wy-alert-danger,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.admonition-todo{background:#fdf3f2}.wy-alert.wy-alert-danger .wy-alert-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .danger .wy-alert-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .danger .admonition-title,.rst-content .error .admonition-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title{background:#f29f97}.wy-alert.wy-alert-warning,.rst-content .wy-alert-warning.note,.rst-content .attention,.rst-content .caution,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.tip,.rst-content .warning,.rst-content .wy-alert-warning.seealso,.rst-content .admonition-todo{background:#ffedcc}.wy-alert.wy-alert-warning .wy-alert-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .attention .wy-alert-title,.rst-content .caution .wy-alert-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .admonition-todo .wy-alert-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .attention .admonition-title,.rst-content .caution .admonition-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .warning .admonition-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .admonition-todo .admonition-title{background:#f0b37e}.wy-alert.wy-alert-info,.rst-content .note,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.rst-content .seealso,.rst-content .wy-alert-info.admonition-todo{background:#e7f2fa}.wy-alert.wy-alert-info .wy-alert-title,.rst-content .note .wy-alert-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.rst-content .note .admonition-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .seealso .admonition-title,.rst-content .wy-alert-info.admonition-todo .admonition-title{background:#6ab0de}.wy-alert.wy-alert-success,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.warning,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.admonition-todo{background:#dbfaf4}.wy-alert.wy-alert-success .wy-alert-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .hint .wy-alert-title,.rst-content .important .wy-alert-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .hint .admonition-title,.rst-content .important .admonition-title,.rst-content .tip .admonition-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.admonition-todo .admonition-title{background:#1abc9c}.wy-alert.wy-alert-neutral,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.admonition-todo{background:#f3f6f6}.wy-alert.wy-alert-neutral .wy-alert-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .admonition-title{color:#404040;background:#e1e4e5}.wy-alert.wy-alert-neutral a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.admonition-todo a{color:#2980B9}.wy-alert p:last-child,.rst-content .note p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.rst-content .seealso p:last-child,.rst-content .admonition-todo p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0px;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,0.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all 0.3s ease-in;-moz-transition:all 0.3s ease-in;transition:all 0.3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27AE60}.wy-tray-container li.wy-tray-item-info{background:#2980B9}.wy-tray-container li.wy-tray-item-warning{background:#E67E22}.wy-tray-container li.wy-tray-item-danger{background:#E74C3C}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width: 768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px 12px;color:#fff;border:1px solid rgba(0,0,0,0.1);background-color:#27AE60;text-decoration:none;font-weight:normal;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:0px 1px 2px -1px rgba(255,255,255,0.5) inset,0px -2px 0px 0px rgba(0,0,0,0.1) inset;outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all 0.1s linear;-moz-transition:all 0.1s linear;transition:all 0.1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:0px -1px 0px 0px rgba(0,0,0,0.05) inset,0px 2px 0px 0px rgba(0,0,0,0.1) inset;padding:8px 12px 6px 12px}.btn:visited{color:#fff}.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn-disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn-disabled:hover,.btn-disabled:focus,.btn-disabled:active{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980B9 !important}.btn-info:hover{background-color:#2e8ece !important}.btn-neutral{background-color:#f3f6f6 !important;color:#404040 !important}.btn-neutral:hover{background-color:#e5ebeb !important;color:#404040}.btn-neutral:visited{color:#404040 !important}.btn-success{background-color:#27AE60 !important}.btn-success:hover{background-color:#295 !important}.btn-danger{background-color:#E74C3C !important}.btn-danger:hover{background-color:#ea6153 !important}.btn-warning{background-color:#E67E22 !important}.btn-warning:hover{background-color:#e98b39 !important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f !important}.btn-link{background-color:transparent !important;color:#2980B9;box-shadow:none;border-color:transparent !important}.btn-link:hover{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:active{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:visited{color:#9B59B6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:before,.wy-btn-group:after{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:solid 1px #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,0.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980B9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:solid 1px #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type="search"]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980B9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned input,.wy-form-aligned textarea,.wy-form-aligned select,.wy-form-aligned .wy-help-inline,.wy-form-aligned label{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{border:0;margin:0;padding:0}legend{display:block;width:100%;border:0;padding:0;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label{display:block;margin:0 0 0.3125em 0;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;*zoom:1;max-width:68em;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#E74C3C}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full input[type="text"],.wy-control-group .wy-form-full input[type="password"],.wy-control-group .wy-form-full input[type="email"],.wy-control-group .wy-form-full input[type="url"],.wy-control-group .wy-form-full input[type="date"],.wy-control-group .wy-form-full input[type="month"],.wy-control-group .wy-form-full input[type="time"],.wy-control-group .wy-form-full input[type="datetime"],.wy-control-group .wy-form-full input[type="datetime-local"],.wy-control-group .wy-form-full input[type="week"],.wy-control-group .wy-form-full input[type="number"],.wy-control-group .wy-form-full input[type="search"],.wy-control-group .wy-form-full input[type="tel"],.wy-control-group .wy-form-full input[type="color"],.wy-control-group .wy-form-halves input[type="text"],.wy-control-group .wy-form-halves input[type="password"],.wy-control-group .wy-form-halves input[type="email"],.wy-control-group .wy-form-halves input[type="url"],.wy-control-group .wy-form-halves input[type="date"],.wy-control-group .wy-form-halves input[type="month"],.wy-control-group .wy-form-halves input[type="time"],.wy-control-group .wy-form-halves input[type="datetime"],.wy-control-group .wy-form-halves input[type="datetime-local"],.wy-control-group .wy-form-halves input[type="week"],.wy-control-group .wy-form-halves input[type="number"],.wy-control-group .wy-form-halves input[type="search"],.wy-control-group .wy-form-halves input[type="tel"],.wy-control-group .wy-form-halves input[type="color"],.wy-control-group .wy-form-thirds input[type="text"],.wy-control-group .wy-form-thirds input[type="password"],.wy-control-group .wy-form-thirds input[type="email"],.wy-control-group .wy-form-thirds input[type="url"],.wy-control-group .wy-form-thirds input[type="date"],.wy-control-group .wy-form-thirds input[type="month"],.wy-control-group .wy-form-thirds input[type="time"],.wy-control-group .wy-form-thirds input[type="datetime"],.wy-control-group .wy-form-thirds input[type="datetime-local"],.wy-control-group .wy-form-thirds input[type="week"],.wy-control-group .wy-form-thirds input[type="number"],.wy-control-group .wy-form-thirds input[type="search"],.wy-control-group .wy-form-thirds input[type="tel"],.wy-control-group .wy-form-thirds input[type="color"]{width:100%}.wy-control-group .wy-form-full{float:left;display:block;margin-right:2.35765%;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child{margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n+1){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child{margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control{margin:6px 0 0 0;font-size:90%}.wy-control-no-input{display:inline-block;margin:6px 0 0 0;font-size:90%}.wy-control-group.fluid-input input[type="text"],.wy-control-group.fluid-input input[type="password"],.wy-control-group.fluid-input input[type="email"],.wy-control-group.fluid-input input[type="url"],.wy-control-group.fluid-input input[type="date"],.wy-control-group.fluid-input input[type="month"],.wy-control-group.fluid-input input[type="time"],.wy-control-group.fluid-input input[type="datetime"],.wy-control-group.fluid-input input[type="datetime-local"],.wy-control-group.fluid-input input[type="week"],.wy-control-group.fluid-input input[type="number"],.wy-control-group.fluid-input input[type="search"],.wy-control-group.fluid-input input[type="tel"],.wy-control-group.fluid-input input[type="color"]{width:100%}.wy-form-message-inline{display:inline-block;padding-left:0.3em;color:#666;vertical-align:middle;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:0.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;*overflow:visible}input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border 0.3s linear;-moz-transition:border 0.3s linear;transition:border 0.3s linear}input[type="datetime-local"]{padding:0.34375em 0.625em}input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0;margin-right:0.3125em;*height:13px;*width:13px}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}input[type="text"]:focus,input[type="password"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus{outline:0;outline:thin dotted \9;border-color:#333}input.no-focus:focus{border-color:#ccc !important}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:1px auto #129FEA}input[type="text"][disabled],input[type="password"][disabled],input[type="email"][disabled],input[type="url"][disabled],input[type="date"][disabled],input[type="month"][disabled],input[type="time"][disabled],input[type="datetime"][disabled],input[type="datetime-local"][disabled],input[type="week"][disabled],input[type="number"][disabled],input[type="search"][disabled],input[type="tel"][disabled],input[type="color"][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#E74C3C;border:1px solid #E74C3C}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#E74C3C}input[type="file"]:focus:invalid:focus,input[type="radio"]:focus:invalid:focus,input[type="checkbox"]:focus:invalid:focus{outline-color:#E74C3C}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif}select,textarea{padding:0.5em 0.625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border 0.3s linear;-moz-transition:border 0.3s linear;transition:border 0.3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type="radio"][disabled],input[type="checkbox"][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:solid 1px #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{width:36px;height:12px;margin:12px 0;position:relative;border-radius:4px;background:#ccc;cursor:pointer;-webkit-transition:all 0.2s ease-in-out;-moz-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out}.wy-switch:before{position:absolute;content:"";display:block;width:18px;height:18px;border-radius:4px;background:#999;left:-3px;top:-3px;-webkit-transition:all 0.2s ease-in-out;-moz-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out}.wy-switch:after{content:"false";position:absolute;left:48px;display:block;font-size:12px;color:#ccc}.wy-switch.active{background:#1e8449}.wy-switch.active:before{left:24px;background:#27AE60}.wy-switch.active:after{content:"true"}.wy-switch.disabled,.wy-switch.active.disabled{cursor:not-allowed}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#E74C3C}.wy-control-group.wy-control-group-error input[type="text"],.wy-control-group.wy-control-group-error input[type="password"],.wy-control-group.wy-control-group-error input[type="email"],.wy-control-group.wy-control-group-error input[type="url"],.wy-control-group.wy-control-group-error input[type="date"],.wy-control-group.wy-control-group-error input[type="month"],.wy-control-group.wy-control-group-error input[type="time"],.wy-control-group.wy-control-group-error input[type="datetime"],.wy-control-group.wy-control-group-error input[type="datetime-local"],.wy-control-group.wy-control-group-error input[type="week"],.wy-control-group.wy-control-group-error input[type="number"],.wy-control-group.wy-control-group-error input[type="search"],.wy-control-group.wy-control-group-error input[type="tel"],.wy-control-group.wy-control-group-error input[type="color"]{border:solid 1px #E74C3C}.wy-control-group.wy-control-group-error textarea{border:solid 1px #E74C3C}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:0.5em 0.625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27AE60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#E74C3C}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#E67E22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980B9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width: 480px){.wy-form button[type="submit"]{margin:0.7em 0 0}.wy-form input[type="text"],.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:0.3em;display:block}.wy-form label{margin-bottom:0.3em;display:block}.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:0.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0 0}.wy-form .wy-help-inline,.wy-form-message-inline,.wy-form-message{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width: 768px){.tablet-hide{display:none}}@media screen and (max-width: 480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.wy-table,.rst-content table.docutils,.rst-content table.field-list{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.wy-table caption,.rst-content table.docutils caption,.rst-content table.field-list caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td,.wy-table th,.rst-content table.docutils th,.rst-content table.field-list th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.wy-table td:first-child,.rst-content table.docutils td:first-child,.rst-content table.field-list td:first-child,.wy-table th:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list th:first-child{border-left-width:0}.wy-table thead,.rst-content table.docutils thead,.rst-content table.field-list thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.wy-table thead th,.rst-content table.docutils thead th,.rst-content table.field-list thead th{font-weight:bold;border-bottom:solid 2px #e1e4e5}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td{background-color:transparent;vertical-align:middle}.wy-table td p,.rst-content table.docutils td p,.rst-content table.field-list td p{line-height:18px}.wy-table td p:last-child,.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child{margin-bottom:0}.wy-table .wy-table-cell-min,.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min{width:1%;padding-right:0}.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:gray;font-size:90%}.wy-table-tertiary{color:gray;font-size:80%}.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td,.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td{background-color:#f3f6f6}.wy-table-backed{background-color:#f3f6f6}.wy-table-bordered-all,.rst-content table.docutils{border:1px solid #e1e4e5}.wy-table-bordered-all td,.rst-content table.docutils td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.wy-table-bordered-all tbody>tr:last-child td,.rst-content table.docutils tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px 0;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0 !important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980B9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9B59B6}html{height:100%;overflow-x:hidden}body{font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;font-weight:normal;color:#404040;min-height:100%;overflow-x:hidden;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#E67E22 !important}a.wy-text-warning:hover{color:#eb9950 !important}.wy-text-info{color:#2980B9 !important}a.wy-text-info:hover{color:#409ad5 !important}.wy-text-success{color:#27AE60 !important}a.wy-text-success:hover{color:#36d278 !important}.wy-text-danger{color:#E74C3C !important}a.wy-text-danger:hover{color:#ed7669 !important}.wy-text-neutral{color:#404040 !important}a.wy-text-neutral:hover{color:#595959 !important}h1,h2,.rst-content .toctree-wrapper p.caption,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif}p{line-height:24px;margin:0;font-size:16px;margin-bottom:24px}h1{font-size:175%}h2,.rst-content .toctree-wrapper p.caption{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}code,.rst-content tt,.rst-content code{white-space:nowrap;max-width:100%;background:#fff;border:solid 1px #e1e4e5;font-size:75%;padding:0 5px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;color:#E74C3C;overflow-x:auto}code.code-large,.rst-content tt.code-large{font-size:90%}.wy-plain-list-disc,.rst-content .section ul,.rst-content .toctree-wrapper ul,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.wy-plain-list-disc li,.rst-content .section ul li,.rst-content .toctree-wrapper ul li,article ul li{list-style:disc;margin-left:24px}.wy-plain-list-disc li p:last-child,.rst-content .section ul li p:last-child,.rst-content .toctree-wrapper ul li p:last-child,article ul li p:last-child{margin-bottom:0}.wy-plain-list-disc li ul,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li ul,article ul li ul{margin-bottom:0}.wy-plain-list-disc li li,.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,article ul li li{list-style:circle}.wy-plain-list-disc li li li,.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,article ul li li li{list-style:square}.wy-plain-list-disc li ol li,.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,article ul li ol li{list-style:decimal}.wy-plain-list-decimal,.rst-content .section ol,.rst-content ol.arabic,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.wy-plain-list-decimal li,.rst-content .section ol li,.rst-content ol.arabic li,article ol li{list-style:decimal;margin-left:24px}.wy-plain-list-decimal li p:last-child,.rst-content .section ol li p:last-child,.rst-content ol.arabic li p:last-child,article ol li p:last-child{margin-bottom:0}.wy-plain-list-decimal li ul,.rst-content .section ol li ul,.rst-content ol.arabic li ul,article ol li ul{margin-bottom:0}.wy-plain-list-decimal li ul li,.rst-content .section ol li ul li,.rst-content ol.arabic li ul li,article ol li ul li{list-style:disc}.codeblock-example{border:1px solid #e1e4e5;border-bottom:none;padding:24px;padding-top:48px;font-weight:500;background:#fff;position:relative}.codeblock-example:after{content:"Example";position:absolute;top:0px;left:0px;background:#9B59B6;color:#fff;padding:6px 12px}.codeblock-example.prettyprint-example-only{border:1px solid #e1e4e5;margin-bottom:24px}.codeblock,pre.literal-block,.rst-content .literal-block,.rst-content pre.literal-block,div[class^='highlight']{border:1px solid #e1e4e5;padding:0px;overflow-x:auto;background:#fff;margin:1px 0 24px 0}.codeblock div[class^='highlight'],pre.literal-block div[class^='highlight'],.rst-content .literal-block div[class^='highlight'],div[class^='highlight'] div[class^='highlight']{border:none;background:none;margin:0}div[class^='highlight'] td.code{width:100%}.linenodiv pre{border-right:solid 1px #e6e9ea;margin:0;padding:12px 12px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;font-size:12px;line-height:1.5;color:#d9d9d9}div[class^='highlight'] pre{white-space:pre;margin:0;padding:12px 12px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;font-size:12px;line-height:1.5;display:block;overflow:auto;color:#404040}@media print{.codeblock,pre.literal-block,.rst-content .literal-block,.rst-content pre.literal-block,div[class^='highlight'],div[class^='highlight'] pre{white-space:pre-wrap}}.hll{background-color:#ffc;margin:0 -12px;padding:0 12px;display:block}.c{color:#998;font-style:italic}.err{color:#a61717;background-color:#e3d2d2}.k{font-weight:bold}.o{font-weight:bold}.cm{color:#998;font-style:italic}.cp{color:#999;font-weight:bold}.c1{color:#998;font-style:italic}.cs{color:#999;font-weight:bold;font-style:italic}.gd{color:#000;background-color:#fdd}.gd .x{color:#000;background-color:#faa}.ge{font-style:italic}.gr{color:#a00}.gh{color:#999}.gi{color:#000;background-color:#dfd}.gi .x{color:#000;background-color:#afa}.go{color:#888}.gp{color:#555}.gs{font-weight:bold}.gu{color:purple;font-weight:bold}.gt{color:#a00}.kc{font-weight:bold}.kd{font-weight:bold}.kn{font-weight:bold}.kp{font-weight:bold}.kr{font-weight:bold}.kt{color:#458;font-weight:bold}.m{color:#099}.s{color:#d14}.n{color:#333}.na{color:teal}.nb{color:#0086b3}.nc{color:#458;font-weight:bold}.no{color:teal}.ni{color:purple}.ne{color:#900;font-weight:bold}.nf{color:#900;font-weight:bold}.nn{color:#555}.nt{color:navy}.nv{color:teal}.ow{font-weight:bold}.w{color:#bbb}.mf{color:#099}.mh{color:#099}.mi{color:#099}.mo{color:#099}.sb{color:#d14}.sc{color:#d14}.sd{color:#d14}.s2{color:#d14}.se{color:#d14}.sh{color:#d14}.si{color:#d14}.sx{color:#d14}.sr{color:#009926}.s1{color:#d14}.ss{color:#990073}.bp{color:#999}.vc{color:teal}.vg{color:teal}.vi{color:teal}.il{color:#099}.gc{color:#999;background-color:#EAF2F5}.wy-breadcrumbs li{display:inline-block}.wy-breadcrumbs li.wy-breadcrumbs-aside{float:right}.wy-breadcrumbs li a{display:inline-block;padding:5px}.wy-breadcrumbs li a:first-child{padding-left:0}.wy-breadcrumbs li code,.wy-breadcrumbs li .rst-content tt,.rst-content .wy-breadcrumbs li tt{padding:5px;border:none;background:none}.wy-breadcrumbs li code.literal,.wy-breadcrumbs li .rst-content tt.literal,.rst-content .wy-breadcrumbs li tt.literal{color:#404040}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width: 480px){.wy-breadcrumbs-extra{display:none}.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:before,.wy-menu-horiz:after{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz ul,.wy-menu-horiz li{display:inline-block}.wy-menu-horiz li:hover{background:rgba(255,255,255,0.1)}.wy-menu-horiz li.divide-left{border-left:solid 1px #404040}.wy-menu-horiz li.divide-right{border-right:solid 1px #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{height:32px;display:inline-block;line-height:32px;padding:0 1.618em;margin-bottom:0;display:block;font-weight:bold;text-transform:uppercase;font-size:80%;color:#555;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:solid 1px #404040}.wy-menu-vertical li.divide-bottom{border-bottom:solid 1px #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:gray;border-right:solid 1px #c9c9c9;padding:0.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.wy-menu-vertical li code,.wy-menu-vertical li .rst-content tt,.rst-content .wy-menu-vertical li tt{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li span.toctree-expand{display:block;float:left;margin-left:-1.2em;font-size:0.8em;line-height:1.6em;color:#4d4d4d}.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a{color:#404040;padding:0.4045em 1.618em;font-weight:bold;position:relative;background:#fcfcfc;border:none;border-bottom:solid 1px #c9c9c9;border-top:solid 1px #c9c9c9;padding-left:1.618em -4px}.wy-menu-vertical li.on a:hover,.wy-menu-vertical li.current>a:hover{background:#fcfcfc}.wy-menu-vertical li.on a:hover span.toctree-expand,.wy-menu-vertical li.current>a:hover span.toctree-expand{color:gray}.wy-menu-vertical li.on a span.toctree-expand,.wy-menu-vertical li.current>a span.toctree-expand{display:block;font-size:0.8em;line-height:1.6em;color:#333}.wy-menu-vertical li.toctree-l1.current li.toctree-l2>ul,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>ul{display:none}.wy-menu-vertical li.toctree-l1.current li.toctree-l2.current>ul,.wy-menu-vertical li.toctree-l2.current li.toctree-l3.current>ul{display:block}.wy-menu-vertical li.toctree-l2.current>a{background:#c9c9c9;padding:0.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{display:block;background:#c9c9c9;padding:0.4045em 4.045em}.wy-menu-vertical li.toctree-l2 a:hover span.toctree-expand{color:gray}.wy-menu-vertical li.toctree-l2 span.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3{font-size:0.9em}.wy-menu-vertical li.toctree-l3.current>a{background:#bdbdbd;padding:0.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{display:block;background:#bdbdbd;padding:0.4045em 5.663em;border-top:none;border-bottom:none}.wy-menu-vertical li.toctree-l3 a:hover span.toctree-expand{color:gray}.wy-menu-vertical li.toctree-l3 span.toctree-expand{color:#969696}.wy-menu-vertical li.toctree-l4{font-size:0.9em}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical .local-toc li ul{display:block}.wy-menu-vertical li ul li a{margin-bottom:0;color:#b3b3b3;font-weight:normal}.wy-menu-vertical a{display:inline-block;line-height:18px;padding:0.4045em 1.618em;display:block;position:relative;font-size:90%;color:#b3b3b3}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover span.toctree-expand{color:#b3b3b3}.wy-menu-vertical a:active{background-color:#2980B9;cursor:pointer;color:#fff}.wy-menu-vertical a:active span.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:0.809em;margin-bottom:0.809em;z-index:200;background-color:#2980B9;text-align:center;padding:0.809em;display:block;color:#fcfcfc;margin-bottom:0.809em}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto 0.809em auto;height:45px;width:45px;background-color:#2980B9;padding:5px;border-radius:100%}.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a{color:#fcfcfc;font-size:100%;font-weight:bold;display:inline-block;padding:4px 6px;margin-bottom:0.809em}.wy-side-nav-search>a:hover,.wy-side-nav-search .wy-dropdown>a:hover{background:rgba(255,255,255,0.1)}.wy-side-nav-search>a img.logo,.wy-side-nav-search .wy-dropdown>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search>a.icon img.logo,.wy-side-nav-search .wy-dropdown>a.icon img.logo{margin-top:0.85em}.wy-side-nav-search>div.version{margin-top:-0.4045em;margin-bottom:0.809em;font-weight:normal;color:rgba(255,255,255,0.3)}.wy-nav .wy-menu-vertical header{color:#2980B9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980B9;color:#fff}[data-menu-wrap]{-webkit-transition:all 0.2s ease-in;-moz-transition:all 0.2s ease-in;transition:all 0.2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:left repeat-y #fcfcfc;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoxOERBMTRGRDBFMUUxMUUzODUwMkJCOThDMEVFNURFMCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoxOERBMTRGRTBFMUUxMUUzODUwMkJCOThDMEVFNURFMCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjE4REExNEZCMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjE4REExNEZDMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+EwrlwAAAAA5JREFUeNpiMDU0BAgwAAE2AJgB9BnaAAAAAElFTkSuQmCC);background-size:300px 1px}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980B9;color:#fff;padding:0.4045em 0.809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:before,.wy-nav-top:after{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:bold}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980B9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,0.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:#999}footer p{margin-bottom:12px}footer span.commit code,footer span.commit .rst-content tt,.rst-content footer span.commit tt{padding:0px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;font-size:1em;background:none;border:none;color:#999}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:before,.rst-footer-buttons:after{display:table;content:""}.rst-footer-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:solid 1px #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:solid 1px #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:gray;font-size:90%}@media screen and (max-width: 768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-side-scroll{width:auto}.wy-side-nav-search{width:auto}.wy-menu.wy-menu-vertical{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width: 1400px){.wy-nav-content-wrap{background:rgba(0,0,0,0.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,footer,.wy-nav-side{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;border-top:solid 10px #343131;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980B9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27AE60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .wy-menu-vertical li span.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version span.toctree-expand,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content p.caption .headerlink,.rst-content p.caption .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .icon{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#E74C3C;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#F1C40F;color:#000}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}img{width:100%;height:auto}}.rst-content img{max-width:100%;height:auto !important}.rst-content div.figure{margin-bottom:24px}.rst-content div.figure p.caption{font-style:italic}.rst-content div.figure.align-center{text-align:center}.rst-content .section>img,.rst-content .section>a>img{margin-bottom:24px}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content .note .last,.rst-content .attention .last,.rst-content .caution .last,.rst-content .danger .last,.rst-content .error .last,.rst-content .hint .last,.rst-content .important .last,.rst-content .tip .last,.rst-content .warning .last,.rst-content .seealso .last,.rst-content .admonition-todo .last{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,0.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent !important;border-color:rgba(0,0,0,0.1) !important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha li{list-style:upper-alpha}.rst-content .section ol p,.rst-content .section ul p{margin-bottom:12px}.rst-content .line-block{margin-left:24px}.rst-content .topic-title{font-weight:bold;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0px 0px 24px 24px}.rst-content .align-left{float:left;margin:0px 24px 24px 0px}.rst-content .align-center{margin:auto;display:block}.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content .toctree-wrapper p.caption .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.rst-content p.caption .headerlink{display:none;visibility:hidden;font-size:14px}.rst-content h1 .headerlink:after,.rst-content h2 .headerlink:after,.rst-content .toctree-wrapper p.caption .headerlink:after,.rst-content h3 .headerlink:after,.rst-content h4 .headerlink:after,.rst-content h5 .headerlink:after,.rst-content h6 .headerlink:after,.rst-content dl dt .headerlink:after,.rst-content p.caption .headerlink:after{visibility:visible;content:"";font-family:FontAwesome;display:inline-block}.rst-content h1:hover .headerlink,.rst-content h2:hover .headerlink,.rst-content .toctree-wrapper p.caption:hover .headerlink,.rst-content h3:hover .headerlink,.rst-content h4:hover .headerlink,.rst-content h5:hover .headerlink,.rst-content h6:hover .headerlink,.rst-content dl dt:hover .headerlink,.rst-content p.caption:hover .headerlink{display:inline-block}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:solid 1px #e1e4e5}.rst-content .sidebar p,.rst-content .sidebar ul,.rst-content .sidebar dl{font-size:90%}.rst-content .sidebar .last{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif;font-weight:bold;background:#e1e4e5;padding:6px 12px;margin:-24px;margin-bottom:24px;font-size:100%}.rst-content .highlighted{background:#F1C40F;display:inline-block;font-weight:bold;padding:0 6px}.rst-content .footnote-reference,.rst-content .citation-reference{vertical-align:super;font-size:90%}.rst-content table.docutils.citation,.rst-content table.docutils.footnote{background:none;border:none;color:#999}.rst-content table.docutils.citation td,.rst-content table.docutils.citation tr,.rst-content table.docutils.footnote td,.rst-content table.docutils.footnote tr{border:none;background-color:transparent !important;white-space:normal}.rst-content table.docutils.citation td.label,.rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}.rst-content table.docutils.citation tt,.rst-content table.docutils.citation code,.rst-content table.docutils.footnote tt,.rst-content table.docutils.footnote code{color:#555}.rst-content table.field-list{border:none}.rst-content table.field-list td{border:none;padding-top:5px}.rst-content table.field-list td>strong{display:inline-block;margin-top:3px}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left;padding-left:0}.rst-content tt,.rst-content tt,.rst-content code{color:#000;padding:2px 5px}.rst-content tt big,.rst-content tt em,.rst-content tt big,.rst-content code big,.rst-content tt em,.rst-content code em{font-size:100% !important;line-height:normal}.rst-content tt.literal,.rst-content tt.literal,.rst-content code.literal{color:#E74C3C}.rst-content tt.xref,a .rst-content tt,.rst-content tt.xref,.rst-content code.xref,a .rst-content tt,a .rst-content code{font-weight:bold;color:#404040}.rst-content a tt,.rst-content a tt,.rst-content a code{color:#2980B9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:bold}.rst-content dl p,.rst-content dl table,.rst-content dl ul,.rst-content dl ol{margin-bottom:12px !important}.rst-content dl dd{margin:0 0 12px 24px}.rst-content dl:not(.docutils){margin-bottom:24px}.rst-content dl:not(.docutils) dt{display:inline-block;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980B9;border-top:solid 3px #6ab0de;padding:6px;position:relative}.rst-content dl:not(.docutils) dt:before{color:#6ab0de}.rst-content dl:not(.docutils) dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dl dt{margin-bottom:6px;border:none;border-left:solid 3px #ccc;background:#f0f0f0;color:#555}.rst-content dl:not(.docutils) dl dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dt:first-child{margin-top:0}.rst-content dl:not(.docutils) tt,.rst-content dl:not(.docutils) tt,.rst-content dl:not(.docutils) code{font-weight:bold}.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) tt.descclassname,.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) code.descname,.rst-content dl:not(.docutils) tt.descclassname,.rst-content dl:not(.docutils) code.descclassname{background-color:transparent;border:none;padding:0;font-size:100% !important}.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) code.descname{font-weight:bold}.rst-content dl:not(.docutils) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:bold}.rst-content dl:not(.docutils) .property{display:inline-block;padding-right:8px}.rst-content .viewcode-link,.rst-content .viewcode-back{display:inline-block;color:#27AE60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:bold}.rst-content tt.download,.rst-content code.download{background:inherit;padding:inherit;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content tt.download span:first-child:before,.rst-content code.download span:first-child:before{margin-right:4px}@media screen and (max-width: 480px){.rst-content .sidebar{width:100%}}span[id*='MathJax-Span']{color:#404040}.math{text-align:center}@font-face{font-family:"Inconsolata";font-style:normal;font-weight:400;src:local("Inconsolata"),local("Inconsolata-Regular"),url(../fonts/Inconsolata-Regular.ttf) format("truetype")}@font-face{font-family:"Inconsolata";font-style:normal;font-weight:700;src:local("Inconsolata Bold"),local("Inconsolata-Bold"),url(../fonts/Inconsolata-Bold.ttf) format("truetype")}@font-face{font-family:"Lato";font-style:normal;font-weight:400;src:local("Lato Regular"),local("Lato-Regular"),url(../fonts/Lato-Regular.ttf) format("truetype")}@font-face{font-family:"Lato";font-style:normal;font-weight:700;src:local("Lato Bold"),local("Lato-Bold"),url(../fonts/Lato-Bold.ttf) format("truetype")}@font-face{font-family:"Roboto Slab";font-style:normal;font-weight:400;src:local("Roboto Slab Regular"),local("RobotoSlab-Regular"),url(../fonts/RobotoSlab-Regular.ttf) format("truetype")}@font-face{font-family:"Roboto Slab";font-style:normal;font-weight:700;src:local("Roboto Slab Bold"),local("RobotoSlab-Bold"),url(../fonts/RobotoSlab-Bold.ttf) format("truetype")} -/*# sourceMappingURL=theme.css.map */ diff --git a/docs/_static/doctools.js b/docs/_static/doctools.js deleted file mode 100644 index 5654977..0000000 --- a/docs/_static/doctools.js +++ /dev/null @@ -1,287 +0,0 @@ -/* - * doctools.js - * ~~~~~~~~~~~ - * - * Sphinx JavaScript utilities for all documentation. - * - * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/** - * select a different prefix for underscore - */ -$u = _.noConflict(); - -/** - * make the code below compatible with browsers without - * an installed firebug like debugger -if (!window.console || !console.firebug) { - var names = ["log", "debug", "info", "warn", "error", "assert", "dir", - "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", - "profile", "profileEnd"]; - window.console = {}; - for (var i = 0; i < names.length; ++i) - window.console[names[i]] = function() {}; -} - */ - -/** - * small helper function to urldecode strings - */ -jQuery.urldecode = function(x) { - return decodeURIComponent(x).replace(/\+/g, ' '); -}; - -/** - * small helper function to urlencode strings - */ -jQuery.urlencode = encodeURIComponent; - -/** - * This function returns the parsed url parameters of the - * current request. Multiple values per key are supported, - * it will always return arrays of strings for the value parts. - */ -jQuery.getQueryParameters = function(s) { - if (typeof s == 'undefined') - s = document.location.search; - var parts = s.substr(s.indexOf('?') + 1).split('&'); - var result = {}; - for (var i = 0; i < parts.length; i++) { - var tmp = parts[i].split('=', 2); - var key = jQuery.urldecode(tmp[0]); - var value = jQuery.urldecode(tmp[1]); - if (key in result) - result[key].push(value); - else - result[key] = [value]; - } - return result; -}; - -/** - * highlight a given string on a jquery object by wrapping it in - * span elements with the given class name. - */ -jQuery.fn.highlightText = function(text, className) { - function highlight(node) { - if (node.nodeType == 3) { - var val = node.nodeValue; - var pos = val.toLowerCase().indexOf(text); - if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { - var span = document.createElement("span"); - span.className = className; - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - node.parentNode.insertBefore(span, node.parentNode.insertBefore( - document.createTextNode(val.substr(pos + text.length)), - node.nextSibling)); - node.nodeValue = val.substr(0, pos); - } - } - else if (!jQuery(node).is("button, select, textarea")) { - jQuery.each(node.childNodes, function() { - highlight(this); - }); - } - } - return this.each(function() { - highlight(this); - }); -}; - -/* - * backward compatibility for jQuery.browser - * This will be supported until firefox bug is fixed. - */ -if (!jQuery.browser) { - jQuery.uaMatch = function(ua) { - ua = ua.toLowerCase(); - - var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || - /(webkit)[ \/]([\w.]+)/.exec(ua) || - /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || - /(msie) ([\w.]+)/.exec(ua) || - ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || - []; - - return { - browser: match[ 1 ] || "", - version: match[ 2 ] || "0" - }; - }; - jQuery.browser = {}; - jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; -} - -/** - * Small JavaScript module for the documentation. - */ -var Documentation = { - - init : function() { - this.fixFirefoxAnchorBug(); - this.highlightSearchWords(); - this.initIndexTable(); - - }, - - /** - * i18n support - */ - TRANSLATIONS : {}, - PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, - LOCALE : 'unknown', - - // gettext and ngettext don't access this so that the functions - // can safely bound to a different name (_ = Documentation.gettext) - gettext : function(string) { - var translated = Documentation.TRANSLATIONS[string]; - if (typeof translated == 'undefined') - return string; - return (typeof translated == 'string') ? translated : translated[0]; - }, - - ngettext : function(singular, plural, n) { - var translated = Documentation.TRANSLATIONS[singular]; - if (typeof translated == 'undefined') - return (n == 1) ? singular : plural; - return translated[Documentation.PLURALEXPR(n)]; - }, - - addTranslations : function(catalog) { - for (var key in catalog.messages) - this.TRANSLATIONS[key] = catalog.messages[key]; - this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); - this.LOCALE = catalog.locale; - }, - - /** - * add context elements like header anchor links - */ - addContextElements : function() { - $('div[id] > :header:first').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this headline')). - appendTo(this); - }); - $('dt[id]').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this definition')). - appendTo(this); - }); - }, - - /** - * workaround a firefox stupidity - * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 - */ - fixFirefoxAnchorBug : function() { - if (document.location.hash) - window.setTimeout(function() { - document.location.href += ''; - }, 10); - }, - - /** - * highlight the search words provided in the url in the text - */ - highlightSearchWords : function() { - var params = $.getQueryParameters(); - var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; - if (terms.length) { - var body = $('div.body'); - if (!body.length) { - body = $('body'); - } - window.setTimeout(function() { - $.each(terms, function() { - body.highlightText(this.toLowerCase(), 'highlighted'); - }); - }, 10); - $('') - .appendTo($('#searchbox')); - } - }, - - /** - * init the domain index toggle buttons - */ - initIndexTable : function() { - var togglers = $('img.toggler').click(function() { - var src = $(this).attr('src'); - var idnum = $(this).attr('id').substr(7); - $('tr.cg-' + idnum).toggle(); - if (src.substr(-9) == 'minus.png') - $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); - else - $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); - }).css('display', ''); - if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { - togglers.click(); - } - }, - - /** - * helper function to hide the search marks again - */ - hideSearchWords : function() { - $('#searchbox .highlight-link').fadeOut(300); - $('span.highlighted').removeClass('highlighted'); - }, - - /** - * make the url absolute - */ - makeURL : function(relativeURL) { - return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; - }, - - /** - * get the current relative url - */ - getCurrentURL : function() { - var path = document.location.pathname; - var parts = path.split(/\//); - $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { - if (this == '..') - parts.pop(); - }); - var url = parts.join('/'); - return path.substring(url.lastIndexOf('/') + 1, path.length - 1); - }, - - initOnKeyListeners: function() { - $(document).keyup(function(event) { - var activeElementType = document.activeElement.tagName; - // don't navigate when in search box or textarea - if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') { - switch (event.keyCode) { - case 37: // left - var prevHref = $('link[rel="prev"]').prop('href'); - if (prevHref) { - window.location.href = prevHref; - return false; - } - case 39: // right - var nextHref = $('link[rel="next"]').prop('href'); - if (nextHref) { - window.location.href = nextHref; - return false; - } - } - } - }); - } -}; - -// quick alias for translations -_ = Documentation.gettext; - -$(document).ready(function() { - Documentation.init(); -}); \ No newline at end of file diff --git a/docs/_static/documentation_options.js b/docs/_static/documentation_options.js deleted file mode 100644 index d0b0ed2..0000000 --- a/docs/_static/documentation_options.js +++ /dev/null @@ -1,9 +0,0 @@ -var DOCUMENTATION_OPTIONS = { - URL_ROOT: '', - VERSION: '', - LANGUAGE: 'None', - COLLAPSE_INDEX: false, - FILE_SUFFIX: '.html', - HAS_SOURCE: true, - SOURCELINK_SUFFIX: '.txt' -}; \ No newline at end of file diff --git a/docs/_static/down-pressed.png b/docs/_static/down-pressed.png deleted file mode 100644 index 5756c8c..0000000 Binary files a/docs/_static/down-pressed.png and /dev/null differ diff --git a/docs/_static/down.png b/docs/_static/down.png deleted file mode 100644 index 1b3bdad..0000000 Binary files a/docs/_static/down.png and /dev/null differ diff --git a/docs/_static/file.png b/docs/_static/file.png deleted file mode 100644 index a858a41..0000000 Binary files a/docs/_static/file.png and /dev/null differ diff --git a/docs/_static/fonts/Inconsolata-Bold.ttf b/docs/_static/fonts/Inconsolata-Bold.ttf deleted file mode 100644 index 58c9fef..0000000 Binary files a/docs/_static/fonts/Inconsolata-Bold.ttf and /dev/null differ diff --git a/docs/_static/fonts/Inconsolata-Regular.ttf b/docs/_static/fonts/Inconsolata-Regular.ttf deleted file mode 100644 index a87ffba..0000000 Binary files a/docs/_static/fonts/Inconsolata-Regular.ttf and /dev/null differ diff --git a/docs/_static/fonts/Lato-Bold.ttf b/docs/_static/fonts/Lato-Bold.ttf deleted file mode 100644 index 7434369..0000000 Binary files a/docs/_static/fonts/Lato-Bold.ttf and /dev/null differ diff --git a/docs/_static/fonts/Lato-Regular.ttf b/docs/_static/fonts/Lato-Regular.ttf deleted file mode 100644 index 04ea8ef..0000000 Binary files a/docs/_static/fonts/Lato-Regular.ttf and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-bold.eot b/docs/_static/fonts/Lato/lato-bold.eot deleted file mode 100644 index 3361183..0000000 Binary files a/docs/_static/fonts/Lato/lato-bold.eot and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-bold.ttf b/docs/_static/fonts/Lato/lato-bold.ttf deleted file mode 100644 index 29f691d..0000000 Binary files a/docs/_static/fonts/Lato/lato-bold.ttf and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-bold.woff b/docs/_static/fonts/Lato/lato-bold.woff deleted file mode 100644 index c6dff51..0000000 Binary files a/docs/_static/fonts/Lato/lato-bold.woff and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-bold.woff2 b/docs/_static/fonts/Lato/lato-bold.woff2 deleted file mode 100644 index bb19504..0000000 Binary files a/docs/_static/fonts/Lato/lato-bold.woff2 and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-bolditalic.eot b/docs/_static/fonts/Lato/lato-bolditalic.eot deleted file mode 100644 index 3d41549..0000000 Binary files a/docs/_static/fonts/Lato/lato-bolditalic.eot and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-bolditalic.ttf b/docs/_static/fonts/Lato/lato-bolditalic.ttf deleted file mode 100644 index f402040..0000000 Binary files a/docs/_static/fonts/Lato/lato-bolditalic.ttf and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-bolditalic.woff b/docs/_static/fonts/Lato/lato-bolditalic.woff deleted file mode 100644 index 88ad05b..0000000 Binary files a/docs/_static/fonts/Lato/lato-bolditalic.woff and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-bolditalic.woff2 b/docs/_static/fonts/Lato/lato-bolditalic.woff2 deleted file mode 100644 index c4e3d80..0000000 Binary files a/docs/_static/fonts/Lato/lato-bolditalic.woff2 and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-italic.eot b/docs/_static/fonts/Lato/lato-italic.eot deleted file mode 100644 index 3f82642..0000000 Binary files a/docs/_static/fonts/Lato/lato-italic.eot and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-italic.ttf b/docs/_static/fonts/Lato/lato-italic.ttf deleted file mode 100644 index b4bfc9b..0000000 Binary files a/docs/_static/fonts/Lato/lato-italic.ttf and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-italic.woff b/docs/_static/fonts/Lato/lato-italic.woff deleted file mode 100644 index 76114bc..0000000 Binary files a/docs/_static/fonts/Lato/lato-italic.woff and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-italic.woff2 b/docs/_static/fonts/Lato/lato-italic.woff2 deleted file mode 100644 index 3404f37..0000000 Binary files a/docs/_static/fonts/Lato/lato-italic.woff2 and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-regular.eot b/docs/_static/fonts/Lato/lato-regular.eot deleted file mode 100644 index 11e3f2a..0000000 Binary files a/docs/_static/fonts/Lato/lato-regular.eot and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-regular.ttf b/docs/_static/fonts/Lato/lato-regular.ttf deleted file mode 100644 index 74decd9..0000000 Binary files a/docs/_static/fonts/Lato/lato-regular.ttf and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-regular.woff b/docs/_static/fonts/Lato/lato-regular.woff deleted file mode 100644 index ae1307f..0000000 Binary files a/docs/_static/fonts/Lato/lato-regular.woff and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-regular.woff2 b/docs/_static/fonts/Lato/lato-regular.woff2 deleted file mode 100644 index 3bf9843..0000000 Binary files a/docs/_static/fonts/Lato/lato-regular.woff2 and /dev/null differ diff --git a/docs/_static/fonts/RobotoSlab-Bold.ttf b/docs/_static/fonts/RobotoSlab-Bold.ttf deleted file mode 100644 index df5d1df..0000000 Binary files a/docs/_static/fonts/RobotoSlab-Bold.ttf and /dev/null differ diff --git a/docs/_static/fonts/RobotoSlab-Regular.ttf b/docs/_static/fonts/RobotoSlab-Regular.ttf deleted file mode 100644 index eb52a79..0000000 Binary files a/docs/_static/fonts/RobotoSlab-Regular.ttf and /dev/null differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot deleted file mode 100644 index 79dc8ef..0000000 Binary files a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot and /dev/null differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf deleted file mode 100644 index df5d1df..0000000 Binary files a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf and /dev/null differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff deleted file mode 100644 index 6cb6000..0000000 Binary files a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff and /dev/null differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 deleted file mode 100644 index 7059e23..0000000 Binary files a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 and /dev/null differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot deleted file mode 100644 index 2f7ca78..0000000 Binary files a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot and /dev/null differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf deleted file mode 100644 index eb52a79..0000000 Binary files a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf and /dev/null differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff deleted file mode 100644 index f815f63..0000000 Binary files a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff and /dev/null differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 deleted file mode 100644 index f2c76e5..0000000 Binary files a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 and /dev/null differ diff --git a/docs/_static/fonts/fontawesome-webfont.eot b/docs/_static/fonts/fontawesome-webfont.eot deleted file mode 100644 index 84677bc..0000000 Binary files a/docs/_static/fonts/fontawesome-webfont.eot and /dev/null differ diff --git a/docs/_static/fonts/fontawesome-webfont.svg b/docs/_static/fonts/fontawesome-webfont.svg deleted file mode 100644 index d907b25..0000000 --- a/docs/_static/fonts/fontawesome-webfont.svg +++ /dev/null @@ -1,520 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_static/fonts/fontawesome-webfont.ttf b/docs/_static/fonts/fontawesome-webfont.ttf deleted file mode 100644 index 96a3639..0000000 Binary files a/docs/_static/fonts/fontawesome-webfont.ttf and /dev/null differ diff --git a/docs/_static/fonts/fontawesome-webfont.woff b/docs/_static/fonts/fontawesome-webfont.woff deleted file mode 100644 index 628b6a5..0000000 Binary files a/docs/_static/fonts/fontawesome-webfont.woff and /dev/null differ diff --git a/docs/_static/fonts/fontawesome-webfont.woff2 b/docs/_static/fonts/fontawesome-webfont.woff2 deleted file mode 100644 index 4d13fc6..0000000 Binary files a/docs/_static/fonts/fontawesome-webfont.woff2 and /dev/null differ diff --git a/docs/_static/jquery-3.1.0.js b/docs/_static/jquery-3.1.0.js deleted file mode 100644 index f2fc274..0000000 --- a/docs/_static/jquery-3.1.0.js +++ /dev/null @@ -1,10074 +0,0 @@ -/*eslint-disable no-unused-vars*/ -/*! - * jQuery JavaScript Library v3.1.0 - * https://jquery.com/ - * - * Includes Sizzle.js - * https://sizzlejs.com/ - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license - * https://jquery.org/license - * - * Date: 2016-07-07T21:44Z - */ -( function( global, factory ) { - - "use strict"; - - if ( typeof module === "object" && typeof module.exports === "object" ) { - - // For CommonJS and CommonJS-like environments where a proper `window` - // is present, execute the factory and get jQuery. - // For environments that do not have a `window` with a `document` - // (such as Node.js), expose a factory as module.exports. - // This accentuates the need for the creation of a real `window`. - // e.g. var jQuery = require("jquery")(window); - // See ticket #14549 for more info. - module.exports = global.document ? - factory( global, true ) : - function( w ) { - if ( !w.document ) { - throw new Error( "jQuery requires a window with a document" ); - } - return factory( w ); - }; - } else { - factory( global ); - } - -// Pass this if window is not defined yet -} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { - -// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 -// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode -// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common -// enough that all such attempts are guarded in a try block. -"use strict"; - -var arr = []; - -var document = window.document; - -var getProto = Object.getPrototypeOf; - -var slice = arr.slice; - -var concat = arr.concat; - -var push = arr.push; - -var indexOf = arr.indexOf; - -var class2type = {}; - -var toString = class2type.toString; - -var hasOwn = class2type.hasOwnProperty; - -var fnToString = hasOwn.toString; - -var ObjectFunctionString = fnToString.call( Object ); - -var support = {}; - - - - function DOMEval( code, doc ) { - doc = doc || document; - - var script = doc.createElement( "script" ); - - script.text = code; - doc.head.appendChild( script ).parentNode.removeChild( script ); - } -/* global Symbol */ -// Defining this global in .eslintrc would create a danger of using the global -// unguarded in another place, it seems safer to define global only for this module - - - -var - version = "3.1.0", - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - - // The jQuery object is actually just the init constructor 'enhanced' - // Need init if jQuery is called (just allow error to be thrown if not included) - return new jQuery.fn.init( selector, context ); - }, - - // Support: Android <=4.0 only - // Make sure we trim BOM and NBSP - rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, - - // Matches dashed string for camelizing - rmsPrefix = /^-ms-/, - rdashAlpha = /-([a-z])/g, - - // Used by jQuery.camelCase as callback to replace() - fcamelCase = function( all, letter ) { - return letter.toUpperCase(); - }; - -jQuery.fn = jQuery.prototype = { - - // The current version of jQuery being used - jquery: version, - - constructor: jQuery, - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function() { - return slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - return num != null ? - - // Return just the one element from the set - ( num < 0 ? this[ num + this.length ] : this[ num ] ) : - - // Return all the elements in a clean array - slice.call( this ); - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - each: function( callback ) { - return jQuery.each( this, callback ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map( this, function( elem, i ) { - return callback.call( elem, i, elem ); - } ) ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); - }, - - end: function() { - return this.prevObject || this.constructor(); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: arr.sort, - splice: arr.splice -}; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[ 0 ] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - - // Skip the boolean and the target - target = arguments[ i ] || {}; - i++; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { - target = {}; - } - - // Extend jQuery itself if only one argument is passed - if ( i === length ) { - target = this; - i--; - } - - for ( ; i < length; i++ ) { - - // Only deal with non-null/undefined values - if ( ( options = arguments[ i ] ) != null ) { - - // Extend the base object - for ( name in options ) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject( copy ) || - ( copyIsArray = jQuery.isArray( copy ) ) ) ) { - - if ( copyIsArray ) { - copyIsArray = false; - clone = src && jQuery.isArray( src ) ? src : []; - - } else { - clone = src && jQuery.isPlainObject( src ) ? src : {}; - } - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend( { - - // Unique for each copy of jQuery on the page - expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), - - // Assume jQuery is ready without the ready module - isReady: true, - - error: function( msg ) { - throw new Error( msg ); - }, - - noop: function() {}, - - isFunction: function( obj ) { - return jQuery.type( obj ) === "function"; - }, - - isArray: Array.isArray, - - isWindow: function( obj ) { - return obj != null && obj === obj.window; - }, - - isNumeric: function( obj ) { - - // As of jQuery 3.0, isNumeric is limited to - // strings and numbers (primitives or objects) - // that can be coerced to finite numbers (gh-2662) - var type = jQuery.type( obj ); - return ( type === "number" || type === "string" ) && - - // parseFloat NaNs numeric-cast false positives ("") - // ...but misinterprets leading-number strings, particularly hex literals ("0x...") - // subtraction forces infinities to NaN - !isNaN( obj - parseFloat( obj ) ); - }, - - isPlainObject: function( obj ) { - var proto, Ctor; - - // Detect obvious negatives - // Use toString instead of jQuery.type to catch host objects - if ( !obj || toString.call( obj ) !== "[object Object]" ) { - return false; - } - - proto = getProto( obj ); - - // Objects with no prototype (e.g., `Object.create( null )`) are plain - if ( !proto ) { - return true; - } - - // Objects with prototype are plain iff they were constructed by a global Object function - Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; - return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; - }, - - isEmptyObject: function( obj ) { - - /* eslint-disable no-unused-vars */ - // See https://github.com/eslint/eslint/issues/6125 - var name; - - for ( name in obj ) { - return false; - } - return true; - }, - - type: function( obj ) { - if ( obj == null ) { - return obj + ""; - } - - // Support: Android <=2.3 only (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; - }, - - // Evaluates a script in a global context - globalEval: function( code ) { - DOMEval( code ); - }, - - // Convert dashed to camelCase; used by the css and data modules - // Support: IE <=9 - 11, Edge 12 - 13 - // Microsoft forgot to hump their vendor prefix (#9572) - camelCase: function( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - }, - - each: function( obj, callback ) { - var length, i = 0; - - if ( isArrayLike( obj ) ) { - length = obj.length; - for ( ; i < length; i++ ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } else { - for ( i in obj ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } - - return obj; - }, - - // Support: Android <=4.0 only - trim: function( text ) { - return text == null ? - "" : - ( text + "" ).replace( rtrim, "" ); - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArrayLike( Object( arr ) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - return arr == null ? -1 : indexOf.call( arr, elem, i ); - }, - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - merge: function( first, second ) { - var len = +second.length, - j = 0, - i = first.length; - - for ( ; j < len; j++ ) { - first[ i++ ] = second[ j ]; - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, invert ) { - var callbackInverse, - matches = [], - i = 0, - length = elems.length, - callbackExpect = !invert; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - callbackInverse = !callback( elems[ i ], i ); - if ( callbackInverse !== callbackExpect ) { - matches.push( elems[ i ] ); - } - } - - return matches; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var length, value, - i = 0, - ret = []; - - // Go through the array, translating each of the items to their new values - if ( isArrayLike( elems ) ) { - length = elems.length; - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - } - - // Flatten any nested arrays - return concat.apply( [], ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // Bind a function to a context, optionally partially applying any - // arguments. - proxy: function( fn, context ) { - var tmp, args, proxy; - - if ( typeof context === "string" ) { - tmp = fn[ context ]; - context = fn; - fn = tmp; - } - - // Quick check to determine if target is callable, in the spec - // this throws a TypeError, but we will just return undefined. - if ( !jQuery.isFunction( fn ) ) { - return undefined; - } - - // Simulated bind - args = slice.call( arguments, 2 ); - proxy = function() { - return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); - }; - - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || jQuery.guid++; - - return proxy; - }, - - now: Date.now, - - // jQuery.support is not used in Core but other projects attach their - // properties to it so it needs to exist. - support: support -} ); - -if ( typeof Symbol === "function" ) { - jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; -} - -// Populate the class2type map -jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), -function( i, name ) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -} ); - -function isArrayLike( obj ) { - - // Support: real iOS 8.2 only (not reproducible in simulator) - // `in` check used to prevent JIT error (gh-2145) - // hasOwn isn't used here due to false negatives - // regarding Nodelist length in IE - var length = !!obj && "length" in obj && obj.length, - type = jQuery.type( obj ); - - if ( type === "function" || jQuery.isWindow( obj ) ) { - return false; - } - - return type === "array" || length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj; -} -var Sizzle = -/*! - * Sizzle CSS Selector Engine v2.3.0 - * https://sizzlejs.com/ - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2016-01-04 - */ -(function( window ) { - -var i, - support, - Expr, - getText, - isXML, - tokenize, - compile, - select, - outermostContext, - sortInput, - hasDuplicate, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - } - return 0; - }, - - // Instance methods - hasOwn = ({}).hasOwnProperty, - arr = [], - pop = arr.pop, - push_native = arr.push, - push = arr.push, - slice = arr.slice, - // Use a stripped-down indexOf as it's faster than native - // https://jsperf.com/thor-indexof-vs-for/5 - indexOf = function( list, elem ) { - var i = 0, - len = list.length; - for ( ; i < len; i++ ) { - if ( list[i] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - - // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", - - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + - // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + - // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + - "*\\]", - - pseudos = ":(" + identifier + ")(?:\\((" + - // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + - // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + - // 3. anything else (capture 2) - ".*" + - ")\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), - - rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), - - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + identifier + ")" ), - "CLASS": new RegExp( "^\\.(" + identifier + ")" ), - "TAG": new RegExp( "^(" + identifier + "|[*])" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + - "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + - "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + - whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rnative = /^[^{]+\{\s*\[native \w/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rsibling = /[+~]/, - - // CSS escapes - // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), - funescape = function( _, escaped, escapedWhitespace ) { - var high = "0x" + escaped - 0x10000; - // NaN means non-codepoint - // Support: Firefox<24 - // Workaround erroneous numeric interpretation of +"0x" - return high !== high || escapedWhitespace ? - escaped : - high < 0 ? - // BMP codepoint - String.fromCharCode( high + 0x10000 ) : - // Supplemental Plane codepoint (surrogate pair) - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // CSS string/identifier serialization - // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g, - fcssescape = function( ch, asCodePoint ) { - if ( asCodePoint ) { - - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if ( ch === "\0" ) { - return "\uFFFD"; - } - - // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; - } - - // Other potentially-special ASCII characters get backslash-escaped - return "\\" + ch; - }, - - // Used for iframes - // See setDocument() - // Removing the function wrapper causes a "Permission Denied" - // error in IE - unloadHandler = function() { - setDocument(); - }, - - disabledAncestor = addCombinator( - function( elem ) { - return elem.disabled === true; - }, - { dir: "parentNode", next: "legend" } - ); - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - (arr = slice.call( preferredDoc.childNodes )), - preferredDoc.childNodes - ); - // Support: Android<4.0 - // Detect silently failing push.apply - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { - push_native.apply( target, slice.call(els) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - // Can't trust NodeList.length - while ( (target[j++] = els[i++]) ) {} - target.length = j - 1; - } - }; -} - -function Sizzle( selector, context, results, seed ) { - var m, i, elem, nid, match, groups, newSelector, - newContext = context && context.ownerDocument, - - // nodeType defaults to 9, since context defaults to document - nodeType = context ? context.nodeType : 9; - - results = results || []; - - // Return early from calls with invalid selector or context - if ( typeof selector !== "string" || !selector || - nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { - - return results; - } - - // Try to shortcut find operations (as opposed to filters) in HTML documents - if ( !seed ) { - - if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { - setDocument( context ); - } - context = context || document; - - if ( documentIsHTML ) { - - // If the selector is sufficiently simple, try using a "get*By*" DOM method - // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { - - // ID selector - if ( (m = match[1]) ) { - - // Document context - if ( nodeType === 9 ) { - if ( (elem = context.getElementById( m )) ) { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - - // Element context - } else { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( newContext && (elem = newContext.getElementById( m )) && - contains( context, elem ) && - elem.id === m ) { - - results.push( elem ); - return results; - } - } - - // Type selector - } else if ( match[2] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Class selector - } else if ( (m = match[3]) && support.getElementsByClassName && - context.getElementsByClassName ) { - - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // Take advantage of querySelectorAll - if ( support.qsa && - !compilerCache[ selector + " " ] && - (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { - - if ( nodeType !== 1 ) { - newContext = context; - newSelector = selector; - - // qSA looks outside Element context, which is not what we want - // Thanks to Andrew Dupont for this workaround technique - // Support: IE <=8 - // Exclude object elements - } else if ( context.nodeName.toLowerCase() !== "object" ) { - - // Capture the context ID, setting it first if necessary - if ( (nid = context.getAttribute( "id" )) ) { - nid = nid.replace( rcssescape, fcssescape ); - } else { - context.setAttribute( "id", (nid = expando) ); - } - - // Prefix every selector in the list - groups = tokenize( selector ); - i = groups.length; - while ( i-- ) { - groups[i] = "#" + nid + " " + toSelector( groups[i] ); - } - newSelector = groups.join( "," ); - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; - } - - if ( newSelector ) { - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Create key-value caches of limited size - * @returns {function(string, object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var keys = []; - - function cache( key, value ) { - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key + " " ) > Expr.cacheLength ) { - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return (cache[ key + " " ] = value); - } - return cache; -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created element and returns a boolean result - */ -function assert( fn ) { - var el = document.createElement("fieldset"); - - try { - return !!fn( el ); - } catch (e) { - return false; - } finally { - // Remove from its parent by default - if ( el.parentNode ) { - el.parentNode.removeChild( el ); - } - // release memory in IE - el = null; - } -} - -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split("|"), - i = arr.length; - - while ( i-- ) { - Expr.attrHandle[ arr[i] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - a.sourceIndex - b.sourceIndex; - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( (cur = cur.nextSibling) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for :enabled/:disabled - * @param {Boolean} disabled true for :disabled; false for :enabled - */ -function createDisabledPseudo( disabled ) { - // Known :disabled false positives: - // IE: *[disabled]:not(button, input, select, textarea, optgroup, option, menuitem, fieldset) - // not IE: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable - return function( elem ) { - - // Check form elements and option elements for explicit disabling - return "label" in elem && elem.disabled === disabled || - "form" in elem && elem.disabled === disabled || - - // Check non-disabled form elements for fieldset[disabled] ancestors - "form" in elem && elem.disabled === false && ( - // Support: IE6-11+ - // Ancestry is covered for us - elem.isDisabled === disabled || - - // Otherwise, assume any non-