Metadata-Version: 2.4
Name: lmschem
Version: 0.1.0
Summary: Coordinate-free circuit schematic engine: port-graph IR, deterministic geometry solver and router, lcapy renderer
Author-email: Chris Cameron <chris.cameron@canterbury.ac.nz>
License-Expression: MIT
Project-URL: Homepage, https://github.com/ccam80/lmschem
Project-URL: Repository, https://github.com/ccam80/lmschem
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Education
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Education
Classifier: Operating System :: OS Independent
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: lcapy==1.26
Requires-Dist: pydantic>=2
Requires-Dist: PyYAML>=6
Provides-Extra: gen
Requires-Dist: numpy; extra == "gen"
Requires-Dist: pillow; extra == "gen"
Provides-Extra: test
Requires-Dist: pytest; extra == "test"
Dynamic: license-file

# lmschem

A coordinate-free circuit schematic engine. Authors describe a circuit as
a **port graph** — components, electrical nets, relational placement — and
a deterministic geometry engine places bodies, routes every wire, enforces
hard drawing rules, and renders a PNG through lcapy/circuitikz. The same
IR always yields the same drawing.

The complete authoring language reference is **`lmschem/rules.md`** — it
is the only document an author needs, and it ships as package data.

## Architecture (four layers)

```
PortGraph IR ──► geometry engine ──► renderer adapter ──► PNG
(portgraph/)     (geometry/solve,     (geometry/adapters/   
                  router, report)      lcapy.py)            
```

1. **Port-graph IR** (`portgraph/ir.py`) — Pydantic models: components,
   nets, relational `place` anchors, rails/snaps, route hints, buses,
   decorations, blocks. No coordinates, ever.
2. **Geometry engine** (`geometry/`) — deterministic placement
   (trunk-and-branch routing, channel assignment, facing-aware
   alignment), hard-rule checks with structured, fix-hinted issues, an
   ASCII occupancy grid, and solver-placed text (net labels, bus
   captions, decoration labels) backed by measured ink metrics.
3. **Renderer adapter** (`geometry/adapters/lcapy.py`) — lowers the
   solved layout to an lcapy netlist with injected node positions
   (lcapy's placer is bypassed entirely) and verifies a real PNG came
   back. Emission is inventory-driven: bipoles, amps, chips/shapes, and
   the generic node-grammar path (`node_pinnames` slot order) cover
   every drawable class.
4. **Incremental edits** (`edit.py`) — targeted IR edit operations with a
   flow-style YAML emitter that keeps files in the shape authors write.

Entry point:

```python
from lmschem.author import author_circuit
result = author_circuit(ir_yaml_string, "out_dir")
# result: ok / report / grid / issues / image
```

## The inventory contract

`lmschem/inventory/*.yaml` is the **sole runtime authority** for
everything lcapy-specific: legal pin tokens, drawn subsets, pin aliases,
netlist-node slot order (`node_pinnames`), measured body extents and pin
coordinates, text metrics, decoration glyph ink, options, and the derived
semantic vocabulary. **Runtime never introspects lcapy.**

The inventory is generated, never hand-edited (except `components.yaml`,
`schemcpts.yaml`, `options.yaml`, `placer.yaml`, which are curated).
Regenerate after any lcapy upgrade:

```
python -m lmschem.gen_inventory
```

This re-measures glyphs, rewrites the generated YAMLs, re-derives the
semantic vocabulary (`semantic_types.yaml`), and re-splices the generated
component reference into `rules.md` between its markers. lcapy is pinned
exactly in `pyproject.toml` because the measurements are against that
version.

Coverage contract: every drawable inventory class either solves and
emits (guarded by `tests/test_vocabulary_coverage.py`, one param per
class, no rendering) or sits on `gen_inventory.EXCLUDED_CLASSES` with a
written reason (K, A, W, MISC, sV, sI). A new lcapy class becomes a
failing test param until supported or excluded.

## Tests

```
python -m pytest tests -q
```

Most tests render real PNGs through pdflatex + ghostscript — a TeX
distribution and ghostscript must be on PATH. The solve+emit coverage
guard and the geometry tests run without rendering.

## lcapy quirks this engine works around (do not "simplify" these away)

- `LatexRunner.run` never checks the pdflatex result and
  `to_png_ghostscript` never checks its output — the adapter verifies the
  PNG file itself and raises a structured error.
- The opts parser splits on commas, so every label/value rides braced
  (`l={...}`); unbraced LaTeX with commas kills the compile silently.
- A netlist with zero numeric nodes (all-unwired chips) crashes lcapy's
  canvas sizing — the adapter raises "nothing in the drawing is wired"
  first.
- Uisoamp's class declares a phantom `ocm` pin its own draw rejects;
  `gen_inventory._pins_for` reads property-pin dicts from the class's own
  `__dict__` only.
- lcapy injection scales by `node_spacing` (2.0); gutters never move
  terminal segments.

## Relationship to coursesmith

lmschem is standalone and speaks bare IR. Coursesmith integrates it
through a thin adapter layer on its side (generator spec/executor, MCP
edit tools, discovery): the generator-spec envelope, analysis requests,
and example specs are coursesmith concepts and live there, not here.
