PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
Functions | Variables
audit_units Namespace Reference

Functions

 load_module (str name, Path path)
 Load a repository script as an importable module.
 
 schema_leaves (dict schema)
 Yield every key path a schema accepts that is not itself a nested mapping.
 
list uncovered_inputs (core)
 Name every solver-facing input with no recorded physical dimension.
 
str dimension_name (core, dimension)
 Name a dimension triple by its constant in the conductor core.
 
dict physical_inputs (core)
 Every input that carries a physical dimension, as the input index lists it.
 
dict compiled_field_dimensions ()
 Field name to (catalog, FIELD_DIM_* suffix) for every compiled entry.
 
str page_section (str heading)
 One @section body of page 19.
 
dict documented_rows (str heading)
 Rows of the first table in a section: first column to second and third.
 
int main ()
 Fail when an input lacks a dimension or an index disagrees with the code.
 

Variables

 REPO_ROOT = Path(__file__).resolve().parents[2]
 
str CORE = REPO_ROOT / "picurv_cli" / "core.py"
 
str IC_GENERATOR = REPO_ROOT / "generators" / "ic.gen"
 
str EULERIAN_CATALOG = REPO_ROOT / "src" / "field_catalog.c"
 
str PARTICLE_CATALOG = REPO_ROOT / "src" / "particle_field_catalog.c"
 
str PAGE = REPO_ROOT / "docs" / "pages" / "19_Nondimensionalization.md"
 
dict SOLVER_INPUT_SCHEMAS
 
dict DEDICATED_TABLE_SURFACES
 
dict BUILTIN_IC_GENERATORS = {"zero", "constant", "streamwise_constant", "poiseuille"}
 
 ENTRY_RE = re.compile(r"\b(?:FIELD_ENTRY|FIELD_COORDINATE_ENTRY|PARTICLE_FIELD_ENTRY)\‍(")
 
 DIMENSION_RE = re.compile(r"FIELD_DIM_(\w+)")
 
 NAME_RE = re.compile(r'"([^"]+)"')
 
 INDEX_ROW_RE = re.compile(r"^\|\s*`([^`]+)`\s*\|\s*(\w+)\s*\|\s*`?(\w+)`?\s*\|", re.M)
 

Function Documentation

◆ load_module()

audit_units.load_module ( str  name,
Path  path 
)

Load a repository script as an importable module.

Parameters
[in]nameModule name to register.
[in]pathSource path of the script.
Returns
Loaded module object.

Definition at line 58 of file audit_units.py.

58def load_module(name: str, path: Path):
59 """!
60 @brief Load a repository script as an importable module.
61 @param[in] name Module name to register.
62 @param[in] path Source path of the script.
63 @return Loaded module object.
64 """
65 loader = importlib.machinery.SourceFileLoader(name, str(path))
66 spec = importlib.util.spec_from_loader(name, loader)
67 module = importlib.util.module_from_spec(spec)
68 loader.exec_module(module)
69 return module
70
71
Here is the caller graph for this function:

◆ schema_leaves()

audit_units.schema_leaves ( dict  schema)

Yield every key path a schema accepts that is not itself a nested mapping.

A path mapped to None accepts arbitrary keys and is yielded as a leaf: its contents are covered as a whole or by a dedicated table. A list's children are keyed under path + ("[]",), so any path prefixing an entry is interior.

Parameters
[in]schemaKey schema mapping parent paths to allowed key sets.
Returns
Generator of key-path tuples.

Definition at line 72 of file audit_units.py.

72def schema_leaves(schema: dict):
73 """!
74 @brief Yield every key path a schema accepts that is not itself a nested mapping.
75 @details A path mapped to `None` accepts arbitrary keys and is yielded as a leaf: its
76 contents are covered as a whole or by a dedicated table. A list's children
77 are keyed under `path + ("[]",)`, so any path prefixing an entry is interior.
78 @param[in] schema Key schema mapping parent paths to allowed key sets.
79 @return Generator of key-path tuples.
80 """
81 interiors = {
82 parent[:length]
83 for parent, keys in schema.items() if keys is not None
84 for length in range(1, len(parent) + 1)
85 }
86 for parent, keys in schema.items():
87 for key in keys or ():
88 path = parent + (key,)
89 if path not in interiors:
90 yield path
91
92
Here is the caller graph for this function:

◆ uncovered_inputs()

list audit_units.uncovered_inputs (   core)

Name every solver-facing input with no recorded physical dimension.

Parameters
[in]coreLoaded conductor core.
Returns
Dotted names of uncovered inputs; empty when complete.

Definition at line 93 of file audit_units.py.

93def uncovered_inputs(core) -> list:
94 """!
95 @brief Name every solver-facing input with no recorded physical dimension.
96 @param[in] core Loaded conductor core.
97 @return Dotted names of uncovered inputs; empty when complete.
98 """
99 problems = []
100 for role, schema_name in SOLVER_INPUT_SCHEMAS.items():
101 for path in schema_leaves(getattr(core, schema_name)):
102 full = (role,) + path
103 if full in DEDICATED_TABLE_SURFACES:
104 continue
105 try:
106 core.input_quantity(full)
107 except KeyError:
108 problems.append(".".join(full))
109
110 accepted = set(core._DEPRECATED_BC_PARAM_ALIASES)
111 for spec in core.BC_HANDLER_SPECS.values():
112 accepted |= spec["required_params"] | spec["optional_params"]
113 problems.extend(f"boundary_conditions[].params.{key}"
114 for key in sorted(accepted - set(core.BC_PARAM_QUANTITIES)))
115
116 generators = BUILTIN_IC_GENERATORS | core._PYTHON_INITIAL_CONDITION_PROVIDERS
117 problems.extend(f"initial_conditions.generator {name}"
118 for name in sorted(generators ^ set(core.IC_PARAM_QUANTITIES)))
119 ic_gen = load_module("audit_units_ic_gen", IC_GENERATOR)
120 provider_params = {
121 "spectral_random_velocity": core.SPECTRAL_RANDOM_VELOCITY_PARAMS,
122 "channel_spectral_velocity": ic_gen.WALL_SPECTRAL_PARAMS,
123 "duct_spectral_velocity": ic_gen.WALL_SPECTRAL_PARAMS,
124 }
125 for generator, keys in provider_params.items():
126 roots = {name.split(".", 1)[0] for name in core.IC_PARAM_QUANTITIES.get(generator, {})}
127 problems.extend(f"initial_conditions.params.{key} ({generator})" for key in sorted(set(keys) - roots))
128 return problems
129
130
Here is the call graph for this function:
Here is the caller graph for this function:

◆ dimension_name()

str audit_units.dimension_name (   core,
  dimension 
)

Name a dimension triple by its constant in the conductor core.

Parameters
[in]coreLoaded conductor core.
[in]dimension(length, velocity, density) exponents.
Returns
Constant name, e.g. VELOCITY.

Definition at line 131 of file audit_units.py.

131def dimension_name(core, dimension) -> str:
132 """!
133 @brief Name a dimension triple by its constant in the conductor core.
134 @param[in] core Loaded conductor core.
135 @param[in] dimension `(length, velocity, density)` exponents.
136 @return Constant name, e.g. `VELOCITY`.
137 """
138 for name in ("DIMENSIONLESS", "LENGTH", "VELOCITY", "TIME", "WAVENUMBER", "VOLUME_FLUX",
139 "DIFFUSIVITY", "PRESSURE", "DENSITY", "DYNAMIC_VISCOSITY"):
140 if getattr(core, name) == dimension:
141 return name
142 raise ValueError(f"dimension {dimension} has no named constant")
143
144
Here is the caller graph for this function:

◆ physical_inputs()

dict audit_units.physical_inputs (   core)

Every input that carries a physical dimension, as the input index lists it.

Dimensionless and non-quantity inputs are omitted: there is nothing to convert. Payload inputs whose dimension follows their content are listed with dimension PAYLOAD.

Parameters
[in]coreLoaded conductor core.
Returns
Mapping of documented input name to (dimension name, conversion site).

Definition at line 145 of file audit_units.py.

145def physical_inputs(core) -> dict:
146 """!
147 @brief Every input that carries a physical dimension, as the input index lists it.
148 @details Dimensionless and non-quantity inputs are omitted: there is nothing to
149 convert. Payload inputs whose dimension follows their content are listed
150 with dimension `PAYLOAD`.
151 @param[in] core Loaded conductor core.
152 @return Mapping of documented input name to `(dimension name, conversion site)`.
153 """
154 rows = {}
155
156 def record(name, quantity):
157 """!
158 @brief Add one input to the index when it carries something to convert.
159 @param[in] name Documented input name.
160 @param[in] quantity `(dimension, site)` entry.
161 """
162 dimension, site = quantity
163 if site in ("", "passthrough"):
164 return
165 if dimension is not None and dimension == core.DIMENSIONLESS:
166 return
167 rows[name] = ("PAYLOAD" if dimension is None else dimension_name(core, dimension), site)
168
169 for path, quantity in core.INPUT_QUANTITIES.items():
170 record(f"{path[0]}.yml: " + ".".join(path[1:]), quantity)
171 for key, quantity in core.BC_PARAM_QUANTITIES.items():
172 record(f"case.yml: boundary_conditions[].params.{key}", quantity)
173 for generator, table in core.IC_PARAM_QUANTITIES.items():
174 for key, quantity in table.items():
175 record(f"case.yml: initial_conditions.params.{key} ({generator})", quantity)
176 return rows
177
178
Here is the call graph for this function:
Here is the caller graph for this function:

◆ compiled_field_dimensions()

dict audit_units.compiled_field_dimensions ( )

Field name to (catalog, FIELD_DIM_* suffix) for every compiled entry.

Returns
Mapping of canonical field names to their catalog and dimension.
Exceptions
ValueErrorwhen an entry carries no dimension argument.

Definition at line 179 of file audit_units.py.

179def compiled_field_dimensions() -> dict:
180 """!
181 @brief Field name to `(catalog, FIELD_DIM_* suffix)` for every compiled entry.
182 @return Mapping of canonical field names to their catalog and dimension.
183 @throws ValueError when an entry carries no dimension argument.
184 """
185 fields = {}
186 for catalog, path in (("Eulerian", EULERIAN_CATALOG), ("particle", PARTICLE_CATALOG)):
187 text = path.read_text(encoding="utf-8")
188 body = text[text.index("gFieldCatalog" if catalog == "Eulerian" else "gParticleFieldCatalog"):]
189 for match in ENTRY_RE.finditer(body):
190 depth, index = 1, match.end()
191 while depth:
192 depth += {"(": 1, ")": -1}.get(body[index], 0)
193 index += 1
194 entry = body[match.end():index - 1]
195 name = NAME_RE.search(entry).group(1)
196 dimension = DIMENSION_RE.search(entry)
197 if dimension is None:
198 raise ValueError(f"{catalog} field '{name}' has no FIELD_DIM_* argument")
199 fields[(catalog, name)] = dimension.group(1)
200 return fields
201
202
Here is the caller graph for this function:

◆ page_section()

str audit_units.page_section ( str  heading)

One @section body of page 19.

Parameters
[in]headingSection anchor to extract.
Returns
Section text up to the next section.

Definition at line 203 of file audit_units.py.

203def page_section(heading: str) -> str:
204 """!
205 @brief One `@section` body of page 19.
206 @param[in] heading Section anchor to extract.
207 @return Section text up to the next section.
208 """
209 text = PAGE.read_text(encoding="utf-8")
210 start = text.index(f"@section {heading}")
211 following = text.find("@section", start + 1)
212 return text[start: following if following != -1 else len(text)]
213
214
Here is the caller graph for this function:

◆ documented_rows()

dict audit_units.documented_rows ( str  heading)

Rows of the first table in a section: first column to second and third.

Parameters
[in]headingSection anchor.
Returns
Mapping of backticked first-column text to the next two cells.

Definition at line 215 of file audit_units.py.

215def documented_rows(heading: str) -> dict:
216 """!
217 @brief Rows of the first table in a section: first column to second and third.
218 @param[in] heading Section anchor.
219 @return Mapping of backticked first-column text to the next two cells.
220 """
221 return {(first, second): third
222 for first, second, third in INDEX_ROW_RE.findall(page_section(heading))}
223
224
Here is the call graph for this function:
Here is the caller graph for this function:

◆ main()

int audit_units.main ( )

Fail when an input lacks a dimension or an index disagrees with the code.

Returns
Process status code.

Definition at line 225 of file audit_units.py.

225def main() -> int:
226 """!
227 @brief Fail when an input lacks a dimension or an index disagrees with the code.
228 @return Process status code.
229 """
230 core = load_module("audit_units_core", CORE)
231 problems = [f"no recorded dimension: {name}" for name in uncovered_inputs(core)]
232
233 inputs = physical_inputs(core)
234 documented_inputs = {name: (dimension, site)
235 for (name, dimension), site in documented_rows("p19_inputs_sec").items()}
236 for name in sorted(set(inputs) - set(documented_inputs)):
237 problems.append(f"input index: '{name}' is physical but not listed")
238 for name in sorted(set(documented_inputs) - set(inputs)):
239 problems.append(f"input index: '{name}' is listed but records nothing to convert")
240 for name in sorted(set(inputs) & set(documented_inputs)):
241 if inputs[name] != documented_inputs[name]:
242 problems.append(f"input index: '{name}' records {inputs[name]}, page says {documented_inputs[name]}")
243
244 fields = compiled_field_dimensions()
245 documented_fields = {(catalog, name): dimension
246 for (name, catalog), dimension in documented_rows("p19_fields_sec").items()}
247 for key in sorted(set(fields) - set(documented_fields)):
248 problems.append(f"field index: {key[0]} field '{key[1]}' is not listed")
249 for key in sorted(set(documented_fields) - set(fields)):
250 problems.append(f"field index: {key[0]} field '{key[1]}' is not in the compiled catalog")
251 for key in sorted(set(fields) & set(documented_fields)):
252 if fields[key] != documented_fields[key]:
253 problems.append(f"field index: {key[1]} is FIELD_DIM_{fields[key]}, page says {documented_fields[key]}")
254
255 if problems:
256 print("Units rule or its published indexes do not match the code:", file=sys.stderr)
257 for problem in problems:
258 print(f" {problem}", file=sys.stderr)
259 print("\nRecord a new input in picurv_cli/core.py INPUT_QUANTITIES (or BC_PARAM_QUANTITIES /\n"
260 "IC_PARAM_QUANTITIES), give a new field a FIELD_DIM_* argument, and update the\n"
261 "indexes in docs/pages/19_Nondimensionalization.md.", file=sys.stderr)
262 return 1
263 print(f"Units audit passed: every solver-facing input records a dimension; {len(inputs)} "
264 f"physical inputs and {len(fields)} catalogued fields match page 19. Whether each "
265 "conversion is performed is checked by the ingress tests and smoke run, not here.")
266 return 0
267
268
int main(int argc, char **argv)
Entry point for the postprocessor executable.
Here is the call graph for this function:
Here is the caller graph for this function:

Variable Documentation

◆ REPO_ROOT

audit_units.REPO_ROOT = Path(__file__).resolve().parents[2]

Definition at line 26 of file audit_units.py.

◆ CORE

str audit_units.CORE = REPO_ROOT / "picurv_cli" / "core.py"

Definition at line 27 of file audit_units.py.

◆ IC_GENERATOR

str audit_units.IC_GENERATOR = REPO_ROOT / "generators" / "ic.gen"

Definition at line 28 of file audit_units.py.

◆ EULERIAN_CATALOG

str audit_units.EULERIAN_CATALOG = REPO_ROOT / "src" / "field_catalog.c"

Definition at line 29 of file audit_units.py.

◆ PARTICLE_CATALOG

str audit_units.PARTICLE_CATALOG = REPO_ROOT / "src" / "particle_field_catalog.c"

Definition at line 30 of file audit_units.py.

◆ PAGE

str audit_units.PAGE = REPO_ROOT / "docs" / "pages" / "19_Nondimensionalization.md"

Definition at line 31 of file audit_units.py.

◆ SOLVER_INPUT_SCHEMAS

dict audit_units.SOLVER_INPUT_SCHEMAS
Initial value:
1= {
2 "case": "_CASE_SCHEMA",
3 "solver": "_SOLVER_SCHEMA",
4 "monitor": "_MONITOR_SCHEMA",
5 "post": "_POST_SCHEMA",
6}

Definition at line 35 of file audit_units.py.

◆ DEDICATED_TABLE_SURFACES

dict audit_units.DEDICATED_TABLE_SURFACES
Initial value:
1= {
2 ("case", "boundary_conditions", "[]", "params"),
3 ("case", "boundary_conditions", "[]", "[]", "params"),
4 ("case", "properties", "initial_conditions", "params"),
5}

Definition at line 43 of file audit_units.py.

◆ BUILTIN_IC_GENERATORS

dict audit_units.BUILTIN_IC_GENERATORS = {"zero", "constant", "streamwise_constant", "poiseuille"}

Definition at line 50 of file audit_units.py.

◆ ENTRY_RE

audit_units.ENTRY_RE = re.compile(r"\b(?:FIELD_ENTRY|FIELD_COORDINATE_ENTRY|PARTICLE_FIELD_ENTRY)\‍(")

Definition at line 52 of file audit_units.py.

◆ DIMENSION_RE

audit_units.DIMENSION_RE = re.compile(r"FIELD_DIM_(\w+)")

Definition at line 53 of file audit_units.py.

◆ NAME_RE

audit_units.NAME_RE = re.compile(r'"([^"]+)"')

Definition at line 54 of file audit_units.py.

◆ INDEX_ROW_RE

audit_units.INDEX_ROW_RE = re.compile(r"^\|\s*`([^`]+)`\s*\|\s*(\w+)\s*\|\s*`?(\w+)`?\s*\|", re.M)

Definition at line 55 of file audit_units.py.