From f2ae74016bcb3e6440b067298abe4358788ee4c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Wed, 26 Aug 2026 19:05:34 +0200 Subject: [PATCH 1/2] Drop support for ESPResSo 4.2 --- .github/actions/dependencies/action.yml | 1 + .github/workflows/samples.yml | 23 ++-- .github/workflows/testsuite.yml | 9 +- pyMBE/simulation_builder/espresso_engine.py | 101 +++++------------- samples/Beyer2024/globular_protein.py | 7 +- samples/Beyer2024/peptide.py | 9 +- .../weak_polyelectrolyte_dialysis.py | 22 ++-- samples/branched_polyampholyte.py | 15 ++- samples/peptide_cpH.py | 14 +-- samples/peptide_mixture_grxmc_ideal.py | 10 +- samples/salt_solution_gcmc.py | 17 ++- samples/weak_polyacid_hydrogel_grxmc.py | 16 +-- testsuite/setup_salt_ions_unit_tests.py | 35 +++--- testsuite/test_handy_functions.py | 54 +++------- 14 files changed, 122 insertions(+), 211 deletions(-) diff --git a/.github/actions/dependencies/action.yml b/.github/actions/dependencies/action.yml index 03953a1..95f3372 100644 --- a/.github/actions/dependencies/action.yml +++ b/.github/actions/dependencies/action.yml @@ -11,6 +11,7 @@ runs: using: "composite" steps: - run: | + test "${RUNNER_ARCH}" = "X64" && module use /cvmfs/dev.eessi.io/espresso/versions/${EESSI_VERSION}/software/linux/x86_64/amd/zen2/modules/all module load ${{ inputs.modules }} module save pymbe python3 -m venv --system-site-packages venv diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index b24527f..ab79356 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -17,28 +17,29 @@ jobs: OMPI_MCA_mtl_ofi_provider_exclude: psm3 strategy: matrix: - espresso: - - version: "4.2.2" - eessi_modules: ESPResSo/4.2.2-foss-2023b - eessi_stack_version: "2023.06" - upload_artifact: true - - version: "5.0.1" + software: + - label: "ESPResSo 5.0.1" eessi_modules: ESPResSo/5.0.1-foss-2025a eessi_stack_version: "2025.06" + upload_artifact: true + - label: "ESPResSo 5.1-dev" + eessi_modules: ESPResSo/cd7547c43b2dae8e96487bf149d049d701dddd43-foss-2025a + eessi_stack_version: "2025.06" upload_artifact: false - name: ubuntu - ESPResSo ${{ matrix.espresso.version }} + name: ubuntu - ${{ matrix.software.label }} steps: + - name: Checkout repository + uses: actions/checkout@main - name: Setup EESSI uses: eessi/github-action-eessi@v3 with: - eessi_stack_version: ${{ matrix.espresso.eessi_stack_version }} - - name: Checkout repository - uses: actions/checkout@main + eessi_stack_version: ${{ matrix.software.eessi_stack_version }} + use_eessi_module: false - name: Install dependencies uses: ./.github/actions/dependencies with: modules: |- - ${{ matrix.espresso.eessi_modules }} + ${{ matrix.software.eessi_modules }} - name: Run testsuite run: | export NUM_PROC=$(nproc) diff --git a/.github/workflows/testsuite.yml b/.github/workflows/testsuite.yml index ecb2ef9..04b29f3 100644 --- a/.github/workflows/testsuite.yml +++ b/.github/workflows/testsuite.yml @@ -17,13 +17,13 @@ jobs: strategy: matrix: software: - - label: "ESPResSo 4.2.2, LAMMPS 2024" - eessi_modules: ESPResSo/4.2.2-foss-2023b LAMMPS/29Aug2024-foss-2023b-kokkos - eessi_stack_version: "2023.06" - upload_artifact: true - label: "ESPResSo 5.0.1" eessi_modules: ESPResSo/5.0.1-foss-2025a eessi_stack_version: "2025.06" + upload_artifact: true + - label: "ESPResSo 5.1-dev" + eessi_modules: ESPResSo/cd7547c43b2dae8e96487bf149d049d701dddd43-foss-2025a + eessi_stack_version: "2025.06" upload_artifact: false name: ubuntu - ${{ matrix.software.label }} steps: @@ -37,6 +37,7 @@ jobs: uses: eessi/github-action-eessi@v3 with: eessi_stack_version: ${{ matrix.software.eessi_stack_version }} + use_eessi_module: false - name: Install dependencies uses: ./.github/actions/dependencies with: diff --git a/pyMBE/simulation_builder/espresso_engine.py b/pyMBE/simulation_builder/espresso_engine.py index 1cbf013..68fe830 100644 --- a/pyMBE/simulation_builder/espresso_engine.py +++ b/pyMBE/simulation_builder/espresso_engine.py @@ -37,7 +37,6 @@ def __init__(self,box_l,db,espresso_system,units,kT,Kw,seed): self.kT=kT self.Kw=Kw self.seed=seed - pass def _add_angle(self,particle_id1,particle_id2,particle_id3, angle_inst): """ @@ -365,36 +364,6 @@ def change_volume_and_rescale_particles(self, d_new, dir="xyz"): self.espresso_system.change_volume_and_rescale_particles(d_new=d_new, dir=dir) - - - 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): """ @@ -442,35 +411,6 @@ def enable_motion_of_rigid_object(self, instance_id, pmb_type): 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: @@ -610,19 +550,13 @@ def setup_electrostatic_interactions(self,units, kT, c_salt=None, solvent_permit 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 + 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 + 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'], @@ -641,10 +575,7 @@ def setup_electrostatic_interactions(self,units, kT, c_salt=None, solvent_permit 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 + 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): @@ -676,11 +607,15 @@ def setup_cpH (self, counter_ion, constant_pH, exclusion_range=None, use_exclusi exclusion_radius_per_type = self.db.get_radius_map() else: exclusion_radius_per_type = {} + kwargs = {} + if espressomd.version.version() >= (5, 1, 0): + kwargs["system"] = self.espresso_system 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) + exclusion_radius_per_type = exclusion_radius_per_type, + **kwargs) conterion_tpl = self.db.get_template(name=counter_ion, pmb_type="particle") conterion_state = self.db.get_template(name=conterion_tpl.initial_state, @@ -749,10 +684,14 @@ def setup_gcmc(self, c_salt_res, salt_cation_name, salt_anion_name, activity_coe exclusion_radius_per_type = self.db.get_radius_map() else: exclusion_radius_per_type = {} + kwargs = {} + if espressomd.version.version() >= (5, 1, 0): + kwargs["system"] = self.espresso_system 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) + exclusion_radius_per_type = exclusion_radius_per_type, + **kwargs) # 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 @@ -846,10 +785,14 @@ def setup_grxmc_reactions(self, pH_res, c_salt_res, proton_name, hydroxide_name, exclusion_radius_per_type = self.db.get_radius_map() else: exclusion_radius_per_type = {} + kwargs = {} + if espressomd.version.version() >= (5, 1, 0): + kwargs["system"] = self.espresso_system 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) + exclusion_radius_per_type = exclusion_radius_per_type, + **kwargs) # 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) @@ -1142,10 +1085,14 @@ def setup_grxmc_unified(self, pH_res, c_salt_res, cation_name, anion_name, activ exclusion_radius_per_type = self.db.get_radius_map() else: exclusion_radius_per_type = {} + kwargs = {} + if espressomd.version.version() >= (5, 1, 0): + kwargs["system"] = self.espresso_system 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) + exclusion_radius_per_type = exclusion_radius_per_type, + **kwargs) # 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) @@ -1319,7 +1266,7 @@ def setup_lj_interactions(self, shift_potential=True, combining_rule='Lorentz-Be 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 + - Check the documentation of ESPResSo for more info about the potential https://espressomd.github.io/doc5.0.1/inter_non-bonded.html """ from itertools import combinations_with_replacement diff --git a/samples/Beyer2024/globular_protein.py b/samples/Beyer2024/globular_protein.py index ea07025..eeec6db 100644 --- a/samples/Beyer2024/globular_protein.py +++ b/samples/Beyer2024/globular_protein.py @@ -19,6 +19,7 @@ from pathlib import Path import tqdm import espressomd +import espressomd.version import argparse import numpy as np import pandas as pd @@ -268,8 +269,8 @@ print(pmb.get_reactions_df()) type_map = pmb.get_type_map() -types = list (type_map.values()) -espresso_system.setup_type_map( type_list = types) +if espressomd.version.version() < (5, 1, 0): + espresso_system.setup_type_map(type_list = type_map.values()) # Setup the non-interacting type for speeding up the sampling of the reactions non_interacting_type = max(type_map.values())+1 @@ -320,7 +321,7 @@ for step in tqdm.trange(N_samples, disable=not verbose): espresso_system.integrator.run (steps = integ_steps) - pmb.simulation_engine.do_reaction(cpH, steps=total_ionisable_groups) + cpH.reaction(steps=total_ionisable_groups) protein_net_charge = pmb.calculate_net_charge( object_name=protein_name, pmb_type="protein", diff --git a/samples/Beyer2024/peptide.py b/samples/Beyer2024/peptide.py index 8fdda11..ca8009f 100644 --- a/samples/Beyer2024/peptide.py +++ b/samples/Beyer2024/peptide.py @@ -19,6 +19,7 @@ # Load espresso, pyMBE and other necessary libraries from pathlib import Path import espressomd +import espressomd.version import pandas as pd import argparse import tqdm @@ -184,11 +185,11 @@ print(pmb.get_reactions_df()) # Setup espresso to track the ionization of the acid/basic groups in peptide -type_map =pmb.get_type_map() -espresso_system.setup_type_map(type_list = list(type_map.values())) +if espressomd.version.version() < (5, 1, 0): + espresso_system.setup_type_map(type_list = pmb.get_type_map().values()) # Setup the non-interacting type for speeding up the sampling of the reactions -non_interacting_type = max(type_map.values())+1 +non_interacting_type = max(pmb.get_type_map().values())+1 cpH.set_non_interacting_type (type=non_interacting_type) if verbose: print(f"The non-interacting type is set to {non_interacting_type}") @@ -224,7 +225,7 @@ # Run LD espresso_system.integrator.run(steps=MD_steps_per_sample) # Run MC - pmb.simulation_engine.do_reaction(cpH, steps=len(sequence)) + cpH.reaction(steps=len(sequence)) # Sample observables charge_dict=pmb.calculate_net_charge( object_name=sequence, diff --git a/samples/Beyer2024/weak_polyelectrolyte_dialysis.py b/samples/Beyer2024/weak_polyelectrolyte_dialysis.py index aa24618..c1e3fd0 100644 --- a/samples/Beyer2024/weak_polyelectrolyte_dialysis.py +++ b/samples/Beyer2024/weak_polyelectrolyte_dialysis.py @@ -16,12 +16,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -####################################################### -# Loading modules -####################################################### - # Load python modules import espressomd +import espressomd.version from pathlib import Path import numpy as np import pandas as pd @@ -36,8 +33,6 @@ # Create an instance of pyMBE library pmb = pyMBE.pymbe_library(seed=42) -# Load some functions from the handy_scripts library for convenience - ####################################################### # Setting parameters for the simulation @@ -209,13 +204,12 @@ print("The acid-base reaction has been successfully set up for:") print(pmb.get_reactions_df()) -# Setup espresso to track the ionization of the acid groups -type_map = pmb.get_type_map() -types = list(type_map.values()) -espresso_system.setup_type_map(type_list = types) +# Setup espresso to track the ionization of the acid/basic groups in peptide +if espressomd.version.version() < (5, 1, 0): + espresso_system.setup_type_map(type_list = pmb.get_type_map().values()) # Setup the non-interacting type for speeding up the sampling of the reactions -non_interacting_type = max(type_map.values())+1 +non_interacting_type = max(pmb.get_type_map().values())+1 grxmc.set_non_interacting_type (type=non_interacting_type) #Set up the interactions @@ -233,7 +227,7 @@ print("Running warmup without electrostatics") for i in tqdm.trange(100, disable=not verbose): espresso_system.integrator.run(steps=1000) - pmb.simulation_engine.do_reaction(grxmc, steps=1000) + grxmc.reaction(steps=1000) pmb.simulation_engine.setup_electrostatic_interactions(units=pmb.units, kT=pmb.kT, @@ -257,7 +251,7 @@ N_warmup_loops = 100 for i in tqdm.trange(N_warmup_loops, disable=not verbose): espresso_system.integrator.run(steps=1000) - pmb.simulation_engine.do_reaction(grxmc, steps=100) + grxmc.reaction(steps=100) # Main loop print("Started production run.") @@ -273,7 +267,7 @@ N_production_loops = 100 for i in tqdm.trange(N_production_loops, disable=not verbose): espresso_system.integrator.run(steps=1000) - pmb.simulation_engine.do_reaction(grxmc, steps=100) + grxmc.reaction(steps=100) # Measure time time_series["time"].append(espresso_system.time) # Measure degree of ionization diff --git a/samples/branched_polyampholyte.py b/samples/branched_polyampholyte.py index ed9b0d9..29353a8 100644 --- a/samples/branched_polyampholyte.py +++ b/samples/branched_polyampholyte.py @@ -16,16 +16,14 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# Load espresso, pyMBE and other necessary libraries from pathlib import Path import espressomd +import espressomd.version import argparse import tqdm import pandas as pd from espressomd.io.writer import vtf import pyMBE - -# Load some functions from the handy_scripts library for convenience from pyMBE.lib.analysis import built_output_name @@ -181,12 +179,11 @@ print(pmb.get_reactions_df()) # Setup espresso to track the ionization of the acid/basic groups -type_map = pmb.get_type_map() -types = list(type_map.values()) -espresso_system.setup_type_map(type_list = types) +if espressomd.version.version() < (5, 1, 0): + espresso_system.setup_type_map(type_list = pmb.get_type_map().values()) # Setup the non-interacting type for speeding up the sampling of the reactions -non_interacting_type = max(type_map.values())+1 +non_interacting_type = max(pmb.get_type_map().values())+1 cpH.set_non_interacting_type (type=non_interacting_type) if verbose: print(f"The non interacting type is set to {non_interacting_type}") @@ -232,9 +229,9 @@ # Production loop N_frame=0 -for step in tqdm.trange(N_samples): +for step in tqdm.trange(N_samples, disable=args.test): espresso_system.integrator.run(steps=MD_steps_per_sample) - pmb.simulation_engine.do_reaction(cpH, steps=total_ionisable_groups) + cpH.reaction(steps=total_ionisable_groups) # Get polyampholyte net charge charge_dict=pmb.calculate_net_charge( object_name="polyampholyte", diff --git a/samples/peptide_cpH.py b/samples/peptide_cpH.py index 78076a5..c62edee 100644 --- a/samples/peptide_cpH.py +++ b/samples/peptide_cpH.py @@ -18,6 +18,7 @@ # Load espresso, pyMBE and other necessary libraries import espressomd +import espressomd.version import pandas as pd import tqdm from espressomd.io.writer import vtf @@ -72,7 +73,7 @@ if args.test: MD_steps_per_sample = 1 ideal=True - N_samples = 2000 # improve sampling for testing + N_samples = 3200 # improve sampling for testing # Peptide parameters sequence = args.sequence @@ -191,12 +192,11 @@ print(pmb.get_reactions_df()) # Setup espresso to track the ionization of the acid/basic groups in peptide -type_map =pmb.get_type_map() -types = list (type_map.values()) -espresso_system.setup_type_map( type_list = types) +if espressomd.version.version() < (5, 1, 0): + espresso_system.setup_type_map(type_list = pmb.get_type_map().values()) # Setup the non-interacting type for speeding up the sampling of the reactions -non_interacting_type = max(type_map.values())+1 +non_interacting_type = max(pmb.get_type_map().values())+1 cpH.set_non_interacting_type (type=non_interacting_type) if verbose: print('The non-interacting type is set to ', non_interacting_type) @@ -240,11 +240,11 @@ # Main loop for performing simulations at different pH-values N_frame=0 -for sample in tqdm.trange(N_samples): +for sample in tqdm.trange(N_samples, disable=args.test): # LD sampling of the configuration space espresso_system.integrator.run(steps=MD_steps_per_sample) # cpH sampling of the reaction space - pmb.simulation_engine.do_reaction(cpH, steps=total_ionisable_groups) # rule of thumb: one reaction step per titratable group (on average) + cpH.reaction(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( object_name=peptide_name, diff --git a/samples/peptide_mixture_grxmc_ideal.py b/samples/peptide_mixture_grxmc_ideal.py index 3cb6a48..d26cc13 100644 --- a/samples/peptide_mixture_grxmc_ideal.py +++ b/samples/peptide_mixture_grxmc_ideal.py @@ -16,8 +16,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -#Load espresso, pyMBE and other necessary libraries import espressomd +import espressomd.version from pathlib import Path import pandas as pd import argparse @@ -269,11 +269,11 @@ print(pmb.get_reactions_df()) # Setup espresso to track the ionization of the acid/basic groups in peptide -type_map =pmb.get_type_map() -types = list (type_map.values()) -espresso_system.setup_type_map(type_list = types) +if espressomd.version.version() < (5, 1, 0): + espresso_system.setup_type_map(type_list = pmb.get_type_map().values()) # Setup the non-interacting type for speeding up the sampling of the reactions +type_map = pmb.get_type_map() non_interacting_type = max(type_map.values())+1 grxmc.set_non_interacting_type (type=non_interacting_type) if verbose: @@ -302,7 +302,7 @@ N_frame=0 for step in range(N_samples): espresso_system.integrator.run(steps=MD_steps_per_sample) - pmb.simulation_engine.do_reaction(grxmc, steps=total_ionisable_groups) + grxmc.reaction(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( diff --git a/samples/salt_solution_gcmc.py b/samples/salt_solution_gcmc.py index b3900ab..5f6dc7c 100644 --- a/samples/salt_solution_gcmc.py +++ b/samples/salt_solution_gcmc.py @@ -19,6 +19,7 @@ # Load python modules from pathlib import Path import espressomd +import espressomd.version import numpy as np import pandas as pd from scipy import interpolate @@ -28,8 +29,6 @@ # Import pyMBE import pyMBE from pyMBE.lib import analysis -#Import functions from handy_functions script - # Create an instance of pyMBE library pmb = pyMBE.pymbe_library(seed=42) @@ -129,11 +128,11 @@ print("Set up GCMC...") # Setup espresso to track the ionization of the acid/basic groups in peptide -type_map = pmb.get_type_map() -types = list (type_map.values()) -espresso_system.setup_type_map(type_list = types) +if espressomd.version.version() < (5, 1, 0): + espresso_system.setup_type_map(type_list = pmb.get_type_map().values()) # Setup the non-interacting type for speeding up the sampling of the reactions +type_map = pmb.get_type_map() non_interacting_type = max(type_map.values())+1 RE.set_non_interacting_type(type=non_interacting_type) if verbose: @@ -159,7 +158,7 @@ print("Running warmup without electrostatics") for i in tqdm.trange(100, disable=not verbose): espresso_system.integrator.run(steps=100) - pmb.simulation_engine.do_reaction(RE, steps=100) + RE.reaction(steps=100) if args.mode == "interacting": pmb.simulation_engine.setup_electrostatic_interactions(units=pmb.units, @@ -181,7 +180,7 @@ N_warmup_loops = 100 for i in tqdm.trange(N_warmup_loops, disable=not verbose): espresso_system.integrator.run(steps=100) - pmb.simulation_engine.do_reaction(RE, steps=100) + RE.reaction(steps=100) # Main loop print("Started production run.") @@ -195,13 +194,13 @@ N_production_loops = 100 for i in tqdm.trange(N_production_loops, disable=not verbose): espresso_system.integrator.run(steps=100) - pmb.simulation_engine.do_reaction(RE, steps=100) + RE.reaction(steps=100) # Measure time time_series["time"].append(espresso_system.time) # Measure degree of ionization - number_of_ion_pairs = pmb.simulation_engine.get_number_of_particles(type_map[cation_name]) + number_of_ion_pairs = espresso_system.number_of_particles(type=type_map[cation_name]) time_series["c_salt"].append((number_of_ion_pairs/(volume * pmb.N_A)).magnitude) data_path = args.output diff --git a/samples/weak_polyacid_hydrogel_grxmc.py b/samples/weak_polyacid_hydrogel_grxmc.py index 944118e..270e87d 100644 --- a/samples/weak_polyacid_hydrogel_grxmc.py +++ b/samples/weak_polyacid_hydrogel_grxmc.py @@ -18,6 +18,7 @@ # import espressomd +import espressomd.version from espressomd.io.writer import vtf from pathlib import Path import numpy as np @@ -235,18 +236,17 @@ salt_anion_name=chloride_name, activity_coefficient=activity_coefficient_monovalent_pair) -# Setup espresso to track the ionization of the acid groups -type_map = pmb.get_type_map() -types = list(type_map.values()) -espresso_system.setup_type_map(type_list = types) +# Setup espresso to track the ionization of the acid/basic groups in peptide +if espressomd.version.version() < (5, 1, 0): + espresso_system.setup_type_map(type_list = pmb.get_type_map().values()) # Setup the non-interacting type for speeding up the sampling of the reactions -non_interacting_type = max(type_map.values())+1 +non_interacting_type = max(pmb.get_type_map().values())+1 grxmc.set_non_interacting_type (type=non_interacting_type) for i in tqdm.trange(100): espresso_system.integrator.run(steps=1000) - pmb.simulation_engine.do_reaction(grxmc,1000) + grxmc.reaction(steps=1000) pmb.simulation_engine.setup_electrostatic_interactions(units=pmb.units, kT=pmb.kT, @@ -260,7 +260,7 @@ print("*** Running warmup with electrostatics... ***") for i in tqdm.trange(N_warmup_loops): espresso_system.integrator.run(steps=1000) - pmb.simulation_engine.do_reaction(grxmc,100) + grxmc.reaction(steps=100) # Main loop print("*** Starting production run... ***") @@ -280,7 +280,7 @@ for i in tqdm.trange(N_production_loops): espresso_system.integrator.run(steps=1000) - pmb.simulation_engine.do_reaction(grxmc,200) + grxmc.reaction(steps=200) # Measure time time_series["time"].append(espresso_system.time) diff --git a/testsuite/setup_salt_ions_unit_tests.py b/testsuite/setup_salt_ions_unit_tests.py index 67caad9..f3c83f5 100644 --- a/testsuite/setup_salt_ions_unit_tests.py +++ b/testsuite/setup_salt_ions_unit_tests.py @@ -44,7 +44,6 @@ sigma=0.3*pmb.units.nm, epsilon=1*pmb.units.Quantity(1,"reduced_energy")) -type_map=pmb.get_type_map() # System parameters c_salt_input = 0.01 * pmb.units.mol/ pmb.units.L N_SALT_ION_PAIRS = 50 @@ -53,7 +52,8 @@ box_l=[L.to('reduced_length').magnitude]*3 # Create an instance of an espresso system espresso_system=espressomd.System (box_l = box_l ) -espresso_system.setup_type_map(type_list=type_map.values()) +if espressomd.version.version() < (5, 1, 0): + espresso_system.setup_type_map(type_list = pmb.get_type_map().values()) pmb.define_particle(name='0P', z=0, @@ -124,14 +124,13 @@ def test_salt_addition(self): def check_salt_concentration(espresso_system,cation_name,anion_name,c_salt,N_SALT_ION_PAIRS): 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(box_l=box_l, cation_name=cation_name, anion_name=anion_name, c_salt=c_salt) pmb.add_instances_to_engine() - self.assertEqual(pmb.simulation_engine.get_number_of_particles(type_map[cation_name]),N_SALT_ION_PAIRS*abs(charge_number_map[type_map[anion_name]])) - self.assertEqual(pmb.simulation_engine.get_number_of_particles(type_map[anion_name]),N_SALT_ION_PAIRS*abs(charge_number_map[type_map[cation_name]])) + self.assertEqual(espresso_system.number_of_particles(type=type_map[cation_name]),N_SALT_ION_PAIRS*abs(charge_number_map[type_map[anion_name]])) + self.assertEqual(espresso_system.number_of_particles(type=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")) cation_ids = pmb.get_particle_id_map(object_name=cation_name)["all"] anion_ids = pmb.get_particle_id_map(object_name=anion_name)["all"] @@ -168,15 +167,15 @@ def test_salt_addition_concentration_units(self): """ Unit test: check that create_added_salt works for an input c_salt in [particle/lenght**3]. """ + type_map=pmb.get_type_map() 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(box_l=box_l, cation_name="Na", anion_name="Cl", c_salt=c_salt_part) pmb.add_instances_to_engine() - self.assertEqual(pmb.simulation_engine.get_number_of_particles(type_map["Na"]),N_SALT_ION_PAIRS) - self.assertEqual(pmb.simulation_engine.get_number_of_particles(type_map["Cl"]),N_SALT_ION_PAIRS) + self.assertEqual(espresso_system.number_of_particles(type=type_map["Na"]),N_SALT_ION_PAIRS) + self.assertEqual(espresso_system.number_of_particles(type=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")) cation_ids = pmb.get_particle_id_map(object_name="Na")["all"] anion_ids = pmb.get_particle_id_map(object_name="Cl")["all"] @@ -233,13 +232,13 @@ def test_counterions(molecule_name, cation_name, anion_name, espresso_system, ex anion_name=anion_name, box_l=box_l) pmb.add_instances_to_engine() - espresso_system.setup_type_map(type_list=type_map.values()) - self.assertEqual(pmb.simulation_engine.get_number_of_particles( - type_map[cation_name]), - expected_numbers[cation_name]) - self.assertEqual(pmb.simulation_engine.get_number_of_particles( - type_map[anion_name]), - expected_numbers[anion_name]) + type_map=pmb.get_type_map() + self.assertEqual( + espresso_system.number_of_particles(type=type_map[cation_name]), + expected_numbers[cation_name]) + self.assertEqual( + espresso_system.number_of_particles(type=type_map[anion_name]), + expected_numbers[anion_name]) 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, @@ -289,6 +288,7 @@ def test_sanity_create_counterions(self): box_l=box_l, use_default_bond=True) pmb.add_instances_to_engine() + type_map=pmb.get_type_map() input_parameters={"cation_name":"Ca", "anion_name":"Cl", "object_name":'isoelectric_polyampholyte', @@ -312,10 +312,9 @@ def test_sanity_create_counterions(self): anion_name="Cl", box_l=box_l) pmb.add_instances_to_engine() - espresso_system.setup_type_map(type_list=type_map.values()) - self.assertEqual(pmb.simulation_engine.get_number_of_particles(type_map["Na"]),0) - self.assertEqual(pmb.simulation_engine.get_number_of_particles(type_map["Cl"]),0) + self.assertEqual(espresso_system.number_of_particles(type=type_map["Na"]),0) + self.assertEqual(espresso_system.number_of_particles(type=type_map["Cl"]),0) # Assert that no counterions are created if the wrong object names are provided inputs = {"object_name":'test', "cation_name":"Na", diff --git a/testsuite/test_handy_functions.py b/testsuite/test_handy_functions.py index 6939e25..f5ec153 100644 --- a/testsuite/test_handy_functions.py +++ b/testsuite/test_handy_functions.py @@ -104,12 +104,9 @@ def test_langevin_setup(self): second=espresso_system.time_step, msg="The input time step in `lib.handy_functions.setup_langevin_dynamics` is not consistent with the one in the espresso simulation System") ## Test setup of the thermostat - if espressomd.version.friendly() == "4.2": - thermostat_setup=espresso_system.thermostat.get_state()[0] - else: - thermostat_setup=espresso_system.thermostat.langevin.get_params() - thermostat_setup["kT"] = espresso_system.thermostat.kT - thermostat_setup["type"] = "LANGEVIN" + thermostat_setup=espresso_system.thermostat.langevin.get_params() + thermostat_setup["kT"] = espresso_system.thermostat.kT + thermostat_setup["type"] = "LANGEVIN" self.assertEqual(first="LANGEVIN", second=thermostat_setup["type"], msg="`lib.handy_functions.setup_langevin_dynamics` is setting a different thermostat than Langevin") @@ -210,10 +207,7 @@ def test_setup_electrostatics(self): coulomb_prefactor=Bjerrum_length*electrostatics_inputs["kT"] # Test the P3M setup pmb.simulation_engine.setup_electrostatic_interactions(**electrostatics_inputs) - if espressomd.version.friendly() == "4.2": - coulomb = espresso_system.actors.active_actors.copy()[0] - else: - coulomb = espresso_system.electrostatics.solver + coulomb = espresso_system.electrostatics.solver coulomb_params = coulomb.get_params() self.assertEqual(first=coulomb.name(), second='Coulomb::CoulombP3M', @@ -227,10 +221,7 @@ def test_setup_electrostatics(self): self.assertEqual(first=electrostatics_inputs["tune_p3m"], second=coulomb_params["is_tuned"], msg="lib.handy_functions.setup_electrostatic_interactions does not tune the P3M method") - if espressomd.version.friendly() == "4.2": - espresso_system.actors.remove(coulomb) - else: - coulomb = espresso_system.electrostatics.solver = None + coulomb = espresso_system.electrostatics.solver = None ## Test the setup of the P3M method without tuning it with some input parameters electrostatics_inputs["tune_p3m"] = False electrostatics_inputs["params"] = {"mesh": [8, 8, 8], @@ -238,30 +229,21 @@ def test_setup_electrostatics(self): "alpha": 1.1265e+01, "r_cut": 1} pmb.simulation_engine.setup_electrostatic_interactions(**electrostatics_inputs) - if espressomd.version.friendly() == "4.2": - coulomb = espresso_system.actors.active_actors.copy()[0] - else: - coulomb = espresso_system.electrostatics.solver + coulomb = espresso_system.electrostatics.solver coulomb_params = coulomb.get_params() for param in electrostatics_inputs["params"]: np.testing.assert_allclose( np.copy(electrostatics_inputs["params"][param]), np.copy(coulomb_params[param]), err_msg="lib.handy_functions.setup_electrostatic_interactions sets up the wrong P3M parameters") - if espressomd.version.friendly() == "4.2": - espresso_system.actors.remove(coulomb) - else: - coulomb = espresso_system.electrostatics.solver = None + coulomb = espresso_system.electrostatics.solver = None electrostatics_inputs["params"] = None # Test the Debye–Hückel setup 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"]) pmb.simulation_engine.setup_electrostatic_interactions(**electrostatics_inputs) - if espressomd.version.friendly() == "4.2": - dh = espresso_system.actors.active_actors.copy()[0] - else: - dh = espresso_system.electrostatics.solver + dh = espresso_system.electrostatics.solver dh_params = dh.get_params() self.assertEqual(first=dh.name(), second='Coulomb::DebyeHueckel', @@ -275,16 +257,10 @@ def test_setup_electrostatics(self): self.assertAlmostEqual(first=dh_params["r_cut"], second=3*kappa.m_as('reduced_length'), msg="lib.handy_functions.setup_electrostatic_interactions sets up the wrong cut-off for the DH method") - if espressomd.version.friendly() == "4.2": - espresso_system.actors.remove(dh) - else: - coulomb = espresso_system.electrostatics.solver = None + coulomb = espresso_system.electrostatics.solver = None electrostatics_inputs["c_salt"] = pmb.units.Quantity(1, "mol/L")*pmb.N_A pmb.simulation_engine.setup_electrostatic_interactions(**electrostatics_inputs) - if espressomd.version.friendly() == "4.2": - dh = espresso_system.actors.active_actors.copy()[0] - else: - dh = espresso_system.electrostatics.solver + dh = espresso_system.electrostatics.solver dh_params = dh.get_params() self.assertAlmostEqual(first=dh_params["kappa"], second=(1./kappa).m_as('1/ reduced_length'), @@ -292,17 +268,11 @@ def test_setup_electrostatics(self): self.assertAlmostEqual(first=dh_params["r_cut"], second=3*kappa.m_as('reduced_length'), msg="lib.handy_functions.setup_electrostatic_interactions sets up the wrong cut-off for the DH method") - if espressomd.version.friendly() == "4.2": - espresso_system.actors.remove(dh) - else: - coulomb = espresso_system.electrostatics.solver = None + coulomb = espresso_system.electrostatics.solver = None # Test a non-default cut-off electrostatics_inputs["params"] = {"r_cut": 3} pmb.simulation_engine.setup_electrostatic_interactions(**electrostatics_inputs) - if espressomd.version.friendly() == "4.2": - dh = espresso_system.actors.active_actors.copy()[0] - else: - dh = espresso_system.electrostatics.solver + dh = espresso_system.electrostatics.solver dh_params = dh.get_params() self.assertAlmostEqual(first=dh_params["r_cut"], second=electrostatics_inputs["params"]["r_cut"], From 3e0f9bad8d7e026de2c067a703e81f4b44531004 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Wed, 26 Aug 2026 19:32:42 +0200 Subject: [PATCH 2/2] Update changelog and readme --- CHANGELOG.md | 1 + README.md | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05025f7..a15004b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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) +- Legacy ESPResSo 4.2-specific helper functions `do_reaction` and `get_number_of_particles` were removed from the API. ESPResSo 4.2 is no longer officially supported. (#155) ## [1.0.0] - 2025-10-08 diff --git a/README.md b/README.md index 0212541..e4bac0f 100644 --- a/README.md +++ b/README.md @@ -68,8 +68,8 @@ git clone git@github.com:pyMBE-dev/pyMBE.git Please, be aware that pyMBE is intended to be a supporting tool to setup simulations with ESPResSo. Thus, for most of its functionalities ESPResSo must also be available. -pyMBE supports ESPResSo 4.2 and ESPResSo 4.3-dev. -Following the NEP29 guidelines, we recommend using Python3.10+. +pyMBE supports ESPResSo 5.0 and ESPResSo 5.1-dev. +Following the NEP29 guidelines, we recommend using Python3.11+. Both NumPy 1 and NumPy 2 are supported. The pyMBE module needs a Python virtual environment to avoid compatibility issues with its dependencies. @@ -99,7 +99,7 @@ python3 -m pip install -r requirements.txt "numpy>=2.1" "pandas>=2.0" We highlight that the path `/home/user/espresso/build` is just an example of a possible path to the ESPResSo build folder. The user should change this path to match the local absolute path where ESPResSo was built. Also, ESPResSo must be built with the same NumPy version as the one installed in the environment to avoid API version mismatch. -For more details on how to install ESPResSo, please consult the [ESPResSo installation guide](https://espressomd.github.io/doc4.2.2/installation.html). +For more details on how to install ESPResSo, please consult the [ESPResSo installation guide](https://espressomd.github.io/doc5.0.1/installation.html). The pyMBE virtual environment can be deactivated at any moment as follows: @@ -111,7 +111,7 @@ Cluster users who rely on module files to load dependencies should opt for the following alternative: ```sh -module load ESPResSo/4.2.2-foss-2023a # adapt release if needed +module load ESPResSo/5.0.1-foss-2025a # adapt release if needed python3 -m venv --system-site-packages pymbe source pymbe/bin/activate python3 maintainer/configure_venv.py @@ -128,7 +128,7 @@ Now you can use pyMBE and ESPResSo by activating the virtual environment: ```sh $ source pymbe/bin/activate (pymbe) $ python3 -c "import espressomd.version; print(espressomd.version.friendly())" -4.2 +5.0.1 (pymbe) $ python3 -c "import pyMBE; print(pyMBE.__file__)" /home/user/Documents/pyMBE/pyMBE/__init__.py $ deactivate