PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
audit_field_catalog.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Enforce that the documented field catalog matches the compiled one."""
3
4from __future__ import annotations
5
6import re
7import sys
8from pathlib import Path
9
10
11REPO_ROOT = Path(__file__).resolve().parents[2]
12EULERIAN_CATALOG = REPO_ROOT / "src" / "field_catalog.c"
13PARTICLE_CATALOG = REPO_ROOT / "src" / "particle_field_catalog.c"
14LAYOUT_HEADER = REPO_ROOT / "include" / "field_catalog.h"
15PAGE = REPO_ROOT / "docs" / "pages" / "56_Field_Identity_and_Layout_Catalog.md"
16
17# Section 6 groups the catalog by layout; each bullet label names one enum value.
18GROUP_LAYOUTS = {
19 "node-centered": "FIELD_LAYOUT_NODE_CENTERED",
20 "shifted cell-centered": "FIELD_LAYOUT_CELL_CENTERED",
21 "component-staggered": "FIELD_LAYOUT_COMPONENT_STAGGERED",
22 "I-face family": "FIELD_LAYOUT_I_FACE",
23 "J-face family": "FIELD_LAYOUT_J_FACE",
24 "K-face family": "FIELD_LAYOUT_K_FACE",
25}
26
27ENTRY_RE = re.compile(
28 r'FIELD_(?:COORDINATE_)?ENTRY\‍(\s*FIELD_ID_\w+\s*,\s*"([^"]+)"'
29 r'[^)]*?(FIELD_LAYOUT_\w+)',
30 re.S,
31)
32PARTICLE_ENTRY_RE = re.compile(r'PARTICLE_FIELD_ENTRY\‍(\s*PARTICLE_FIELD_ID_\w+\s*,\s*"([^"]+)"')
33LAYOUT_ENUM_RE = re.compile(r"^\s*(FIELD_LAYOUT_\w+)", re.M)
34BACKTICKED_RE = re.compile(r"`([A-Za-z_][A-Za-z0-9_]*)`")
35
36
37def compiled_eulerian() -> dict:
38 """!
39 @brief Canonical name to layout for every compiled Eulerian catalog entry.
40 @return Mapping of field name to `FIELD_LAYOUT_*` value.
41 """
42 return dict(ENTRY_RE.findall(EULERIAN_CATALOG.read_text(encoding="utf-8")))
43
44
45def compiled_particle() -> set:
46 """!
47 @brief Canonical names of every compiled particle catalog entry.
48 @return Set of particle field names.
49 """
50 return set(PARTICLE_ENTRY_RE.findall(PARTICLE_CATALOG.read_text(encoding="utf-8")))
51
52
53def compiled_layouts() -> set:
54 """!
55 @brief Layout vocabulary the header defines.
56 @return Set of `FIELD_LAYOUT_*` enumerators.
57 """
58 text = LAYOUT_HEADER.read_text(encoding="utf-8")
59 body = text[text.index("FIELD_LAYOUT_NODE_CENTERED"):]
60 return set(LAYOUT_ENUM_RE.findall(body[: body.index("} FieldLayout;")]))
61
62
63def page_section(heading: str) -> str:
64 """!
65 @brief One `@section` body from the catalog page.
66 @param[in] heading Section id to extract.
67 @return Section text up to the next section.
68 """
69 text = PAGE.read_text(encoding="utf-8")
70 start = text.index(heading)
71 following = text.find("@section", start + len(heading))
72 return text[start: following if following != -1 else len(text)]
73
74
75def documented_eulerian(section: str) -> tuple:
76 """!
77 @brief Field-to-layout mapping the inventory section publishes.
78 @param[in] section Inventory section body.
79 @return Tuple of the mapping and any structural problems found.
80 """
81 problems: list = []
82 block = re.search(r"((?:^- .*(?:\n(?!\n)(?!- ).*)*\n)+)", section, re.M)
83 if not block:
84 return {}, ["section 6 no longer contains a layout-grouped list"]
85 documented: dict = {}
86 for item in re.findall(r"^- (.*?)(?=^- |\Z)", block.group(1), re.S | re.M):
87 label, _, body = item.partition(":")
88 layout = GROUP_LAYOUTS.get(label.strip())
89 if layout is None:
90 problems.append(f"section 6 lists an unrecognized layout group '{label.strip()}'")
91 continue
92 for name in BACKTICKED_RE.findall(body):
93 documented[name] = layout
94 return documented, problems
95
96
97def documented_particle(section: str) -> set:
98 """!
99 @brief Particle field names the inventory section publishes.
100 @param[in] section Inventory section body.
101 @return Set of documented particle field names.
102 """
103 match = re.search(r"The particle inventory is:(.*?)\.\s", section, re.S)
104 return set(BACKTICKED_RE.findall(match.group(1))) if match else set()
105
106
108 """!
109 @brief Layout vocabulary the layout section tabulates.
110 @return Set of documented `FIELD_LAYOUT_*` enumerators.
111 """
112 section = page_section("@section p56_layout_sec")
113 return set(re.findall(r"\|\s*`(FIELD_LAYOUT_\w+)`\s*\|", section))
114
115
116def main() -> int:
117 """!
118 @brief Fail when the published catalog inventory and the compiled catalog disagree.
119
120 @details Checks identity sets, layout assignment, and layout vocabulary. It does not
121 check degrees of freedom, DM family, synchronization class, availability,
122 capability flags, or whether the runtime actually allocates a field.
123 @return Process status code.
124 """
125 code = compiled_eulerian()
126 section = page_section("@section p56_inventory_sec")
127 documented, problems = documented_eulerian(section)
128
129 for name in sorted(set(code) - set(documented)):
130 problems.append(f"{name}: in the compiled catalog, absent from section 6")
131 for name in sorted(set(documented) - set(code)):
132 problems.append(f"{name}: listed in section 6, absent from the compiled catalog")
133 for name in sorted(set(code) & set(documented)):
134 if code[name] != documented[name]:
135 problems.append(
136 f"{name}: compiled as {code[name]}, documented under {documented[name]}"
137 )
138
139 header_layouts, page_layouts = compiled_layouts(), documented_layouts()
140 for layout in sorted(header_layouts - page_layouts):
141 problems.append(f"{layout}: defined in the header, absent from the section 4 table")
142 for layout in sorted(page_layouts - header_layouts):
143 problems.append(f"{layout}: tabulated in section 4, absent from the header")
144
145 particles_code, particles_page = compiled_particle(), documented_particle(section)
146 for name in sorted(particles_code - particles_page):
147 problems.append(f"{name}: in the compiled particle catalog, absent from section 6")
148 for name in sorted(particles_page - particles_code):
149 problems.append(f"{name}: listed as a particle field, absent from the compiled catalog")
150
151 if problems:
152 print("Field catalog documentation does not match the compiled catalog:", file=sys.stderr)
153 for problem in problems:
154 print(f" {problem}", file=sys.stderr)
155 print(
156 "\nUpdate docs/pages/56_Field_Identity_and_Layout_Catalog.md section 6 to match\n"
157 "src/field_catalog.c and src/particle_field_catalog.c. The compiled catalog is\n"
158 "authoritative; the page is the published record of it.",
159 file=sys.stderr,
160 )
161 return 1
162
163 print(
164 f"Field catalog audit passed: {len(code)} Eulerian and {len(particles_code)} particle "
165 f"identities, {len(header_layouts)} layouts. Degrees of freedom, DM family, "
166 "synchronization class, availability, capabilities, and runtime allocation are "
167 "not checked."
168 )
169 return 0
170
171
172if __name__ == "__main__":
173 raise SystemExit(main())
set documented_particle(str section)
Particle field names the inventory section publishes.
int main()
Fail when the published catalog inventory and the compiled catalog disagree.
set documented_layouts()
Layout vocabulary the layout section tabulates.
set compiled_particle()
Canonical names of every compiled particle catalog entry.
str page_section(str heading)
One @section body from the catalog page.
set compiled_layouts()
Layout vocabulary the header defines.
dict compiled_eulerian()
Canonical name to layout for every compiled Eulerian catalog entry.
tuple documented_eulerian(str section)
Field-to-layout mapping the inventory section publishes.