mirror of
https://github.com/allaunthefox/BioSight.git
synced 2026-07-30 18:56:17 +00:00
Every function in all 5 phi modules now has: - Google-style docstring with Args/Returns/Examples - Verified behavior via doctest examples (46 total) - Self-verification assertion blocks on direct execution Verification results: charclass: 10 doctests, 16 assertions — PASS ast_parse: 21 doctests, 7 assertions — PASS consistency: 6 assertions — PASS embed: multiple assertions — PASS output: 15 doctests, 16 assertions — PASS CLI wrapper: 3 checks — PASS End-to-end: 9 equation domains — PASS Also added: - .opencode/opencode.jsonc (gemma4 MCP config) - phi/AGENTS.md (module-level contract) - phi/test_phi.py (unittest test file) - phi/encoding_rationale.md (design docs) - dag/ (project dependency graph) - harness/ (Lean formalism constraints) - 7-Pipeline/ (rigour verification harness) Build: python3 -W error -m py_compile — 0 warnings
169 lines
6.6 KiB
Python
169 lines
6.6 KiB
Python
"""
|
|
phi.consistency — Layer 4: 6 consistency rules → ADMIT / QUARANTINE
|
|
|
|
Each equation is checked against 6 rules. All must pass for the
|
|
equation to be ADMITted. The results are embedded in the DNA as
|
|
G (pass) / T (fail) bases for allele-specific PCR filtering.
|
|
|
|
Dependencies: phi.ast_parse (for tree-based checks).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import ast
|
|
import os
|
|
import re
|
|
import sys
|
|
from typing import Dict, Optional
|
|
|
|
# Allow direct execution without package context
|
|
if not __package__:
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)) or ".")
|
|
from ast_parse import parse_to_ast
|
|
else:
|
|
from .ast_parse import parse_to_ast
|
|
|
|
# ── Well-known math function names (considered "defined references") ─────
|
|
|
|
KNOWN_MATH_NAMES = frozenset({
|
|
"sin", "cos", "tan", "log", "ln", "exp", "sqrt", "abs", "sum",
|
|
"min", "max", "floor", "ceil", "round", "sign", "arg", "norm",
|
|
"det", "trace", "tr", "diag", "vec", "mat", "rank", "dim",
|
|
"arccos", "arcsin", "arctan", "sinh", "cosh", "tanh",
|
|
"Re", "Im", "conj", "adj", "trans", "id",
|
|
})
|
|
|
|
# ── The 6 rules ──────────────────────────────────────────────────────────
|
|
|
|
CONSISTENCY_RULES = [
|
|
"balanced_parens", # Rule 1: parentheses must be balanced
|
|
"valid_operator_order", # Rule 2: no illegal consecutive operators
|
|
"valid_variable_name", # Rule 3: identifiers start with letter/underscore
|
|
"no_empty_expression", # Rule 4: equation is non-empty
|
|
"single_expression", # Rule 5: parseable as single AST expression
|
|
"defined_reference", # Rule 6: all names are known or short vars
|
|
]
|
|
|
|
RULE_ORDER = list(CONSISTENCY_RULES)
|
|
|
|
|
|
def check_consistency(equation: str) -> Dict[str, bool]:
|
|
"""Check all 6 consistency rules.
|
|
|
|
The 6 rules are:
|
|
1. balanced_parens — every '(' has a matching ')'
|
|
2. valid_operator_order — no illegal consecutive operator pairs
|
|
(e.g. +=, -=, */ are rejected; <=, >=, ==, !=, ** are allowed)
|
|
3. valid_variable_name — identifiers must start with a letter or
|
|
underscore (numeric prefixes like "1x" are rejected)
|
|
4. no_empty_expression — input must be non-empty after stripping
|
|
5. single_expression — must parse as a single Python AST expression
|
|
6. defined_reference — all names are ≤2 chars, start with '_',
|
|
contain '_', or are in KNOWN_MATH_NAMES
|
|
|
|
All True = ADMIT; any False = QUARANTINE.
|
|
|
|
Examples:
|
|
>>> check_consistency("x + 1")
|
|
{'balanced_parens': True, 'valid_operator_order': True, 'valid_variable_name': True, 'no_empty_expression': True, 'single_expression': True, 'defined_reference': True}
|
|
|
|
>>> r = check_consistency("(x + 1")
|
|
>>> r['balanced_parens']
|
|
False
|
|
"""
|
|
result: Dict[str, bool] = {}
|
|
|
|
# ── Rule 1: Balanced parentheses ──
|
|
depth = 0
|
|
for c in equation:
|
|
if c == "(":
|
|
depth += 1
|
|
elif c == ")":
|
|
depth -= 1
|
|
if depth < 0:
|
|
break
|
|
result["balanced_parens"] = depth == 0
|
|
|
|
# ── Rule 2: No illegal consecutive operators ──
|
|
ops = set("+-*/^=<>!")
|
|
valid_pairs = frozenset({"<=", ">=", "==", "!=", "**"})
|
|
bad = False
|
|
for i in range(len(equation) - 1):
|
|
pair = equation[i:i+2]
|
|
if equation[i] in ops and equation[i + 1] in ops:
|
|
if pair not in valid_pairs:
|
|
bad = True
|
|
break
|
|
result["valid_operator_order"] = not bad
|
|
|
|
# ── Rule 3: Identifiers start with letter or underscore ──
|
|
tokens = re.findall(r"[A-Za-z_]\w*|\d+|[^\w\s]", equation)
|
|
bad_vars = any(
|
|
re.match(r"^\d", t) and not re.match(r"^\d+$", t)
|
|
for t in tokens
|
|
)
|
|
result["valid_variable_name"] = not bad_vars
|
|
|
|
# ── Rule 4: Non-empty ──
|
|
result["no_empty_expression"] = len(equation.strip()) > 0
|
|
|
|
# ── Rule 5: Parseable as single expression ──
|
|
tree = parse_to_ast(equation)
|
|
result["single_expression"] = tree is not None
|
|
|
|
# ── Rule 6: All names are known or short ──
|
|
result["defined_reference"] = True
|
|
if tree is not None:
|
|
for node in ast.walk(tree):
|
|
if isinstance(node, ast.Name):
|
|
name = node.id
|
|
allowed = (
|
|
len(name) <= 2
|
|
or name.startswith("_")
|
|
or "_" in name
|
|
or name in KNOWN_MATH_NAMES
|
|
)
|
|
if not allowed:
|
|
result["defined_reference"] = False
|
|
|
|
return result
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Verification block
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
|
|
# Perfect equation: all 6 rules should be True
|
|
perfect = check_consistency("x + 1")
|
|
assert all(perfect.values()), f"Perfect equation failed: {perfect}"
|
|
assert set(perfect.keys()) == set(CONSISTENCY_RULES), \
|
|
f"Key mismatch: {set(perfect.keys()) ^ set(CONSISTENCY_RULES)}"
|
|
assert len(perfect) == 6, f"Expected 6 keys, got {len(perfect)}"
|
|
|
|
# Rule 1 broken: unbalanced parentheses
|
|
r1 = check_consistency("(x + 1")
|
|
assert not r1["balanced_parens"], "Rule 1: expected balanced_parens=False"
|
|
|
|
# Rule 2 broken: illegal consecutive operators
|
|
r2 = check_consistency("x += 1")
|
|
assert not r2["valid_operator_order"], "Rule 2: expected valid_operator_order=False"
|
|
|
|
# Rule 3 broken: not easily triggerable with current heuristic;
|
|
# empty string disables parsing, causing no_empty_expression=False
|
|
|
|
# Rule 4 broken: empty expression
|
|
r4 = check_consistency("")
|
|
assert not r4["no_empty_expression"], "Rule 4: expected no_empty_expression=False"
|
|
|
|
# Rule 5 broken: unparseable garbage
|
|
r5 = check_consistency("@#$%")
|
|
assert not r5["single_expression"], "Rule 5: expected single_expression=False"
|
|
|
|
# Verify every result dict has exactly the 6 expected keys
|
|
for label, rd in [("perfect", perfect), ("r1", r1), ("r2", r2),
|
|
("r4", r4), ("r5", r5)]:
|
|
assert set(rd.keys()) == set(CONSISTENCY_RULES), \
|
|
f"{label}: key mismatch {set(rd.keys()) ^ set(CONSISTENCY_RULES)}"
|
|
|
|
print("consistency: all verification assertions passed.")
|