diff --git a/src/python/espressomd/electrokinetics.py b/src/python/espressomd/electrokinetics.py index 48532405f8f..f7585cd6cc7 100644 --- a/src/python/espressomd/electrokinetics.py +++ b/src/python/espressomd/electrokinetics.py @@ -25,7 +25,6 @@ from .script_interface import ScriptInterfaceHelper, script_interface_register, ScriptObjectList, array_variant import espressomd.detail.walberla import espressomd.shapes -import espressomd.code_features @script_interface_register @@ -34,16 +33,10 @@ class EKFFT(ScriptInterfaceHelper): A FFT-based Poisson solver. """ - _so_name = "walberla::EKFFT" + _so_features = ("WALBERLA_FFT",) _so_creation_policy = "GLOBAL" - def __init__(self, *args, **kwargs): - if not espressomd.code_features.has_features("WALBERLA_FFT"): - raise NotImplementedError("Feature WALBERLA not compiled in") - - super().__init__(*args, **kwargs) - @script_interface_register class EKNone(ScriptInterfaceHelper): @@ -53,24 +46,14 @@ class EKNone(ScriptInterfaceHelper): """ _so_name = "walberla::EKNone" + _so_features = ("WALBERLA",) _so_creation_policy = "GLOBAL" - def __init__(self, *args, **kwargs): - if not espressomd.code_features.has_features("WALBERLA"): - raise NotImplementedError("Feature WALBERLA not compiled in") - - super().__init__(*args, **kwargs) - @script_interface_register class EKContainer(ScriptObjectList): _so_name = "walberla::EKContainer" - - def __init__(self, *args, **kwargs): - if not espressomd.code_features.has_features("WALBERLA"): - raise NotImplementedError("Feature WALBERLA not compiled in") - - super().__init__(*args, **kwargs) + _so_features = ("WALBERLA",) def add(self, ekspecies): self.call_method("add", object=ekspecies) @@ -165,6 +148,7 @@ class EKSpecies(ScriptInterfaceHelper, """ _so_name = "walberla::EKSpecies" + _so_features = ("WALBERLA",) _so_creation_policy = "GLOBAL" _so_bind_methods = ( "clear_density_boundaries", @@ -176,9 +160,6 @@ class EKSpecies(ScriptInterfaceHelper, ) def __init__(self, *args, **kwargs): - if not espressomd.code_features.has_features("WALBERLA"): - raise NotImplementedError("Feature WALBERLA not compiled in") - if "sip" not in kwargs: params = self.default_params() params.update(kwargs) diff --git a/src/python/espressomd/electrostatics.py b/src/python/espressomd/electrostatics.py index 2ed531fc354..b24b86f2505 100644 --- a/src/python/espressomd/electrostatics.py +++ b/src/python/espressomd/electrostatics.py @@ -19,20 +19,14 @@ from . import utils from .script_interface import ScriptInterfaceHelper, script_interface_register -from .code_features import has_features @script_interface_register class Container(ScriptInterfaceHelper): _so_name = "Coulomb::Container" + _so_features = ("ELECTROSTATICS",) _so_bind_methods = ("clear",) - def __init__(self, *args, **kwargs): - if not has_features("ELECTROSTATICS"): - raise NotImplementedError("Feature ELECTROSTATICS not compiled in") - - super().__init__(*args, **kwargs) - class ElectrostaticInteraction(ScriptInterfaceHelper): """ @@ -45,10 +39,9 @@ class ElectrostaticInteraction(ScriptInterfaceHelper): """ _so_creation_policy = "GLOBAL" + _so_features = ("ELECTROSTATICS",) def __init__(self, **kwargs): - self._check_required_features() - if 'sip' not in kwargs: for key in self.required_keys(): if key not in kwargs: @@ -64,10 +57,6 @@ def __init__(self, **kwargs): else: super().__init__(**kwargs) - def _check_required_features(self): - if not has_features("ELECTROSTATICS"): - raise NotImplementedError("Feature ELECTROSTATICS not compiled in") - def validate_params(self, params): """Check validity of given parameters. """ @@ -243,10 +232,7 @@ class P3M(_P3MBase): """ _so_name = "Coulomb::CoulombP3M" _so_creation_policy = "GLOBAL" - - def _check_required_features(self): - if not has_features("P3M"): - raise NotImplementedError("Feature P3M not compiled in") + _so_features = ("P3M",) @script_interface_register @@ -296,12 +282,7 @@ class P3MGPU(_P3MBase): """ _so_name = "Coulomb::CoulombP3MGPU" _so_creation_policy = "GLOBAL" - - def _check_required_features(self): - if not has_features("P3M"): - raise NotImplementedError("Feature P3M not compiled in") - if not has_features("CUDA"): - raise NotImplementedError("Feature CUDA not compiled in") + _so_features = ("P3M", "CUDA") @script_interface_register @@ -360,10 +341,7 @@ class ELC(ElectrostaticInteraction): """ _so_name = "Coulomb::ElectrostaticLayerCorrection" _so_creation_policy = "GLOBAL" - - def _check_required_features(self): - if not has_features("P3M"): - raise NotImplementedError("Feature P3M not compiled in") + _so_features = ("P3M",) def validate_params(self, params): utils.check_type_or_throw_except( @@ -447,10 +425,7 @@ class MMM1DGPU(ElectrostaticInteraction): """ _so_name = "Coulomb::CoulombMMM1DGpu" _so_creation_policy = "GLOBAL" - - def _check_required_features(self): - if not has_features("MMM1D_GPU"): - raise NotImplementedError("Feature MMM1D_GPU not compiled in") + _so_features = ("MMM1D_GPU",) def default_params(self): return {"far_switch_radius": -1., @@ -505,17 +480,12 @@ class Scafacos(ElectrostaticInteraction): """ _so_name = "Coulomb::CoulombScafacos" _so_creation_policy = "GLOBAL" + _so_features = ("ELECTROSTATICS", "SCAFACOS") _so_bind_methods = ElectrostaticInteraction._so_bind_methods + \ ("get_available_methods", "get_near_field_delegation", "set_near_field_delegation") - def _check_required_features(self): - if not has_features("ELECTROSTATICS"): - raise NotImplementedError("Feature ELECTROSTATICS not compiled in") - if not has_features("SCAFACOS"): - raise NotImplementedError("Feature SCAFACOS not compiled in") - def default_params(self): return {"check_neutrality": True} diff --git a/src/python/espressomd/lb.py b/src/python/espressomd/lb.py index d2bc3f34c70..24afb8bb374 100644 --- a/src/python/espressomd/lb.py +++ b/src/python/espressomd/lb.py @@ -226,6 +226,7 @@ class LBFluidWalberla(HydrodynamicInteraction, """ _so_name = "walberla::LBFluid" + _so_features = ("WALBERLA",) _so_creation_policy = "GLOBAL" _so_bind_methods = ( "add_force_at_pos", @@ -237,9 +238,6 @@ class LBFluidWalberla(HydrodynamicInteraction, ) def __init__(self, *args, **kwargs): - if not espressomd.code_features.has_features("WALBERLA"): - raise NotImplementedError("Feature WALBERLA not compiled in") - if "sip" not in kwargs: params = self.default_params() params.update(kwargs) @@ -329,13 +327,10 @@ class LBFluidWalberlaGPU(HydrodynamicInteraction): list of parameters. """ + _so_features = ("WALBERLA", "CUDA") # pylint: disable=unused-argument def __init__(self, *args, **kwargs): - if not espressomd.code_features.has_features("CUDA"): - raise NotImplementedError("Feature CUDA not compiled in") - if not espressomd.code_features.has_features("WALBERLA"): - raise NotImplementedError("Feature WALBERLA not compiled in") raise NotImplementedError("Not implemented yet") diff --git a/src/python/espressomd/magnetostatics.py b/src/python/espressomd/magnetostatics.py index a3ddba7c829..79473672cdf 100644 --- a/src/python/espressomd/magnetostatics.py +++ b/src/python/espressomd/magnetostatics.py @@ -19,20 +19,14 @@ from . import utils from .script_interface import ScriptInterfaceHelper, script_interface_register -from .code_features import has_features @script_interface_register class Container(ScriptInterfaceHelper): _so_name = "Dipoles::Container" + _so_features = ("DIPOLES",) _so_bind_methods = ("clear",) - def __init__(self, *args, **kwargs): - if not has_features("DIPOLES"): - raise NotImplementedError("Feature DIPOLES not compiled in") - - super().__init__(*args, **kwargs) - class MagnetostaticInteraction(ScriptInterfaceHelper): """ @@ -45,10 +39,9 @@ class MagnetostaticInteraction(ScriptInterfaceHelper): """ _so_creation_policy = "GLOBAL" + _so_features = ("DIPOLES",) def __init__(self, **kwargs): - self._check_required_features() - if 'sip' not in kwargs: for key in self.required_keys(): if key not in kwargs: @@ -65,10 +58,6 @@ def __init__(self, **kwargs): else: super().__init__(**kwargs) - def _check_required_features(self): - if not has_features("DIPOLES"): - raise NotImplementedError("Feature DIPOLES not compiled in") - def validate_params(self, params): """Check validity of given parameters. """ @@ -128,10 +117,7 @@ class DipolarP3M(MagnetostaticInteraction): """ _so_name = "Dipoles::DipolarP3M" - - def _check_required_features(self): - if not has_features("DP3M"): - raise NotImplementedError("Feature DP3M not compiled in") + _so_features = ("DP3M",) def validate_params(self, params): """Check validity of parameters. @@ -234,16 +220,10 @@ class Scafacos(MagnetostaticInteraction): """ _so_name = "Dipoles::DipolarScafacos" _so_creation_policy = "GLOBAL" + _so_features = ("DIPOLES", "SCAFACOS_DIPOLES") _so_bind_methods = MagnetostaticInteraction._so_bind_methods + \ ("get_available_methods", ) - def _check_required_features(self): - if not has_features("DIPOLES"): - raise NotImplementedError("Feature DIPOLES not compiled in") - if not has_features("SCAFACOS_DIPOLES"): - raise NotImplementedError( - "Feature SCAFACOS_DIPOLES not compiled in") - def default_params(self): return {} @@ -274,11 +254,7 @@ class DipolarDirectSumGpu(MagnetostaticInteraction): """ _so_name = "Dipoles::DipolarDirectSumGpu" _so_creation_policy = "GLOBAL" - - def _check_required_features(self): - if not has_features("DIPOLAR_DIRECT_SUM"): - raise NotImplementedError( - "Features CUDA and DIPOLES not compiled in") + _so_features = ("DIPOLAR_DIRECT_SUM", "CUDA") def default_params(self): return {} @@ -310,11 +286,7 @@ class DipolarBarnesHutGpu(MagnetostaticInteraction): """ _so_name = "Dipoles::DipolarBarnesHutGpu" _so_creation_policy = "GLOBAL" - - def _check_required_features(self): - if not has_features("DIPOLAR_BARNES_HUT"): - raise NotImplementedError( - "Features CUDA and DIPOLES not compiled in") + _so_features = ("DIPOLAR_BARNES_HUT", "CUDA") def default_params(self): return {"epssq": 100.0, "itolsq": 4.0} diff --git a/src/python/espressomd/script_interface.pxd b/src/python/espressomd/script_interface.pxd index d485b3db740..bf074a0c542 100644 --- a/src/python/espressomd/script_interface.pxd +++ b/src/python/espressomd/script_interface.pxd @@ -73,4 +73,7 @@ cdef extern from "script_interface/initialize.hpp" namespace "ScriptInterface": cdef extern from "script_interface/get_value.hpp" namespace "ScriptInterface": T get_value[T](const Variant T) +cdef extern from "script_interface/code_info/CodeInfo.hpp" namespace "ScriptInterface::CodeInfo": + void check_features(const vector[string] & features) except + + cdef void init(MpiCallbacks &) diff --git a/src/python/espressomd/script_interface.pyx b/src/python/espressomd/script_interface.pyx index d341c829d3f..d663a96adb4 100644 --- a/src/python/espressomd/script_interface.pyx +++ b/src/python/espressomd/script_interface.pyx @@ -426,10 +426,16 @@ def _unpickle_so_class(so_name, state): class ScriptInterfaceHelper(PScriptInterface): _so_name = None + _so_features = () _so_bind_methods = () _so_creation_policy = "GLOBAL" def __init__(self, **kwargs): + cdef vector[string] features_vec + if self._so_features: + for feature in self._so_features: + features_vec.push_back(utils.to_char_pointer(feature)) + check_features(features_vec) super().__init__(self._so_name, policy=self._so_creation_policy, **kwargs) self.define_bound_methods() diff --git a/src/script_interface/code_info/CodeInfo.cpp b/src/script_interface/code_info/CodeInfo.cpp index 8cb57175d4b..eb2870a42d1 100644 --- a/src/script_interface/code_info/CodeInfo.cpp +++ b/src/script_interface/code_info/CodeInfo.cpp @@ -23,12 +23,19 @@ #include "config/version.hpp" #include "script_interface/scafacos/scafacos.hpp" +#include + +#include #include #include namespace ScriptInterface { namespace CodeInfo { +static auto get_feature_vector(char const *const ptr[], unsigned int len) { + return std::vector{ptr, ptr + len}; +} + static Variant get_feature_list(char const *const ptr[], unsigned int len) { return make_vector_of_variants(std::vector{ptr, ptr + len}); } @@ -54,5 +61,23 @@ Variant CodeInfo::do_call_method(std::string const &name, return {}; } +void check_features(std::vector const &features) { + auto const allowed = get_feature_vector(FEATURES_ALL, NUM_FEATURES_ALL); + auto const built = get_feature_vector(FEATURES, NUM_FEATURES); + std::vector missing_features{}; + for (auto const &feature : features) { + if (std::find(allowed.begin(), allowed.end(), feature) == allowed.end()) { + throw std::runtime_error("Unknown feature '" + feature + "'"); + } + if (std::find(built.begin(), built.end(), feature) == built.end()) { + missing_features.emplace_back(feature); + } + } + if (not missing_features.empty()) { + throw std::runtime_error("Missing features " + + boost::algorithm::join(missing_features, ", ")); + } +} + } // namespace CodeInfo } // namespace ScriptInterface diff --git a/src/script_interface/code_info/CodeInfo.hpp b/src/script_interface/code_info/CodeInfo.hpp index 70f80c2471e..4e2cbf72bb5 100644 --- a/src/script_interface/code_info/CodeInfo.hpp +++ b/src/script_interface/code_info/CodeInfo.hpp @@ -23,6 +23,7 @@ #include "script_interface/ScriptInterface.hpp" #include +#include namespace ScriptInterface { namespace CodeInfo { @@ -33,6 +34,8 @@ class CodeInfo : public ObjectHandle { VariantMap const ¶meters) override; }; +void check_features(std::vector const &features); + } // namespace CodeInfo } // namespace ScriptInterface diff --git a/testsuite/python/script_interface.py b/testsuite/python/script_interface.py index ad973686aa5..7665a682514 100644 --- a/testsuite/python/script_interface.py +++ b/testsuite/python/script_interface.py @@ -22,6 +22,8 @@ import espressomd.shapes import espressomd.constraints import espressomd.interactions +import espressomd.script_interface +import espressomd.code_info class SphereWithProperties(espressomd.shapes.Sphere): @@ -100,8 +102,27 @@ def test_autoparameter_exceptions(self): with self.assertRaisesRegex(AttributeError, "Object 'HarmonicBond' has no attribute 'unknown'"): bond.unknown + def test_feature_exceptions(self): + """Check feature verification""" + all_features = set(espressomd.code_info.all_features()) + active_features = set(espressomd.code_info.features()) + missing_features = sorted(list(all_features - active_features)) + + class Unknown(espressomd.script_interface.ScriptInterfaceHelper): + _so_features = ("UNKNOWN",) + + class Missing(espressomd.script_interface.ScriptInterfaceHelper): + _so_features = missing_features + + with self.assertRaisesRegex(RuntimeError, "Unknown feature 'UNKNOWN'"): + Unknown() + + if missing_features: + with self.assertRaisesRegex(RuntimeError, f"Missing features {', '.join(missing_features)}"): + Missing() + def test_variant_exceptions(self): - """Check AutoParameters framework""" + """Check variant conversion""" constraint = espressomd.constraints.ShapeBasedConstraint() # check conversion of unsupported types err_msg = "No conversion from type 'module' to 'Variant'"