Skip to content

Commit dd1a240

Browse files
authored
Refactor: structured INPUT parameter availability (Phase 1) (deepmodeling#7783)
* Tools: add structured parser for INPUT availability strings The Input_Item availability field mixes prose and ad-hoc conditions as free text, which cannot be consumed programmatically for tree-structured docs, validation or agent tooling. Add a small dependency-free parser (tools/03_code_analysis/availability_parser.py) that normalises the historical spellings (==, =, 'is set to', 'contains') onto a canonical form and classifies each value as an Expression, a bare Label, or Unstructured prose waiting for review. - availability_parser.py: parse_availability() -> Availability - test_availability_parser.py: unit tests - generate_input_main.py: add --check-availability to report the Expression/Label/Unstructured distribution without changing the generated markdown. On the current docs/parameters.yaml this classifies 217 non-empty values as 82 Expression / 84 Label / 51 Unstructured. * feat(input): structured availability for INPUT parameters (Phase 1) Make the Input_Item availability field a concrete, machine-readable boolean condition (single source of truth) so the INPUT docs, validation and tooling can consume the actual condition instead of free text or bare tags. - Add AvailabilityKind/AvailabilityCondition/AvailabilityExpr and parse_availability() in a new module (availability.{h,cpp}, wired into CMake). - Input_Item now carries availability_kind + availability_expr and a set_availability() helper that keeps the canonical string and the structured form in sync (single source of truth). - Rewrite all 217 non-empty availability registrations to canonical boolean syntax; the exported YAML classifies them as 216 Expression / 0 Unstructured. - Export the structured fields from --generate-parameters-yaml (input_help.cpp). - Consume the structured fields in generate_input_main.py; extend availability_parser.py to the canonical grammar and cover it with tests. - Regenerate docs/parameters.yaml and input-main.md. Former bare "label"-style tags (e.g. "OFDFT", "Numerical atomic orbital basis") are now expressed as concrete conditions (esolver_type==ofdft, basis_type==lcao, ...) so they can be evaluated by validation/error tooling. mixing_tau has no hard gate, so its availability is empty (always available); its meta-GGA relevance is kept in the description. * fix(input): enforce canonical availability metadata * refactor(input): drop redundant python availability tooling The strict C++ parse_availability() in Input_Item::set_availability() is now the single source of truth for the availability grammar (it throws on any non-canonical non-empty string). The legacy python classifier, its unit tests, and the --check-availability report plumbing in generate_input_main.py were an earlier design step and are no longer referenced by any workflow; remove them to keep a single grammar implementation. * fix(input): harden availability metadata * docs(input): keep review context out of repository * docs(input): generalize availability guide * fix(input): validate availability prerequisites * fix(input): quote multi-token availability values * fix(input): standardize vector containment syntax * fix(input): keep contains for vector availability * fix(build): include availability objects in Makefile * fix(input): align mixing_dmr availability --------- Co-authored-by: Stardust0831 <169599847+Stardust0831@users.noreply.github.com>
1 parent 65a5039 commit dd1a240

33 files changed

Lines changed: 1930 additions & 901 deletions

docs/CONTRIBUTING.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,12 @@ An practical example is class [LCAO_Deepks](https://github.com/deepmodeling/abac
172172
173173
## Documenting INPUT Parameters
174174
175-
ABACUS includes a built-in help system that allows users to query INPUT parameters directly from the command line (e.g., `abacus -h ecutwfc`). Parameter metadata is defined inline in the C++ source files (`source/source_io/module_parameter/read_input_item_*.cpp`) using `Input_Item` registrations.
175+
ABACUS includes a built-in help system that allows users to query INPUT parameters directly from the command line (e.g., `abacus -h ecutwfc`). Parameter metadata is defined inline in the C++ source files under `source/source_io/module_parameter/` using `Input_Item` registrations.
176176
177-
A checked-in file `docs/parameters.yaml` contains a YAML dump of all parameter metadata, generated from the binary itself. This file is used by Sphinx to produce the online documentation page `input-main.md`.
177+
The C++ `Input_Item` registrations are the source of truth for parameter metadata. The checked-in `docs/parameters.yaml` and `docs/advanced/input_files/input-main.md` files are generated artifacts: do not edit either file manually. `parameters.yaml` is generated from the binary and is used by Sphinx to produce `input-main.md`.
178+
179+
Availability expressions follow the grammar and invariants in
180+
[`developers_guide/input_availability.md`](developers_guide/input_availability.md).
178181
179182
### When to Update `docs/parameters.yaml`
180183
@@ -198,13 +201,13 @@ Then verify the YAML is valid:
198201
python3 -c "import yaml; d=yaml.safe_load(open('docs/parameters.yaml')); print(len(d['parameters']), 'parameters')"
199202
```
200203

201-
You can also regenerate the markdown documentation locally:
204+
Then regenerate the markdown documentation locally:
202205

203206
```bash
204207
python3 docs/generate_input_main.py docs/parameters.yaml --output docs/advanced/input_files/input-main.md
205208
```
206209

207-
**Important:** Include the updated `docs/parameters.yaml` and `input-main.md` in your commit when submitting a PR that modifies INPUT parameters. Reviewers should verify the YAML changes match the C++ source changes and the `input-main.md` is updated.
210+
**Important:** Include the updated `docs/parameters.yaml` and `input-main.md` in your commit when submitting a PR that modifies INPUT parameters. CI regenerates both files from the built binary and rejects any mismatch. Do not fix a documentation mismatch by editing either generated file; update the C++ `Input_Item` registration and regenerate them instead.
208211

209212
### Parameter Documentation Format
210213

@@ -218,7 +221,7 @@ When adding or modifying INPUT parameters in C++ source, set the following field
218221
item.description = "Description of what this parameter does.";
219222
item.default_value = "0";
220223
item.unit = "Ry"; // Optional, empty string if no unit
221-
item.availability = ""; // Optional, empty string if always available
224+
item.set_availability("basis_type==pw"); // Optional; omit if always available
222225
// ... read_value, reset_value, check_value functions ...
223226
this->add_item(item);
224227
}

docs/advanced/input_files/input-main.md

Lines changed: 223 additions & 222 deletions
Large diffs are not rendered by default.

docs/developers_guide/agent_governance.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,10 @@ or parsing behavior should include both:
220220
- `docs/parameters.yaml`
221221
- `docs/advanced/input_files/input-main.md`
222222

223+
These files are generated artifacts, not additional sources of truth. Update
224+
the C++ `Input_Item` registrations and regenerate both files; do not edit the
225+
YAML or Markdown output by hand.
226+
223227
If the diff touches parameter internals but does not change user-visible INPUT
224228
behavior, the PR should state why no documentation update is required. Missing
225229
documentation updates trigger a governance warning (not a block), but maintainers

docs/developers_guide/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ This section provides guidelines and resources for developers working on the ABA
1010
:caption: Developer Resources
1111

1212
basic_types_class.md
13+
input_availability.md
1314
agent_governance.md
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Structured INPUT availability
2+
3+
## Purpose
4+
5+
Input item availability metadata describes when an INPUT parameter is
6+
applicable. It is used by documentation and tooling; it does not reject or
7+
alter a user's INPUT based on this condition. Runtime diagnostics, if added,
8+
must define how explicitly supplied parameters whose conditions are false are
9+
handled.
10+
11+
## Invariants
12+
13+
- The C++ `Input_Item` registration is the source of truth. YAML and Markdown
14+
are generated artifacts.
15+
- Every non-empty registration is canonical. `set_availability()` rejects
16+
syntax errors and non-canonical spelling.
17+
- The AST is the stored representation; the exported string is serialized from
18+
it, so the two forms cannot diverge.
19+
- Every expression is a complete, independently evaluable predicate. It must
20+
include enclosing requirements rather than inheriting them implicitly from a
21+
referenced parameter.
22+
- After all INPUT items are registered, every referenced label, operator and
23+
literal is checked against machine-readable parameter type information.
24+
25+
## Grammar and meaning
26+
27+
```text
28+
expression := or-expression
29+
or-expression := and-expression ("or" and-expression)*
30+
and-expression := primary (("and" | ",") primary)*
31+
primary := condition | "(" expression ")"
32+
condition := parameter comparison value
33+
| parameter "in" "[" value "," value ("," value)* "]"
34+
| parameter "contains" value
35+
comparison := "==" | "!=" | ">" | ">=" | "<" | "<="
36+
value := token | '"' quoted-value '"'
37+
```
38+
39+
`and` binds more tightly than `or`. `==` compares one complete value; double
40+
quotes delimit a complete value containing whitespace, such as
41+
`relax_method=="cg 2"`. Two or more alternatives use `in [...]`, while
42+
`parameter contains value` tests whether a vector contains one element, such as
43+
`td_ttype contains 0`. `/` is an ordinary value character, not another spelling
44+
of membership. Ordered comparisons require a numeric scalar.
45+
46+
A path that references a parameter must imply that parameter's availability.
47+
Every `and` operand is required, while satisfying either branch of an `or` is
48+
sufficient. Repeated `and` or `or` groups are order-independent. Different leaf
49+
conditions are not related; for example, `mode==a` does not satisfy
50+
`mode in [a, b]`.
51+
52+
Examples:
53+
54+
```cpp
55+
item.set_availability("basis_type==pw");
56+
item.set_availability("vdw_method in [d2, d3_0]");
57+
item.set_availability("td_ttype contains 2");
58+
item.set_availability("esolver_type==sdft and method_sto==2");
59+
```
60+
61+
## Registration and validation workflow
62+
63+
1. Parse and require canonical spelling in `set_availability()`.
64+
2. Finish registering all `Input_Item` objects.
65+
3. Validate referenced parameter names, operator compatibility, literal values, and that every referenced parameter carries its own enclosing requirements on the referencing path.
66+
4. Serialize the AST into `docs/parameters.yaml`.
67+
5. Generate `docs/advanced/input_files/input-main.md` from that YAML.
68+
69+
Runtime evaluation is outside this metadata contract. Any implementation must
70+
define evaluation timing, treatment of defaults and reset values, and warning
71+
behavior for explicitly supplied parameters.

docs/generate_input_main.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,28 @@ def format_description(desc: str) -> str:
140140
return result.strip()
141141

142142

143-
def generate_parameter_markdown(param: Dict[str, str]) -> str:
143+
def link_availability(availability: str, name_anchors: Dict[str, str]) -> str:
144+
"""
145+
Link parameter names on the left side of a condition.
146+
147+
This deliberately recognizes the condition boundary instead of linking
148+
every identifier: a value such as ``pw`` may happen to have the same name
149+
as another parameter, but it is not a parameter reference.
150+
"""
151+
def replace(match):
152+
name = match.group(0)
153+
anchor = name_anchors.get(name)
154+
if anchor:
155+
return f"[`{name}`]({anchor})"
156+
return name
157+
left_parameter = (
158+
r'\b[A-Za-z_][A-Za-z0-9_]*\b'
159+
r'(?=\s*(?:==|!=|>=|<=|>|<|\bin\b|\bcontains\b))'
160+
)
161+
return re.sub(left_parameter, replace, availability)
162+
163+
def generate_parameter_markdown(param: Dict[str, str],
164+
name_anchors: Dict[str, str]) -> str:
144165
"""
145166
Generate markdown for a single parameter.
146167
"""
@@ -153,8 +174,9 @@ def generate_parameter_markdown(param: Dict[str, str]) -> str:
153174

154175
# Availability (before description, as in original format)
155176
if param.get('availability', '') != '':
156-
availability_text = escape_md_text(str(param['availability']))
157-
lines.append(f"- **Availability**: *{availability_text}*")
177+
lines.append("- **Availability**: *"
178+
+ link_availability(str(param['availability']), name_anchors)
179+
+ "*")
158180

159181
# Description
160182
if param.get('description', '') != '':
@@ -184,14 +206,15 @@ def generate_parameter_markdown(param: Dict[str, str]) -> str:
184206
return '\n'.join(lines)
185207

186208

187-
def generate_category_markdown(category: str, params: List[Dict[str, str]]) -> str:
209+
def generate_category_markdown(category: str, params: List[Dict[str, str]],
210+
name_anchors: Dict[str, str]) -> str:
188211
"""
189212
Generate markdown for a category section.
190213
"""
191214
lines = [f"## {category}", ""]
192215

193216
for param in params:
194-
lines.append(generate_parameter_markdown(param))
217+
lines.append(generate_parameter_markdown(param, name_anchors))
195218

196219
# Keep legacy navigation aid used by downstream tooling/rendering.
197220
lines.append("[back to top](#full-list-of-input-keywords)")
@@ -266,6 +289,9 @@ def generate(yaml_path: Path, output: Path, verbose: bool = False):
266289
if cat not in sorted_categories:
267290
sorted_categories[cat] = by_category[cat]
268291

292+
name_anchors = {param['name']: '#' + generate_anchor(param['name'])
293+
for param in all_params}
294+
269295
# Generate markdown
270296
md_parts = [
271297
"# Full List of INPUT Keywords",
@@ -281,7 +307,7 @@ def generate(yaml_path: Path, output: Path, verbose: bool = False):
281307
for category, params in sorted_categories.items():
282308
if verbose:
283309
print(f"Category '{category}': {len(params)} parameters")
284-
md_parts.append(generate_category_markdown(category, params))
310+
md_parts.append(generate_category_markdown(category, params, name_anchors))
285311

286312
# Write output
287313
output_content = '\n'.join(md_parts)

0 commit comments

Comments
 (0)