3from __future__
import annotations
9 Represents one named, point-wise PDE operator that a solver exposes for
10 the user to implement: a C++ method name, its parameter list, and an
11 implementation. flux(), eigenvalues(), the nonconservative product,
12 algebraic/point sources, boundary conditions, ... all have this same
13 shape (see python/exahype2/solvers/fv/riemann/kernels.py for how they
14 are actually declared in the generated abstract solver header).
16 Well-known terms should get their own PDETerm subclass which pins down
17 name and signature and adds any term-specific behaviour - @see Flux for
18 a fully worked example. Use the plain PDETerm constructor directly only
19 for a new, solver-specific term that does not warrant its own subclass
22 A term's implementation is a C++ code snippet that becomes the generated
23 method's body verbatim - including the empty string, which generates a
24 method that does nothing (a no-op, not "contributes zero": it does not
25 zero out the output for you) - or None (the default), which leaves the
26 method pure virtual for you to implement by hand in your solver
27 subclass instead of generating it. A term you don't want at all is
28 simply never passed to solver.add_pde_term() in the first place.
30 implementation must not be None when stateless is True: the constructor
31 raises ValueError in that case, since a stateless term's generated
32 method is static, and static methods cannot be pure virtual - there
33 would be no declaration left to signal "implement this by hand".
35 PDETerm only models *what* the term is (name, signature, implementation).
36 It deliberately knows nothing about Jinja2 or the code generator: turning
37 a solver's collection of terms into the generated abstract solver header
38 remains the code generator's job, exactly as it is today.
40 @see exahype2.api.Solver.add_pde_term()
50 implementation: str =
None,
51 stateless: bool =
False,
56 The C++ method name, e.g. "flux" or "eigenvalues". Used by
57 exahype2.api.Solver to key its collection of terms, i.e. has to be
61 A C++ snippet that becomes the method's body verbatim (the empty
62 string is a valid, generated no-op body), or None (the default) to
63 leave the method pure virtual for you to implement by hand. Must
64 not be None if stateless is True: the generated method is then
65 static, and static methods cannot be pure virtual, so there would
66 be nothing left to tell you to implement it by hand.
69 if stateless
and implementation
is None:
71 f
"PDETerm {name!r} is stateless, so its generated method is static - "
72 "static methods cannot be pure virtual, so implementation must not be "
73 "None (pass a C++ snippet, or the empty string for a no-op body)."
80 return f
"{self.__class__.__name__}(name={self.name!r}, implementation={self.implementation!r})"
83 return f
"{self.name}: {self.implementation}"
88 Turns a PDETerm into the C++ text for its generated method: exactly
89 one method - the static, GPU-offloadable overload if the term is
90 stateless, otherwise the virtual instance method. Never both from
91 the same term; @see exahype2.api.Solver.add_pde_term() for how to
92 get both variants of the same conceptual operation.
94 If term.implementation is None, the method is left for the user to
95 implement by hand: pure virtual (" = 0") for the non-stateless case,
96 or - since static methods cannot be pure virtual - simply declared
97 without a matching out-of-class definition for the stateless case.
98 Otherwise the method is generated non-overridable ("final") with
99 term.implementation as its body.
101 Embedded into PDETerm (reachable as term.generator) so that a term
102 subclass can supply its own Generator - @see Flux.Generator - and
103 term.generator resolves to the right one automatically, since it is
104 looked up on the term's actual class rather than hardcoded to
115 The term's C++ parameter list. Looked up as a SIGNATURE attribute
116 on the owning term (e.g. Flux.SIGNATURE); a plain, custom PDETerm
117 has to set term.SIGNATURE itself before it can generate code.
119 signature = getattr(self.
term,
"SIGNATURE",
None)
120 if signature
is None:
121 raise AttributeError(
122 f
"{self.term!r} (or its class) has no SIGNATURE to generate code from; "
123 "either subclass PDETerm and provide one (see Flux), or set "
124 "term.SIGNATURE explicitly."
130 static GPU-offloadable declaration, or the empty string if the
131 term isn't stateless. exahype2::Solver::Offloadable is a dummy
132 tag argument that exists precisely because C++ cannot overload on
133 staticness alone - it is what lets this static method coexist
134 with the same-named, non-static, no-Offloadable-argument method
135 that registering the same term name again without stateless
138 if not self.
term.stateless:
141 "#if defined(GPUOffloadingOMP)\n"
142 "#pragma omp declare target\n"
144 f
"static GPUCallableMethod void {self.term.name}(\n"
145 f
" {self.signature},\n"
148 "#if defined(GPUOffloadingOMP)\n"
149 "#pragma omp end declare target\n"
155 Declaration text for the abstract solver header: the static,
156 GPU-offloadable overload if this term is stateless, otherwise
157 the virtual instance method - never both from one term. Pure
158 virtual (" = 0") if term.implementation is None, "final"
159 otherwise; the static, stateless overload is unaffected by this
160 distinction (static methods cannot be pure virtual) - @see
161 generate_out_of_class_definitions for how it is left for the
162 user to implement by hand instead.
164 if self.
term.stateless:
166 body_suffix =
" = 0" if self.
term.implementation
is None else " final"
168 f
"virtual void {self.term.name}(\n"
169 f
" {self.signature}\n"
175 Body of the generated method: the term's implementation verbatim.
176 Only meaningful when term.implementation is not None - @see
177 generate_out_of_class_definitions, which never calls this
180 return self.
term.implementation
184 Out-of-class method body for the solver's .cpp file: the static,
185 GPU-offloadable overload if this term is stateless, otherwise
186 the virtual instance method - mirrors generate_declaration(),
187 never both from one term. The empty string if
188 term.implementation is None: the user implements that method by
189 hand instead, whether as an override of the pure virtual
190 instance method or, for a stateless term, as an ordinary static
191 method matching the declaration (name hiding, not override -
192 static methods cannot be virtual).
194 qualified_class_name: str
195 Fully qualified class name the definition is attached to,
196 e.g. "examples::MySolver::AbstractMySolver".
198 if self.
term.implementation
is None:
201 if self.
term.stateless:
203 "#if defined(GPUOffloadingOMP)\n"
204 "#pragma omp declare target\n"
206 f
"GPUCallableMethod void {qualified_class_name}::{self.term.name}(\n"
207 f
" {self.signature},\n"
209 f
") {{\n {body}\n}}\n"
210 "#if defined(GPUOffloadingOMP)\n"
211 "#pragma omp end declare target\n"
215 f
"void {qualified_class_name}::{self.term.name}(\n"
216 f
" {self.signature}\n"
217 f
") {{\n {body}\n}}\n"
222 Override declaration for the concrete solver class that the
223 Python API generates when the user hasn't provided their own
224 implementation of this term - the empty string if the term
225 isn't left abstract (term.implementation is not None; a
226 stateless term's method is static and thus never overridable
227 in the first place, but that combination cannot occur anyway,
228 @see PDETerm.__init__).
230 if self.
term.implementation
is not None or self.
term.stateless:
233 f
"virtual void {self.term.name}(\n"
234 f
" {self.signature}\n"
240 Out-of-class body for generate_stub_declaration(): a trace-in
241 (with x, and normal too if the term's signature has one), a
242 logWarning noting the method is unimplemented, a
243 "// @todo implement" comment, and logTraceOut - or the empty
244 string under the same condition as generate_stub_declaration().
246 qualified_class_name: str
247 Fully qualified class name the definition is attached to,
248 e.g. "examples::MySolver::MySolver".
250 if self.
term.implementation
is not None or self.
term.stateless:
252 method_name = f
"{self.term.name}(...)"
254 f
'logTraceInWith2Arguments("{method_name}", x, normal);'
256 else f
'logTraceInWith1Argument("{method_name}", x);'
259 f
"void {qualified_class_name}::{self.term.name}(\n"
260 f
" {self.signature}\n"
263 f
' logWarning("{method_name}", "not implemented yet");\n'
264 f
" // @todo implement\n"
265 f
' logTraceOut("{method_name}");\n'
272 Code generator for this term: an instance of this term's own
273 (possibly subclass-specific) Generator. @see PDETerm.Generator
Turns a PDETerm into the C++ text for its generated method: exactly one method - the static,...
__init__(self, "PDETerm" term)
str generate_declaration(self)
Declaration text for the abstract solver header: the static, GPU-offloadable overload if this term is...
str generate_definition(self)
Body of the generated method: the term's implementation verbatim.
str generate_out_of_class_definitions(self, str qualified_class_name)
Out-of-class method body for the solver's .cpp file: the static, GPU-offloadable overload if this ter...
str _generate_static_offloadable_declaration(self)
static GPU-offloadable declaration, or the empty string if the term isn't stateless.
str generate_stub_declaration(self)
Override declaration for the concrete solver class that the Python API generates when the user hasn't...
str signature(self)
The term's C++ parameter list.
str generate_stub_definition(self, str qualified_class_name)
Out-of-class body for generate_stub_declaration(): a trace-in (with x, and normal too if the term's s...
Represents one named, point-wise PDE operator that a solver exposes for the user to implement: a C++ ...
__init__(self, str name, str implementation=None, bool stateless=False)
name: str The C++ method name, e.g.
"PDETerm.Generator" generator(self)
Code generator for this term: an instance of this term's own (possibly subclass-specific) Generator.