From 2200cc9ebdd560fc4dd6c637e8d551ba164dea81 Mon Sep 17 00:00:00 2001 From: Marti Municoy Date: Thu, 15 Oct 2020 15:35:27 +0200 Subject: [PATCH 1/3] Allow multiple atom constraints --- offpele/tests/test_rotamers.py | 91 ++++++++++++++++- offpele/topology/molecule.py | 41 ++++---- offpele/topology/rotamer.py | 182 ++++++++++++++++++++++++++------- 3 files changed, 257 insertions(+), 57 deletions(-) diff --git a/offpele/tests/test_rotamers.py b/offpele/tests/test_rotamers.py index 7a7c6b42..24604a4d 100644 --- a/offpele/tests/test_rotamers.py +++ b/offpele/tests/test_rotamers.py @@ -160,7 +160,7 @@ def test_rotamer_core_constraint(self): ligand_path = get_data_file_path(LIGAND_PATH) # Test atom index constraint - molecule = Molecule(ligand_path, core_constraint=19, + molecule = Molecule(ligand_path, core_constraints=[19, ], exclude_terminal_rotamers=False) rotamers_per_branch = molecule.rotamers @@ -186,7 +186,7 @@ def test_rotamer_core_constraint(self): "Invalid rotamer library" # Test PDB atom name constraint - molecule = Molecule(ligand_path, core_constraint=' C18', + molecule = Molecule(ligand_path, core_constraints=[' C18', ], exclude_terminal_rotamers=False) rotamers_per_branch = molecule.rotamers @@ -212,7 +212,7 @@ def test_rotamer_core_constraint(self): "Invalid rotamer library" # Test core constraint with terminal exclusion - molecule = Molecule(ligand_path, core_constraint=' C18', + molecule = Molecule(ligand_path, core_constraints=[' C18', ], exclude_terminal_rotamers=True) rotamers_per_branch = molecule.rotamers @@ -237,7 +237,7 @@ def test_rotamer_core_constraint(self): "Invalid rotamer library" # Test core constraint with a central core - molecule = Molecule(ligand_path, core_constraint=' C9 ', + molecule = Molecule(ligand_path, core_constraints=[' C9 ', ], exclude_terminal_rotamers=True) rotamers_per_branch = molecule.rotamers @@ -296,3 +296,86 @@ def test_rotamer_core_constraint(self): and len(where_1) == len(EXPECTED_INDICES_2) and len(where_2) == len(EXPECTED_INDICES_1)), "Unexpected " + \ "number of rotamers" + + # Test core constraint with a multiple central core + molecule = Molecule(ligand_path, + core_constraints=[' C8 ', ' C9 ', ' C10'], + exclude_terminal_rotamers=True) + + rotamers_per_branch = molecule.rotamers + + assert len(rotamers_per_branch) == 2, "Found an invalid number " + \ + "of branches: {}".format(len(rotamers_per_branch)) + + atom_list_1 = list() + atom_list_2 = list() + rotamers = rotamers_per_branch[0] + for rotamer in rotamers: + atom_list_1.append(set([rotamer.index1, rotamer.index2])) + + rotamers = rotamers_per_branch[1] + for rotamer in rotamers: + atom_list_2.append(set([rotamer.index1, rotamer.index2])) + + EXPECTED_INDICES_1 = [set([8, 9]), set([7, 8]), set([6, 7]), + set([5, 6]), set([2, 5]), set([0, 2]), + set([0, 1])] + + EXPECTED_INDICES_2 = [set([12, 11]), set([12, 13]), set([13, 14]), + set([14, 15]), set([15, 16]), set([16, 17]), + set([17, 18])] + + where_1 = list() + for atom_pair in atom_list_1: + if atom_pair in EXPECTED_INDICES_1: + where_1.append(1) + elif atom_pair in EXPECTED_INDICES_2: + where_1.append(2) + else: + where_1.append(0) + + where_2 = list() + for atom_pair in atom_list_2: + if atom_pair in EXPECTED_INDICES_1: + where_2.append(1) + elif atom_pair in EXPECTED_INDICES_2: + where_2.append(2) + else: + where_2.append(0) + + assert (all(i == 1 for i in where_1) + and all(i == 2 for i in where_2)) or \ + (all(i == 2 for i in where_1) + and all(i == 1 for i in where_2)), "Invalid rotamer library " + \ + "{}, {}".format(where_1, where_2) + + assert (all(i == 1 for i in where_1) + and all(i == 2 for i in where_2) + and len(where_1) == len(EXPECTED_INDICES_1) + and len(where_2) == len(EXPECTED_INDICES_2)) or \ + (all(i == 2 for i in where_1) + and all(i == 1 for i in where_2) + and len(where_1) == len(EXPECTED_INDICES_2) + and len(where_2) == len(EXPECTED_INDICES_1)), "Unexpected " + \ + "number of rotamers" + + def test_rotamer_core_constraint_adjacency(self): + """ + It tests the adjacency check up that is performed prior building + the rotamer library builder with core constraints. + """ + + LIGAND_PATH = 'ligands/OLC.pdb' + ligand_path = get_data_file_path(LIGAND_PATH) + + # Test adjacent core constraint selection + _ = Molecule(ligand_path, + core_constraints=[' C8 ', ' C9 ', ' C10']) + + # Test non adjacent core constraint selection + with pytest.raises(ValueError) as e: + _ = Molecule(ligand_path, + core_constraints=[' C1 ', ' C9 ', ' C10']) + + assert str(e.value) == 'All atoms in atom constraints must be ' \ + + 'adjacent and atom C1 is not' diff --git a/offpele/topology/molecule.py b/offpele/topology/molecule.py index 8c2b0499..6dcfc99c 100644 --- a/offpele/topology/molecule.py +++ b/offpele/topology/molecule.py @@ -449,7 +449,7 @@ class Molecule(object): def __init__(self, path=None, smiles=None, rotamer_resolution=30, exclude_terminal_rotamers=True, name='', tag='UNK', - connectivity_template=None, core_constraint=None): + connectivity_template=None, core_constraints=[]): """ It initializes a Molecule object through a PDB file or a SMILES tag. @@ -473,11 +473,11 @@ def __init__(self, path=None, smiles=None, rotamer_resolution=30, connectivity_template : an rdkit.Chem.rdchem.Mol object A molecule represented with RDKit to use when assigning the connectivity of this Molecule object - core_constraint : int or str - It defines the atom to constrain in the core, thus, the core - will be forced to contain it. It can be an integer that - specifies the atom index or a string that specifies the atom - name. Default is None, which deactivates this option + core_constraints : list[int or str] + It defines the list of atoms to constrain in the core, thus, + the core will be forced to contain them. Atoms can be specified + through integers that match the atom index or strings that + match with the atom PDB name Examples -------- @@ -524,7 +524,7 @@ def __init__(self, path=None, smiles=None, rotamer_resolution=30, >>> molecule = Molecule(smiles='CCCC', name='butane', tag='BUT', exclude_terminal_rotamers=False, - core_constraint=0) + core_constraints=[0, ]) >>> rotamer_library = RotamerLibrary(mol) >>> rotamer_library.to_file('butz') @@ -535,7 +535,7 @@ def __init__(self, path=None, smiles=None, rotamer_resolution=30, self._rotamer_resolution = rotamer_resolution self._exclude_terminal_rotamers = exclude_terminal_rotamers self._connectivity_template = connectivity_template - self._core_constraint = core_constraint + self._core_constraints = core_constraints if isinstance(path, str): from pathlib import Path @@ -655,11 +655,16 @@ def _build_rotamers(self): if self.off_molecule and self.rdkit_molecule: logger.info(' - Generating rotamer library') - if self.core_constraint is not None: + if len(self.core_constraints) != 0: self._graph = MolecularGraphWithConstrainedCore( - self, self.core_constraint) - logger.info(' - Core forced to contain atom ' - + '{}'.format(self._graph.constraint_name.strip())) + self, self.core_constraints) + if len(self.core_constraints) == 1: + logger.info(' - Core forced to contain atom: ' + + self._graph.constraint_names[0]) + else: + logger.info(' - Core forced to contain atoms: ' + + ', '.join(atom_name.strip() for atom_name + in self._graph.constraint_names)) else: self._graph = MolecularGraph(self) logger.info(' - Core set to the center of the molecule') @@ -1351,17 +1356,17 @@ def connectivity_template(self): return self._connectivity_template @property - def core_constraint(self): + def core_constraints(self): """ - The index or the PDB name of the atom to constraint to the core - when building the rotamers. + The list of indices or PDB names of the atoms to constraint to + the core when building the rotamers. Returns ------- - constraint_index : int or str - The index or PDB name of the atom to constrain + core_constraints : list[int or str] + The list of indices or PDB names of the atoms to constrain """ - return self._core_constraint + return self._core_constraints @property def off_molecule(self): diff --git a/offpele/topology/rotamer.py b/offpele/topology/rotamer.py index e27bb980..adbecbd4 100644 --- a/offpele/topology/rotamer.py +++ b/offpele/topology/rotamer.py @@ -32,6 +32,13 @@ def __init__(self, index1, index2, resolution=30): self._index2 = index2 self._resolution = resolution + def __eq__(self, other): + """Define equality operator""" + return (self.index1 == other.index1 + and self.index2 == other.index2) \ + or (self.index1 == other.index2 + and self.index2 == other.index1) + @property def index1(self): """ @@ -101,7 +108,7 @@ def __init__(self, molecule): >>> molecule = Molecule(smiles='CCCC', name='butane', tag='BUT', exclude_terminal_rotamers=False, - core_constraint=0) + core_constraints=[0, ]) >>> rotamer_library = RotamerLibrary(mol) >>> rotamer_library.to_file('butz') @@ -145,6 +152,76 @@ def molecule(self): """ return self._molecule + def _ipython_display_(self): + """ + It displays a 2D molecular representation with bonds highlighted + according to this rotamer library object. + + Returns + ------- + representation_2D : a IPython display object + It is displayable RDKit molecule with an embeded 2D + representation + """ + COLORS = [(82 / 255, 215 / 255, 255 / 255), (255 / 255, 154 / 255, 71 / 255), + (161 / 255, 255 / 255, 102 / 255), (255 / 255, 173 / 255, 209 / 255), + (154 / 255, 92 / 255, 255 / 255), (66 / 255, 255 / 255, 167 / 255), + (251 / 255, 255 / 255, 17 / 255)] + + from rdkit import Chem + from rdkit.Chem.Draw import rdMolDraw2D + + # Get 2D molecular representation + rdkit_toolkit = RDKitToolkitWrapper() + representation = rdkit_toolkit.get_2D_representation(self.molecule) + + # Get rotamer branches from molecule + rotamer_branches = self.molecule.rotamers + + bond_indexes = list() + bond_color_dict = dict() + for bond in representation.GetBonds(): + rotamer = Rotamer(bond.GetBeginAtom().GetIdx(), + bond.GetEndAtom().GetIdx()) + + for color_index, group in enumerate(rotamer_branches): + if rotamer in group: + bond_indexes.append(bond.GetIdx()) + try: + bond_color_dict[bond.GetIdx()] = COLORS[color_index] + except IndexError: + bond_color_dict[bond.GetIdx()] = (99 / 255, + 122 / 255, + 126 / 255) + break + + atom_indexes = list() + radii_dict = dict() + atom_color_dict = dict() + + for atom in representation.GetAtoms(): + atom_index = atom.GetIdx() + if atom_index in self.molecule._graph.core_nodes: + atom_indexes.append(atom.GetIdx()) + radii_dict[atom.GetIdx()] = 0.6 + atom_color_dict[atom.GetIdx()] = (255 / 255, 243 / 255, 133 / 255) + + draw = rdMolDraw2D.MolDraw2DSVG(500, 500) + draw.SetLineWidth(4) + rdMolDraw2D.PrepareAndDrawMolecule(draw, representation, + highlightAtoms=atom_indexes, + highlightAtomRadii=radii_dict, + highlightAtomColors=atom_color_dict, + highlightBonds=bond_indexes, + highlightBondColors=bond_color_dict) + draw.FinishDrawing() + + from IPython.display import SVG, display + + image = SVG(draw.GetDrawingText()) + + return display(image) + class MolecularGraph(nx.Graph): """ @@ -630,7 +707,7 @@ class MolecularGraphWithConstrainedCore(MolecularGraph): It represents the structure of a Molecule as a networkx.Graph. """ - def __init__(self, molecule, atom_constraint): + def __init__(self, molecule, atom_constraints): """ It initializes a MolecularGraph object. @@ -638,37 +715,46 @@ def __init__(self, molecule, atom_constraint): ---------- molecule : An offpele.topology.Molecule A Molecule object to be written as an Impact file - atom_constraint : int or str - It defines the atom to constrain in the core, thus, the core - will be forced to contain it. It can be an integer that - specifies the atom index or a string that specifies the atom - name + atom_constraint : list[int or str] + It defines the list of atoms to constrain in the core, thus, + the core will be forced to contain them. Atoms can be specified + through integers that match the atom index or strings that + match with the atom PDB name Raises ------ ValueError + If the supplied array of atom constraints is empty If the PDB atom name in atom_constraint does not match with any atom in the molecule TypeError If the atom_constraint is of invalid type """ - if isinstance(atom_constraint, int): - self._constraint_index = atom_constraint - elif isinstance(atom_constraint, str): - atom_names = molecule.get_pdb_atom_names() - for index, name in enumerate(atom_names): - if name == atom_constraint: - self._constraint_index = index - break + if len(atom_constraints) == 0: + raise ValueError('Supplied empty array of atom constraints') + + self._constraint_indices = list() + + for atom_constraint in atom_constraints: + if isinstance(atom_constraint, int): + self._constraint_indices.append(atom_constraint) + elif isinstance(atom_constraint, str): + atom_names = molecule.get_pdb_atom_names() + for index, name in enumerate(atom_names): + if name == atom_constraint: + self._constraint_indices.append(index) + break + else: + raise ValueError('Supplied PDB atom name ' + + '\'{}\''.format(atom_constraint) + + 'is missing in molecule') else: - raise ValueError('Supplied PDB atom name ' - + '\'{}\''.format(atom_constraint) - + 'is missing in molecule') - else: - raise TypeError('Invalid type for the atom_constraint') + raise TypeError('Invalid type for the atom_constraint') super().__init__(molecule) + self._safety_check() + def _build_core_nodes(self): """ It builds the list of core nodes @@ -677,8 +763,8 @@ def _build_core_nodes(self): from networkx.algorithms.shortest_paths.generic import \ shortest_path_length - # Force core to contain constrained atom - core_nodes = [self.constraint_index, ] + # Force core to contain constrained atoms + core_nodes = [index for index in self.constraint_indices] # Calculate graph distances according to weight values weighted_distances = dict(shortest_path_length(self, weight="weight")) @@ -686,33 +772,59 @@ def _build_core_nodes(self): # Add also all atoms at 0 distance with respect to constrained # atom into the core for node in self.nodes: - d = weighted_distances[self.constraint_index][node] - if d == 0 and node not in core_nodes: - core_nodes.append(node) + for constraint_index in self.constraint_indices: + d = weighted_distances[constraint_index][node] + if d == 0 and node not in core_nodes: + core_nodes.append(node) self._core_nodes = core_nodes + def _safety_check(self): + """Perform a safety check on the atom constraints.""" + if len(self.constraint_indices) < 2: + return + + safe_indices = set() + for i, cidx1 in enumerate(self.constraint_indices): + if cidx1 in safe_indices: + continue + for cidx2 in self.constraint_indices: + if cidx2 in self.neighbors(cidx1): + safe_indices.add(cidx1) + safe_indices.add(cidx2) + break + else: + raise ValueError('All atoms in atom constraints must be ' + + 'adjacent and atom ' + + self.constraint_names[i].strip() + + ' is not') + @property - def constraint_index(self): + def constraint_indices(self): """ - The index of the atom to constraint to the core. + The indices of atoms to constraint to the core. Returns ------- - constraint_index : int - The atom index + constraint_indices : list[int] + List of atom indices """ - return self._constraint_index + return self._constraint_indices @property - def constraint_name(self): + def constraint_names(self): """ - The name of the atom to constraint to the core. + The names of atoms to constraint to the core. Returns ------- - constraint_index : str - The atom name + constraint_names : list[str] + List of atom names """ atom_names = self.molecule.get_pdb_atom_names() - return atom_names[self.constraint_index] + + constraint_names = list() + for index in self.constraint_indices: + constraint_names.append(atom_names[index]) + + return constraint_names From 66c21746264852f2e4da2829317de52080c1a560 Mon Sep 17 00:00:00 2001 From: Marti Municoy Date: Thu, 15 Oct 2020 15:36:03 +0200 Subject: [PATCH 2/3] Add side chain perturbation example --- .../sidechain_perturbation_template.ipynb | 437 ++++++++++++++++++ 1 file changed, 437 insertions(+) create mode 100644 examples/rotamers/sidechain_perturbation_template.ipynb diff --git a/examples/rotamers/sidechain_perturbation_template.ipynb b/examples/rotamers/sidechain_perturbation_template.ipynb new file mode 100644 index 00000000..dd621ea9 --- /dev/null +++ b/examples/rotamers/sidechain_perturbation_template.ipynb @@ -0,0 +1,437 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Generate template for PELE's side chain perturbation\n", + "This example shows how to generate a template with `offpele` for `PELE`'s `side chain perturbation` workflow." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from offpele.topology import Molecule\n", + "from offpele.topology import RotamerLibrary\n", + "from offpele.utils import get_data_file_path" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exclude terminal rotamers" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " - Initializing molecule from PDB\n", + " - Loading molecule from RDKit\n", + " - Assigning stereochemistry from 3D coordinates\n", + " - Setting molecule name to 'modified_sidechain'\n", + " - Representing molecule with the Open Force Field Toolkit\n", + "Warning: Unable to load toolkit 'OpenEye Toolkit'. The Open Force Field Toolkit does not require the OpenEye Toolkits, and can use RDKit/AmberTools instead. However, if you have a valid license for the OpenEye Toolkits, consider installing them for faster performance and additional file format support: https://docs.eyesopen.com/toolkits/python/quickstart-python/linuxosx.html OpenEye offers free Toolkit licenses for academics: https://www.eyesopen.com/academic-licensing\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "fc8c1909748d433092eb5fca55801c3d", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + " - Generating rotamer library\n", + " - Core forced to contain atoms: CA, C, N\n" + ] + } + ], + "source": [ + "ligand_path = get_data_file_path('ligands/modified_sidechain.pdb')\n", + "molecule = Molecule(ligand_path, tag='GRW',\n", + " core_constraints=[' CA ', ' C ', ' N '],\n", + " exclude_terminal_rotamers=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "display(molecule)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "rotamer_library = RotamerLibrary(molecule)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "rotamer_library.to_file(molecule.tag.lower() + '.rot.assign')" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "N\n", + "O\n", + "S\n", + "N\n", + "O\n", + "N\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "display(rotamer_library)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Include terminal rotamers" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " - Initializing molecule from PDB\n", + " - Loading molecule from RDKit\n", + " - Assigning stereochemistry from 3D coordinates\n", + " - Setting molecule name to 'modified_sidechain'\n", + " - Representing molecule with the Open Force Field Toolkit\n", + " - Generating rotamer library\n", + " - Core forced to contain atoms: CA, C, N\n" + ] + } + ], + "source": [ + "ligand_path = get_data_file_path('ligands/modified_sidechain.pdb')\n", + "molecule = Molecule(ligand_path, tag='GRW',\n", + " core_constraints=[' CA ', ' C ', ' N '],\n", + " exclude_terminal_rotamers=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAcIAAACWCAIAAADCEh9HAAAABmJLR0QA/wD/AP+gvaeTAAAgAElEQVR4nO3de1xM6f8A8M+ZmtKdKJG1RfWlFtvN1ka59t39shvrUmJqrd0I3xJWrVuuKdYKaeW2utpvQm6/VW7jHlJCpK1WttSUQqVSM+f5/XHsbBtSzTlNU5/3a/8Yc858nk+zfHrOc57zPBQhBBBCCLUWT94JIISQYsMyihBCMsEyihBCMsEyihBCMsEyihBCMsEyihBCMsEyil57/Phxeno681okEl27dk2++SCkKLCMoteEQuHu3buZ1xkZGZs2bZJvPggpCiyjCCEkE2V5J4DakcTExMzMTAAoLy83NTWVdzoIKQYso+hvzs7OzLW8UCg8cOCAvNNBSDFgGUV/U1dX19PTAwAdHR1554KQwsCx0Xatze6eP3v2TCKRUBTF/JGiKOlrRYczEBDXsIy2a21z95ymaTc3t7CwsMWLFzPvjB079tChQ1y01fZwBgLiGl7UIwgKCkpOTtbT01NRUZF3LggpHiyj7R3Xd88vXry4evVqHo8XHR1taGjIevz2AGcgIE7hRX175+zsnJCQkJCQsHLlSgA4fPhwbm4uW8FLS0vd3d3FYnFAQMC///1vtsK2N42+Q4TYhWW0vWPunuvp6eno6Lx48WL69OlWVlYHDx6UPTJN0wKBoLCwcPjw4atXr5Y9YLvV8DuUdy6oA8Iy2q41vGNOUZSGhoaLi0tFRcXUqVM9PDxqampkCb5hw4akpCQ9Pb0DBw4oK3fY4Z2G3yEhJD8/PywsTL4poQ6Gwr2YFE5UVNScOXNqamosLS3j4+NNTExaEeTixYujR4+WSCRHjx794osvWE+yfbp27dqnn36qqamZnZ3dq1cveaeDOgjsjSoeDw+Py5cvm5iYpKenW1lZ/frrry2NIB0S/eGHHzpPDQUAe3v7CRMmVFVV4SApYhH2RhVVZWWll5cXU0MFAkFERISamlpzPkjT9Lhx406dOmVnZ3fx4kU+n89xpu1Lbm6uubm5RCJJT08fNGiQvNNBHQH2RhWVlpbWgQMHIiMj1dTUoqOjHRwccnJymvPB4ODgU6dO6erq/vrrr52thgJA//79Z8+eLZFIFixYIO9cUAeBvVGFl56ePnXq1JycHC0trYiIiGnTpjVxckpKiqOjo1gsTkxM/PLLL9ssyXbl2bNnJiYm5eXlv/3222effSbvdJDCw96owrO0tExLS3Nzc6usrHR3d/fw8Kiurn7rmeXl5a6urvX19QEBAZ22hgJAt27dfvjhBwBYuHChWCyWdzpI8RHUUTAX+ABgaWmZnZ3d6ChN08zdJDs7u7q6Orlk2H7U1tYaGxsDwL59sfLOBSk8vKjvUJq4wN+wYcPSpUt1dXXT0tI+/PBDOSbZTiQkHNu2rTgvb1ZWlpKmpryzQQpN3nUcsayiosLNzY35nysQCF6+fEkIuXbtGp/Ppyjq6NGj8k6wvaBp4uBAAMiKFfJOBSk47I12TFFRUd7e3tXV1ZaWlrt27Zo8eXJ+fn5AQMCGDRvknVo7kpICn34KXbpAVhb07SvvbJDCwjLaYUkv8JWVlcVisYODg1Ao7MAPfbbO1Klw8CB4esL+/fJOBSksvFPfYVlaWt66dcvExIR5qNzAwKCurk7eSbU7ISGgqgrR0XDrlrxTQQoLy2iHRQhZunRpTk4Oj8fj8/mHDh1ycHD4/fff5Z1X+2JsDPPmAU3DXwv/I9RiWEY7rB9++GHHjh0qKipHjhxJSUkxNTW9ffu2tbU1bvnZyIoV0L07CIVw/Li8U0GKCctox7Rq1aqQkBA+n5+QkPD5558zF/jTpk177xT9TqhrV1ixAng8SE2VdypIMeEtpg4oNDTUz89PSUkpNjbW1dW14SHpHXxzc/P4+HgLCwt5Jdmu1NXB2bOgrAxjxwIAPH0KOTlgZyfvtJCCwN5oRxMWFubn58fj8fbv39+ohgKAh4fHjRs3zM3N79+/b29vHxcX1wYpPXoEp0+/fv30KaSktEGbLaOiAk+egLMzXLkCAHD/PoSGyjsnpDiwjHYo+/fv9/X1pSgqPDx8xowZbz3HwsIiJSWFucCfPn16G1zgnz2rGBVq+HCYMwfq6+WdB1I0WEY7joSEhG+//Zam6Y0bN86ePbuJM7W0tOLi4iIjI9XV1aOjo21tbZmNM7mjEBXK0RHs7eHHH+WdB1I0WEY7iMTExGnTpkkkkqCgoMXNm7zj4eFx8+ZN5gLfzs6Oiwv8P/+EHTvgyROFqVAbN8Lu3cDe1quoU8Ay2hEkJye7ubmJxeLAwEBmCbhmMjc3T0lJcXd3r6qqYvECPzMTQkJg2DD48EOYPx+uXwdQkArVtSsEB0NAgLzzQAoFy6jCO3funIuLy6tXrxYsWLBq1aqWflxLSys2Nlb2C3yxGM6eBV9fMDKCjz6CgAC4cgU0NGDSJOjfH6B9VyiaBumMlalTwdZWrtkghSPflVGQjK5evaqpqQkAs2bNomlallCZmZnM/CdNTc2YmJhmfqqiouLw4dLp00m3bgTg9X8GBsTLi5w4QWpqCCFkzx6ybNnr88eNI66uJCeHFBTIkizLfv6ZDBtGMjLknQdSTFhGFVh6enq3bt0AwNPTUyKRyB6woqLC3d2d+f0qXWTvrUpKSiIjI8ePH6+qqjpixA9M9ezXj/j4kEuXSNO5JCcTLS0yevR7TmszZWWkRw8CQA4dkncqSDFhGVVUd+7c6d69OwBMmjSpvr6excjMBT4ADBw48O7duw0P3b17d926dba2thRFMdVWSUlp0qSvN20ibyy3/04lJcTAgACQzZtZzLr1vvuOAJAxY+SdB1JYWEYVUnZ2toGBAQC4uLhwsSNIwwv8qKioS5cu+fv7DxgwQDoWpKamNmbMmNDQ0CdPnrQi/qlThKKIqiq5fZv13FsmNZXweERFhTx4IOdMkOLCMqqQ1q9fDwCfffZZbW0tR01IL/BVVVWl1VNfX3/WrFlHjx6trq6WMb63NwEg5uZE5kitJ5GQTz4hACQgQG45oA4An6lXDI8fPy4rK7O0tAQAkUiUl5f38OFDV1dXZg877vj4+OzZs0dZWXnOnDkuLi729vY8HjuzO2prwdYW7t0DPz/46SdWQrbY7t3g5QV9+sCDB8Bsx3T+POjogJWVfPJBikredRw1S2RkpLe3N/M6KSlp4sSJbdPuoUOHAOCrr75iJVphIfnuO1JV9fqPt24RFRVCUeT//o+V8C1TXk709AgA+d//Xr/z8iXp25coKZELF+SQD1JcOG8UNYXZxp2trUdmzIDdu2HRotd/tLKCwEAgBL79FsrKWGmhBZYuhdJScHSEKVNevxMUBI8fw5Ah4ODQ1skghYZlVGEkJiY6OTk5OTktktYh7jFlVElJiZVoYWGgpgYREX8vkBwQACNGwJMn8N13rLTQXOnpsHs3KCtDWBgwkw5yc2HzZqAoCAsDln5c1FlgGVUYzs7OCQkJCQkJK1eubLNGJRIJNOiNPnz4kKKooUOHti6auTkEBQEAzJoFxcUAADwe/PIL6OjAgwc1sbEnWcn5vQghmzaF9+lTv2ABDBr0+k1fX6ithZkzwd6+bbJAHQeWUYWhrq6up6enp6eno6PTZo02uqhn/ijLc/e+vvD551BaCjNnvn7+0sgI9u79Iy9P39t7Wl5eHgtJv8/+/fsPHJhHUYNWrpQw7xw9CidPgrY2rF/fBu2jjgbLqGJgdvd88zXX3lpGZRkqpSjYswe6d4dTp2DnztdvTppkPGnSF5WVlQKBgOn/cqeiomLZsmUAEBQUqKWlBAA1NRI/PwCA9evBwIDTxlHHhGVUMQgEgh07djCvx44dy9xAbwNvLaMyDpX27g0REQAABw/mZmVlMW+Gh4f37dv36tWrQcxlP2eWLVtWVFQ0bNgwNzc35p2QkLW6uks/+6zK25vTllGHhWUUNaVR3Ww0VNpqkyaBv3/i+fMmM2bMqK+vB4CuXbvGxMQoKSmtWbPmOrOyHgfu3bu3c+dOZWXlHTt2MD36vLy8kJCQtLTggIBbLN5Zevz4cXp6OvNaJBJdu3at/URDrMMyiprSqG6yOP9p+fIxpqamt27dki7uN3z4cD8/P7FYPH369MrKStmbaIQQMn/+fLFYPH/+/MGDBzNv+vr61tbWCgQCJycnFtsSCoW7d+9mXmdkZGzatKn9REOswzKKmsL62KiUpqbm/v37lZSUgoODhUIh8+a6des+/vjj3NzcZi7g3yLR0dEXLlzo2bNnYGAg805SUtKJEye0tbWDg4NZbw51HuxMq0YdFXdlFAA+/fTTpUuXrl271sPDIyMjo1u3bqqqqrGxsTY2Nrt27Ro7duzkyZNZaQgAKioqAgICAGDTpk1du3YFgFevXvn4+ADAmjVrevXqxVZDUomJicwC2OXl5aampu0qGmIXllHUlEZ1k7nGZ2s2PgCsXLkyOTn5+vXrCxYsiIyMBABzc/Pg4GBfX18vL6/y8nI9PT0tLS3p+Zqamnw+n3mtrKzczEMAEBgYWFRU5ODgIN0wNSQkJDs728LCYu7cuWz9OA05OzszV99CofDAgQPtKlpzvLmMgz1OqX0HLKOoKY1uMbHbG2VCRUdHW1panjhx4smTJ7179waA//73v9u3b6+qqmp6f9Pm09TUJITweDzpnSUAuHnzJrMTtbT4souZ5wsArMzzZTdacwiFwpSUlPDwcADIyMjYuXPn4cOH26ZphYNlFDWlUfeT9TIKAKampvHx8UOGDGFqKACcPXuWmYfv7OysoaHR8HZTZWUlkwOTzLsO1dfXV1VVSQ9VVVVpaWnx+XxmoWvG8ePHU1JS7OzsWPxZpCiKomk6Ly+vS5cuMs7zzc/PDwoKevny5e+//w4A5eXlDX801B5gGUVN4e5OfUP/+c9/pK9FIpGHhwdN02vWrFmxYgUr8SsrKz09PY8cObJs2TJm6IDBUQ0FAIFA0Ldv3/79+zs5OQmFwrFjx7Y61Pz58x8+fAgABQUF0jcPHTo0adIkFhJtEg7INhPeqUdN4WL6fRNomp4xY0ZRUdHIkSOXLl3KVlgtLa3NmzerqqrGxMSkpqayFbZpbH1XzJaFjTC7vHBNLss4KCIso6gpb73FxHpvVGr16tVnzpzp2bNnbGwsu8Xa2Nh4/vz5NE1zMZXqrdj6rgwNDd98s1+/fjKGbQ65LOOgiLCMoqZwOuGpkfPnz69fv57H48XExHAxA2n58uU9evS4cOHCcek6fVxi67t686vg8XhGRkYyhm1abW0tAMhlGQdFhGVUAcjxWUCu79RLiUSi6dOnSySSwMDAMWPGsB4fALp27coMtn7//ffME6icYuu7kt55kzI0NGy4QRYXvv/++59//tn7r1UG2nIZB0WEZVQByPFZwLa5xUTTtEAgYIZEmeWXOOLt7W1mZvbw4cNdu3Zx1wqDrbHRN8uosbGxjDGbxqw8kJqaStM0pw11GFhGUVPa5hbTrW3bzp45Y2BgcODAAe7uXwEAn89nnvtctWrVixcvuGsI2BsbfbOMcjowKl154L///a905QHUNJzwpBjkNfWkURkdP368iYlJnz592Gzj7FnbRYuKhg69HxTUs2dPNiO/zcSJE0eNGnXu3LmgoKCQkBDuGmKr5/7mLSZOe6NvrjyA3gvLqGJo+2cBGY1qQZ8+fViuoSUlIBAATet//rn+qFFsRn63kJCQlSvnOzoeq6vzVlEx4qgVtsqourq6jo5Ow74zd73RhisP4N355sOLesXQaOoJIYTpnHKN24miNA0zZkBREYwYAcuXc9LE29jY2OzYYWJgkFVY+AN3rbA4jtzoup673uibKw+g5sAyqgAa7SACAF5eXjY2NhcuXOC03bq6uvv37wNAXFzcrVu32G9gzRo4fRp69oS4uDbejdPQMJjHUy8v/19V1VWOmmBxGZdGZZSj3mhmZuaOHTuUlJTCwsJwelOLYBlVAI12EDl8+LCmpmZtbe2XX34pnQjFuvv379vZ2d27d69bt27Hjh2zsbExMjLy9fW9fPkyOw0IhbBuHfB4EB0NHMwSbZqKSh99fV8AUlCwGIBw0QRHvVE1NTUDDnaMIgR+/FHUo0dPb2/vjz/+mPX4HRxBCkgikUydOhUA9PX1s7Oz2Q1O03RERISGhgYAGBkZ/fzzzz4+Pg0ngZubm69evTorK6v1bYhEpFcvAkBWrWIv8ZYRiysyMgxSU6G8PIGL+Nu3bweA+fPnyx7K39+/4Zcve8A3xcYSAGJkVPXsWQUX8Ts2LKOK6tWrV87OzgDQv3//4uJitsKKRKLx48cz/2IFAkFlZSXzvkQiOX/+/Jw5c3r06CH9J/3xxx9v2LDh+R9/tKwNiYSMHUsAyIgRRCxmK/NWKCkJT02Fu3f70XQt68G3bNkCAAsWLJA91NatW6Xf+bhx42QP2EhFBTE0JADkl19Yj90pYBlVYBUVFVZWVgBgY2MjrXey+O2335gLxh49ehw5cuSt54jF4kuXLvn4+DDLX2qqqNDduhFzcxIYSHJzm9XMqlUEgOjrk8JC2XOWBU2LMzMtUlOhuPgn1oP/+OOPALBo0SLZQx08eFBaRn18fGQP2MiiRQSA2NoSiYT12J0CllHFVlJSYmZmBgCjRo2qrW19l6q6uprZUQMAxowZU1BQ8N6PvHr16tixY1F+fkRLiwAQAMLjkWHDyPbtpKioqU+eP0/69CHJya3OlkXPn59ITYX09G719U/ZjczM8/f395c91JUrV6RldMuWLbIHbCgzk/D5hMcj16+zG7gTwTKq8HJzc5kupJubm6RV3YkbN24wtbhLly7BwcEtDlJTQ44dIwIB0dT8u546OJDQUCIdbaiqIvHxZONGcvIkoWlSU9OKPDmSnT02NRX+/NOP3bDr1q0DgGXLlske6o8//pCW0cTERNkDNjRqFAEg3t7sRu1csIy2qfz8/LS0NOZ1cXHx1atXWQmbkZHBbNM2b968Fn1QLBYHBwerqKgAwEcffZSRkSFTHlVVJC6OuLgQVdXX9ZTPJ19/TSoriZUVcXIi69YRc3Py1VcytcK2ly/Tb91SystzI4RmMSyzcXRgYKDsoV69eiWdgXTnzh3ZA0r9+isBILq6pLSUxaidDpbRNhUZGen91+/9pKSkiRMnshX53LlzzKo/ISEhzfzIH3/8MXz4cACgKMrHx0eWMYHGnj8nkZFk/HiiokL8/MjBg8TZ+fUhiYT07EkePWKtLTbU1rI824EQwqwmtWbNGlaiSe/ssTIIzqBpMnAgASC7drEVspPCeaMdxMiRIyMjI3k8XkBAwL59+957/sGDBy0tLS9duvTBBx+cPXt269atbK69pqMDHh5w/Dg8eQJLlsBvv8GECa8P8XgwbhycPs1aW2xQVTUVi5+Wlf3y/PlhiaSClZjsPgDGTB3V19d/62L4rUNRcPo0LF8Os2axFbKTwmfq2xp3i4y4urqWl5fPnTvXy8tLV1d3grRy/dPz58/nzp3LPJg/ZcqUnTt36urqspjGPzBbyPXsCdXVf79ZWAhmZly12CpicWlWln2PHl5VVVeLizcOGJBSX19M09UAoKSkQ1GvexsUxefxmlvF2N0poHfv3nfu3JH9+aVHj+D334HZGurpU/jzT1i7loX0Ojkso22N00VGvL29CwoKgoKC3N3dT58+7eDg0OiEM2fOfP3114WFhdra2ps2bfLy8mI3gbebOBHmzIFp06B3b7h2DTIz4Y3E5Kui4qy6urWBwRIAEIm21NcXP3r0dUVFUhMf4fH6OjqWSzdnVlZW1tLSkh7V0tIqLi4GgO3btx8+fLjRIWltffNT0kN8Pr9hx5NZmkQikezatavRpzQ1NZk0KIrH443S1v47SXV1kF5jUBR07QqnToG3N1y+DA4OcP8+hIfDr782+2tC74BltK1xveH4unXrRCLR3r17XVxcLl26NHDgQOb92traVatWbdq0iaZpe3v76Ojo/v37c5HAW9jYgL09GBvDgAHw55+wf38bP0H/Xl27TiguDr53z0xbe3S3bq58voGysp6qaj8AkEieE/L6aVFC6mj65V8fUmq00XFpaWmjsOrq6gUFBQ139JSFhobGzZs3b968+a4T1NTUamqq33WUMW0aDB8Oc+ZAWhorSSEALKNtrOEiI/X19ZmZmatXr2Z3YUeKoiIiIp49e3b48GFnZ+crV6707ds3MzNzxowZt2/fVlZWXrFixYoVKzhdHfnNnCAsDEJCoLgYjI2B1+5G5AmpGTAgpb6+sKIi6Y8/ZvzrX0Jj4+j3fqqiopIZAAWA+vr6hlW1srLym2++SUtLCw8Pt7S0bHToXZ+qqKhghgLePHTmzJlbt27Z2NhYWVk1OlRVVcXsiaKiolZTAxUNhnZfvoS6OunPCM+fg4oKODpCSQn8+GN7uyRQZPK+x9V5Xb16VUlJSVlZma1pTw1VV1cPGzYMACwsLIKCgpjbRwMGDEhNTWW9rQ5AJNpeWLiSeZ2d/e/S0r2yx2SWW87Ly5M9FCGEWQZ0w4YNMsbZs4csW0aePSPGxmTfPuLqykp2nV276xd0Hvb29t9//71YLJ4xY0ZFBTt3h6XU1NSOHj1qZmaWmZm5dOnSurq6uXPnpqWlWVtbs9tQx9C9u6Cq6uL9+4Ozs0cTUt+t22QZA9bW1hYVFfH5/A8++ICVDNndBatrVwgOhoAAVoIhXChPrtasWTN06NC8vDw/Pz/Wg585c0YkEgEARVHu7u47duxQU1NjvZWOQUlJx8zsvJnZuf79E83Mziopab//M03Kzc2ladrIyIitwsf6+tlTp4KtLZSVwapVbIXsvLCMyhOfz4+MjFRXV9+3b198fDxbYSsqKmbPnu3q6vrixYuhQ4fyeLz//e9/bbkts4JSVu6hpKT1/vOaITc3FwBYvInH1vSpWbNg3brXr2NjISUFVq+GS5dkjNrZYRmVswEDBmzcuBEA5syZ8+eff8oeMCUlxdraeteuXWpqaqGhoSkpKYsWLWKGDiorK2WPj5qDKaMmJiZsBeRia2sdHVi8GADA1xdwK2VZYBmVv7lz544fP/7Zs2ezZs0ipPUrsYvF4lWrVg0bNiwnJ8fW1vb27du+vr4URa1bt87W1jYvL2/hwoUspo2awHpvlIsyCgBLlsAHH0B6Os4elQmWUfmjKGrfvn09e/Y8ffr0tm3bWhckLy/Pyclp9erVAODv73/58mWzv54UYoYO1NTU9uzZ03DlSsSdnJwcUIQyqqb2emzU3/8fD5qhFsEy2i7o6en98ssvFEX5+/vfuXOnpR+PiooaMmTI1atXP/zww3PnzkkXbZIaOHAgM3Qwd+7coqIi1vJG78BRb5SL2b5ffw1WVlBQANu3sx6705D3jCtFwtEyd1LMo5kWFhY1zV6Os6Sk5Msvv2T+V06ZMuXZs2fvOpOm6XHjxgHA2LFjaZrNFeFQI2KxWEVFhaKo6upqtmK6u7sDQGxsLFsBGzp3jgAQLS3C3mY0nQv2RltAKBTu3r2beZ2RkcE8Gs+in3766V//+ldmZiazxtp7JSUlDRky5NixYzo6OrGxsfHx8cyqo2/VcOggLCyMvaxRY/n5+XV1dYaGhizOMOPoop4xciR8/jlUVsL69XXvPxu9ActoO6KhoREbG8vn83/66adz5841cWZNTY2vr+/nn39eVFQ0evToe/fuMb2Vpunr60dERADAkiVL7t69y1re6J9Yv6IHjssoAGzaBE5OO+PjjbKysjhqogPDMtoyiYmJTk5OTk5OixYt4iK+tbX1ihUraJr28PAoLy9/6zmpqamWlpbbtm1TVVUNDg5OTk7u06dPM+O7uLh89913tbW17u7utbW17CWO/sb6bCfgvoxaWMCAAbdFoqIlS5Zw1EQHhmW0ZZydnRMSEhISElauXMlRE8uWLXNyciosLHxzFTuaprdu3erg4PDw4UMLC4tr1675+/vzWrjSx5YtW8zMzO7du8fukihIirveKKcLyqxZs0ZbW/v48eNnzpzhrpUOCctoyzDL3Onp6XG0zB0A8Hi8X375RVtb+9ChQzExMdL38/PzR44cuWDBgvr6ei8vrxs3bnz88cetiK+hoREXF8fn83/88cemhw5Q63BRRtldBPqt9PX1ma7o4sWLaZyO3xJYRlug4TJ3FEXV1NSMHj36+vXrrDdkbGy8detWAJg/f/6jR4/grz0/Ll68aGBgcPLkyYiICHV19VbHt7a2Xr58edNDB6jVWJ80Ctxf1DMWLlzYt2/fjIyM6Oj3rxOI/ibvqQIKjFm7zMzMrKqqiov4rq6uAGBnZye9fTRp0qSnT9nZTl0ikTg6OjIxWQmIpJhV68vLy1mM6eTkBABCoZDFmG8VFRUFAIaGhhz9re6QsIy2Xm1t7ZAhQwBg9uzZXMQvKyuT7gepra29f/9+duPn5eVpa2sDQExMDLuRO7MnT54AQPfu3dkNy+wHc/nyZXbDvommaRsbGwBYu3Yt1211GHhR33qqqqpxcXFqamoRERHHjh1jN/irV6+CgoLKysr4fD5FUTExMZ6enuw2YWxsHBoaCgDz5s1jhg6Q7LgYGIU2ucXEoCiKmRB95swZIsMKD50KllGZmJubBwUFAcC3337LbGHGivv379vb22/evFlJScna2poQsnDhwkY7/7Bi5syZU6dOffHihUAgkG5fgWTBURltg1tMUiNGjIiLi9u8eTNzJ0AkEuEqi03DMiorZhp8aWnp119/Lftvb0LIrl27bG1t09PTjY2NhUKhUCgcPHhwTk6Ov78/Kwk3Eh4ebmhoOHDgQGY/HyQjLiaNQlvdYpKqr6/fu3cv85qLB/Y6GCyjsqIoas+ePd27d09KStq5c6csoUQi0RdffDF79uzq6mqBQHDnzh0HBwfp0MHPP/98/PhxttKW6t69e0ZGxvLlyx88eCBNA3sfrTZ79uwTJ064ubmxG7aNyyhqGfkOzXYYR44cAYAuXbrcvcthVXoAAAl1SURBVHu3dREOHTrUvXt3ANDT00tMTGx0dPPmzcyhoqIimZN9i8jISG9vb+Z1UlLSxIkTuWilY+N05ZoBAwYAwLVr17KyslgM+y6RkZG9evVydHR0dHT86KOP8O9D07A3yo4JEybMnDmztrbW09Ozrq5l6ztUVlbOnj170qRJZWVlzs7Ot2/fdnFxaXSOn58fM3Qwc+ZMggP/7RKnK9cwY6OhoaGDBg3y9fVtg40M2uCBvQ4Dyyhrtm/fbmpqmpaWtqolm4TduHGj4Z4fp06d6t2795unURS1e/fu7t27nzp1ateuXawl3QDXywUgWTAj10pKShKJZNu2bebm5ixu3vVWbfDAXoeBZZQ10vWZQkJChELhe88Xi8UhISHDhg37/fffBw0adP36dWbPj3edb2hoyHR2Fi5c+PDhQxYzZ2DvQ3Yc/SqKiooqKyv74osvtm7devPmTXt7+4KCAldX15EjR967d4/FhqQaPbDXxF9LBIBjo2xjalCfPn2afoglLy+PmVBNUZSPj8+rV6+aGZ+ZPWplZdX8jzQHjo3KLjIy0tPTs6SkpKSkJD4+npXvsLy8fOLEicw/1W3btjFv0jQdGRmpr68PAMrKyj4+Pi9evJC9LdRq2Btl2YoVK+zs7AoKCnx8fN51TlRU1ODBg69cudK3b9/z589v3bq10Z4fTQgLCzMxMUlLS1uzZg1LKQNg74Ml7F4IX79+3dra+siRI1paWmZmZtLNtSiK8vDwyMrK8vHxIYRs27ZtwIABUVFRBAfN5UXedbwDysnJ0dLSAoC4uLhGh0pLSydMmMB881OmTGndY9eXL19WUlLi8Xht8IQ1ar6oqKi5c+cyr5OTk7/66qtWh6JpOjQ0lM/nA4Ctre28efOYvzNubm4FBQUNz7x165adnR1zNN/Tk7R2ogiSBZZRTjB3gbp27Zqfny99Mzk5mbl9pKOjEx0dLUv85cuXA4CxsTFezXU8xcXFzs7O0GDAp66uLjQ0lFnxRF1dPTAwsLa2Vno+TdP79u0LHTOGABBlZbJgAXn+XI75d0JYRrkyefJkABg+fLhYLK6pqZGur/zpp5/m5ubKGLy+vv6TTz4BAE9PTzaSRe1FUlJSz549AUBfX/+3335reKigoEAgEDAdT1NT00ZHybNnxMeHKCsTAGJgQCIiiETSpql3YlhGuVJaWtqrVy8AWLRo0eDBgwGAz+cHBgZKWPrLLR06OHDgACsBkXzV1tb6+Pgwo9Jjxox58uTJW087d+6chYUFU0zHjx//6NGjfxy+fZsMG0YACACxtSU3brRF6p0ellEOnTx5kqIophNqYWGRnp7Obnzm2dNGQwdIEWVlZVlaWgKAsrLye3/Xvnr1Kjg4WENDAwA0NDSitmwhDadt0DSJjyd9+hAAwuMRgYCUlnL+A3RuWEa5kp+fP2LECABQUVHR1dUtKyvjohVmk3pHR0e2Ormo7UVGRjLjnkZGRs1/hLSwsFAgEFAUVfTJJ8TEhJw8+Y/Dz5+TBQteX+Pr6xO2F6tFDWEZ5UR8fLyuri4zwmVkZAQAzNwU1pWUlBgYGADA5s2buYiPOFVRUTFjxgzmCn3y5MnPnj1raYTbQiExN399FT9xIml0jZ+VRZydCQDhZmVxxMAyyrIXL15Id/ScOHFiaWlpWlqaiooKRVEnG/UXWHLy5ElNTc19+/ZxERxx5+bNm8x6esxzwK0PVF9PQkOJtjYBIGpqJDCQ1NT8fZSmSVwcXtdzCssom65evdqvXz9mVkrDfxjBwcFMz1QkEnHRbmlpKafLCyF2MdNCmWcuLCws7t27x0LQJ0+IQEAoigCQ/v3J8eP/OFpVReLjycaN5ORJQtMsNIcawDLKjvr6+sDAQGaPh6FDh2ZnZzc8KpFIRo0aBQATJkzgKAF8mlNRlJSUfPbZZ8y0UF9f34YzQFlw4QIZNOj1Nf748YSZWldZSaysiJMTWbeOmJsTGZ4LQG+FD4Oy4MGDB5988snq1aspivL39798+bKpqWnDE3g8XmRkpK6ubmJionRRcdRJPH78OD09nXktEonS09MfPHjQo0ePY8eOhYaGqqqqstmYoyOkpcGWLaCjAydOwKBBUFQEp05Bjx4gFMKyZXD3Lly5Avn5bDaK5F3HFRtN0xEREczUEyMjo0uXLjVx8sGDBwFAQ0Pj4cOHrGeC6+y2W29eKKSmpjZ6ppN9RUXEy4t8+y0hhHzzDQkP//vQN9+Q3bu5bb2Twd5o65WUlHz55ZezZ89++fKlQCC4e/fusGHDmjh/8uTJ7u7uL1++nD59OhcbH+FKd4rC2tra0NCQ2zYMDCAiAiIiAAB69oTq6r8PFRbCX6ucIFZgGW2lU6dODRky5MSJEz169Dhy5EhUVBQz9a9p4eHhH374YWpq6vr161lPCdfZbbfktiQ2jwcAMHEixMXBkycAANeuQWYmODi0aRodHZbRFqupqWF2Ay0uLh4zZszt27eliza9l46OTkxMjJKS0vr169ndNg5XumvP5HyhYGMD9vZgbAxDhsC4cbBjB3C/333nIu9RBQVz48YNZtnHLl26BAcHt+7ZIWar5H79+lVUVLCeIWpv2sskiqoqkpOD65VwAXujzSWRSEJCQhwcHLKzsz/66KPr169LF21qqbVr19ra2ubl5fn5+bGeJ2pv2suFgoYG9O8Prfobi5pGEVwx+x0eP35cVlbGLBghEomuXLni6elZXV29ePHitWvXNn+9+rd68OCBjY1NdXV1fHz8lClTWEoZISQH+KvpnRrtlxsTE7N3796zZ8+GhITIWEMBYODAgSEhIQAwd+7coqIiWXNFCMkPltEWmDp1KrNoEyvmzZs3fvz4p0+fMksvsxUWIdTGlOWdQLuWmJiYmZkJAOXl5Y0eTJIdRVF79+4dPHjw6dOnt2/f3sQWeAih9gx7o03hep6Kvr5+REQEACxZsuTu3btcNIEQ4hqW0aa0wYR2FxcXLy8vIyMjmqY5agIhxCm8qH+nNpun8tNPPxUUFFT/9bieSCTKy8uzt7fnqDmEELtwwlO7EBUVlZKSEh4eDgDJyck7d+48fPiwvJNCCDULXtQjhJBM8KK+veB0VgBCiDtYRtsLZ2fnTZs2AYBQKDxw4IC800EINReW0faCmRUAALjMHUKKBcdG24X2snoFQqjl8E49QgjJBHujCCEkEyyjCCEkEyyjCCEkEyyjCCEkEyyjCCEkEyyjCCEkk/8HdrGNfydHGlQAAAAASUVORK5CYII=\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "display(molecule)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "rotamer_library = RotamerLibrary(molecule)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "rotamer_library.to_file(molecule.tag.lower() + '.rot.assign_2')" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "N\n", + "O\n", + "S\n", + "N\n", + "O\n", + "N\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "H\n", + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "display(rotamer_library)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From b31a97c81d7f32b0d63deb881cea26b24ffdb4d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Municoy?= Date: Thu, 15 Oct 2020 15:44:09 +0200 Subject: [PATCH 3/3] Update releasehistory.rst --- docs/releasehistory.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/releasehistory.rst b/docs/releasehistory.rst index 4fd17037..160de889 100644 --- a/docs/releasehistory.rst +++ b/docs/releasehistory.rst @@ -16,10 +16,12 @@ This minor release extends the compatibility of offpele to fully handle dihedral New features """""""""""" - `PR #62 `_: New functionality to generate rotamer libraries forcing an atom to be in the core. +- `PR #63 `_: Enhancements to the core constraints to allow the selection of multiple core atoms. Tests added """"""""""" - `PR #62 `_: Adds tests to validate the new rotamer library with core constraints. +- `PR #63 `_: More tests are added to validate the new rotamer library with core constraints. 0.3.1 - General stability improvements