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 tarch::multicore::spawnTask(
116 predecessorEnclaveTaskNumber>=0 ? std::set<int>{predecessorEnclaveTaskNumber} : tarch::multicore::NoInDependencies,
117 newEnclaveTask->getTaskId()
120 if (predecessorEnclaveTaskNumber>=0) {
121 ::exahype2::EnclaveTask::releaseTaskNumber(predecessorEnclaveTaskNumber);
124 fineGridCell{{SEMAPHORE_LABEL}}.setSemaphoreNumber( newEnclaveTask->getTaskId() );
125 fineGridCell{{SOLVER_NAME}}CellLabel.setTimeStamp(timeStamp + timeStepSize);
132 ReconstructPatchAndApplyFunctor.__init__(
136 patch_overlap=solver._patch_overlap_new,
137 functor_implementation=
"<not yet set - will do this later>",
138 reconstructed_array_memory_location=peano4.toolbox.blockstructured.ReconstructedArrayMemoryLocation.ManagedSharedAcceleratorDeviceMemoryThroughTarchWithoutDelete,
140 guard=
"not marker.hasBeenRefined() and ("
142 + solver.get_name_of_global_instance()
143 +
".getSolverState()=="
145 +
"::SolverState::Primary or "
147 + solver.get_name_of_global_instance()
148 +
".getSolverState()=="
150 +
"::SolverState::PrimaryAfterGridInitialisation"
152 add_assertions_to_halo_exchange=
True,
159 + solver.get_name_of_global_instance()
160 +
""".getSolverState()=="""
162 +
"""::SolverState::Primary or
164 + solver.get_name_of_global_instance()
165 +
""".getSolverState()=="""
167 +
"""::SolverState::PrimaryAfterGridInitialisation
171 +
"""CellLabel.setHasUpdated(false);
182 ReconstructPatchAndApplyFunctor.get_includes(self)
184#include "tarch/multicore/Task.h"
185#include "repositories/SolverRepository.h"
190 + self.
_solver._get_default_includes()
191 + self.
_solver.user_action_set_includes
196 return __name__.replace(
".py",
"").replace(
".",
"_")
201 First ask the solver to add its symbols, and then re-construct the
202 functor which should not contain any symbols after that anymore.
203 Next, we call the superclass routine which supplements all those
204 instructions that any reconstruction wants to have.
207 self.
_solver._init_dictionary_with_default_parameters(d)
208 self.
_solver.add_entries_to_text_replacement_dictionary(d)
218 not marker.hasBeenRefined()
222 repositories::{{SOLVER_INSTANCE}}.getSolverState() == {{SOLVER_NAME}}::SolverState::Secondary
224 const int taskNumber = fineGridCell{{LABEL_NAME}}.getSemaphoreNumber();
225 if (marker.hasBeenEnclaveCell() and taskNumber >= 0) {
226 double maxEigenvalue;
227 ::exahype2::EnclaveBookkeeping::getInstance().waitForTaskToTerminateAndCopyResultOver( taskNumber, fineGridCell{{UNKNOWN_IDENTIFIER}}.value, maxEigenvalue );
229 ::exahype2::fv::validatePatch(
230 fineGridCell{{UNKNOWN_IDENTIFIER}}.value,
231 {{NUMBER_OF_UNKNOWNS}},
232 {{NUMBER_OF_AUXILIARY_VARIABLES}},
233 {{NUMBER_OF_VOLUMES_PER_AXIS}},
235 std::string(__FILE__) + ": " + std::to_string(__LINE__) + "; marker=" + marker.toString()
238 {{COMPUTE_NEW_TIME_STEP_SIZE}}
240 fineGridCell{{LABEL_NAME}}.setSemaphoreNumber( ::exahype2::EnclaveBookkeeping::NoEnclaveTaskNumber );
241 fineGridCell{{SOLVER_NAME}}CellLabel.setHasUpdated(true);
242 fineGridCell{{SOLVER_NAME}}CellLabel.setTimeStepSize(newTimeStepSize);
245 if (fineGridCell{{SOLVER_NAME}}CellLabel.getHasUpdated()) {
246 double* newQ = fineGridCell{{UNKNOWN_IDENTIFIER}}.value;
248 {{POSTPROCESS_UPDATED_PATCH}}
250 repositories::{{SOLVER_INSTANCE}}.update(
251 fineGridCell{{SOLVER_NAME}}CellLabel.getTimeStepSize(),
252 fineGridCell{{SOLVER_NAME}}CellLabel.getTimeStamp(),
256 repositories::{{SOLVER_INSTANCE}}.update(
258 fineGridCell{{SOLVER_NAME}}CellLabel.getTimeStamp(),
267 super(MergeEnclaveTaskOutcome, self).
__init__(solver)
268 self.
label_name = exahype2.grid.UpdateCellLabel.get_attribute_name(solver._name)
277 == peano4.solversteps.ActionSet.OPERATION_TOUCH_CELL_FIRST_TIME
280 self.
_solver._init_dictionary_with_default_parameters(d)
281 self.
_solver.add_entries_to_text_replacement_dictionary(d)
283 d[
"GUARD"] = self.
guard
284 result = jinja2.Template(self.
Template).render(**d)
290 return __name__.replace(
".py",
"").replace(
".",
"_")
295 Enclave tasking variant of the Finite Volume scheme
297 The concept of (enclave) tasking within ExaHyPE solvers is described in
298 detail in the @ref page_exahype_solvers_enclave_solvers "generic enclave discussion of ExaHyPE".
299 This class is a prototype realisation of this concept which other solvers
300 then specialise for particular numerical schemes.
302 The class basically replaces the standard "update a cell" action set with an
303 action set that might or might not spawn a task. In return, it adds a further
304 action set which merges the arising task outcomes into the actual mesh
305 structure. By default, we use peano4::datamanagement::CellMarker::willBeEnclaveCell()
306 and peano4::datamanagement::CellMarker::hasBeenEnclaveCell() to guide the
307 decision whether to spawn a task or not. You can overwrite this decision
308 by redefining the corresponding entry in the dictionary befilled by
309 add_entries_to_text_replacement_dictionary().
314 Use the attributes self.enclave_task_priority to change the priority of the
315 task. This value can either be a string that C++ can evaluate into a
316 priority or a plain numerical value. I set it to
318 self.enclave_task_priority = "tarch::multicore::Task::DefaultPriority-1"
332 plot_grid_properties,
333 pde_terms_without_state: bool,
337 Not so nice. I have to store this field as I later rely on get_name_of_global_instance()
338 which uses this field.
345 super(EnclaveTasking, self).
__init__(
353 plot_grid_properties,
354 pde_terms_without_state,
359 additional_includes =
"""
360#include "exahype2/EnclaveBookkeeping.h"
361#include "exahype2/EnclaveTask.h"
370 All the internal logic depends on guards, i.e., boolean predicates. We
371 want to be able to alter them in subclasses, but we need a certain
372 baseline. It is defined in this routine.
378 +
".getSolverState()=="
380 +
"::SolverState::GridInitialisation"
388 +
".getSolverState()=="
390 +
"::SolverState::PrimaryAfterGridInitialisation or "
393 +
".getSolverState()=="
395 +
"::SolverState::PlottingInitialCondition"
403 +
".getSolverState()=="
405 +
"::SolverState::Primary or "
408 +
".getSolverState()=="
410 +
"::SolverState::PrimaryAfterGridInitialisation"
415 repositories::{}.getSolverState()=={}::SolverState::Primary
416 or repositories::{}.getSolverState()=={}::SolverState::PrimaryAfterGridInitialisation
417 or repositories::{}.getSolverState()=={}::SolverState::Plotting
418 or repositories::{}.getSolverState()=={}::SolverState::PlottingInitialCondition
419 or repositories::{}.getSolverState()=={}::SolverState::Suspended
429 repositories::{}.getSolverState()=={}::SolverState::GridInitialisation
430 or repositories::{}.getSolverState()=={}::SolverState::Primary
431 or repositories::{}.getSolverState()=={}::SolverState::PrimaryAfterGridInitialisation
439 repositories::{}.getSolverState()=={}::SolverState::GridInitialisation
440 or repositories::{}.getSolverState()=={}::SolverState::Primary
441 or repositories::{}.getSolverState()=={}::SolverState::PrimaryAfterGridInitialisation
442 or repositories::{}.getSolverState()=={}::SolverState::GridConstruction
454 +
".getSolverState()=="
456 +
"::SolverState::Secondary"
464 +
".getSolverState()=="
466 +
"::SolverState::Secondary or "
469 +
".getSolverState()=="
471 +
"::SolverState::GridConstruction"
479 +
".getSolverState()=="
481 +
"::SolverState::Secondary or "
484 +
".getSolverState()=="
486 +
"::SolverState::GridInitialisation"
491 repositories::{}.getSolverState()=={}::SolverState::Secondary
492 or repositories::{}.getSolverState()=={}::SolverState::GridInitialisation
493 or repositories::{}.getSolverState()=={}::SolverState::PlottingInitialCondition
494 or repositories::{}.getSolverState()=={}::SolverState::Plotting
495 or repositories::{}.getSolverState()=={}::SolverState::Suspended
507 This routine does not really add new data, but it heavily tailors when data are
508 stored, exchanged, ... Each generator has some guard attributes, i.e., some guards,
509 which control when data is stored, sent, received. The routine takes these guards
510 and rewires them to the local guards of this object. If you alter these guards
511 further, you have to alter them before you invoke this class' create_data_structures().
534 Make storage and loading more restrictive such that enclave data are not held in-between primary and secondary sweep
536 If you work with global time stepping, you know that each enclave cell will
537 be updated per grid traversal duo. Consequently, every enclave cell's data
538 doesn't have to be stored in-between two grid traversals - we know that it
539 is currently outsourced to a task.
541 Things are different if we use local time stepping, as there will always be
542 cells that are currently processed, and then there are cells which are not
543 updated and which we consequently should keep.
545 If you want to have this optimisation, you have to call this routine
546 explicitly in create_data_structures(). By default, we always store the
547 patches all the time.
549 If we work with smart pointers, it is a bad idea to call this routine,
550 as the enclave framework does not(!) use smart pointers. So we rely on
551 the fact that someone holds the raw pointers alive. If we don't store
552 data here, we run risk that the smart pointer becomes zero and the
553 underlying memory is freed while the enclave task still works against it.
555 As I don't know what storage scheme we employ, I decided to disable
556 this routine. Notably as I don't think storing makes much of a
557 difference if data are held on the heap anyway.
575 Adaptive mesh handing
577 Adaptive meshes require us to clear the patch overlaps and to restrict/interpolate.
578 Obviously, there's no need to do this for a refined faces. So we can eliminate these
579 cases a priori. Furthermore, we clear faces only in the primary sweep. We know that
580 either the primary sweep (for skeleton) or the secondary sweep (for enclaves) will
581 write in proper data into anything that's cleared, and we know that restriction only
582 has to happen after the primary sweep, as all cells next to an adaptivity boundary
585 As pointed out, both interpolation and restriction are to be active for the first
586 sweep only. We interpolate into hanging faces, and we have to restrict immediately
587 again as they are non-persistent. The projection onto the (hanging) faces is also
588 happening directly in the primary sweep, as the cells adjacent to the hanging
589 face are skeleton cells.
591 AMR and adjust cell have to be there always, i.e., also throughout
592 the grid construction. But the criterion is something that we only
593 evaluate in the secondary sweep. That's when we have an updated/changed time step.
594 If we identify coarsening and refinement instructions in the secondary sweep, the
595 next primary one will actually see them and trigger the update. That is, the
596 subsequent secondary switch will actually implement the grid changes, and we can
597 evaluate the criteria again.
599 For dynamic AMR, this implies that we have to ensure that all changed grid parts
600 are labelled as skeleton cells. This way, we can implement the AMR properly, we
601 ensure that all the enclaves run in parallel, and we know that all data is held
602 persistently on the stacks.
632 +
".getSolverState()=="
634 +
"::SolverState::Primary and marker.willBeSkeletonCell() ) "
635 +
"or (repositories::"
637 +
".getSolverState()=="
639 +
"::SolverState::PrimaryAfterGridInitialisation and marker.willBeSkeletonCell() ) "
640 +
"or (repositories::"
642 +
".getSolverState()=="
644 +
"::SolverState::Secondary and marker.willBeEnclaveCell() ) "
645 +
"or (repositories::"
647 +
".getSolverState()=="
649 +
"::SolverState::GridInitialisation )"
667 refinement_criterion,
671 additional_action_set_includes,
672 additional_user_includes,
675 If you pass in User_Defined, then the generator will create C++ stubs
676 that you have to befill manually. If you pass in None_Implementation, it
677 will create nop, i.e., no implementation or defaults. Any other string
678 is copied 1:1 into the implementation. If you pass in None, then the
679 set value so far won't be overwritten.
681 if boundary_conditions
is not None:
683 if refinement_criterion
is not None:
685 if initial_conditions
is not None:
687 if memory_location
is not None:
692 if refinement_criterion == exahype2.solvers.PDETerms.None_Implementation:
693 assert False,
"Refinement criterion cannot be none"
694 if initial_conditions == exahype2.solvers.PDETerms.None_Implementation:
695 assert False,
"Initial conditions cannot be none"
699 != peano4.toolbox.blockstructured.ReconstructedArrayMemoryLocation.HeapThroughTarchWithoutDelete
700 and memory_location !=
None
703 "Only valid memory mode for enclave tasking is heap without a delete, as enclave tasks delete memory themselves through the tarch. Selected mode="
704 + str(solver._reconstructed_array_memory_location)
712 d: Dictionary of string to string
715 d[
"NUMBER_OF_DOUBLE_VALUES_IN_PATCH_2D"] = (
716 d[
"NUMBER_OF_VOLUMES_PER_AXIS"]
717 * d[
"NUMBER_OF_VOLUMES_PER_AXIS"]
718 * (d[
"NUMBER_OF_UNKNOWNS"] + d[
"NUMBER_OF_AUXILIARY_VARIABLES"])
720 d[
"NUMBER_OF_DOUBLE_VALUES_IN_PATCH_3D"] = (
721 d[
"NUMBER_OF_VOLUMES_PER_AXIS"]
722 * d[
"NUMBER_OF_VOLUMES_PER_AXIS"]
723 * d[
"NUMBER_OF_VOLUMES_PER_AXIS"]
724 * (d[
"NUMBER_OF_UNKNOWNS"] + d[
"NUMBER_OF_AUXILIARY_VARIABLES"])
727 d[
"NUMBER_OF_DOUBLE_VALUES_IN_PATCH_PLUS_HALO_2D"] = (
728 (d[
"NUMBER_OF_VOLUMES_PER_AXIS"] + 2)
729 * (d[
"NUMBER_OF_VOLUMES_PER_AXIS"] + 2)
730 * (d[
"NUMBER_OF_UNKNOWNS"] + d[
"NUMBER_OF_AUXILIARY_VARIABLES"])
732 d[
"NUMBER_OF_DOUBLE_VALUES_IN_PATCH_PLUS_HALO_3D"] = (
733 (d[
"NUMBER_OF_VOLUMES_PER_AXIS"] + 2)
734 * (d[
"NUMBER_OF_VOLUMES_PER_AXIS"] + 2)
735 * (d[
"NUMBER_OF_VOLUMES_PER_AXIS"] + 2)
736 * (d[
"NUMBER_OF_UNKNOWNS"] + d[
"NUMBER_OF_AUXILIARY_VARIABLES"])
739 d[
"FUSED_COMPUTE_KERNEL_CALL_STATELESS_CPU"] = jinja2.Template(
742 d[
"FUSED_COMPUTE_KERNEL_CALL_STATELESS_GPU"] = jinja2.Template(
746 d[
"SEMAPHORE_LABEL"] = exahype2.grid.UpdateCellLabel.get_attribute_name(
755 step, evaluate_refinement_criterion
767 Add enclave aspect to time stepping
769 There's a bunch of different things to do to extend my standard solver
770 into an enclave solver. In this operation, we add the runtime logic,
771 i.e., what happens at which point.
773 We need additional action sets that are
774 triggered throughout the traversal in every second time step. I call this
775 one task_based_implementation_primary_iteration or secondary,
776 respectively. One wraps the implementation of _HandleCellTemplate into a
777 task, the other communicates with the task bookkeeping only. Both rely on
778 additional labels within the cell. We therefore end up with three new
779 action sets: reconstruct_patch_and_apply_FV_kernel, exahype2.grid.UpdateCellLabel
780 and roll_over_enclave_task_results.
788 namespace, output, dimensions
791 templatefile_prefix = os.path.join(
792 os.path.dirname(os.path.realpath(__file__)),
793 "EnclaveTasking.EnclaveTask.template",
799 implementationDictionary = {}
809 generated_solver_files = (
811 "{}.h".format(templatefile_prefix),
812 "{}.cpp".format(templatefile_prefix),
814 namespace + [
"tasks"],
815 subdirectory +
"tasks",
816 implementationDictionary,
821 output.add(generated_solver_files)
822 output.makefile.add_h_file(subdirectory +
"tasks/" + task_name +
".h", generated=
True)
823 output.makefile.add_cpp_file(subdirectory +
"tasks/" + task_name +
".cpp", generated=
True)
827 return "{}EnclaveTask".format(self.
_name_name)
832 cell_data_storage: Storage,
833 face_data_storage: Storage,
835 if cell_data_storage == Storage.SmartPointers:
Update the cell label within a sweep.
Enclave tasking variant of the Finite Volume scheme.
_initial_conditions_implementation
_boundary_conditions_implementation
_secondary_sweep_or_grid_construction_guard
_first_iteration_after_initialisation_guard
_fused_compute_kernel_call_stateless_cpu
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
__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.
_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.
_primary_sweep_or_plot_guard
_secondary_sweep_or_grid_initialisation_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.
_fused_compute_kernel_call_stateless_gpu
_refinement_criterion_implementation
_solver_template_file_class_name
_primary_or_initialisation_sweep_guard
add_actions_to_perform_time_step(self, step)
Add enclave aspect to time stepping.
_primary_or_grid_construction_or_initialisation_sweep_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.
make_copy_of_enclave_task_data
_initialisation_sweep_guard
add_actions_to_create_grid(self, step, evaluate_refinement_criterion)
The boundary information is set only once.
_secondary_sweep_or_grid_initialisation_or_plot_guard
_reconstructed_array_memory_location
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...
__init__(self, solver)
solver: ADERDG Reference to creating class
Update cell in primary sweep.
get_includes(self)
Return include statements that you need.
__init__(self, solver)
patch: peano4.datamodel.Patch Patch which is to be used
_Template_TouchCellFirstTime_Preamble
_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.
_solver_template_file_class_name
_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...
_action_set_couple_resolution_transitions_and_handle_dynamic_mesh_refinement
_init_dictionary_with_default_parameters(self, d)
This one is called by all algorithmic steps before I invoke add_entries_to_text_replacement_dictionar...
_boundary_conditions_implementation
create_action_sets(self)
Create all the action sets.
_action_set_AMR_commit_without_further_analysis
_action_set_handle_boundary
_action_set_initial_conditions
_store_cell_data_default_guard(self)
Extend the guard via ands only.
_action_set_project_patch_onto_faces
get_name_of_global_instance(self)
_initial_conditions_implementation
user_action_set_includes(self)
Add further includes to this property, if your action sets require some additional routines from othe...
add_entries_to_text_replacement_dictionary(self, d)
_reconstructed_array_memory_location
_refinement_criterion_implementation
add_user_action_set_includes(self, value)
Add further includes to this property, if your action sets require some additional routines from othe...
_action_set_roll_over_update_of_faces