From 744e40e2fc3a5ec298ef0408e23a3a0a02245f1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Thu, 27 Aug 2020 16:42:01 +0200 Subject: [PATCH 1/5] Bump CMake minimal version to 3.11 Imported targets with namespaces are only supported since 3.11. --- CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 43f5a7ef8a3..cd6295c2ca8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ # along with this program. If not, see . # -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.11) message(STATUS "CMake version: ${CMAKE_VERSION}") if(POLICY CMP0076) # make target_sources() convert relative paths to absolute @@ -278,7 +278,6 @@ endif(WITH_STOKESIAN_DYNAMICS) if(WITH_STOKESIAN_DYNAMICS) set(CMAKE_INSTALL_LIBDIR "${CMAKE_INSTALL_PREFIX}/${PYTHON_INSTDIR}/espressomd") - cmake_minimum_required(VERSION 3.11) include(FetchContent) FetchContent_Declare( stokesian_dynamics From 6f4aa64d279d1ba2e807726ce53d1c4932ebfc91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Thu, 27 Aug 2020 16:44:14 +0200 Subject: [PATCH 2/5] Bump Python minimal version to 3.6 Python 3.5 is deprecated in the NEP 29 support table since 2019. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cd6295c2ca8..6e887417044 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -166,7 +166,7 @@ if(WITH_CUDA) endif() endif(WITH_CUDA) -find_package(PythonInterp 3.5 REQUIRED) +find_package(PythonInterp 3.6 REQUIRED) if(WITH_PYTHON) find_package(Cython 0.26 REQUIRED) From d94203ba465e5eb693f0b9efc3a38441476536b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Thu, 27 Aug 2020 17:38:35 +0200 Subject: [PATCH 3/5] python: Use f-strings --- src/python/espressomd/actors.pyx | 2 +- src/python/espressomd/analyze.pyx | 11 ++--- src/python/espressomd/cellsystem.pyx | 4 +- src/python/espressomd/checkpointing.py | 14 +++--- src/python/espressomd/collision_detection.pyx | 2 +- src/python/espressomd/electrokinetics.pyx | 6 +-- src/python/espressomd/highlander.py | 3 +- src/python/espressomd/interactions.pyx | 24 +++++----- src/python/espressomd/lb.pyx | 2 +- src/python/espressomd/particle_data.pyx | 48 +++++++++---------- src/python/espressomd/polymer.pyx | 2 +- src/python/espressomd/reaction_ensemble.pyx | 14 +++--- src/python/espressomd/system.pyx | 4 +- src/python/espressomd/utils.pyx | 23 ++++----- src/python/espressomd/version.pyx | 2 +- 15 files changed, 80 insertions(+), 81 deletions(-) diff --git a/src/python/espressomd/actors.pyx b/src/python/espressomd/actors.pyx index 53747d92948..30d6d53998d 100644 --- a/src/python/espressomd/actors.pyx +++ b/src/python/espressomd/actors.pyx @@ -57,7 +57,7 @@ cdef class Actor: if k in self.valid_keys(): self._params[k] = kwargs[k] else: - raise KeyError("%s is not a valid key" % k) + raise KeyError(f"{k} is not a valid key") def _activate(self): inter = self._get_interaction_type() diff --git a/src/python/espressomd/analyze.pyx b/src/python/espressomd/analyze.pyx index d1f80e9c9ce..163e3fff588 100644 --- a/src/python/espressomd/analyze.pyx +++ b/src/python/espressomd/analyze.pyx @@ -186,12 +186,12 @@ class Analysis: for i in range(len(p1)): if not is_valid_type(p1[i], int): raise TypeError( - "Particle types in p1 have to be of type int, got: " + repr(p1[i])) + f"Particle types in p1 have to be of type int, got: {repr(p1[i])}") for i in range(len(p2)): if not is_valid_type(p2[i], int): raise TypeError( - "Particle types in p2 have to be of type int, got: " + repr(p2[i])) + f"Particle types in p2 have to be of type int, got: {repr(p2[i])}") return analyze.mindist(analyze.partCfg(), p1, p2) @@ -249,7 +249,7 @@ class Analysis: "The p_type keyword argument must be provided (particle type)") check_type_or_throw_except(p_type, 1, int, "p_type has to be an int") if p_type < 0 or p_type >= analyze.max_seen_particle_type: - raise ValueError("Particle type {} does not exist!".format(p_type)) + raise ValueError(f"Particle type {p_type} does not exist!") return analyze.centerofmass(analyze.partCfg(), p_type) @@ -736,8 +736,7 @@ class Analysis: check_type_or_throw_except( ptype, 1, int, "particle type has to be an int") if ptype < 0 or ptype >= analyze.max_seen_particle_type: - raise ValueError( - "Particle type {} does not exist!".format(ptype)) + raise ValueError(f"Particle type {ptype} does not exist!") selection = self._system.part.select(lambda p: (p.type in p_type)) cm = np.mean(selection.pos, axis=0) mat = np.zeros(shape=(3, 3)) @@ -787,7 +786,7 @@ class Analysis: "The p_type keyword argument must be provided (particle type)") check_type_or_throw_except(p_type, 1, int, "p_type has to be an int") if p_type < 0 or p_type >= analyze.max_seen_particle_type: - raise ValueError("Particle type {} does not exist!".format(p_type)) + raise ValueError(f"Particle type {p_type} does not exist!") analyze.momentofinertiamatrix( analyze.partCfg(), p_type, MofImatrix) diff --git a/src/python/espressomd/cellsystem.pyx b/src/python/espressomd/cellsystem.pyx index 02cfc4ea458..48eadba1185 100644 --- a/src/python/espressomd/cellsystem.pyx +++ b/src/python/espressomd/cellsystem.pyx @@ -137,8 +137,8 @@ cdef class CellSystem: def __set__(self, _node_grid): if not np.prod(_node_grid) == n_nodes: - raise ValueError("Number of available nodes " + str( - n_nodes) + " and imposed node grid " + str(_node_grid) + " do not agree.") + raise ValueError( + f"Number of available nodes {n_nodes} and imposed node grid {_node_grid} do not agree.") else: node_grid[0] = _node_grid[0] node_grid[1] = _node_grid[1] diff --git a/src/python/espressomd/checkpointing.py b/src/python/espressomd/checkpointing.py index 57141e10896..37023468dae 100644 --- a/src/python/espressomd/checkpointing.py +++ b/src/python/espressomd/checkpointing.py @@ -67,7 +67,7 @@ def __init__(self, checkpoint_id=None, checkpoint_path="."): # update checkpoint counter self.counter = 0 while os.path.isfile(os.path.join( - self.checkpoint_dir, "{}.checkpoint".format(self.counter))): + self.checkpoint_dir, f"{self.counter}.checkpoint")): self.counter += 1 # init signals @@ -134,11 +134,11 @@ def register(self, *args): # if not a in dir(self.calling_module): if not self.__hasattr_submodule(self.calling_module, a): raise KeyError( - "The given object '{}' was not found in the current scope.".format(a)) + f"The given object '{a}' was not found in the current scope.") if a in self.checkpoint_objects: raise KeyError( - "The given object '{}' is already registered for checkpointing.".format(a)) + f"The given object '{a}' is already registered for checkpointing.") self.checkpoint_objects.append(a) @@ -154,7 +154,7 @@ def unregister(self, *args): for a in args: if not isinstance(a, str) or a not in self.checkpoint_objects: raise KeyError( - "The given object '{}' was not registered for checkpointing yet.".format(a)) + f"The given object '{a}' was not registered for checkpointing yet.") self.checkpoint_objects.remove(a) @@ -204,7 +204,7 @@ def save(self, checkpoint_index=None): if checkpoint_index is None: checkpoint_index = self.counter filename = os.path.join( - self.checkpoint_dir, "{}.checkpoint".format(checkpoint_index)) + self.checkpoint_dir, f"{checkpoint_index}.checkpoint") tmpname = filename + ".__tmp__" with open(tmpname, "wb") as checkpoint_file: @@ -226,7 +226,7 @@ def load(self, checkpoint_index=None): checkpoint_index = self.get_last_checkpoint_index() filename = os.path.join( - self.checkpoint_dir, "{}.checkpoint".format(checkpoint_index)) + self.checkpoint_dir, f"{checkpoint_index}.checkpoint") with open(filename, "rb") as f: checkpoint_data = pickle.load(f) @@ -288,7 +288,7 @@ def register_signal(self, signum=None): if signum in self.checkpoint_signals: raise KeyError( - "The signal {} is already registered for checkpointing.".format(signum)) + f"The signal {signum} is already registered for checkpointing.") signal.signal(signum, self.__signal_handler) self.checkpoint_signals.append(signum) diff --git a/src/python/espressomd/collision_detection.pyx b/src/python/espressomd/collision_detection.pyx index 9bd28373acf..4674f84b789 100644 --- a/src/python/espressomd/collision_detection.pyx +++ b/src/python/espressomd/collision_detection.pyx @@ -216,7 +216,7 @@ class CollisionDetection(ScriptInterfaceHelper): for key in self._int_mode: if self._int_mode[key] == int_mode: return key - raise Exception("Unknown integer collision mode %d" % int_mode) + raise Exception(f"Unknown integer collision mode {int_mode}") # Pickle support def __reduce__(self): diff --git a/src/python/espressomd/electrokinetics.pyx b/src/python/espressomd/electrokinetics.pyx index ad24bf1242f..162016df24a 100644 --- a/src/python/espressomd/electrokinetics.pyx +++ b/src/python/espressomd/electrokinetics.pyx @@ -43,7 +43,7 @@ IF ELECTROKINETICS: return ElectrokineticsRoutines(np.array(key)) else: raise Exception( - "%s is not a valid key. Should be a point on the nodegrid e.g. ek[0,0,0]," % key) + f"{key} is not a valid key. Should be a point on the nodegrid e.g. ek[0,0,0].") def validate_params(self): """ @@ -431,7 +431,7 @@ IF ELECTROKINETICS: return SpecieRoutines(np.array(key), self.id) else: raise Exception( - "%s is not a valid key. Should be a point on the nodegrid e.g. species[0,0,0]," % key) + f"{key} is not a valid key. Should be a point on the nodegrid e.g. species[0,0,0].") def __init__(self, **kwargs): Species.py_number_of_species += 1 @@ -449,7 +449,7 @@ IF ELECTROKINETICS: if k in self.valid_keys(): self._params[k] = kwargs[k] else: - raise KeyError("%s is not a valid key" % k) + raise KeyError(f"{k} is not a valid key") def valid_keys(self): """ diff --git a/src/python/espressomd/highlander.py b/src/python/espressomd/highlander.py index f27d110062c..d170442bded 100644 --- a/src/python/espressomd/highlander.py +++ b/src/python/espressomd/highlander.py @@ -25,8 +25,7 @@ def __init__(self, cls): self._cls = cls def __str__(self): - return "There can only be one instance of '{}' at any time.".format( - self._cls) + return f"There can only be one instance of '{self._cls}' at any time." def highlander(klass): diff --git a/src/python/espressomd/interactions.pyx b/src/python/espressomd/interactions.pyx index 791cd43fac6..ce67701e173 100644 --- a/src/python/espressomd/interactions.pyx +++ b/src/python/espressomd/interactions.pyx @@ -1825,37 +1825,37 @@ class BondedInteractionNotDefined: self.__class__.__name__ + " not compiled into ESPResSo core") def type_number(self): - raise Exception(("%s has to be defined in myconfig.hpp.") % self.name) + raise Exception(f"{self.name} has to be defined in myconfig.hpp.") def type_name(self): """Name of interaction type. """ - raise Exception(("%s has to be defined in myconfig.hpp.") % self.name) + raise Exception(f"{self.name} has to be defined in myconfig.hpp.") def valid_keys(self): """All parameters that can be set. """ - raise Exception(("%s has to be defined in myconfig.hpp.") % self.name) + raise Exception(f"{self.name} has to be defined in myconfig.hpp.") def required_keys(self): """Parameters that have to be set. """ - raise Exception(("%s has to be defined in myconfig.hpp.") % self.name) + raise Exception(f"{self.name} has to be defined in myconfig.hpp.") def set_default_params(self): """Sets parameters that are not required to their default value. """ - raise Exception(("%s has to be defined in myconfig.hpp.") % self.name) + raise Exception(f"{self.name} has to be defined in myconfig.hpp.") def _get_params_from_es_core(self): - raise Exception(("%s has to be defined in myconfig.hpp.") % self.name) + raise Exception(f"{self.name} has to be defined in myconfig.hpp.") def _set_params_in_es_core(self): - raise Exception(("%s has to be defined in myconfig.hpp.") % self.name) + raise Exception(f"{self.name} has to be defined in myconfig.hpp.") class FeneBond(BondedInteraction): @@ -2591,8 +2591,8 @@ class TabulatedAngle(_TabulatedBase): """ phi = [self._params["min"], self._params["max"]] if abs(phi[0] - 0.) > 1e-5 or abs(phi[1] - self.pi) > 1e-5: - raise ValueError("Tabulated angle expects forces/energies " - "within the range [0, pi], got " + str(phi)) + raise ValueError(f"Tabulated angle expects forces/energies " + f"within the range [0, pi], got {phi}") class TabulatedDihedral(_TabulatedBase): @@ -2631,8 +2631,8 @@ class TabulatedDihedral(_TabulatedBase): """ phi = [self._params["min"], self._params["max"]] if abs(phi[0] - 0.) > 1e-5 or abs(phi[1] - 2 * self.pi) > 1e-5: - raise ValueError("Tabulated dihedral expects forces/energies " - "within the range [0, 2*pi], got " + str(phi)) + raise ValueError(f"Tabulated dihedral expects forces/energies " + f"within the range [0, 2*pi], got {phi}") IF TABULATED == 1: @@ -3313,7 +3313,7 @@ class BondedInteractions: # Check if the bonded interaction exists in ESPResSo core if bond_type == -1: raise ValueError( - "The bonded interaction with the id " + str(key) + " is not yet defined.") + f"The bonded interaction with the id {key} is not yet defined.") # Find the appropriate class representing such a bond bond_class = bonded_interaction_classes[bond_type] diff --git a/src/python/espressomd/lb.pyx b/src/python/espressomd/lb.pyx index bb774784fd1..9fd7697e50d 100644 --- a/src/python/espressomd/lb.pyx +++ b/src/python/espressomd/lb.pyx @@ -57,7 +57,7 @@ cdef class HydrodynamicInteraction(Actor): return LBFluidRoutines(np.array(key)) else: raise Exception( - "%s is not a valid key. Should be a point on the nodegrid e.g. lbf[0,0,0]," % key) + f"{key} is not a valid key. Should be a point on the nodegrid e.g. lbf[0,0,0].") # validate the given parameters on actor initialization #################################################### diff --git a/src/python/espressomd/particle_data.pyx b/src/python/espressomd/particle_data.pyx index 4227de0fe71..0f11b0c04c1 100644 --- a/src/python/espressomd/particle_data.pyx +++ b/src/python/espressomd/particle_data.pyx @@ -1067,27 +1067,26 @@ cdef class ParticleHandle: """ if _partner in self.exclusions: - raise Exception("Exclusion id {} already in exclusion list of particle {}".format( - _partner, self._id)) + raise Exception( + f"Exclusion id {_partner} already in exclusion list of particle {self._id}") check_type_or_throw_except( _partner, 1, int, "PID of partner has to be an int.") if self._id == _partner: raise Exception( - "Cannot exclude of a particle with itself!\n->particle id %i, partner %i." % (self._id, _partner)) + "Cannot exclude of a particle with itself!\n" + f"->particle id {self._id}, partner {_partner}.") if change_exclusion(self._id, _partner, 0) == 1: - raise Exception("Particle with id " + - str(_partner) + " does not exist.") + raise Exception(f"Particle with id {_partner} does not exist.") def delete_exclusion(self, _partner): check_type_or_throw_except( _partner, 1, int, "PID of partner has to be an int.") if _partner not in self.exclusions: - raise Exception("Particle with id " + - str(_partner) + " is not in exclusion list.") + raise Exception( + f"Particle with id {_partner} is not in exclusion list.") if change_exclusion(self._id, _partner, 1) == 1: - raise Exception("Particle with id " + - str(_partner) + " does not exist.") + raise Exception(f"Particle with id {_partner} does not exist.") IF ENGINE: property swimming: @@ -1379,8 +1378,8 @@ cdef class ParticleHandle: """ if tuple(_bond) in self.bonds: - raise Exception("Bond {} already exists on particle {}.".format( - tuple(_bond), self._id)) + raise Exception( + f"Bond {tuple(_bond)} already exists on particle {self._id}.") bond = list(_bond) # As we will modify it self.check_bond_or_throw_exception(bond) @@ -1523,14 +1522,16 @@ cdef class _ParticleSliceImpl: self.id_selection = np.array(slice_, dtype=int) else: raise TypeError( - "ParticleSlice must be initialized with an instance of slice or range, or with a list, tuple, or ndarray of ints, but got {} of type {}".format((str(slice_), str(type(slice_))))) + f"ParticleSlice must be initialized with an instance of " + f"slice or range, or with a list, tuple, or ndarray of ints, " + f"but got {slice_} of type {type(slice_)}") def _id_selection_from_slice(self, slice_): """Returns an ndarray of particle ids to be included in the ParticleSlice for a given range or slice object. """ # Prevent negative bounds - if (not slice_.start is None and slice_.start < 0) or\ + if (not slice_.start is None and slice_.start < 0) or \ (not slice_.stop is None and slice_.stop < 0): raise IndexError( "Negative start and end ids are not supported on ParticleSlice") @@ -1555,9 +1556,9 @@ cdef class _ParticleSliceImpl: for pid in pid_list: if not is_valid_type(pid, int): raise TypeError( - "Particle id must be an integer but got " + str(pid)) + f"Particle id must be an integer but got {pid}") if not particle_exists(pid): - raise IndexError("Particle does not exist " + str(pid)) + raise IndexError(f"Particle does not exist {pid}") def __iter__(self): return self._id_gen() @@ -1606,9 +1607,9 @@ cdef class _ParticleSliceImpl: pl = ParticleList() for i in self.id_selection: if pl.exists(i): - res += str(pl[i]) + ", " + res += f"{pl[i]}, " # Remove final comma - return "ParticleSlice([" + res[:-2] + "])" + return f"ParticleSlice([{res[:-2]}])" def update(self, P): if "id" in P: @@ -1662,7 +1663,7 @@ class ParticleSlice(_ParticleSliceImpl): def __setattr__(self, name, value): if name != "_chunk_size" and not hasattr(ParticleHandle, name): raise AttributeError( - "ParticleHandle does not have the attribute {}.".format(name)) + f"ParticleHandle does not have the attribute {name}.") super().__setattr__(name, value) @@ -1796,7 +1797,7 @@ cdef class ParticleList: P["id"] = get_maximal_particle_id() + 1 else: if particle_exists(P["id"]): - raise Exception("Particle %d already exists." % P["id"]) + raise Exception(f"Particle {P['id']} already exists.") # Prevent setting of contradicting attributes IF DIPOLES: @@ -2119,20 +2120,19 @@ def _add_particle_slice_properties(): elif np.shape(values)[0] == N: set_slice_one_for_each(particle_slice, attribute, values) else: - raise Exception("Shape of value (%s) does not broadcast to shape of attribute (%s)." % ( - np.shape(values), target_shape)) + raise Exception( + f"Value shape {np.shape(values)} does not broadcast to attribute shape {target_shape}.") return else: # fixed length vector quantity - if target_shape == np.shape(values): set_slice_one_for_all(particle_slice, attribute, values) elif target_shape == tuple(np.shape(values)[1:]) and np.shape(values)[0] == N: set_slice_one_for_each(particle_slice, attribute, values) else: - raise Exception("Shape of value (%s) does not broadcast to shape of attribute (%s)." % ( - np.shape(values), target_shape)) + raise Exception( + f"Value shape {np.shape(values)} does not broadcast to attribute shape {target_shape}.") return diff --git a/src/python/espressomd/polymer.pyx b/src/python/espressomd/polymer.pyx index e6884aedbca..8a312171bcc 100644 --- a/src/python/espressomd/polymer.pyx +++ b/src/python/espressomd/polymer.pyx @@ -131,7 +131,7 @@ def linear_polymer_positions(**kwargs): for k in kwargs: if k not in valid_keys: - raise ValueError("Unknown parameter '%s'" % k) + raise ValueError(f"Unknown parameter '{k}'") params[k] = kwargs[k] for k in required_keys: diff --git a/src/python/espressomd/reaction_ensemble.pyx b/src/python/espressomd/reaction_ensemble.pyx index 4f6f0698919..474a03b660a 100644 --- a/src/python/espressomd/reaction_ensemble.pyx +++ b/src/python/espressomd/reaction_ensemble.pyx @@ -426,7 +426,7 @@ cdef class ReactionEnsemble(ReactionAlgorithm): if k in self._valid_keys(): self._params[k] = kwargs[k] else: - raise KeyError("%s is not a valid key" % k) + raise KeyError(f"{k} is not a valid key") self._set_params_in_es_core() @@ -449,7 +449,7 @@ cdef class ConstantpHEnsemble(ReactionAlgorithm): if k in self._valid_keys(): self._params[k] = kwargs[k] else: - raise KeyError("%s is not a valid key" % k) + raise KeyError(f"{k} is not a valid key") self._set_params_in_es_core() @@ -495,7 +495,7 @@ cdef class WangLandauReactionEnsemble(ReactionAlgorithm): if k in self._valid_keys(): self._params[k] = kwargs[k] else: - raise KeyError("%s is not a valid key" % k) + raise KeyError(f"{k} is not a valid key") self.WLRptr.reset(new CWangLandauReactionEnsemble(int(self._params["seed"]))) self.RE = self.WLRptr.get() @@ -539,7 +539,7 @@ cdef class WangLandauReactionEnsemble(ReactionAlgorithm): if k in self._valid_keys_add_collective_variable_degree_of_association(): self._params[k] = kwargs[k] else: - raise KeyError("%s is not a valid key" % k) + raise KeyError(f"{k} is not a valid key") for k in self._required_keys_add_collective_variable_degree_of_association(): if k not in kwargs: @@ -591,7 +591,7 @@ cdef class WangLandauReactionEnsemble(ReactionAlgorithm): if k in self._valid_keys_add_collective_variable_potential_energy(): self._params[k] = kwargs[k] else: - raise KeyError("%s is not a valid key" % k) + raise KeyError(f"{k} is not a valid key") for k in self._required_keys_add_collective_variable_potential_energy(): if k not in kwargs: @@ -633,7 +633,7 @@ cdef class WangLandauReactionEnsemble(ReactionAlgorithm): if k in self._valid_keys_set_wang_landau_parameters(): self._params[k] = kwargs[k] else: - raise KeyError("%s is not a valid key" % k) + raise KeyError(f"{k} is not a valid key") deref(self.WLRptr).final_wang_landau_parameter = self._params[ "final_wang_landau_parameter"] @@ -760,7 +760,7 @@ cdef class WidomInsertion(ReactionAlgorithm): if k in self._valid_keys(): self._params[k] = kwargs[k] else: - raise KeyError("%s is not a valid key" % k) + raise KeyError(f"{k} is not a valid key") self._set_params_in_es_core() diff --git a/src/python/espressomd/system.pyx b/src/python/espressomd/system.pyx index 6de9d7bb60f..5ddc28cc110 100644 --- a/src/python/espressomd/system.pyx +++ b/src/python/espressomd/system.pyx @@ -112,7 +112,7 @@ cdef class System: def __init__(self, **kwargs): global _system_created - if (not _system_created): + if not _system_created: self.globals = Globals() if 'box_l' not in kwargs: raise ValueError("Required argument box_l not provided.") @@ -123,7 +123,7 @@ cdef class System: System.__setattr__(self, arg, kwargs.get(arg)) else: raise ValueError( - "Property {} can not be set via argument to System class.".format(arg)) + f"Property {arg} can not be set via argument to System class.") self.actors = Actors() self.analysis = Analysis(self) self.auto_update_accumulators = AutoUpdateAccumulators() diff --git a/src/python/espressomd/utils.pyx b/src/python/espressomd/utils.pyx index 492c158206d..2b2a18d9c92 100644 --- a/src/python/espressomd/utils.pyx +++ b/src/python/espressomd/utils.pyx @@ -39,17 +39,17 @@ cpdef check_type_or_throw_except(x, n, t, msg): or (t == float and issubclass(type(x[i]), np.integer))) \ and not (t == int and issubclass(type(x[i]), np.integer)): raise ValueError( - msg + " -- Item " + str(i) + " was of type " + type(x[i]).__name__) + msg + f" -- Item {i} was of type {type(x[i]).__name__}") else: # if n>1, but the user passed a single value, also throw exception raise ValueError( - msg + " -- A single value was given but " + str(n) + " were expected.") + msg + f" -- A single value was given but {n} were expected.") else: # N=1 and a single value if not isinstance(x, t): if not (t == float and is_valid_type(x, int)) and not ( t == int and issubclass(type(x), np.integer)): - raise ValueError(msg + " -- Got an " + type(x).__name__) + raise ValueError(msg + f" -- Got an {type(x).__name__}") cdef np.ndarray create_nparray_from_double_array(double * x, int len_x): @@ -93,14 +93,16 @@ cdef check_range_or_except(D, name, v_min, incl_min, v_max, incl_max): or (not incl_min and not all(v > v_min for v in x)))) or \ (v_max != "inf" and ((incl_max and not all(v <= v_max for v in x)) or (not incl_max and not all(v < v_max for v in x)))): - raise ValueError("In " + name + ": Some values in " + str(x) + "are out of range " + - ("[" if incl_min else "]") + str(v_min) + "," + str(v_max) + ("]" if incl_max else "[")) + raise ValueError(f"In {name}: Some values in {x} are out of" + f" range {'[' if incl_min else ']'}{v_min}," + f"{v_max}{']' if incl_max else '['}") # Single Value else: if (v_min != "inf" and ((incl_min and x < v_min) or (not incl_min and x <= v_min)) or v_max != "inf" and ((incl_max and x > v_max) or (not incl_max and x >= v_max))): - raise ValueError("In " + name + ": Value " + str(x) + " is out of range " + ("[" if incl_min else "]") + - str(v_min) + "," + str(v_max) + ("]" if incl_max else "[")) + raise ValueError(f"In {name}: Value {x} is out of" + f" range {'[' if incl_min else ']'}{v_min}," + f"{v_max}{']' if incl_max else '['}") def to_char_pointer(s): @@ -286,10 +288,9 @@ def requires_experimental_features(reason): def exception_raiser(self, *args, **kwargs): raise Exception( - "Class " + - self.__class__.__name__ + - " is experimental. Define EXPERIMENTAL_FEATURES in myconfig.hpp to use it.\nReason: " + - reason) + f"Class {self.__class__.__name__} is experimental. Define " + "EXPERIMENTAL_FEATURES in myconfig.hpp to use it.\n" + f"Reason: {reason}") def modifier(cls): cls.__init__ = exception_raiser diff --git a/src/python/espressomd/version.pyx b/src/python/espressomd/version.pyx index 63c440befa5..6b93e7a39fa 100644 --- a/src/python/espressomd/version.pyx +++ b/src/python/espressomd/version.pyx @@ -33,7 +33,7 @@ def minor(): def friendly(): """Dot version of the version. """ - return "{}.{}".format(major(), minor()) + return f"{major()}.{minor()}" def git_branch(): From 94ce6667a7d4de8571e0dd34f9fdd1a189538800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Thu, 27 Aug 2020 18:30:09 +0200 Subject: [PATCH 4/5] python: Check length in check_type_or_throw_except Without this check, we could assign a vector of length 4 to a variable holding a vector of length 3 (trailing values would be discarded). We could also assign a vector of a smaller size to trigger an IndexError. --- src/python/espressomd/utils.pyx | 3 +++ testsuite/python/interactions_bonded_interface.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/python/espressomd/utils.pyx b/src/python/espressomd/utils.pyx index 2b2a18d9c92..5246b29b1ee 100644 --- a/src/python/espressomd/utils.pyx +++ b/src/python/espressomd/utils.pyx @@ -33,6 +33,9 @@ cpdef check_type_or_throw_except(x, n, t, msg): # Check whether x is an array/list/tuple or a single value if n > 1: if hasattr(x, "__getitem__"): + if len(x) != n: + raise ValueError( + msg + f" -- {len(x)} values were given but {n} were expected.") for i in range(len(x)): if not isinstance(x[i], t): if not ((t == float and is_valid_type(x[i], int)) diff --git a/testsuite/python/interactions_bonded_interface.py b/testsuite/python/interactions_bonded_interface.py index 58ca1d814ae..0753317b344 100644 --- a/testsuite/python/interactions_bonded_interface.py +++ b/testsuite/python/interactions_bonded_interface.py @@ -75,7 +75,7 @@ def parameterKeys(self, bondObject): def setUp(self): if not self.system.part.exists(self.pid): - self.system.part.add(id=self.pid, pos=(0, 0, 0, 0)) + self.system.part.add(id=self.pid, pos=(0, 0, 0)) def generateTestForBondParams(_bondId, _bondClass, _params): """Generates test cases for checking bond parameters set and gotten From 0d258c49cb8f4ccf92d0accd35d4c3bae831689a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Thu, 27 Aug 2020 18:38:01 +0200 Subject: [PATCH 5/5] tests: Check espressomd.utils functions --- testsuite/python/CMakeLists.txt | 1 + testsuite/python/utils.py | 74 +++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 testsuite/python/utils.py diff --git a/testsuite/python/CMakeLists.txt b/testsuite/python/CMakeLists.txt index 514645e27fe..bee190337b7 100644 --- a/testsuite/python/CMakeLists.txt +++ b/testsuite/python/CMakeLists.txt @@ -217,6 +217,7 @@ python_test(FILE shapes.py MAX_NUM_PROC 1) python_test(FILE h5md.py MAX_NUM_PROC 2) python_test(FILE mdanalysis.py MAX_NUM_PROC 2) python_test(FILE p3m_tuning_exceptions.py MAX_NUM_PROC 1 LABELS gpu) +python_test(FILE utils.py MAX_NUM_PROC 1) add_custom_target( python_test_data diff --git a/testsuite/python/utils.py b/testsuite/python/utils.py new file mode 100644 index 00000000000..dbd6e88d75a --- /dev/null +++ b/testsuite/python/utils.py @@ -0,0 +1,74 @@ +# +# Copyright (C) 2020 The ESPResSo project +# +# This file is part of ESPResSo. +# +# ESPResSo 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. +# +# ESPResSo 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 unittest as ut +import numpy as np + +import espressomd.utils as utils + + +class espresso_utils(ut.TestCase): + + def test_check_type_or_throw_except(self): + with self.assertRaisesRegex( + ValueError, 'A -- 4 values were given but 3 were expected.'): + utils.check_type_or_throw_except([1, 2, 3, 4], 3, float, 'A') + with self.assertRaisesRegex( + ValueError, 'B -- 2 values were given but 3 were expected.'): + utils.check_type_or_throw_except([1, 2], 3, float, 'B') + with self.assertRaisesRegex( + ValueError, 'C -- A single value was given but 3 were expected.'): + utils.check_type_or_throw_except(1, 3, float, 'C') + with self.assertRaisesRegex( + ValueError, 'D -- Item 1 was of type str'): + utils.check_type_or_throw_except([1, '2', '3'], 3, float, 'D') + try: + utils.check_type_or_throw_except([1, 2, 3], 3, float, 'E') + except ValueError as err: + self.fail(f'check_type_or_throw_except raised ValueError("{err}")') + + def test_is_valid_type(self): + # basic types + self.assertFalse(utils.is_valid_type(None, int)) + self.assertFalse(utils.is_valid_type('12', int)) + self.assertFalse(utils.is_valid_type(0.99, int)) + self.assertFalse(utils.is_valid_type(12, float)) + self.assertFalse(utils.is_valid_type(1234, str)) + self.assertTrue(utils.is_valid_type(1.0, float)) + self.assertTrue(utils.is_valid_type(12345, int)) + self.assertTrue(utils.is_valid_type('123', str)) + # numpy types + self.assertTrue(utils.is_valid_type( + np.array([12], dtype=int)[0], int)) + self.assertTrue(utils.is_valid_type( + np.array([12], dtype=np.long)[0], int)) + self.assertTrue(utils.is_valid_type( + np.array([1.], dtype=float)[0], float)) + self.assertTrue(utils.is_valid_type( + np.array([1.], dtype=np.float64)[0], float)) + + def test_nesting_level(self): + self.assertEqual(utils.nesting_level(12345), 0) + self.assertEqual(utils.nesting_level('123'), 0) + self.assertEqual(utils.nesting_level((1, )), 1) + self.assertEqual(utils.nesting_level([1, ]), 1) + self.assertEqual(utils.nesting_level([[1]]), 2) + + +if __name__ == "__main__": + ut.main()