Skip to content

Commit 4196adb

Browse files
authored
Merge pull request #399 from errorcorrectionzoo/fix/codelist-membership-rule
AGENTS.md rule 14: check code lists; count_list_codes --code
2 parents 569980b + 82186e8 commit 4196adb

3 files changed

Lines changed: 104 additions & 12 deletions

File tree

‎AGENTS.md‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,23 @@ lint scripts `spellcheck.py`, `remove_trailing_block_apostrophes.py`, and
145145
`scripts/params/check_tableaux.py`.
146146
This is the routine-check list in `scripts/script_list_for_checking.txt`.
147147

148+
**14. Check the code lists after adding a code or changing its relations.** The
149+
lists in `codelists/` select codes mostly by descendancy and cousinhood (e.g.
150+
`descendant_of: color`). A code placed outside the expected branch therefore
151+
drops out of a list where it belongs, and nothing reports it. For each new or
152+
reparented code, run
153+
154+
```
155+
python3 scripts/codelists/count_list_codes.py --code <code_id>
156+
```
157+
158+
It prints the lists that contain the code, then the lists that contain one of
159+
its children or cousins but not the code itself. Go through the second
160+
group. Where the code belongs in a list, fix its relations or add it to that
161+
list's `select`, and say so in the PR. E.g. `3d_xyz_color` has no color-code
162+
ancestor, so `list_color` (descendants of `color` and `twist_defect_color`)
163+
misses it.
164+
148165
**15. Explicit codes with \(d\leq 5\) get a small-distance parent.** An entry
149166
for one explicit code whose distance is at most five also lists a
150167
small-distance family as a parent. Use `small_distance_qubit_stabilizer` for
@@ -265,7 +282,9 @@ describing its exact behavior and usage.
265282
- `scripts/bib/` — bibliography/citation tooling: extract arXiv IDs, DOIs,
266283
manual cites and presets; append reference counts.
267284
- `scripts/queries/` — external lookups: `query_qecdb`, `semantic_scholar_recommend`.
268-
- `scripts/codelists/` — code-list tooling: `count_list_codes`.
285+
- `scripts/codelists/` — code-list tooling: `count_list_codes` (list sizes; with
286+
`--code <code_id>`, the lists that contain a code and the lists that miss it
287+
while containing a relative, per rule 14).
269288
- `scripts/params/` — code-parameter checks (Python >= 3.10 and `PyYAML` only).
270289
`check_tableaux.py` (routine) reads every Pauli tableau displayed in an entry
271290
(`array`/`smallmatrix` blocks with I/X/Y/Z cells, and "cyclic permutations of

‎codelists/list_all.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ codes:
5454

5555
# Possible predicate types to be included in each "select" code set include:
5656
#
57-
# manual_code_ids: ['code_id_1', 'code_id_2', ...]
57+
# manual_code_list: ['code_id_1', 'code_id_2', ...]
5858
#
5959
# exclude: ['code_id_1', 'code_id_2', ...]
6060
#

‎scripts/codelists/count_list_codes.py‎

Lines changed: 83 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@
1111
# python3 scripts/codelists/count_list_codes.py # sort by count descending
1212
# python3 scripts/codelists/count_list_codes.py --sort count --reverse
1313
# python3 scripts/codelists/count_list_codes.py --sort properties
14+
# python3 scripts/codelists/count_list_codes.py --code 3d_xyz_color
15+
#
16+
# With --code X, print instead the lists that contain X, followed by the lists
17+
# that contain a child or cousin of X but not X itself. Check that
18+
# second group after adding or reparenting a code: it is where a code is most
19+
# often missed (AGENTS.md rule 14).
1420
#
1521
# Codelist select semantics – each select entry is a condition group.
1622
# Within a group all conditions are ANDed; between groups they are ORed.
@@ -22,7 +28,7 @@
2228
# cousin_of: X – candidates = cousin-neighbours of X (both dirs)
2329
# property_set: a.b – candidates = codes whose YAML has the dotted key set
2430
# domain: D – candidates = codes in domain D
25-
# manual_code_ids: [A,B] – explicit code list
31+
# manual_code_list: [A,B] – explicit code list
2632
# not_descendant_of: X – remove descendants of X from candidates
2733
# not_all_descendant_of: [A,B] – remove codes that are descendants of ALL of A,B
2834

@@ -227,7 +233,7 @@ def _descendants(roots: list[str], p2c: dict[str, list[str]]) -> set[str]:
227233
"any_cousin_of", "all_cousin_of",
228234
"not_any_cousin_of", "not_all_cousin_of",
229235
"any_domain", "not_any_domain",
230-
"manual_code_ids", "exclude",
236+
"manual_code_list", "exclude",
231237
})
232238

233239
_SELECTORS = (
@@ -239,7 +245,7 @@ def _descendants(roots: list[str], p2c: dict[str, list[str]]) -> set[str]:
239245
"cousin_of",
240246
"property_set",
241247
"not_any_domain", "any_domain", "not_domain", "domain",
242-
"manual_code_ids", "exclude",
248+
"manual_code_list", "exclude",
243249
)
244250

245251

@@ -388,7 +394,7 @@ def _add(group: dict[str, list[str]], sel: str, raw_val: str) -> None:
388394
"cousin_of", "any_cousin_of", "all_cousin_of",
389395
"property_set",
390396
"domain", "any_domain",
391-
"manual_code_ids",
397+
"manual_code_list",
392398
})
393399

394400
_NEGATIVE_KEYS = frozenset({
@@ -446,16 +452,16 @@ def _inter_cousin(cids: list[str], cout: dict, cin: dict, known: set,
446452
return result if result is not None else set()
447453

448454

449-
def count_codes(
455+
def list_members(
450456
groups: list[dict[str, list[str]]],
451457
p2c: dict[str, list[str]],
452458
cout: dict[str, set[str]],
453459
cin: dict[str, set[str]],
454460
code_props: dict[str, frozenset[str]],
455461
code_domain: dict[str, str],
456462
known: set[str],
457-
) -> tuple[int, list[str]]:
458-
"""Return (count, warnings)."""
463+
) -> tuple[set[str], list[str]]:
464+
"""Return (set of code_ids in the list, warnings)."""
459465
accumulated: set[str] = set()
460466
global_exclude: set[str] = set()
461467
warnings: list[str] = []
@@ -539,8 +545,8 @@ def _ix(s: set[str]) -> None:
539545
dom_set = set(group["any_domain"])
540546
_ix({c for c, d in code_domain.items() if d in dom_set})
541547

542-
if "manual_code_ids" in group:
543-
_ix({cid for cid in group["manual_code_ids"] if cid in known})
548+
if "manual_code_list" in group:
549+
_ix({cid for cid in group["manual_code_list"] if cid in known})
544550

545551
if candidates is None:
546552
candidates = set()
@@ -566,7 +572,22 @@ def _ix(s: set[str]) -> None:
566572

567573
accumulated |= candidates
568574

569-
return len(accumulated - global_exclude), warnings
575+
return accumulated - global_exclude, warnings
576+
577+
578+
def count_codes(
579+
groups: list[dict[str, list[str]]],
580+
p2c: dict[str, list[str]],
581+
cout: dict[str, set[str]],
582+
cin: dict[str, set[str]],
583+
code_props: dict[str, frozenset[str]],
584+
code_domain: dict[str, str],
585+
known: set[str],
586+
) -> tuple[int, list[str]]:
587+
"""Return (count, warnings)."""
588+
members, warnings = list_members(
589+
groups, p2c, cout, cin, code_props, code_domain, known)
590+
return len(members), warnings
570591

571592

572593
# ---------------------------------------------------------------------------
@@ -599,9 +620,57 @@ def _parse_args() -> argparse.Namespace:
599620
"codelists/features relative to the repo root."
600621
),
601622
)
623+
parser.add_argument(
624+
"--code",
625+
default=None,
626+
metavar="CODE_ID",
627+
help=(
628+
"Print the lists that contain CODE_ID, then the lists that contain "
629+
"a child or cousin of CODE_ID but not CODE_ID itself."
630+
),
631+
)
602632
return parser.parse_args()
603633

604634

635+
def report_code(
636+
code_id: str,
637+
list_files: list[Path],
638+
repo_root: Path,
639+
p2c: dict[str, list[str]],
640+
cout: dict[str, set[str]],
641+
cin: dict[str, set[str]],
642+
code_props: dict[str, frozenset[str]],
643+
code_domain: dict[str, str],
644+
known: set[str],
645+
) -> int:
646+
if code_id not in known:
647+
print(f"Unknown code_id '{code_id}'", file=sys.stderr)
648+
return 1
649+
relatives = set(p2c.get(code_id, [])) | cout.get(code_id, set()) \
650+
| cin.get(code_id, set())
651+
inside: list[tuple[str, str]] = []
652+
missing: list[tuple[str, str, list[str]]] = []
653+
for lf in list_files:
654+
title, groups, _cols = _parse_codelist(str(lf))
655+
members, _w = list_members(
656+
groups, p2c, cout, cin, code_props, code_domain, known)
657+
rel = str(lf.relative_to(repo_root))
658+
if code_id in members:
659+
inside.append((rel, title))
660+
else:
661+
hits = sorted(relatives & members)
662+
if hits:
663+
missing.append((rel, title, hits))
664+
print(f"Lists containing {code_id} ({len(inside)}):")
665+
for rel, title in inside:
666+
print(f" {title} [{rel}]")
667+
print(f"\nLists containing a relative of {code_id} but not {code_id} "
668+
f"({len(missing)}); check whether it belongs:")
669+
for rel, title, hits in missing:
670+
print(f" {title} [{rel}] via {', '.join(hits)}")
671+
return 0
672+
673+
605674
def main() -> int:
606675
args = _parse_args()
607676

@@ -627,6 +696,10 @@ def main() -> int:
627696
print(f"No YAML files found under {lists_dirs}", file=sys.stderr)
628697
return 1
629698

699+
if args.code:
700+
return report_code(args.code, list_files, repo_root, p2c, cout, cin,
701+
code_props, code_domain, known)
702+
630703
print(f"Total number of lists: {len(list_files)}\n")
631704

632705
rows: list[tuple[str, str, int, list[str]]] = []

0 commit comments

Comments
 (0)