diff --git a/src/pystencils_autodiff/CMakeLists.tmpl.txt b/src/pystencils_autodiff/CMakeLists.tmpl.txt index 6f9bcf59ed79ecf85e5cd6ca5073ff68f0f82a94..3765fab75bc575e1789d564d0ba06a5bb3ac97ae 100644 --- a/src/pystencils_autodiff/CMakeLists.tmpl.txt +++ b/src/pystencils_autodiff/CMakeLists.tmpl.txt @@ -1,6 +1,6 @@ waLBerla_link_files_to_builddir( *.prm ) -waLBerla_add_executable ( NAME autogen +waLBerla_add_executable ( NAME {{ cmake_target_name }} FILES {%- for f in files_to_compile %} {{ f }} {%- endfor %} - DEPENDS blockforest core cuda field lbm geometry timeloop gui ) + DEPENDS {{depends | join(' ')}}) diff --git a/src/pystencils_autodiff/_backport.py b/src/pystencils_autodiff/_backport.py index c49103ab7c0f21ca5b08cf150671dc4a9ebe52ef..e9ba2790bac9a743e3175a464526a579eb878b00 100644 --- a/src/pystencils_autodiff/_backport.py +++ b/src/pystencils_autodiff/_backport.py @@ -10,6 +10,8 @@ import itertools +import sympy as sp + import pystencils from pystencils.astnodes import KernelFunction, ResolvedFieldAccess, SympyAssignment from pystencils.interpolation_astnodes import InterpolatorAccess @@ -42,6 +44,7 @@ def compatibility_hacks(): pystencils.fields = fields KernelFunction.fields_read = property(fields_read) KernelFunction.fields_written = property(fields_written) + sp.Expr.undefined_symbols = sp.Expr.free_symbols compatibility_hacks() diff --git a/src/pystencils_autodiff/framework_integration/astnodes.py b/src/pystencils_autodiff/framework_integration/astnodes.py index 3f60bf879f4c38a8eac9123a425c5246bc7f8a92..d83b6bd2984d141536e696d04267e948da8d8be4 100644 --- a/src/pystencils_autodiff/framework_integration/astnodes.py +++ b/src/pystencils_autodiff/framework_integration/astnodes.py @@ -164,6 +164,7 @@ def generate_kernel_call(kernel_function): class JinjaCppFile(Node): TEMPLATE: jinja2.Template = None + NOT_PRINT_TYPES = (pystencils.Field, pystencils.TypedSymbol, bool) def __init__(self, ast_dict): self.ast_dict = pystencils.utils.DotDict(ast_dict) @@ -174,18 +175,23 @@ class JinjaCppFile(Node): def args(self): """Returns all arguments/children of this node.""" ast_nodes = [a for a in self.ast_dict.values() if isinstance(a, (Node, str))] - iterables_of_ast_nodes = [a for a in self.ast_dict.values() if isinstance(a, Iterable)] + iterables_of_ast_nodes = [a for a in self.ast_dict.values() if isinstance(a, Iterable) + and not isinstance(a, str)] return ast_nodes + list(itertools.chain.from_iterable(iterables_of_ast_nodes)) @property def symbols_defined(self): """Set of symbols which are defined by this node.""" - return set() + return set(itertools.chain.from_iterable(a.symbols_defined + for a in self.args + if hasattr(a, 'symbols_defined'))) @property def undefined_symbols(self): """Symbols which are used but are not defined inside this node.""" - return set() + return set(itertools.chain.from_iterable(a.undefined_symbols + for a in self.args + if hasattr(a, 'undefined_symbols'))) - self.symbols_defined def _print(self, node): if isinstance(node, Node): @@ -210,11 +216,11 @@ class JinjaCppFile(Node): def __str__(self): assert self.TEMPLATE, f"Template of {self.__class__} must be set" render_dict = {k: (self._print(v) - if not isinstance(v, (pystencils.Field, pystencils.TypedSymbol, bool)) and v is not None + if not isinstance(v, self.NOT_PRINT_TYPES) and v is not None else v) if not isinstance(v, Iterable) or isinstance(v, str) else [(self._print(a) - if not isinstance(a, (pystencils.Field, pystencils.TypedSymbol, bool)) and a is not None + if not isinstance(a, self.NOT_PRINT_TYPES) and a is not None else a) for a in v] for k, v in self.ast_dict.items()} diff --git a/src/pystencils_autodiff/simulation.py b/src/pystencils_autodiff/simulation.py index 9cc7c682eb8ff00208bc171e3a2a4f34c07b2dfd..8b89e334835758cd15106bcf6ddb41324c3b837d 100644 --- a/src/pystencils_autodiff/simulation.py +++ b/src/pystencils_autodiff/simulation.py @@ -16,15 +16,42 @@ import lbmpy_walberla import pystencils import pystencils_walberla.codegen from pystencils.astnodes import Block, EmptyLine +from pystencils.cpu.cpujit import get_headers from pystencils_autodiff.walberla import ( AllocateAllFields, CMakeLists, Communication, DefineKernelObjects, DefinitionsHeader, FieldCopy, ForLoop, InitBoundaryHandling, LbCommunicationSetup, ResolveUndefinedSymbols, SwapFields, SweepCreation, SweepOverAllBlocks, UniformBlockforestFromConfig, WalberlaMain, WalberlaModule) +WALBERLA_MODULES = ["blockforest", + "boundary", + "communication", + "core", + "cuda", + "domain_decomposition", + "executiontree", + "fft", + "field", + "gather", + "geometry", + "gui", + "lbm", + "mesa_pd", + "mesh", + "pde", + "pe", + "pe_coupling", + "postprocessing", + "python_coupling", + "simd", + "sqlite", + "stencil", + "timeloop", + "vtk", + "walberla_openvdb"] + class Simulation(): def _get_sweep_class_name(prefix='Kernel'): - ctr = 0 while True: yield f'{prefix}{ctr}' @@ -36,7 +63,8 @@ class Simulation(): boundary_handling: pystencils.boundaries.BoundaryHandling = None, lb_rule=None, refinement_scaling=None, - boundary_handling_target='gpu'): + boundary_handling_target='gpu', + cmake_target_name='autogen_app'): self._data_handling = graph_data_handling self._lb_rule = lb_rule self._refinement_scaling = refinement_scaling @@ -53,6 +81,7 @@ class Simulation(): self._boundary_handling_target = boundary_handling_target self._data_handling.merge_swaps_with_kernel_calls() self._packinfo_class = 'PackInfo' + self.cmake_target_name = cmake_target_name def _create_helper_files(self) -> Dict[str, str]: @@ -109,11 +138,11 @@ class Simulation(): Block([ field_allocations, InitBoundaryHandling(self._block_forest.blocks, - flag_field_id, - pdf_field_id, - self.boundary_conditions, - self._boundary_kernels, - self._field_allocations) + flag_field_id, + pdf_field_id, + self.boundary_conditions, + self._boundary_kernels, + self._field_allocations) if self._boundary_handling else EmptyLine(), LbCommunicationSetup(self._lb_model_name, pdf_field_id, @@ -126,27 +155,47 @@ class Simulation(): ]), self.parameter_config_block ) ]))) + from pystencils_autodiff.framework_integration.printer import DebugFrameworkPrinter + + module.printer = DebugFrameworkPrinter() self._codegen_context.write_file("main.cpp", str(module)) return module def _create_defintions_header(self): self._codegen_context.write_file("UserDefinitions.h", - str(DefinitionsHeader(self._lb_model_name, self._flag_field_dtype))) - - def _create_cmake_file(self): + str(DefinitionsHeader(self._lb_model_name if self._lb_rule else None, + self._flag_field_dtype))) + + def _create_cmake_file(self, extra_dependencis=[]): + walberla_dependencies = [] + import re + regex = re.compile(r'^["<](\w*)/.*[>$"]') + headers = get_headers(self._module) + for h in headers: + match = regex.match(h) + if match: + module = match[1] + if module in WALBERLA_MODULES: + walberla_dependencies.append(module) + + dependencies = walberla_dependencies + extra_dependencis try: self._codegen_context.write_file("CMakeLists.txt", - str(CMakeLists([f for f in self._codegen_context.files_written() - if f.endswith('.cpp') or f.endswith('.cu')]))) + str(CMakeLists(self.cmake_target_name, + [f for f in self._codegen_context.files_written() + if f.endswith('.cpp') or f.endswith('.cu')], + depends=dependencies))) except AttributeError: self._codegen_context.write_file("CMakeLists.txt", - str(CMakeLists([f for f in self._codegen_context.files.keys() - if f.endswith('.cpp') or f.endswith('.cu')]))) + str(CMakeLists(self.cmake_target_name, + [f for f in self._codegen_context.files.keys() + if f.endswith('.cpp') or f.endswith('.cu')], + depends=dependencies))) def write_files(self): self._create_helper_files() - self._create_module() + self._module = self._create_module() self._create_defintions_header() self._create_cmake_file() # has to be called last diff --git a/src/pystencils_autodiff/walberla.py b/src/pystencils_autodiff/walberla.py index 005e4e334f6dfa9a21717ff82e056e0c8f702a60..e882ce8b1dc8fdb9ee31e9ba85e3d78dc05a152d 100644 --- a/src/pystencils_autodiff/walberla.py +++ b/src/pystencils_autodiff/walberla.py @@ -62,8 +62,10 @@ class WalberlaModule(JinjaCppFile): class CMakeLists(JinjaCppFile): TEMPLATE = read_template_from_file(join(dirname(__file__), 'CMakeLists.tmpl.txt')) - def __init__(self, files_to_compile): - ast_dict = {'files_to_compile': files_to_compile} # we try to avoid evil globbing + def __init__(self, cmake_target_name, files_to_compile, depends): + ast_dict = {'files_to_compile': files_to_compile, # we try to avoid evil globbing + 'cmake_target_name': cmake_target_name, + 'depends': depends} JinjaCppFile.__init__(self, ast_dict) @@ -138,7 +140,9 @@ class DefinitionsHeader(JinjaCppFile): TEMPLATE = read_template_from_file(join(dirname(__file__), 'walberla_user_defintions.tmpl.hpp')) def __init__(self, lb_model_name, flag_field_type): - self.headers = ['<cstdint>', '"lbm/field/PdfField.h"', f'"{lb_model_name}.h"', '"field/FlagField.h"'] + self.headers = ['<cstdint>', '"field/FlagField.h"'] + if lb_model_name: + self.headers.append(f'"{lb_model_name}.h"') super().__init__({'lb_model_name': lb_model_name, 'flag_field_type': flag_field_type}) @@ -256,12 +260,11 @@ class FlagFieldAllocation(FieldAllocation): class WalberlaVector(JinjaCppFile): - TEMPLATE = jinja2.Template("""math::Vector{{ndim}}<{{dtype}}>({{offsets}})""") + TEMPLATE = jinja2.Template("""math::Vector{{ndim}}<{{dtype}}>({{offset | join(', ')}})""") def __init__(self, offset, dtype='real_t'): ast_dict = { 'offset': offset, - 'offsets': ', '.join(str(o) for o in offset), 'dtype': dtype, 'ndim': len(offset), } @@ -553,17 +556,20 @@ timeloop.run(); class CppObjectConstruction(JinjaCppFile, pystencils.astnodes.SympyAssignment): - TEMPLATE = jinja2.Template("""{{ symbol.dtype }} {{ symbol }}({{ arg_list }});""") # noqa + TEMPLATE = jinja2.Template("""{{ symbol.dtype }} {{ symbol }}({{ args | join(', ') }});""") # noqa def __init__(self, symbol, args): JinjaCppFile.__init__(self, {}) self.ast_dict.update({ 'symbol': symbol, 'args': args, - 'arg_list': ', '.join(self.printer(a) for a in args) }) pystencils.astnodes.SympyAssignment.__init__(self, symbol, 1) + @property + def symbol(self): + return self.lhs + class ResolveUndefinedSymbols(JinjaCppFile): @@ -658,10 +664,12 @@ class AllocateAllFields(JinjaCppFile): {% for c in cpu_arrays %} {{- c }} {% endfor %} +{%- if gpu_arrays -%} // GPU Arrays {% for g in gpu_arrays %} {{- g }} {% endfor %} +{%- endif %} {{ block }} """) # noqa @@ -747,7 +755,7 @@ class InitBoundaryHandling(JinjaCppFile): class GeneratedBoundaryInitialization(JinjaCppFile): - TEMPLATE = jinja2.Template("""lbm::{{ boundary_condition }} {{ identifier }}( {{ block_forest }}, {{ pdf_field_id }}{{ parameter_str }} ); + TEMPLATE = jinja2.Template("""lbm::{{ boundary_condition }} {{ identifier }}( {{ block_forest }}, {{ parameter_ids | join(', ') }} ); {{ identifier }}.fillFromFlagField<FlagField_T>( {{ block_forest }}, {{ flag_field_id }}, FlagUID("{{ boundary_condition }}"), {{ fluid_uid }} ); """) # noqa @@ -771,9 +779,6 @@ class GeneratedBoundaryInitialization(JinjaCppFile): for p in parameters if (p.is_field_pointer or not p.is_field_parameter) and p.symbol.name not in ('_data_indexVector', '_data_pdfs', 'indexVectorSize',)] - parameter_str = ', '.join(p.name for p in parameter_ids) - if parameter_str: - parameter_str = ', ' + parameter_str self.fluid = FlagUidDefinition("fluid") ast_dict = {'block_forest': block_forest, @@ -782,8 +787,7 @@ class GeneratedBoundaryInitialization(JinjaCppFile): 'pdf_field_id': pdf_field_id, 'fluid_uid': fluid_uid, 'flag_field_id': flag_field_id, - 'parameter_ids': parameter_ids, - 'parameter_str': parameter_str + 'parameter_ids': [pdf_field_id] + parameter_ids, } super().__init__(ast_dict) @@ -803,13 +807,17 @@ class GeneratedBoundaryInitialization(JinjaCppFile): class SweepCreation(JinjaCppFile): - TEMPLATE = jinja2.Template("""{{ sweep_class_name }}( {{ parameter_str }} )""") # noqa + TEMPLATE = jinja2.Template("""{{ sweep_class_name }}( {{ parameter_ids | join(', ') }} )""") # noqa def __init__(self, sweep_class_name: str, field_allocation: AllocateAllFields, ast, parameters_to_ignore=[]): + + import pystencils_walberla.jinja_filters + parameters_to_ignore += pystencils_walberla.jinja_filters.SPECIAL_SYMBOL_NAMES + def resolve_parameter(p): if ast.target == 'cpu': dict = field_allocation._cpu_allocations @@ -822,11 +830,11 @@ class SweepCreation(JinjaCppFile): parameter_ids = [resolve_parameter(p) for p in parameters if (p.is_field_pointer and p.symbol.field_name not in parameters_to_ignore) - or not p.is_field_parameter] + or not p.is_field_parameter and p.symbol.name not in parameters_to_ignore] ast_dict = {'sweep_class_name': sweep_class_name, 'parameter_ids': parameter_ids, - 'parameter_str': ', '.join(p.name for p in parameter_ids)} + } super().__init__(ast_dict) self.symbol = TypedSymbol(self.ast_dict.sweep_class_name.lower(), self.ast_dict.sweep_class_name) @@ -838,6 +846,8 @@ class SweepCreation(JinjaCppFile): def undefined_symbols(self): return set(self.ast_dict.parameter_ids) + free_symbols = undefined_symbols + @property def symbols_defined(self): return {self.symbol} @@ -918,3 +928,27 @@ class Communication(JinjaCppFile): super().__init__(ast_dict) headers = ["<algorithm>"] + + +class VdbWriter(CppObjectConstruction): + def __init__(self, block_forest): + symbol = TypedSymbol('vdb', 'walberla_openvdb::OpenVdbFieldWriter') + super().__init__(symbol, block_forest) + + headers = ['"walberla_openvdb/OpenVdbFieldWriter.h"'] + + +class WriteVdb(SweepOverAllBlocks): + TEMPLATE = jinja2.Template( + """{{vdb_writer}}.beginFile({{file_prefix}}{% if time %}, optional<float>({{time}}){% endif %}); +{% for f in fields %} +{{ vdb_writer }}.addField<{{ f.dtype }}, {{ f.vdb_type }}>( {{f.id}}, {{f.name}} ); +{% endfor %} +{{vdb_writer}}.writeFile();""") + + def __init__(self, fields_to_write, block_forest): + functor = VdbWriter(block_forest) + ast_dict = {'functor': functor, + 'vdb_writer': functor.symbol, + 'block_forest': block_forest} + super().__init__(ast_dict) diff --git a/src/pystencils_autodiff/walberla_user_defintions.tmpl.hpp b/src/pystencils_autodiff/walberla_user_defintions.tmpl.hpp index 0df3276c1d924ade412a667fe6a39bb18e3d53d8..278e8fef9f1d5ce2fdc0361dafe15737c8cd1361 100644 --- a/src/pystencils_autodiff/walberla_user_defintions.tmpl.hpp +++ b/src/pystencils_autodiff/walberla_user_defintions.tmpl.hpp @@ -14,14 +14,12 @@ namespace walberla_user { using namespace walberla; +{% if with_lbm %} using LatticeModel_T = lbm::{{ lb_model_name }}; using Stencil_T = LatticeModel_T::Stencil; using CommunicationStencil_T = LatticeModel_T::CommunicationStencil; -using PdfField_T = lbm::PdfField<LatticeModel_T>; +{% endif %} using flag_t = {{ flag_field_type }}; using FlagField_T = FlagField<flag_t>; -using PdfField_T = lbm::PdfField<LatticeModel_T>; -using VectorField_T = GhostLayerField<real_t, LatticeModel_T::Stencil::D>; -using ScalarField_T = GhostLayerField<real_t, 1>; } // namespace walberla_user diff --git a/tests/test_walberla.py b/tests/test_walberla.py index 6dcbabeb4db9157461aa7721a85b51dbaa72e68b..dc8ad0ca93c864db3eebc3bcce18fcb7e5c94dda 100644 --- a/tests/test_walberla.py +++ b/tests/test_walberla.py @@ -83,7 +83,8 @@ def test_wald_wiesen_lbm(): sim = Simulation(lbm_step.data_handling, ctx, lbm_step.boundary_handling, - create_lb_collision_rule(lbm_step.method, optimization=opt_params)) + create_lb_collision_rule(lbm_step.method, optimization=opt_params), + cmake_target_name='autogen') sim.write_files() dir = '/localhome/seitz_local/projects/walberla/apps/autogen/' @@ -98,15 +99,15 @@ def test_global_idx(): with ManualCodeGenerationContext() as ctx: from pystencils_walberla.special_symbols import current_global_idx, aabb_min_x - dh = GraphDataHandling((20, 30)) + dh = GraphDataHandling((20, 30, 40)) my_array = dh.add_array('my_array') - ast = pystencils.create_kernel([pystencils.Assignment(my_array.center, sum(current_global_idx))]).compile() - dh.run_kernel(ast, simulate_only=True) ast = pystencils.create_kernel([pystencils.Assignment(my_array.center, aabb_min_x)]).compile() dh.run_kernel(ast, simulate_only=True) + # ast = pystencils.create_kernel([pystencils.Assignment(my_array.center, sum(current_global_idx))]).compile() + # dh.run_kernel(ast, simulate_only=True) - sim = Simulation(dh, ctx) + sim = Simulation(dh, ctx, cmake_target_name='foo') sim.write_files() dir = '/localhome/seitz_local/projects/walberla/apps/foo/'