From c16a9c2769a29bfc2284052f575c769bbbb6fff6 Mon Sep 17 00:00:00 2001 From: Kilian Weishaupt <kilian.weishaupt@iws.uni-stuttgart.de> Date: Wed, 9 Jun 2021 19:25:17 +0200 Subject: [PATCH] [python] Add python properties and TypeTag --- python/dumux/common/CMakeLists.txt | 1 + python/dumux/common/properties.py | 235 +++++++++++++++++++++++++++++ 2 files changed, 236 insertions(+) create mode 100644 python/dumux/common/properties.py diff --git a/python/dumux/common/CMakeLists.txt b/python/dumux/common/CMakeLists.txt index dc0edac4c6..ffc35a5823 100644 --- a/python/dumux/common/CMakeLists.txt +++ b/python/dumux/common/CMakeLists.txt @@ -1,5 +1,6 @@ add_python_targets(common __init__ + properties ) dune_add_pybind11_module(NAME _common) set_property(TARGET _common PROPERTY LINK_LIBRARIES dunecommon dunegrid APPEND) diff --git a/python/dumux/common/properties.py b/python/dumux/common/properties.py new file mode 100644 index 0000000000..9b512be8fe --- /dev/null +++ b/python/dumux/common/properties.py @@ -0,0 +1,235 @@ +import os + +""" +Properties +""" + +class Property: + """"Properties are used to construct a model""" + + def __init__(self, object=None, value=None, type=None, includes=[], requiredProperties=[]): + if object is not None: + assert(hasattr(object, '_typeName')) + if type is not None or value is not None: + raise ValueError("The Property constructor expects exactly one of the following arguments: object, type, or value.") + if includes or requiredProperties: + raise ValueError("The arguments includes and requiredProperties are ignored if the object argument is specified.") + self._typeName = object._typeName + self._includes = object._includes if hasattr(object, '_includes') else [] + self._requiredPropertyTypes = object._requiredPropertyTypes if hasattr(object, '_requiredPropertyTypes') else [] + elif value is not None: + if object is not None or type is not None: + raise ValueError("The Property constructor expects exactly one of the following arguments: object, type, or value.") + if includes or requiredProperties: + raise ValueError("The arguments includes and requiredProperties are ignored if the value argument is specified.") + self._value = value + elif type is not None: + if object is not None or value is not None: + raise ValueError("The Property constructor expects exactly one of the following arguments: object, type, or value.") + self._typeName = type + self._includes = includes + self._requiredPropertyTypes = requiredProperties + else: + raise ValueError("The Property constructor expects exactly one of the following arguments: object, type, or value.") + + +def typePropertyToString(propertyName, typeTagName, typeArg): + """Converts a Type Property to a string""" + + propertyString = 'template<class TypeTag>\n' + propertyString += 'struct {}<TypeTag, TTag::{}>\n{{\n'.format(propertyName, typeTagName) + + if isinstance(typeArg, (float)): + propertyString += ' using type = {};\n'.format('double') + elif isinstance(typeArg, (int)): + propertyString += ' using type = {};\n'.format('int') + elif isinstance(typeArg, Property) or not isinstance(typeArg, (str)): + if hasattr(typeArg, '_requiredPropertyTypes') or hasattr(typeArg, '_requiredPropertyValues'): + propertyString += 'private:\n' + if hasattr(typeArg, '_requiredPropertyTypes'): + for reqProp in typeArg._requiredPropertyTypes: + propertyString += ' using {} = {};\n'.format(reqProp, 'GetPropType<TypeTag, Properties::{}>'.format(reqProp)) + + if hasattr(typeArg, '_requiredPropertyValues'): + for reqProp in typeArg._requiredPropertyValues: + reqPropLowerCase = reqProp[0].lower() + reqProp[1:] + propertyString += ' static constexpr auto {} = {};\n'.format(reqPropLowerCase, 'getPropValue<TypeTag, Properties::{}>()'.format(reqProp)) + + propertyString += 'public:\n' + propertyString += ' using type = {};\n'.format(typeArg._typeName) + + propertyString += '};' + + return propertyString + + +def valuePropertyToString(propertyName, typeTagName, value): + """Converts a Value Property to a string""" + + propertyString = 'template<class TypeTag>\n' + propertyString += 'struct {}<TypeTag, TTag::{}>\n{{'.format(propertyName, typeTagName) + + # make sure to get the correct C++ types and values + if isinstance(value, bool): + value = str(value).lower() + type = 'bool' + elif isinstance(value, int): + type = 'int' + else: + type = 'Scalar' + propertyString += '\nprivate:\n' + propertyString += ' using Scalar = GetPropType<TypeTag, Properties::Scalar>;\n' + propertyString += 'public:' + + propertyString += '\n static constexpr {} value = {};\n'.format(type, value) + propertyString += '};' + + return propertyString + +existingTypeTags = {'CCTpfaModel':{'include':'dumux/discretization/cctpfa.hh', + 'description':'A cell-centered two-point flux finite volume discretization scheme.'}, + 'BoxModel':{'include':'dumux/discretization/box.hh', + 'description':'A node-centered two-point flux finite volume discretization scheme'}, + 'OneP':{'include':'dumux/porousmediumflow/1p/model.hh', + 'description':'A model for single-phase flow in porous media.'}} + + +def listTypeTags(): + """List all available TypeTags/Models that can be inherited from""" + + print("\n**********************************\n") + print("The following TypeTags are availabe:") + for key in existingTypeTags.keys(): + print(key,":", existingTypeTags[key]['description']) + print("\n**********************************") + + +def getKnownProperties(): + filepath = os.path.abspath(os.path.dirname(__file__) + '/../../../../dumux/common/properties.hh') + with open(filepath) as f: + result = [] + for line in f: + if line.startswith('struct'): + result.append(line.split(' ')[1]) + return result + + +class TypeTag: + knownProperties = getKnownProperties() + + def __init__(self, name, *, inheritsFrom=None, gridGeometry=None, scalar='double'): + self.name = name + self.inheritsFrom = inheritsFrom + self.includes = [] + self.properties = {} + self.newPropertyDefinitions = [] + self.gridGeometry = gridGeometry + + if name in existingTypeTags.keys(): + if inheritsFrom is not None: + raise ValueError("Existing TypeTag {} cannot inherit from other TypeTags. Use TypeTag({}) only.".format(name, name)) + self.isExistingTypeTag = True + self.includes = [existingTypeTags[name]['include']] + else: + self.isExistingTypeTag = False + + if self.inheritsFrom is not None: + # treat existing TypeTags by converting the given string to a real TypeTag object + for idx, parentTypeTag in enumerate(self.inheritsFrom): + if not isinstance(parentTypeTag, TypeTag): + if not isinstance(parentTypeTag, str): + raise ValueError("Unknown parent TypeTag {}. Use either argument of type TypeTag " + "or a string for an existing TypeTag. List of existing TypeTags: {}".format(parentTypeTag, existingTypeTags.keys())) + if parentTypeTag not in existingTypeTags.keys(): + raise ValueError("Unknown TypeTag {}. List of existing TypeTags: {}".format(parentTypeTag, existingTypeTags.keys())) + self.inheritsFrom[idx] = TypeTag(parentTypeTag) + + # pick up the properties and includes of the parent TypeTag + for parentTypeTag in reversed(self.inheritsFrom): + self.newPropertyDefinitions += parentTypeTag.newPropertyDefinitions + for key in parentTypeTag.properties: + self.properties[key] = parentTypeTag.properties[key] + if parentTypeTag.includes is not None: + for include in parentTypeTag.includes: + self.includes.append(include) + + self._typeName = 'Dumux::Properties::TTag::' + name + + # set the scalar type + self.__setitem__('Scalar', Property(type=scalar)) + + # the [] operator for setting values + def __setitem__(self, key, value): + if not isinstance(value, Property): + raise ValueError('Only values of type Property can be assigned to a model') + + if key not in self.knownProperties: + print(f'Adding {key} as new property') + self.newPropertyDefinitions += [key] + + self.properties[key] = value + if hasattr(value, '_includes'): + for include in value._includes: + self.includes.append(include) + + # the [] operator for getting values + def __getitem__(self, key): + return self.properties[key] + + # returns the TypeTag as a string + def getTypeTag(self): + return 'Dumux::Properties::TTag::' + self.name + + # creates a string resembling a properties.hh file + def getProperties(self): + file = '#ifndef DUMUX_{}_PROPERTIES_HH\n'.format(self.name.upper()) + file += '#define DUMUX_{}_PROPERTIES_HH\n\n'.format(self.name.upper()) + + file += '#include <dumux/common/properties.hh>\n' + + for include in self.includes: + assert('<' not in include) + file += '#include <{}>\n'.format(include) + + file += '\n' + + file += 'namespace Dumux::Properties {\n\n' + file += 'namespace TTag {\n' + + if self.inheritsFrom is not None: + for otherTypeTag in self.inheritsFrom: + if not otherTypeTag.isExistingTypeTag: + file += 'struct {}{{}}; \n'.format(otherTypeTag.name) + + args = ",".join(x.name for x in self.inheritsFrom) + else: + args = "" + + file += f'struct {self.name}\n{{\n' + file += f' using InheritsFrom = std::tuple<{args}>;\n' + if self.gridGeometry is not None: + file += f' using GridGeometry = {self.gridGeometry._typeName};\n' + file += '};\n} // end namespace TTag\n\n' + + for newDef in self.newPropertyDefinitions: + file += 'template<class TypeTag, class MyTypeTag>\n' + file += 'struct ' + newDef + ' { using type = UndefinedProperty; };\n\n' + + for prop in self.properties: + if hasattr(self[prop], '_value'): + file += valuePropertyToString(prop, self.name, self[prop]._value) +'\n\n' + else: + file += typePropertyToString(prop, self.name, self[prop]) +'\n\n' + + if self.gridGeometry is not None: + file += typePropertyToString('Grid', self.name, Property(type='typename TypeTag::GridGeometry::Grid')) +'\n\n' + + file += '} // end namespace Dumux::Properties \n\n' + file += '#endif' + + return file + + +class Model(TypeTag): + # TODO maybe rename TypeTag to Model and remove this class here + pass -- GitLab