gugen 0.8.0

Explainable materials synthesis and process planning
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
# gugen (具現)

[![Crates.io](https://img.shields.io/crates/v/gugen.svg)](https://crates.io/crates/gugen)
[![docs.rs](https://img.shields.io/docsrs/gugen)](https://docs.rs/gugen)
[![CI](https://github.com/kent-tokyo/gugen/actions/workflows/ci.yml/badge.svg)](https://github.com/kent-tokyo/gugen/actions/workflows/ci.yml)
[![License](https://img.shields.io/crates/l/gugen.svg)](#license)

**English** | [日本語]README_ja.md

Explainable materials synthesis and process planning, in Rust.

Given a target inorganic composition (and optionally a target structure),
gugen returns candidate precursor sets, balanced reactions, and
solid-state process plans — each with its evidence, assumptions, and
unresolved conditions kept explicit and machine-readable. It does not
predict experimental success.

> **Status: v0.8.0 pending publication** (bug-check-and-refactoring
> sweep; one breaking validation-bypass fix -- see CHANGELOG.md).
> [crates.io]https://crates.io/crates/gugen /
> [docs.rs]https://docs.rs/gugen.
> v0.8.0 closes a validation-bypass gap in `SolidThermodynamicEntry`
> (private fields, matching the `BalancedReaction`/`ReactionSpecies`
> precedent), fixes a grammar-guard bug in the experimental
> `HydroxideToOxideGrammar`, and moves `process.rs`'s
> condition-conflict-resolution code into its own file (no public API
> change). See [`CHANGELOG.md`]CHANGELOG.md for the full
> breaking-change list and migration notes.

## Try it in your browser

Explore real, cited inorganic synthesis examples in the
[gugen Playground](https://kent-tokyo.github.io/gugen/).

It runs entirely in your browser via WebAssembly. No account,
server, external API, or uploaded data is required. The playground
shows accepted plans, rejected candidates, and unresolved conditions;
it does not predict experimental success.

## What gugen does and doesn't guarantee

gugen's output is a set of candidate plans, not a validated SOP. It does
not guarantee: experimental success, target-phase formation, a single
phase product, reaction completion at a stated temperature, high yield,
safe executability, patentability, or industrial scalability. A ranking
score is an ordinal, explainable measure for sorting candidates against
each other — never a success probability. See
[`docs/scientific_scope.md`](docs/scientific_scope.md) for the full list
of what's in and out of scope for v0.1, and
[`docs/evidence_model.md`](docs/evidence_model.md) for how evidence,
assumptions, and unresolved conditions are kept separate.

## What works today

### Reaction balancing

Exact-rational Gauss-Jordan elimination over the element × species
matrix — never floating-point approximation (see
[`docs/architecture.md`](docs/architecture.md)). The full runnable source
for this example is [`examples/balance_batio3.rs`](examples/balance_batio3.rs).

```rust
use gugen::{balance, Composition, Element};

let ba = Element::new("Ba")?;
let ti = Element::new("Ti")?;
let o = Element::new("O")?;

let bao = Composition::new([(ba, 1.0), (o, 1.0)])?;
let tio2 = Composition::new([(ti, 1.0), (o, 2.0)])?;
let batio3 = Composition::new([(ba, 1.0), (ti, 1.0), (o, 3.0)])?;

let reactions = balance(&[bao, tio2], &[batio3])?;
```

Output (`cargo run --example balance_batio3`):

```
1 Ba:1, O:1 + 1 O:2, Ti:1 -> 1 Ba:1, O:3, Ti:1
```

### Bounded precursor-set search

`search_precursor_sets` runs a deterministic, budget-bounded search over
a precursor catalog, returning both accepted precursor sets (each with
its balanced reaction) and every rejected candidate with a reason code —
never just the winners. See [`src/precursor.rs`](src/precursor.rs)'s
tests for worked examples.

### Multi-step synthesis routes

`search_two_step_routes` chains two `search_precursor_sets` calls into a
stoichiometrically connected route (precursors → intermediate → target)
for targets a single step can't reach within budget. It is a
**primitive**, not an accuracy improvement: `intermediate_candidates` is
always caller-supplied (gugen never proposes or fetches them itself),
every route is validated for connectivity only (each stage's reactants
are explained by a base precursor or an earlier stage's product) — not
for whether it matches any real synthesis procedure — and `Planner`
never calls this automatically. Measured against a real 408-row
literature holdout, of the 294 targets confirmed genuinely unreachable
in one step this recovered 12 net-new (4.08%); see
[`docs/phase31_pr2_two_step_arity_recall.md`](docs/phase31_pr2_two_step_arity_recall.md)
for the full methodology and honest limitations. The full runnable
source is in `search_two_step_routes`'s own
[rustdoc example](https://docs.rs/gugen/latest/gugen/fn.search_two_step_routes.html).

```rust
use gugen::{
    Composition, Element, PlanningConstraints, PrecursorCandidate, PrecursorId, SearchBudget,
    search_two_step_routes,
};

// Target needs 5 elements at once -- more than a tight one-step budget
// (max_precursors_per_plan = 4) allows, so it's only reachable by first
// combining three of them into an intermediate.
let target = Composition::new([
    (Element::new("Fe")?, 1.0), (Element::new("Li")?, 1.0),
    (Element::new("Na")?, 1.0), (Element::new("K")?, 1.0),
    (Element::new("O")?, 1.0),
])?;
let base = vec![
    PrecursorCandidate { id: PrecursorId("Fe".into()), composition: Composition::new([(Element::new("Fe")?, 1.0)])?, availability: None },
    PrecursorCandidate { id: PrecursorId("Li".into()), composition: Composition::new([(Element::new("Li")?, 1.0)])?, availability: None },
    PrecursorCandidate { id: PrecursorId("Na".into()), composition: Composition::new([(Element::new("Na")?, 1.0)])?, availability: None },
    PrecursorCandidate { id: PrecursorId("K".into()), composition: Composition::new([(Element::new("K")?, 1.0)])?, availability: None },
    PrecursorCandidate { id: PrecursorId("O2".into()), composition: Composition::new([(Element::new("O")?, 2.0)])?, availability: None },
];
let intermediate = Composition::new([
    (Element::new("Fe")?, 1.0), (Element::new("Li")?, 1.0), (Element::new("Na")?, 1.0),
])?;
let budget = SearchBudget { max_precursor_sets: 10_000, max_precursors_per_plan: 4, max_plans_returned: 20 };

let routes = search_two_step_routes(
    &target, &base, &[intermediate], &PlanningConstraints::default(), &budget,
)?;
// routes[0].stages() has exactly 2 stages; the last stage's products
// include `target`.
```

An optional, separate `experimental_grammar` feature (default off) adds
a small set of hand-written decomposition grammars
(`src/transformation_grammar.rs`) that propose candidate intermediate
compositions from stoichiometry alone (e.g. `MCO3 -> MO + CO2`). It is
explicitly experimental: measured against the same holdout, it did not
recover any target beyond what a plain corpus-frequency prior already
reaches (see
[`docs/phase31_pr3_transformation_grammar_audit.md`](docs/phase31_pr3_transformation_grammar_audit.md)),
its API may change in any `0.x` release, and it is not wired into
`Planner`.

The gugen Playground does not currently visualize multi-step routes —
only single-step plans.

### Solid-state process templates

`conventional_solid_state_template` turns an accepted precursor set into a
weigh/mix/grind/form/heat/cool/characterize step sequence, each step marked
`Required`/`Recommended`/`Optional`/`Unresolved`. It does not apply the same
template to every material: a route that releases a byproduct (e.g. a
carbonate route releasing CO₂) gets an extra calcination step that an
oxide-only route to the same target does not. Temperature, duration, ramp
rate, and atmosphere are left unresolved (`None`) rather than guessed —
this worked example uses `Planner::offline_minimal`, which wires in no
provider at all. A caller can configure a `ProcessEvidenceProvider` (e.g.
`InMemoryLiteratureConditionProvider`) to resolve some of these fields from
cited literature; see `docs/integration.md`.

### Plan scoring and confidence

`score_plan` computes a `PlanScoreBreakdown` and a `ConfidenceAssessment`
per plan — never a single collapsed number. Missing thermodynamic data is
excluded from the score rather than treated as failure; a plan with no
evidence scores lower than one with evidence. `total_ranking_score` is an
ordinal, explainable score for comparing candidates, never a success
probability — `thermodynamic_support` stays `None` regardless of whether a
`ThermodynamicProvider` is configured (a resolved reaction energy becomes
evidence, never a numeric score — AGENTS.md §4.3), so `total_ranking_score`
is honestly driven by only one real signal (`process_simplicity`, now
computed per-route-family since Phase 12 added a second route family — see
below); see
[`PlanScoreBreakdown`'s doc comment](src/score.rs) for the full breakdown
of what's currently constant versus load-bearing. Every plan currently sets `manual_review_required:
true`, since gugen has no hazard/safety data source wired in yet.

### Commercial Precursor Catalog

Optional, off-by-default `commercial_catalog` feature: matches an existing
`SynthesisPlan`'s precursors against a caller-supplied catalog of
commercial offers (price, purity, package size, supplier), as a
post-planning stage that never touches the plan's score, confidence,
reaction, or process steps. Matches on a canonical, scale-invariant
composition ratio via exact rational arithmetic (`Fe2O3` matches `Fe4O6`;
hydrate vs. anhydrous and different compounds stay distinct; no alias or
substitute-precursor inference); CSV/JSON catalog import with a
structured accepted/rejected load report; stoichiometric quantity
calculation, purity-adjusted purchase mass, package-count rounding,
currency-safe checked-arithmetic cost totals; a bounded search over
complete purchasing combinations. See
[`docs/commercial_precursor_catalog.md`](docs/commercial_precursor_catalog.md)
for the full CSV/JSON schema, exact-match policy, and quantity-calculation
rules, and `examples/commercial_catalog_assessment.rs` for a runnable
worked example (`cargo run --example commercial_catalog_assessment
--features commercial_catalog`). No real catalog data ships with gugen;
gugen does not certify commercial data (prices/availability are supplied
estimates, not guarantees).

### CLI

```
$ gugen balance reaction.json
```

`reaction.json`:

```json
{
  "reactants": [
    {"Ba": 1.0, "O": 1.0},
    {"Ti": 1.0, "O": 2.0}
  ],
  "products": [
    {"Ba": 1.0, "Ti": 1.0, "O": 3.0}
  ]
}
```

Output (`serde_json::to_string_pretty`, one field per line):

```json
[
  {
    "reactants": [
      {
        "composition": {
          "Ba": 1.0,
          "O": 1.0
        },
        "coefficient": 1
      },
      {
        "composition": {
          "O": 2.0,
          "Ti": 1.0
        },
        "coefficient": 1
      }
    ],
    "products": [
      {
        "composition": {
          "Ba": 1.0,
          "O": 3.0,
          "Ti": 1.0
        },
        "coefficient": 1
      }
    ]
  }
]
```

The `gugen` binary itself requires the `serde` and `clap` features
(`[[bin]]` in `Cargo.toml`) — `cargo install gugen` with no `--features`
flag builds **no binary at all** (default features are `[]`), it only
installs the library. To get the CLI:

```
cargo install gugen --features serde,clap
```

or, from a checkout, `cargo build --features serde,clap --bin gugen`.
Subcommands (AGENTS.md §19):

```
gugen balance reaction.json
gugen plan target.json --catalog precursors.json [--output report.json] [--format json|markdown]
gugen explain report.json --plan plan-001
gugen validate-target target.json
gugen doctor
gugen batch input.json --catalog precursors.json [--output out.json]
```

`target.json`/`precursors.json`/`input.json` reuse gugen's own public JSON
shapes (`TargetSpecification`, a JSON array of `PrecursorCandidate`, and a
JSON array of `TargetSpecification` respectively) rather than a separate
CLI-specific format. `gugen batch` plans every target independently — one
target's failure doesn't abort the rest.

`commercial-plan` (Commercial Precursor Catalog, matching a target's
plans against a commercial-offers catalog for price/purity/lead-time)
additionally needs the `commercial_catalog` feature:

```
cargo install gugen --features serde,clap,commercial_catalog
```

```
gugen commercial-plan target.json --catalog precursors.json \
  --commercial-catalog offers.csv \
  [--commercial-catalog-column-map column_map.json] \
  [--ranking-policy balanced|cost-first|lead-time-first|purity-first|minimum-unresolved-data|pareto] \
  [--min-purity 0.99] [--max-lead-time-days 30] [--max-total-cost 50000 --currency USD] \
  [--format json|markdown|csv]
```

Without `commercial_catalog` enabled, `commercial-plan` doesn't exist as
a subcommand at all (not a runtime error — the binary is built without
it). See `docs/commercial_precursor_catalog.md` for the full option
list and `--commercial-catalog-column-map`'s declarative CSV
column-name mapping for real-world supplier exports with non-standard
headers.

### Worked example: a full synthesis plan

```
$ gugen plan target.json --catalog precursors.json --format markdown
```

`target.json` (BaTiO3) and `precursors.json` (the standard BaCO3 + TiO2
solid-state route to it):

```json
{
  "composition": {"Ba": 1.0, "Ti": 1.0, "O": 3.0},
  "structure": null,
  "desired_phase": null,
  "constraints": {"forbidden_elements": []}
}
```

```json
[
  {"id": "BaCO3", "composition": {"Ba": 1.0, "C": 1.0, "O": 3.0}, "availability": null},
  {"id": "TiO2", "composition": {"Ti": 1.0, "O": 2.0}, "availability": null}
]
```

Output (real, unedited `gugen plan` output; this is also
`tests/fixtures/batio3_report.md`'s golden snapshot, minus its score-
breakdown/confidence/assumptions/unresolved-conditions sections and
rejected-candidate section for length — all are in the full file):

```markdown
# Synthesis Planning Report (schema v1)

**Target:** Ba:1, O:3, Ti:1

**Applicability:** PartiallyInDomain -- formula-only target, no structure provided (AGENTS.md §16's own example for this level)

## Plan plan-1677d44bfe4dbdc2 (score 0.062)

- Target: Ba:1, O:3, Ti:1
- Route family: ConventionalSolidState
- Reaction: 1x(Ba:1, C:1, O:3) + 1x(O:2, Ti:1) -> 1x(Ba:1, O:3, Ti:1) + 1x(C:1, O:2)
- Manual review required: true
- Applicability: PartiallyInDomain -- formula-only target, no structure provided (AGENTS.md §16's own example for this level)

### Steps

- [Required] Weigh: BaCO3 x1, TiO2 x1
- [Required] Mix (DryMixing)
- [Required] Grind (MortarAndPestle), duration=unresolved
- [Optional] Form (UniaxialPressing), pressure=unresolved
- [Required] Heat (Calcination): temperature=unresolved, duration=unresolved, atmosphere=unresolved, ramp=unresolved
- [Recommended] Grind (MortarAndPestle), duration=unresolved
- [Required] Heat (Sintering): temperature=unresolved, duration=unresolved, atmosphere=unresolved, ramp=unresolved
- [Required] Cool (FurnaceCooling)
- [Recommended] Characterize (Xrd): verify target-phase formation

### Evidence

- [Weak/ProcessTemplate] weigh/mix/grind/form are the fixed opening sequence of the v0.1 conventional solid-state template
- [Strong/StoichiometricBalance] balanced reaction releases a byproduct beyond the target, indicating a decomposition (calcination) step is needed before the final firing step
- [Weak/ProcessTemplate] AGENTS.md §11's template outline places a regrind between calcination and final firing

### Warnings

- [Caution] temperature, duration, ramp rate, and atmosphere are unresolved for every heating step: gugen has no thermodynamic or literature evidence provider wired in yet (AGENTS.md §4.1)
- [Severe] no hazard or safety data source is wired in yet: safety_penalty carries no real safety information, and this is not a safety clearance (AGENTS.md §15 "unknown hazardを安全と扱わない")

## Plan plan-ee311be9350b7d8b (score 0.062)

- Target: Ba:1, O:3, Ti:1
- Route family: Mechanochemical
- Reaction: 1x(Ba:1, C:1, O:3) + 1x(O:2, Ti:1) -> 1x(Ba:1, O:3, Ti:1) + 1x(C:1, O:2)
- Manual review required: true
- Applicability: PartiallyInDomain -- formula-only target, no structure provided (AGENTS.md §16's own example for this level)

### Steps

- [Required] Weigh: BaCO3 x1, TiO2 x1
- [Required] Grind (BallMilling), duration=unresolved
- [Optional] Form (UniaxialPressing), pressure=unresolved
- [Required] Heat (Annealing): temperature=unresolved, duration=unresolved, atmosphere=unresolved, ramp=unresolved
- [Required] Cool (FurnaceCooling)
- [Recommended] Characterize (Xrd): verify target-phase formation

### Evidence

- [Weak/ProcessTemplate] weigh, then a single high-energy ball-milling step (which performs mixing and grinding together, unlike the separate Mix/Grind steps of the conventional solid-state template) is the fixed opening sequence of the mechanochemical route template
- [Moderate/StoichiometricBalance] balanced reaction releases a byproduct beyond the target; ball milling alone is not reliably sufficient to complete such a reaction at room temperature, so a post-milling anneal is included -- the cited review reports specific byproduct-releasing compounds (e.g. gamma-Al2O3, ZrO2) that formed only after heating the as-milled powder

### Warnings

- [Caution] grinding duration, forming pressure, and (if present) heating temperature/duration/atmosphere/ramp are unresolved: gugen has no thermodynamic or literature evidence provider wired in yet (AGENTS.md §4.1)
- [Severe] no hazard or safety data source is wired in yet: safety_penalty carries no real safety information, and this is not a safety clearance (AGENTS.md §15 "unknown hazardを安全と扱わない")
```

Note the two plans: since Phase 12, every accepted precursor set is offered
under *every* applicable route family (currently 2), not just one — gugen
has no route-suitability classifier to prefer one for a given target, so
both are always shown, ranked independently (AGENTS.md §13). Note also the
calcination/regrind step in the first plan: it's there because the balanced
reaction releases CO2 (see the Evidence entry), not because every plan gets
the same template — a carbonate-free route to the same target wouldn't have
it, and the mechanochemical plan's own post-milling anneal is conditioned
the same way. The full report also carries a per-plan score breakdown,
confidence assessment (five independent sub-scores, not one blended
number), assumptions list, and every rejected single-precursor candidate
with its reason code.

## Ecosystem

```
                       chematic-crystal
               periodic structure foundation
                                             ┌────────────┴────────────┐
                │                         │
             mikiwame                  gugen
     explainable diagnostics    synthesis/process planning
```

gugen depends on `chematic-crystal` for periodic structure types once
that crate is published (not yet, as of 2026-08-14 — see
[`docs/integration.md`](docs/integration.md)); until then it builds
against a minimal trait boundary it owns itself. `mikiwame` is published
and integrated as an optional, off-by-default `mikiwame` feature
(`cargo build --features mikiwame`) that maps its structural diagnostics
onto gugen's own warnings/confidence — not yet wired into `Planner::plan`,
since that still needs `chematic-crystal`-shaped structure data gugen
doesn't have. gugen never depends on `renkin` (molecular retrosynthesis)
and does not reuse its algorithms — gugen is a materials-domain sibling,
not a port.

## Development

```
cargo fmt --all -- --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-features
cargo test --workspace --no-default-features
cargo test --no-default-features --features mikiwame
cargo test --no-default-features --features commercial_catalog
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps
cargo build --features serde,clap --bin gugen
cargo check --target wasm32-unknown-unknown
cargo check --target wasm32-unknown-unknown --features mikiwame
cargo audit
```

Architecture and design decisions: [`docs/`](docs/).

## License

Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or
[MIT license](LICENSE-MIT) at your option.

日本語版: [README_ja.md](README_ja.md)