13 ReconstructPatchAndApplyFunctor,
23 Update cell in primary sweep
25 This action set is used in the primary sweeps only. In the secondary sweep,
26 its counterpart, the action set MergeEnclaveTaskOutcome, is active and works in all data
27 computed by tasks which have been spawned here.
29 We extend the superclass ReconstructPatchAndApplyFunctor and hence have
30 access to the reconstructed data including a halo layer of one. Furthermore,
31 the superclass provides us with a guard which we should use, as this guard
32 ensures that we reconstruct the patch plus halo if and only if certain
33 conditions are met. By default, we compute only on unrefined octants.
35 Our condition whether to spawn a task or to compute the new time step
36 data immediately depends on peano4::datamanagement::CellMarker::willBeSkeletonCell().
37 If this predicate holds, we compute stuff straightaway. Otherwise, we
38 span a new enclave task. While this check does the job in almost all
39 cases, there are special situations where you might want to label more
40 cells as skeleton cells.
45 You can alter the template. Typical codes augment _Template_TouchCellFirstTime_Preamble
46 for example. However, there are two things to consider:
48 - _Template_TouchCellFirstTime_Preamble is a member of the class and
49 initialised in the constructor.
50 - The constructor is used to create an object in the create_action_sets()
53 If you want to alter the preamble, you thus should specialise
54 create_action_sets() and invoke the supertype's create_action_sets(). After
55 that, alter self._action_set_update_cell._Template_TouchCellFirstTime_Preamble.
56 We recommend to add an entry and not to replace the preamble, as the
57 preamble already consists meaningful code.
60 TemplateUpdateCell = jinja2.Template(
62 double timeStamp = fineGridCell{{SOLVER_NAME}}CellLabel.getTimeStamp();
64 // Set the following two parameters
65 // double timeStepSize
66 {{COMPUTE_TIME_STEP_SIZE}}
68 {{PREPROCESS_RECONSTRUCTED_PATCH}}
70 assertion2(tarch::la::greaterEquals( timeStepSize, 0.0 ), timeStepSize, timeStamp);
71 assertion2(tarch::la::greaterEquals( timeStamp, 0.0 ), timeStepSize, timeStamp);
73 ::exahype2::fv::validatePatch(
75 {{NUMBER_OF_UNKNOWNS}},
76 {{NUMBER_OF_AUXILIARY_VARIABLES}},
77 {{NUMBER_OF_VOLUMES_PER_AXIS}},
79 std::string(__FILE__) + "(" + std::to_string(__LINE__) + "): " + marker.toString()
80 ); // Previous time step has to be valid
82 if (marker.willBeSkeletonCell()) {
83 const double maxEigenvalue = tasks::{{SOLVER_NAME}}EnclaveTask::applyKernelToCell(
91 {{COMPUTE_NEW_TIME_STEP_SIZE}}
93 fineGridCell{{SEMAPHORE_LABEL}}.setSemaphoreNumber(::exahype2::EnclaveBookkeeping::SkeletonTask);
94 fineGridCell{{SOLVER_NAME}}CellLabel.setHasUpdated(true);
95 fineGridCell{{SOLVER_NAME}}CellLabel.setTimeStamp(timeStamp + timeStepSize);
96 fineGridCell{{SOLVER_NAME}}CellLabel.setTimeStepSize(newTimeStepSize);
97 } else { // is an enclave cell
98 assertion(marker.willBeEnclaveCell());
99 assertion(not marker.willBeRefined());
100 auto newEnclaveTask = new tasks::{{SOLVER_NAME}}EnclaveTask(
105 {% if MAKE_COPY_OF_ENCLAVE_TASK_DATA %}
112 int predecessorEnclaveTaskNumber = fineGridCell{{SEMAPHORE_LABEL}}.getSemaphoreNumber();
114 fineGridCell{{SEMAPHORE_LABEL}}.setSemaphoreNumber( newEnclaveTask->getTaskId() );
116 tarch::multicore::spawnTask(
118 predecessorEnclaveTaskNumber>=0 ? std::set<int>{predecessorEnclaveTaskNumber} : tarch::multicore::NoInDependencies,
119 newEnclaveTask->getTaskId()
122 if (predecessorEnclaveTaskNumber>=0) {
123 ::exahype2::EnclaveTask::releaseTaskNumber(predecessorEnclaveTaskNumber);
126 fineGridCell{{SOLVER_NAME}}CellLabel.setTimeStamp(timeStamp + timeStepSize);
133 ReconstructPatchAndApplyFunctor.__init__(
137 patch_overlap=solver._patch_overlap_new,
138 functor_implementation=
"<not yet set - will do this later>",
139 reconstructed_array_memory_location=peano4.toolbox.blockstructured.ReconstructedArrayMemoryLocation.ManagedSharedAcceleratorDeviceMemoryThroughTarchWithoutDelete,
141 guard=
"not marker.hasBeenRefined() and ("
143 + solver.get_name_of_global_instance()
144 +
".getSolverState()=="
146 +
"::SolverState::Primary or "
148 + solver.get_name_of_global_instance()
149 +
".getSolverState()=="
151 +
"::SolverState::PrimaryAfterGridInitialisation"
153 add_assertions_to_halo_exchange=
True,
160 + solver.get_name_of_global_instance()
161 +
""".getSolverState()=="""
163 +
"""::SolverState::Primary or
165 + solver.get_name_of_global_instance()
166 +
""".getSolverState()=="""
168 +
"""::SolverState::PrimaryAfterGridInitialisation
172 +
"""CellLabel.setHasUpdated(false);
183 ReconstructPatchAndApplyFunctor.get_includes(self)
185#include "tarch/multicore/Task.h"
186#include "repositories/SolverRepository.h"
191 + self.
_solver._get_default_includes()
192 + self.
_solver.user_action_set_includes
197 return __name__.replace(
".py",
"").replace(
".",
"_") +
"_ReconstructPatchAndApplyFunctor"
202 First ask the solver to add its symbols, and then re-construct the
203 functor which should not contain any symbols after that anymore.
204 Next, we call the superclass routine which supplements all those
205 instructions that any reconstruction wants to have.
208 self.
_solver._init_dictionary_with_default_parameters(d)
209 self.
_solver.add_entries_to_text_replacement_dictionary(d)
219 not marker.hasBeenRefined()
223 repositories::{{SOLVER_INSTANCE}}.getSolverState() == {{SOLVER_NAME}}::SolverState::Secondary
225 const int taskNumber = fineGridCell{{LABEL_NAME}}.getSemaphoreNumber();
226 if (marker.hasBeenEnclaveCell() and taskNumber >= 0) {
227 double maxEigenvalue;
228 ::exahype2::EnclaveBookkeeping::getInstance().waitForTaskToTerminateAndCopyResultOver(
230 fineGridCell{{UNKNOWN_IDENTIFIER}}.value,
236 ::exahype2::fv::validatePatch(
237 fineGridCell{{UNKNOWN_IDENTIFIER}}.value,
238 {{NUMBER_OF_UNKNOWNS}},
239 {{NUMBER_OF_AUXILIARY_VARIABLES}},
240 {{NUMBER_OF_VOLUMES_PER_AXIS}},
242 std::string(__FILE__) + ": " + std::to_string(__LINE__) + "; marker=" + marker.toString()
245 {{COMPUTE_NEW_TIME_STEP_SIZE}}
247 fineGridCell{{LABEL_NAME}}.setSemaphoreNumber( ::exahype2::EnclaveBookkeeping::NoEnclaveTaskNumber );
248 fineGridCell{{SOLVER_NAME}}CellLabel.setHasUpdated(true);
249 fineGridCell{{SOLVER_NAME}}CellLabel.setTimeStepSize(newTimeStepSize);
252 if (fineGridCell{{SOLVER_NAME}}CellLabel.getHasUpdated()) {
253 double* newQ = fineGridCell{{UNKNOWN_IDENTIFIER}}.value;
255 {{POSTPROCESS_UPDATED_PATCH}}
257 repositories::{{SOLVER_INSTANCE}}.update(
258 fineGridCell{{SOLVER_NAME}}CellLabel.getTimeStepSize(),
259 fineGridCell{{SOLVER_NAME}}CellLabel.getTimeStamp(),
263 repositories::{{SOLVER_INSTANCE}}.update(
265 fineGridCell{{SOLVER_NAME}}CellLabel.getTimeStamp(),
274 super(MergeEnclaveTaskOutcome, self).
__init__(solver)
275 self.
label_name = exahype2.grid.UpdateCellLabel.get_attribute_name(solver._name)
284 == peano4.solversteps.ActionSet.OPERATION_TOUCH_CELL_FIRST_TIME
287 self.
_solver._init_dictionary_with_default_parameters(d)
288 self.
_solver.add_entries_to_text_replacement_dictionary(d)
290 d[
"GUARD"] = self.
guard
291 result = jinja2.Template(self.
Template).render(**d)
297 return __name__.replace(
".py",
"").replace(
".",
"_") +
"_MergeEnclaveTaskOutcome"
302 Enclave tasking variant of the Finite Volume scheme
304 The concept of (enclave) tasking within ExaHyPE solvers is described in
305 detail in the @ref page_exahype_solvers_enclave_solvers "generic enclave discussion of ExaHyPE".
306 This class is a prototype realisation of this concept which other solvers
307 then specialise for particular numerical schemes.
309 The class basically replaces the standard "update a cell" action set with an
310 action set that might or might not spawn a task. In return, it adds a further
311 action set which merges the arising task outcomes into the actual mesh
312 structure. By default, we use peano4::datamanagement::CellMarker::willBeEnclaveCell()
313 and peano4::datamanagement::CellMarker::hasBeenEnclaveCell() to guide the
314 decision whether to spawn a task or not. You can overwrite this decision
315 by redefining the corresponding entry in the dictionary befilled by
316 add_entries_to_text_replacement_dictionary().
321 Use the attributes self.enclave_task_priority to change the priority of the
322 task. This value can either be a string that C++ can evaluate into a
323 priority or a plain numerical value. I set it to
325 self.enclave_task_priority = "tarch::multicore::Task::DefaultPriority-1"
339 plot_grid_properties,
340 pde_terms_without_state: bool,
344 Not so nice. I have to store this field as I later rely on get_name_of_global_instance()
345 which uses this field.
352 super(EnclaveTasking, self).
__init__(
360 plot_grid_properties,
361 pde_terms_without_state,
366 additional_includes =
"""
367#include "exahype2/EnclaveBookkeeping.h"
368#include "exahype2/EnclaveTask.h"
377 All the internal logic depends on guards, i.e., boolean predicates. We
378 want to be able to alter them in subclasses, but we need a certain
379 baseline. It is defined in this routine.
385 +
".getSolverState()=="
387 +
"::SolverState::GridInitialisation"
395 +
".getSolverState()=="
397 +
"::SolverState::PrimaryAfterGridInitialisation or "
400 +
".getSolverState()=="
402 +
"::SolverState::PlottingInitialCondition"
410 +
".getSolverState()=="
412 +
"::SolverState::Primary or "
415 +
".getSolverState()=="
417 +
"::SolverState::PrimaryAfterGridInitialisation"
422 repositories::{}.getSolverState()=={}::SolverState::Primary
423 or repositories::{}.getSolverState()=={}::SolverState::PrimaryAfterGridInitialisation
424 or repositories::{}.getSolverState()=={}::SolverState::Plotting
425 or repositories::{}.getSolverState()=={}::SolverState::PlottingInitialCondition
426 or repositories::{}.getSolverState()=={}::SolverState::Suspended
436 repositories::{}.getSolverState()=={}::SolverState::GridInitialisation
437 or repositories::{}.getSolverState()=={}::SolverState::Primary
438 or repositories::{}.getSolverState()=={}::SolverState::PrimaryAfterGridInitialisation
446 repositories::{}.getSolverState()=={}::SolverState::GridInitialisation
447 or repositories::{}.getSolverState()=={}::SolverState::Primary
448 or repositories::{}.getSolverState()=={}::SolverState::PrimaryAfterGridInitialisation
449 or repositories::{}.getSolverState()=={}::SolverState::GridConstruction
461 +
".getSolverState()=="
463 +
"::SolverState::Secondary"
471 +
".getSolverState()=="
473 +
"::SolverState::Secondary or "
476 +
".getSolverState()=="
478 +
"::SolverState::GridConstruction"
486 +
".getSolverState()=="
488 +
"::SolverState::Secondary or "
491 +
".getSolverState()=="
493 +
"::SolverState::GridInitialisation"
498 repositories::{}.getSolverState()=={}::SolverState::Secondary
499 or repositories::{}.getSolverState()=={}::SolverState::GridInitialisation
500 or repositories::{}.getSolverState()=={}::SolverState::PlottingInitialCondition
501 or repositories::{}.getSolverState()=={}::SolverState::Plotting
502 or repositories::{}.getSolverState()=={}::SolverState::Suspended
514 This routine does not really add new data, but it heavily tailors when data are
515 stored, exchanged, ... Each generator has some guard attributes, i.e., some guards,
516 which control when data is stored, sent, received. The routine takes these guards
517 and rewires them to the local guards of this object. If you alter these guards
518 further, you have to alter them before you invoke this class' create_data_structures().
541 Make storage and loading more restrictive such that enclave data are not held in-between primary and secondary sweep
543 If you work with global time stepping, you know that each enclave cell will
544 be updated per grid traversal duo. Consequently, every enclave cell's data
545 doesn't have to be stored in-between two grid traversals - we know that it
546 is currently outsourced to a task.
548 Things are different if we use local time stepping, as there will always be
549 cells that are currently processed, and then there are cells which are not
550 updated and which we consequently should keep.
552 If you want to have this optimisation, you have to call this routine
553 explicitly in create_data_structures(). By default, we always store the
554 patches all the time.
556 If we work with smart pointers, it is a bad idea to call this routine,
557 as the enclave framework does not(!) use smart pointers. So we rely on
558 the fact that someone holds the raw pointers alive. If we don't store
559 data here, we run risk that the smart pointer becomes zero and the
560 underlying memory is freed while the enclave task still works against it.
562 As I don't know what storage scheme we employ, I decided to disable
563 this routine. Notably as I don't think storing makes much of a
564 difference if data are held on the heap anyway.
582 Adaptive mesh handing
584 Adaptive meshes require us to clear the patch overlaps and to restrict/interpolate.
585 Obviously, there's no need to do this for a refined faces. So we can eliminate these
586 cases a priori. Furthermore, we clear faces only in the primary sweep. We know that
587 either the primary sweep (for skeleton) or the secondary sweep (for enclaves) will
588 write in proper data into anything that's cleared, and we know that restriction only
589 has to happen after the primary sweep, as all cells next to an adaptivity boundary
592 As pointed out, both interpolation and restriction are to be active for the first
593 sweep only. We interpolate into hanging faces, and we have to restrict immediately
594 again as they are non-persistent. The projection onto the (hanging) faces is also
595 happening directly in the primary sweep, as the cells adjacent to the hanging
596 face are skeleton cells.
598 AMR and adjust cell have to be there always, i.e., also throughout
599 the grid construction. But the criterion is something that we only
600 evaluate in the secondary sweep. That's when we have an updated/changed time step.
601 If we identify coarsening and refinement instructions in the secondary sweep, the
602 next primary one will actually see them and trigger the update. That is, the
603 subsequent secondary switch will actually implement the grid changes, and we can
604 evaluate the criteria again.
606 For dynamic AMR, this implies that we have to ensure that all changed grid parts
607 are labelled as skeleton cells. This way, we can implement the AMR properly, we
608 ensure that all the enclaves run in parallel, and we know that all data is held
609 persistently on the stacks.
639 +
".getSolverState()=="
641 +
"::SolverState::Primary and marker.willBeSkeletonCell() ) "
642 +
"or (repositories::"
644 +
".getSolverState()=="
646 +
"::SolverState::PrimaryAfterGridInitialisation and marker.willBeSkeletonCell() ) "
647 +
"or (repositories::"
649 +
".getSolverState()=="
651 +
"::SolverState::Secondary and marker.willBeEnclaveCell() ) "
652 +
"or (repositories::"
654 +
".getSolverState()=="
656 +
"::SolverState::GridInitialisation )"
674 refinement_criterion,
678 additional_action_set_includes,
679 additional_user_includes,
682 If you pass in User_Defined, then the generator will create C++ stubs
683 that you have to befill manually. If you pass in None_Implementation, it
684 will create nop, i.e., no implementation or defaults. Any other string
685 is copied 1:1 into the implementation. If you pass in None, then the
686 set value so far won't be overwritten.
688 if boundary_conditions
is not None:
690 if refinement_criterion
is not None:
692 if initial_conditions
is not None:
694 if memory_location
is not None:
699 if refinement_criterion == exahype2.solvers.PDETerms.None_Implementation:
700 assert False,
"Refinement criterion cannot be none"
701 if initial_conditions == exahype2.solvers.PDETerms.None_Implementation:
702 assert False,
"Initial conditions cannot be none"
706 != peano4.toolbox.blockstructured.ReconstructedArrayMemoryLocation.HeapThroughTarchWithoutDelete
707 and memory_location !=
None
710 "Only valid memory mode for enclave tasking is heap without a delete, as enclave tasks delete memory themselves through the tarch. Selected mode="
711 + str(solver._reconstructed_array_memory_location)
719 d: Dictionary of string to string
722 d[
"NUMBER_OF_DOUBLE_VALUES_IN_PATCH_2D"] = (
723 d[
"NUMBER_OF_VOLUMES_PER_AXIS"]
724 * d[
"NUMBER_OF_VOLUMES_PER_AXIS"]
725 * (d[
"NUMBER_OF_UNKNOWNS"] + d[
"NUMBER_OF_AUXILIARY_VARIABLES"])
727 d[
"NUMBER_OF_DOUBLE_VALUES_IN_PATCH_3D"] = (
728 d[
"NUMBER_OF_VOLUMES_PER_AXIS"]
729 * d[
"NUMBER_OF_VOLUMES_PER_AXIS"]
730 * d[
"NUMBER_OF_VOLUMES_PER_AXIS"]
731 * (d[
"NUMBER_OF_UNKNOWNS"] + d[
"NUMBER_OF_AUXILIARY_VARIABLES"])
734 d[
"NUMBER_OF_DOUBLE_VALUES_IN_PATCH_PLUS_HALO_2D"] = (
735 (d[
"NUMBER_OF_VOLUMES_PER_AXIS"] + 2)
736 * (d[
"NUMBER_OF_VOLUMES_PER_AXIS"] + 2)
737 * (d[
"NUMBER_OF_UNKNOWNS"] + d[
"NUMBER_OF_AUXILIARY_VARIABLES"])
739 d[
"NUMBER_OF_DOUBLE_VALUES_IN_PATCH_PLUS_HALO_3D"] = (
740 (d[
"NUMBER_OF_VOLUMES_PER_AXIS"] + 2)
741 * (d[
"NUMBER_OF_VOLUMES_PER_AXIS"] + 2)
742 * (d[
"NUMBER_OF_VOLUMES_PER_AXIS"] + 2)
743 * (d[
"NUMBER_OF_UNKNOWNS"] + d[
"NUMBER_OF_AUXILIARY_VARIABLES"])
746 d[
"FUSED_COMPUTE_KERNEL_CALL_STATELESS_CPU"] = jinja2.Template(
749 d[
"FUSED_COMPUTE_KERNEL_CALL_STATELESS_GPU"] = jinja2.Template(
753 d[
"SEMAPHORE_LABEL"] = exahype2.grid.UpdateCellLabel.get_attribute_name(
762 step, evaluate_refinement_criterion
774 Add enclave aspect to time stepping
776 There's a bunch of different things to do to extend my standard solver
777 into an enclave solver. In this operation, we add the runtime logic,
778 i.e., what happens at which point.
780 We need additional action sets that are
781 triggered throughout the traversal in every second time step. I call this
782 one task_based_implementation_primary_iteration or secondary,
783 respectively. One wraps the implementation of _HandleCellTemplate into a
784 task, the other communicates with the task bookkeeping only. Both rely on
785 additional labels within the cell. We therefore end up with three new
786 action sets: reconstruct_patch_and_apply_FV_kernel, exahype2.grid.UpdateCellLabel
787 and roll_over_enclave_task_results.
795 namespace, output, dimensions
798 templatefile_prefix = os.path.join(
799 os.path.dirname(os.path.realpath(__file__)),
800 "EnclaveTasking.EnclaveTask.template",
806 implementationDictionary = {}
816 generated_solver_files = (
818 "{}.h".format(templatefile_prefix),
819 "{}.cpp".format(templatefile_prefix),
821 namespace + [
"tasks"],
822 subdirectory +
"tasks",
823 implementationDictionary,
828 output.add(generated_solver_files)
829 output.makefile.add_h_file(subdirectory +
"tasks/" + task_name +
".h", generated=
True)
830 output.makefile.add_cpp_file(subdirectory +
"tasks/" + task_name +
".cpp", generated=
True)
834 return "{}EnclaveTask".format(self.
_name)
839 cell_data_storage: Storage,
840 face_data_storage: Storage,
842 if cell_data_storage == Storage.SmartPointers:
Update the cell label within a sweep.
Enclave tasking variant of the Finite Volume scheme.
str _primary_or_initialisation_sweep_guard
str enclave_task_priority
switch_storage_scheme(self, Storage cell_data_storage, Storage face_data_storage)
By default, we hold all data on the heap using smart pointers.
_action_set_merge_enclave_task_outcome
add_entries_to_text_replacement_dictionary(self, d)
d: Dictionary of string to string in/out argument
str _secondary_sweep_or_grid_initialisation_or_plot_guard
__init__(self, name, patch_size, overlap, unknowns, auxiliary_variables, min_volume_h, max_volume_h, plot_grid_properties, bool pde_terms_without_state, kernel_namespace)
Not so nice.
create_action_sets(self)
Adaptive mesh handing.
tuple _secondary_sweep_guard
str _fused_compute_kernel_call_stateless_cpu
_optimise_patch_storage_for_global_time_stepping(self)
Make storage and loading more restrictive such that enclave data are not held in-between primary and ...
set_implementation(self, boundary_conditions, refinement_criterion, initial_conditions, memory_location, use_split_loop, additional_action_set_includes, additional_user_includes)
If you pass in User_Defined, then the generator will create C++ stubs that you have to befill manuall...
_create_guards(self)
All the internal logic depends on guards, i.e., boolean predicates.
tuple _primary_sweep_guard
create_data_structures(self)
This routine does not really add new data, but it heavily tailors when data are stored,...
add_actions_to_init_grid(self, step)
Add all the action sets to init grid.
tuple _secondary_sweep_or_grid_initialisation_guard
tuple _initialisation_sweep_guard
add_actions_to_perform_time_step(self, step)
Add enclave aspect to time stepping.
tuple _secondary_sweep_or_grid_construction_guard
add_implementation_files_to_project(self, namespace, output, dimensions, subdirectory="")
The ExaHyPE project will call this operation when it sets up the overall environment.
tuple _first_iteration_after_initialisation_guard
bool make_copy_of_enclave_task_data
str _primary_or_grid_construction_or_initialisation_sweep_guard
str _fused_compute_kernel_call_stateless_gpu
add_actions_to_create_grid(self, step, evaluate_refinement_criterion)
The boundary information is set only once.
str _primary_sweep_or_plot_guard
get_body_of_operation(self, operation_name)
Return actual C++ code snippets to be inserted into C++ code.
get_action_set_name(self)
You should replicate this function in each subclass, so you get meaningful action set names (otherwis...
Update cell in primary sweep.
get_includes(self)
Return include statements that you need.
_add_action_set_entries_to_dictionary(self, d)
First ask the solver to add its symbols, and then re-construct the functor which should not contain a...
get_action_set_name(self)
Return unique action set name.
Abstract finite volume solver step sizes that works on patch-based AMR with a halo layer of one.
tuple _action_set_initial_conditions_for_grid_construction
_store_face_data_default_guard(self)
Extend the guard via ands only.
user_solver_includes(self)
Add further includes to this property, if your solver requires some additional routines from other he...
_init_dictionary_with_default_parameters(self, d)
This one is called by all algorithmic steps before I invoke add_entries_to_text_replacement_dictionar...
tuple _action_set_AMR_commit_without_further_analysis
tuple _action_set_handle_boundary
tuple _action_set_initial_conditions
_store_cell_data_default_guard(self)
Extend the guard via ands only.
tuple _action_set_project_patch_onto_faces
get_name_of_global_instance(self)
user_action_set_includes(self)
Add further includes to this property, if your action sets require some additional routines from othe...
tuple _action_set_couple_resolution_transitions_and_handle_dynamic_mesh_refinement
add_user_action_set_includes(self, value)
Add further includes to this property, if your action sets require some additional routines from othe...
tuple _action_set_roll_over_update_of_faces