diff --git a/AUTHORS.md b/AUTHORS.md index d1e7e256..e6860353 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -12,6 +12,7 @@ The following people have contributed to pyMBE (Github username, Name, current a - paobtorres, Paola B. Torres (Universidad Tecnológica Nacional, Argentina) - pinedaps, Sebastian P. Pineda (University of Lund, Sweden) - 1234somesh, Somesh Kurahatti (University of Stuttgart, Germany) +- jorch28, Jordi Sans-Duñó (Freelance,Figaró, Spain) ## Tier-1 Contributors - TommyTraan, Duy Tommy Tran (Norwegian University of Science and Technology, Norway) @@ -28,4 +29,4 @@ The following people have contributed to pyMBE (Github username, Name, current a - Magdaléna Nejedlá (Charles University, Czech Republic) - Raju Lunkad (University of Goettingen, Germany) - Rita S. Dias (Norwegian University of Science and Technology, Norway) -- Sergio Madurga (University of Barcelona, Spain) \ No newline at end of file +- Sergio Madurga (University of Barcelona, Spain) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b513a04..b1b4ef1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ## Added +- Simulation-engine abstraction layer to decouple pyMBE molecular-building logic from simulation-engine-specific implementation details. (#151) +- Added `EspressoEngine` to delegate ESPResSo-specific operations, including reaction methods, setup utilities, and particle/bond/angle insertion. (#151) +- Added `add_instances_to_engine()` to transfer pyMBE particles, bonds, and angles to the selected simulation engine. (#151) +- Added protocol classes to validate supported simulation-engine interfaces, including ESPResSo-system compatibility checks. (#151) +- Added preliminary LAMMPS engine scaffolding to prepare pyMBE for future interoperability with additional simulation backends. (#151) - Support to setup angular potentials with pyMBE. All flexible pyMBE templates now support angula potentials: hydrogels, molecules, peptides and residues (including residues with nested residues). (#150) - Sample scripts and tests for the new functionality. (#150) - Template and instance `Angle` to store information about angular potentials in the pyMBE database. (#150) @@ -22,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added utility functions in `lib/handy_functions` to define residue and particle templates for aminoacids en peptides and residues: `define_protein_AA_particles`, `define_protein_AA_residues` and `define_peptide_AA_residues`. (#147) ## Changed +- Moved engine-dependent functionality from pyMBE-level methods and helper functions to the selected simulation engine. (#151) - Create methods (`create_particle`, `create_residue`, `create_molecule`, `create_protein`, `create_hydrogel`) now raise a ValueError if no template is found for an input `name` instead than a warning. (#147) - Refactored core modules to use the new database schema based on templates and instances for particles, residues, molecules, hydrogels, proteins and peptides. (#147) - Particle states now are independent templates and are now disentangled from particle templates. (#147) @@ -35,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed +- Deprecated legacy ESPResSo-specific helper functions in favor of the corresponding pmb.simulation_engine.* methods, including reaction execution, particle counting, system relaxation, Langevin dynamics setup, and electrostatic-interaction setup. - Methods that interact directly with the pyMBE dataframe. These methods have been replaced by private methods that instead interact with the new canonical pyMBE database in (`pyMBE/storage/manager`). This includes the methods: `add_bond_in_df`, `add_value_to_df`, `assign_molecule_id`, `check_if_df_cell_has_a_value`, `check_if_name_is_defined_in_df`, `check_if_multiple_pmb_types_for_name`, `clean_df_row`, `clean_ids_in_df_row`, `copy_df_entry`, `create_variable_with_units`, `convert_columns_to_original_format`, `convert_str_to_bond_object`, `delete_entries_in_df`, `find_bond_key`, `setup_df`, `define_particle_entry_in_df`, custom `NumpyEncoder`. (#145,#147) - Method `add_bonds_to_espresso` has been removed from the API. pyMBE now adds bonds internally to ESPResSo when molecule instances are created into ESPResSo. (#147) - Tutorial `lattice_builder.ipynb` has been removed because its content is redundant with sample script `build_hydrogel.py`. (#147) diff --git a/pyMBE/exceptions/pmb_warnings.py b/pyMBE/exceptions/pmb_warnings.py new file mode 100644 index 00000000..aa1284f8 --- /dev/null +++ b/pyMBE/exceptions/pmb_warnings.py @@ -0,0 +1,35 @@ +# +# Copyright (C) 2023-2026 pyMBE-dev team +# +# This file is part of pyMBE. +# +# pyMBE is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# pyMBE is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import warnings +from functools import wraps + +def deprecated(new_function): + """Wrapper function to display a warning deprecation message to all functions that are going to be removed in a future + """ + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + warnings.warn( + f"{func.__name__} is deprecated and it will be removed in the future. Please use"+ new_function +"instead.", + DeprecationWarning, + stacklevel=2 + ) + return func(*args, **kwargs) + return wrapper + return decorator diff --git a/pyMBE/lib/handy_functions.py b/pyMBE/lib/handy_functions.py index 33da68b0..78ac8977 100644 --- a/pyMBE/lib/handy_functions.py +++ b/pyMBE/lib/handy_functions.py @@ -20,6 +20,7 @@ import re import numpy as np import scipy +from pyMBE.exceptions.pmb_warnings import deprecated def calculate_initial_bond_length(bond_parameters, bond_type, lj_parameters): """ @@ -281,7 +282,7 @@ def define_peptide_AA_residues(sequence,model, pmb): pmb.define_residue(name = residue_name, central_bead = central_bead, side_chains = side_chains) - +@deprecated('pmb.simulation_engine.do_reaction') def do_reaction(algorithm, steps): """ Executes reaction steps using an ESPResSo reaction algorithm with @@ -311,6 +312,7 @@ def do_reaction(algorithm, steps): else: algorithm.reaction(steps=steps) +@deprecated('pmb.simulation_engine.get_number_of_particles') def get_number_of_particles(espresso_system, ptype): """ Returns the number of particles of a given ESPResSo particle type. @@ -508,7 +510,7 @@ def protein_sequence_parser(sequence): clean_sequence.append(residue_ok) return clean_sequence - +@deprecated('pmb.simulation_engine.relax_system') def relax_espresso_system(espresso_system, seed, gamma=1e-3, Nsteps_steepest_descent=5000, max_displacement=0.01, Nsteps_iter_relax=500): """ Relaxes the energy of the given ESPResSo system by performing the following steps: @@ -571,7 +573,7 @@ def relax_espresso_system(espresso_system, seed, gamma=1e-3, Nsteps_steepest_des logging.info(f"*** Minimum particle distance after relaxation: {espresso_system.analysis.min_dist()} ***") logging.debug("*** Relaxation finished ***") return espresso_system.analysis.min_dist() - +@deprecated('pmb.simulation_engine.setup_langevin_dynamics') def setup_langevin_dynamics(espresso_system, kT, seed,time_step=1e-2, gamma=1, tune_skin=True, min_skin=1, max_skin=None, tolerance=1e-3, int_steps=200, adjust_max_skin=True): """ Sets up Langevin Dynamics for an ESPResSo simulation system. @@ -634,7 +636,7 @@ def setup_langevin_dynamics(espresso_system, kT, seed,time_step=1e-2, gamma=1, t int_steps=int_steps, adjust_max_skin=adjust_max_skin) logging.info(f"Optimized skin value: {espresso_system.cell_system.skin}") - +@deprecated('pmb.simulation_engine.setup_electrostatic_interactions') def setup_electrostatic_interactions(units, espresso_system, kT, c_salt=None, solvent_permittivity=78.5, method='p3m', tune_p3m=True, accuracy=1e-3, params=None, verbose=False): """ Sets up electrostatic interactions in an ESPResSo system. diff --git a/pyMBE/lib/lattice.py b/pyMBE/lib/lattice.py index 6b19a25a..4d63c492 100644 --- a/pyMBE/lib/lattice.py +++ b/pyMBE/lib/lattice.py @@ -333,5 +333,5 @@ def __init__(self,mpc,bond_l): raise ValueError("mpc must be a non-zero positive integer.") self.mpc = mpc self.bond_l = bond_l - self.box_l = (self.mpc+2)*self.bond_l.magnitude / (np.sqrt(3)*0.25) + self.box_l = (self.mpc+1)*self.bond_l.magnitude / (np.sqrt(3)*0.25) diff --git a/pyMBE/pyMBE.py b/pyMBE/pyMBE.py index 22746bc0..2788d90a 100644 --- a/pyMBE/pyMBE.py +++ b/pyMBE/pyMBE.py @@ -38,7 +38,6 @@ from pyMBE.storage.templates.hydrogel import HydrogelTemplate, HydrogelNode, HydrogelChain from pyMBE.storage.templates.bond import BondTemplate from pyMBE.storage.templates.angle import AngleTemplate -from pyMBE.storage.templates.lj import LJInteractionTemplate ## Instances from pyMBE.storage.instances.particle import ParticleInstance from pyMBE.storage.instances.residue import ResidueInstance @@ -48,6 +47,11 @@ from pyMBE.storage.instances.bond import BondInstance from pyMBE.storage.instances.angle import AngleInstance from pyMBE.storage.instances.hydrogel import HydrogelInstance + +from pyMBE.simulation_builder.espresso_engine import EspressoSimulation +from pyMBE.simulation_builder.lammps_engine import LammpsSimulation +from pyMBE.simulation_builder.base_engine import DummyEngine +from pyMBE.simulation_builder.engine_protocol import EspressoSystemProtocolversion422,EspressoSystemProtocolversion501,LammpsProtocol ## Reactions from pyMBE.storage.reactions.reaction import Reaction, ReactionParticipant # Utilities @@ -126,6 +130,7 @@ def __init__(self, seed, temperature=None, unit_length=None, unit_charge=None, K Kw=Kw) self.db = Manager(units=self.units) + self.simulation_engine = DummyEngine() self.lattice_builder = None self.root = importlib.resources.files(__package__) @@ -215,19 +220,10 @@ def _create_espresso_bond_instance(self, bond_type, bond_parameters): Returns: ('espressomd.interactions'): instance of an ESPResSo bond object """ - from espressomd import interactions - self._check_bond_inputs(bond_parameters=bond_parameters, - bond_type=bond_type) - if bond_type == 'harmonic': - bond_instance = interactions.HarmonicBond(k = bond_parameters["k"].m_as("reduced_energy/reduced_length**2"), - r_0 = bond_parameters["r_0"].m_as("reduced_length")) - elif bond_type == 'FENE': - bond_instance = interactions.FeneBond(k = bond_parameters["k"].m_as("reduced_energy/reduced_length**2"), - r_0 = bond_parameters["r_0"].m_as("reduced_length"), - d_r_max = bond_parameters["d_r_max"].m_as("reduced_length")) + bond_instance = self.simulation_engine._create_bond_instance(bond_type,bond_parameters) return bond_instance - def _create_hydrogel_chain(self, hydrogel_chain, nodes, espresso_system, use_default_bond=False, gen_angle=False): + def _create_hydrogel_chain(self, hydrogel_chain, nodes,box_l, use_default_bond=False, gen_angle=False): """ Creates a chain between two nodes of a hydrogel. @@ -236,10 +232,7 @@ def _create_hydrogel_chain(self, hydrogel_chain, nodes, espresso_system, use_def template of a hydrogel chain nodes ('dict'): {node_index: {"name": node_particle_name, "pos": node_position, "id": node_particle_instance_id}} - - espresso_system ('espressomd.system.System'): - ESPResSo system object where the hydrogel chain will be created. - + box_l('list[float,float,float]'): side length of the simulation box for x,y and z coordinates. use_default_bond ('bool', optional): If True, use a default bond template if no specific template exists. Defaults to False. @@ -278,7 +271,7 @@ def _create_hydrogel_chain(self, hydrogel_chain, nodes, espresso_system, use_def # Finding a backbone vector between node_start and node_end vec_between_nodes = np.array(nodes[node_end_label]["pos"]) - np.array(nodes[node_start_label]["pos"]) vec_between_nodes = vec_between_nodes - self.lattice_builder.box_l * np.round(vec_between_nodes/self.lattice_builder.box_l) - backbone_vector = vec_between_nodes / np.linalg.norm(vec_between_nodes) + backbone_vector = vec_between_nodes / (self.lattice_builder.mpc+1) if reverse_residue_order: vec_between_nodes *= -1.0 # Calculate the start position of the chain @@ -297,27 +290,24 @@ def _create_hydrogel_chain(self, hydrogel_chain, nodes, espresso_system, use_def first_bead_pos = np.array((nodes[node_start_label]["pos"])) + np.array(backbone_vector)*l0 mol_id = self.create_molecule(name=molecule_name, # Use the name defined earlier number_of_molecules=1, # Creating one chain - espresso_system=espresso_system, + box_l=box_l, ### Add lattice_builder box length size, this should be box_l=[self.lattice_builder.box_l]*3 list_of_first_residue_positions=[first_bead_pos.tolist()], #Start at the first node backbone_vector=np.array(backbone_vector)/l0, use_default_bond=use_default_bond, reverse_residue_order=reverse_residue_order, gen_angle=gen_angle)[0] # Bond chain to the hydrogel nodes + ### The following implementation belongs to espresso engine chain_pids = self.db._find_instance_ids_by_attribute(pmb_type="particle", attribute="molecule_id", value=mol_id) - self.create_bond(particle_id1=start_node_id, - particle_id2=chain_pids[0], - espresso_system=espresso_system, - use_default_bond=use_default_bond) - self.create_bond(particle_id1=chain_pids[-1], - particle_id2=end_node_id, - espresso_system=espresso_system, - use_default_bond=use_default_bond) + + self.create_bond(particle_id1=start_node_id,particle_id2=chain_pids[0],use_default_bond=use_default_bond) + self.create_bond(particle_id1=chain_pids[-1],particle_id2=end_node_id,use_default_bond=use_default_bond) + return mol_id - def _generate_hydrogel_crosslinker_angles(self, espresso_system, central_particle_ids): + def _generate_hydrogel_crosslinker_angles(self, central_particle_ids): """ Generate hydrogel angles centered on crosslinkers and adjacent terminal beads. @@ -375,10 +365,9 @@ def _generate_hydrogel_crosslinker_angles(self, espresso_system, central_particl self.create_angular_potential(particle_id1=side_particle_id1, particle_id2=central_particle_id, particle_id3=side_particle_id3, - espresso_system=espresso_system, use_default_angle=False) - def _create_hydrogel_node(self, node_index, node_name, espresso_system): + def _create_hydrogel_node(self, node_index, node_name,box_l): """ Set a node residue type. @@ -388,9 +377,8 @@ def _create_hydrogel_node(self, node_index, node_name, espresso_system): node_name ('str'): name of the node particle defined in pyMBE. - - espresso_system (espressomd.system.System): - ESPResSo system object where the hydrogel node will be created. + + box_l('list[float,float,float]'): list of floats with the dimensions of the box Returns: ('tuple(list,int)'): @@ -401,22 +389,20 @@ def _create_hydrogel_node(self, node_index, node_name, espresso_system): raise ValueError("LatticeBuilder is not initialized. Use 'initialize_lattice_builder' first.") node_position = np.array(node_index)*0.25*self.lattice_builder.box_l p_id = self.create_particle(name = node_name, - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1, position = [node_position]) key = self.lattice_builder._get_node_by_label(f"[{node_index[0]} {node_index[1]} {node_index[2]}]") self.lattice_builder.nodes[key] = node_name return node_position.tolist(), p_id[0] - def _get_espresso_bond_instance(self, bond_template, espresso_system): + def _get_espresso_bond_instance(self, bond_template): """ Retrieve or create a bond instance in an ESPResSo system for a given pair of particle names. Args: bond_template ('BondTemplate'): BondTemplate object from the pyMBE database. - espresso_system ('espressomd.system.System'): - An ESPResSo system object where the bond will be added or retrieved. Returns: ('espressomd.interactions.BondedInteraction'): @@ -425,14 +411,7 @@ def _get_espresso_bond_instance(self, bond_template, espresso_system): Notes: When a new bond instance is created, it is not added to the ESPResSo system. """ - if bond_template.name in self.db.espresso_bond_instances.keys(): - bond_inst = self.db.espresso_bond_instances[bond_template.name] - else: - # Create an instance of the bond - bond_inst = self._create_espresso_bond_instance(bond_type=bond_template.bond_type, - bond_parameters=bond_template.get_parameters(self.units)) - self.db.espresso_bond_instances[bond_template.name]= bond_inst - espresso_system.bonded_inter.add(bond_inst) + bond_inst=self.simulation_engine._get_bond_instance(bond_template) return bond_inst def _get_label_id_map(self, pmb_type): @@ -447,12 +426,7 @@ def _get_label_id_map(self, pmb_type): 'str': Label identifying the appropriate particle ID map. """ - if pmb_type in self.db._assembly_like_types: - label="assembly_map" - elif pmb_type in self.db._molecule_like_types: - label="molecule_map" - else: - label=f"{pmb_type}_map" + label=self.db._get_label_id_map(pmb_type=pmb_type) return label def _get_residue_list_from_sequence(self, sequence): @@ -500,7 +474,7 @@ def _get_template_type(self, name, allowed_types): raise ValueError(f"No {allowed_types} template found with name '{name}'. Found templates of types: {filtered_types}.") return next(iter(filtered_types)) - def _delete_particles_from_espresso(self, particle_ids, espresso_system): + def _delete_particles_from_engine(self, particle_ids): """ Remove a list of particles from an ESPResSo simulation system. @@ -508,10 +482,6 @@ def _delete_particles_from_espresso(self, particle_ids, espresso_system): particle_ids ('Iterable[int]'): A list (or other iterable) of ESPResSo particle IDs to remove. - espresso_system ('espressomd.system.System'): - The ESPResSo simulation system from which the particles - will be removed. - Notess: - This method removes particles only from the ESPResSo simulation, **not** from the pyMBE database. Database cleanup must be handled @@ -519,10 +489,14 @@ def _delete_particles_from_espresso(self, particle_ids, espresso_system): - Attempting to remove a non-existent particle ID will raise an ESPResSo error. """ - for pid in particle_ids: - espresso_system.part.by_id(pid).remove() + # for pid in particle_ids: + # espresso_system.part.by_id(pid).remove() + self.simulation_engine._delete_particles(particle_ids) - def calculate_center_of_mass(self, instance_id, pmb_type, espresso_system): + def add_instances_to_engine(self): + self.simulation_engine.add_instances_to_engine() + + def calculate_center_of_mass(self, instance_id, pmb_type): """ Calculates the center of mass of a pyMBE object instance in an ESPResSo system. @@ -534,9 +508,6 @@ def calculate_center_of_mass(self, instance_id, pmb_type, espresso_system): Type of the pyMBE object. Must correspond to a particle-aggregating template type (e.g. '"molecule"', '"residue"', '"peptide"', '"protein"'). - espresso_system ('espressomd.system.System'): - ESPResSo system containing the particle instances. - Returns: ('numpy.ndarray'): Array of shape '(3,)' containing the Cartesian coordinates of the @@ -547,17 +518,14 @@ def calculate_center_of_mass(self, instance_id, pmb_type, espresso_system): - Periodic boundary conditions are *not* unfolded; positions are taken directly from ESPResSo particle coordinates. """ - center_of_mass = np.zeros(3) - axis_list = [0,1,2] - inst = self.db.get_instance(pmb_type=pmb_type, - instance_id=instance_id) - particle_id_list = self.get_particle_id_map(object_name=inst.name)["all"] - for pid in particle_id_list: - for axis in axis_list: - center_of_mass [axis] += espresso_system.part.by_id(pid).pos[axis] - center_of_mass = center_of_mass / len(particle_id_list) + if isinstance(self.simulation_engine,EspressoSimulation): + center_of_mass=self.simulation_engine.calculate_center_of_mass(instance_id=instance_id, + pmb_type=pmb_type) + elif isinstance(self.simulation_engine,LammpsSimulation): + raise NotImplementedError('In this version it has not yet been implemented this method for LammpsSimulation engine') + else: + raise RuntimeError('Please set a currently working simulation engine') return center_of_mass - def calculate_HH(self, template_name, pH_list=None, pka_set=None): """ Calculates the charge in the template object according to the ideal Henderson–Hasselbalch titration curve. @@ -726,13 +694,11 @@ def calc_partition_coefficient(charge, c_macro): partition_coefficients_list.append(partition_coefficient) return {"charges_dict": Z_HH_Donnan, "pH_system_list": pH_system_list, "partition_coefficients": partition_coefficients_list} - def calculate_net_charge(self,espresso_system,object_name,pmb_type,dimensionless=False): + def calculate_net_charge(self,object_name,pmb_type,dimensionless=False): """ Calculates the net charge per instance of a given pmb object type. Args: - espresso_system (espressomd.system.System): - ESPResSo system containing the particles. object_name (str): Name of the object (e.g. molecule, residue, peptide, protein). pmb_type (str): @@ -745,29 +711,14 @@ def calculate_net_charge(self,espresso_system,object_name,pmb_type,dimensionless dict: {"mean": mean_net_charge, "instances": {instance_id: net_charge}} """ - id_map = self.get_particle_id_map(object_name=object_name) - label = self._get_label_id_map(pmb_type=pmb_type) - instance_map = id_map[label] - charges = {} - for instance_id, particle_ids in instance_map.items(): - if dimensionless: - net_charge = 0.0 - else: - net_charge = 0 * self.units.Quantity(1, "reduced_charge") - for pid in particle_ids: - q = espresso_system.part.by_id(pid).q - if not dimensionless: - q *= self.units.Quantity(1, "reduced_charge") - net_charge += q - charges[instance_id] = net_charge - # Mean charge - if dimensionless: - mean_charge = float(np.mean(list(charges.values()))) + if isinstance(self.simulation_engine,EspressoSimulation): + net_charge=self.simulation_engine.calculate_net_charge(object_name,pmb_type,dimensionless) + elif isinstance(self.simulation_engine,LammpsSimulation): + raise NotImplementedError('In this version this method is only implemented with espresso') else: - mean_charge = (np.mean([q.magnitude for q in charges.values()])* self.units.Quantity(1, "reduced_charge")) - return {"mean": mean_charge, "instances": charges} - - def center_object_in_simulation_box(self, instance_id, espresso_system, pmb_type): + raise RuntimeError('Please set a currently working simulation engine') + return net_charge + def center_object_in_simulation_box(self, instance_id, box_l,pmb_type): """ Centers a pyMBE object instance in the simulation box of an ESPResSo system. The object is translated such that its center of mass coincides with the @@ -776,35 +727,39 @@ def center_object_in_simulation_box(self, instance_id, espresso_system, pmb_type Args: instance_id ('int'): ID of the pyMBE object instance to be centered. + + box_l('list[float,float,float]'): list of floats with the dimensions of the box pmb_type ('str'): Type of the pyMBE object. - espresso_system ('espressomd.system.System'): - ESPResSo system object in which the particles are defined. - Notes: - Works for both cubic and non-cubic simulation boxes. """ inst = self.db.get_instance(instance_id=instance_id, pmb_type=pmb_type) center_of_mass = self.calculate_center_of_mass(instance_id=instance_id, - espresso_system=espresso_system, pmb_type=pmb_type) - box_center = [espresso_system.box_l[0]/2.0, - espresso_system.box_l[1]/2.0, - espresso_system.box_l[2]/2.0] + box_center = [box_l[0]/2.0, + box_l[1]/2.0, + box_l[2]/2.0] particle_id_list = self.get_particle_id_map(object_name=inst.name)["all"] + print(particle_id_list,"particle_id_list") for pid in particle_id_list: - es_pos = espresso_system.part.by_id(pid).pos - espresso_system.part.by_id(pid).pos = es_pos - center_of_mass + box_center + es_pos=self.db.get_instance(instance_id=pid, + pmb_type='particle').position + centered_position=es_pos - center_of_mass + box_center - def create_added_salt(self, espresso_system, cation_name, anion_name, c_salt): + self.db._update_instance(instance_id=pid, + pmb_type='particle', + attribute='position', + value=centered_position) + + def create_added_salt(self, box_l, cation_name, anion_name, c_salt): """ Creates a 'c_salt' concentration of 'cation_name' and 'anion_name' ions into the 'espresso_system'. Args: - espresso_system('espressomd.system.System'): instance of an espresso system object. cation_name('str'): 'name' of a particle with a positive charge. anion_name('str'): 'name' of a particle with a negative charge. c_salt('float'): Salt concentration. @@ -827,7 +782,7 @@ def create_added_salt(self, espresso_system, cation_name, anion_name, c_salt): if anion_charge >= 0: raise ValueError(f'ERROR anion charge must be negative, charge {anion_charge}') # Calculate the number of ions in the simulation box - volume=self.units.Quantity(espresso_system.volume(), 'reduced_length**3') + volume=self.units.Quantity(np.prod(box_l), 'reduced_length**3') if c_salt.check('[substance] [length]**-3'): N_ions= int((volume*c_salt.to('mol/reduced_length**3')*self.N_A).magnitude) c_salt_calculated=N_ions/(volume*self.N_A) @@ -838,10 +793,10 @@ def create_added_salt(self, espresso_system, cation_name, anion_name, c_salt): raise ValueError('Unknown units for c_salt, please provided it in [mol / volume] or [particle / volume]', c_salt) N_cation = N_ions*abs(anion_charge) N_anion = N_ions*abs(cation_charge) - self.create_particle(espresso_system=espresso_system, + self.create_particle(box_l=box_l, name=cation_name, number_of_particles=N_cation) - self.create_particle(espresso_system=espresso_system, + self.create_particle(box_l=box_l, name=anion_name, number_of_particles=N_anion) if c_salt_calculated.check('[substance] [length]**-3'): @@ -850,7 +805,7 @@ def create_added_salt(self, espresso_system, cation_name, anion_name, c_salt): logging.info(f"added salt concentration of {c_salt_calculated.to('reduced_length**-3')} given by {N_cation} cations and {N_anion} anions") return c_salt_calculated - def create_bond(self, particle_id1, particle_id2, espresso_system, use_default_bond=False): + def create_bond(self, particle_id1, particle_id2, use_default_bond=False): """ Creates a bond between two particle instances in an ESPResSo system and registers it in the pyMBE database. @@ -868,9 +823,6 @@ def create_bond(self, particle_id1, particle_id2, espresso_system, use_default_b particle_id2 ('int'): pyMBE and ESPResSo ID of the second particle. - espresso_system ('espressomd.system.System'): - ESPResSo system object where the bond will be created. - use_default_bond ('bool', optional): If True, use a default bond template if no specific template exists. Defaults to False. @@ -885,9 +837,9 @@ def create_bond(self, particle_id1, particle_id2, espresso_system, use_default_b bond_tpl = self.get_bond_template(particle_name1=particle_inst_1.name, particle_name2=particle_inst_2.name, use_default_bond=use_default_bond) - bond_inst = self._get_espresso_bond_instance(bond_template=bond_tpl, - espresso_system=espresso_system) - espresso_system.part.by_id(particle_id1).add_bond((bond_inst, particle_id2)) + # bond_inst = self._get_espresso_bond_instance(bond_template=bond_tpl, + # espresso_system=espresso_system) + # espresso_system.part.by_id(particle_id1).add_bond((bond_inst, particle_id2)) bond_id = self.db._propose_instance_id(pmb_type="bond") pmb_bond_instance = BondInstance(bond_id=bond_id, name=bond_tpl.name, @@ -895,7 +847,7 @@ def create_bond(self, particle_id1, particle_id2, espresso_system, use_default_b particle_id2=particle_id2) self.db._register_instance(instance=pmb_bond_instance) - def create_counterions(self, object_name, cation_name, anion_name, espresso_system): + def create_counterions(self, object_name, cation_name, anion_name, box_l): """ Creates particles of 'cation_name' and 'anion_name' in 'espresso_system' to counter the net charge of 'object_name'. @@ -903,14 +855,13 @@ def create_counterions(self, object_name, cation_name, anion_name, espresso_syst object_name ('str'): 'name' of a pyMBE object. - espresso_system ('espressomd.system.System'): - Instance of a system object from the espressomd library. - cation_name ('str'): 'name' of a particle with a positive charge. anion_name ('str'): 'name' of a particle with a negative charge. + + box_l('list[float,float,float]'): list of floats with the dimensions of the box Returns: ('dict'): @@ -935,10 +886,17 @@ def create_counterions(self, object_name, cation_name, anion_name, espresso_syst for name in ['positive', 'negative']: object_charge[name]=0 for id in object_ids: - if espresso_system.part.by_id(id).q > 0: - object_charge['positive']+=1*(np.abs(espresso_system.part.by_id(id).q )) - elif espresso_system.part.by_id(id).q < 0: - object_charge['negative']+=1*(np.abs(espresso_system.part.by_id(id).q )) + object_name = self.db.get_instance(pmb_type="particle", + instance_id=id).name + object_tpl = self.db.get_template(pmb_type="particle", + name=object_name) + object_state = self.db.get_template(pmb_type="particle_state", + name=object_tpl.initial_state) + object_z = object_state.z + if object_z > 0: + object_charge['positive']+=1*(np.abs(object_z )) + elif object_z < 0: + object_charge['negative']+=1*(np.abs(object_z )) if object_charge['positive'] % abs(anion_charge) == 0: counterion_number[anion_name]=int(object_charge['positive']/abs(anion_charge)) else: @@ -948,13 +906,13 @@ def create_counterions(self, object_name, cation_name, anion_name, espresso_syst else: raise ValueError('The number of negative charges in the pmb_object must be divisible by the charge of the cation') if counterion_number[cation_name] > 0: - self.create_particle(espresso_system=espresso_system, + self.create_particle(box_l=box_l, name=cation_name, number_of_particles=counterion_number[cation_name]) else: counterion_number[cation_name]=0 if counterion_number[anion_name] > 0: - self.create_particle(espresso_system=espresso_system, + self.create_particle(box_l=box_l, name=anion_name, number_of_particles=counterion_number[anion_name]) else: @@ -964,17 +922,17 @@ def create_counterions(self, object_name, cation_name, anion_name, espresso_syst logging.info(f'Ion type: {name} created number: {counterion_number[name]}') return counterion_number - def create_hydrogel(self, name, espresso_system, use_default_bond=False, gen_angle=False): + + def create_hydrogel(self, name, box_l, use_default_bond=False, gen_angle=False): """ Creates a hydrogel in espresso_system using a pyMBE hydrogel template given by 'name' Args: + box_l('list[float,float,float]'): list of floats with the dimensions of the box + name ('str'): name of the hydrogel template in the pyMBE database. - espresso_system ('espressomd.system.System'): - ESPResSo system object where the hydrogel will be created. - use_default_bond ('bool', optional): If True, use a default bond template if no specific template exists. Defaults to False. @@ -1000,7 +958,7 @@ def create_hydrogel(self, name, espresso_system, use_default_bond=False, gen_ang node_name = node.particle_name node_pos, node_id = self._create_hydrogel_node(node_index=node_index, node_name=node_name, - espresso_system=espresso_system) + box_l=box_l) node_label = self.lattice_builder._create_node_label(node_index=node_index) nodes[node_label] = {"name": node_name, "id": node_id, "pos": node_pos} self.db._update_instance(instance_id=node_id, @@ -1009,10 +967,11 @@ def create_hydrogel(self, name, espresso_system, use_default_bond=False, gen_ang value=assembly_id) for hydrogel_chain in hydrogel_tpl.chain_map: molecule_id = self._create_hydrogel_chain(hydrogel_chain=hydrogel_chain, - nodes=nodes, - espresso_system=espresso_system, + nodes=nodes, + box_l=box_l, use_default_bond=use_default_bond, - gen_angle=gen_angle) + gen_angle=gen_angle, + ) self.db._update_instance(instance_id=molecule_id, pmb_type="molecule", attribute="assembly_id", @@ -1062,14 +1021,15 @@ def create_hydrogel(self, name, espresso_system, use_default_bond=False, gen_ang attribute="assembly_id", value=assembly_id) if gen_angle: - self._generate_hydrogel_crosslinker_angles(espresso_system=espresso_system, + self._generate_hydrogel_crosslinker_angles( central_particle_ids=hydrogel_angle_centers) # Register an hydrogel instance in the pyMBE databasegit self.db._register_instance(HydrogelInstance(name=name, assembly_id=assembly_id)) return assembly_id - def create_molecule(self, name, number_of_molecules, espresso_system, list_of_first_residue_positions=None, backbone_vector=None, use_default_bond=False, reverse_residue_order = False, gen_angle=False): + + def create_molecule(self, name, number_of_molecules, box_l, list_of_first_residue_positions=None, backbone_vector=None, use_default_bond=False, reverse_residue_order = False, gen_angle=False): """ Creates instances of a given molecule template name into ESPResSo. @@ -1077,8 +1037,7 @@ def create_molecule(self, name, number_of_molecules, espresso_system, list_of_fi name ('str'): Label of the molecule type to be created. 'name'. - espresso_system ('espressomd.system.System'): - Instance of a system object from espressomd library. + box_l('list[float,float,float]'): list of floats with the dimensions of the box number_of_molecules ('int'): Number of molecules or peptides of type 'name' to be created. @@ -1139,11 +1098,13 @@ def create_molecule(self, name, number_of_molecules, espresso_system, list_of_fi if list_of_first_residue_positions is None: central_bead_pos = None else: + ### This seems like a bug + ### the variable central bead pos gets assigned the same value for the lengthh of list_of_first_residue_positions for item in list_of_first_residue_positions: central_bead_pos = [np.array(list_of_first_residue_positions[pos_index])] residue_id = self.create_residue(name=residue, - espresso_system=espresso_system, + box_l=box_l, central_bead_position=central_bead_pos, use_default_bond= use_default_bond, backbone_vector=backbone_vector) @@ -1159,7 +1120,9 @@ def create_molecule(self, name, number_of_molecules, espresso_system, list_of_fi prev_central_bead_id = particle_ids_in_residue[0] prev_central_bead_name = self.db.get_instance(pmb_type="particle", instance_id=prev_central_bead_id).name - prev_central_bead_pos = espresso_system.part.by_id(prev_central_bead_id).pos + prev_central_bead_pos = self.db.get_instance(pmb_type="particle", + instance_id=prev_central_bead_id).position + # prev_central_bead_pos = espresso_system.part.by_id(prev_central_bead_id).pos first_residue = False else: @@ -1177,7 +1140,7 @@ def create_molecule(self, name, number_of_molecules, espresso_system, list_of_fi central_bead_pos = prev_central_bead_pos+backbone_vector*l0 # Create the residue residue_id = self.create_residue(name=residue, - espresso_system=espresso_system, + box_l=box_l, central_bead_position=[central_bead_pos], use_default_bond= use_default_bond, backbone_vector=backbone_vector) @@ -1194,7 +1157,6 @@ def create_molecule(self, name, number_of_molecules, espresso_system, list_of_fi # Bond the central beads of the new and previous residues self.create_bond(particle_id1=prev_central_bead_id, particle_id2=central_bead_id, - espresso_system=espresso_system, use_default_bond=use_default_bond) prev_central_bead_id = central_bead_id @@ -1210,7 +1172,6 @@ def create_molecule(self, name, number_of_molecules, espresso_system, list_of_fi self.db._register_instance(inst) if gen_angle: self._generate_angles_for_entity( - espresso_system=espresso_system, entity_id=molecule_id, entity_id_col='molecule_id') first_residue = True @@ -1218,16 +1179,15 @@ def create_molecule(self, name, number_of_molecules, espresso_system, list_of_fi molecule_ids.append(molecule_id) return molecule_ids - def create_particle(self, name, espresso_system, number_of_particles, position=None, fix=False): + def create_particle(self, name, box_l, number_of_particles, position=None, fix=False): """ Creates one or more particles in an ESPResSo system based on the particle template in the pyMBE database. Args: name ('str'): Label of the particle template in the pyMBE database. - - espresso_system ('espressomd.system.System'): - Instance of a system object from the espressomd library. + + box_l('list[float,float,float]'): list of floats with the dimensions of the box number_of_particles ('int'): Number of particles to be created. @@ -1251,30 +1211,30 @@ def create_particle(self, name, espresso_system, number_of_particles, position=N name=name) part_state = self.db.get_template(pmb_type="particle_state", name=part_tpl.initial_state) - z = part_state.z - es_type = part_state.es_type - # Create the new particles into ESPResSo + name_state=part_state.name + + if fix is False: + fix=[fix]*3 + created_pid_list=[] for index in range(number_of_particles): if position is None: - particle_position = self.rng.random((1, 3))[0] *np.copy(espresso_system.box_l) + particle_position = self.rng.random((1, 3))[0] *np.copy(box_l) else: - particle_position = position[index] + particle_position = np.array(position[index]) particle_id = self.db._propose_instance_id(pmb_type="particle") created_pid_list.append(particle_id) - kwargs = dict(id=particle_id, pos=particle_position, type=es_type, q=z) - if fix: - kwargs["fix"] = 3 * [fix] - espresso_system.part.add(**kwargs) part_inst = ParticleInstance(name=name, particle_id=particle_id, - initial_state=part_state.name) + initial_state=name_state, + position=particle_position, + fix=fix) self.db._register_instance(part_inst) return created_pid_list - def create_protein(self, name, number_of_proteins, espresso_system, topology_dict): + def create_protein(self, name, number_of_proteins, box_l, topology_dict): """ Creates one or more protein molecules in an ESPResSo system based on the protein template in the pyMBE database and a provided topology. @@ -1284,10 +1244,9 @@ def create_protein(self, name, number_of_proteins, espresso_system, topology_dic Name of the protein template stored in the pyMBE database. number_of_proteins (int): - Number of protein molecules to generate. - - espresso_system (espressomd.system.System): - The ESPResSo simulation system where the protein molecules will be created. + Number of protein molecules to generate. + + box_l('list[float,float,float]'): list of floats with the dimensions of the box topology_dict (dict): Dictionary defining the internal structure of the protein. Expected format: @@ -1316,7 +1275,7 @@ def create_protein(self, name, number_of_proteins, espresso_system, topology_dic if not self.db._has_template(name=name, pmb_type="protein"): raise ValueError(f"Protein template with name '{name}' is not defined in the pyMBE database.") protein_tpl = self.db.get_template(pmb_type="protein", name=name) - box_half = espresso_system.box_l[0] / 2.0 + box_half = box_l[0] / 2.0 # Create protein mol_ids = [] for _ in range(number_of_proteins): @@ -1345,10 +1304,10 @@ def create_protein(self, name, number_of_proteins, espresso_system, topology_dic relative_pos = topology_dict[bead_id]["initial_pos"] absolute_pos = relative_pos + protein_center particle_id = self.create_particle(name=bead_type, - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1, position=[absolute_pos], - fix=True)[0] + fix=[True,True,True])[0] # update metadata self.db._update_instance(instance_id=particle_id, pmb_type="particle", @@ -1364,7 +1323,7 @@ def create_protein(self, name, number_of_proteins, espresso_system, topology_dic mol_ids.append(molecule_id) return mol_ids - def create_residue(self, name, espresso_system, central_bead_position=None,use_default_bond=False, backbone_vector=None, gen_angle=False): + def create_residue(self, name, box_l, central_bead_position=None,use_default_bond=False, backbone_vector=None, gen_angle=False): """ Creates a residue into ESPResSo. @@ -1372,11 +1331,10 @@ def create_residue(self, name, espresso_system, central_bead_position=None,use_d name ('str'): Label of the residue type to be created. - espresso_system ('espressomd.system.System'): - Instance of a system object from espressomd library. - central_bead_position ('list' of 'float'): Position of the central bead. + + box_l('list[float,float,float]'): list of floats with the dimensions of the box use_default_bond ('bool'): Switch to control if a bond of type 'default' is used to bond a particle whose bonds types are not defined in the pyMBE database. @@ -1400,11 +1358,14 @@ def create_residue(self, name, espresso_system, central_bead_position=None,use_d # create the principal bead central_bead_name = res_tpl.central_bead central_bead_id = self.create_particle(name=central_bead_name, - espresso_system=espresso_system, + box_l=box_l, position=central_bead_position, number_of_particles = 1)[0] - central_bead_position=espresso_system.part.by_id(central_bead_id).pos + central_bead_position = self.db.get_instance(pmb_type="particle", + instance_id=central_bead_id).position + # # central_bead_position=espresso_system.part.by_id(central_bead_id).pos + # Assigns residue_id to the central_bead particle created. self.db._update_instance(pmb_type="particle", instance_id=central_bead_id, @@ -1436,7 +1397,7 @@ def create_residue(self, name, espresso_system, central_bead_position=None,use_d magnitude=l0) side_bead_id = self.create_particle(name=side_chain_name, - espresso_system=espresso_system, + box_l=box_l, position=[bead_position], number_of_particles=1)[0] side_chain_beads_ids.append(side_bead_id) @@ -1446,9 +1407,10 @@ def create_residue(self, name, espresso_system, central_bead_position=None,use_d value=residue_id) self.create_bond(particle_id1=central_bead_id, particle_id2=side_bead_id, - espresso_system=espresso_system, use_default_bond=use_default_bond) + elif pmb_type == 'residue': + side_residue_tpl = self.db.get_template(name=side_chain_name, pmb_type=pmb_type) central_bead_side_chain = side_residue_tpl.central_bead @@ -1469,7 +1431,7 @@ def create_residue(self, name, espresso_system, central_bead_position=None,use_d residue_position=central_bead_position+self.generate_trial_perpendicular_vector(vector=backbone_vector, magnitude=l0) side_residue_id = self.create_residue(name=side_chain_name, - espresso_system=espresso_system, + box_l=box_l, central_bead_position=[residue_position], use_default_bond=use_default_bond) # Find particle ids of the inner residue @@ -1487,10 +1449,9 @@ def create_residue(self, name, espresso_system, central_bead_position=None,use_d instance_id=side_residue_id) self.create_bond(particle_id1=central_bead_id, particle_id2=side_chain_beads_ids[0], - espresso_system=espresso_system, use_default_bond=use_default_bond) if gen_angle: - self._generate_angles_for_entity(espresso_system=espresso_system, + self._generate_angles_for_entity( entity_id=residue_id, entity_id_col="residue_id") return residue_id @@ -1655,7 +1616,7 @@ def define_default_angular_potential(self, angle_type, angle_parameters): tpl.name = "default" self.db._register_template(tpl) - def create_angular_potential(self, particle_id1, particle_id2, particle_id3, espresso_system, use_default_angle=False): + def create_angular_potential(self, particle_id1, particle_id2, particle_id3, use_default_angle=False): """ Creates an angle between three particle instances in an ESPResSo system and registers it in the pyMBE database. @@ -1664,7 +1625,6 @@ def create_angular_potential(self, particle_id1, particle_id2, particle_id3, esp particle_id1 ('int'): ID of the first side particle. particle_id2 ('int'): ID of the central particle. particle_id3 ('int'): ID of the second side particle. - espresso_system ('espressomd.system.System'): ESPResSo system. use_default_angle ('bool', optional): If True, use the default angle if no specific one is found. """ particle_inst_1 = self.db.get_instance(pmb_type="particle", instance_id=particle_id1) @@ -1686,10 +1646,10 @@ def create_angular_potential(self, particle_id1, particle_id2, particle_id3, esp central_name=particle_inst_2.name, side_name2=particle_inst_3.name, use_default_angle=use_default_angle) - angle_inst = self._get_espresso_angle_instance(angle_template=angle_tpl, espresso_system=espresso_system) + # angle_inst = self._get_espresso_angle_instance(angle_template=angle_tpl, espresso_system=espresso_system) # ESPResSo angle bonds are added to the central particle - espresso_system.part.by_id(particle_id2).add_bond((angle_inst, particle_id1, particle_id3)) + # espresso_system.part.by_id(particle_id2).add_bond((angle_inst, particle_id1, particle_id3)) angle_id = self.db._propose_instance_id(pmb_type="angle") pmb_angle_instance = AngleInstance(angle_id=angle_id, @@ -1723,24 +1683,23 @@ def get_angle_template(self, side_name1, central_name, side_name2, use_default_a raise ValueError(f"No angle template found for '{side_name1}-{central_name}-{side_name2}', and default angles are deactivated.") - def _get_espresso_angle_instance(self, angle_template, espresso_system): + def _get_espresso_angle_instance(self, angle_template): """ Retrieve or create an angle interaction in an ESPResSo system for a given angle template. Args: angle_template ('AngleTemplate'): The angle template to use. - espresso_system ('espressomd.system.System'): ESPResSo system. Returns: ('espressomd.interactions.BondedInteraction'): The ESPResSo angle interaction object. """ - if angle_template.name in self.db.espresso_angle_instances: - return self.db.espresso_angle_instances[angle_template.name] - angle_inst = self._create_espresso_angle_instance(angle_type=angle_template.angle_type, - angle_parameters=angle_template.get_parameters(self.units)) - self.db.espresso_angle_instances[angle_template.name] = angle_inst - espresso_system.bonded_inter.add(angle_inst) - return angle_inst + if isinstance(self.simulation_engine,EspressoSimulation): + angle_inst=self.simulation_engine._get_angle_instance(angle_template=angle_template) + return angle_inst + elif isinstance(self.simulation_engine,LammpsSimulation): + raise NotImplementedError('It has not yet been implemented for Lammps') + else: + raise RuntimeError('You have not set up any simulation engine yet') def _create_espresso_angle_instance(self, angle_type, angle_parameters): """ @@ -1753,19 +1712,9 @@ def _create_espresso_angle_instance(self, angle_type, angle_parameters): Returns: ('espressomd.interactions.BondedInteraction'): The ESPResSo angle interaction object. """ - from espressomd import interactions - - k = angle_parameters["k"].m_as("reduced_energy") - phi_0 = float(angle_parameters["phi_0"].magnitude) - - if angle_type == "harmonic": - return interactions.AngleHarmonic(bend=k, phi0=phi_0) - elif angle_type == "cosine": - return interactions.AngleCosine(bend=k, phi0=phi_0) - elif angle_type == "harmonic_cosine": - return interactions.AngleCossquare(bend=k, phi0=phi_0) + self.simulation_engine._create_angle_instance(angle_type, angle_parameters) - def _generate_angles_for_entity(self, espresso_system, entity_id, entity_id_col): + def _generate_angles_for_entity(self, entity_id, entity_id_col): """ Auto-generates angles from bond topology for an entity (molecule or residue). @@ -1773,7 +1722,6 @@ def _generate_angles_for_entity(self, espresso_system, entity_id, entity_id_col) this method finds all neighbor pairs and applies any matching angle potential. Args: - espresso_system ('espressomd.system.System'): ESPResSo system. entity_id ('int'): The molecule_id or residue_id to generate angles for. entity_id_col ('str'): Either "molecule_id" or "residue_id". """ @@ -1808,7 +1756,6 @@ def _generate_angles_for_entity(self, espresso_system, entity_id, entity_id_col) self.create_angular_potential(particle_id1=i, particle_id2=j, particle_id3=k, - espresso_system=espresso_system, use_default_angle=True) except ValueError: # No angle template defined for this triplet — skip @@ -2098,7 +2045,8 @@ def define_residue(self, name, central_bead, side_chains): side_chains=side_chains) self.db._register_template(tpl) - def delete_instances_in_system(self, instance_id, pmb_type, espresso_system): + + def delete_instances_in_system(self, instance_id, pmb_type): """ Deletes the instance with instance_id from the ESPResSo system. Related assembly, molecule, residue, particles and bond instances will also be deleted from the pyMBE dataframe. @@ -2124,8 +2072,7 @@ def delete_instances_in_system(self, instance_id, pmb_type, espresso_system): particle_ids = self.db._find_instance_ids_by_attribute(pmb_type="particle", attribute=instance_identifier, value=instance_id) - self._delete_particles_from_espresso(particle_ids=particle_ids, - espresso_system=espresso_system) + self._delete_particles_from_engine(particle_ids=particle_ids) self.db.delete_instance(pmb_type=pmb_type, instance_id=instance_id) @@ -2166,63 +2113,10 @@ def determine_reservoir_concentrations(self, pH_res, c_salt_res, activity_coeffi Landsgesell (PhD thesis, Sec. 5.3, doi:10.18419/opus-10831), adapted from the original code (doi:10.18419/darus-2237). """ - def determine_reservoir_concentrations_selfconsistently(cH_res, c_salt_res): - """ - Iteratively determines reservoir ion concentrations self-consistently. - - Args: - cH_res ('pint.Quantity'): - Current estimate of the H⁺ concentration. - c_salt_res ('pint.Quantity'): - Concentration of monovalent salt in the reservoir. - - Returns: - 'tuple': - (cH_res, cOH_res, cNa_res, cCl_res) - """ - # Initial ideal estimate - cOH_res = self.Kw / cH_res - if cOH_res >= cH_res: - cNa_res = c_salt_res + (cOH_res - cH_res) - cCl_res = c_salt_res - else: - cCl_res = c_salt_res + (cH_res - cOH_res) - cNa_res = c_salt_res - # Self-consistent iteration - for _ in range(max_number_sc_runs): - ionic_strength_res = 0.5 * (cNa_res + cCl_res + cOH_res + cH_res) - cOH_new = self.Kw / (cH_res * activity_coefficient_monovalent_pair(ionic_strength_res)) - if cOH_new >= cH_res: - cNa_new = c_salt_res + (cOH_new - cH_res) - cCl_new = c_salt_res - else: - cCl_new = c_salt_res + (cH_res - cOH_new) - cNa_new = c_salt_res - # Update values - cOH_res = cOH_new - cNa_res = cNa_new - cCl_res = cCl_new - return cH_res, cOH_res, cNa_res, cCl_res - # Initial guess for H+ concentration from target pH - cH_res = 10 ** (-pH_res) * self.units.mol / self.units.l - # First self-consistent solve - cH_res, cOH_res, cNa_res, cCl_res = (determine_reservoir_concentrations_selfconsistently(cH_res, - c_salt_res)) - ionic_strength_res = 0.5 * (cNa_res + cCl_res + cOH_res + cH_res) - determined_pH = -np.log10(cH_res.to("mol/L").magnitude* np.sqrt(activity_coefficient_monovalent_pair(ionic_strength_res))) - # Outer loop to enforce target pH - while abs(determined_pH - pH_res) > 1e-6: - if determined_pH > pH_res: - cH_res *= 1.005 - else: - cH_res /= 1.003 - cH_res, cOH_res, cNa_res, cCl_res = (determine_reservoir_concentrations_selfconsistently(cH_res, - c_salt_res)) - ionic_strength_res = 0.5 * (cNa_res + cCl_res + cOH_res + cH_res) - determined_pH = -np.log10(cH_res.to("mol/L").magnitude * np.sqrt(activity_coefficient_monovalent_pair(ionic_strength_res))) + cH_res, cOH_res, cNa_res, cCl_res = self.simulation_engine.determine_reservoir_concentrations( pH_res, c_salt_res, activity_coefficient_monovalent_pair, max_number_sc_runs) return cH_res, cOH_res, cNa_res, cCl_res - def enable_motion_of_rigid_object(self, instance_id, pmb_type, espresso_system): + def enable_motion_of_rigid_object(self, instance_id, pmb_type): """ Enables translational and rotational motion of a rigid pyMBE object instance in an ESPResSo system.This method creates a rigid-body center particle at the center of mass of @@ -2238,9 +2132,6 @@ def enable_motion_of_rigid_object(self, instance_id, pmb_type, espresso_system): pyMBE object type of the instance (e.g. '"molecule"', '"peptide"', '"protein"', or any assembly-like type). - espresso_system ('espressomd.system.System'): - ESPResSo system in which the rigid object is defined. - Notess: - This method requires ESPResSo to be compiled with the following features enabled: @@ -2252,25 +2143,12 @@ def enable_motion_of_rigid_object(self, instance_id, pmb_type, espresso_system): - The rotational inertia tensor is approximated from the squared distances of the particles to the center of mass. """ - logging.info('enable_motion_of_rigid_object requires that espressomd has the following features activated: ["VIRTUAL_SITES_RELATIVE", "MASS"]') - inst = self.db.get_instance(pmb_type=pmb_type, - instance_id=instance_id) - label = self._get_label_id_map(pmb_type=pmb_type) - particle_ids_list = self.get_particle_id_map(object_name=inst.name)[label][instance_id] - center_of_mass = self.calculate_center_of_mass (instance_id=instance_id, - espresso_system=espresso_system, - pmb_type=pmb_type) - rigid_object_center = espresso_system.part.add(pos=center_of_mass, - rotation=[True,True,True], - type=self.propose_unused_type()) - rigid_object_center.mass = len(particle_ids_list) - momI = 0 - for pid in particle_ids_list: - momI += np.power(np.linalg.norm(center_of_mass - espresso_system.part.by_id(pid).pos), 2) - rigid_object_center.rinertia = np.ones(3) * momI - for particle_id in particle_ids_list: - pid = espresso_system.part.by_id(particle_id) - pid.vs_auto_relate_to(rigid_object_center.id) + if isinstance(self.simulation_engine,EspressoSimulation): + self.simulation_engine.enable_motion_of_rigid_object(instance_id, pmb_type) + elif isinstance(self.simulation_engine,LammpsSimulation): + raise NotImplementedError('In this current version LammpsSimulation is not yet implemented') + else: + raise RuntimeError('Please setup a currently working simulation engine') def generate_coordinates_outside_sphere(self, center, radius, max_dist, n_samples): """ @@ -2488,26 +2366,8 @@ def get_lj_parameters(self, particle_name1, particle_name2, combining_rule='Lore - Currently, the only 'combining_rule' supported is Lorentz-Berthelot. - If the sigma value of 'particle_name1' or 'particle_name2' is 0, the function will return an empty dictionary. No LJ interactions are set up for particles with sigma = 0. """ - supported_combining_rules=["Lorentz-Berthelot"] - if combining_rule not in supported_combining_rules: - raise ValueError(f"Combining_rule {combining_rule} currently not implemented in pyMBE, valid keys are {supported_combining_rules}") - part_tpl1 = self.db.get_template(name=particle_name1, - pmb_type="particle") - part_tpl2 = self.db.get_template(name=particle_name2, - pmb_type="particle") - lj_parameters1 = part_tpl1.get_lj_parameters(ureg=self.units) - lj_parameters2 = part_tpl2.get_lj_parameters(ureg=self.units) - - # If one of the particle has sigma=0, no LJ interations are set up between that particle type and the others - if part_tpl1.sigma.magnitude == 0 or part_tpl2.sigma.magnitude == 0: - return {} - # Apply combining rule - if combining_rule == 'Lorentz-Berthelot': - sigma=(lj_parameters1["sigma"]+lj_parameters2["sigma"])/2 - cutoff=(lj_parameters1["cutoff"]+lj_parameters2["cutoff"])/2 - offset=(lj_parameters1["offset"]+lj_parameters2["offset"])/2 - epsilon=np.sqrt(lj_parameters1["epsilon"]*lj_parameters2["epsilon"]) - return {"sigma": sigma, "cutoff": cutoff, "offset": offset, "epsilon": epsilon} + lj_parameters=self.db.get_lj_parameters(particle_name1=particle_name1,particle_name2=particle_name2,combining_rule=combining_rule) + return lj_parameters def get_particle_id_map(self, object_name): """ @@ -2578,17 +2438,7 @@ def get_radius_map(self, dimensionless=True): Notes: - The radius corresponds to (sigma+offset)/2 """ - if "particle" not in self.db._templates: - return {} - result = {} - for _, tpl in self.db._templates["particle"].items(): - radius = (tpl.sigma.to_quantity(self.units) + tpl.offset.to_quantity(self.units))/2.0 - if dimensionless: - magnitude_reduced_length = radius.m_as("reduced_length") - radius = magnitude_reduced_length - for state in self.db.get_particle_states_templates(particle_name=tpl.name).values(): - result[state.es_type] = radius - return result + return self.db.get_radius_map(dimensionless) def get_reactions_df(self): """ @@ -2730,15 +2580,8 @@ def propose_unused_type(self): ('int'): The next available integer ESPResSo type. Returns ''0'' if no integer types are currently defined. """ - type_map = self.get_type_map() - # Flatten all es_type values across all particles and states - all_types = [] - for es_type in type_map.values(): - all_types.append(es_type) - # If no es_types exist, start at 0 - if not all_types: - return 0 - return max(all_types) + 1 + + return self.db.propose_unused_type() def read_protein_vtf(self, filename, unit_length=None): """ @@ -2914,6 +2757,36 @@ def set_reduced_units(self, unit_length=None, unit_charge=None, temperature=None self.units.define(f'reduced_charge = {unit_charge}') logging.info(self.get_reduced_units()) + def set_simulation_engine(self,simulation_engine,box_l=None): + """ + Sets the instance attribute simulation_engine to an instance of a class of type SimulationEngine. + + Args: + simulation_engine (Any): object which contains the methods to setup molecular dynamics and montecarlo simulations + box_l('list[float,float,float]'): list of floats with the dimensions of the box + + Raises: + ValueError: _description_ + """ + if isinstance(simulation_engine,EspressoSystemProtocolversion422) or isinstance(simulation_engine,EspressoSystemProtocolversion501): + self.simulation_engine=EspressoSimulation(box_l=simulation_engine.box_l, + db=self.db, + espresso_system=simulation_engine, + units=self.units, + kT=self.kT, + Kw=self.Kw, + seed=self.seed) + elif isinstance(simulation_engine,LammpsProtocol): + self.simulation_engine=LammpsSimulation(box_l=box_l, + db=self.db, + lammps=simulation_engine, + units=self.units, + kT=self.kT, + Kw=self.Kw, + seed=self.seed) + else: + raise ValueError('The specified simulation engine is not implemented yet') + def setup_cpH (self, counter_ion, constant_pH, exclusion_range=None, use_exclusion_radius_per_type = False): """ Sets up the Acid/Base reactions for acidic/basic particles defined in the pyMBE database @@ -2936,49 +2809,12 @@ def setup_cpH (self, counter_ion, constant_pH, exclusion_range=None, use_exclusi ('reaction_methods.ConstantpHEnsemble'): Instance of a reaction_methods.ConstantpHEnsemble object from the espressomd library. """ - from espressomd import reaction_methods - if exclusion_range is None: - exclusion_range = max(self.get_radius_map().values())*2.0 - if use_exclusion_radius_per_type: - exclusion_radius_per_type = self.get_radius_map() - else: - exclusion_radius_per_type = {} - RE = reaction_methods.ConstantpHEnsemble(kT=self.kT.to('reduced_energy').magnitude, - exclusion_range=exclusion_range, - seed=self.seed, - constant_pH=constant_pH, - exclusion_radius_per_type = exclusion_radius_per_type) - conterion_tpl = self.db.get_template(name=counter_ion, - pmb_type="particle") - conterion_state = self.db.get_template(name=conterion_tpl.initial_state, - pmb_type="particle_state") - for reaction in self.db.get_reactions(): - if reaction.reaction_type not in ["monoprotic_acid", "monoprotic_base"]: - continue - default_charges = {} - reactant_types = [] - product_types = [] - for participant in reaction.participants: - state_tpl = self.db.get_template(name=participant.state_name, - pmb_type="particle_state") - default_charges[state_tpl.es_type] = state_tpl.z - if participant.coefficient < 0: - reactant_types.append(state_tpl.es_type) - elif participant.coefficient > 0: - product_types.append(state_tpl.es_type) - # Add counterion to the products - if conterion_state.es_type not in product_types: - product_types.append(conterion_state.es_type) - default_charges[conterion_state.es_type] = conterion_state.z - reaction.add_participant(particle_name=counter_ion, - state_name=conterion_tpl.initial_state, - coefficient=1) - gamma=10**-reaction.pK - RE.add_reaction(gamma=gamma, - reactant_types=reactant_types, - product_types=product_types, - default_charges=default_charges) - reaction.add_simulation_method(simulation_method="cpH") + + + RE = self.simulation_engine.setup_cpH(counter_ion=counter_ion, + constant_pH=constant_pH, + exclusion_range=exclusion_range, + use_exclusion_radius_per_type = use_exclusion_radius_per_type) return RE def setup_gcmc(self, c_salt_res, salt_cation_name, salt_anion_name, activity_coefficient, exclusion_range=None, use_exclusion_radius_per_type = False): @@ -3009,61 +2845,22 @@ def setup_gcmc(self, c_salt_res, salt_cation_name, salt_anion_name, activity_coe ('reaction_methods.ReactionEnsemble'): Instance of a reaction_methods.ReactionEnsemble object from the espressomd library. """ - from espressomd import reaction_methods - if exclusion_range is None: - exclusion_range = max(self.get_radius_map().values())*2.0 - if use_exclusion_radius_per_type: - exclusion_radius_per_type = self.get_radius_map() - else: - exclusion_radius_per_type = {} - RE = reaction_methods.ReactionEnsemble(kT=self.kT.to('reduced_energy').magnitude, - exclusion_range=exclusion_range, - seed=self.seed, - exclusion_radius_per_type = exclusion_radius_per_type) - # Determine the concentrations of the various species in the reservoir and the equilibrium constants - determined_activity_coefficient = activity_coefficient(c_salt_res) - K_salt = (c_salt_res.to('1/(N_A * reduced_length**3)')**2) * determined_activity_coefficient - cation_tpl = self.db.get_template(pmb_type="particle", - name=salt_cation_name) - cation_state = self.db.get_template(pmb_type="particle_state", - name=cation_tpl.initial_state) - anion_tpl = self.db.get_template(pmb_type="particle", - name=salt_anion_name) - anion_state = self.db.get_template(pmb_type="particle_state", - name=anion_tpl.initial_state) - salt_cation_es_type = cation_state.es_type - salt_anion_es_type = anion_state.es_type - salt_cation_charge = cation_state.z - salt_anion_charge = anion_state.z - if salt_cation_charge <= 0: - raise ValueError('ERROR salt cation charge must be positive, charge ', salt_cation_charge) - if salt_anion_charge >= 0: - raise ValueError('ERROR salt anion charge must be negative, charge ', salt_anion_charge) - # Grand-canonical coupling to the reservoir - RE.add_reaction(gamma = K_salt.magnitude, - reactant_types = [], - reactant_coefficients = [], - product_types = [ salt_cation_es_type, salt_anion_es_type ], - product_coefficients = [ 1, 1 ], - default_charges = {salt_cation_es_type: salt_cation_charge, - salt_anion_es_type: salt_anion_charge}) - rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=salt_cation_name, - state_name=cation_state.name, - coefficient=1), - ReactionParticipant(particle_name=salt_anion_name, - state_name=anion_state.name, - coefficient=1)], - pK=-np.log10(K_salt.magnitude), - reaction_type="ion_insertion", - simulation_method="GCMC") - self.db._register_reaction(rx_tpl) + + RE = self.simulation_engine.setup_gcmc(c_salt_res=c_salt_res, + salt_anion_name=salt_anion_name, + salt_cation_name=salt_cation_name, + activity_coefficient=activity_coefficient, + exclusion_range=exclusion_range, + use_exclusion_radius_per_type = use_exclusion_radius_per_type) return RE + def setup_grxmc_reactions(self, pH_res, c_salt_res, proton_name, hydroxide_name, salt_cation_name, salt_anion_name, activity_coefficient, exclusion_range=None, use_exclusion_radius_per_type = False): """ Sets up acid/base reactions for acidic/basic monoprotic particles defined in the pyMBE database, as well as a grand-canonical coupling to a reservoir of small ions. + Args: pH_res ('float'): pH-value in the reservoir. @@ -3093,7 +2890,8 @@ def setup_grxmc_reactions(self, pH_res, c_salt_res, proton_name, hydroxide_name, Controls if one exclusion_radius for each espresso_type is used. Defaults to 'False'. Returns: - 'tuple(reaction_methods.ReactionEnsemble,pint.Quantity)': + For the Espresso system class: + Output ('tuple(reaction_methods.ReactionEnsemble,pint.Quantity)'): 'reaction_methods.ReactionEnsemble': espressomd reaction_methods object with all reactions necesary to run the GRxMC ensamble. @@ -3106,259 +2904,19 @@ def setup_grxmc_reactions(self, pH_res, c_salt_res, proton_name, hydroxide_name, [1] Landsgesell, J., Hebbeker, P., Rud, O., Lunkad, R., Košovan, P., & Holm, C. (2020). Grand-reaction method for simulations of ionization equilibria coupled to ion partitioning. Macromolecules, 53(8), 3007-3020. """ - from espressomd import reaction_methods - if exclusion_range is None: - exclusion_range = max(self.get_radius_map().values())*2.0 - if use_exclusion_radius_per_type: - exclusion_radius_per_type = self.get_radius_map() - else: - exclusion_radius_per_type = {} - RE = reaction_methods.ReactionEnsemble(kT=self.kT.to('reduced_energy').magnitude, - exclusion_range=exclusion_range, - seed=self.seed, - exclusion_radius_per_type = exclusion_radius_per_type) - # Determine the concentrations of the various species in the reservoir and the equilibrium constants - cH_res, cOH_res, cNa_res, cCl_res = self.determine_reservoir_concentrations(pH_res, c_salt_res, activity_coefficient) - ionic_strength_res = 0.5*(cNa_res+cCl_res+cOH_res+cH_res) - determined_activity_coefficient = activity_coefficient(ionic_strength_res) - K_W = cH_res.to('1/(N_A * reduced_length**3)') * cOH_res.to('1/(N_A * reduced_length**3)') * determined_activity_coefficient - K_NACL = cNa_res.to('1/(N_A * reduced_length**3)') * cCl_res.to('1/(N_A * reduced_length**3)') * determined_activity_coefficient - K_HCL = cH_res.to('1/(N_A * reduced_length**3)') * cCl_res.to('1/(N_A * reduced_length**3)') * determined_activity_coefficient - cation_tpl = self.db.get_template(pmb_type="particle", - name=salt_cation_name) - cation_state = self.db.get_template(pmb_type="particle_state", - name=cation_tpl.initial_state) - anion_tpl = self.db.get_template(pmb_type="particle", - name=salt_anion_name) - anion_state = self.db.get_template(pmb_type="particle_state", - name=anion_tpl.initial_state) - proton_tpl = self.db.get_template(pmb_type="particle", - name=proton_name) - proton_state = self.db.get_template(pmb_type="particle_state", - name=proton_tpl.initial_state) - hydroxide_tpl = self.db.get_template(pmb_type="particle", - name=hydroxide_name) - hydroxide_state = self.db.get_template(pmb_type="particle_state", - name=hydroxide_tpl.initial_state) - proton_es_type = proton_state.es_type - hydroxide_es_type = hydroxide_state.es_type - salt_cation_es_type = cation_state.es_type - salt_anion_es_type = anion_state.es_type - proton_charge = proton_state.z - hydroxide_charge = hydroxide_state.z - salt_cation_charge = cation_state.z - salt_anion_charge = anion_state.z - if proton_charge <= 0: - raise ValueError('ERROR proton charge must be positive, charge ', proton_charge) - if salt_cation_charge <= 0: - raise ValueError('ERROR salt cation charge must be positive, charge ', salt_cation_charge) - if hydroxide_charge >= 0: - raise ValueError('ERROR hydroxide charge must be negative, charge ', hydroxide_charge) - if salt_anion_charge >= 0: - raise ValueError('ERROR salt anion charge must be negative, charge ', salt_anion_charge) - # Grand-canonical coupling to the reservoir - # 0 = H+ + OH- - RE.add_reaction(gamma = K_W.magnitude, - reactant_types = [], - reactant_coefficients = [], - product_types = [ proton_es_type, hydroxide_es_type ], - product_coefficients = [ 1, 1 ], - default_charges = {proton_es_type: proton_charge, - hydroxide_es_type: hydroxide_charge}) - rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=proton_name, - state_name=proton_state.name, - coefficient=1), - ReactionParticipant(particle_name=hydroxide_name, - state_name=hydroxide_state.name, - coefficient=1)], - pK=-np.log10(K_W.magnitude), - reaction_type="ion_insertion", - simulation_method="GRxMC") - self.db._register_reaction(rx_tpl) - # 0 = Na+ + Cl- - RE.add_reaction(gamma = K_NACL.magnitude, - reactant_types = [], - reactant_coefficients = [], - product_types = [ salt_cation_es_type, salt_anion_es_type ], - product_coefficients = [ 1, 1 ], - default_charges = {salt_cation_es_type: salt_cation_charge, - salt_anion_es_type: salt_anion_charge}) - rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=salt_cation_name, - state_name=cation_state.name, - coefficient=1), - ReactionParticipant(particle_name=salt_anion_name, - state_name=anion_state.name, - coefficient=1)], - pK=-np.log10(K_NACL.magnitude), - reaction_type="ion_insertion", - simulation_method="GRxMC") - self.db._register_reaction(rx_tpl) - # 0 = Na+ + OH- - RE.add_reaction(gamma = (K_NACL * K_W / K_HCL).magnitude, - reactant_types = [], - reactant_coefficients = [], - product_types = [ salt_cation_es_type, hydroxide_es_type ], - product_coefficients = [ 1, 1 ], - default_charges = {salt_cation_es_type: salt_cation_charge, - hydroxide_es_type: hydroxide_charge}) - rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=salt_cation_name, - state_name=cation_state.name, - coefficient=1), - ReactionParticipant(particle_name=hydroxide_name, - state_name=hydroxide_state.name, - coefficient=1)], - pK=-np.log10((K_NACL * K_W / K_HCL).magnitude), - reaction_type="ion_insertion", - simulation_method="GRxMC") - self.db._register_reaction(rx_tpl) - # 0 = H+ + Cl- - RE.add_reaction(gamma = K_HCL.magnitude, - reactant_types = [], - reactant_coefficients = [], - product_types = [ proton_es_type, salt_anion_es_type ], - product_coefficients = [ 1, 1 ], - default_charges = {proton_es_type: proton_charge, - salt_anion_es_type: salt_anion_charge}) - rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=proton_name, - state_name=proton_state.name, - coefficient=1), - ReactionParticipant(particle_name=salt_anion_name, - state_name=anion_state.name, - coefficient=1)], - pK=-np.log10(K_HCL.magnitude), - reaction_type="ion_insertion", - simulation_method="GRxMC") - self.db._register_reaction(rx_tpl) - # Annealing moves to ensure sufficient sampling - # Cation annealing H+ = Na+ - RE.add_reaction(gamma = (K_NACL / K_HCL).magnitude, - reactant_types = [proton_es_type], - reactant_coefficients = [ 1 ], - product_types = [ salt_cation_es_type ], - product_coefficients = [ 1 ], - default_charges = {proton_es_type: proton_charge, - salt_cation_es_type: salt_cation_charge}) - rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=proton_name, - state_name=proton_state.name, - coefficient=-1), - ReactionParticipant(particle_name=salt_cation_name, - state_name=cation_state.name, - coefficient=1)], - pK=-np.log10((K_NACL / K_HCL).magnitude), - reaction_type="particle replacement", - simulation_method="GRxMC") - self.db._register_reaction(rx_tpl) - # Anion annealing OH- = Cl- - RE.add_reaction(gamma = (K_HCL / K_W).magnitude, - reactant_types = [hydroxide_es_type], - reactant_coefficients = [ 1 ], - product_types = [ salt_anion_es_type ], - product_coefficients = [ 1 ], - default_charges = {hydroxide_es_type: hydroxide_charge, - salt_anion_es_type: salt_anion_charge}) - rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=hydroxide_name, - state_name=hydroxide_state.name, - coefficient=-1), - ReactionParticipant(particle_name=salt_anion_name, - state_name=anion_state.name, - coefficient=1)], - pK=-np.log10((K_HCL / K_W).magnitude), - reaction_type="particle replacement", - simulation_method="GRxMC") - self.db._register_reaction(rx_tpl) - for reaction in self.db.get_reactions(): - if reaction.reaction_type not in ["monoprotic_acid", "monoprotic_base"]: - continue - default_charges = {} - reactant_types = [] - product_types = [] - for participant in reaction.participants: - state_tpl = self.db.get_template(name=participant.state_name, - pmb_type="particle_state") - default_charges[state_tpl.es_type] = state_tpl.z - if participant.coefficient < 0: - reactant_types.append(state_tpl.es_type) - reactant_name=state_tpl.particle_name - reactant_state_name=state_tpl.name - elif participant.coefficient > 0: - product_types.append(state_tpl.es_type) - product_name=state_tpl.particle_name - product_state_name=state_tpl.name - - Ka = (10**-reaction.pK * self.units.mol/self.units.l).to('1/(N_A * reduced_length**3)') - # Reaction in terms of proton: HA = A + H+ - RE.add_reaction(gamma=Ka.magnitude, - reactant_types=reactant_types, - reactant_coefficients=[1], - product_types=product_types+[proton_es_type], - product_coefficients=[1, 1], - default_charges= default_charges | {proton_es_type: proton_charge}) - reaction.add_participant(particle_name=proton_name, - state_name=proton_state.name, - coefficient=1) - reaction.add_simulation_method("GRxMC") - # Reaction in terms of salt cation: HA = A + Na+ - RE.add_reaction(gamma=(Ka * K_NACL / K_HCL).magnitude, - reactant_types=reactant_types, - reactant_coefficients=[1], - product_types=product_types+[salt_cation_es_type], - product_coefficients=[1, 1], - default_charges=default_charges | {salt_cation_es_type: salt_cation_charge}) - rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=reactant_name, - state_name=reactant_state_name, - coefficient=-1), - ReactionParticipant(particle_name=product_name, - state_name=product_state_name, - coefficient=1), - ReactionParticipant(particle_name=salt_cation_name, - state_name=cation_state.name, - coefficient=1),], - pK=-np.log10((Ka * K_NACL / K_HCL).magnitude), - reaction_type=reaction.reaction_type+"_salt", - simulation_method="GRxMC") - self.db._register_reaction(rx_tpl) - # Reaction in terms of hydroxide: OH- + HA = A - RE.add_reaction(gamma=(Ka / K_W).magnitude, - reactant_types=reactant_types+[hydroxide_es_type], - reactant_coefficients=[1, 1], - product_types=product_types, - product_coefficients=[1], - default_charges=default_charges | {hydroxide_es_type: hydroxide_charge}) - rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=reactant_name, - state_name=reactant_state_name, - coefficient=-1), - ReactionParticipant(particle_name=product_name, - state_name=product_state_name, - coefficient=1), - ReactionParticipant(particle_name=hydroxide_name, - state_name=hydroxide_state.name, - coefficient=-1),], - pK=-np.log10((Ka / K_W).magnitude), - reaction_type=reaction.reaction_type+"_conjugate", - simulation_method="GRxMC") - self.db._register_reaction(rx_tpl) - # Reaction in terms of salt anion: Cl- + HA = A - RE.add_reaction(gamma=(Ka / K_HCL).magnitude, - reactant_types=reactant_types+[salt_anion_es_type], - reactant_coefficients=[1, 1], - product_types=product_types, - product_coefficients=[1], - default_charges=default_charges | {salt_anion_es_type: salt_anion_charge}) - rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=reactant_name, - state_name=reactant_state_name, - coefficient=-1), - ReactionParticipant(particle_name=product_name, - state_name=product_state_name, - coefficient=1), - ReactionParticipant(particle_name=salt_anion_name, - state_name=anion_state.name, - coefficient=-1),], - pK=-np.log10((Ka / K_HCL).magnitude), - reaction_type=reaction.reaction_type+"_salt", - simulation_method="GRxMC") - self.db._register_reaction(rx_tpl) - return RE, ionic_strength_res - + + output=self.simulation_engine.setup_grxmc_reactions(pH_res=pH_res, + c_salt_res=c_salt_res, + proton_name=proton_name, + hydroxide_name=hydroxide_name, + salt_cation_name=salt_cation_name, + salt_anion_name=salt_anion_name, + activity_coefficient=activity_coefficient, + exclusion_range=exclusion_range, + use_exclusion_radius_per_type=use_exclusion_radius_per_type) + + return output + def setup_grxmc_unified(self, pH_res, c_salt_res, cation_name, anion_name, activity_coefficient, exclusion_range=None, use_exclusion_radius_per_type = False): """ Sets up acid/base reactions for acidic/basic 'particles' defined in the pyMBE database, as well as a grand-canonical coupling to a @@ -3387,7 +2945,8 @@ def setup_grxmc_unified(self, pH_res, c_salt_res, cation_name, anion_name, activ Controls if one exclusion_radius per each espresso_type. Defaults to 'False'. Returns: - 'tuple(reaction_methods.ReactionEnsemble,pint.Quantity)': + For the Espresso system class: + Output ('tuple(reaction_methods.ReactionEnsemble,pint.Quantity)'): 'reaction_methods.ReactionEnsemble': espressomd reaction_methods object with all reactions necesary to run the GRxMC ensamble. @@ -3402,120 +2961,23 @@ def setup_grxmc_unified(self, pH_res, c_salt_res, cation_name, anion_name, activ [1] Curk, T., Yuan, J., & Luijten, E. (2022). Accelerated simulation method for charge regulation effects. The Journal of Chemical Physics, 156(4). [2] Landsgesell, J., Hebbeker, P., Rud, O., Lunkad, R., Košovan, P., & Holm, C. (2020). Grand-reaction method for simulations of ionization equilibria coupled to ion partitioning. Macromolecules, 53(8), 3007-3020. """ - from espressomd import reaction_methods - if exclusion_range is None: - exclusion_range = max(self.get_radius_map().values())*2.0 - if use_exclusion_radius_per_type: - exclusion_radius_per_type = self.get_radius_map() - else: - exclusion_radius_per_type = {} - RE = reaction_methods.ReactionEnsemble(kT=self.kT.to('reduced_energy').magnitude, - exclusion_range=exclusion_range, - seed=self.seed, - exclusion_radius_per_type = exclusion_radius_per_type) - # Determine the concentrations of the various species in the reservoir and the equilibrium constants - cH_res, cOH_res, cNa_res, cCl_res = self.determine_reservoir_concentrations(pH_res, c_salt_res, activity_coefficient) - ionic_strength_res = 0.5*(cNa_res+cCl_res+cOH_res+cH_res) - determined_activity_coefficient = activity_coefficient(ionic_strength_res) - a_hydrogen = (10 ** (-pH_res) * self.units.mol/self.units.l).to('1/(N_A * reduced_length**3)') - a_cation = (cH_res+cNa_res).to('1/(N_A * reduced_length**3)') * np.sqrt(determined_activity_coefficient) - a_anion = (cH_res+cNa_res).to('1/(N_A * reduced_length**3)') * np.sqrt(determined_activity_coefficient) - K_XX = a_cation * a_anion - cation_tpl = self.db.get_template(pmb_type="particle", - name=cation_name) - cation_state = self.db.get_template(pmb_type="particle_state", - name=cation_tpl.initial_state) - anion_tpl = self.db.get_template(pmb_type="particle", - name=anion_name) - anion_state = self.db.get_template(pmb_type="particle_state", - name=anion_tpl.initial_state) - cation_es_type = cation_state.es_type - anion_es_type = anion_state.es_type - cation_charge = cation_state.z - anion_charge = anion_state.z - if cation_charge <= 0: - raise ValueError('ERROR cation charge must be positive, charge ', cation_charge) - if anion_charge >= 0: - raise ValueError('ERROR anion charge must be negative, charge ', anion_charge) - # Coupling to the reservoir: 0 = X+ + X- - RE.add_reaction(gamma = K_XX.magnitude, - reactant_types = [], - reactant_coefficients = [], - product_types = [ cation_es_type, anion_es_type ], - product_coefficients = [ 1, 1 ], - default_charges = {cation_es_type: cation_charge, - anion_es_type: anion_charge}) - rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=cation_name, - state_name=cation_state.name, - coefficient=1), - ReactionParticipant(particle_name=anion_name, - state_name=anion_state.name, - coefficient=1)], - pK=-np.log10(K_XX.magnitude), - reaction_type="ion_insertion", - simulation_method="GCMC") - self.db._register_reaction(rx_tpl) - for reaction in self.db.get_reactions(): - if reaction.reaction_type not in ["monoprotic_acid", "monoprotic_base"]: - continue - default_charges = {} - reactant_types = [] - product_types = [] - for participant in reaction.participants: - state_tpl = self.db.get_template(name=participant.state_name, - pmb_type="particle_state") - default_charges[state_tpl.es_type] = state_tpl.z - if participant.coefficient < 0: - reactant_types.append(state_tpl.es_type) - reactant_name=state_tpl.particle_name - reactant_state_name=state_tpl.name - elif participant.coefficient > 0: - product_types.append(state_tpl.es_type) - product_name=state_tpl.particle_name - product_state_name=state_tpl.name - - Ka = (10**-reaction.pK * self.units.mol/self.units.l).to('1/(N_A * reduced_length**3)') - gamma_K_AX = Ka.to('1/(N_A * reduced_length**3)').magnitude * a_cation / a_hydrogen - # Reaction in terms of small cation: HA = A + X+ - RE.add_reaction(gamma=gamma_K_AX.magnitude, - reactant_types=reactant_types, - reactant_coefficients=[1], - product_types=product_types+[cation_es_type], - product_coefficients=[1, 1], - default_charges=default_charges|{cation_es_type: cation_charge}) - reaction.add_participant(particle_name=cation_name, - state_name=cation_state.name, - coefficient=1) - reaction.add_simulation_method("GRxMC") - # Reaction in terms of small anion: X- + HA = A - RE.add_reaction(gamma=gamma_K_AX.magnitude / K_XX.magnitude, - reactant_types=reactant_types+[anion_es_type], - reactant_coefficients=[1, 1], - product_types=product_types, - product_coefficients=[1], - default_charges=default_charges|{anion_es_type: anion_charge}) - rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=reactant_name, - state_name=reactant_state_name, - coefficient=-1), - ReactionParticipant(particle_name=product_name, - state_name=product_state_name, - coefficient=1), - ReactionParticipant(particle_name=anion_name, - state_name=anion_state.name, - coefficient=-1),], - pK=-np.log10(gamma_K_AX.magnitude / K_XX.magnitude), - reaction_type=reaction.reaction_type+"_conjugate", - simulation_method="GRxMC") - self.db._register_reaction(rx_tpl) - return RE, ionic_strength_res - - def setup_lj_interactions(self, espresso_system, shift_potential=True, combining_rule='Lorentz-Berthelot'): + + output=self.simulation_engine.setup_grxmc_unified(pH_res=pH_res, + c_salt_res=c_salt_res, + cation_name=cation_name, + anion_name=anion_name, + activity_coefficient=activity_coefficient, + exclusion_range=exclusion_range, + use_exclusion_radius_per_type = use_exclusion_radius_per_type) + return output + + + + def setup_lj_interactions(self, shift_potential=True, combining_rule='Lorentz-Berthelot'): """ Sets up the Lennard-Jones (LJ) potential between all pairs of particle states defined in the pyMBE database. Args: - espresso_system('espressomd.system.System'): - Instance of a system object from the espressomd library. shift_potential('bool', optional): If True, a shift will be automatically computed such that the potential is continuous at the cutoff radius. Otherwise, no shift will be applied. Defaults to True. @@ -3531,50 +2993,5 @@ def setup_lj_interactions(self, espresso_system, shift_potential=True, combining - Check the documentation of ESPResSo for more info about the potential https://espressomd.github.io/doc4.2.0/inter_non-bonded.html """ - from itertools import combinations_with_replacement - particle_templates = self.db.get_templates("particle") - shift = "auto" if shift_potential else 0 - if shift == "auto": - shift_tpl = shift - else: - shift_tpl = PintQuantity.from_quantity(q=shift*self.units.reduced_length, - expected_dimension="length", - ureg=self.units) - # Get all particle states registered in pyMBE - state_entries = [] - for tpl in particle_templates.values(): - for state in self.db.get_particle_states_templates(particle_name=tpl.name).values(): - state_entries.append((tpl, state)) - - # Iterate over all unique state pairs - for (tpl1, state1), (tpl2, state2) in combinations_with_replacement(state_entries, 2): - - lj_parameters = self.get_lj_parameters(particle_name1=tpl1.name, - particle_name2=tpl2.name, - combining_rule=combining_rule) - if not lj_parameters: - continue - - espresso_system.non_bonded_inter[state1.es_type, state2.es_type].lennard_jones.set_params( - epsilon=lj_parameters["epsilon"].to("reduced_energy").magnitude, - sigma=lj_parameters["sigma"].to("reduced_length").magnitude, - cutoff=lj_parameters["cutoff"].to("reduced_length").magnitude, - offset=lj_parameters["offset"].to("reduced_length").magnitude, - shift=shift) - - lj_template = LJInteractionTemplate(state1=state1.name, - state2=state2.name, - sigma=PintQuantity.from_quantity(q=lj_parameters["sigma"], - expected_dimension="length", - ureg=self.units), - epsilon=PintQuantity.from_quantity(q=lj_parameters["epsilon"], - expected_dimension="energy", - ureg=self.units), - cutoff=PintQuantity.from_quantity(q=lj_parameters["cutoff"], - expected_dimension="length", - ureg=self.units), - offset=PintQuantity.from_quantity(q=lj_parameters["offset"], - expected_dimension="length", - ureg=self.units), - shift=shift_tpl) - self.db._register_template(lj_template) + self.simulation_engine.setup_lj_interactions(shift_potential=shift_potential, + combining_rule=combining_rule) diff --git a/pyMBE/simulation_builder/base_engine.py b/pyMBE/simulation_builder/base_engine.py new file mode 100644 index 00000000..5c596074 --- /dev/null +++ b/pyMBE/simulation_builder/base_engine.py @@ -0,0 +1,168 @@ +# +# Copyright (C) 2023-2026 pyMBE-dev team +# +# This file is part of pyMBE. +# +# pyMBE is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# pyMBE is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from abc import ABC,abstractmethod +import numpy as np + +class SimulationEngine(ABC): + """Base Class for simulation engines contains methods """ + def __init__(self): + pass + @abstractmethod + def _check_bond_inputs(self): + return + @abstractmethod + def _create_bond_instance(self): + return + @abstractmethod + def _get_bond_instance(self): + return + @abstractmethod + def add_instances_to_engine(self): + return + def calculate_center_of_mass(self, instance_id, pmb_type): + """ + Calculates the center of mass of a pyMBE object instance in an ESPResSo system. + + Args: + instance_id ('int'): + pyMBE instance ID of the object whose center of mass is calculated. + + pmb_type ('str'): + Type of the pyMBE object. Must correspond to a particle-aggregating + template type (e.g. '"molecule"', '"residue"', '"peptide"', '"protein"'). + + Returns: + ('numpy.ndarray'): + Array of shape '(3,)' containing the Cartesian coordinates of the + center of mass. + + Notes: + - This method assumes equal mass for all particles. + - Periodic boundary conditions are *not* unfolded; positions are taken + directly from ESPResSo particle coordinates. + """ + center_of_mass = np.zeros(3) + axis_list = [0,1,2] + inst = self.db.get_instance(pmb_type=pmb_type, + instance_id=instance_id) + particle_id_list = self.db.get_particle_id_map(object_name=inst.name)["all"] + for pid in particle_id_list: + for axis in axis_list: + center_of_mass[axis] += self.db.get_instance(pmb_type='particle', + instance_id=pid).position[axis] + center_of_mass = center_of_mass / len(particle_id_list) + return center_of_mass + def determine_reservoir_concentrations(self, pH_res, c_salt_res, activity_coefficient_monovalent_pair, max_number_sc_runs=200): + """ + Determines ionic concentrations in the reservoir at fixed pH and salt concentration. + + Args: + pH_res ('float'): + Target pH value in the reservoir. + + c_salt_res ('pint.Quantity'): + Concentration of monovalent salt (e.g., NaCl) in the reservoir. + + activity_coefficient_monovalent_pair ('callable'): + Function returning the activity coefficient of a monovalent ion pair + as a function of ionic strength: + 'gamma = activity_coefficient_monovalent_pair(I)'. + + max_number_sc_runs ('int', optional): + Maximum number of self-consistent iterations allowed before + convergence is enforced. Defaults to 200. + + Returns: + tuple: + (cH_res, cOH_res, cNa_res, cCl_res) + - cH_res ('pint.Quantity'): Concentration of H⁺ ions. + - cOH_res ('pint.Quantity'): Concentration of OH⁻ ions. + - cNa_res ('pint.Quantity'): Concentration of Na⁺ ions. + - cCl_res ('pint.Quantity'): Concentration of Cl⁻ ions. + + Notess: + - The algorithm enforces electroneutrality in the reservoir. + - Water autodissociation is included via the equilibrium constant 'Kw'. + - Non-ideal effects enter through activity coefficients depending on + ionic strength. + - The implementation follows the self-consistent scheme described in + Landsgesell (PhD thesis, Sec. 5.3, doi:10.18419/opus-10831), adapted + from the original code (doi:10.18419/darus-2237). + """ + def determine_reservoir_concentrations_selfconsistently(cH_res, c_salt_res): + """ + Iteratively determines reservoir ion concentrations self-consistently. + + Args: + cH_res ('pint.Quantity'): + Current estimate of the H⁺ concentration. + c_salt_res ('pint.Quantity'): + Concentration of monovalent salt in the reservoir. + + Returns: + 'tuple': + (cH_res, cOH_res, cNa_res, cCl_res) + """ + # Initial ideal estimate + cOH_res = self.Kw / cH_res + if cOH_res >= cH_res: + cNa_res = c_salt_res + (cOH_res - cH_res) + cCl_res = c_salt_res + else: + cCl_res = c_salt_res + (cH_res - cOH_res) + cNa_res = c_salt_res + # Self-consistent iteration + for _ in range(max_number_sc_runs): + ionic_strength_res = 0.5 * (cNa_res + cCl_res + cOH_res + cH_res) + cOH_new = self.Kw / (cH_res * activity_coefficient_monovalent_pair(ionic_strength_res)) + if cOH_new >= cH_res: + cNa_new = c_salt_res + (cOH_new - cH_res) + cCl_new = c_salt_res + else: + cCl_new = c_salt_res + (cH_res - cOH_new) + cNa_new = c_salt_res + # Update values + cOH_res = cOH_new + cNa_res = cNa_new + cCl_res = cCl_new + return cH_res, cOH_res, cNa_res, cCl_res + # Initial guess for H+ concentration from target pH + cH_res = 10 ** (-pH_res) * self.units.mol / self.units.l + # First self-consistent solve + cH_res, cOH_res, cNa_res, cCl_res = (determine_reservoir_concentrations_selfconsistently(cH_res, + c_salt_res)) + ionic_strength_res = 0.5 * (cNa_res + cCl_res + cOH_res + cH_res) + determined_pH = -np.log10(cH_res.to("mol/L").magnitude* np.sqrt(activity_coefficient_monovalent_pair(ionic_strength_res))) + # Outer loop to enforce target pH + while abs(determined_pH - pH_res) > 1e-6: + if determined_pH > pH_res: + cH_res *= 1.005 + else: + cH_res /= 1.003 + cH_res, cOH_res, cNa_res, cCl_res = (determine_reservoir_concentrations_selfconsistently(cH_res, + c_salt_res)) + ionic_strength_res = 0.5 * (cNa_res + cCl_res + cOH_res + cH_res) + determined_pH = -np.log10(cH_res.to("mol/L").magnitude * np.sqrt(activity_coefficient_monovalent_pair(ionic_strength_res))) + return cH_res, cOH_res, cNa_res, cCl_res + +class DummyEngine: + def __getattr__(self, attr): + if attr not in self.__dict__: + raise RuntimeError('You have not set up any simulation engine yet') + return super().__getattr__(attr) \ No newline at end of file diff --git a/pyMBE/simulation_builder/engine_protocol.py b/pyMBE/simulation_builder/engine_protocol.py new file mode 100644 index 00000000..2efa40b8 --- /dev/null +++ b/pyMBE/simulation_builder/engine_protocol.py @@ -0,0 +1,59 @@ +# +# Copyright (C) 2023-2026 pyMBE-dev team +# +# This file is part of pyMBE. +# +# pyMBE is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# pyMBE is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from typing import Protocol,runtime_checkable + +class EspressoParticleProtocol(Protocol): + """Class that emulates the estructure of the Espresso Particle class""" + def add(): + return + def by_id(): + return +class EspressoBondedInterProtocol(Protocol): + """Class that emulates the structure of the EspressoBondedInterProtocol""" + def add(): + return + +@runtime_checkable +class EspressoSystemProtocolversion422(Protocol): + """ Class that emulates the structure of the methods employed by Pymbe from the espressomd.System class + . The decorator @runtime_checkable allows to only check for the structure not the types""" + part: EspressoParticleProtocol + bonded_inter: EspressoBondedInterProtocol + + +@runtime_checkable +class EspressoSystemProtocolversion501(Protocol): + def change_volume_and_rescale_particles(self, d_new, dir="xyz"): + return + def volume(self): + return + def distance(self, p1, p2): + return + def distance_vec(self, p1, p2): + return + def velocity_difference(self, p1, p2): + return + def auto_exclusions(self, distance): + pass + +@runtime_checkable +class LammpsProtocol(Protocol): + """ Class that emulates the structure of the methods employed by Pymbe from the Lammps class + . The decorator @runtime_checkable allows to only check for the structure not the types""" + diff --git a/pyMBE/simulation_builder/espresso_engine.py b/pyMBE/simulation_builder/espresso_engine.py new file mode 100644 index 00000000..500c1e59 --- /dev/null +++ b/pyMBE/simulation_builder/espresso_engine.py @@ -0,0 +1,1473 @@ +# +# Copyright (C) 2023-2026 pyMBE-dev team +# +# This file is part of pyMBE. +# +# pyMBE is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# pyMBE is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import espressomd +import espressomd.electrostatics +import espressomd.version +import warnings +from typing import List +import numpy as np +import logging +from pyMBE.simulation_builder.base_engine import SimulationEngine +from pyMBE.storage.reactions.reaction import Reaction, ReactionParticipant +from pyMBE.storage.templates.lj import LJInteractionTemplate +from pyMBE.storage.pint_quantity import PintQuantity + + + + + + +class EspressoSimulation(SimulationEngine): + def __init__(self,box_l,db,espresso_system,units,kT,Kw,seed): + self.db=db + self.box_l: List[float]=box_l + self.espresso_system=espresso_system + self.units=units + self.kT=kT + self.Kw=Kw + self.seed=seed + pass + + def _add_angle(self,particle_id1,particle_id2,particle_id3, angle_inst): + """ helper function to add angle instances to espresso + + + Args: + particle_id1 (int): pid of the particle instance 1 that composes the angle + particle_id2 (int): pid of the particle instance 2 that composes the angle + particle_id3 (int): pid of the particle instance 3 that composes the angle + angle_inst (pmb.AngleInstance): dataclass containing information of an angle instance + """ + angle_tpl=self.db.get_template(name=angle_inst.name, + pmb_type="angle") + espresso_angle_inst=self._get_angle_instance(angle_template=angle_tpl) + + self.espresso_system.part.by_id(particle_id2).add_bond((espresso_angle_inst, particle_id1, particle_id3)) + self.db._update_instance(instance_id=angle_inst.angle_id, + pmb_type='angle', + attribute='added_to_engine', + value=True) + + def _add_bond(self,particle_id1,particle_id2,bond_inst): + """helper function to add bond instances to espresso + + Args: + particle_id1 (int): pid of the particle instance 1 that composes the bond + particle_id2 (int): pid of the particle instance 2 that composes the bond + bond_inst (pmb.BondInstance): dataclass containing information of a bond instance + """ + bond_tpl=self.db.get_template(name=bond_inst.name, + pmb_type="bond") + espresso_bond_inst=self._get_bond_instance(bond_template=bond_tpl) + self.espresso_system.part.by_id(particle_id1).add_bond((espresso_bond_inst, particle_id2)) + self.db._update_instance(instance_id=bond_inst.bond_id, + pmb_type='bond', + attribute='added_to_engine', + value=True) + + def _add_particle(self,particle_id): + """helper function to add particle instances to espresso + + Args: + particle_id (int): pid of the particle instance + """ + particle_instance=self.db.get_instance(pmb_type='particle', + instance_id=particle_id) + part_state = self.db.get_template(pmb_type="particle_state", + name=particle_instance.initial_state) + kwargs = dict(id=particle_id, + pos=particle_instance.position, + type=part_state.es_type, + q=part_state.z, + fix=particle_instance.fix) + self.espresso_system.part.add(**kwargs) + self.db._update_instance(instance_id=particle_id, + pmb_type='particle', + attribute='added_to_engine', + value=True) + + + def _check_particle_exists_in_espresso(self,particle_id): + """ + Checks the existance of a particle_id in a espresso_system instance. + + Args: + particle_id (int): pid of the particle that we want to check that exists within espresso + + Returns: + particle_exists(bool): result of the espresso_exists function + """ + particle_exists=self.espresso_system.exists(particle_id) + return particle_exists + + def _check_bond_inputs(self, bond_type, bond_parameters): + """ + Checks that the input bond parameters are valid within the current pyMBE implementation. + + Args: + bond_type ('str'): + label to identify the potential to model the bond. + + bond_parameters ('dict'): + parameters of the potential of the bond. + """ + valid_bond_types = ["harmonic", "FENE"] + if bond_type not in valid_bond_types: + raise NotImplementedError(f"Bond type '{bond_type}' currently not implemented in pyMBE, accepted types are {valid_bond_types}") + required_parameters = {"harmonic": ["r_0","k"], + "FENE": ["r_0","k","d_r_max"]} + for required_parameter in required_parameters[bond_type]: + if required_parameter not in bond_parameters.keys(): + raise ValueError(f"Missing required parameter {required_parameter} for {bond_type} bond") + + def _create_bond_instance(self, bond_type, bond_parameters): + """ + Creates an ESPResSo bond instance. + + Args: + bond_type ('str'): + label to identify the potential to model the bond. + + bond_parameters ('dict'): + parameters of the potential of the bond. + + Notes: + Currently, only HARMONIC and FENE bonds are supported. + + For a HARMONIC bond the dictionary must contain: + - k ('Pint.Quantity') : Magnitude of the bond. It should have units of energy/length**2 + using the 'pmb.units' UnitRegistry. + - r_0 ('Pint.Quantity') : Equilibrium bond length. It should have units of length using + the 'pmb.units' UnitRegistry. + + For a FENE bond the dictionary must additionally contain: + - d_r_max ('Pint.Quantity'): Maximal stretching length for FENE. It should have + units of length using the 'pmb.units' UnitRegistry. Default 'None'. + + Returns: + ('espressomd.interactions'): instance of an ESPResSo bond object + """ + + self._check_bond_inputs(bond_parameters=bond_parameters, + bond_type=bond_type) + if bond_type == 'harmonic': + bond_instance = espressomd.interactions.HarmonicBond(k = bond_parameters["k"].m_as("reduced_energy/reduced_length**2"), + r_0 = bond_parameters["r_0"].m_as("reduced_length")) + elif bond_type == 'FENE': + bond_instance = espressomd.interactions.FeneBond(k = bond_parameters["k"].m_as("reduced_energy/reduced_length**2"), + r_0 = bond_parameters["r_0"].m_as("reduced_length"), + d_r_max = bond_parameters["d_r_max"].m_as("reduced_length")) + return bond_instance + + def _delete_particles(self, particle_ids): + """ + Remove a list of particles from an ESPResSo simulation system. + + Args: + particle_ids ('Iterable[int]'): + A list (or other iterable) of ESPResSo particle IDs to remove. + + + Notess: + - This method removes particles only from the ESPResSo simulation, + **not** from the pyMBE database. Database cleanup must be handled + separately by the caller. + - Attempting to remove a non-existent particle ID will raise + an ESPResSo error. + """ + for pid in particle_ids: + self.espresso_system.part.by_id(pid).remove() + self.db._update_instance(instance_id=pid, + pmb_type='particle', + attribute='added_to_engine', + value=False) + + def _get_bond_instance(self, bond_template): + """ + Retrieve or create a bond instance in an ESPResSo system for a given pair of particle names. + + Args: + bond_template ('BondTemplate'): + BondTemplate object from the pyMBE database. + + Returns: + ('espressomd.interactions.BondedInteraction'): + The ESPResSo bond instance object. + + Notes: + When a new bond instance is created, it is not added to the ESPResSo system. + """ + if bond_template.name in self.db.espresso_bond_instances.keys(): + bond_inst = self.db.espresso_bond_instances[bond_template.name] + else: + # Create an instance of the bond + bond_inst = self._create_bond_instance(bond_type=bond_template.bond_type, + bond_parameters=bond_template.get_parameters(self.units)) + self.db.espresso_bond_instances[bond_template.name]= bond_inst + self.espresso_system.bonded_inter.add(bond_inst) + return bond_inst + + def _get_angle_instance(self,angle_template): + """ + Retrieve or create an angle interaction in an ESPResSo system for a given angle template. + + Args: + angle_template ('AngleTemplate'): The angle template to use. + + Returns: + ('espressomd.interactions.BondedInteraction'): The ESPResSo angle interaction object. + """ + if angle_template.name in self.db.espresso_angle_instances: + return self.db.espresso_angle_instances[angle_template.name] + + angle_inst = self._create_angle_instance(angle_type=angle_template.angle_type, + angle_parameters=angle_template.get_parameters(self.units)) + self.db.espresso_angle_instances[angle_template.name] = angle_inst + self.espresso_system.bonded_inter.add(angle_inst) + + return angle_inst + + def _create_angle_instance(self, angle_type, angle_parameters): + """ + Creates an ESPResSo angle interaction object. + + Args: + angle_type ('str'): Type of angle potential ("harmonic", "cosine", "harmonic_cosine"). + angle_parameters ('dict'): Parameters of the angle potential (k, phi_0). + + Returns: + ('espressomd.interactions.BondedInteraction'): The ESPResSo angle interaction object. + """ + from espressomd import interactions + + k = angle_parameters["k"].m_as("reduced_energy") + phi_0 = float(angle_parameters["phi_0"].magnitude) + + if angle_type == "harmonic": + return interactions.AngleHarmonic(bend=k, phi0=phi_0) + elif angle_type == "cosine": + return interactions.AngleCosine(bend=k, phi0=phi_0) + elif angle_type == "harmonic_cosine": + return interactions.AngleCossquare(bend=k, phi0=phi_0) + + + def _get_particle_ids_in_espresso(self): + """ + Gets a list of all the particles_id in espresso_system instance. + + Returns: + espresso_particles_id(list): list of pids of the particles that are saved in espresso + """ + espresso_particles=self.espresso_system.part.all() + return espresso_particles.id + + + def _get_particle_pos_espresso(self,id): + """ + Gets the particle position of + Args: + id (int): pid of the particle that we want to check that exists within espresso + + Returns: + espresso_particles_id(List[List[float,float,float]]): returns a nested list of floats containing x,y,z coordinates for each particle in espresso + """ + return self.espresso_system.part.by_id(id).pos + + def get_box_side_length(self): + """_summary_ + + Returns: + box_l(list[float,float,float]): return a list of floats regarding the dimensions of the box + """ + return self.box_l + + def calculate_net_charge(self,object_name,pmb_type,dimensionless=False): + """ + Calculates the net charge per instance of a given pmb object type. + + Args: + object_name (str): + Name of the object (e.g. molecule, residue, peptide, protein). + pmb_type (str): + Type of object to analyze. Must be molecule-like. + dimensionless (bool, optional): + If True, return charge as a pure number. + If False, return a quantity with reduced_charge units. + + Returns: + dict: + {"mean": mean_net_charge, "instances": {instance_id: net_charge}} + """ + id_map = self.db.get_particle_id_map(object_name=object_name) + label = self.db._get_label_id_map(pmb_type=pmb_type) + instance_map = id_map[label] + charges = {} + for instance_id, particle_ids in instance_map.items(): + if dimensionless: + net_charge = 0.0 + else: + net_charge = 0 * self.units.Quantity(1, "reduced_charge") + for pid in particle_ids: + q = self.espresso_system.part.by_id(pid).q + if not dimensionless: + q *= self.units.Quantity(1, "reduced_charge") + net_charge += q + charges[instance_id] = net_charge + # Mean charge + if dimensionless: + mean_charge = float(np.mean(list(charges.values()))) + else: + mean_charge = (np.mean([q.magnitude for q in charges.values()])* self.units.Quantity(1, "reduced_charge")) + return {"mean": mean_charge, "instances": charges} + + def change_volume_and_rescale_particles(self, d_new, dir="xyz"): + """ + Change the volume for a particular dimension into the espresso system. + args: + d_new(float): new value for the dimension + dir(Literal[x,y,z]): coordinate in which to set the new dimension. + + """ + rescale_factor=np.array([1,1,1]) + if d_new<=0: + raise ValueError("The dimension cannot be negative, neither 0") + if "x" in dir: + rescale_factor[0]=d_new/self.box_l[0] + if "y" in dir: + rescale_factor[1]=d_new/self.box_l[1] + if "z" in dir: + rescale_factor[2]=d_new/self.box_l[2] + + instances=self.db._get_instances_df(pmb_type='particle') + for pid in range(instances.index.size): + es_pos=self.db.get_instance(instance_id=pid, + pmb_type='particle').position + rescaled_position=es_pos*rescale_factor + self.db._update_instance(instance_id=pid, + pmb_type='particle', + attribute='position', + value=rescaled_position) + + self.espresso_system.change_volume_and_rescale_particles(d_new=d_new, + dir=dir) + return + + + def do_reaction(self,algorithm, steps): + """ + Executes reaction steps using an ESPResSo reaction algorithm with + version-compatible calling semantics. + + This function wraps the `reaction` method of an ESPResSo reaction + algorithm to account for differences in the method signature between + ESPResSo versions. + + Args: + algorithm ('espressomd.reaction_methods'): + ESPResSo reaction algorithm object (e.g. constant pH, + reaction ensemble, or similar). + steps ('int'): + Number of reaction steps to perform. + + Notes: + - In ESPResSo 4.2, the `reaction` method expects the number of steps + to be passed as the keyword argument `reaction_steps`. + - In newer ESPResSo versions, the keyword argument is `steps`. + - This helper function provides a stable interface across ESPResSo + versions by dispatching to the appropriate keyword internally. + """ + import espressomd.version + if espressomd.version.friendly() == '4.2': + algorithm.reaction(reaction_steps=steps) + else: + algorithm.reaction(steps=steps) + + def enable_motion_of_rigid_object(self, instance_id, pmb_type): + """ + Enables translational and rotational motion of a rigid pyMBE object instance + in an ESPResSo system.This method creates a rigid-body center particle at the center of mass of + the specified pyMBE object and attaches all constituent particles to it + using ESPResSo virtual sites. The resulting rigid object can translate and + rotate as a single body. + + Args: + instance_id ('int'): + Instance ID of the pyMBE object whose rigid-body motion is enabled. + + pmb_type ('str'): + pyMBE object type of the instance (e.g. '"molecule"', '"peptide"', + '"protein"', or any assembly-like type). + + Notess: + - This method requires ESPResSo to be compiled with the following + features enabled: + - '"VIRTUAL_SITES_RELATIVE"' + - '"MASS"' + - A new ESPResSo particle is created to represent the rigid-body center. + - The mass of the rigid-body center is set to the number of particles + belonging to the object. + - The rotational inertia tensor is approximated from the squared + distances of the particles to the center of mass. + """ + logging.info('enable_motion_of_rigid_object requires that espressomd has the following features activated: ["VIRTUAL_SITES_RELATIVE", "MASS"]') + inst = self.db.get_instance(pmb_type=pmb_type, + instance_id=instance_id) + label = self.db._get_label_id_map(pmb_type=pmb_type) + particle_ids_list = self.db.get_particle_id_map(object_name=inst.name)[label][instance_id] + center_of_mass = self.calculate_center_of_mass (instance_id=instance_id, + pmb_type=pmb_type) + rigid_object_center = self.espresso_system.part.add(pos=center_of_mass, + rotation=[True,True,True], + type=self.db.propose_unused_type()) + rigid_object_center.mass = len(particle_ids_list) + momI = 0 + for pid in particle_ids_list: + momI += np.power(np.linalg.norm(center_of_mass - self.espresso_system.part.by_id(pid).pos), 2) + rigid_object_center.rinertia = np.ones(3) * momI + for particle_id in particle_ids_list: + pid = self.espresso_system.part.by_id(particle_id) + pid.vs_auto_relate_to(rigid_object_center.id) + + def get_number_of_particles(self, ptype): + """ + Returns the number of particles of a given ESPResSo particle type. + + Args: + ptype ('int'): + ESPResSo particle type identifier. + + Returns: + ('int'): + Number of particles in `espresso_system` with particle type `ptype`. + + Notes: + - In ESPResSo 4.2, `number_of_particles` expects the particle type + as a positional argument. + - In later ESPResSo versions, the particle type must be passed as a + keyword argument (`type=ptype`). + - This helper function hides these API differences and provides + a uniform interface across ESPResSo versions. + """ + import espressomd.version + if espressomd.version.friendly() == "4.2": + args = (ptype,) + kwargs = {} + else: + args = () + kwargs = {"type": ptype} + return self.espresso_system.number_of_particles(*args, **kwargs) + + def relax_espresso_system(self, seed, gamma=1e-3, Nsteps_steepest_descent=5000, max_displacement=0.01, Nsteps_iter_relax=500): + """ + Relaxes the energy of the given ESPResSo system by performing the following steps: + (1) Steepest descent energy minimization, to remove large forces and relax the system to a local minimum. + (2) A Langevin Dynamics run, to further relax the system and ensure that it is in thermal equilibrium. + + This function is useful to avoid code repetition in the sample scripts of pyMBE, but it is by no means general-purpose. + Similarly, the default parameters are not universal and should be adapted to the specific system at hand. + In general, system relaxation is a complex procedure and should be adapted for each particular application. + If you experience crashes or unexpected behavior, please consider using your own relaxation procedure. + + Args: + + seed (`int`): + Seed for the random number generator for the thermostat. + + gamma (`float`, optional): + Starting damping constant for Langevin dynamics. Defaults to 1e-3 reduced time**-1. + + Nsteps_steepest_descent (`int`, optional): + Total number of steps for steepest descent minimization. Defaults to 5000. + + max_displacement (`float`, optional): + Maximum particle displacement allowed during minimization. Defaults to 0.01 reduced length. + + Nsteps_iter_relax (`int`, optional): + Number of steps per iteration for Langevin dynamics relaxation. Defaults to 500. + + Return: + (`float`): + minimum distance between particles in the system after the relaxation + + Notes: + - The thermostat is turned off by the end of the procedure. + - Make sure the system is initialized properly before calling this function. + """ + # Sanity checks + if gamma <= 0: + raise ValueError("The damping constant 'gamma' must be positive.") + if Nsteps_steepest_descent <= 0 or Nsteps_iter_relax <= 0: + raise ValueError("Step counts must be positive integers.") + if max_displacement <= 0: + raise ValueError("'max_displacement' must be positive.") + logging.debug("*** Relaxing the energy of the system... ***") + logging.debug("*** Starting steepest descent minimization ***") + self.espresso_system.thermostat.turn_off() + self.espresso_system.integrator.set_steepest_descent(f_max=0, + gamma=gamma, + max_displacement=max_displacement) + self.espresso_system.integrator.run(Nsteps_steepest_descent) + logging.debug("*** Finished steepest descent minimization ***") + logging.debug("*** Starting Langevin Dynamics relaxation ***") + self.espresso_system.integrator.set_vv() + self.espresso_system.thermostat.set_langevin(kT=1., gamma=gamma, seed=seed) + self.espresso_system.integrator.run(Nsteps_iter_relax) + self.espresso_system.thermostat.turn_off() + logging.debug("*** Finished Langevin Dynamics relaxation ***") + logging.info(f"*** Minimum particle distance after relaxation: {self.espresso_system.analysis.min_dist()} ***") + logging.debug("*** Relaxation finished ***") + return self.espresso_system.analysis.min_dist() + + def setup_electrostatic_interactions(self,units, kT, c_salt=None, solvent_permittivity=78.5, method='p3m', tune_p3m=True, accuracy=1e-3, params=None, verbose=False): + """ + Sets up electrostatic interactions in an ESPResSo system. + + Args: + units (`pint.UnitRegistry`): + Unit registry for handling physical units. + + + kT (`pint.Quantity`): + Thermal energy. + + c_salt (`pint.Quantity`): + Added salt concentration. If provided, the program outputs the debye screening length. It is a mandatory parameter for the Debye-Hückel method. + + solvent_permittivity (`float`): + Solvent relative permittivity. Defaults to 78.5, correspoding to its value in water at 298.15 K. + + method (`str`): + Method for computing electrostatic interactions. Defaults to "p3m". + + tune_p3m (`bool`): + If True, tunes P3M parameters for efficiency. Defaults to True. + + accuracy (`float`): + Desired accuracy for electrostatics. Defaults to 1e-3. + + params (`dict`): + Additional parameters for the electrostatic method. For P3M, it can include 'mesh', 'alpha', 'cao' and `r_cut`. For Debye-Hückel, it can include 'r_cut'. + + verbose (`bool`): + If True, enables verbose output for P3M tuning. Defaults to False. + + Notes: + - `c_salt` is a mandatory argument for setting up the Debye-Hückel electrostatic potential. + - The calculated Bjerrum length is ouput to the log. If `c_salt` is provided, the calculated Debye screening length is also output to the log. + - Currently, the only supported electrostatic methods are P3M ("p3m") and Debye-Hückel ("dh"). + """ + import numpy as np + import scipy.constants + + logging.debug("*** Starting electrostatic interactions setup... ***") + # Initial sanity checks + if not hasattr(units, 'Quantity'): + raise TypeError("Invalid 'units' argument: Expected a pint.UnitRegistry object") + valid_methods_list=['p3m', 'dh'] + if method not in valid_methods_list: + raise ValueError('Method not supported, supported methods are', valid_methods_list) + if c_salt is None and method == 'dh': + raise ValueError('Please provide the added salt concentration c_salt to setup the Debye-Huckel potential') + e = scipy.constants.e * units.C + N_A = scipy.constants.N_A / units.mol + BJERRUM_LENGTH = e**2 / (4 * units.pi * units.eps0 * solvent_permittivity * kT) + logging.info(f" Bjerrum length {BJERRUM_LENGTH.to('nm')} = {BJERRUM_LENGTH.to('reduced_length')}") + COULOMB_PREFACTOR=BJERRUM_LENGTH * kT + if c_salt is not None: + if c_salt.check('[substance] [length]**-3'): + KAPPA=1./np.sqrt(8*units.pi*BJERRUM_LENGTH*N_A*c_salt) + elif c_salt.check('[length]**-3'): + KAPPA=1./np.sqrt(8*units.pi*BJERRUM_LENGTH*c_salt) + else: + raise ValueError('Unknown units for c_salt, supported units for salt concentration are [mol / volume] or [particle / volume]', c_salt) + + logging.info(f"Debye kappa {KAPPA.to('nm')} = {KAPPA.to('reduced_length')}") + + if params is None: + params = {} + + if method == 'p3m': + logging.debug("*** Setting up Coulomb electrostatics using the P3M method ***") + coulomb = espressomd.electrostatics.P3M(prefactor = COULOMB_PREFACTOR.m_as("reduced_length * reduced_energy"), + accuracy=accuracy, + verbose=verbose, + tune=tune_p3m, + **params) + + if tune_p3m: + self.espresso_system.time_step=0.01 + if espressomd.version.friendly() == "4.2": + self.espresso_system.actors.add(coulomb) + else: + self.espresso_system.electrostatics.solver = coulomb + + + # save the optimal parameters and add them by hand + + p3m_params = coulomb.get_params() + if espressomd.version.friendly() == "4.2": + self.espresso_system.actors.remove(coulomb) + else: + self.espresso_system.electrostatics.solver = None + coulomb = espressomd.electrostatics.P3M(prefactor = COULOMB_PREFACTOR.m_as("reduced_length * reduced_energy"), + accuracy = accuracy, + mesh = p3m_params['mesh'], + alpha = p3m_params['alpha'] , + cao = p3m_params['cao'], + r_cut = p3m_params['r_cut'], + tune = False) + + elif method == 'dh': + logging.debug("*** Setting up Debye-Hückel electrostatics ***") + if params: + r_cut = params['r_cut'] + else: + r_cut = 3*KAPPA.to('reduced_length').magnitude + + coulomb = espressomd.electrostatics.DH(prefactor = COULOMB_PREFACTOR.m_as("reduced_length * reduced_energy"), + kappa = (1./KAPPA).to('1/ reduced_length').magnitude, + r_cut = r_cut) + if espressomd.version.friendly() == "4.2": + self.espresso_system.actors.add(coulomb) + else: + self.espresso_system.electrostatics.solver = coulomb + logging.debug("*** Electrostatics successfully added to the system ***") + + def setup_cpH (self, counter_ion, constant_pH, exclusion_range=None, use_exclusion_radius_per_type = False): + """ + Sets up the Acid/Base reactions for acidic/basic particles defined in the pyMBE database + to be sampled in the constant pH ensemble. + + Args: + counter_ion ('str'): + 'name' of the counter_ion 'particle'. + + constant_pH ('float'): + pH-value. + + exclusion_range ('pint.Quantity', optional): + Below this value, no particles will be inserted. + + use_exclusion_radius_per_type ('bool', optional): + Controls if one exclusion_radius for each espresso_type is used. Defaults to 'False'. + + Returns: + ('reaction_methods.ConstantpHEnsemble'): + Instance of a reaction_methods.ConstantpHEnsemble object from the espressomd library. + """ + from espressomd import reaction_methods + if exclusion_range is None: + exclusion_range = max(self.db.get_radius_map().values())*2.0 + if use_exclusion_radius_per_type: + exclusion_radius_per_type = self.db.get_radius_map() + else: + exclusion_radius_per_type = {} + RE = reaction_methods.ConstantpHEnsemble(kT=self.kT.to('reduced_energy').magnitude, + exclusion_range=exclusion_range, + seed=self.seed, + constant_pH=constant_pH, + exclusion_radius_per_type = exclusion_radius_per_type) + conterion_tpl = self.db.get_template(name=counter_ion, + pmb_type="particle") + conterion_state = self.db.get_template(name=conterion_tpl.initial_state, + pmb_type="particle_state") + for reaction in self.db.get_reactions(): + if reaction.reaction_type not in ["monoprotic_acid", "monoprotic_base"]: + continue + default_charges = {} + reactant_types = [] + product_types = [] + for participant in reaction.participants: + state_tpl = self.db.get_template(name=participant.state_name, + pmb_type="particle_state") + default_charges[state_tpl.es_type] = state_tpl.z + if participant.coefficient < 0: + reactant_types.append(state_tpl.es_type) + elif participant.coefficient > 0: + product_types.append(state_tpl.es_type) + # Add counterion to the products + if conterion_state.es_type not in product_types: + product_types.append(conterion_state.es_type) + default_charges[conterion_state.es_type] = conterion_state.z + reaction.add_participant(particle_name=counter_ion, + state_name=conterion_tpl.initial_state, + coefficient=1) + gamma=10**-reaction.pK + RE.add_reaction(gamma=gamma, + reactant_types=reactant_types, + product_types=product_types, + default_charges=default_charges) + reaction.add_simulation_method(simulation_method="cpH") + return RE + + def setup_gcmc(self, c_salt_res, salt_cation_name, salt_anion_name, activity_coefficient, exclusion_range=None, use_exclusion_radius_per_type = False): + """ + Sets up grand-canonical coupling to a reservoir of salt. + For reactive systems coupled to a reservoir, the grand-reaction method has to be used instead. + + Args: + c_salt_res ('pint.Quantity'): + Concentration of monovalent salt (e.g. NaCl) in the reservoir. + + salt_cation_name ('str'): + Name of the salt cation (e.g. Na+) particle. + + salt_anion_name ('str'): + Name of the salt anion (e.g. Cl-) particle. + + activity_coefficient ('callable'): + A function that calculates the activity coefficient of an ion pair as a function of the ionic strength. + + exclusion_range('pint.Quantity', optional): + For distances shorter than this value, no particles will be inserted. + + use_exclusion_radius_per_type('bool',optional): + Controls if one exclusion_radius for each espresso_type is used. Defaults to 'False'. + + Returns: + ('reaction_methods.ReactionEnsemble'): + Instance of a reaction_methods.ReactionEnsemble object from the espressomd library. + """ + from espressomd import reaction_methods + if exclusion_range is None: + exclusion_range = max(self.db.get_radius_map().values())*2.0 + if use_exclusion_radius_per_type: + exclusion_radius_per_type = self.db.get_radius_map() + else: + exclusion_radius_per_type = {} + RE = reaction_methods.ReactionEnsemble(kT=self.kT.to('reduced_energy').magnitude, + exclusion_range=exclusion_range, + seed=self.seed, + exclusion_radius_per_type = exclusion_radius_per_type) + # Determine the concentrations of the various species in the reservoir and the equilibrium constants + determined_activity_coefficient = activity_coefficient(c_salt_res) + K_salt = (c_salt_res.to('1/(N_A * reduced_length**3)')**2) * determined_activity_coefficient + cation_tpl = self.db.get_template(pmb_type="particle", + name=salt_cation_name) + cation_state = self.db.get_template(pmb_type="particle_state", + name=cation_tpl.initial_state) + anion_tpl = self.db.get_template(pmb_type="particle", + name=salt_anion_name) + anion_state = self.db.get_template(pmb_type="particle_state", + name=anion_tpl.initial_state) + salt_cation_es_type = cation_state.es_type + salt_anion_es_type = anion_state.es_type + salt_cation_charge = cation_state.z + salt_anion_charge = anion_state.z + if salt_cation_charge <= 0: + raise ValueError('ERROR salt cation charge must be positive, charge ', salt_cation_charge) + if salt_anion_charge >= 0: + raise ValueError('ERROR salt anion charge must be negative, charge ', salt_anion_charge) + # Grand-canonical coupling to the reservoir + RE.add_reaction(gamma = K_salt.magnitude, + reactant_types = [], + reactant_coefficients = [], + product_types = [ salt_cation_es_type, salt_anion_es_type ], + product_coefficients = [ 1, 1 ], + default_charges = {salt_cation_es_type: salt_cation_charge, + salt_anion_es_type: salt_anion_charge}) + rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=salt_cation_name, + state_name=cation_state.name, + coefficient=1), + ReactionParticipant(particle_name=salt_anion_name, + state_name=anion_state.name, + coefficient=1)], + pK=-np.log10(K_salt.magnitude), + reaction_type="ion_insertion", + simulation_method="GCMC") + self.db._register_reaction(rx_tpl) + return RE + + def setup_grxmc_reactions(self, pH_res, c_salt_res, proton_name, hydroxide_name, salt_cation_name, salt_anion_name, activity_coefficient, exclusion_range=None, use_exclusion_radius_per_type = False): + """ + Sets up acid/base reactions for acidic/basic monoprotic particles defined in the pyMBE database, + as well as a grand-canonical coupling to a reservoir of small ions. + + Args: + pH_res ('float'): + pH-value in the reservoir. + + c_salt_res ('pint.Quantity'): + Concentration of monovalent salt (e.g. NaCl) in the reservoir. + + proton_name ('str'): + Name of the proton (H+) particle. + + hydroxide_name ('str'): + Name of the hydroxide (OH-) particle. + + salt_cation_name ('str'): + Name of the salt cation (e.g. Na+) particle. + + salt_anion_name ('str'): + Name of the salt anion (e.g. Cl-) particle. + + activity_coefficient ('callable'): + A function that calculates the activity coefficient of an ion pair as a function of the ionic strength. + + exclusion_range('pint.Quantity', optional): + For distances shorter than this value, no particles will be inserted. + + use_exclusion_radius_per_type('bool', optional): + Controls if one exclusion_radius for each espresso_type is used. Defaults to 'False'. + + Returns: + 'tuple(reaction_methods.ReactionEnsemble,pint.Quantity)': + + 'reaction_methods.ReactionEnsemble': + espressomd reaction_methods object with all reactions necesary to run the GRxMC ensamble. + + 'pint.Quantity': + Ionic strength of the reservoir (useful for calculating partition coefficients). + + Notess: + - This implementation uses the original formulation of the grand-reaction method by Landsgesell et al. [1]. + + [1] Landsgesell, J., Hebbeker, P., Rud, O., Lunkad, R., Košovan, P., & Holm, C. (2020). Grand-reaction method for simulations of ionization equilibria coupled to ion partitioning. Macromolecules, 53(8), 3007-3020. + """ + from espressomd import reaction_methods + if exclusion_range is None: + exclusion_range = max(self.db.get_radius_map().values())*2.0 + if use_exclusion_radius_per_type: + exclusion_radius_per_type = self.db.get_radius_map() + else: + exclusion_radius_per_type = {} + RE = reaction_methods.ReactionEnsemble(kT=self.kT.to('reduced_energy').magnitude, + exclusion_range=exclusion_range, + seed=self.seed, + exclusion_radius_per_type = exclusion_radius_per_type) + # Determine the concentrations of the various species in the reservoir and the equilibrium constants + cH_res, cOH_res, cNa_res, cCl_res = self.determine_reservoir_concentrations(pH_res, c_salt_res, activity_coefficient) + ionic_strength_res = 0.5*(cNa_res+cCl_res+cOH_res+cH_res) + determined_activity_coefficient = activity_coefficient(ionic_strength_res) + K_W = cH_res.to('1/(N_A * reduced_length**3)') * cOH_res.to('1/(N_A * reduced_length**3)') * determined_activity_coefficient + K_NACL = cNa_res.to('1/(N_A * reduced_length**3)') * cCl_res.to('1/(N_A * reduced_length**3)') * determined_activity_coefficient + K_HCL = cH_res.to('1/(N_A * reduced_length**3)') * cCl_res.to('1/(N_A * reduced_length**3)') * determined_activity_coefficient + cation_tpl = self.db.get_template(pmb_type="particle", + name=salt_cation_name) + cation_state = self.db.get_template(pmb_type="particle_state", + name=cation_tpl.initial_state) + anion_tpl = self.db.get_template(pmb_type="particle", + name=salt_anion_name) + anion_state = self.db.get_template(pmb_type="particle_state", + name=anion_tpl.initial_state) + proton_tpl = self.db.get_template(pmb_type="particle", + name=proton_name) + proton_state = self.db.get_template(pmb_type="particle_state", + name=proton_tpl.initial_state) + hydroxide_tpl = self.db.get_template(pmb_type="particle", + name=hydroxide_name) + hydroxide_state = self.db.get_template(pmb_type="particle_state", + name=hydroxide_tpl.initial_state) + proton_es_type = proton_state.es_type + hydroxide_es_type = hydroxide_state.es_type + salt_cation_es_type = cation_state.es_type + salt_anion_es_type = anion_state.es_type + proton_charge = proton_state.z + hydroxide_charge = hydroxide_state.z + salt_cation_charge = cation_state.z + salt_anion_charge = anion_state.z + if proton_charge <= 0: + raise ValueError('ERROR proton charge must be positive, charge ', proton_charge) + if salt_cation_charge <= 0: + raise ValueError('ERROR salt cation charge must be positive, charge ', salt_cation_charge) + if hydroxide_charge >= 0: + raise ValueError('ERROR hydroxide charge must be negative, charge ', hydroxide_charge) + if salt_anion_charge >= 0: + raise ValueError('ERROR salt anion charge must be negative, charge ', salt_anion_charge) + # Grand-canonical coupling to the reservoir + # 0 = H+ + OH- + RE.add_reaction(gamma = K_W.magnitude, + reactant_types = [], + reactant_coefficients = [], + product_types = [ proton_es_type, hydroxide_es_type ], + product_coefficients = [ 1, 1 ], + default_charges = {proton_es_type: proton_charge, + hydroxide_es_type: hydroxide_charge}) + rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=proton_name, + state_name=proton_state.name, + coefficient=1), + ReactionParticipant(particle_name=hydroxide_name, + state_name=hydroxide_state.name, + coefficient=1)], + pK=-np.log10(K_W.magnitude), + reaction_type="ion_insertion", + simulation_method="GRxMC") + self.db._register_reaction(rx_tpl) + # 0 = Na+ + Cl- + RE.add_reaction(gamma = K_NACL.magnitude, + reactant_types = [], + reactant_coefficients = [], + product_types = [ salt_cation_es_type, salt_anion_es_type ], + product_coefficients = [ 1, 1 ], + default_charges = {salt_cation_es_type: salt_cation_charge, + salt_anion_es_type: salt_anion_charge}) + rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=salt_cation_name, + state_name=cation_state.name, + coefficient=1), + ReactionParticipant(particle_name=salt_anion_name, + state_name=anion_state.name, + coefficient=1)], + pK=-np.log10(K_NACL.magnitude), + reaction_type="ion_insertion", + simulation_method="GRxMC") + self.db._register_reaction(rx_tpl) + # 0 = Na+ + OH- + RE.add_reaction(gamma = (K_NACL * K_W / K_HCL).magnitude, + reactant_types = [], + reactant_coefficients = [], + product_types = [ salt_cation_es_type, hydroxide_es_type ], + product_coefficients = [ 1, 1 ], + default_charges = {salt_cation_es_type: salt_cation_charge, + hydroxide_es_type: hydroxide_charge}) + rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=salt_cation_name, + state_name=cation_state.name, + coefficient=1), + ReactionParticipant(particle_name=hydroxide_name, + state_name=hydroxide_state.name, + coefficient=1)], + pK=-np.log10((K_NACL * K_W / K_HCL).magnitude), + reaction_type="ion_insertion", + simulation_method="GRxMC") + self.db._register_reaction(rx_tpl) + # 0 = H+ + Cl- + RE.add_reaction(gamma = K_HCL.magnitude, + reactant_types = [], + reactant_coefficients = [], + product_types = [ proton_es_type, salt_anion_es_type ], + product_coefficients = [ 1, 1 ], + default_charges = {proton_es_type: proton_charge, + salt_anion_es_type: salt_anion_charge}) + rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=proton_name, + state_name=proton_state.name, + coefficient=1), + ReactionParticipant(particle_name=salt_anion_name, + state_name=anion_state.name, + coefficient=1)], + pK=-np.log10(K_HCL.magnitude), + reaction_type="ion_insertion", + simulation_method="GRxMC") + self.db._register_reaction(rx_tpl) + # Annealing moves to ensure sufficient sampling + # Cation annealing H+ = Na+ + RE.add_reaction(gamma = (K_NACL / K_HCL).magnitude, + reactant_types = [proton_es_type], + reactant_coefficients = [ 1 ], + product_types = [ salt_cation_es_type ], + product_coefficients = [ 1 ], + default_charges = {proton_es_type: proton_charge, + salt_cation_es_type: salt_cation_charge}) + rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=proton_name, + state_name=proton_state.name, + coefficient=-1), + ReactionParticipant(particle_name=salt_cation_name, + state_name=cation_state.name, + coefficient=1)], + pK=-np.log10((K_NACL / K_HCL).magnitude), + reaction_type="particle replacement", + simulation_method="GRxMC") + self.db._register_reaction(rx_tpl) + # Anion annealing OH- = Cl- + RE.add_reaction(gamma = (K_HCL / K_W).magnitude, + reactant_types = [hydroxide_es_type], + reactant_coefficients = [ 1 ], + product_types = [ salt_anion_es_type ], + product_coefficients = [ 1 ], + default_charges = {hydroxide_es_type: hydroxide_charge, + salt_anion_es_type: salt_anion_charge}) + rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=hydroxide_name, + state_name=hydroxide_state.name, + coefficient=-1), + ReactionParticipant(particle_name=salt_anion_name, + state_name=anion_state.name, + coefficient=1)], + pK=-np.log10((K_HCL / K_W).magnitude), + reaction_type="particle replacement", + simulation_method="GRxMC") + self.db._register_reaction(rx_tpl) + for reaction in self.db.get_reactions(): + if reaction.reaction_type not in ["monoprotic_acid", "monoprotic_base"]: + continue + default_charges = {} + reactant_types = [] + product_types = [] + for participant in reaction.participants: + state_tpl = self.db.get_template(name=participant.state_name, + pmb_type="particle_state") + default_charges[state_tpl.es_type] = state_tpl.z + if participant.coefficient < 0: + reactant_types.append(state_tpl.es_type) + reactant_name=state_tpl.particle_name + reactant_state_name=state_tpl.name + elif participant.coefficient > 0: + product_types.append(state_tpl.es_type) + product_name=state_tpl.particle_name + product_state_name=state_tpl.name + + Ka = (10**-reaction.pK * self.units.mol/self.units.l).to('1/(N_A * reduced_length**3)') + # Reaction in terms of proton: HA = A + H+ + RE.add_reaction(gamma=Ka.magnitude, + reactant_types=reactant_types, + reactant_coefficients=[1], + product_types=product_types+[proton_es_type], + product_coefficients=[1, 1], + default_charges= default_charges | {proton_es_type: proton_charge}) + reaction.add_participant(particle_name=proton_name, + state_name=proton_state.name, + coefficient=1) + reaction.add_simulation_method("GRxMC") + # Reaction in terms of salt cation: HA = A + Na+ + RE.add_reaction(gamma=(Ka * K_NACL / K_HCL).magnitude, + reactant_types=reactant_types, + reactant_coefficients=[1], + product_types=product_types+[salt_cation_es_type], + product_coefficients=[1, 1], + default_charges=default_charges | {salt_cation_es_type: salt_cation_charge}) + rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=reactant_name, + state_name=reactant_state_name, + coefficient=-1), + ReactionParticipant(particle_name=product_name, + state_name=product_state_name, + coefficient=1), + ReactionParticipant(particle_name=salt_cation_name, + state_name=cation_state.name, + coefficient=1),], + pK=-np.log10((Ka * K_NACL / K_HCL).magnitude), + reaction_type=reaction.reaction_type+"_salt", + simulation_method="GRxMC") + self.db._register_reaction(rx_tpl) + # Reaction in terms of hydroxide: OH- + HA = A + RE.add_reaction(gamma=(Ka / K_W).magnitude, + reactant_types=reactant_types+[hydroxide_es_type], + reactant_coefficients=[1, 1], + product_types=product_types, + product_coefficients=[1], + default_charges=default_charges | {hydroxide_es_type: hydroxide_charge}) + rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=reactant_name, + state_name=reactant_state_name, + coefficient=-1), + ReactionParticipant(particle_name=product_name, + state_name=product_state_name, + coefficient=1), + ReactionParticipant(particle_name=hydroxide_name, + state_name=hydroxide_state.name, + coefficient=-1),], + pK=-np.log10((Ka / K_W).magnitude), + reaction_type=reaction.reaction_type+"_conjugate", + simulation_method="GRxMC") + self.db._register_reaction(rx_tpl) + # Reaction in terms of salt anion: Cl- + HA = A + RE.add_reaction(gamma=(Ka / K_HCL).magnitude, + reactant_types=reactant_types+[salt_anion_es_type], + reactant_coefficients=[1, 1], + product_types=product_types, + product_coefficients=[1], + default_charges=default_charges | {salt_anion_es_type: salt_anion_charge}) + rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=reactant_name, + state_name=reactant_state_name, + coefficient=-1), + ReactionParticipant(particle_name=product_name, + state_name=product_state_name, + coefficient=1), + ReactionParticipant(particle_name=salt_anion_name, + state_name=anion_state.name, + coefficient=-1),], + pK=-np.log10((Ka / K_HCL).magnitude), + reaction_type=reaction.reaction_type+"_salt", + simulation_method="GRxMC") + self.db._register_reaction(rx_tpl) + return RE, ionic_strength_res + + def setup_grxmc_unified(self, pH_res, c_salt_res, cation_name, anion_name, activity_coefficient, exclusion_range=None, use_exclusion_radius_per_type = False): + """ + Sets up acid/base reactions for acidic/basic 'particles' defined in the pyMBE database, as well as a grand-canonical coupling to a + reservoir of small ions using a unified formulation for small ions. + + Args: + pH_res ('float'): + pH-value in the reservoir. + + c_salt_res ('pint.Quantity'): + Concentration of monovalent salt (e.g. NaCl) in the reservoir. + + cation_name ('str'): + Name of the cationic particle. + + anion_name ('str'): + Name of the anionic particle. + + activity_coefficient ('callable'): + A function that calculates the activity coefficient of an ion pair as a function of the ionic strength. + + exclusion_range('pint.Quantity', optional): + Below this value, no particles will be inserted. + + use_exclusion_radius_per_type('bool', optional): + Controls if one exclusion_radius per each espresso_type. Defaults to 'False'. + + Returns: + 'tuple(reaction_methods.ReactionEnsemble,pint.Quantity)': + + 'reaction_methods.ReactionEnsemble': + espressomd reaction_methods object with all reactions necesary to run the GRxMC ensamble. + + 'pint.Quantity': + Ionic strength of the reservoir (useful for calculating partition coefficients). + + Notes: + - This implementation uses the formulation of the grand-reaction method by Curk et al. [1], which relies on "unified" ion types X+ = {H+, Na+} and X- = {OH-, Cl-}. + - A function that implements the original version of the grand-reaction method by Landsgesell et al. [2] is also available under the name 'setup_grxmc_reactions'. + + [1] Curk, T., Yuan, J., & Luijten, E. (2022). Accelerated simulation method for charge regulation effects. The Journal of Chemical Physics, 156(4). + [2] Landsgesell, J., Hebbeker, P., Rud, O., Lunkad, R., Košovan, P., & Holm, C. (2020). Grand-reaction method for simulations of ionization equilibria coupled to ion partitioning. Macromolecules, 53(8), 3007-3020. + """ + from espressomd import reaction_methods + if exclusion_range is None: + exclusion_range = max(self.db.get_radius_map().values())*2.0 + if use_exclusion_radius_per_type: + exclusion_radius_per_type = self.db.get_radius_map() + else: + exclusion_radius_per_type = {} + RE = reaction_methods.ReactionEnsemble(kT=self.kT.to('reduced_energy').magnitude, + exclusion_range=exclusion_range, + seed=self.seed, + exclusion_radius_per_type = exclusion_radius_per_type) + # Determine the concentrations of the various species in the reservoir and the equilibrium constants + cH_res, cOH_res, cNa_res, cCl_res = self.determine_reservoir_concentrations(pH_res, c_salt_res, activity_coefficient) + ionic_strength_res = 0.5*(cNa_res+cCl_res+cOH_res+cH_res) + determined_activity_coefficient = activity_coefficient(ionic_strength_res) + a_hydrogen = (10 ** (-pH_res) * self.units.mol/self.units.l).to('1/(N_A * reduced_length**3)') + a_cation = (cH_res+cNa_res).to('1/(N_A * reduced_length**3)') * np.sqrt(determined_activity_coefficient) + a_anion = (cH_res+cNa_res).to('1/(N_A * reduced_length**3)') * np.sqrt(determined_activity_coefficient) + K_XX = a_cation * a_anion + cation_tpl = self.db.get_template(pmb_type="particle", + name=cation_name) + cation_state = self.db.get_template(pmb_type="particle_state", + name=cation_tpl.initial_state) + anion_tpl = self.db.get_template(pmb_type="particle", + name=anion_name) + anion_state = self.db.get_template(pmb_type="particle_state", + name=anion_tpl.initial_state) + cation_es_type = cation_state.es_type + anion_es_type = anion_state.es_type + cation_charge = cation_state.z + anion_charge = anion_state.z + if cation_charge <= 0: + raise ValueError('ERROR cation charge must be positive, charge ', cation_charge) + if anion_charge >= 0: + raise ValueError('ERROR anion charge must be negative, charge ', anion_charge) + # Coupling to the reservoir: 0 = X+ + X- + RE.add_reaction(gamma = K_XX.magnitude, + reactant_types = [], + reactant_coefficients = [], + product_types = [ cation_es_type, anion_es_type ], + product_coefficients = [ 1, 1 ], + default_charges = {cation_es_type: cation_charge, + anion_es_type: anion_charge}) + rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=cation_name, + state_name=cation_state.name, + coefficient=1), + ReactionParticipant(particle_name=anion_name, + state_name=anion_state.name, + coefficient=1)], + pK=-np.log10(K_XX.magnitude), + reaction_type="ion_insertion", + simulation_method="GCMC") + self.db._register_reaction(rx_tpl) + for reaction in self.db.get_reactions(): + if reaction.reaction_type not in ["monoprotic_acid", "monoprotic_base"]: + continue + default_charges = {} + reactant_types = [] + product_types = [] + for participant in reaction.participants: + state_tpl = self.db.get_template(name=participant.state_name, + pmb_type="particle_state") + default_charges[state_tpl.es_type] = state_tpl.z + if participant.coefficient < 0: + reactant_types.append(state_tpl.es_type) + reactant_name=state_tpl.particle_name + reactant_state_name=state_tpl.name + elif participant.coefficient > 0: + product_types.append(state_tpl.es_type) + product_name=state_tpl.particle_name + product_state_name=state_tpl.name + + Ka = (10**-reaction.pK * self.units.mol/self.units.l).to('1/(N_A * reduced_length**3)') + gamma_K_AX = Ka.to('1/(N_A * reduced_length**3)').magnitude * a_cation / a_hydrogen + # Reaction in terms of small cation: HA = A + X+ + RE.add_reaction(gamma=gamma_K_AX.magnitude, + reactant_types=reactant_types, + reactant_coefficients=[1], + product_types=product_types+[cation_es_type], + product_coefficients=[1, 1], + default_charges=default_charges|{cation_es_type: cation_charge}) + reaction.add_participant(particle_name=cation_name, + state_name=cation_state.name, + coefficient=1) + reaction.add_simulation_method("GRxMC") + # Reaction in terms of small anion: X- + HA = A + RE.add_reaction(gamma=gamma_K_AX.magnitude / K_XX.magnitude, + reactant_types=reactant_types+[anion_es_type], + reactant_coefficients=[1, 1], + product_types=product_types, + product_coefficients=[1], + default_charges=default_charges|{anion_es_type: anion_charge}) + rx_tpl = Reaction(participants=[ReactionParticipant(particle_name=reactant_name, + state_name=reactant_state_name, + coefficient=-1), + ReactionParticipant(particle_name=product_name, + state_name=product_state_name, + coefficient=1), + ReactionParticipant(particle_name=anion_name, + state_name=anion_state.name, + coefficient=-1),], + pK=-np.log10(gamma_K_AX.magnitude / K_XX.magnitude), + reaction_type=reaction.reaction_type+"_conjugate", + simulation_method="GRxMC") + self.db._register_reaction(rx_tpl) + return RE, ionic_strength_res + + def setup_langevin_dynamics(self, kT, seed,time_step=1e-2, gamma=1, tune_skin=True, min_skin=1, max_skin=None, tolerance=1e-3, int_steps=200, adjust_max_skin=True): + """ + Sets up Langevin Dynamics for an ESPResSo simulation system. + + Args: + + kT (`pint.Quantity`): + Target temperature in reduced energy units. + + seed (`int`): + Seed for the random number generator for the thermostat. + + time_step (`float`, optional): + Integration time step. Defaults to 1e-2. + + gamma (`float`, optional): + Damping coefficient for the Langevin thermostat. Defaults to 1. + + tune_skin (`bool`, optional): + Whether to optimize the skin parameter. Defaults to True. + + min_skin (`float`, optional): + Minimum skin value for optimization. Defaults to 1. + + max_skin (`float`, optional): + Maximum skin value for optimization. Defaults to None, which is handled by setting its value to box length / 2. + + tolerance (`float`, optional): + Tolerance for skin optimization. Defaults to 1e-3. + + int_steps (`int`, optional): + Number of integration steps for tuning. Defaults to 200. + + adjust_max_skin (`bool`, optional): + Whether to adjust the maximum skin value during tuning. Defaults to True. + """ + if not isinstance(seed, int): + raise TypeError("seed must be an integer.") + if not isinstance(time_step, (float, int)) or time_step <= 0: + raise ValueError("time_step must be a positive number.") + if not isinstance(gamma, (float, int)) or gamma <= 0: + raise ValueError("gamma must be a positive number.") + if max_skin is None: + max_skin=self.espresso_system.box_l[0]/2 + if min_skin >= max_skin: + raise ValueError("min_skin must be smaller than max_skin.") + self.espresso_system.time_step=time_step + self.espresso_system.integrator.set_vv() + self.espresso_system.thermostat.set_langevin(kT= kT.to('reduced_energy').magnitude, + gamma= gamma, + seed= seed) + # Optimize the value of skin + if tune_skin: + logging.debug("*** Optimizing skin ... ***") + self.espresso_system.cell_system.tune_skin(min_skin=min_skin, + max_skin=max_skin, + tol=tolerance, + int_steps=int_steps, + adjust_max_skin=adjust_max_skin) + logging.info(f"Optimized skin value: {self.espresso_system.cell_system.skin}") + + def setup_lj_interactions(self, shift_potential=True, combining_rule='Lorentz-Berthelot'): + """ + Sets up the Lennard-Jones (LJ) potential between all pairs of particle states defined in the pyMBE database. + + Args: + shift_potential('bool', optional): + If True, a shift will be automatically computed such that the potential is continuous at the cutoff radius. Otherwise, no shift will be applied. Defaults to True. + + combining_rule('string', optional): + combining rule used to calculate 'sigma' and 'epsilon' for the potential between a pair of particles. Defaults to 'Lorentz-Berthelot'. + + warning('bool', optional): + switch to activate/deactivate warning messages. Defaults to True. + + Notes: + - Currently, the only 'combining_rule' supported is Lorentz-Berthelot. + - Check the documentation of ESPResSo for more info about the potential https://espressomd.github.io/doc4.2.0/inter_non-bonded.html + + """ + from itertools import combinations_with_replacement + particle_templates = self.db.get_templates("particle") + shift = "auto" if shift_potential else 0 + if shift == "auto": + shift_tpl = shift + else: + shift_tpl = PintQuantity.from_quantity(q=shift*self.units.reduced_length, + expected_dimension="length", + ureg=self.units) + # Get all particle states registered in pyMBE + state_entries = [] + for tpl in particle_templates.values(): + for state in self.db.get_particle_states_templates(particle_name=tpl.name).values(): + state_entries.append((tpl, state)) + + # Iterate over all unique state pairs + for (tpl1, state1), (tpl2, state2) in combinations_with_replacement(state_entries, 2): + + lj_parameters = self.db.get_lj_parameters(particle_name1=tpl1.name, + particle_name2=tpl2.name, + combining_rule=combining_rule) + if not lj_parameters: + continue + + self.espresso_system.non_bonded_inter[state1.es_type, state2.es_type].lennard_jones.set_params( + epsilon=lj_parameters["epsilon"].to("reduced_energy").magnitude, + sigma=lj_parameters["sigma"].to("reduced_length").magnitude, + cutoff=lj_parameters["cutoff"].to("reduced_length").magnitude, + offset=lj_parameters["offset"].to("reduced_length").magnitude, + shift=shift) + + lj_template = LJInteractionTemplate(state1=state1.name, + state2=state2.name, + sigma=PintQuantity.from_quantity(q=lj_parameters["sigma"], + expected_dimension="length", + ureg=self.units), + epsilon=PintQuantity.from_quantity(q=lj_parameters["epsilon"], + expected_dimension="energy", + ureg=self.units), + cutoff=PintQuantity.from_quantity(q=lj_parameters["cutoff"], + expected_dimension="length", + ureg=self.units), + offset=PintQuantity.from_quantity(q=lj_parameters["offset"], + expected_dimension="length", + ureg=self.units), + shift=shift_tpl) + self.db._register_template(lj_template) + + def update_particle_id(self, old_pid, new_pid): + """ + Method to be called if particles have been previously added to a simulation engine without using pyMBE. + + Args: + old_pid (int): old particle id to be replaced. + new_pid (int): new particle id to be assigned to instance in the pyMBE database. + """ + + if self.espresso_system == None: + raise ValueError('No simulation engine has been set to pymbe') + + self.db._update_instance(instance_id=old_pid, + pmb_type='particle', + attribute='particle_id', + value=new_pid) + + particle_id1_instances_ids=self.db._find_instance_ids_by_attribute(pmb_type='bond', + attribute='particle_id1', + value=old_pid) + for bond_id in particle_id1_instances_ids: + self.db._update_instance(instance_id=bond_id, + pmb_type='bond', + attribute='particle_id1', + value=new_pid) + particle_id2_instances_ids=self.db._find_instance_ids_by_attribute(pmb_type='bond', + attribute='particle_id2', + value=old_pid) + for bond_id in particle_id2_instances_ids: + self.db._update_instance(instance_id=bond_id, + pmb_type='bond', + attribute='particle_id2', + value=new_pid) + + + def add_instances_to_engine(self): + """ + This method adds the set of particles instances and bond instances and angle instances + that are not present in the pymbe data base + """ + + missing_particle_ids=self.db._find_instance_ids_by_attribute(pmb_type='particle', + attribute='added_to_engine', + value=False) + if not missing_particle_ids: + raise RuntimeError('No particles instances in the pyMBE database set to be added to the simulation engine') + + added_particle_ids=self.db._find_instance_ids_by_attribute(pmb_type='particle', + attribute='added_to_engine', + value=True) + ids_in_espresso = list(self._get_particle_ids_in_espresso()) + + overlapping_ids = set(ids_in_espresso).intersection(set(missing_particle_ids)) + for overlapping_id in overlapping_ids: + missing_particle_ids.remove(overlapping_id) + new_id = max(ids_in_espresso+added_particle_ids+missing_particle_ids)+1 + self.update_particle_id(old_pid=overlapping_id, + new_pid=new_id) + missing_particle_ids.append(new_id) + + if overlapping_ids: + warnings.warn("""Please review your setup, you have previously added a set of particles to ESPResSo. + The particle ids of the pyMBE database have been updated taking into account the id of + the last particle from ESPResSo. The following particle ids were updated: {}. """.format(overlapping_ids)) + + for id in missing_particle_ids: + self._add_particle(id) + + missing_bond_ids=self.db._find_instance_ids_by_attribute(pmb_type='bond', + attribute='added_to_engine', + value=False) + + if len(missing_bond_ids)>0: + for id in missing_bond_ids: + bond_instance=self.db.get_instance(pmb_type='bond', + instance_id=id) + particle_id1=bond_instance.particle_id1 + particle_id2=bond_instance.particle_id2 + self._add_bond(particle_id1, + particle_id2, + bond_instance) + + missing_angle_ids=self.db._find_instance_ids_by_attribute(pmb_type='angle', + attribute='added_to_engine', + value=False) + if len(missing_angle_ids)>0: + for id in missing_angle_ids: + angle_instance=self.db.get_instance(pmb_type='angle', + instance_id=id) + particle_id1=angle_instance.particle_id1 + particle_id2=angle_instance.particle_id2 + particle_id3=angle_instance.particle_id3 + self._add_angle(particle_id1, + particle_id2, + particle_id3, + angle_instance) + + return + diff --git a/pyMBE/simulation_builder/lammps_engine.py b/pyMBE/simulation_builder/lammps_engine.py new file mode 100644 index 00000000..2984ccef --- /dev/null +++ b/pyMBE/simulation_builder/lammps_engine.py @@ -0,0 +1,38 @@ +# +# Copyright (C) 2023-2026 pyMBE-dev team +# +# This file is part of pyMBE. +# +# pyMBE is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# pyMBE is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from pyMBE.simulation_builder.base_engine import SimulationEngine + + +class LammpsSimulation(SimulationEngine): + def __init__(self): + pass + def __getattr__(self, attr): + raise NotImplementedError('Lammps Simulation Engine is not yet implemented') + def _add_angle(self): + return + def _check_bond_inputs(self): + return + def _create_bond_instance(self): + return + def _get_bond_instance(self): + return + def add_instances_to_engine(self): + return + + \ No newline at end of file diff --git a/pyMBE/storage/instances/angle.py b/pyMBE/storage/instances/angle.py index 34adbb80..ef9b004e 100644 --- a/pyMBE/storage/instances/angle.py +++ b/pyMBE/storage/instances/angle.py @@ -50,6 +50,7 @@ class AngleInstance(PMBBaseModel): particle_id1: int particle_id2: int particle_id3: int + added_to_engine: bool = False @validator("angle_id", "particle_id1", "particle_id2", "particle_id3") def validate_non_negative_int(cls, value, field): diff --git a/pyMBE/storage/instances/bond.py b/pyMBE/storage/instances/bond.py index 24b07f2b..e38eb340 100644 --- a/pyMBE/storage/instances/bond.py +++ b/pyMBE/storage/instances/bond.py @@ -53,6 +53,7 @@ class BondInstance(PMBBaseModel): name : str # bond template name particle_id1: int particle_id2: int + added_to_engine: bool = False @validator("bond_id", "particle_id1", "particle_id2") def validate_non_negative_int(cls, value, field): diff --git a/pyMBE/storage/instances/particle.py b/pyMBE/storage/instances/particle.py index 33ce56e5..407cb98b 100644 --- a/pyMBE/storage/instances/particle.py +++ b/pyMBE/storage/instances/particle.py @@ -17,10 +17,16 @@ # along with this program. If not, see . # -from typing import Literal, Optional +from typing import Literal, Optional,List,Annotated +import numpy as np +from numpy.typing import NDArray from pydantic import validator from ..base_type import PMBBaseModel +def validate_position(position): + if not isinstance(position,np.ndarray): + raise TypeError("Position has to be a numpy array") + return position class ParticleInstance(PMBBaseModel): """ @@ -56,10 +62,18 @@ class ParticleInstance(PMBBaseModel): name: str particle_id: int initial_state: str + position: Annotated[NDArray,validate_position] + fix: List[bool]=[False,False,False] residue_id: Optional[int] = None molecule_id: Optional[int] = None assembly_id: Optional[int] = None - + added_to_engine: bool = False + + class Config: + arbitrary_types_allowed=True + validate_assignment = True + extra = "forbid" + @validator("particle_id") def validate_particle_id(cls, pid): if pid < 0: diff --git a/pyMBE/storage/io.py b/pyMBE/storage/io.py index ec97d7d6..0469ce1b 100644 --- a/pyMBE/storage/io.py +++ b/pyMBE/storage/io.py @@ -22,6 +22,7 @@ from pathlib import Path from typing import Any, Dict import pandas as pd +import numpy as np import logging from pyMBE.storage.pint_quantity import PintQuantity @@ -278,7 +279,7 @@ def _load_database_csv(db, folder): csv_file = folder / f"instances_{pmb_type}.csv" if not csv_file.exists(): continue - df = pd.read_csv(csv_file, dtype=str).fillna("") + df = pd.read_csv(csv_file, dtype=str,float_precision='round_trip').fillna("") instances: Dict[Any, Any] = {} @@ -291,6 +292,9 @@ def _load_database_csv(db, folder): inst = ParticleInstance(name=row["name"], particle_id=int(row["particle_id"]), initial_state=row["initial_state"], + position=np.array([row["posx"],row["posy"],row["posz"]],dtype='float64'), + added_to_engine=row["added_to_engine"], + fix=[row["fixx"],row["fixy"],row["fixz"]], residue_id=None if residue_val == "" else int(residue_val), molecule_id=None if molecule_val == "" else int(molecule_val), assembly_id=None if assembly_val == "" else int(assembly_val)) @@ -324,6 +328,7 @@ def _load_database_csv(db, folder): elif pmb_type == "bond": inst = BondInstance(name=row["name"], bond_id=int(row["bond_id"]), + added_to_engine=row["added_to_engine"], particle_id1=int(row["particle_id1"]), particle_id2=int(row["particle_id2"])) instances[inst.bond_id] = inst @@ -332,7 +337,8 @@ def _load_database_csv(db, folder): angle_id=int(row["angle_id"]), particle_id1=int(row["particle_id1"]), particle_id2=int(row["particle_id2"]), - particle_id3=int(row["particle_id3"])) + particle_id3=int(row["particle_id3"]), + added_to_engine=row["added_to_engine"]) instances[inst.angle_id] = inst elif pmb_type == "hydrogel": inst = HydrogelInstance(name=row["name"], @@ -472,6 +478,13 @@ def _save_database_csv(db, folder): "name": inst.name, "particle_id": int(inst.particle_id), "initial_state": inst.initial_state, + "posx": inst.position[0], + "posy": inst.position[1], + "posz": inst.position[2], + "added_to_engine":inst.added_to_engine, + "fixx":inst.fix[0], + "fixy":inst.fix[1], + "fixz":inst.fix[2], "residue_id": int(inst.residue_id) if inst.residue_id is not None else "", "molecule_id": int(inst.molecule_id) if inst.molecule_id is not None else "", "assembly_id": int(inst.assembly_id) if inst.assembly_id is not None else ""}) @@ -500,6 +513,7 @@ def _save_database_csv(db, folder): rows.append({"pmb_type": pmb_type, "name": inst.name, "bond_id": int(inst.bond_id), + "added_to_engine":inst.added_to_engine, "particle_id1": int(inst.particle_id1), "particle_id2": int(inst.particle_id2)}) elif pmb_type == "angle" and isinstance(inst, AngleInstance): @@ -508,7 +522,8 @@ def _save_database_csv(db, folder): "angle_id": int(inst.angle_id), "particle_id1": int(inst.particle_id1), "particle_id2": int(inst.particle_id2), - "particle_id3": int(inst.particle_id3)}) + "particle_id3": int(inst.particle_id3), + "added_to_engine":inst.added_to_engine,}) elif pmb_type == "hydrogel" and isinstance(inst, HydrogelInstance): rows.append({"pmb_type": pmb_type, "name": inst.name, @@ -521,7 +536,7 @@ def _save_database_csv(db, folder): rows.append({"name": getattr(inst, "name", None)}) df = pd.DataFrame(rows) - df.to_csv(os.path.join(folder, f"instances_{pmb_type}.csv"), index=False) + df.to_csv(os.path.join(folder, f"instances_{pmb_type}.csv"), index=False,float_format="%.12f") # REACTIONS rows = [] @@ -532,4 +547,4 @@ def _save_database_csv(db, folder): "reaction_type": rx.reaction_type, "metadata": _encode(rx.metadata) if getattr(rx, "metadata", None) is not None else ""}) if rows: - pd.DataFrame(rows).to_csv(os.path.join(folder, "reactions.csv"), index=False) + pd.DataFrame(rows).to_csv(os.path.join(folder, "reactions.csv"), index=False,float_format="%.4f") \ No newline at end of file diff --git a/pyMBE/storage/manager.py b/pyMBE/storage/manager.py index f8ad826a..5119c54f 100644 --- a/pyMBE/storage/manager.py +++ b/pyMBE/storage/manager.py @@ -18,6 +18,8 @@ # import pandas as pd +import numpy as np + from collections import defaultdict from typing import Dict, Any from pyMBE.storage.templates.particle import ParticleTemplate @@ -250,7 +252,7 @@ def _find_template_types(self, name): if name in group: found.append(pmb_type) return found - + def _get_instances_df(self, pmb_type): """ @@ -281,6 +283,8 @@ def _get_instances_df(self, pmb_type): rows.append({"pmb_type": pmb_type, "name": inst.name, "particle_id": inst.particle_id, + "position":inst.position, + "fix":inst.fix, "initial_state": inst.initial_state, "residue_id": int(inst.residue_id) if inst.residue_id is not None else pd.NA, "molecule_id": int(inst.molecule_id) if inst.molecule_id is not None else pd.NA, @@ -300,7 +304,27 @@ def _get_instances_df(self, pmb_type): # Generic representation for other types rows.append(inst.dict()) return pd.DataFrame(rows) + + def _get_label_id_map(self, pmb_type): + """ + Returns the key used to access the particle ID map for a given pyMBE object type. + Args: + pmb_type ('str'): + pyMBE object type for which the particle ID map label is requested. + + Returns: + 'str': + Label identifying the appropriate particle ID map. + """ + if pmb_type in self._assembly_like_types: + label="assembly_map" + elif pmb_type in self._molecule_like_types: + label="molecule_map" + else: + label=f"{pmb_type}_map" + return label + def _get_reactions_df(self): """ Returns a DataFrame summarizing all registered chemical reactions. @@ -541,11 +565,15 @@ def _update_instance(self, instance_id, pmb_type, attribute, value): if instance_id not in self._instances[pmb_type]: raise ValueError(f"Instance '{instance_id}' not found for type '{pmb_type}' in the pyMBE database.") if pmb_type == "particle": - allowed = ["initial_state", "residue_id", "molecule_id", "assembly_id"] + allowed = ["initial_state", "residue_id", "molecule_id", "assembly_id","particle_id","position","added_to_engine"] elif pmb_type == "residue": allowed = ["molecule_id", "assembly_id"] elif pmb_type in self._molecule_like_types: allowed = ["assembly_id"] + elif pmb_type =="bond": + allowed = ["particle_id1","particle_id2","added_to_engine"] + elif pmb_type =="angle": + allowed = ["particle_id1","particle_id2","particle_id3","added_to_engine"] else: allowed = [None] # No attributes allowed for other types if attribute not in allowed: @@ -887,7 +915,78 @@ def get_instances(self, pmb_type): Returns an empty dict if no instances exist for the given type. """ return self._instances.get(pmb_type, {}).copy() + + def get_lj_parameters(self, particle_name1, particle_name2, combining_rule='Lorentz-Berthelot'): + """ + Returns the Lennard-Jones parameters for the interaction between the particle types given by + 'particle_name1' and 'particle_name2' in the pyMBE database, calculated according to the provided combining rule. + Args: + particle_name1 ('str'): + label of the type of the first particle type + + particle_name2 ('str'): + label of the type of the second particle type + + combining_rule ('string', optional): + combining rule used to calculate 'sigma' and 'epsilon' for the potential betwen a pair of particles. Defaults to 'Lorentz-Berthelot'. + + Returns: + ('dict'): + {"epsilon": epsilon_value, "sigma": sigma_value, "offset": offset_value, "cutoff": cutoff_value} + + Notes: + - Currently, the only 'combining_rule' supported is Lorentz-Berthelot. + - If the sigma value of 'particle_name1' or 'particle_name2' is 0, the function will return an empty dictionary. No LJ interactions are set up for particles with sigma = 0. + """ + supported_combining_rules=["Lorentz-Berthelot"] + if combining_rule not in supported_combining_rules: + raise ValueError(f"Combining_rule {combining_rule} currently not implemented in pyMBE, valid keys are {supported_combining_rules}") + part_tpl1 = self.get_template(name=particle_name1, + pmb_type="particle") + part_tpl2 = self.get_template(name=particle_name2, + pmb_type="particle") + lj_parameters1 = part_tpl1.get_lj_parameters(ureg=self._units) + lj_parameters2 = part_tpl2.get_lj_parameters(ureg=self._units) + + # If one of the particle has sigma=0, no LJ interations are set up between that particle type and the others + if part_tpl1.sigma.magnitude == 0 or part_tpl2.sigma.magnitude == 0: + return {} + # Apply combining rule + if combining_rule == 'Lorentz-Berthelot': + sigma=(lj_parameters1["sigma"]+lj_parameters2["sigma"])/2 + cutoff=(lj_parameters1["cutoff"]+lj_parameters2["cutoff"])/2 + offset=(lj_parameters1["offset"]+lj_parameters2["offset"])/2 + epsilon=np.sqrt(lj_parameters1["epsilon"]*lj_parameters2["epsilon"]) + return {"sigma": sigma, "cutoff": cutoff, "offset": offset, "epsilon": epsilon} + + def get_radius_map(self, dimensionless=True): + """ + Gets the effective radius of each particle defined in the pyMBE database. + + Args: + dimensionless ('bool'): + If ``True``, return magnitudes expressed in ``reduced_length``. + If ``False``, return Pint quantities with units. + + Returns: + ('dict'): + {espresso_type: radius}. + + Notes: + - The radius corresponds to (sigma+offset)/2 + """ + if "particle" not in self._templates: + return {} + result = {} + for _, tpl in self._templates["particle"].items(): + radius = (tpl.sigma.to_quantity(self._units) + tpl.offset.to_quantity(self._units))/2.0 + if dimensionless: + magnitude_reduced_length = radius.m_as("reduced_length") + radius = magnitude_reduced_length + for state in self.get_particle_states_templates(particle_name=tpl.name).values(): + result[state.es_type] = radius + return result def get_reaction(self, name): """ Retrieve a reaction stored in the pyMBE database by name. @@ -1112,3 +1211,22 @@ def get_particle_states_templates(self, particle_name): particle_states = {state.name: state for state in states.values() if state.particle_name == particle_name} return particle_states + def propose_unused_type(self): + """ + Propose an unused ESPResSo particle type. + + Returns: + ('int'): + The next available integer ESPResSo type. Returns ''0'' if no integer types are currently defined. + """ + type_map = self.get_es_types_map() + # Flatten all es_type values across all particles and states + all_types = [] + for es_type in type_map.values(): + all_types.append(es_type) + # If no es_types exist, start at 0 + if not all_types: + return 0 + return max(all_types) + 1 + + diff --git a/samples/Beyer2024/globular_protein.py b/samples/Beyer2024/globular_protein.py index f1adcef0..b172ddce 100644 --- a/samples/Beyer2024/globular_protein.py +++ b/samples/Beyer2024/globular_protein.py @@ -28,7 +28,7 @@ pmb = pyMBE.pymbe_library(seed=42) #Import functions from handy_functions script -from pyMBE.lib.handy_functions import setup_electrostatic_interactions, relax_espresso_system, setup_langevin_dynamics, do_reaction, define_protein_AA_particles, define_protein_AA_residues +from pyMBE.lib.handy_functions import do_reaction, define_protein_AA_particles, define_protein_AA_residues from pyMBE.lib import analysis # Here you can adjust the width of the panda columns displayed when running the code pd.options.display.max_colwidth = 10 @@ -130,8 +130,8 @@ # The trajectories of the simulations will be stored using espresso built-up functions in separed files in the folder 'frames' frames_path = args.output / "frames" frames_path.mkdir(parents=True, exist_ok=True) - -espresso_system = espressomd.System(box_l=[Box_L.to('reduced_length').magnitude] * 3) +box_l=[Box_L.to('reduced_length').magnitude] * 3 +espresso_system = espressomd.System(box_l=box_l) espresso_system.time_step=dt espresso_system.cell_system.skin=0.4 #Reads the VTF file of the protein model @@ -178,18 +178,20 @@ #We create the protein in espresso protein_id = pmb.create_protein(name=protein_name, number_of_proteins=1, - espresso_system=espresso_system, + box_l=box_l, topology_dict=topology_dict)[0] + +pmb.set_simulation_engine(espresso_system) +pmb.add_instances_to_engine() #Here we activate the motion of the protein if args.move_protein: pmb.enable_motion_of_rigid_object(instance_id=protein_id, - pmb_type="protein", - espresso_system=espresso_system) + pmb_type="protein") # Here we put the protein on the center of the simulation box pmb.center_object_in_simulation_box(instance_id=protein_id, - pmb_type="protein", - espresso_system=espresso_system) + box_l=box_l, + pmb_type="protein") if not args.ideal: # Estimate the radius of the protein @@ -203,7 +205,7 @@ if dist > protein_radius: protein_radius = dist # Create counter-ions - protein_net_charge = pmb.calculate_net_charge(espresso_system=espresso_system, + protein_net_charge = pmb.calculate_net_charge( object_name=protein_name, pmb_type="protein", dimensionless=True)["mean"] @@ -218,7 +220,7 @@ counter_ion_name=cation_name pmb.create_particle(name=counter_ion_name, - espresso_system=espresso_system, + box_l=box_l, number_of_particles=int(abs(protein_net_charge)), position=counter_ion_coords) @@ -232,14 +234,15 @@ n_samples=N_ions*2) ## Create cations pmb.create_particle(name=cation_name, - espresso_system=espresso_system, + box_l=box_l, number_of_particles=N_ions, position=added_salt_ions_coords[:N_ions]) ## Create anions pmb.create_particle(name=anion_name, - espresso_system=espresso_system, + box_l=box_l, number_of_particles=N_ions, position=added_salt_ions_coords[N_ions:]) + pmb.add_instances_to_engine() #Here we calculated the ionisable groups acid_base_ids = [] @@ -282,15 +285,14 @@ # Setup the potential energy if WCA: - pmb.setup_lj_interactions(espresso_system=espresso_system) - relax_espresso_system(espresso_system=espresso_system, + pmb.setup_lj_interactions() + pmb.simulation_engine.relax_espresso_system( seed=langevin_seed) if Electrostatics: - setup_electrostatic_interactions(units=pmb.units, - espresso_system=espresso_system, + pmb.simulation_engine.setup_electrostatic_interactions(units=pmb.units, kT=pmb.kT) -setup_langevin_dynamics(espresso_system=espresso_system, +pmb.simulation_engine.setup_langevin_dynamics( kT = pmb.kT, seed = langevin_seed) @@ -319,7 +321,7 @@ for step in tqdm.trange(N_samples, disable=not verbose): espresso_system.integrator.run (steps = integ_steps) do_reaction(cpH, steps=total_ionisable_groups) - protein_net_charge = pmb.calculate_net_charge(espresso_system=espresso_system, + protein_net_charge = pmb.calculate_net_charge( object_name=protein_name, pmb_type="protein", dimensionless=True)["mean"] @@ -329,7 +331,7 @@ charge_residues_per_type = {} for label in AA_label_list: charge_residues_per_type[label]=[] - charge_res=pmb.calculate_net_charge (espresso_system=espresso_system, + charge_res=pmb.calculate_net_charge ( object_name=label, pmb_type="residue", dimensionless=True)["mean"] diff --git a/samples/Beyer2024/peptide.py b/samples/Beyer2024/peptide.py index 6dd1c8fd..d82f8cbf 100644 --- a/samples/Beyer2024/peptide.py +++ b/samples/Beyer2024/peptide.py @@ -26,7 +26,6 @@ # Import pyMBE import pyMBE from pyMBE.lib import analysis -from pyMBE.lib import handy_functions as hf from pyMBE.lib.handy_functions import do_reaction, define_peptide_AA_residues # Create an instance of pyMBE library @@ -151,7 +150,8 @@ L = volume ** (1./3.) # Side of the simulation box # Create an instance of an espresso system -espresso_system=espressomd.System (box_l = [L.to('reduced_length').magnitude]*3) +box_l=[L.to('reduced_length').magnitude]*3 +espresso_system=espressomd.System (box_l = box_l) espresso_system.time_step=dt espresso_system.cell_system.skin=0.4 @@ -159,17 +159,21 @@ # Create your molecules into the espresso system pmb.create_molecule(name=sequence, number_of_molecules=N_peptide_chains, - espresso_system=espresso_system) + box_l=box_l) # Create counterions for the peptide chains pmb.create_counterions(object_name=sequence, cation_name=cation_name, anion_name=anion_name, - espresso_system=espresso_system) + box_l=box_l) -c_salt_calculated = pmb.create_added_salt(espresso_system=espresso_system, +c_salt_calculated = pmb.create_added_salt( cation_name=cation_name, anion_name=anion_name, - c_salt=c_salt) + c_salt=c_salt, + box_l=box_l) + +pmb.set_simulation_engine(espresso_system) +pmb.add_instances_to_engine() cpH = pmb.setup_cpH(counter_ion=cation_name, constant_pH=pH) @@ -190,18 +194,17 @@ print(f"The non-interacting type is set to {non_interacting_type}") #Setup the potential energy -pmb.setup_lj_interactions (espresso_system=espresso_system) -hf.relax_espresso_system(espresso_system=espresso_system, +pmb.setup_lj_interactions() +pmb.simulation_engine.relax_espresso_system( seed=langevin_seed) -hf.setup_electrostatic_interactions(units=pmb.units, - espresso_system=espresso_system, +pmb.simulation_engine.setup_electrostatic_interactions(units=pmb.units, kT=pmb.kT, verbose=verbose) -hf.relax_espresso_system(espresso_system=espresso_system, +pmb.simulation_engine.relax_espresso_system( seed=langevin_seed) -hf.setup_langevin_dynamics(espresso_system=espresso_system, +pmb.simulation_engine.setup_langevin_dynamics( kT = pmb.kT, seed = langevin_seed, time_step=dt, @@ -223,7 +226,7 @@ # Run MC do_reaction(cpH, steps=len(sequence)) # Sample observables - charge_dict=pmb.calculate_net_charge(espresso_system=espresso_system, + charge_dict=pmb.calculate_net_charge( object_name=sequence, pmb_type="peptide", dimensionless=True) diff --git a/samples/Beyer2024/weak_polyelectrolyte_dialysis.py b/samples/Beyer2024/weak_polyelectrolyte_dialysis.py index 71dc2012..0b15084b 100644 --- a/samples/Beyer2024/weak_polyelectrolyte_dialysis.py +++ b/samples/Beyer2024/weak_polyelectrolyte_dialysis.py @@ -37,9 +37,6 @@ pmb = pyMBE.pymbe_library(seed=42) # Load some functions from the handy_scripts library for convenience -from pyMBE.lib.handy_functions import setup_electrostatic_interactions -from pyMBE.lib.handy_functions import relax_espresso_system -from pyMBE.lib.handy_functions import setup_langevin_dynamics from pyMBE.lib.handy_functions import do_reaction @@ -170,22 +167,25 @@ ####################################################### # Create an instance of an espresso system -espresso_system = espressomd.System(box_l = [L.to('reduced_length').magnitude]*3) +box_l=[L.to('reduced_length').magnitude]*3 +espresso_system = espressomd.System(box_l =box_l ) espresso_system.time_step=dt espresso_system.cell_system.skin=0.4 # Create molecules and ions in the espresso system pmb.create_molecule(name=polyacid_name, number_of_molecules=N_chains, - espresso_system=espresso_system) + box_l=box_l) pmb.create_counterions(object_name=polyacid_name, cation_name=proton_name, anion_name=hydroxide_name, - espresso_system=espresso_system) -c_salt_calculated = pmb.create_added_salt(espresso_system=espresso_system, + box_l=box_l) +c_salt_calculated = pmb.create_added_salt(box_l=box_l, cation_name=sodium_name, anion_name=chloride_name, c_salt=c_salt_res) +pmb.set_simulation_engine(espresso_system) +pmb.add_instances_to_engine() if verbose: print("Created molecules") @@ -220,12 +220,12 @@ grxmc.set_non_interacting_type (type=non_interacting_type) #Set up the interactions -pmb.setup_lj_interactions(espresso_system=espresso_system) +pmb.setup_lj_interactions() # Relax the system -relax_espresso_system(espresso_system=espresso_system, +pmb.simulation_engine.relax_espresso_system( seed=langevin_seed) -setup_langevin_dynamics(espresso_system=espresso_system, +pmb.simulation_engine.setup_langevin_dynamics( kT = pmb.kT, seed = langevin_seed, time_step=dt, @@ -236,16 +236,15 @@ espresso_system.integrator.run(steps=1000) do_reaction(grxmc, steps=1000) -setup_electrostatic_interactions(units=pmb.units, - espresso_system=espresso_system, +pmb.simulation_engine.setup_electrostatic_interactions(units=pmb.units, kT=pmb.kT, solvent_permittivity=solvent_permittivity, verbose=verbose) espresso_system.thermostat.turn_off() -relax_espresso_system(espresso_system=espresso_system, +pmb.simulation_engine.relax_espresso_system( seed=langevin_seed, max_displacement=0.01) -setup_langevin_dynamics(espresso_system=espresso_system, +pmb.simulation_engine.setup_langevin_dynamics( kT = pmb.kT, seed = langevin_seed, time_step=dt, @@ -279,7 +278,7 @@ # Measure time time_series["time"].append(espresso_system.time) # Measure degree of ionization - charge_dict=pmb.calculate_net_charge(espresso_system=espresso_system, + charge_dict=pmb.calculate_net_charge( object_name=polyacid_name, pmb_type="molecule", dimensionless=True) diff --git a/samples/Landsgesell2022/plot_P_vs_V.py b/samples/Landsgesell2022/plot_P_vs_V.py index 069d555a..2130320c 100644 --- a/samples/Landsgesell2022/plot_P_vs_V.py +++ b/samples/Landsgesell2022/plot_P_vs_V.py @@ -39,8 +39,7 @@ unit_length=0.355*pmb.units.nm temperature=300*pmb.units.K pmb.set_reduced_units(unit_length=unit_length, - temperature=temperature, - verbose=False) + temperature=temperature) ref_cs = pmb.units.Quantity(0.00134770889, "1/reduced_length**3") ref_pH = 5 ref_max_L = 89.235257606948 # Maximum chain length for mpc=39 diff --git a/samples/branched_polyampholyte.py b/samples/branched_polyampholyte.py index 5478be72..70715a76 100644 --- a/samples/branched_polyampholyte.py +++ b/samples/branched_polyampholyte.py @@ -26,12 +26,9 @@ import pyMBE # Load some functions from the handy_scripts library for convenience -from pyMBE.lib.handy_functions import setup_langevin_dynamics -from pyMBE.lib.handy_functions import relax_espresso_system -from pyMBE.lib.handy_functions import setup_electrostatic_interactions -from pyMBE.lib.handy_functions import do_reaction from pyMBE.lib.analysis import built_output_name + # Create an instance of pyMBE library pmb = pyMBE.pymbe_library(seed=42) @@ -143,21 +140,22 @@ calculated_polyampholyte_concentration = N_polyampholyte_chains/(volume*pmb.N_A) # Create an instance of an espresso system -espresso_system=espressomd.System(box_l = [L.to('reduced_length').magnitude]*3) +box_l = [L.to('reduced_length').magnitude]*3 +espresso_system=espressomd.System(box_l = box_l) espresso_system.time_step=dt espresso_system.cell_system.skin=0.4 # Create your molecules into the espresso system pmb.create_molecule(name="polyampholyte", number_of_molecules=N_polyampholyte_chains, - espresso_system=espresso_system, + box_l=box_l, use_default_bond=True) pmb.create_counterions(object_name="polyampholyte", cation_name=cation_name, anion_name=anion_name, - espresso_system=espresso_system) + box_l=box_l) -c_salt_calculated = pmb.create_added_salt(espresso_system=espresso_system, +c_salt_calculated = pmb.create_added_salt(box_l=box_l, cation_name=cation_name, anion_name=anion_name, c_salt=c_salt) @@ -174,6 +172,8 @@ print(f"The box length of your system is {L.to('reduced_length')}, {L.to('nm')}") print(f"The polyampholyte concentration in your system is {calculated_polyampholyte_concentration.to('mol/L')} with {N_polyampholyte_chains} molecules") +pmb.set_simulation_engine(espresso_system) + cpH = pmb.setup_cpH(counter_ion=cation_name, constant_pH=pH_value) if verbose: print("The acid-base reaction has been successfully set up for:") @@ -189,30 +189,32 @@ cpH.set_non_interacting_type (type=non_interacting_type) if verbose: print(f"The non interacting type is set to {non_interacting_type}") + + +pmb.add_instances_to_engine() if not ideal: ##Setup the potential energy if verbose: print('Setup LJ interaction (this can take a few seconds)') - pmb.setup_lj_interactions (espresso_system=espresso_system) + pmb.setup_lj_interactions() if verbose: print('Minimize energy before adding electrostatics') - relax_espresso_system(espresso_system=espresso_system, + pmb.simulation_engine.relax_espresso_system( seed=langevin_seed) if verbose: print('Setup and tune electrostatics (this can take a few seconds)') - setup_electrostatic_interactions(units=pmb.units, - espresso_system=espresso_system, + pmb.simulation_engine.setup_electrostatic_interactions(units=pmb.units, kT=pmb.kT) if verbose: print('Minimize energy after adding electrostatics') - relax_espresso_system(espresso_system=espresso_system, + pmb.simulation_engine.relax_espresso_system( seed=langevin_seed) #Setup Langevin -setup_langevin_dynamics(espresso_system=espresso_system, +pmb.simulation_engine.setup_langevin_dynamics( kT = pmb.kT, seed = langevin_seed, time_step=dt, @@ -231,9 +233,9 @@ N_frame=0 for step in tqdm.trange(N_samples): espresso_system.integrator.run(steps=MD_steps_per_sample) - do_reaction(cpH, steps=total_ionisable_groups) + pmb.simulation_engine.do_reaction(cpH, steps=total_ionisable_groups) # Get polyampholyte net charge - charge_dict=pmb.calculate_net_charge(espresso_system=espresso_system, + charge_dict=pmb.calculate_net_charge( object_name="polyampholyte", pmb_type="molecule", dimensionless=True) diff --git a/samples/build_hydrogel.py b/samples/build_hydrogel.py index df6c61db..caf53cde 100644 --- a/samples/build_hydrogel.py +++ b/samples/build_hydrogel.py @@ -149,7 +149,7 @@ def define_hydrogel_with_angular_potential(espresso_system, include_crosslinker_ node_map=node_topology, chain_map=chain_topology) hydrogel_id = pmb.create_hydrogel(name="simple_hydrogel", - espresso_system=espresso_system, + box_l=espresso_system.box_l, gen_angle=True) return pmb, hydrogel_id, lattice_builder diff --git a/samples/peptide_cpH.py b/samples/peptide_cpH.py index 5bd6ed40..046aa326 100644 --- a/samples/peptide_cpH.py +++ b/samples/peptide_cpH.py @@ -29,7 +29,7 @@ pmb = pyMBE.pymbe_library(seed=42) # Load some functions from the handy_scripts library for convenience -from pyMBE.lib.handy_functions import setup_electrostatic_interactions, relax_espresso_system, setup_langevin_dynamics, do_reaction, define_peptide_AA_residues +from pyMBE.lib.handy_functions import define_peptide_AA_residues from pyMBE.lib.analysis import built_output_name parser = argparse.ArgumentParser(description='Sample script to run the pre-made peptide models with pyMBE') @@ -135,27 +135,31 @@ # Create an instance of an espresso system -espresso_system=espressomd.System (box_l = [L.to('reduced_length').magnitude]*3) +box_l=[L.to('reduced_length').magnitude]*3 +espresso_system=espressomd.System (box_l = box_l) espresso_system.time_step=dt espresso_system.cell_system.skin=0.4 # Create your molecules into the espresso system pmb.create_molecule(name=peptide_name, number_of_molecules=N_peptide_chains, - espresso_system=espresso_system, - use_default_bond=True) + use_default_bond=True, + box_l=box_l) # Create counterions for the peptide chains pmb.create_counterions(object_name=peptide_name, cation_name=cation_name, anion_name=anion_name, - espresso_system=espresso_system) + box_l=box_l) # check what is the actual salt concentration in the box # if the number of salt ions is a small integer, then the actual and desired salt concentration may significantly differ -c_salt_calculated = pmb.create_added_salt(espresso_system=espresso_system, +c_salt_calculated = pmb.create_added_salt( cation_name=cation_name, anion_name=anion_name, - c_salt=c_salt) + c_salt=c_salt, + box_l=box_l) +pmb.set_simulation_engine(espresso_system) +pmb.add_instances_to_engine() with open(frames_path / "trajectory0.vtf", mode='w+t') as coordinates: vtf.writevsf(espresso_system, coordinates) @@ -178,6 +182,7 @@ print(f"The peptide concentration in your system is {calculated_peptide_concentration.to('mol/L')} with {N_peptide_chains} peptides") print(f"The ionisable groups in your peptide are {list_ionisable_groups}") +print(pmb.get_reactions_df(),"before the acid-base reaction has been setup") cpH = pmb.setup_cpH(counter_ion=cation_name, constant_pH=pH_value) if verbose: @@ -198,25 +203,24 @@ ##Setup the potential energy if verbose: print('Setup LJ interaction (this can take a few seconds)') - pmb.setup_lj_interactions (espresso_system=espresso_system) + pmb.setup_lj_interactions() if verbose: print('Minimize energy before adding electrostatics') - relax_espresso_system(espresso_system=espresso_system, + pmb.simulation_engine.relax_espresso_system( seed=langevin_seed) if verbose: print('Setup and tune electrostatics (this can take a few seconds)') - setup_electrostatic_interactions(units=pmb.units, - espresso_system=espresso_system, + pmb.simulation_engine.setup_electrostatic_interactions(units=pmb.units, kT=pmb.kT, verbose=verbose) if verbose: print('Minimize energy after adding electrostatics') - relax_espresso_system(espresso_system=espresso_system, + pmb.simulation_engine.relax_espresso_system( seed=langevin_seed) if verbose: print('Setup Langevin dynamics') -setup_langevin_dynamics(espresso_system=espresso_system, +pmb.simulation_engine.setup_langevin_dynamics( kT = pmb.kT, seed = langevin_seed, time_step=dt, @@ -239,9 +243,9 @@ # LD sampling of the configuration space espresso_system.integrator.run(steps=MD_steps_per_sample) # cpH sampling of the reaction space - do_reaction(cpH, steps=total_ionisable_groups) # rule of thumb: one reaction step per titratable group (on average) + pmb.simulation_engine.do_reaction(cpH, steps=total_ionisable_groups) # rule of thumb: one reaction step per titratable group (on average) # Get peptide net charge - charge_dict=pmb.calculate_net_charge(espresso_system=espresso_system, + charge_dict=pmb.calculate_net_charge( object_name=peptide_name, pmb_type="peptide", dimensionless=True) diff --git a/samples/peptide_mixture_grxmc_ideal.py b/samples/peptide_mixture_grxmc_ideal.py index deb35d3b..3cb6a482 100644 --- a/samples/peptide_mixture_grxmc_ideal.py +++ b/samples/peptide_mixture_grxmc_ideal.py @@ -24,7 +24,7 @@ from espressomd.io.writer import vtf import pyMBE from pyMBE.lib.analysis import built_output_name -from pyMBE.lib.handy_functions import do_reaction, define_peptide_AA_residues +from pyMBE.lib.handy_functions import define_peptide_AA_residues # Create an instance of pyMBE library pmb = pyMBE.pymbe_library(seed=42) @@ -189,29 +189,30 @@ calculated_peptide_concentration = N_peptide1_chains/(volume*pmb.N_A) # Create an instance of an espresso system -espresso_system=espressomd.System (box_l = [L.to('reduced_length').magnitude]*3) +box_l=[L.to('reduced_length').magnitude]*3 +espresso_system=espressomd.System (box_l = box_l) # Create your molecules into the espresso system pmb.create_molecule(name=peptide1, number_of_molecules=N_peptide1_chains, - espresso_system=espresso_system, + box_l=box_l, use_default_bond=True) pmb.create_molecule(name=peptide2, number_of_molecules=N_peptide2_chains, - espresso_system=espresso_system, + box_l=box_l, use_default_bond=True) if args.mode == 'standard': pmb.create_counterions(object_name=peptide1, cation_name=proton_name, anion_name=hydroxide_name, - espresso_system=espresso_system) # Create counterions for the peptide chains with sequence 1 + box_l=box_l,) # Create counterions for the peptide chains with sequence 1 pmb.create_counterions(object_name=peptide2, cation_name=proton_name, anion_name=hydroxide_name, - espresso_system=espresso_system) # Create counterions for the peptide chains with sequence 2 + box_l=box_l,) # Create counterions for the peptide chains with sequence 2 - c_salt_calculated = pmb.create_added_salt(espresso_system=espresso_system, + c_salt_calculated = pmb.create_added_salt(box_l=box_l, cation_name=sodium_name, anion_name=chloride_name, c_salt=c_salt) @@ -219,13 +220,13 @@ pmb.create_counterions(object_name=peptide1, cation_name=cation_name, anion_name=anion_name, - espresso_system=espresso_system) # Create counterions for the peptide chains with sequence 1 + box_l=box_l,) # Create counterions for the peptide chains with sequence 1 pmb.create_counterions(object_name=peptide2, cation_name=cation_name, anion_name=anion_name, - espresso_system=espresso_system) # Create counterions for the peptide chains with sequence 2 + box_l=box_l,) # Create counterions for the peptide chains with sequence 2 - c_salt_calculated = pmb.create_added_salt(espresso_system=espresso_system, + c_salt_calculated = pmb.create_added_salt(box_l=box_l, cation_name=cation_name, anion_name=anion_name, c_salt=c_salt) @@ -246,6 +247,9 @@ if verbose: print("The box length of your system is", L.to('reduced_length'), L.to('nm')) +pmb.set_simulation_engine(espresso_system) +pmb.add_instances_to_engine() + if args.mode == 'standard': grxmc, ionic_strength_res = pmb.setup_grxmc_reactions(pH_res=pH_value, c_salt_res=c_salt, @@ -298,14 +302,14 @@ N_frame=0 for step in range(N_samples): espresso_system.integrator.run(steps=MD_steps_per_sample) - do_reaction(grxmc, steps=total_ionisable_groups) + pmb.simulation_engine.do_reaction(grxmc, steps=total_ionisable_groups) time_series["time"].append(espresso_system.time) # Get net charge of peptide1 and peptide2 - charge_dict_peptide1=pmb.calculate_net_charge(espresso_system=espresso_system, + charge_dict_peptide1=pmb.calculate_net_charge( object_name=peptide1, pmb_type="peptide", dimensionless=True) - charge_dict_peptide2=pmb.calculate_net_charge(espresso_system=espresso_system, + charge_dict_peptide2=pmb.calculate_net_charge( object_name=peptide2, pmb_type="peptide", dimensionless=True) diff --git a/samples/salt_solution_gcmc.py b/samples/salt_solution_gcmc.py index 704053cb..9aeb4aba 100644 --- a/samples/salt_solution_gcmc.py +++ b/samples/salt_solution_gcmc.py @@ -29,11 +29,8 @@ import pyMBE from pyMBE.lib import analysis #Import functions from handy_functions script -from pyMBE.lib.handy_functions import relax_espresso_system -from pyMBE.lib.handy_functions import setup_electrostatic_interactions -from pyMBE.lib.handy_functions import setup_langevin_dynamics + from pyMBE.lib.handy_functions import get_number_of_particles -from pyMBE.lib.handy_functions import do_reaction # Create an instance of pyMBE library pmb = pyMBE.pymbe_library(seed=42) @@ -94,15 +91,21 @@ L = volume ** (1./3.) # Side of the simulation box # Create an instance of an espresso system -espresso_system=espressomd.System (box_l = [L.to('reduced_length').magnitude]*3) +box_l= [L.to('reduced_length').magnitude]*3 +espresso_system=espressomd.System (box_l =box_l) +pmb.set_simulation_engine(espresso_system) + if verbose: print("Created espresso object") # Add salt -c_salt_calculated = pmb.create_added_salt(espresso_system=espresso_system, +c_salt_calculated = pmb.create_added_salt(box_l=box_l, cation_name=cation_name, anion_name=anion_name, + c_salt=0.5*c_salt_res) +pmb.add_instances_to_engine() + if verbose: print("Added salt") @@ -142,12 +145,12 @@ espresso_system.cell_system.skin=0.4 if args.mode == "interacting": #Set up the short-range interactions - pmb.setup_lj_interactions(espresso_system=espresso_system) + pmb.setup_lj_interactions() # Minimzation -relax_espresso_system(espresso_system=espresso_system, +pmb.simulation_engine.relax_espresso_system( seed=langevin_seed) -setup_langevin_dynamics(espresso_system=espresso_system, +pmb.simulation_engine.setup_langevin_dynamics( kT = pmb.kT, seed = langevin_seed, time_step=dt, @@ -157,18 +160,17 @@ print("Running warmup without electrostatics") for i in tqdm.trange(100, disable=not verbose): espresso_system.integrator.run(steps=100) - do_reaction(RE, steps=100) + pmb.simulation_engine.do_reaction(RE, steps=100) if args.mode == "interacting": - setup_electrostatic_interactions(units=pmb.units, - espresso_system=espresso_system, + pmb.simulation_engine.setup_electrostatic_interactions(units=pmb.units, kT=pmb.kT, solvent_permittivity=solvent_permittivity) espresso_system.thermostat.turn_off() -relax_espresso_system(espresso_system=espresso_system, +pmb.simulation_engine.relax_espresso_system( seed=langevin_seed) -setup_langevin_dynamics(espresso_system=espresso_system, +pmb.simulation_engine.setup_langevin_dynamics( kT = pmb.kT, seed = langevin_seed, time_step=dt, @@ -180,7 +182,7 @@ N_warmup_loops = 100 for i in tqdm.trange(N_warmup_loops, disable=not verbose): espresso_system.integrator.run(steps=100) - do_reaction(RE, steps=100) + pmb.simulation_engine.do_reaction(RE, steps=100) # Main loop print("Started production run.") @@ -194,7 +196,7 @@ N_production_loops = 100 for i in tqdm.trange(N_production_loops, disable=not verbose): espresso_system.integrator.run(steps=100) - do_reaction(RE, steps=100) + pmb.simulation_engine.do_reaction(RE, steps=100) # Measure time time_series["time"].append(espresso_system.time) diff --git a/samples/setup_angular_potential.py b/samples/setup_angular_potential.py index 4f6bb3da..8c12b116 100644 --- a/samples/setup_angular_potential.py +++ b/samples/setup_angular_potential.py @@ -36,7 +36,8 @@ # ---------------------------------------------------------------------- # Set up the ESPResSo system and pyMBE # ---------------------------------------------------------------------- -espresso_system = espressomd.System(box_l=[20] * 3) +box_l=[20] * 3 +espresso_system = espressomd.System(box_l=box_l) pmb = pyMBE.pymbe_library(seed=42) # ---------------------------------------------------------------------- @@ -102,7 +103,7 @@ def show_database(label): side_chains=["A", "B", "C"]) residue_id = pmb.create_residue(name="Res_1", - espresso_system=espresso_system, + box_l=box_l, use_default_bond=True, gen_angle=True) @@ -113,8 +114,7 @@ def show_database(label): # both ESPResSo and the pyMBE database before running Demo 2. # ---------------------------------------------------------------------- pmb.delete_instances_in_system(instance_id=residue_id, - pmb_type="residue", - espresso_system=espresso_system) + pmb_type="residue") # ====================================================================== @@ -146,7 +146,7 @@ def show_database(label): side_chains=["D", "SubRes_3"]) nested_residue_id = pmb.create_residue(name="NestedRes_3", - espresso_system=espresso_system, + box_l=box_l, use_default_bond=True, gen_angle=True) @@ -188,7 +188,7 @@ def show_database(label): molecule_ids = pmb.create_molecule(name="Mol_1", number_of_molecules=1, - espresso_system=espresso_system, + box_l=box_l, backbone_vector=[1.0, 0.0, 0.0], use_default_bond=True, gen_angle=True) @@ -196,6 +196,6 @@ def show_database(label): show_database("Demo 3: linear molecule with gen_angle=True") pmb.delete_instances_in_system(instance_id=molecule_ids[0], - pmb_type="molecule", - espresso_system=espresso_system) + pmb_type="molecule" + ) diff --git a/samples/weak_polyacid_hydrogel_grxmc.py b/samples/weak_polyacid_hydrogel_grxmc.py index 2e883b85..944118ee 100644 --- a/samples/weak_polyacid_hydrogel_grxmc.py +++ b/samples/weak_polyacid_hydrogel_grxmc.py @@ -33,10 +33,6 @@ pmb = pyMBE.pymbe_library(seed=42) # Load some functions from the handy_scripts library for convenience -from pyMBE.lib.handy_functions import setup_electrostatic_interactions -from pyMBE.lib.handy_functions import relax_espresso_system -from pyMBE.lib.handy_functions import setup_langevin_dynamics -from pyMBE.lib.handy_functions import do_reaction ####################################################### # Setting parameters for the simulation @@ -142,7 +138,8 @@ epsilon=1*pmb.units('reduced_energy')) diamond_lattice = DiamondLattice(args.mpc, bond_length) -espresso_system = espressomd.System(box_l = [diamond_lattice.box_l]*3) +box_l=[diamond_lattice.box_l]*3 +espresso_system = espressomd.System(box_l = box_l) lattice_builder = pmb.initialize_lattice_builder(diamond_lattice) # Setting up node topology @@ -163,12 +160,14 @@ 'molecule_name':molecule_name}) pmb.define_hydrogel("my_hydrogel", node_topology, chain_topology) -hydrogel_info = pmb.create_hydrogel("my_hydrogel", espresso_system) +hydrogel_info = pmb.create_hydrogel("my_hydrogel", box_l=box_l) -c_salt_calculated = pmb.create_added_salt(espresso_system=espresso_system, +c_salt_calculated = pmb.create_added_salt(box_l=box_l, cation_name=sodium_name, anion_name=chloride_name, c_salt=c_salt_res) +pmb.set_simulation_engine(espresso_system) +pmb.add_instances_to_engine() print(f"Salt concentration {c_salt_calculated.to('mol/L')} added") @@ -176,7 +175,7 @@ dt = 0.01 # Timestep espresso_system.time_step = dt -pmb.setup_lj_interactions(espresso_system=espresso_system) +pmb.setup_lj_interactions() #Save the initial state n_frame = 0 frames_dir = data_path / "frames" @@ -186,11 +185,11 @@ vtf.writevcf(espresso_system, coordinates) print("*** Relaxing the system... ***") -relax_espresso_system(espresso_system=espresso_system, +pmb.simulation_engine.relax_espresso_system( seed=seed, Nsteps_iter_relax=100) -setup_langevin_dynamics(espresso_system=espresso_system, +pmb.simulation_engine.setup_langevin_dynamics( kT = pmb.kT, seed = seed, time_step=dt, @@ -210,7 +209,7 @@ # Just to make sure that the system has the target size espresso_system.change_volume_and_rescale_particles( d_new=L_target, dir="xyz") -relax_espresso_system(espresso_system=espresso_system, +pmb.simulation_engine.relax_espresso_system( seed=seed, Nsteps_iter_relax=100, max_displacement=0.01) @@ -247,10 +246,9 @@ for i in tqdm.trange(100): espresso_system.integrator.run(steps=1000) - do_reaction(grxmc,1000) + pmb.simulation_engine.do_reaction(grxmc,1000) -setup_electrostatic_interactions(units=pmb.units, - espresso_system=espresso_system, +pmb.simulation_engine.setup_electrostatic_interactions(units=pmb.units, kT=pmb.kT, solvent_permittivity=solvent_permittivity) @@ -262,7 +260,7 @@ print("*** Running warmup with electrostatics... ***") for i in tqdm.trange(N_warmup_loops): espresso_system.integrator.run(steps=1000) - do_reaction(grxmc,100) + pmb.simulation_engine.do_reaction(grxmc,100) # Main loop print("*** Starting production run... ***") @@ -282,7 +280,7 @@ for i in tqdm.trange(N_production_loops): espresso_system.integrator.run(steps=1000) - do_reaction(grxmc,200) + pmb.simulation_engine.do_reaction(grxmc,200) # Measure time time_series["time"].append(espresso_system.time) diff --git a/testsuite/angle_tests.py b/testsuite/angle_tests.py index 14f97f8f..d022670e 100644 --- a/testsuite/angle_tests.py +++ b/testsuite/angle_tests.py @@ -23,7 +23,8 @@ import espressomd # Create an instance of the ESPResSo system -espresso_system = espressomd.System(box_l=[10]*3) +box_l=[10]*3 +espresso_system = espressomd.System(box_l=box_l) class Test(ut.TestCase): @@ -125,29 +126,28 @@ def test_angle_setup_harmonic(self): # Create three particles: A, B, C pid_A = pmb.create_particle(name="A", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) pid_B = pmb.create_particle(name="B", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) pid_C = pmb.create_particle(name="C", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) # Create bonds: A-B and B-C pmb.create_bond(particle_id1=pid_A[0], - particle_id2=pid_B[0], - espresso_system=espresso_system) + particle_id2=pid_B[0]) pmb.create_bond(particle_id1=pid_B[0], - particle_id2=pid_C[0], - espresso_system=espresso_system) + particle_id2=pid_C[0]) # Create the angle: A-B-C (B is central) + pmb.create_angular_potential(particle_id1=pid_A[0], particle_id2=pid_B[0], - particle_id3=pid_C[0], - espresso_system=espresso_system) - + particle_id3=pid_C[0]) + pmb.set_simulation_engine(espresso_system) + pmb.add_instances_to_engine() angle_object = self.get_angle_object(central_particle_id=pid_B[0]) self.assertIsNotNone(angle_object, "No angle object found on the central particle") @@ -158,8 +158,7 @@ def test_angle_setup_harmonic(self): # Clean-up for inst_id in pid_A + pid_B + pid_C: pmb.delete_instances_in_system(instance_id=inst_id, - pmb_type="particle", - espresso_system=espresso_system) + pmb_type="particle") pmb.db.delete_templates(pmb_type="angle") pmb.db.delete_templates(pmb_type="bond") @@ -182,27 +181,29 @@ def test_angle_setup_cosine(self): # Create three particles pid_A1 = pmb.create_particle(name="A", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) pid_B = pmb.create_particle(name="B", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) pid_A2 = pmb.create_particle(name="A", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) # Create bonds: A1-B and B-A2 pmb.create_bond(particle_id1=pid_A1[0], particle_id2=pid_B[0], - espresso_system=espresso_system) + ) pmb.create_bond(particle_id1=pid_B[0], particle_id2=pid_A2[0], - espresso_system=espresso_system) + ) pmb.create_angular_potential(particle_id1=pid_A1[0], particle_id2=pid_B[0], particle_id3=pid_A2[0], - espresso_system=espresso_system) + ) + pmb.set_simulation_engine(espresso_system) + pmb.add_instances_to_engine() angle_object = self.get_angle_object(central_particle_id=pid_B[0]) self.assertIsNotNone(angle_object, "No angle object found on the central particle") @@ -214,8 +215,7 @@ def test_angle_setup_cosine(self): # Clean-up for inst_id in pid_A1 + pid_B + pid_A2: pmb.delete_instances_in_system(instance_id=inst_id, - pmb_type="particle", - espresso_system=espresso_system) + pmb_type="particle") pmb.db.delete_templates(pmb_type="angle") pmb.db.delete_templates(pmb_type="bond") @@ -235,27 +235,25 @@ def test_angle_setup_harmonic_cosine(self): particle_triplets=[('A', 'B', 'C')]) pid_A = pmb.create_particle(name="A", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) pid_B = pmb.create_particle(name="B", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) pid_C = pmb.create_particle(name="C", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) pmb.create_bond(particle_id1=pid_A[0], - particle_id2=pid_B[0], - espresso_system=espresso_system) + particle_id2=pid_B[0]) pmb.create_bond(particle_id1=pid_B[0], - particle_id2=pid_C[0], - espresso_system=espresso_system) + particle_id2=pid_C[0]) pmb.create_angular_potential(particle_id1=pid_A[0], particle_id2=pid_B[0], - particle_id3=pid_C[0], - espresso_system=espresso_system) - + particle_id3=pid_C[0]) + pmb.set_simulation_engine(espresso_system) + pmb.add_instances_to_engine() angle_object = self.get_angle_object(central_particle_id=pid_B[0]) self.assertIsNotNone(angle_object, "No angle object found on the central particle") @@ -265,8 +263,7 @@ def test_angle_setup_harmonic_cosine(self): for inst_id in pid_A + pid_B + pid_C: pmb.delete_instances_in_system(instance_id=inst_id, - pmb_type="particle", - espresso_system=espresso_system) + pmb_type="particle") pmb.db.delete_templates(pmb_type="angle") pmb.db.delete_templates(pmb_type="bond") @@ -284,11 +281,11 @@ def test_get_espresso_angle_instance_reuses_cached_object(self): angle_template = pmb.get_angle_template(side_name1="A", central_name="B", side_name2="C") - - first_angle_object = pmb._get_espresso_angle_instance(angle_template=angle_template, - espresso_system=espresso_system) - second_angle_object = pmb._get_espresso_angle_instance(angle_template=angle_template, - espresso_system=espresso_system) + + pmb.set_simulation_engine(espresso_system) + print(espresso_system,"espresso_system") + first_angle_object = pmb._get_espresso_angle_instance(angle_template=angle_template) + second_angle_object = pmb._get_espresso_angle_instance(angle_template=angle_template) self.assertIs(first_angle_object, second_angle_object) self.assertEqual(len(pmb.db.espresso_angle_instances), 1) @@ -313,30 +310,28 @@ def test_default_angle(self): # Create three particles pid_A = pmb.create_particle(name="A", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) pid_B = pmb.create_particle(name="B", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) pid_C = pmb.create_particle(name="C", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) # Create bonds: A-B and B-C pmb.create_bond(particle_id1=pid_A[0], - particle_id2=pid_B[0], - espresso_system=espresso_system) + particle_id2=pid_B[0]) pmb.create_bond(particle_id1=pid_B[0], - particle_id2=pid_C[0], - espresso_system=espresso_system) + particle_id2=pid_C[0]) # Create angle using default (no specific A-B-C template exists) pmb.create_angular_potential(particle_id1=pid_A[0], particle_id2=pid_B[0], particle_id3=pid_C[0], - espresso_system=espresso_system, use_default_angle=True) - + pmb.set_simulation_engine(espresso_system) + pmb.add_instances_to_engine() angle_object = self.get_angle_object(central_particle_id=pid_B[0]) self.assertIsNotNone(angle_object, "No angle object found on the central particle") @@ -347,8 +342,7 @@ def test_default_angle(self): # Clean-up for inst_id in pid_A + pid_B + pid_C: pmb.delete_instances_in_system(instance_id=inst_id, - pmb_type="particle", - espresso_system=espresso_system) + pmb_type="particle") pmb.db.delete_templates(pmb_type="angle") pmb.db.delete_templates(pmb_type="bond") @@ -374,30 +368,28 @@ def test_specific_angle_over_default(self): particle_triplets=[('A', 'B', 'C')]) pid_A = pmb.create_particle(name="A", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) pid_B = pmb.create_particle(name="B", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) pid_C = pmb.create_particle(name="C", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) # Create bonds: A-B and B-C pmb.create_bond(particle_id1=pid_A[0], - particle_id2=pid_B[0], - espresso_system=espresso_system) + particle_id2=pid_B[0]) pmb.create_bond(particle_id1=pid_B[0], - particle_id2=pid_C[0], - espresso_system=espresso_system) + particle_id2=pid_C[0]) # Should use the specific A-B-C template, not the default pmb.create_angular_potential(particle_id1=pid_A[0], particle_id2=pid_B[0], particle_id3=pid_C[0], - espresso_system=espresso_system, use_default_angle=True) - + pmb.set_simulation_engine(espresso_system) + pmb.add_instances_to_engine() angle_object = self.get_angle_object(central_particle_id=pid_B[0]) self.assertIsNotNone(angle_object) @@ -409,8 +401,7 @@ def test_specific_angle_over_default(self): # Clean-up for inst_id in pid_A + pid_B + pid_C: pmb.delete_instances_in_system(instance_id=inst_id, - pmb_type="particle", - espresso_system=espresso_system) + pmb_type="particle") pmb.db.delete_templates(pmb_type="angle") pmb.db.delete_templates(pmb_type="bond") @@ -519,27 +510,26 @@ def test_angle_without_bonds_raises_error(self): # Create three particles without any bonds between them pid_A = pmb.create_particle(name="A", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) pid_B = pmb.create_particle(name="B", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) pid_C = pmb.create_particle(name="C", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) - + pmb.set_simulation_engine(espresso_system) + pmb.add_instances_to_engine() # Attempting to create angle without bonds should raise ValueError with self.assertRaises(ValueError, msg="create_angular_potential should raise ValueError when no bonds exist"): pmb.create_angular_potential(particle_id1=pid_A[0], particle_id2=pid_B[0], - particle_id3=pid_C[0], - espresso_system=espresso_system) + particle_id3=pid_C[0]) # Clean-up for inst_id in pid_A + pid_B + pid_C: pmb.delete_instances_in_system(instance_id=inst_id, - pmb_type="particle", - espresso_system=espresso_system) + pmb_type="particle") pmb.db.delete_templates(pmb_type="angle") def test_angle_with_partial_bonds_raises_error(self): @@ -559,32 +549,30 @@ def test_angle_with_partial_bonds_raises_error(self): particle_triplets=[('A', 'B', 'C')]) pid_A = pmb.create_particle(name="A", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) pid_B = pmb.create_particle(name="B", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) pid_C = pmb.create_particle(name="C", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) # Create only the A-B bond, not B-C pmb.create_bond(particle_id1=pid_A[0], - particle_id2=pid_B[0], - espresso_system=espresso_system) - + particle_id2=pid_B[0]) + pmb.set_simulation_engine(espresso_system) + pmb.add_instances_to_engine() # Should raise ValueError because B-C bond is missing with self.assertRaises(ValueError, msg="create_angular_potential should raise ValueError when a bond is missing"): pmb.create_angular_potential(particle_id1=pid_A[0], particle_id2=pid_B[0], - particle_id3=pid_C[0], - espresso_system=espresso_system) + particle_id3=pid_C[0]) # Clean-up for inst_id in pid_A + pid_B + pid_C: pmb.delete_instances_in_system(instance_id=inst_id, - pmb_type="particle", - espresso_system=espresso_system) + pmb_type="particle") pmb.db.delete_templates(pmb_type="angle") pmb.db.delete_templates(pmb_type="bond") @@ -595,7 +583,7 @@ def test_generate_angles_for_entity_without_particles_returns(self): pmb = pyMBE.pymbe_library(seed=42) self.define_templates(pmb) - pmb._generate_angles_for_entity(espresso_system=espresso_system, + pmb._generate_angles_for_entity( entity_id=999, entity_id_col="residue_id") @@ -616,15 +604,15 @@ def test_generate_angles_for_entity_creates_angle_from_bonds(self): particle_triplets=[('A', 'B', 'C')]) pid_A = pmb.create_particle(name="A", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) pid_B = pmb.create_particle(name="B", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) pid_C = pmb.create_particle(name="C", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) - + for particle_id in (pid_A[0], pid_B[0], pid_C[0]): pmb.db._update_instance(instance_id=particle_id, pmb_type="particle", @@ -632,24 +620,23 @@ def test_generate_angles_for_entity_creates_angle_from_bonds(self): value=0) pmb.create_bond(particle_id1=pid_A[0], - particle_id2=pid_B[0], - espresso_system=espresso_system) + particle_id2=pid_B[0]) pmb.create_bond(particle_id1=pid_B[0], - particle_id2=pid_C[0], - espresso_system=espresso_system) + particle_id2=pid_C[0]) + - pmb._generate_angles_for_entity(espresso_system=espresso_system, + pmb._generate_angles_for_entity( entity_id=0, entity_id_col="residue_id") - + pmb.set_simulation_engine(espresso_system) + pmb.add_instances_to_engine() angle_object = self.get_angle_object(central_particle_id=pid_B[0]) self.assertIsNotNone(angle_object, "No angle object found on the central particle") self.assertEqual(len(pmb.get_instances_df(pmb_type="angle")), 1) for inst_id in pid_A + pid_B + pid_C: pmb.delete_instances_in_system(instance_id=inst_id, - pmb_type="particle", - espresso_system=espresso_system) + pmb_type="particle") pmb.db.delete_templates(pmb_type="angle") pmb.db.delete_templates(pmb_type="bond") @@ -665,15 +652,15 @@ def test_generate_angles_for_entity_skips_missing_templates(self): particle_pairs=[['A', 'B'], ['B', 'C']]) pid_A = pmb.create_particle(name="A", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) pid_B = pmb.create_particle(name="B", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) pid_C = pmb.create_particle(name="C", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) - + for particle_id in (pid_A[0], pid_B[0], pid_C[0]): pmb.db._update_instance(instance_id=particle_id, pmb_type="particle", @@ -681,13 +668,12 @@ def test_generate_angles_for_entity_skips_missing_templates(self): value=0) pmb.create_bond(particle_id1=pid_A[0], - particle_id2=pid_B[0], - espresso_system=espresso_system) + particle_id2=pid_B[0]) pmb.create_bond(particle_id1=pid_B[0], - particle_id2=pid_C[0], - espresso_system=espresso_system) - - pmb._generate_angles_for_entity(espresso_system=espresso_system, + particle_id2=pid_C[0]) + pmb.set_simulation_engine(espresso_system) + pmb.add_instances_to_engine() + pmb._generate_angles_for_entity( entity_id=0, entity_id_col="residue_id") @@ -696,8 +682,7 @@ def test_generate_angles_for_entity_skips_missing_templates(self): for inst_id in pid_A + pid_B + pid_C: pmb.delete_instances_in_system(instance_id=inst_id, - pmb_type="particle", - espresso_system=espresso_system) + pmb_type="particle") pmb.db.delete_templates(pmb_type="bond") def test_create_residue_with_gen_angle_generates_angles(self): @@ -718,9 +703,10 @@ def test_create_residue_with_gen_angle_generates_angles(self): side_chains=["A", "C"]) residue_id = pmb.create_residue(name="R_angle", - espresso_system=espresso_system, + box_l=box_l, gen_angle=True) - + pmb.set_simulation_engine(espresso_system) + pmb.add_instances_to_engine() particle_ids = pmb.db._find_instance_ids_by_attribute(pmb_type="particle", attribute="residue_id", value=residue_id) @@ -733,8 +719,7 @@ def test_create_residue_with_gen_angle_generates_angles(self): self.assertEqual(len(pmb.get_instances_df(pmb_type="angle")), 1) pmb.delete_instances_in_system(instance_id=residue_id, - pmb_type="residue", - espresso_system=espresso_system) + pmb_type="residue") pmb.db.delete_templates(pmb_type="angle") pmb.db.delete_templates(pmb_type="bond") @@ -765,9 +750,10 @@ def test_create_molecule_with_gen_angle_generates_angles(self): molecule_ids = pmb.create_molecule(name="M_angle", number_of_molecules=1, - espresso_system=espresso_system, + box_l=box_l, gen_angle=True) - + pmb.set_simulation_engine(espresso_system) + pmb.add_instances_to_engine() particle_ids = pmb.db._find_instance_ids_by_attribute(pmb_type="particle", attribute="molecule_id", value=molecule_ids[0]) @@ -780,8 +766,7 @@ def test_create_molecule_with_gen_angle_generates_angles(self): self.assertEqual(len(pmb.get_instances_df(pmb_type="angle")), 1) pmb.delete_instances_in_system(instance_id=molecule_ids[0], - pmb_type="molecule", - espresso_system=espresso_system) + pmb_type="molecule") pmb.db.delete_templates(pmb_type="angle") pmb.db.delete_templates(pmb_type="bond") diff --git a/testsuite/bond_tests.py b/testsuite/bond_tests.py index bd3b2f8d..bbac0d16 100644 --- a/testsuite/bond_tests.py +++ b/testsuite/bond_tests.py @@ -23,7 +23,8 @@ import espressomd # Create an instance of pyMBE library -espresso_system=espressomd.System (box_l = [10]*3) +box_l=[10]*3 +espresso_system=espressomd.System (box_l = box_l) @@ -90,14 +91,16 @@ def test_bond_setup(self): particle_pairs = [['A', 'A']]) # Create two particles pids = pmb.create_particle(name="A", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=2) pmb.create_bond(particle_id1=pids[0], particle_id2=pids[1], - espresso_system=espresso_system, use_default_bond=False) + pmb.set_simulation_engine(espresso_system) + pmb.add_instances_to_engine() + bond_object = self.get_bond_object(particle_id_pair=pids) self.check_bond_setup(bond_object=bond_object, @@ -106,10 +109,9 @@ def test_bond_setup(self): # Clean-up database for inst_id in pids: pmb.delete_instances_in_system(instance_id=inst_id, - pmb_type="particle", - espresso_system=espresso_system) + pmb_type="particle") pid_A = pmb.create_particle(name="A", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) harmonic_params_test = {'r_0' : 0.5 * pmb.units.nm, @@ -119,7 +121,7 @@ def test_bond_setup(self): particle_pairs = [['A', 'B']]) pid_B = pmb.create_particle(name="B", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) # Test that the bond is properly setup when there is a default bond @@ -128,9 +130,10 @@ def test_bond_setup(self): pmb.create_bond(particle_id1=pid_B[0], particle_id2=pid_A[0], - espresso_system=espresso_system, use_default_bond=True) + pmb.add_instances_to_engine() + bond_object = self.get_bond_object(particle_id_pair=[pid_B[0],pid_A[0]]) self.check_bond_setup(bond_object=bond_object, @@ -139,8 +142,7 @@ def test_bond_setup(self): # Clean-up database for inst_id in pid_B+pid_A: pmb.delete_instances_in_system(instance_id=inst_id, - pmb_type="particle", - espresso_system=espresso_system) + pmb_type="particle") pmb.db.delete_templates(pmb_type="bond") # Test setup of FENE bonds @@ -149,14 +151,15 @@ def test_bond_setup(self): particle_pairs = [['A', 'A']]) # Create two particles pids = pmb.create_particle(name="A", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=2) pmb.create_bond(particle_id1=pids[0], particle_id2=pids[1], - espresso_system=espresso_system, use_default_bond=False) + pmb.add_instances_to_engine() + bond_object = self.get_bond_object(particle_id_pair=pids) self.check_bond_setup(bond_object=bond_object, @@ -165,10 +168,9 @@ def test_bond_setup(self): # Clean-up database for inst_id in pids: pmb.delete_instances_in_system(instance_id=inst_id, - pmb_type="particle", - espresso_system=espresso_system) + pmb_type="particle") pid_A = pmb.create_particle(name="A", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) FENE_params_test = {'r_0' : 0.5 * pmb.units.nm, @@ -179,7 +181,7 @@ def test_bond_setup(self): particle_pairs = [['A', 'B']]) pid_B = pmb.create_particle(name="B", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) # Test that the FENE bond is properly setup when there is a default bond @@ -188,9 +190,10 @@ def test_bond_setup(self): pmb.create_bond(particle_id1=pid_B[0], particle_id2=pid_A[0], - espresso_system=espresso_system, use_default_bond=True) + pmb.add_instances_to_engine() + bond_object = self.get_bond_object(particle_id_pair=[pid_B[0],pid_A[0]]) self.check_bond_setup(bond_object=bond_object, @@ -199,8 +202,7 @@ def test_bond_setup(self): # Clean-up database for inst_id in pid_B+pid_A: pmb.delete_instances_in_system(instance_id=inst_id, - pmb_type="particle", - espresso_system=espresso_system) + pmb_type="particle") pmb.db.delete_templates(pmb_type="bond") @@ -209,14 +211,15 @@ def test_bond_setup(self): bond_parameters = self.harmonic_params) pids = pmb.create_particle(name="A", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=2) pmb.create_bond(particle_id1=pids[0], particle_id2=pids[1], - espresso_system=espresso_system, use_default_bond=True) + pmb.add_instances_to_engine() + bond_object = self.get_bond_object(particle_id_pair=pids) self.check_bond_setup(bond_object=bond_object, @@ -225,16 +228,15 @@ def test_bond_setup(self): # Clean-up database for inst_id in pids: pmb.delete_instances_in_system(instance_id=inst_id, - pmb_type="particle", - espresso_system=espresso_system) + pmb_type="particle") pmb.db.delete_templates(pmb_type="bond") # Test setup of default bond when there are other bonds defined pid_A = pmb.create_particle(name="A", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) pid_B = pmb.create_particle(name="B", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) pmb.define_default_bond(bond_type = "FENE", @@ -245,9 +247,10 @@ def test_bond_setup(self): pmb.create_bond(particle_id1=pid_B[0], particle_id2=pid_A[0], - espresso_system=espresso_system, use_default_bond=True) + pmb.add_instances_to_engine() + bond_object = self.get_bond_object(particle_id_pair=[pid_B[0],pid_A[0]]) self.check_bond_setup(bond_object=bond_object, @@ -256,8 +259,7 @@ def test_bond_setup(self): # Clean-up database for inst_id in pid_B+pid_A: pmb.delete_instances_in_system(instance_id=inst_id, - pmb_type="particle", - espresso_system=espresso_system) + pmb_type="particle") pmb.db.delete_templates(pmb_type="bond") def test_bond_raised_exceptions(self): diff --git a/testsuite/calculate_net_charge_unit_test.py b/testsuite/calculate_net_charge_unit_test.py index 2f3992e8..ca6432f2 100644 --- a/testsuite/calculate_net_charge_unit_test.py +++ b/testsuite/calculate_net_charge_unit_test.py @@ -68,7 +68,8 @@ residue_list = ['R1']*2+['R2']*3) # Create an instance of an espresso system -espresso_system=espressomd.System(box_l = [10]*3) +box_l=[10]*3 +espresso_system=espressomd.System(box_l = box_l ) # Create your molecules into the espresso system @@ -93,9 +94,10 @@ node_topology, chain_topology) pmb.create_hydrogel(name="my_hydrogel", - espresso_system=espresso_system, - use_default_bond=True) - + use_default_bond=True, + box_l=box_l) +pmb.set_simulation_engine(espresso_system) +pmb.add_instances_to_engine() class Test(ut.TestCase): def test_calculate_net_charge_with_units(self): """ @@ -104,16 +106,14 @@ def test_calculate_net_charge_with_units(self): # Check that it calculates properly the charge of the whole hydrogel charge_map = pmb.calculate_net_charge(object_name="my_hydrogel", - pmb_type="hydrogel", - espresso_system=espresso_system) + pmb_type="hydrogel") np.testing.assert_equal(charge_map["mean"], 40.0*pmb.units.Quantity(1,'reduced_charge')) np.testing.assert_equal(charge_map["instances"], {0: 40.0*pmb.units.Quantity(1,'reduced_charge')}) # Check that it calculates properly the charge of the chains in the hydrogel charge_map = pmb.calculate_net_charge(object_name=molecule_name, - pmb_type="molecule", - espresso_system=espresso_system) + pmb_type="molecule") # Check mean charge np.testing.assert_equal(charge_map["mean"], 2.0*pmb.units.Quantity(1,'reduced_charge')) # Check molecule charge map @@ -137,11 +137,9 @@ def test_calculate_net_charge_with_units(self): # Check that it calculates properly the charge of the residues in the hydrogel charge_map_r1 = pmb.calculate_net_charge(object_name="R1", - pmb_type="residue", - espresso_system=espresso_system) + pmb_type="residue") charge_map_r2 = pmb.calculate_net_charge(object_name="R2", - pmb_type="residue", - espresso_system=espresso_system) + pmb_type="residue") res_charge_map = charge_map_r1["instances"] | charge_map_r2["instances"] np.testing.assert_equal(res_charge_map[0], 1.0*pmb.units.Quantity(1,'reduced_charge')) np.testing.assert_equal(res_charge_map[1], 1.0*pmb.units.Quantity(1,'reduced_charge')) @@ -157,14 +155,12 @@ def test_calculate_net_charge_without_units(self): # Check that it calculates properly the charge of the whole hydrogel charge_map = pmb.calculate_net_charge(object_name="my_hydrogel", pmb_type="hydrogel", - espresso_system=espresso_system, dimensionless=True) np.testing.assert_equal(charge_map["mean"], 40.0) np.testing.assert_equal(charge_map["instances"], {0: 40.0}) # Check the case where the returned charge does not have a dimension charge_map = pmb.calculate_net_charge(object_name=molecule_name, pmb_type="molecule", - espresso_system=espresso_system, dimensionless=True) # Check mean charge np.testing.assert_equal(charge_map["mean"], 2.0) @@ -188,11 +184,9 @@ def test_calculate_net_charge_without_units(self): 15: 2.0}) charge_map_r1 = pmb.calculate_net_charge(object_name="R1", pmb_type="residue", - espresso_system=espresso_system, dimensionless=True) charge_map_r2 = pmb.calculate_net_charge(object_name="R2", pmb_type="residue", - espresso_system=espresso_system, dimensionless=True) res_charge_map = charge_map_r1["instances"] | charge_map_r2["instances"] np.testing.assert_equal(res_charge_map[0], 1.0) diff --git a/testsuite/create_molecule_position_test.py b/testsuite/create_molecule_position_test.py index f6edb529..cbbc0583 100644 --- a/testsuite/create_molecule_position_test.py +++ b/testsuite/create_molecule_position_test.py @@ -49,7 +49,9 @@ # Create an instance of an espresso system L = 52 -espresso_system=espressomd.System(box_l = [L]*3) +box_l=[L]*3 +espresso_system=espressomd.System(box_l = box_l) +pmb.set_simulation_engine(espresso_system) pos_list = [[10,10,10], [20,20,20], [30,30,30]] class Test(ut.TestCase): @@ -59,9 +61,11 @@ def test_create_molecule_at_position(self): """ molecule_ids = pmb.create_molecule(name=molecule_name, number_of_molecules= 3, - espresso_system=espresso_system, + box_l=box_l, use_default_bond=True, list_of_first_residue_positions = pos_list) + + pmb.add_instances_to_engine() particle_id_map = pmb.get_particle_id_map(object_name=molecule_name) central_bead_pos = [] for molecule_id in molecule_ids: @@ -72,8 +76,7 @@ def test_create_molecule_at_position(self): central_bead_pos) for molid in molecule_ids: pmb.delete_instances_in_system(instance_id=molid, - pmb_type="molecule", - espresso_system=espresso_system) + pmb_type="molecule") def test_sanity_create_molecule(self): """ @@ -83,7 +86,7 @@ def test_sanity_create_molecule(self): # Check that create_molecule raises a ValueError if the user does not provide a nested list for list_of_first_residue_positions input_parameters={"name": "generic_molecule", "number_of_molecules": 1, - "espresso_system": espresso_system, + "box_l": box_l, "list_of_first_residue_positions": [1,2,3]} self.assertRaises(ValueError, pmb.create_molecule, @@ -91,7 +94,7 @@ def test_sanity_create_molecule(self): # Check that create_molecule raises a ValueError if the user does not provide a nested list with three coordinates input_parameters={"name": "generic_molecule", "number_of_molecules": 1, - "espresso_system": espresso_system, + "box_l": box_l, "list_of_first_residue_positions": [[1,2]]} self.assertRaises(ValueError, pmb.create_molecule, @@ -99,7 +102,7 @@ def test_sanity_create_molecule(self): # Check that create_molecule raises a ValueError if the user does not provide a the same number of first_residue_positions as number_of_molecules input_parameters={"name": "generic_molecule", "number_of_molecules": 2, - "espresso_system": espresso_system, + "box_l": box_l, "list_of_first_residue_positions": [[1,2,3]]} self.assertRaises(ValueError, pmb.create_molecule, @@ -111,30 +114,40 @@ def test_center_molecule_in_simulation_box(self): """ molecule_ids = pmb.create_molecule(name=molecule_name, number_of_molecules= 3, - espresso_system=espresso_system, + box_l=box_l, use_default_bond=True, list_of_first_residue_positions = pos_list) - + + # Check that center_molecule_in_simulation_box works correctly for cubic boxes + pmb.center_object_in_simulation_box(instance_id=molecule_ids[0], - espresso_system=espresso_system, + box_l=box_l, pmb_type="molecule") + + center_of_mass = pmb.calculate_center_of_mass(instance_id=molecule_ids[0], - pmb_type="molecule", - espresso_system=espresso_system) + pmb_type="molecule") + ### The method is decoupled from espresso and the center of mass can be calculated without using any simulation engine + center_of_mass_ref = [L/2]*3 + for ind in range(len(center_of_mass)): self.assertAlmostEqual(center_of_mass[ind], center_of_mass_ref[ind]) #Check that center_molecule_in_simulation_box works correctly for non-cubic boxes - espresso_system.change_volume_and_rescale_particles(d_new=3*L, dir="z") + ### New implementation in order to avoid using espresso + # espresso_system.change_volume_and_rescale_particles(d_new=3*L, dir="z") + + pmb.simulation_engine.change_volume_and_rescale_particles(d_new=3*L, dir="z") + new_box_l=[box_l[0],box_l[1],3*L] pmb.center_object_in_simulation_box(instance_id=molecule_ids[2], pmb_type="molecule", - espresso_system=espresso_system) + box_l=new_box_l) center_of_mass = pmb.calculate_center_of_mass(instance_id=molecule_ids[2], - pmb_type="molecule", - espresso_system=espresso_system) + pmb_type="molecule") + center_of_mass_ref = [L/2, L/2, 1.5*L] for ind in range(len(center_of_mass)): self.assertAlmostEqual(center_of_mass[ind], @@ -148,7 +161,7 @@ def test_sanity_center_object_in_simulation_box(self): input_parameters = {"instance_id": 20 , "pmb_type": "molecule", - "espresso_system":espresso_system} + "box_l":box_l} self.assertRaises(ValueError, pmb.center_object_in_simulation_box, diff --git a/testsuite/database_unit_tests.py b/testsuite/database_unit_tests.py index 46e01a15..a3e80208 100644 --- a/testsuite/database_unit_tests.py +++ b/testsuite/database_unit_tests.py @@ -34,7 +34,9 @@ from pyMBE.storage.pint_quantity import PintQuantity from pyMBE.storage.reactions.reaction import Reaction, ReactionParticipant import pint -espresso_system=espressomd.System(box_l = [10]*3) +import numpy as np +box_l=[10]*3 +espresso_system=espressomd.System(box_l =box_l ) class Test(ut.TestCase): @@ -77,7 +79,8 @@ class DummyInstance(): acidity="acidic") part_inst = ParticleInstance(name="A", particle_id=0, - initial_state="A") + initial_state="A", + position=np.array([box_l[0]*0.5,box_l[1]*0.5,box_l[2]*0.5])) pmb.db._register_instance(part_inst) inputs = {"instance": part_inst} self.assertRaises(ValueError, @@ -85,7 +88,8 @@ class DummyInstance(): **inputs) templateless_part_inst = ParticleInstance(name="B", particle_id=1, - initial_state="B") + initial_state="B", + position=np.array([box_l[0]*0.5,box_l[1]*0.5,box_l[2]*0.5])) inputs = {"instance": templateless_part_inst} self.assertRaises(ValueError, pmb.db._register_instance, @@ -187,7 +191,8 @@ class DummyInstance(): ## Calling the function deletes all instances of a given pmb_type part_inst = ParticleInstance(name="A", particle_id=1, - initial_state="A") + initial_state="A", + position=np.array([box_l[0]*0.5,box_l[1]*0.5,box_l[2]*0.5])) pmb.db._register_instance(part_inst) pmb.db.delete_instances(pmb_type="particle") assert "particle" not in pmb.db._instances.keys() @@ -218,9 +223,9 @@ def test_find_instance_ids(self): pmb.define_molecule(name="M1", residue_list=["R1"]*2) pmb.create_molecule(name="M1", - espresso_system=espresso_system, number_of_molecules=1, - use_default_bond=True) + use_default_bond=True, + box_l=box_l) instance_ids_r1 = pmb.db._find_instance_ids_by_attribute(pmb_type="particle", attribute="residue_id", value=0) @@ -362,7 +367,8 @@ def test_instance_id_validators(self): """ inputs = {"name":"A", "particle_id":-1, - "initial_state":"A"} + "initial_state":"A", + "position":np.array([box_l[0]*0.5,box_l[1]*0.5,box_l[2]*0.5])} self.assertRaises(ValueError, ParticleInstance, **inputs) diff --git a/testsuite/define_and_create_molecules_unit_tests.py b/testsuite/define_and_create_molecules_unit_tests.py index 8060556b..7e01a6c0 100644 --- a/testsuite/define_and_create_molecules_unit_tests.py +++ b/testsuite/define_and_create_molecules_unit_tests.py @@ -69,7 +69,8 @@ pmb.define_molecule(**parameter_set) # Create an instance of an espresso system -espresso_system=espressomd.System(box_l = [10]*3) +box_l=[10]*3 +espresso_system=espressomd.System(box_l = box_l) particle_positions=[[0,0,0],[1,1,1]] bond_type = 'harmonic' @@ -115,10 +116,14 @@ def test_create_and_delete_particles(self): """ retval = pmb.create_particle(name="S1", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=2, - fix=True, + fix=[True,True,True], position=particle_positions) + + pmb.set_simulation_engine(espresso_system) + pmb.add_instances_to_engine() + self.assertListEqual(retval, [0, 1]) particle_ids=pmb.get_particle_id_map(object_name="S1")["all"] @@ -135,30 +140,36 @@ def test_create_and_delete_particles(self): list2=particle_positions[pid]) starting_number_of_particles=len(espresso_system.part.all()) + ### Tests that when number of particles is 0 or -1 no particles is created for number_of_particles in [0, -1]: retval = pmb.create_particle(name="S1", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=number_of_particles) self.assertEqual(len(retval), 0) + + # If no particles have been created, only two particles should be in the system (from the previous test) self.assertEqual(first=len(espresso_system.part.all()), second=starting_number_of_particles) + + ### Returns ValueError as no particle with name S23 has been previously defined. with self.assertRaises(ValueError): pmb.create_particle(name="S23", - espresso_system=espresso_system, + box_l=box_l, number_of_particles=1) # If no particles have been created, only two particles should be in the system (from the previous test) + self.assertEqual(first=len(espresso_system.part.all()), second=starting_number_of_particles) + # Unit tests for delete particle starting_number_of_particles=len(espresso_system.part.all()) starting_number_of_rows=len(pmb.get_instances_df(pmb_type="particle")) # This should delete one particle instance pmb.delete_instances_in_system(instance_id=0, - pmb_type="particle", - espresso_system=espresso_system) + pmb_type="particle") self.assertEqual(first=len(espresso_system.part.all()), second=starting_number_of_particles-1) particle_df = pmb.get_instances_df(pmb_type="particle") @@ -167,21 +178,21 @@ def test_create_and_delete_particles(self): # Delete the other particle instance to simplify the rest of the tests pmb.delete_instances_in_system(instance_id=1, - pmb_type="particle", - espresso_system=espresso_system) + pmb_type="particle") central_bead_position=[[0,0,0]] backbone_vector=np.array([1.,2.,3.]) pmb.create_residue(name="R2", - espresso_system=espresso_system, + box_l=box_l, central_bead_position=central_bead_position, backbone_vector=backbone_vector, use_default_bond=True) particle_ids=pmb.get_particle_id_map(object_name="R2")["all"] + pmb.add_instances_to_engine() # Check that the particle properties are correct for pid in particle_ids: particle=espresso_system.part.by_id(pid) @@ -228,11 +239,11 @@ def test_create_and_delete_particles(self): second=frozenset([frozenset([0,1]),frozenset([0,2])])) pmb.create_residue(name="R3", - espresso_system=espresso_system, + box_l=box_l, use_default_bond=True) particle_ids=pmb.get_particle_id_map(object_name="R3")["all"] - + pmb.add_instances_to_engine() # Check that the particle properties are correct for pid in particle_ids: particle=espresso_system.part.by_id(pid) @@ -254,6 +265,7 @@ def test_create_and_delete_particles(self): bonded_pairs=[] bond_df = pmb.get_instances_df(pmb_type="bond") + for bond_index in bond_df.index: particle_id1= bond_df.loc[bond_index,"particle_id1"] @@ -278,7 +290,7 @@ def test_create_and_delete_particles(self): starting_number_of_particles=len(espresso_system.part.all()) with self.assertRaises(ValueError): pmb.create_residue(name="R51", - espresso_system=espresso_system, + box_l=box_l, use_default_bond=True) # If no particles have been created, the number of particles should be the same as before self.assertEqual(first=len(espresso_system.part.all()), @@ -288,8 +300,7 @@ def test_create_and_delete_particles(self): # This should delete 3 particles (residue 0 is a R2 residue) starting_number_of_particles=len(espresso_system.part.all()) pmb.delete_instances_in_system(instance_id=0, - pmb_type="residue", - espresso_system=espresso_system) + pmb_type="residue") self.assertEqual(first=len(espresso_system.part.all()), second=starting_number_of_particles-3) # There should be only one residue instance now in the pyMBE database @@ -300,18 +311,17 @@ def test_create_and_delete_particles(self): second=4) # Delete the other residue instance to simplify the rest of the tests pmb.delete_instances_in_system(instance_id=1, - pmb_type="residue", - espresso_system=espresso_system) + pmb_type="residue") backbone_vector = np.array([1,3,-4]) magnitude = np.linalg.norm(backbone_vector) backbone_vector = backbone_vector/magnitude pmb.create_molecule(name="M2", number_of_molecules=1, - espresso_system=espresso_system, + box_l=box_l, backbone_vector = backbone_vector, use_default_bond=True) - + pmb.add_instances_to_engine() particle_ids=pmb.get_particle_id_map(object_name="M2")["all"] # Residue and molecule IDs expected @@ -405,12 +415,13 @@ def test_create_and_delete_particles(self): starting_number_of_particles=len(espresso_system.part.all()) pmb.create_molecule(name="M2", number_of_molecules=0, - espresso_system=espresso_system, + box_l=box_l, use_default_bond=True) pmb.create_molecule(name="M2", number_of_molecules=-1, - espresso_system=espresso_system, + box_l=box_l, use_default_bond=True) + ### No particle instances are added thus no need to add them to the engine # If no particles have been created, only two particles should be in the system (from the previous test) self.assertEqual(first=len(espresso_system.part.all()), second=starting_number_of_particles) @@ -418,21 +429,21 @@ def test_create_and_delete_particles(self): self.assertRaises(ValueError, pmb.create_molecule, name="M3", number_of_molecules=1, - espresso_system=espresso_system, + box_l=box_l, use_default_bond=True) # Tests for delete_molecule # create another molecule just to have two molecules in the system pmb.create_molecule(name="M2", number_of_molecules=1, - espresso_system=espresso_system, + box_l=box_l, backbone_vector = backbone_vector, use_default_bond=True) # This should delete 8 particles (molecule 0 is a M2 molecule) + pmb.add_instances_to_engine() starting_number_of_particles=len(espresso_system.part.all()) pmb.delete_instances_in_system(instance_id=0, - pmb_type="molecule", - espresso_system=espresso_system) + pmb_type="molecule") self.assertEqual(first=len(espresso_system.part.all()), second=starting_number_of_particles-8) # There should only one molecule instance now in the pyMBE database diff --git a/testsuite/determine_reservoir_concentrations_unit_test.py b/testsuite/determine_reservoir_concentrations_unit_test.py index 87d1909f..0ffbea67 100644 --- a/testsuite/determine_reservoir_concentrations_unit_test.py +++ b/testsuite/determine_reservoir_concentrations_unit_test.py @@ -20,13 +20,18 @@ import pyMBE import numpy as np import pandas as pd +import espressomd from scipy import interpolate +pmb = pyMBE.pymbe_library(seed=42) +Box_L = 7.5*pmb.units.nm +box_l=[Box_L.to('reduced_length').magnitude]*3 +espresso_system = espressomd.System(box_l = box_l) +pmb.set_simulation_engine(espresso_system) def determine_reservoir_concentrations_test_ideal(pH_res, c_salt_res): # Create an instance of the pyMBE library - pmb = pyMBE.pymbe_library(seed=42) - + # Determine the reservoir composition using pyMBE cH_res_pyMBE, cOH_res_pyMBE, cNa_res_pyMBE, cCl_res_pyMBE = pmb.determine_reservoir_concentrations( pH_res, @@ -49,8 +54,7 @@ def determine_reservoir_concentrations_test_ideal(pH_res, c_salt_res): def determine_reservoir_concentrations_test_interacting(pH_res, c_salt_res, reference_data): # Create an instance of the pyMBE library - pmb = pyMBE.pymbe_library(seed=42) - + # Load the excess chemical potential data monovalent_salt_ref_data=pd.read_csv(pmb.root / "parameters" / "salt" / "excess_chemical_potential_excess_pressure.csv") ionic_strength = pmb.units.Quantity(monovalent_salt_ref_data["cs_bulk_[1/sigma^3]"].values, "1/reduced_length**3") diff --git a/testsuite/globular_protein_unit_tests.py b/testsuite/globular_protein_unit_tests.py index 19c6634d..be130734 100644 --- a/testsuite/globular_protein_unit_tests.py +++ b/testsuite/globular_protein_unit_tests.py @@ -28,6 +28,10 @@ # Create an instance of pyMBE library pmb = pyMBE.pymbe_library(seed=42) +Box_L = 100 * pmb.units.reduced_length +box_l=[Box_L.to('reduced_length').magnitude] * 3 +espresso_system=espressomd.System(box_l = box_l) +pmb.set_simulation_engine(espresso_system) protein_pdb = '1f6s' path_to_parfile = pathlib.Path(__file__).parent / "tests_data" / "protein_topology_dict.json" path_to_cg=pmb.root / "parameters" / "globular_proteins" / f"{protein_pdb}.vtf" @@ -38,12 +42,13 @@ ref_residue_list.append(f"AA-{key}") + class Test(ut.TestCase): def test_protein_setup(self): """ Unit tests for setting up globular proteins in pyMBE. """ - Box_L = 100 * pmb.units.reduced_length + def custom_deserializer(dct): if "value" in dct and "unit" in dct: return pmb.units.Quantity(dct["value"], dct["unit"]) @@ -101,20 +106,23 @@ def custom_deserializer(dct): np.testing.assert_raises(ValueError, pmb.define_protein, **input_parameters) - espresso_system=espressomd.System(box_l = [Box_L.to('reduced_length').magnitude] * 3) + + + + with self.assertRaises(ValueError): pmb.create_protein(name="missing_protein_template", number_of_proteins=1, - espresso_system=espresso_system, + box_l=box_l, topology_dict=topology_dict) molecule_id = pmb.create_protein(name=protein_pdb, number_of_proteins=1, - espresso_system=espresso_system, + box_l=box_l, topology_dict=topology_dict)[0] + pmb.add_instances_to_engine() particle_id_list = pmb.get_particle_id_map(object_name=protein_pdb)["all"] center_of_mass_es = pmb.calculate_center_of_mass(instance_id=molecule_id, - pmb_type="protein", - espresso_system=espresso_system) + pmb_type="protein") center_of_mass = np.zeros(3) axis_list = [0,1,2] for aminoacid in topology_dict.keys(): @@ -156,12 +164,13 @@ def custom_deserializer(dct): starting_number_of_particles=len(espresso_system.part.all()) pmb.create_protein(name=protein_pdb, number_of_proteins=0, - espresso_system=espresso_system, + box_l=box_l, topology_dict=topology_dict) pmb.create_protein(name=protein_pdb, number_of_proteins=-1, - espresso_system=espresso_system, + box_l=box_l, topology_dict=topology_dict) + ### No particles instances are created, thus it is not needed to call add_instances_to_engine np.testing.assert_equal(actual=len(espresso_system.part.all()), desired=starting_number_of_particles, verbose=True) @@ -169,13 +178,11 @@ def custom_deserializer(dct): for pid in particle_id_list: positions.append(espresso_system.part.by_id(pid).pos) pmb.enable_motion_of_rigid_object(instance_id=molecule_id, - espresso_system=espresso_system, pmb_type="protein") momI = 0 center_of_mass = pmb.calculate_center_of_mass(instance_id=molecule_id, - pmb_type="protein", - espresso_system=espresso_system) + pmb_type="protein") for p in espresso_system.part: if p.mass > 1: rigid_object_id = p.id diff --git a/testsuite/hydrogel_builder.py b/testsuite/hydrogel_builder.py index da4017cc..f7586136 100644 --- a/testsuite/hydrogel_builder.py +++ b/testsuite/hydrogel_builder.py @@ -73,16 +73,16 @@ [NodeType, BeadType2]]) diamond_lattice = DiamondLattice(mpc, generic_bond_length) -box_l = diamond_lattice.box_l -espresso_system = espressomd.System(box_l = [box_l]*3) +box_l = [diamond_lattice.box_l]*3 +espresso_system = espressomd.System(box_l = box_l) lattice_builder = pmb.initialize_lattice_builder(diamond_lattice) pmb.create_molecule(name=molecule_name, number_of_molecules=1, - espresso_system=espresso_system, + box_l=box_l, use_default_bond=False, - list_of_first_residue_positions = [[np.random.uniform(0,box_l)]*3]) + list_of_first_residue_positions = [[np.random.uniform(0,diamond_lattice.box_l)]*3]) # Setting up node topology indices = diamond_lattice.indices @@ -106,18 +106,22 @@ pmb.define_hydrogel(hydrogel_name,node_topology, chain_topology) # Creating hydrogel -hydrogel_id= pmb.create_hydrogel(hydrogel_name, espresso_system) +hydrogel_id= pmb.create_hydrogel(hydrogel_name,box_l=box_l) hydrogel_tpl = pmb.db.get_template(pmb_type="hydrogel", name=hydrogel_name) hydrogel_inst = pmb.db.get_instance(pmb_type="hydrogel", instance_id=hydrogel_id) + +pmb.set_simulation_engine(espresso_system) +pmb.add_instances_to_engine() + class Test(ut.TestCase): def test_create_hydrogel_missing_template(self): """ Unit test that create_hydrogel raises if the template is missing. """ with self.assertRaises(ValueError): - pmb.create_hydrogel("missing_hydrogel_template", espresso_system) + pmb.create_hydrogel("missing_hydrogel_template",box_l=box_l) def test_hydrogel_template_storage(self): """ @@ -199,14 +203,14 @@ def test_chain_length(self): molecule_ids = pmb.db._find_instance_ids_by_attribute(pmb_type="molecule", attribute="assembly_id", value=hydrogel_id) - expected = (diamond_lattice.mpc - 1) * generic_bond_length.m_as("reduced_length") + expected = (diamond_lattice.mpc -1) * generic_bond_length.m_as('reduced_length') for mol_id in molecule_ids: particle_ids = pmb.db._find_instance_ids_by_attribute(pmb_type="particle", attribute="molecule_id", value=mol_id) positions = np.array([espresso_system.part.by_id(pid).pos for pid in particle_ids]) - contour = np.sum(np.linalg.norm(np.diff(positions, axis=0), axis=1)) - np.testing.assert_allclose(contour, expected, atol=1e-7) + contour = np.sum(np.linalg.norm(np.diff(positions, axis=0), axis=1))*pmb.units.nm + np.testing.assert_allclose(contour.m_as('reduced_length'), expected, atol=1e-7) def test_exceptions(self): """ diff --git a/testsuite/hydrogel_builder_with_angles.py b/testsuite/hydrogel_builder_with_angles.py index 632c1f74..d3686ad6 100644 --- a/testsuite/hydrogel_builder_with_angles.py +++ b/testsuite/hydrogel_builder_with_angles.py @@ -22,8 +22,8 @@ import pyMBE from pyMBE.lib.lattice import DiamondLattice import espressomd - -espresso_system = espressomd.System(box_l=[10] * 3) +box_l=[10] * 3 +espresso_system = espressomd.System(box_l=box_l) def build_simple_hydrogel_with_optional_angles(junction_angle_mode="none", mpc_local=4): @@ -176,10 +176,10 @@ def test_hydrogel_gen_angle_defaults_to_false(self): """ Hydrogel creation should not generate angles unless gen_angle is requested. """ - pmb_local, espresso_system_local, hydrogel_name_local, _ = build_simple_hydrogel_with_optional_angles( + pmb_local, _, hydrogel_name_local, _ = build_simple_hydrogel_with_optional_angles( junction_angle_mode="full" ) - pmb_local.create_hydrogel(hydrogel_name_local, espresso_system_local) + pmb_local.create_hydrogel(hydrogel_name_local, box_l) self.assertEqual(len(pmb_local.db.get_instances(pmb_type="angle")), 0) def test_hydrogel_chain_angles_are_created_without_crosslinker_templates(self): @@ -191,9 +191,11 @@ def test_hydrogel_chain_angles_are_created_without_crosslinker_templates(self): junction_angle_mode="none" ) with self.assertLogs(level="WARNING") as log_context: - pmb_local.create_hydrogel(hydrogel_name_local, espresso_system_local, gen_angle=True) + pmb_local.create_hydrogel(hydrogel_name_local, box_l, gen_angle=True) angle_counts = get_angle_counts(pmb_local) + pmb_local.set_simulation_engine(espresso_system_local) + pmb_local.add_instances_to_engine() self.assertEqual( angle_counts, {"C-C-C": len(diamond_lattice_local.connectivity) * (diamond_lattice_local.mpc - 2)}, @@ -210,7 +212,9 @@ def test_hydrogel_junction_angle_counts_are_correct_when_defined(self): pmb_local, espresso_system_local, hydrogel_name_local, diamond_lattice_local = build_simple_hydrogel_with_optional_angles( junction_angle_mode="full" ) - pmb_local.create_hydrogel(hydrogel_name_local, espresso_system_local, gen_angle=True) + pmb_local.create_hydrogel(hydrogel_name_local, box_l, gen_angle=True) + pmb_local.set_simulation_engine(espresso_system_local) + pmb_local.add_instances_to_engine() self.assertEqual(get_angle_counts(pmb_local), expected_crosslinker_angle_counts(diamond_lattice_local)) @@ -218,11 +222,11 @@ def test_hydrogel_partial_crosslinker_angle_definitions_raise(self): """ Defining only a subset of required crosslinker-adjacent angles should fail. """ - pmb_local, espresso_system_local, hydrogel_name_local, _ = build_simple_hydrogel_with_optional_angles( + pmb_local, _, hydrogel_name_local, _ = build_simple_hydrogel_with_optional_angles( junction_angle_mode="partial" ) with self.assertRaises(ValueError): - pmb_local.create_hydrogel(hydrogel_name_local, espresso_system_local, gen_angle=True) + pmb_local.create_hydrogel(hydrogel_name_local, box_l, gen_angle=True) if __name__ == "__main__": diff --git a/testsuite/lattice_builder.py b/testsuite/lattice_builder.py index 2788ab7a..acc5f3c2 100644 --- a/testsuite/lattice_builder.py +++ b/testsuite/lattice_builder.py @@ -36,7 +36,8 @@ diamond = pyMBE.lib.lattice.DiamondLattice(mpc, bond_l) -espresso_system = espressomd.System(box_l=[diamond.box_l] * 3) +box_l=[diamond.box_l] * 3 +espresso_system = espressomd.System(box_l=box_l) # Define node particle @@ -120,13 +121,13 @@ def test_lattice_setup(self): define_templates(pmb=pmb) # --- Invalid low-level operations --- with self.assertRaises(ValueError): - pmb._create_hydrogel_node("[1 1 1]", NodeType1, espresso_system) + pmb._create_hydrogel_node("[1 1 1]", NodeType1,box_l=box_l) with self.assertRaises(ValueError): pmb._create_hydrogel_chain( "[0 0 0]", "[1 1 1]", {0: [0, 0, 0], 1: diamond.box_l / 4.0 * np.ones(3)}, - espresso_system, + [diamond.box_l]*3 ) # --- Lattice initialization --- @@ -155,7 +156,7 @@ def test_lattice_setup(self): np.testing.assert_equal(lattice.get_node("[3 1 3]"), "default_linker") # Clean espresso system - espresso_system.part.clear() + # espresso_system.part.clear() pmb2 = pyMBE.pymbe_library(23) define_templates(pmb=pmb2) @@ -231,9 +232,9 @@ def test_lattice_setup(self): for label, index in lattice.node_labels.items(): node_index = lattice.lattice.indices[index] node_name = lattice.get_node(label) - node_pos, node_id = pmb2._create_hydrogel_node(node_index=node_index, - node_name=node_name, - espresso_system=espresso_system) + node_pos, node_id = pmb2._create_hydrogel_node(box_l=box_l, + node_index=node_index, + node_name=node_name) nodes[label] = {"name": node_name, "pos": node_pos, "id": node_id} @@ -244,7 +245,7 @@ def test_lattice_setup(self): molecule_name="test_chain") mol_id = pmb2._create_hydrogel_chain(hydrogel_chain=hydrogel_chain, nodes=nodes, - espresso_system=espresso_system, + box_l=box_l, use_default_bond=True) # Extract created particle IDs chain_pids = pmb2.db._find_instance_ids_by_attribute(pmb_type="particle", @@ -285,7 +286,7 @@ def test_non_palindromic_self_loop_chain_raises(self): ): pmb._create_hydrogel_chain(hydrogel_chain=hydrogel_chain, nodes={}, - espresso_system=espresso_system, + box_l=box_l, use_default_bond=False) def test_plot(self): diff --git a/testsuite/lj_tests.py b/testsuite/lj_tests.py index 35f89e62..138b3ced 100644 --- a/testsuite/lj_tests.py +++ b/testsuite/lj_tests.py @@ -24,8 +24,10 @@ # Create an instance of pyMBE library pmb = pyMBE.pymbe_library(seed=42) import espressomd -espresso_system=espressomd.System(box_l = [50]*3) +box_l=[50]*3 +espresso_system=espressomd.System(box_l = box_l) +pmb.set_simulation_engine(espresso_system,box_l) class Test(ut.TestCase): @@ -124,7 +126,7 @@ def test_lj_interaction_setup(self): pmb.define_particle(**B_input_parameters) pmb.define_particle(**C_input_parameters) # Setup LJ interactions shift="auto" - pmb.setup_lj_interactions(espresso_system=espresso_system) + pmb.setup_lj_interactions() # Check A-A LJ setup lj_templates = pmb.db.get_templates(pmb_type="lj") # Check B-B, B-BH, BH-BH setup @@ -144,10 +146,10 @@ def test_lj_interaction_setup(self): # Clean LJ interactions pmb.db.delete_templates(pmb_type="lj") # ValueError if combining-rule other than Lorentz_-Berthelot is used - input_params = {"espresso_system":espresso_system, "combining_rule": "Geometric"} + input_params = { "combining_rule": "Geometric"} self.assertRaises(ValueError, pmb.setup_lj_interactions, **input_params) # Check initialization with shift=0 - pmb.setup_lj_interactions(espresso_system=espresso_system, shift_potential=False) + pmb.setup_lj_interactions( shift_potential=False) # Calculate the reference parameters using Lorentz-Berthelot combining rule # Check A-BH, A-B, setup labels=["A-BH", "A-B"] diff --git a/testsuite/reaction_methods_unit_tests.py b/testsuite/reaction_methods_unit_tests.py index e5732714..1df6d77d 100644 --- a/testsuite/reaction_methods_unit_tests.py +++ b/testsuite/reaction_methods_unit_tests.py @@ -29,7 +29,8 @@ def reaction_method_test_template(parameters): # Create an instance of the pyMBE library pmb = pyMBE.pymbe_library(seed=42) - + pmb.set_simulation_engine(espresso_system) + if parameters["method"] in ["cpH", "grxmc", "grxmc_unified"]: # Define the acidic particle pmb.define_particle( @@ -377,7 +378,8 @@ def test_mixed_setup(self): "salt_cation_name": "Na", "salt_anion_name": "Cl", "activity_coefficient": lambda x: 1.0} - + + pmb.set_simulation_engine(espresso_system) # Add the reactions using pyMBE pmb.setup_gcmc(**input_parameters) pmb.setup_cpH(counter_ion="Na", diff --git a/testsuite/seed_test.py b/testsuite/seed_test.py index 9885b2fc..8548bf87 100644 --- a/testsuite/seed_test.py +++ b/testsuite/seed_test.py @@ -22,7 +22,8 @@ from pyMBE.lib import handy_functions as hf import unittest as ut -espresso_system = espressomd.System(box_l = [100]*3) +box_l=[100]*3 +espresso_system = espressomd.System(box_l = box_l) def build_peptide_in_espresso(seed): pmb = pyMBE.pymbe_library(seed=seed) @@ -57,15 +58,17 @@ def build_peptide_in_espresso(seed): # Create molecule in the espresso system pmb.create_molecule(name=peptide_name, number_of_molecules=1, - espresso_system=espresso_system, + box_l=box_l, use_default_bond=True) + + pmb.set_simulation_engine(espresso_system) + pmb.add_instances_to_engine() # Extract positions of particles in the peptide particle_id_list = pmb.get_particle_id_map("generic_peptide")["all"] positions = [] for pid in particle_id_list: positions.append(espresso_system.part.by_id(pid).pos) - pmb.delete_instances_in_system(espresso_system=espresso_system, - instance_id=0, + pmb.delete_instances_in_system(instance_id=0, pmb_type="peptide") return np.asarray(positions) diff --git a/testsuite/setup_salt_ions_unit_tests.py b/testsuite/setup_salt_ions_unit_tests.py index 085fc77c..a0a3e88a 100644 --- a/testsuite/setup_salt_ions_unit_tests.py +++ b/testsuite/setup_salt_ions_unit_tests.py @@ -51,9 +51,9 @@ N_SALT_ION_PAIRS = 50 volume = N_SALT_ION_PAIRS/(pmb.N_A*c_salt_input) L = volume ** (1./3.) # Side of the simulation box - +box_l=[L.to('reduced_length').magnitude]*3 # Create an instance of an espresso system -espresso_system=espressomd.System (box_l = [L.to('reduced_length').magnitude]*3) +espresso_system=espressomd.System (box_l = box_l ) espresso_system.setup_type_map(type_list=type_map.values()) pmb.define_particle(name='0P', @@ -114,6 +114,7 @@ pmb.define_molecule(name='neutral_molecule', residue_list = ['R0']) +pmb.set_simulation_engine(espresso_system) class Test(ut.TestCase): @@ -125,11 +126,11 @@ def check_salt_concentration(espresso_system,cation_name,anion_name,c_salt,N_SAL charge_number_map=pmb.get_charge_number_map() type_map=pmb.get_type_map() espresso_system.setup_type_map(type_list=type_map.values()) - c_salt_calculated = pmb.create_added_salt(espresso_system=espresso_system, + c_salt_calculated = pmb.create_added_salt(box_l=box_l, cation_name=cation_name, anion_name=anion_name, c_salt=c_salt) - + pmb.add_instances_to_engine() self.assertEqual(get_number_of_particles(espresso_system, type_map[cation_name]),N_SALT_ION_PAIRS*abs(charge_number_map[type_map[anion_name]])) self.assertEqual(get_number_of_particles(espresso_system, type_map[anion_name]),N_SALT_ION_PAIRS*abs(charge_number_map[type_map[cation_name]])) self.assertAlmostEqual(c_salt_calculated.m_as("mol/L"), c_salt.m_as("mol/L")) @@ -137,7 +138,6 @@ def check_salt_concentration(espresso_system,cation_name,anion_name,c_salt,N_SAL anion_ids = pmb.get_particle_id_map(object_name=anion_name)["all"] for id in cation_ids+anion_ids: pmb.delete_instances_in_system(instance_id=id, - espresso_system=espresso_system, pmb_type="particle") # Unit test: test that create_added_salt works for a 1:1 salt (NaCl-like). @@ -171,10 +171,11 @@ def test_salt_addition_concentration_units(self): """ c_salt_part=c_salt_input*pmb.N_A espresso_system.setup_type_map(type_list=type_map.values()) - c_salt_calculated = pmb.create_added_salt(espresso_system=espresso_system, + c_salt_calculated = pmb.create_added_salt(box_l=box_l, cation_name="Na", anion_name="Cl", c_salt=c_salt_part) + pmb.add_instances_to_engine() self.assertEqual(get_number_of_particles(espresso_system, type_map["Na"]),N_SALT_ION_PAIRS) self.assertEqual(get_number_of_particles(espresso_system, type_map["Cl"]),N_SALT_ION_PAIRS) self.assertAlmostEqual(c_salt_calculated.m_as("reduced_length**-3"), c_salt_part.m_as("reduced_length**-3")) @@ -182,7 +183,6 @@ def test_salt_addition_concentration_units(self): anion_ids = pmb.get_particle_id_map(object_name="Cl")["all"] for id in cation_ids+anion_ids: pmb.delete_instances_in_system(instance_id=id, - espresso_system=espresso_system, pmb_type="particle") def test_sanity_create_salt(self): @@ -194,30 +194,30 @@ def test_sanity_create_salt(self): input_parameters={"cation_name":"Cl", "anion_name":"SO4", "c_salt":c_salt_input, - "espresso_system":espresso_system} + "box_l":box_l} self.assertRaises(ValueError, pmb.create_added_salt, **input_parameters) # check that create_added_salt raises a ValueError if one provides a anion_name of an object that has been defined with a non-negative charge input_parameters={"cation_name":"Na", "anion_name":"Ca", "c_salt":c_salt_input, - "espresso_system":espresso_system} + "box_l":box_l} self.assertRaises(ValueError, pmb.create_added_salt, **input_parameters) # check that create_added_salt raises a ValueError if one provides a c_salt with the wrong dimensionality input_parameters={"cation_name":"Na", "anion_name":"Cl", "c_salt":1*pmb.units.nm, - "espresso_system":espresso_system} + "box_l":box_l} self.assertRaises(ValueError, pmb.create_added_salt, **input_parameters) # Test that no salt ions are created if the wrong object names are provided input_parameters={"cation_name":"Na", "anion_name":"X", "c_salt":c_salt_input, - "espresso_system":espresso_system} + "box_l":box_l} self.assertRaises(ValueError, pmb.create_added_salt, **input_parameters) input_parameters={"cation_name":"X", "anion_name":"Cl", "c_salt":c_salt_input, - "espresso_system":espresso_system} + "box_l":box_l} self.assertRaises(ValueError, pmb.create_added_salt, **input_parameters) def test_counterions_setup(self): @@ -227,12 +227,13 @@ def test_counterions_setup(self): def test_counterions(molecule_name, cation_name, anion_name, espresso_system, expected_numbers): pmb.create_molecule(name=molecule_name, number_of_molecules= 2, - espresso_system=espresso_system, + box_l=box_l, use_default_bond=True) pmb.create_counterions(object_name=molecule_name, cation_name=cation_name, anion_name=anion_name, - espresso_system=espresso_system) + box_l=box_l) + pmb.add_instances_to_engine() espresso_system.setup_type_map(type_list=type_map.values()) self.assertEqual(get_number_of_particles(espresso_system, type_map[cation_name]), @@ -243,14 +244,12 @@ def test_counterions(molecule_name, cation_name, anion_name, espresso_system, ex molecule_ids = list(pmb.get_particle_id_map(object_name=molecule_name)["molecule_map"].keys()) for mol_id in molecule_ids: pmb.delete_instances_in_system(instance_id=mol_id, - espresso_system=espresso_system, pmb_type="molecule") cation_ids = pmb.get_particle_id_map(object_name=cation_name)["all"] anion_ids = pmb.get_particle_id_map(object_name=anion_name)["all"] for id in cation_ids+anion_ids: pmb.delete_instances_in_system(instance_id=id, - espresso_system=espresso_system, pmb_type="particle") # Check that create_counterions creates the right number of monovalent counter ions for a polyampholyte with positive net charge. @@ -288,30 +287,32 @@ def test_sanity_create_counterions(self): # Check that create_counterions raises a ValueError if the charge number of the cation is not divisible by the negative charge of the polyampholyte pmb.create_molecule(name='isoelectric_polyampholyte', number_of_molecules= 1, - espresso_system=espresso_system, + box_l=box_l, use_default_bond=True) + pmb.add_instances_to_engine() input_parameters={"cation_name":"Ca", "anion_name":"Cl", "object_name":'isoelectric_polyampholyte', - "espresso_system":espresso_system} + "box_l":box_l} self.assertRaises(ValueError, pmb.create_counterions, **input_parameters) # Check that create_counterions raises a ValueError if the charge number of the anion is not divisible by the positive charge of the polyampholyte input_parameters={"cation_name":"Na", "anion_name":"SO4", "object_name":'isoelectric_polyampholyte', - "espresso_system":espresso_system} + "box_l":box_l} self.assertRaises(ValueError, pmb.create_counterions, **input_parameters) pmb.delete_instances_in_system(instance_id=0, - espresso_system=espresso_system, pmb_type="molecule") # Check that no create_counterions does not create counterions for molecules with no charge pmb.create_molecule(name='neutral_molecule', number_of_molecules= 1, - espresso_system=espresso_system) + box_l=box_l) + pmb.create_counterions(object_name='neutral_molecule', cation_name="Na", anion_name="Cl", - espresso_system=espresso_system) + box_l=box_l) + pmb.add_instances_to_engine() espresso_system.setup_type_map(type_list=type_map.values()) self.assertEqual(get_number_of_particles(espresso_system, type_map["Na"]),0) @@ -320,35 +321,35 @@ def test_sanity_create_counterions(self): inputs = {"object_name":'test', "cation_name":"Na", "anion_name":"Cl", - "espresso_system":espresso_system} + "box_l":box_l} self.assertRaises(ValueError, pmb.create_counterions, **inputs) inputs = {"object_name":'isoelectric_polyampholyte', "cation_name":"Z", "anion_name":"Cl", - "espresso_system":espresso_system} + "box_l":box_l} self.assertRaises(ValueError, pmb.create_counterions, **inputs) inputs = {"object_name":'isoelectric_polyampholyte', "cation_name":"Na", "anion_name":"Y", - "espresso_system":espresso_system} + "box_l":box_l} self.assertRaises(ValueError, pmb.create_counterions, **inputs) input_parameters={"object_name":'isoelectric_polyampholyte', "cation_name":"isoelectric_polyampholyte", "anion_name":"Cl", - "espresso_system":espresso_system} + "box_l":box_l} self.assertRaises(ValueError, pmb.create_counterions, **input_parameters) input_parameters={"object_name":'isoelectric_polyampholyte', "cation_name":"Na", "anion_name":'isoelectric_polyampholyte', - "espresso_system":espresso_system} + "box_l":box_l} self.assertRaises(ValueError, pmb.create_counterions, **input_parameters) if __name__ == "__main__": diff --git a/testsuite/test_handy_functions.py b/testsuite/test_handy_functions.py index 61ad4fce..6939e253 100644 --- a/testsuite/test_handy_functions.py +++ b/testsuite/test_handy_functions.py @@ -22,7 +22,6 @@ import espressomd import espressomd.version import pyMBE -import pyMBE.lib.handy_functions as hf import logging import io import re @@ -34,12 +33,14 @@ handlers=[logging.StreamHandler(log_stream)] ) # Create instances of espresso and pyMBE -espresso_system = espressomd.System(box_l=[60,60,60]) +box_l=[60,60,60] +espresso_system = espressomd.System(box_l=box_l) seed = 23 pmb = pyMBE.pymbe_library(seed=seed) +pmb.set_simulation_engine(espresso_system) kT = pmb.kT -langevin_inputs={"espresso_system":espresso_system, +langevin_inputs={ "kT" : kT, "seed" : seed, "time_step" : 1e-2, @@ -51,7 +52,7 @@ "int_steps": 200, "adjust_max_skin": True} -relax_inputs={"espresso_system":espresso_system, +relax_inputs={ "gamma":0.01, "Nsteps_steepest_descent":5000, "max_displacement":0.1, @@ -59,7 +60,6 @@ "seed": seed} electrostatics_inputs={"units": pmb.units, - "espresso_system": espresso_system, "kT": pmb.kT, "c_salt": None, "solvent_permittivity":78.5, @@ -68,6 +68,7 @@ "accuracy":1e-3, "verbose":False} +#### To import handy_functions is no longer needed as the methods employed in this tests now they belong to the simulation_engine class Test(ut.TestCase): @@ -83,21 +84,21 @@ def test_exceptions_langevin_setup(self): """Test exceptions in :func:`lib.handy_functions.setup_langevin_dynamics`""" broken_inputs = langevin_inputs.copy() broken_inputs["seed"] = "pyMBE" - self.assertRaises(TypeError, hf.setup_langevin_dynamics, **broken_inputs) + self.assertRaises(TypeError, pmb.simulation_engine.setup_langevin_dynamics, **broken_inputs) broken_inputs = langevin_inputs.copy() broken_inputs["time_step"] = -1 - self.assertRaises(ValueError, hf.setup_langevin_dynamics, **broken_inputs) + self.assertRaises(ValueError, pmb.simulation_engine.setup_langevin_dynamics, **broken_inputs) broken_inputs = langevin_inputs.copy() broken_inputs["gamma"] = -1 - self.assertRaises(ValueError, hf.setup_langevin_dynamics, **broken_inputs) + self.assertRaises(ValueError, pmb.simulation_engine.setup_langevin_dynamics, **broken_inputs) broken_inputs = langevin_inputs.copy() broken_inputs["min_skin"] = 10 broken_inputs["max_skin"] = 1 - self.assertRaises(ValueError, hf.setup_langevin_dynamics, **broken_inputs) + self.assertRaises(ValueError, pmb.simulation_engine.setup_langevin_dynamics, **broken_inputs) def test_langevin_setup(self): """Test :func:`lib.handy_functions.setup_langevin_dynamics`""" - hf.setup_langevin_dynamics(**langevin_inputs) + pmb.simulation_engine.setup_langevin_dynamics(**langevin_inputs) ## Test setup of the integrator self.assertEqual(first=langevin_inputs["time_step"], second=espresso_system.time_step, @@ -130,7 +131,7 @@ def test_langevin_setup(self): epsilon = 1, sigma = 1, cutoff = 3, shift = "auto") langevin_inputs_opt_skin = langevin_inputs.copy() langevin_inputs_opt_skin["tune_skin"] = True - hf.setup_langevin_dynamics(**langevin_inputs_opt_skin) + pmb.simulation_engine.setup_langevin_dynamics(**langevin_inputs_opt_skin) log_contents = log_stream.getvalue() optimized_skin = float(re.search(r"Optimized skin value: ([\d.]+)", log_contents).group(1)) self.assertEqual(first=optimized_skin, @@ -141,16 +142,16 @@ def test_exceptions_relax_espresso_system(self): """Test exceptions in :func:`lib.handy_functions.relax_espresso_system`""" broken_inputs = relax_inputs.copy() broken_inputs["gamma"] = -1 - self.assertRaises(ValueError, hf.relax_espresso_system, **broken_inputs) + self.assertRaises(ValueError, pmb.simulation_engine.relax_espresso_system, **broken_inputs) broken_inputs = relax_inputs.copy() broken_inputs["Nsteps_steepest_descent"] = -1 - self.assertRaises(ValueError, hf.relax_espresso_system, **broken_inputs) + self.assertRaises(ValueError, pmb.simulation_engine.relax_espresso_system, **broken_inputs) broken_inputs = relax_inputs.copy() broken_inputs["Nsteps_iter_relax"] = -1 - self.assertRaises(ValueError, hf.relax_espresso_system, **broken_inputs) + self.assertRaises(ValueError, pmb.simulation_engine.relax_espresso_system, **broken_inputs) broken_inputs = relax_inputs.copy() broken_inputs["max_displacement"] = -1 - self.assertRaises(ValueError, hf.relax_espresso_system, **broken_inputs) + self.assertRaises(ValueError, pmb.simulation_engine.relax_espresso_system, **broken_inputs) broken_inputs = relax_inputs.copy() def test_relax_espresso_system(self): @@ -165,7 +166,7 @@ def test_relax_espresso_system(self): espresso_system.part.add(pos=pos) espresso_system.non_bonded_inter[0,0].lennard_jones.set_params( epsilon = 1, sigma = 1, cutoff = 2**(1./6.), shift = "auto") - min_dist = hf.relax_espresso_system(**relax_inputs) + min_dist = pmb.simulation_engine.relax_espresso_system(**relax_inputs) self.assertGreater(a=min_dist, b=0.9, msg="lib.handy_functions.relax_espresso_system is unable to relax a simple lj system") @@ -176,7 +177,7 @@ def test_relax_espresso_system(self): test_inputs = relax_inputs.copy() test_inputs["Nsteps_steepest_descent"] = 1 test_inputs["max_displacement"] = 0 - hf.relax_espresso_system(**relax_inputs) + pmb.simulation_engine.relax_espresso_system(**relax_inputs) new_positions = espresso_system.part.all().pos distances = np.linalg.norm(np.array(positions) - new_positions, axis=1) @@ -190,16 +191,16 @@ def test_exceptions_electrostatics(self): """Test exceptions in :func:`lib.handy_functions.setup_electrostatic_interactions`""" broken_inputs = electrostatics_inputs.copy() broken_inputs["units"] = "pyMBE" - self.assertRaises(TypeError, hf.setup_electrostatic_interactions, **broken_inputs) + self.assertRaises(TypeError, pmb.simulation_engine.setup_electrostatic_interactions, **broken_inputs) broken_inputs = electrostatics_inputs.copy() broken_inputs["method"] = "dH" - self.assertRaises(ValueError, hf.setup_electrostatic_interactions, **broken_inputs) + self.assertRaises(ValueError, pmb.simulation_engine.setup_electrostatic_interactions, **broken_inputs) broken_inputs = electrostatics_inputs.copy() broken_inputs["method"] = "dh" - self.assertRaises(ValueError, hf.setup_electrostatic_interactions, **broken_inputs) + self.assertRaises(ValueError, pmb.simulation_engine.setup_electrostatic_interactions, **broken_inputs) broken_inputs = electrostatics_inputs.copy() broken_inputs["c_salt"] = 10*pmb.units.nm - self.assertRaises(ValueError, hf.setup_electrostatic_interactions, **broken_inputs) + self.assertRaises(ValueError, pmb.simulation_engine.setup_electrostatic_interactions, **broken_inputs) def test_setup_electrostatics(self): """Test :func:`lib.handy_functions.setup_electrostatic_interactions`""" @@ -208,7 +209,7 @@ def test_setup_electrostatics(self): Bjerrum_length = pmb.e.to('reduced_charge')**2 / (4 * pmb.units.pi * pmb.units.eps0 * electrostatics_inputs["solvent_permittivity"] * electrostatics_inputs["kT"].to('reduced_energy')) coulomb_prefactor=Bjerrum_length*electrostatics_inputs["kT"] # Test the P3M setup - hf.setup_electrostatic_interactions(**electrostatics_inputs) + pmb.simulation_engine.setup_electrostatic_interactions(**electrostatics_inputs) if espressomd.version.friendly() == "4.2": coulomb = espresso_system.actors.active_actors.copy()[0] else: @@ -236,7 +237,7 @@ def test_setup_electrostatics(self): "cao": 5, "alpha": 1.1265e+01, "r_cut": 1} - hf.setup_electrostatic_interactions(**electrostatics_inputs) + pmb.simulation_engine.setup_electrostatic_interactions(**electrostatics_inputs) if espressomd.version.friendly() == "4.2": coulomb = espresso_system.actors.active_actors.copy()[0] else: @@ -256,7 +257,7 @@ def test_setup_electrostatics(self): electrostatics_inputs["method"] = "dh" electrostatics_inputs["c_salt"] = pmb.units.Quantity(1, "mol/L") kappa=1./np.sqrt(8*pmb.units.pi*Bjerrum_length*pmb.N_A*electrostatics_inputs["c_salt"]) - hf.setup_electrostatic_interactions(**electrostatics_inputs) + pmb.simulation_engine.setup_electrostatic_interactions(**electrostatics_inputs) if espressomd.version.friendly() == "4.2": dh = espresso_system.actors.active_actors.copy()[0] else: @@ -279,7 +280,7 @@ def test_setup_electrostatics(self): else: coulomb = espresso_system.electrostatics.solver = None electrostatics_inputs["c_salt"] = pmb.units.Quantity(1, "mol/L")*pmb.N_A - hf.setup_electrostatic_interactions(**electrostatics_inputs) + pmb.simulation_engine.setup_electrostatic_interactions(**electrostatics_inputs) if espressomd.version.friendly() == "4.2": dh = espresso_system.actors.active_actors.copy()[0] else: @@ -297,7 +298,7 @@ def test_setup_electrostatics(self): coulomb = espresso_system.electrostatics.solver = None # Test a non-default cut-off electrostatics_inputs["params"] = {"r_cut": 3} - hf.setup_electrostatic_interactions(**electrostatics_inputs) + pmb.simulation_engine.setup_electrostatic_interactions(**electrostatics_inputs) if espressomd.version.friendly() == "4.2": dh = espresso_system.actors.active_actors.copy()[0] else: diff --git a/testsuite/test_io_database.py b/testsuite/test_io_database.py index 186eaa5c..7667f365 100644 --- a/testsuite/test_io_database.py +++ b/testsuite/test_io_database.py @@ -30,9 +30,10 @@ from pyMBE.storage.instances.bond import BondInstance from pathlib import Path import csv +import numpy as np - -espresso_system=espressomd.System (box_l = [100]*3) +box_l=[100]*3 +espresso_system=espressomd.System (box_l = box_l) class DummyDB: def __init__(self): @@ -256,7 +257,8 @@ def test_io_lj_templates(self): offset=0 * units.reduced_length, epsilon=0.2 * units.reduced_energy, z=1) - pmb.setup_lj_interactions(espresso_system=espresso_system) + pmb.set_simulation_engine(espresso_system) + pmb.setup_lj_interactions() new_pmb = pyMBE.pymbe_library(23) with tempfile.TemporaryDirectory() as tmp_directory: # Save and load the database @@ -495,7 +497,7 @@ def test_io_instances(self): pmb.define_molecule(name="M1", residue_list=["R1"]*1) angle_residue_id = pmb.create_residue(name="R1", - espresso_system=espresso_system, + box_l=box_l, gen_angle=True) diamond_lattice = DiamondLattice(4, 3.5 * pmb.units.reduced_length) lattice_builder = pmb.initialize_lattice_builder(diamond_lattice) @@ -520,7 +522,11 @@ def test_io_instances(self): node_topology, chain_topology) assembly_id = pmb.create_hydrogel(name="my_hydrogel", - espresso_system=espresso_system) + box_l=box_l) + + pmb.set_simulation_engine(espresso_system) + pmb.add_instances_to_engine() + new_pmb = pyMBE.pymbe_library(23) with tempfile.TemporaryDirectory() as tmp_directory: # Save and load the database @@ -537,21 +543,29 @@ def test_io_instances(self): new_pmb.get_instances_df(pmb_type="bond")) pd.testing.assert_frame_equal(pmb.get_instances_df(pmb_type="angle"), new_pmb.get_instances_df(pmb_type="angle")) - pd.testing.assert_frame_equal(pmb.get_instances_df(pmb_type="particle"), - new_pmb.get_instances_df(pmb_type="particle")) + instances_before=pmb.get_instances_df(pmb_type="particle").copy() + positions_before=instances_before['position'].to_numpy() + instances_after=new_pmb.get_instances_df(pmb_type="particle").copy() + positions_after=instances_after['position'].to_numpy() + pd.testing.assert_frame_equal(instances_before.drop(columns=['position']), + instances_after.drop(columns=['position'])) + for i in range(len(positions_after)): + self.assertTrue(np.allclose(positions_after[i],positions_before[i],atol=1e-12)) + # Clean up before the next test - pmb.delete_instances_in_system(espresso_system=espresso_system, + pmb.delete_instances_in_system( instance_id=assembly_id, pmb_type="hydrogel") - pmb.delete_instances_in_system(espresso_system=espresso_system, + pmb.delete_instances_in_system( instance_id=angle_residue_id, pmb_type="residue") pmb.db.delete_templates(pmb_type="angle") # Test instances of a peptide (tests peptide, residue, bond and particle instances) path_to_interactions=pmb.root / "parameters" / "peptides" / "Lunkad2021" path_to_pka=pmb.root / "parameters" / "pka_sets" / "Hass2015.json" - pmb.load_database (folder=path_to_interactions) # Defines particles - pmb.load_pka_set(filename=path_to_pka) + ### pmb.load_database(folder): Returns metadata, but not used ? + pmb.load_database (folder=path_to_interactions) # Defines particles + pmb.load_pka_set(filename=path_to_pka) pka_set = pmb.get_pka_set() for particle_name in pka_set.keys(): pmb.define_monoprototic_particle_states(particle_name=particle_name, @@ -567,10 +581,15 @@ def test_io_instances(self): pmb.define_peptide(name="Peptide1", model="1beadAA", sequence="KKKKDDDD") + pep_ids = pmb.create_molecule(name="Peptide1", number_of_molecules=2, - espresso_system=espresso_system, + box_l=box_l, ###set box_l use_default_bond=True) + + pmb.set_simulation_engine(espresso_system) + pmb.add_instances_to_engine() + new_pmb = pyMBE.pymbe_library(23) with tempfile.TemporaryDirectory() as tmp_directory: # Save and load the database @@ -583,11 +602,19 @@ def test_io_instances(self): new_pmb.get_instances_df(pmb_type="residue")) pd.testing.assert_frame_equal(pmb.get_instances_df(pmb_type="bond"), new_pmb.get_instances_df(pmb_type="bond")) - pd.testing.assert_frame_equal(pmb.get_instances_df(pmb_type="particle"), - new_pmb.get_instances_df(pmb_type="particle")) + + instances_before=pmb.get_instances_df(pmb_type="particle") + positions_before=instances_before['position'].to_numpy() + instances_after=new_pmb.get_instances_df(pmb_type="particle") + positions_after=instances_after['position'].to_numpy() + + pd.testing.assert_frame_equal(instances_before.drop(columns=['position']), + instances_after.drop(columns=['position'])) + for i in range(len(positions_after)): + self.assertTrue(np.allclose(positions_after[i],positions_before[i],atol=1e-12)) # Clean up before the next test for pepid in pep_ids: - pmb.delete_instances_in_system(espresso_system=espresso_system, + pmb.delete_instances_in_system( instance_id=pepid, pmb_type="peptide") pmb.db.delete_templates(pmb_type="particle") @@ -610,8 +637,12 @@ def test_io_instances(self): sequence="KKKKKK") prot_ids = pmb.create_protein(name="1beb", number_of_proteins=1, - espresso_system=espresso_system, + box_l=box_l, topology_dict=topology_dict) + + pmb.set_simulation_engine(espresso_system) + pmb.add_instances_to_engine() + new_pmb = pyMBE.pymbe_library(23) with tempfile.TemporaryDirectory() as tmp_directory: # Save and load the database @@ -624,11 +655,18 @@ def test_io_instances(self): new_pmb.get_instances_df(pmb_type="residue")) pd.testing.assert_frame_equal(pmb.get_instances_df(pmb_type="bond"), new_pmb.get_instances_df(pmb_type="bond")) - pd.testing.assert_frame_equal(pmb.get_instances_df(pmb_type="particle"), - new_pmb.get_instances_df(pmb_type="particle")) + instances_before=pmb.get_instances_df(pmb_type="particle") + positions_before=instances_before['position'].to_numpy() + instances_after=new_pmb.get_instances_df(pmb_type="particle") + positions_after=instances_after['position'].to_numpy() + pd.testing.assert_frame_equal(instances_before.drop(columns=['position']), + instances_after.drop(columns=['position'])) + + for i in range(len(positions_after)): + self.assertTrue(np.allclose(positions_after[i],positions_before[i],atol=1e-12)) # Clean up for protid in prot_ids: - pmb.delete_instances_in_system(espresso_system=espresso_system, + pmb.delete_instances_in_system( instance_id=protid, pmb_type="protein") @@ -758,9 +796,10 @@ def test_default_bond_particle_names(self): Test io for default bonds """ pmb = pyMBE.pymbe_library(1) - pmb.define_default_bond(bond_type="FENE", bond_parameters={'r_0' : 0.5 * pmb.units.nm, - 'k' : 500 * pmb.units('reduced_energy / reduced_length**2'), - 'd_r_max': 0.5 * pmb.units.nm}) + pmb.define_default_bond(bond_type="FENE", + bond_parameters={'r_0' : 0.5 * pmb.units.nm, + 'k' : 500 * pmb.units('reduced_energy / reduced_length**2'), + 'd_r_max': 0.5 * pmb.units.nm}) with tempfile.TemporaryDirectory() as tmp: pmb.save_database(tmp) diff --git a/testsuite/weak_polyelectrolyte_dialysis_test_data/data.csv b/testsuite/weak_polyelectrolyte_dialysis_test_data/data.csv deleted file mode 100644 index 7633410b..00000000 --- a/testsuite/weak_polyelectrolyte_dialysis_test_data/data.csv +++ /dev/null @@ -1,6 +0,0 @@ -csalt,cmon,pH,pKa,n_blocks,block_size,mean,err_mean,n_eff,tau_int -value,value,value,value,nan,nan,alpha,alpha,alpha,alpha -0.01,0.435,9,4,16.0,5.625,0.9938888888888889,0.0009782669231040671,69.726915596805,6.453748830676951 -0.01,0.435,3,4,16.0,5.625,0.03822222222222222,0.002196173720894064,49.65617564819513,9.062316904704018 -0.01,0.435,7,4,16.0,5.625,0.7143333333333334,0.008198146056605243,20.631416329721212,21.811396406718313 -0.01,0.435,5,4,16.0,5.625,0.24022222222222223,0.005238508882176492,29.478155904183986,15.265541082769019 diff --git a/tutorials/pyMBE_tutorial.ipynb b/tutorials/pyMBE_tutorial.ipynb index 82b43d05..5ca47d3f 100644 --- a/tutorials/pyMBE_tutorial.ipynb +++ b/tutorials/pyMBE_tutorial.ipynb @@ -40,18 +40,6 @@ "Let us get started by importing pyMBE library and other important libraries for this tutorial, such as ESPResSo." ] }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "# Import pyMBE and ESPResSo\n", - "import pyMBE\n", - "import espressomd\n", - "from espressomd import interactions" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -61,10 +49,12 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ + "import pyMBE\n", + "import espressomd\n", "pmb = pyMBE.pymbe_library(seed=42)" ] }, @@ -83,7 +73,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -116,19 +106,20 @@ "metadata": {}, "outputs": [ { - "name": "stderr", + "name": "stdout", "output_type": "stream", "text": [ - "WARNING:pint.util:Redefining 'reduced_energy' ()\n", - "WARNING:pint.util:Redefining 'reduced_length' ()\n", - "WARNING:pint.util:Redefining 'reduced_charge' ()\n" + "Current set of reduced units:\n", + "0.5 nanometer = 1 reduced_length\n", + "4.1164e-21 joule = 1 reduced_energy\n", + "8.0109e-19 coulomb = 1 reduced_charge\n", + "Temperature: 298.15 kelvin\n" ] } ], "source": [ - "pmb.set_reduced_units(unit_length = 0.5*pmb.units.nm, \n", - " unit_charge = 5*pmb.units.e)\n", - " #, temperature=300*pmb.units.K)" + "reduced_unit_set = pmb.get_reduced_units()\n", + "print(reduced_unit_set)" ] }, { @@ -147,23 +138,26 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "The side of the simulation box is 7.5 nanometer = 15.0 reduced_length\n" + "The side of the simulation box is 7.5 nanometer = 14.999999999999998 reduced_length\n", + " type\n" ] } ], "source": [ "Box_L = 7.5*pmb.units.nm\n", + "box_l=[Box_L.to('reduced_length').magnitude]*3\n", "\n", - "espresso_system = espressomd.System(box_l = [Box_L.to('reduced_length').magnitude]*3)\n", + "espresso_system = espressomd.System(box_l = box_l)\n", "\n", - "print('The side of the simulation box is ', Box_L, '=' ,Box_L.to('reduced_length'))" + "print('The side of the simulation box is ', Box_L, '=' ,Box_L.to('reduced_length'))\n", + "print(type(espresso_system),\"type\")" ] }, { @@ -185,6 +179,15 @@ "execution_count": 6, "metadata": {}, "outputs": [], + "source": [ + "pmb.set_simulation_engine(espresso_system)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], "source": [ "cation_name = 'Na'\n", "pmb.define_particle(name = cation_name, \n", @@ -202,7 +205,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -241,8 +244,8 @@ " particle\n", " Na\n", " 0.35 nanometer\n", - " 25.69257912108585 millielectron_volt\n", - " 0.5612310241546865 nanometer\n", + " 25.692579121085853 millielectron_volt\n", + " 0.5612310241546864 nanometer\n", " 0.0 nanometer\n", " Na\n", " \n", @@ -251,14 +254,14 @@ "" ], "text/plain": [ - " pmb_type name sigma epsilon \\\n", - "0 particle Na 0.35 nanometer 25.69257912108585 millielectron_volt \n", + " pmb_type name sigma epsilon \\\n", + "0 particle Na 0.35 nanometer 25.692579121085853 millielectron_volt \n", "\n", " cutoff offset initial_state \n", - "0 0.5612310241546865 nanometer 0.0 nanometer Na " + "0 0.5612310241546864 nanometer 0.0 nanometer Na " ] }, - "execution_count": 7, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -276,7 +279,28 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cation_name\n", + "pmb.db._has_template(name=cation_name, pmb_type=\"particle\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -285,7 +309,7 @@ "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]" ] }, - "execution_count": 8, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -294,7 +318,7 @@ "N_cations = 20\n", "pmb.create_particle(name = cation_name,\n", " number_of_particles = N_cations,\n", - " espresso_system = espresso_system)" + " box_l=box_l)" ] }, { @@ -306,299 +330,23 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\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", - " \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", - " \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", - "
pmb_typenameparticle_idinitial_stateresidue_idmolecule_idassembly_id
0particleNa0Na<NA><NA><NA>
1particleNa1Na<NA><NA><NA>
2particleNa2Na<NA><NA><NA>
3particleNa3Na<NA><NA><NA>
4particleNa4Na<NA><NA><NA>
5particleNa5Na<NA><NA><NA>
6particleNa6Na<NA><NA><NA>
7particleNa7Na<NA><NA><NA>
8particleNa8Na<NA><NA><NA>
9particleNa9Na<NA><NA><NA>
10particleNa10Na<NA><NA><NA>
11particleNa11Na<NA><NA><NA>
12particleNa12Na<NA><NA><NA>
13particleNa13Na<NA><NA><NA>
14particleNa14Na<NA><NA><NA>
15particleNa15Na<NA><NA><NA>
16particleNa16Na<NA><NA><NA>
17particleNa17Na<NA><NA><NA>
18particleNa18Na<NA><NA><NA>
19particleNa19Na<NA><NA><NA>
\n", - "
" - ], - "text/plain": [ - " pmb_type name particle_id initial_state residue_id molecule_id \\\n", - "0 particle Na 0 Na \n", - "1 particle Na 1 Na \n", - "2 particle Na 2 Na \n", - "3 particle Na 3 Na \n", - "4 particle Na 4 Na \n", - "5 particle Na 5 Na \n", - "6 particle Na 6 Na \n", - "7 particle Na 7 Na \n", - "8 particle Na 8 Na \n", - "9 particle Na 9 Na \n", - "10 particle Na 10 Na \n", - "11 particle Na 11 Na \n", - "12 particle Na 12 Na \n", - "13 particle Na 13 Na \n", - "14 particle Na 14 Na \n", - "15 particle Na 15 Na \n", - "16 particle Na 16 Na \n", - "17 particle Na 17 Na \n", - "18 particle Na 18 Na \n", - "19 particle Na 19 Na \n", - "\n", - " assembly_id \n", - "0 \n", - "1 \n", - "2 \n", - "3 \n", - "4 \n", - "5 \n", - "6 \n", - "7 \n", - "8 \n", - "9 \n", - "10 \n", - "11 \n", - "12 \n", - "13 \n", - "14 \n", - "15 \n", - "16 \n", - "17 \n", - "18 \n", - "19 " - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "pmb.get_instances_df(pmb_type = 'particle')" ] }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "pmb.set_simulation_engine(espresso_system)\n", + "pmb.add_instances_to_engine()" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -608,7 +356,36 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 29, + "metadata": {}, + "outputs": [], + "source": [ + "from PIL import Image\n", + "def create_snapshot_of_espresso_system(espresso_system, filename):\n", + " \"\"\"\n", + " Uses espresso visualizer for creating a snapshot of the current state of the espresso_system\n", + "\n", + " Args:\n", + " espresso_system(`espressomd.system.System`): Instance of a system object from the espressomd library.\n", + " filename(`str`): Name of the ouput file for the snapshot\n", + " \"\"\" \n", + " from espressomd import visualization\n", + " visualizer = visualization.openGLLive(\n", + " espresso_system, bond_type_radius=[0.3], particle_coloring='type', draw_axis=False, background_color=[1, 1, 1],\n", + " particle_type_colors=[[1.02,0.51,0], # Brown\n", + " [1,1,1], # Grey\n", + " [2.55,0,0], # Red\n", + " [0,0,2.05], # Blue\n", + " [0,0,2.05], # Blue\n", + " [2.55,0,0], # Red\n", + " [2.05,1.02,0]]) # Orange\n", + " visualizer.screenshot(filename)\n", + " return" + ] + }, + { + "cell_type": "code", + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -650,7 +427,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 31, "metadata": {}, "outputs": [], "source": [ @@ -659,8 +436,7 @@ "# This will delete all particles that we created before\n", "for pid in particle_id_map[\"all\"]:\n", " pmb.delete_instances_in_system(instance_id=pid, \n", - " pmb_type=\"particle\",\n", - " espresso_system = espresso_system)" + " pmb_type=\"particle\")" ] }, { @@ -672,48 +448,9 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
\n", - "
" - ], - "text/plain": [ - "Empty DataFrame\n", - "Columns: []\n", - "Index: []" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "pmb.get_instances_df(pmb_type = 'particle')" ] @@ -768,6 +505,15 @@ " epsilon = 1*pmb.units('reduced_energy'))\n" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pmb.get_templates_df(pmb_type = 'particle')" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -777,7 +523,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -797,60 +543,11 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
pmb_typenamecentral_beadside_chains
0residuePDha_monBB-PDha[COOH-PDha, NH3-PDha]
\n", - "
" - ], - "text/plain": [ - " pmb_type name central_bead side_chains\n", - "0 residue PDha_mon BB-PDha [COOH-PDha, NH3-PDha]" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "pmb.get_templates_df(pmb_type = 'residue')" + "pmb.get_templates_df(pmb_type='residue')" ] }, { @@ -862,7 +559,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -889,94 +586,9 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/bin/eog: symbol lookup error: /snap/core20/current/lib/x86_64-linux-gnu/libpthread.so.0: undefined symbol: __libc_pthread_init, version GLIBC_PRIVATE\n" - ] - }, - { - "data": { - "text/html": [ - "
\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", - "
pmb_typenamebond_typeparticle_name1particle_name2parameters
0bondBB-PDha-BB-PDhaharmonicBB-PDhaBB-PDha{'r_0': 0.4 nanometer, 'k': 41108.12659373736 ...
1bondBB-PDha-COOH-PDhaharmonicBB-PDhaCOOH-PDha{'r_0': 0.4 nanometer, 'k': 41108.12659373736 ...
2bondBB-PDha-NH3-PDhaharmonicBB-PDhaNH3-PDha{'r_0': 0.4 nanometer, 'k': 41108.12659373736 ...
\n", - "
" - ], - "text/plain": [ - " pmb_type name bond_type particle_name1 particle_name2 \\\n", - "0 bond BB-PDha-BB-PDha harmonic BB-PDha BB-PDha \n", - "1 bond BB-PDha-COOH-PDha harmonic BB-PDha COOH-PDha \n", - "2 bond BB-PDha-NH3-PDha harmonic BB-PDha NH3-PDha \n", - "\n", - " parameters \n", - "0 {'r_0': 0.4 nanometer, 'k': 41108.12659373736 ... \n", - "1 {'r_0': 0.4 nanometer, 'k': 41108.12659373736 ... \n", - "2 {'r_0': 0.4 nanometer, 'k': 41108.12659373736 ... " - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "pmb.get_templates_df(pmb_type = 'bond')" ] @@ -997,12 +609,12 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "PDha_polymer = 'PDha'\n", - "N_monomers = 10\n", + "N_monomers = 4\n", "\n", "pmb.define_molecule(name = PDha_polymer,\n", " residue_list = [PDha_residue]*N_monomers)" @@ -1017,7 +629,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "metadata": {}, "outputs": [ { @@ -1051,18 +663,18 @@ " 0\n", " molecule\n", " PDha\n", - " [PDha_mon, PDha_mon, PDha_mon, PDha_mon, PDha_...\n", + " [PDha_mon, PDha_mon, PDha_mon, PDha_mon]\n", " \n", " \n", "\n", "" ], "text/plain": [ - " pmb_type name residue_list\n", - "0 molecule PDha [PDha_mon, PDha_mon, PDha_mon, PDha_mon, PDha_..." + " pmb_type name residue_list\n", + "0 molecule PDha [PDha_mon, PDha_mon, PDha_mon, PDha_mon]" ] }, - "execution_count": 19, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -1080,7 +692,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "metadata": {}, "outputs": [], "source": [ @@ -1088,7 +700,7 @@ "\n", "molecule_ids = pmb.create_molecule(name = PDha_polymer, \n", " number_of_molecules = N_polymers,\n", - " espresso_system = espresso_system, \n", + " box_l = box_l, \n", " list_of_first_residue_positions = [[Box_L.to('reduced_length').magnitude/2]*3]) " ] }, @@ -1101,934 +713,54 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\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", - " \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", - " \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", - " \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", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
pmb_typenameparticle_idinitial_stateresidue_idmolecule_idassembly_id
0particleBB-PDha0BB-PDha00<NA>
1particleCOOH-PDha1COOH-PDha00<NA>
2particleNH3-PDha2NH3-PDha00<NA>
3particleBB-PDha3BB-PDha10<NA>
4particleCOOH-PDha4COOH-PDha10<NA>
5particleNH3-PDha5NH3-PDha10<NA>
6particleBB-PDha6BB-PDha20<NA>
7particleCOOH-PDha7COOH-PDha20<NA>
8particleNH3-PDha8NH3-PDha20<NA>
9particleBB-PDha9BB-PDha30<NA>
10particleCOOH-PDha10COOH-PDha30<NA>
11particleNH3-PDha11NH3-PDha30<NA>
12particleBB-PDha12BB-PDha40<NA>
13particleCOOH-PDha13COOH-PDha40<NA>
14particleNH3-PDha14NH3-PDha40<NA>
15particleBB-PDha15BB-PDha50<NA>
16particleCOOH-PDha16COOH-PDha50<NA>
17particleNH3-PDha17NH3-PDha50<NA>
18particleBB-PDha18BB-PDha60<NA>
19particleCOOH-PDha19COOH-PDha60<NA>
20particleNH3-PDha20NH3-PDha60<NA>
21particleBB-PDha21BB-PDha70<NA>
22particleCOOH-PDha22COOH-PDha70<NA>
23particleNH3-PDha23NH3-PDha70<NA>
24particleBB-PDha24BB-PDha80<NA>
25particleCOOH-PDha25COOH-PDha80<NA>
26particleNH3-PDha26NH3-PDha80<NA>
27particleBB-PDha27BB-PDha90<NA>
28particleCOOH-PDha28COOH-PDha90<NA>
29particleNH3-PDha29NH3-PDha90<NA>
\n", - "
" - ], - "text/plain": [ - " pmb_type name particle_id initial_state residue_id molecule_id \\\n", - "0 particle BB-PDha 0 BB-PDha 0 0 \n", - "1 particle COOH-PDha 1 COOH-PDha 0 0 \n", - "2 particle NH3-PDha 2 NH3-PDha 0 0 \n", - "3 particle BB-PDha 3 BB-PDha 1 0 \n", - "4 particle COOH-PDha 4 COOH-PDha 1 0 \n", - "5 particle NH3-PDha 5 NH3-PDha 1 0 \n", - "6 particle BB-PDha 6 BB-PDha 2 0 \n", - "7 particle COOH-PDha 7 COOH-PDha 2 0 \n", - "8 particle NH3-PDha 8 NH3-PDha 2 0 \n", - "9 particle BB-PDha 9 BB-PDha 3 0 \n", - "10 particle COOH-PDha 10 COOH-PDha 3 0 \n", - "11 particle NH3-PDha 11 NH3-PDha 3 0 \n", - "12 particle BB-PDha 12 BB-PDha 4 0 \n", - "13 particle COOH-PDha 13 COOH-PDha 4 0 \n", - "14 particle NH3-PDha 14 NH3-PDha 4 0 \n", - "15 particle BB-PDha 15 BB-PDha 5 0 \n", - "16 particle COOH-PDha 16 COOH-PDha 5 0 \n", - "17 particle NH3-PDha 17 NH3-PDha 5 0 \n", - "18 particle BB-PDha 18 BB-PDha 6 0 \n", - "19 particle COOH-PDha 19 COOH-PDha 6 0 \n", - "20 particle NH3-PDha 20 NH3-PDha 6 0 \n", - "21 particle BB-PDha 21 BB-PDha 7 0 \n", - "22 particle COOH-PDha 22 COOH-PDha 7 0 \n", - "23 particle NH3-PDha 23 NH3-PDha 7 0 \n", - "24 particle BB-PDha 24 BB-PDha 8 0 \n", - "25 particle COOH-PDha 25 COOH-PDha 8 0 \n", - "26 particle NH3-PDha 26 NH3-PDha 8 0 \n", - "27 particle BB-PDha 27 BB-PDha 9 0 \n", - "28 particle COOH-PDha 28 COOH-PDha 9 0 \n", - "29 particle NH3-PDha 29 NH3-PDha 9 0 \n", - "\n", - " assembly_id \n", - "0 \n", - "1 \n", - "2 \n", - "3 \n", - "4 \n", - "5 \n", - "6 \n", - "7 \n", - "8 \n", - "9 \n", - "10 \n", - "11 \n", - "12 \n", - "13 \n", - "14 \n", - "15 \n", - "16 \n", - "17 \n", - "18 \n", - "19 \n", - "20 \n", - "21 \n", - "22 \n", - "23 \n", - "24 \n", - "25 \n", - "26 \n", - "27 \n", - "28 \n", - "29 " - ] - }, - "execution_count": 21, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Check particle instances\n", - "pmb.get_instances_df(pmb_type = 'particle')" + "pmb.get_instances_df(pmb_type = 'particle')\n", + "\n" ] }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\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", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
pmb_typenameresidue_idmolecule_idassembly_id
0residuePDha_mon00<NA>
1residuePDha_mon10<NA>
2residuePDha_mon20<NA>
3residuePDha_mon30<NA>
4residuePDha_mon40<NA>
5residuePDha_mon50<NA>
6residuePDha_mon60<NA>
7residuePDha_mon70<NA>
8residuePDha_mon80<NA>
9residuePDha_mon90<NA>
\n", - "
" - ], - "text/plain": [ - " pmb_type name residue_id molecule_id assembly_id\n", - "0 residue PDha_mon 0 0 \n", - "1 residue PDha_mon 1 0 \n", - "2 residue PDha_mon 2 0 \n", - "3 residue PDha_mon 3 0 \n", - "4 residue PDha_mon 4 0 \n", - "5 residue PDha_mon 5 0 \n", - "6 residue PDha_mon 6 0 \n", - "7 residue PDha_mon 7 0 \n", - "8 residue PDha_mon 8 0 \n", - "9 residue PDha_mon 9 0 " - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "# Check residue instances\n", - "pmb.get_instances_df(pmb_type = 'residue')" + "pmb.get_instances_df(pmb_type='bond')" ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\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", - " \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", - " \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", - " \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", - "
pmb_typenamebond_idparticle_id1particle_id2
0bondBB-PDha-COOH-PDha001
1bondBB-PDha-NH3-PDha102
2bondBB-PDha-COOH-PDha234
3bondBB-PDha-NH3-PDha335
4bondBB-PDha-BB-PDha403
5bondBB-PDha-COOH-PDha567
6bondBB-PDha-NH3-PDha668
7bondBB-PDha-BB-PDha736
8bondBB-PDha-COOH-PDha8910
9bondBB-PDha-NH3-PDha9911
10bondBB-PDha-BB-PDha1069
11bondBB-PDha-COOH-PDha111213
12bondBB-PDha-NH3-PDha121214
13bondBB-PDha-BB-PDha13912
14bondBB-PDha-COOH-PDha141516
15bondBB-PDha-NH3-PDha151517
16bondBB-PDha-BB-PDha161215
17bondBB-PDha-COOH-PDha171819
18bondBB-PDha-NH3-PDha181820
19bondBB-PDha-BB-PDha191518
20bondBB-PDha-COOH-PDha202122
21bondBB-PDha-NH3-PDha212123
22bondBB-PDha-BB-PDha221821
23bondBB-PDha-COOH-PDha232425
24bondBB-PDha-NH3-PDha242426
25bondBB-PDha-BB-PDha252124
26bondBB-PDha-COOH-PDha262728
27bondBB-PDha-NH3-PDha272729
28bondBB-PDha-BB-PDha282427
\n", - "
" - ], - "text/plain": [ - " pmb_type name bond_id particle_id1 particle_id2\n", - "0 bond BB-PDha-COOH-PDha 0 0 1\n", - "1 bond BB-PDha-NH3-PDha 1 0 2\n", - "2 bond BB-PDha-COOH-PDha 2 3 4\n", - "3 bond BB-PDha-NH3-PDha 3 3 5\n", - "4 bond BB-PDha-BB-PDha 4 0 3\n", - "5 bond BB-PDha-COOH-PDha 5 6 7\n", - "6 bond BB-PDha-NH3-PDha 6 6 8\n", - "7 bond BB-PDha-BB-PDha 7 3 6\n", - "8 bond BB-PDha-COOH-PDha 8 9 10\n", - "9 bond BB-PDha-NH3-PDha 9 9 11\n", - "10 bond BB-PDha-BB-PDha 10 6 9\n", - "11 bond BB-PDha-COOH-PDha 11 12 13\n", - "12 bond BB-PDha-NH3-PDha 12 12 14\n", - "13 bond BB-PDha-BB-PDha 13 9 12\n", - "14 bond BB-PDha-COOH-PDha 14 15 16\n", - "15 bond BB-PDha-NH3-PDha 15 15 17\n", - "16 bond BB-PDha-BB-PDha 16 12 15\n", - "17 bond BB-PDha-COOH-PDha 17 18 19\n", - "18 bond BB-PDha-NH3-PDha 18 18 20\n", - "19 bond BB-PDha-BB-PDha 19 15 18\n", - "20 bond BB-PDha-COOH-PDha 20 21 22\n", - "21 bond BB-PDha-NH3-PDha 21 21 23\n", - "22 bond BB-PDha-BB-PDha 22 18 21\n", - "23 bond BB-PDha-COOH-PDha 23 24 25\n", - "24 bond BB-PDha-NH3-PDha 24 24 26\n", - "25 bond BB-PDha-BB-PDha 25 21 24\n", - "26 bond BB-PDha-COOH-PDha 26 27 28\n", - "27 bond BB-PDha-NH3-PDha 27 27 29\n", - "28 bond BB-PDha-BB-PDha 28 24 27" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "# Check bond instances\n", - "pmb.get_instances_df(pmb_type = 'bond')" + "# Check residue instances\n", + "pmb.get_instances_df(pmb_type = 'residue')" ] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
pmb_typenamemolecule_idassembly_id
0moleculePDha0<NA>
\n", - "
" - ], - "text/plain": [ - " pmb_type name molecule_id assembly_id\n", - "0 molecule PDha 0 " - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Check molecule instances\n", "pmb.get_instances_df(pmb_type = 'molecule')" ] }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [], + "source": [ + "pmb.set_simulation_engine(espresso_system)\n", + "pmb.add_instances_to_engine()" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -2038,7 +770,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 30, "metadata": {}, "outputs": [], "source": [ @@ -2058,54 +790,27 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
\n", - "
" - ], - "text/plain": [ - "Empty DataFrame\n", - "Columns: []\n", - "Index: []" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], + "source": [ + "pmb.delete_instances_in_system(instance_id=molecule_ids[0],\n", + " pmb_type=\"molecule\")\n", + "# Check particle instances\n", + "pmb.get_instances_df(pmb_type = 'particle')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ - "pmb.delete_instances_in_system(instance_id=molecule_ids[0],\n", - " pmb_type=\"molecule\", \n", - " espresso_system = espresso_system)\n", - "# Check particle instances\n", - "pmb.get_instances_df(pmb_type = 'particle')" + "particle_id_map = pmb.get_particle_id_map(object_name=cation_name)\n", + "# This will delete all particles that we created before\n", + "for pid in particle_id_map[\"all\"]:\n", + " pmb.delete_instances_in_system(instance_id=pid, \n", + " pmb_type=\"particle\")" ] }, { @@ -2133,7 +838,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 32, "metadata": {}, "outputs": [], "source": [ @@ -2173,7 +878,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 33, "metadata": {}, "outputs": [], "source": [ @@ -2193,7 +898,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 34, "metadata": {}, "outputs": [], "source": [ @@ -2212,7 +917,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 35, "metadata": {}, "outputs": [], "source": [ @@ -2232,6 +937,13 @@ " [PDAGA_beta_carboxyl_bead, PDAGA_cyclic_amine_bead]])" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "markdown", "metadata": {}, @@ -2241,20 +953,12 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 36, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/bin/eog: symbol lookup error: /snap/core20/current/lib/x86_64-linux-gnu/libpthread.so.0: undefined symbol: __libc_pthread_init, version GLIBC_PRIVATE\n" - ] - } - ], + "outputs": [], "source": [ "PDAGA_polymer = 'PDAGA'\n", - "N_monomers = 8\n", + "N_monomers = 4\n", "\n", "pmb.define_molecule(name = PDAGA_polymer,\n", " residue_list = [PDAGA_monomer_residue]*N_monomers)" @@ -2269,7 +973,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 37, "metadata": {}, "outputs": [], "source": [ @@ -2277,165 +981,79 @@ "\n", "mol_ids = pmb.create_molecule(name = PDAGA_polymer,\n", " number_of_molecules= N_polymers,\n", - " espresso_system = espresso_system,\n", + " box_l= box_l,\n", " list_of_first_residue_positions = [[Box_L.to('reduced_length').magnitude/2]*3])" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, let us see our PDAGA molecule." - ] - }, { "cell_type": "code", - "execution_count": 33, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "picture_name = 'PDAGA_system.png'\n", - "create_snapshot_of_espresso_system(espresso_system = espresso_system, \n", - " filename = picture_name)\n", - "img = Image.open(picture_name)\n", - "img.show()\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Delete the particles and check that the pyMBE database is empty." - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
\n", - "
" - ], - "text/plain": [ - "Empty DataFrame\n", - "Columns: []\n", - "Index: []" - ] - }, - "execution_count": 34, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "\n", - "for mol_id in mol_ids:\n", - " pmb.delete_instances_in_system(instance_id=mol_id,\n", - " pmb_type=\"molecule\",\n", - " espresso_system=espresso_system)\n", "pmb.get_instances_df(pmb_type = 'particle')" ] }, { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## How to create di-block copolymers " - ] - }, - { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "In turn, the residues previously defined to build the PDAGA and PDha molecules can be used to build more complex polymers such as a di-block PDha-PDAGA copolymer, as shown in the picture below\n", - "\n", - "" + "pmb.get_instances_df(pmb_type = 'residue')" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "Define the bond template between the backbone particle of PDha and the backbone particle of PDAGA" + "pmb.get_instances_df(pmb_type = 'bond')" ] }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 41, "metadata": {}, "outputs": [], "source": [ - "\n", - "pmb.define_bond(bond_type = bond_type,\n", - " bond_parameters = harmonic_bond,\n", - " particle_pairs = [[PDha_backbone_bead, PDAGA_backbone_bead]])" + "pmb.set_simulation_engine(espresso_system)\n", + "pmb.add_instances_to_engine()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Define a molecule template for the di-block polymer molecule using Python list comprehension methods" + "Now, let us see our PDAGA molecule." ] }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 42, "metadata": {}, "outputs": [], "source": [ - "N_monomers_PDha = 4\n", - "N_monomers_PDAGA = 4\n", - "diblock_polymer = 'diblock'\n", - "\n", - "pmb.define_molecule(name = diblock_polymer,\n", - " residue_list = [PDha_residue]*N_monomers_PDha+[PDAGA_monomer_residue]*N_monomers_PDAGA)" + "picture_name = 'PDAGA_system.png'\n", + "create_snapshot_of_espresso_system(espresso_system = espresso_system, \n", + " filename = picture_name)\n", + "img = Image.open(picture_name)\n", + "img.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Creating the di-block polymer into the ESPResSo system" + "Delete the particles and check that the pyMBE database is empty." ] }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 44, "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/bin/eog: symbol lookup error: /snap/core20/current/lib/x86_64-linux-gnu/libpthread.so.0: undefined symbol: __libc_pthread_init, version GLIBC_PRIVATE\n" - ] - }, { "data": { "text/html": [ @@ -2444,391 +1062,133 @@ " .dataframe tbody tr th:only-of-type {\n", " vertical-align: middle;\n", " }\n", - "\n", - " .dataframe tbody tr th {\n", - " vertical-align: top;\n", - " }\n", - "\n", - " .dataframe thead th {\n", - " text-align: right;\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", - " \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", - " \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", - " \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", + "\n", + " .dataframe tbody tr th {\n", + " vertical-align: top;\n", + " }\n", + "\n", + " .dataframe thead th {\n", + " text-align: right;\n", + " }\n", + "\n", + "
pmb_typenameparticle_idinitial_stateresidue_idmolecule_idassembly_id
0particleBB-PDha0BB-PDha00<NA>
1particleCOOH-PDha1COOH-PDha00<NA>
2particleNH3-PDha2NH3-PDha00<NA>
3particleBB-PDha3BB-PDha10<NA>
4particleCOOH-PDha4COOH-PDha10<NA>
5particleNH3-PDha5NH3-PDha10<NA>
6particleBB-PDha6BB-PDha20<NA>
7particleCOOH-PDha7COOH-PDha20<NA>
8particleNH3-PDha8NH3-PDha20<NA>
9particleBB-PDha9BB-PDha30<NA>
10particleCOOH-PDha10COOH-PDha30<NA>
11particleNH3-PDha11NH3-PDha30<NA>
12particleBB-PDAGA12BB-PDAGA40<NA>
13particleNH3-PDAGA13NH3-PDAGA40<NA>
14particleaCOOH-PDAGA14aCOOH-PDAGA40<NA>
15particlebCOOH-PDAGA15bCOOH-PDAGA40<NA>
16particleBB-PDAGA16BB-PDAGA50<NA>
17particleNH3-PDAGA17NH3-PDAGA50<NA>
18particleaCOOH-PDAGA18aCOOH-PDAGA50<NA>
19particlebCOOH-PDAGA19bCOOH-PDAGA50<NA>
20particleBB-PDAGA20BB-PDAGA60<NA>
21particleNH3-PDAGA21NH3-PDAGA60<NA>
22particleaCOOH-PDAGA22aCOOH-PDAGA60<NA>
23particlebCOOH-PDAGA23bCOOH-PDAGA60<NA>
24particleBB-PDAGA24BB-PDAGA70<NA>
25particleNH3-PDAGA25NH3-PDAGA70<NA>
26particleaCOOH-PDAGA26aCOOH-PDAGA70<NA>
27particlebCOOH-PDAGA27bCOOH-PDAGA70<NA>
\n", + " \n", + " \n", + " \n", " \n", + " \n", + " \n", " \n", "
\n", "" ], "text/plain": [ - " pmb_type name particle_id initial_state residue_id molecule_id \\\n", - "0 particle BB-PDha 0 BB-PDha 0 0 \n", - "1 particle COOH-PDha 1 COOH-PDha 0 0 \n", - "2 particle NH3-PDha 2 NH3-PDha 0 0 \n", - "3 particle BB-PDha 3 BB-PDha 1 0 \n", - "4 particle COOH-PDha 4 COOH-PDha 1 0 \n", - "5 particle NH3-PDha 5 NH3-PDha 1 0 \n", - "6 particle BB-PDha 6 BB-PDha 2 0 \n", - "7 particle COOH-PDha 7 COOH-PDha 2 0 \n", - "8 particle NH3-PDha 8 NH3-PDha 2 0 \n", - "9 particle BB-PDha 9 BB-PDha 3 0 \n", - "10 particle COOH-PDha 10 COOH-PDha 3 0 \n", - "11 particle NH3-PDha 11 NH3-PDha 3 0 \n", - "12 particle BB-PDAGA 12 BB-PDAGA 4 0 \n", - "13 particle NH3-PDAGA 13 NH3-PDAGA 4 0 \n", - "14 particle aCOOH-PDAGA 14 aCOOH-PDAGA 4 0 \n", - "15 particle bCOOH-PDAGA 15 bCOOH-PDAGA 4 0 \n", - "16 particle BB-PDAGA 16 BB-PDAGA 5 0 \n", - "17 particle NH3-PDAGA 17 NH3-PDAGA 5 0 \n", - "18 particle aCOOH-PDAGA 18 aCOOH-PDAGA 5 0 \n", - "19 particle bCOOH-PDAGA 19 bCOOH-PDAGA 5 0 \n", - "20 particle BB-PDAGA 20 BB-PDAGA 6 0 \n", - "21 particle NH3-PDAGA 21 NH3-PDAGA 6 0 \n", - "22 particle aCOOH-PDAGA 22 aCOOH-PDAGA 6 0 \n", - "23 particle bCOOH-PDAGA 23 bCOOH-PDAGA 6 0 \n", - "24 particle BB-PDAGA 24 BB-PDAGA 7 0 \n", - "25 particle NH3-PDAGA 25 NH3-PDAGA 7 0 \n", - "26 particle aCOOH-PDAGA 26 aCOOH-PDAGA 7 0 \n", - "27 particle bCOOH-PDAGA 27 bCOOH-PDAGA 7 0 \n", - "\n", - " assembly_id \n", - "0 \n", - "1 \n", - "2 \n", - "3 \n", - "4 \n", - "5 \n", - "6 \n", - "7 \n", - "8 \n", - "9 \n", - "10 \n", - "11 \n", - "12 \n", - "13 \n", - "14 \n", - "15 \n", - "16 \n", - "17 \n", - "18 \n", - "19 \n", - "20 \n", - "21 \n", - "22 \n", - "23 \n", - "24 \n", - "25 \n", - "26 \n", - "27 " + "Empty DataFrame\n", + "Columns: []\n", + "Index: []" ] }, - "execution_count": 37, + "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], + "source": [ + "\n", + "for mol_id in mol_ids:\n", + " pmb.delete_instances_in_system(instance_id=mol_id,\n", + " pmb_type=\"molecule\")\n", + "pmb.get_instances_df(pmb_type = 'particle')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## How to create di-block copolymers " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In turn, the residues previously defined to build the PDAGA and PDha molecules can be used to build more complex polymers such as a di-block PDha-PDAGA copolymer, as shown in the picture below\n", + "\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define the bond template between the backbone particle of PDha and the backbone particle of PDAGA" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "pmb.define_bond(bond_type = bond_type,\n", + " bond_parameters = harmonic_bond,\n", + " particle_pairs = [[PDha_backbone_bead, PDAGA_backbone_bead]])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define a molecule template for the di-block polymer molecule using Python list comprehension methods" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [], + "source": [ + "N_monomers_PDha = 4\n", + "N_monomers_PDAGA = 4\n", + "diblock_polymer = 'diblock'\n", + "\n", + "pmb.define_molecule(name = diblock_polymer,\n", + " residue_list = [PDha_residue]*N_monomers_PDha+[PDAGA_monomer_residue]*N_monomers_PDAGA)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Creating the di-block polymer into the ESPResSo system" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "N_polymers = 1\n", "\n", "mol_ids = pmb.create_molecule(name = diblock_polymer,\n", " number_of_molecules= N_polymers,\n", - " espresso_system = espresso_system,\n", + " box_l= box_l,\n", " list_of_first_residue_positions = [[Box_L.to('reduced_length').magnitude/2]*3]) \n", "# See the particle instances you have created\n", "pmb.get_instances_df(pmb_type=\"particle\")" ] }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [], + "source": [ + "pmb.add_instances_to_engine()" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -2838,7 +1198,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 49, "metadata": {}, "outputs": [], "source": [ @@ -2858,7 +1218,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 51, "metadata": {}, "outputs": [ { @@ -2895,7 +1255,7 @@ "Index: []" ] }, - "execution_count": 39, + "execution_count": 51, "metadata": {}, "output_type": "execute_result" } @@ -2903,8 +1263,7 @@ "source": [ "for mol_id in mol_ids:\n", " pmb.delete_instances_in_system(instance_id=0, \n", - " pmb_type=\"molecule\",\n", - " espresso_system = espresso_system)\n", + " pmb_type=\"molecule\")\n", "pmb.get_instances_df(pmb_type=\"particle\")" ] }, @@ -2952,10 +1311,30 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 55, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "PDha_backbone_bead = 'BB-part1'\n", + "PDha_carboxyl_bead = 'COOH-part1'\n", + "PDha_amine_bead = 'NH3-part1'\n", + "\n", + "pmb.define_particle(name = PDha_backbone_bead, \n", + " z = 0, \n", + " sigma = 0.4*pmb.units.nm,\n", + " epsilon = 1*pmb.units('reduced_energy'))\n", + "\n", + "pmb.define_particle(name = PDha_carboxyl_bead, \n", + " z = 0, \n", + " sigma = 0.5*pmb.units.nm, \n", + " epsilon = 1*pmb.units('reduced_energy'))\n", + "\n", + "pmb.define_particle(name = PDha_amine_bead, \n", + " z = 0, \n", + " sigma = 0.3*pmb.units.nm, \n", + " epsilon = 1*pmb.units('reduced_energy'))\n", + "\n" + ] }, { "cell_type": "markdown", @@ -2969,7 +1348,77 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "pmb.get_templates_df('particle')\n", + "# pmb.get_instances_df('particle')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pmb.get_templates_df('particle_state')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pmb.get_templates_df('residue')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pmb.get_templates_df('molecule')" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "metadata": {}, + "outputs": [], + "source": [ + "pmb.db.delete_templates(\"bond\")" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "metadata": {}, + "outputs": [], + "source": [ + "pmb.db.delete_instances('particle_state')" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "metadata": {}, + "outputs": [], + "source": [ + "res1='Residue1'\n", + "pmb.define_residue(name = res1,\n", + " central_bead = PDha_backbone_bead,\n", + " side_chains = [PDha_carboxyl_bead,PDha_amine_bead])\n", + "\n", + "side_chain_name_res2='side_chain_res_2'\n", + "pmb.define_residue( name = side_chain_name_res2,\n", + " central_bead = PDha_backbone_bead,\n", + " side_chains = [PDha_carboxyl_bead,PDha_amine_bead])\n", + "\n", + "res2='Residue2'\n", + "pmb.define_residue(name = res2,\n", + " central_bead = PDha_backbone_bead,\n", + " side_chains = [side_chain_name_res2])" + ] }, { "cell_type": "markdown", @@ -2983,7 +1432,32 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "print(dir(pmb))\n", + "print(pmb.get_instances_df(pmb_type=\"residue\"))\n", + "pmb.get_instances_df(pmb_type = 'bond')" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "metadata": {}, + "outputs": [], + "source": [ + "bond_type = 'harmonic'\n", + "generic_bond_lenght=0.4 * pmb.units.nm\n", + "generic_harmonic_constant = 400 * pmb.units('reduced_energy / reduced_length**2')\n", + "\n", + "harmonic_bond = {'r_0' : generic_bond_lenght,\n", + " 'k' : generic_harmonic_constant,\n", + " }\n", + "\n", + "pmb.define_bond(bond_type = bond_type,\n", + " bond_parameters = harmonic_bond,\n", + " particle_pairs = [[PDha_backbone_bead, PDha_backbone_bead],\n", + " [PDha_backbone_bead, PDha_carboxyl_bead],\n", + " [PDha_backbone_bead, PDha_amine_bead]])" + ] }, { "cell_type": "markdown", @@ -2994,10 +1468,14 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 66, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "diblock_polymer='custom_polyampholyte'\n", + "pmb.define_molecule(name = diblock_polymer,\n", + " residue_list = 2*[res1]+[res2]+2*[res1]+2*[res2])" + ] }, { "cell_type": "markdown", @@ -3011,7 +1489,34 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "N_polymers = 1\n", + "\n", + "mol_ids = pmb.create_molecule(name = diblock_polymer,\n", + " number_of_molecules= N_polymers,\n", + " box_l = box_l,\n", + " list_of_first_residue_positions = [[Box_L.to('reduced_length').magnitude/2]*3]) \n", + "# See the particle instances you have created\n", + "pmb.get_instances_df(pmb_type=\"particle\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pmb.get_instances_df(pmb_type=\"residue\")" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "metadata": {}, + "outputs": [], + "source": [ + "pmb.add_instances_to_engine()" + ] }, { "cell_type": "markdown", @@ -3022,25 +1527,76 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 71, "metadata": {}, "outputs": [], - "source": [] + "source": [ + "picture_name = 'CustomPolyampholite2_new.png'\n", + "create_snapshot_of_espresso_system(espresso_system = espresso_system, \n", + " filename = picture_name)\n", + "img = Image.open(picture_name)\n", + "img.show()\n" + ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "#### 6. Delete the molecule and check that the pyMBE database is empty." + "#### 6. Delete the molecule and check that the pyMBE database is empty." + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
\n", + "
" + ], + "text/plain": [ + "Empty DataFrame\n", + "Columns: []\n", + "Index: []" + ] + }, + "execution_count": 72, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "for mol_id in mol_ids:\n", + " pmb.delete_instances_in_system(instance_id=mol_id,\n", + " pmb_type=\"molecule\",\n", + " espresso_system=espresso_system)\n", + "pmb.get_instances_df(pmb_type = 'particle')" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "markdown", "metadata": {}, @@ -3079,7 +1635,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": 73, "metadata": {}, "outputs": [], "source": [ @@ -3099,132 +1655,7 @@ "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\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", - " \n", - " \n", - " \n", - "
pmb_typenamesigmaepsiloncutoffoffsetinitial_state
0particleCA0.35 nanometer25.69257912108585 millielectron_volt0.3984740271498274 nanometer0.0 nanometerCA
1particleD0.35 nanometer25.69257912108585 millielectron_volt0.3984740271498274 nanometer0.0 nanometerDH
2particleE0.35 nanometer25.69257912108585 millielectron_volt0.3984740271498274 nanometer0.0 nanometerEH
3particleH0.35 nanometer25.69257912108585 millielectron_volt0.3984740271498274 nanometer0.0 nanometerHH
4particleY0.35 nanometer25.69257912108585 millielectron_volt0.3984740271498274 nanometer0.0 nanometerYH
5particleK0.35 nanometer25.69257912108585 millielectron_volt0.3984740271498274 nanometer0.0 nanometerKH
\n", - "
" - ], - "text/plain": [ - " pmb_type name sigma epsilon \\\n", - "0 particle CA 0.35 nanometer 25.69257912108585 millielectron_volt \n", - "1 particle D 0.35 nanometer 25.69257912108585 millielectron_volt \n", - "2 particle E 0.35 nanometer 25.69257912108585 millielectron_volt \n", - "3 particle H 0.35 nanometer 25.69257912108585 millielectron_volt \n", - "4 particle Y 0.35 nanometer 25.69257912108585 millielectron_volt \n", - "5 particle K 0.35 nanometer 25.69257912108585 millielectron_volt \n", - "\n", - " cutoff offset initial_state \n", - "0 0.3984740271498274 nanometer 0.0 nanometer CA \n", - "1 0.3984740271498274 nanometer 0.0 nanometer DH \n", - "2 0.3984740271498274 nanometer 0.0 nanometer EH \n", - "3 0.3984740271498274 nanometer 0.0 nanometer HH \n", - "4 0.3984740271498274 nanometer 0.0 nanometer YH \n", - "5 0.3984740271498274 nanometer 0.0 nanometer KH " - ] - }, - "execution_count": 41, - "metadata": {}, - "output_type": "execute_result" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/bin/eog: symbol lookup error: /snap/core20/current/lib/x86_64-linux-gnu/libpthread.so.0: undefined symbol: __libc_pthread_init, version GLIBC_PRIVATE\n" - ] - } - ], + "outputs": [], "source": [ "path_to_interactions=pmb.root / \"parameters\" / \"peptides\" / \"Lunkad2021\"\n", "path_to_pka=pmb.root / \"parameters\" / \"pka_sets\" / \"Hass2015.json\"\n", @@ -3242,153 +1673,9 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\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", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
reactionstoichiometrypKreaction_typemetadatasimulation_method
0DH <-> D{'DH': -1, 'D': 1}4.0monoprotic_acid{'summary': 'pKa-values of Hass et al.', 'sour...None
1EH <-> E{'EH': -1, 'E': 1}4.4monoprotic_acid{'summary': 'pKa-values of Hass et al.', 'sour...None
2YH <-> Y{'YH': -1, 'Y': 1}9.6monoprotic_acid{'summary': 'pKa-values of Hass et al.', 'sour...None
3CH <-> C{'CH': -1, 'C': 1}8.3monoprotic_acid{'summary': 'pKa-values of Hass et al.', 'sour...None
4HH <-> H{'HH': -1, 'H': 1}6.8monoprotic_base{'summary': 'pKa-values of Hass et al.', 'sour...None
5KH <-> K{'KH': -1, 'K': 1}10.4monoprotic_base{'summary': 'pKa-values of Hass et al.', 'sour...None
6RH <-> R{'RH': -1, 'R': 1}13.5monoprotic_base{'summary': 'pKa-values of Hass et al.', 'sour...None
7nH <-> n{'nH': -1, 'n': 1}8.0monoprotic_base{'summary': 'pKa-values of Hass et al.', 'sour...None
8cH <-> c{'cH': -1, 'c': 1}3.6monoprotic_acid{'summary': 'pKa-values of Hass et al.', 'sour...None
\n", - "
" - ], - "text/plain": [ - " reaction stoichiometry pK reaction_type \\\n", - "0 DH <-> D {'DH': -1, 'D': 1} 4.0 monoprotic_acid \n", - "1 EH <-> E {'EH': -1, 'E': 1} 4.4 monoprotic_acid \n", - "2 YH <-> Y {'YH': -1, 'Y': 1} 9.6 monoprotic_acid \n", - "3 CH <-> C {'CH': -1, 'C': 1} 8.3 monoprotic_acid \n", - "4 HH <-> H {'HH': -1, 'H': 1} 6.8 monoprotic_base \n", - "5 KH <-> K {'KH': -1, 'K': 1} 10.4 monoprotic_base \n", - "6 RH <-> R {'RH': -1, 'R': 1} 13.5 monoprotic_base \n", - "7 nH <-> n {'nH': -1, 'n': 1} 8.0 monoprotic_base \n", - "8 cH <-> c {'cH': -1, 'c': 1} 3.6 monoprotic_acid \n", - "\n", - " metadata simulation_method \n", - "0 {'summary': 'pKa-values of Hass et al.', 'sour... None \n", - "1 {'summary': 'pKa-values of Hass et al.', 'sour... None \n", - "2 {'summary': 'pKa-values of Hass et al.', 'sour... None \n", - "3 {'summary': 'pKa-values of Hass et al.', 'sour... None \n", - "4 {'summary': 'pKa-values of Hass et al.', 'sour... None \n", - "5 {'summary': 'pKa-values of Hass et al.', 'sour... None \n", - "6 {'summary': 'pKa-values of Hass et al.', 'sour... None \n", - "7 {'summary': 'pKa-values of Hass et al.', 'sour... None \n", - "8 {'summary': 'pKa-values of Hass et al.', 'sour... None " - ] - }, - "execution_count": 42, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "pmb.load_pka_set(path_to_pka)\n", "# Check the loaded pKa set\n", @@ -3406,27 +1693,7 @@ "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'D': {'pka_value': 4.0, 'acidity': 'acidic'}, 'E': {'pka_value': 4.4, 'acidity': 'acidic'}, 'Y': {'pka_value': 9.6, 'acidity': 'acidic'}, 'C': {'pka_value': 8.3, 'acidity': 'acidic'}, 'H': {'pka_value': 6.8, 'acidity': 'basic'}, 'K': {'pka_value': 10.4, 'acidity': 'basic'}, 'R': {'pka_value': 13.5, 'acidity': 'basic'}, 'n': {'pka_value': 8.0, 'acidity': 'basic'}, 'c': {'pka_value': 3.6, 'acidity': 'acidic'}}\n" - ] - }, - { - "ename": "ValueError", - "evalue": "Acidity {'pka_value': 4.0, 'acidity': 'acidic'} provided for particle name D is not supported. Valid keys are: ['acidic', 'basic']", - "output_type": "error", - "traceback": [ - "\u001b[31m---------------------------------------------------------------------------\u001b[39m", - "\u001b[31mValueError\u001b[39m Traceback (most recent call last)", - "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[45]\u001b[39m\u001b[32m, line 7\u001b[39m\n\u001b[32m 5\u001b[39m \u001b[38;5;66;03m# define templates for the different particle states of monoprotic acid an basic groups:\u001b[39;00m\n\u001b[32m 6\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m acidbase_particle \u001b[38;5;129;01min\u001b[39;00m pka_set.keys():\n\u001b[32m----> \u001b[39m\u001b[32m7\u001b[39m \u001b[43mpmb\u001b[49m\u001b[43m.\u001b[49m\u001b[43mdefine_monoprototic_particle_states\u001b[49m\u001b[43m(\u001b[49m\u001b[43mparticle_name\u001b[49m\u001b[43m=\u001b[49m\u001b[43macidbase_particle\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 8\u001b[39m \u001b[43m \u001b[49m\u001b[43macidity\u001b[49m\u001b[43m=\u001b[49m\u001b[43mpka_set\u001b[49m\u001b[43m[\u001b[49m\u001b[43macidbase_particle\u001b[49m\u001b[43m]\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 9\u001b[39m pmb.get_templates_df(pmb_type=\u001b[33m\"\u001b[39m\u001b[33mparticle_state\u001b[39m\u001b[33m\"\u001b[39m)\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/pyMBE_pmb/pyMBE/pyMBE.py:1561\u001b[39m, in \u001b[36mpymbe_library.define_monoprototic_particle_states\u001b[39m\u001b[34m(self, particle_name, acidity)\u001b[39m\n\u001b[32m 1559\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m pd.isna(acidity):\n\u001b[32m 1560\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m acidity \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m acidity_valid_keys:\n\u001b[32m-> \u001b[39m\u001b[32m1561\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mAcidity \u001b[39m\u001b[38;5;132;01m{\u001b[39;00macidity\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m provided for particle name \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mparticle_name\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m is not supported. Valid keys are: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00macidity_valid_keys\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m)\n\u001b[32m 1562\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m acidity == \u001b[33m\"\u001b[39m\u001b[33macidic\u001b[39m\u001b[33m\"\u001b[39m:\n\u001b[32m 1563\u001b[39m states = [{\u001b[33m\"\u001b[39m\u001b[33mname\u001b[39m\u001b[33m\"\u001b[39m: \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mparticle_name\u001b[38;5;132;01m}\u001b[39;00m\u001b[33mH\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33mz\u001b[39m\u001b[33m\"\u001b[39m: \u001b[32m0\u001b[39m}, \n\u001b[32m 1564\u001b[39m {\u001b[33m\"\u001b[39m\u001b[33mname\u001b[39m\u001b[33m\"\u001b[39m: \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mparticle_name\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33mz\u001b[39m\u001b[33m\"\u001b[39m: -\u001b[32m1\u001b[39m}]\n", - "\u001b[31mValueError\u001b[39m: Acidity {'pka_value': 4.0, 'acidity': 'acidic'} provided for particle name D is not supported. Valid keys are: ['acidic', 'basic']" - ] - } - ], + "outputs": [], "source": [ "# Get the pKa set stored in pyMBE\n", "pka_set = pmb.get_pka_set()\n", @@ -3439,6 +1706,15 @@ "pmb.get_templates_df(pmb_type=\"particle_state\")" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pmb.get_templates_df(pmb_type=\"particle\")" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -3448,63 +1724,9 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
pmb_typenamemodelresidue_listsequence
0peptideKKKKKEEEEE2beadAA[AA-K, AA-K, AA-K, AA-K, AA-K, AA-E, AA-E, AA-...KKKKKEEEEE
\n", - "
" - ], - "text/plain": [ - " pmb_type name model \\\n", - "0 peptide KKKKKEEEEE 2beadAA \n", - "\n", - " residue_list sequence \n", - "0 [AA-K, AA-K, AA-K, AA-K, AA-K, AA-E, AA-E, AA-... KKKKKEEEEE " - ] - }, - "execution_count": 43, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "from pyMBE.lib.handy_functions import define_peptide_AA_residues\n", "\n", @@ -3529,34 +1751,27 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "ename": "ValueError", - "evalue": "Template 'KH' not found in type 'particle_state'.", - "output_type": "error", - "traceback": [ - "\u001b[31m---------------------------------------------------------------------------\u001b[39m", - "\u001b[31mValueError\u001b[39m Traceback (most recent call last)", - "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[44]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m mol_ids = \u001b[43mpmb\u001b[49m\u001b[43m.\u001b[49m\u001b[43mcreate_molecule\u001b[49m\u001b[43m(\u001b[49m\u001b[43mname\u001b[49m\u001b[43m \u001b[49m\u001b[43m=\u001b[49m\u001b[43m \u001b[49m\u001b[43msequence\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 2\u001b[39m \u001b[43m \u001b[49m\u001b[43mnumber_of_molecules\u001b[49m\u001b[43m=\u001b[49m\u001b[43m \u001b[49m\u001b[43mN_peptide\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 3\u001b[39m \u001b[43m \u001b[49m\u001b[43mespresso_system\u001b[49m\u001b[43m \u001b[49m\u001b[43m=\u001b[49m\u001b[43m \u001b[49m\u001b[43mespresso_system\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 4\u001b[39m \u001b[43m \u001b[49m\u001b[43mlist_of_first_residue_positions\u001b[49m\u001b[43m \u001b[49m\u001b[43m=\u001b[49m\u001b[43m \u001b[49m\u001b[43m[\u001b[49m\u001b[43m[\u001b[49m\u001b[43mBox_L\u001b[49m\u001b[43m.\u001b[49m\u001b[43mto\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mreduced_length\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m.\u001b[49m\u001b[43mmagnitude\u001b[49m\u001b[43m/\u001b[49m\u001b[32;43m2\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m*\u001b[49m\u001b[32;43m3\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 5\u001b[39m pmb.get_instances_df(pmb_type=\u001b[33m\"\u001b[39m\u001b[33mpeptide\u001b[39m\u001b[33m\"\u001b[39m)\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/pyMBE_pmb/pyMBE/pyMBE.py:1030\u001b[39m, in \u001b[36mpymbe_library.create_molecule\u001b[39m\u001b[34m(self, name, number_of_molecules, espresso_system, list_of_first_residue_positions, backbone_vector, use_default_bond, reverse_residue_order)\u001b[39m\n\u001b[32m 1027\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m item \u001b[38;5;129;01min\u001b[39;00m list_of_first_residue_positions:\n\u001b[32m 1028\u001b[39m central_bead_pos = [np.array(list_of_first_residue_positions[pos_index])]\n\u001b[32m-> \u001b[39m\u001b[32m1030\u001b[39m residue_id = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mcreate_residue\u001b[49m\u001b[43m(\u001b[49m\u001b[43mname\u001b[49m\u001b[43m=\u001b[49m\u001b[43mresidue\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 1031\u001b[39m \u001b[43m \u001b[49m\u001b[43mespresso_system\u001b[49m\u001b[43m=\u001b[49m\u001b[43mespresso_system\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\n\u001b[32m 1032\u001b[39m \u001b[43m \u001b[49m\u001b[43mcentral_bead_position\u001b[49m\u001b[43m=\u001b[49m\u001b[43mcentral_bead_pos\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\n\u001b[32m 1033\u001b[39m \u001b[43m \u001b[49m\u001b[43muse_default_bond\u001b[49m\u001b[43m=\u001b[49m\u001b[43m \u001b[49m\u001b[43muse_default_bond\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\n\u001b[32m 1034\u001b[39m \u001b[43m \u001b[49m\u001b[43mbackbone_vector\u001b[49m\u001b[43m=\u001b[49m\u001b[43mbackbone_vector\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1036\u001b[39m \u001b[38;5;66;03m# Add molecule_id to the residue instance and all particles associated\u001b[39;00m\n\u001b[32m 1037\u001b[39m \u001b[38;5;28mself\u001b[39m.db._propagate_id(root_type=\u001b[33m\"\u001b[39m\u001b[33mresidue\u001b[39m\u001b[33m\"\u001b[39m, \n\u001b[32m 1038\u001b[39m root_id=residue_id,\n\u001b[32m 1039\u001b[39m attribute=\u001b[33m\"\u001b[39m\u001b[33mmolecule_id\u001b[39m\u001b[33m\"\u001b[39m, \n\u001b[32m 1040\u001b[39m value=molecule_id)\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/pyMBE_pmb/pyMBE/pyMBE.py:1319\u001b[39m, in \u001b[36mpymbe_library.create_residue\u001b[39m\u001b[34m(self, name, espresso_system, central_bead_position, use_default_bond, backbone_vector)\u001b[39m\n\u001b[32m 1315\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 1316\u001b[39m bead_position=central_bead_position+\u001b[38;5;28mself\u001b[39m.generate_trial_perpendicular_vector(vector=np.array(backbone_vector),\n\u001b[32m 1317\u001b[39m magnitude=l0)\n\u001b[32m-> \u001b[39m\u001b[32m1319\u001b[39m side_bead_id = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mcreate_particle\u001b[49m\u001b[43m(\u001b[49m\u001b[43mname\u001b[49m\u001b[43m=\u001b[49m\u001b[43mside_chain_name\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\n\u001b[32m 1320\u001b[39m \u001b[43m \u001b[49m\u001b[43mespresso_system\u001b[49m\u001b[43m=\u001b[49m\u001b[43mespresso_system\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 1321\u001b[39m \u001b[43m \u001b[49m\u001b[43mposition\u001b[49m\u001b[43m=\u001b[49m\u001b[43m[\u001b[49m\u001b[43mbead_position\u001b[49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\n\u001b[32m 1322\u001b[39m \u001b[43m \u001b[49m\u001b[43mnumber_of_particles\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m1\u001b[39;49m\u001b[43m)\u001b[49m[\u001b[32m0\u001b[39m]\n\u001b[32m 1323\u001b[39m side_chain_beads_ids.append(side_bead_id)\n\u001b[32m 1324\u001b[39m \u001b[38;5;28mself\u001b[39m.db._update_instance(pmb_type=\u001b[33m\"\u001b[39m\u001b[33mparticle\u001b[39m\u001b[33m\"\u001b[39m,\n\u001b[32m 1325\u001b[39m instance_id=side_bead_id,\n\u001b[32m 1326\u001b[39m attribute=\u001b[33m\"\u001b[39m\u001b[33mresidue_id\u001b[39m\u001b[33m\"\u001b[39m,\n\u001b[32m 1327\u001b[39m value=residue_id)\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/pyMBE_pmb/pyMBE/pyMBE.py:1133\u001b[39m, in \u001b[36mpymbe_library.create_particle\u001b[39m\u001b[34m(self, name, espresso_system, number_of_particles, position, fix)\u001b[39m\n\u001b[32m 1129\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m []\n\u001b[32m 1131\u001b[39m part_tpl = \u001b[38;5;28mself\u001b[39m.db.get_template(pmb_type=\u001b[33m\"\u001b[39m\u001b[33mparticle\u001b[39m\u001b[33m\"\u001b[39m,\n\u001b[32m 1132\u001b[39m name=name)\n\u001b[32m-> \u001b[39m\u001b[32m1133\u001b[39m part_state = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mdb\u001b[49m\u001b[43m.\u001b[49m\u001b[43mget_template\u001b[49m\u001b[43m(\u001b[49m\u001b[43mpmb_type\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mparticle_state\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 1134\u001b[39m \u001b[43m \u001b[49m\u001b[43mname\u001b[49m\u001b[43m=\u001b[49m\u001b[43mpart_tpl\u001b[49m\u001b[43m.\u001b[49m\u001b[43minitial_state\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1135\u001b[39m z = part_state.z\n\u001b[32m 1136\u001b[39m es_type = part_state.es_type\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/pyMBE_pmb/pyMBE/storage/manager.py:935\u001b[39m, in \u001b[36mManager.get_template\u001b[39m\u001b[34m(self, pmb_type, name)\u001b[39m\n\u001b[32m 932\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mThere are no \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mpmb_type\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m templates defined in the database\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m 934\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m name \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m._templates[pmb_type]:\n\u001b[32m--> \u001b[39m\u001b[32m935\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mTemplate \u001b[39m\u001b[33m'\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mname\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m'\u001b[39m\u001b[33m not found in type \u001b[39m\u001b[33m'\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mpmb_type\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m'\u001b[39m\u001b[33m.\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m 936\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 937\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m._templates[pmb_type][name]\n", - "\u001b[31mValueError\u001b[39m: Template 'KH' not found in type 'particle_state'." - ] - } - ], + "outputs": [], "source": [ "\n", "mol_ids = pmb.create_molecule(name = sequence,\n", " number_of_molecules= N_peptide,\n", - " espresso_system = espresso_system,\n", + " box_l = box_l,\n", " list_of_first_residue_positions = [[Box_L.to('reduced_length').magnitude/2]*3])\n", "pmb.get_instances_df(pmb_type=\"peptide\")" ] }, + { + "cell_type": "code", + "execution_count": 82, + "metadata": {}, + "outputs": [], + "source": [ + "pmb.add_instances_to_engine()" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -3566,11 +1781,11 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 83, "metadata": {}, "outputs": [], "source": [ - "picture_name = 'peptide.png'\n", + "picture_name = 'peptide_new.png'\n", "create_snapshot_of_espresso_system(espresso_system = espresso_system, \n", " filename = picture_name)\n", "img = Image.open(picture_name)\n", @@ -3586,7 +1801,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 84, "metadata": {}, "outputs": [], "source": [ @@ -3629,7 +1844,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.3" + "version": "3.10.20" } }, "nbformat": 4,