diff --git a/README.md b/README.md
index 8ed2f44..e60f92d 100644
--- a/README.md
+++ b/README.md
@@ -130,17 +130,16 @@ cd python && maturin develop --release && pytest # Python wheel
## Documentation
-- **Guide** — the Markdown chapters under [`docs/`](./docs/): install,
- getting started, concepts, examples, Packmol parity, architecture, and
- extending.
+- **Guide** — [`docs/`](./docs/), built with `zensical build`. Chapters:
+ install, getting started, concepts, examples, Packmol parity, architecture,
+ and extending.
- **Rust API** — `cargo doc --open`, or [docs.rs](https://docs.rs/molcrafts-molpack).
The four long-form chapters (getting started, concepts, architecture,
extending) are also embedded in the rustdoc as
`molpack::getting_started`, `molpack::concepts`,
`molpack::architecture`, and `molpack::extending`.
-- **Python** — the binding docs under [`docs/python/`](./docs/python/)
- (installation, guide, examples, and the Python API reference), published as
- the **Python** section of the same Zensical site.
+- **Python** — binding docs live under `docs/python/`, published as the
+ **Python** section of the site.
## Contributing
diff --git a/docs/assets/stylesheets/extra.css b/docs/assets/stylesheets/extra.css
index 351b40f..c6501f8 100644
--- a/docs/assets/stylesheets/extra.css
+++ b/docs/assets/stylesheets/extra.css
@@ -1,535 +1,17 @@
-:root {
- --molpack-accent: #c2410c;
- --molpack-accent-strong: #9a3412;
- --molpack-accent-soft: rgba(194, 65, 12, 0.14);
- --molpack-secondary: #d97706;
- --molpack-secondary-soft: rgba(217, 119, 6, 0.12);
- --molpack-warm: #ea580c;
- --molpack-panel-bg: color-mix(in srgb, var(--md-default-bg-color) 88%, white);
- --molpack-panel-border: color-mix(in srgb, var(--molpack-accent) 24%, transparent);
- --molpack-panel-shadow: 0 0.65rem 1.7rem rgba(92, 43, 17, 0.1);
-}
-
-[data-md-color-scheme="molcrafts"],
-[data-md-color-scheme="molcrafts-slate"] {
- --md-typeset-color: var(--md-default-fg-color);
- color: var(--md-typeset-color);
-}
-
-[data-md-color-scheme="molcrafts-slate"] {
- --molpack-accent: #fb923c;
- --molpack-accent-strong: #fdba74;
- --molpack-accent-soft: rgba(251, 146, 60, 0.16);
- --molpack-secondary: #f97316;
- --molpack-secondary-soft: rgba(249, 115, 22, 0.13);
- --molpack-warm: #fdba74;
- --molpack-panel-bg: color-mix(in srgb, var(--md-default-bg-color) 88%, #23170f);
- --molpack-panel-border: color-mix(in srgb, var(--molpack-accent) 26%, transparent);
- --molpack-panel-shadow: 0 0.65rem 1.7rem rgba(0, 0, 0, 0.28);
-}
-
-[data-md-color-scheme="molcrafts-slate"] .md-typeset,
-[data-md-color-scheme="molcrafts-slate"] .md-content,
-[data-md-color-scheme="molcrafts-slate"] .molcrafts-home-hero,
-[data-md-color-scheme="molcrafts-slate"] .molcrafts-home-hero__button {
- color: var(--md-default-fg-color);
-}
-
-[data-md-color-scheme="molcrafts-slate"] .md-typeset p,
-[data-md-color-scheme="molcrafts-slate"] .md-typeset dd,
-[data-md-color-scheme="molcrafts-slate"] .molcrafts-home-hero__description {
- color: var(--md-default-fg-color--light);
-}
-
-html[data-molcrafts-product="molpack"] .molcrafts-home-hero__aside::before {
- display: none;
- content: none;
-}
-
-html[data-molcrafts-product="molpack"] .molcrafts-home-hero__aside {
- width: min(100%, 23rem);
-}
-
-html[data-molcrafts-product="molpack"] .molcrafts-home-hero__install {
- width: 100%;
- min-width: 0;
- box-sizing: border-box;
-}
-
-html[data-molcrafts-product="molpack"] .molcrafts-home-hero__install-tabs {
- width: 100%;
- display: flex;
- min-width: 0;
-}
-
-html[data-molcrafts-product="molpack"] .molcrafts-home-hero__install-row {
- min-width: 0;
-}
-
-html[data-molcrafts-product="molpack"] .molcrafts-home-hero__install-row code {
- min-width: 0;
-}
-
-html {
- scroll-behavior: smooth;
-}
-
-html[data-molcrafts-product="molpack"] .molcrafts-home-hero {
- min-height: 27rem;
- background:
- linear-gradient(112deg, rgba(255, 247, 237, 0.98) 0%, rgba(255, 247, 237, 0.9) 44%, rgba(194, 65, 12, 0.13) 100%),
- linear-gradient(90deg, rgba(194, 65, 12, 0.12) 0 1px, transparent 1px 100%),
- linear-gradient(0deg, rgba(217, 119, 6, 0.09) 0 1px, transparent 1px 100%),
- linear-gradient(135deg, var(--molpack-secondary-soft), transparent 46%),
- var(--md-default-bg-color);
- background-size: auto, 4.2rem 4.2rem, 4.2rem 4.2rem, auto, auto;
-}
-
-html[data-molcrafts-product="molpack"] [data-md-color-scheme="molcrafts-slate"] .molcrafts-home-hero,
-html[data-molcrafts-product="molpack"][data-md-color-scheme="molcrafts-slate"] .molcrafts-home-hero {
- background:
- linear-gradient(112deg, rgba(24, 18, 14, 0.98) 0%, rgba(24, 18, 14, 0.9) 44%, rgba(251, 146, 60, 0.16) 100%),
- linear-gradient(90deg, rgba(251, 146, 60, 0.11) 0 1px, transparent 1px 100%),
- linear-gradient(0deg, rgba(255, 247, 237, 0.07) 0 1px, transparent 1px 100%),
- linear-gradient(135deg, var(--molpack-secondary-soft), transparent 46%),
- var(--md-default-bg-color);
- background-size: auto, 4.2rem 4.2rem, 4.2rem 4.2rem, auto, auto;
-}
-
-html[data-molcrafts-product="molpack"] .molcrafts-home-hero::before {
- background:
- linear-gradient(112deg, transparent 0 48%, rgba(194, 65, 12, 0.12) 48% 48.3%, transparent 48.3% 100%),
- linear-gradient(112deg, transparent 0 64%, rgba(217, 119, 6, 0.1) 64% 64.25%, transparent 64.25% 100%),
- repeating-linear-gradient(112deg, transparent 0 1.4rem, rgba(92, 43, 17, 0.04) 1.4rem 1.45rem, transparent 1.45rem 3.7rem);
-}
-
-html[data-molcrafts-product="molpack"] .molcrafts-home-hero__kicker,
-html[data-molcrafts-product="molpack"] .molcrafts-manual-eyebrow {
- color: var(--molpack-accent);
-}
-
-html[data-molcrafts-product="molpack"] .molcrafts-home-hero h1 {
- max-width: 17rem;
- background: linear-gradient(96deg, var(--molpack-accent) 0%, var(--molpack-secondary) 58%, var(--molpack-warm) 100%);
- -webkit-background-clip: text;
- background-clip: text;
- color: transparent;
-}
-
-html[data-molcrafts-product="molpack"] .molcrafts-home-hero__description {
- max-width: 39rem;
-}
-
-.md-typeset .molcrafts-manual-home.molpack-home {
- max-width: 1160px;
- padding-top: 1.45rem;
-}
-
-.md-typeset .molpack-system-section {
- grid-template-columns: minmax(0, 1fr);
- gap: 1rem;
-}
-
-.md-typeset .molpack-system-section .molcrafts-manual-section__header {
- position: static;
- max-width: 43rem;
-}
-
-.md-typeset .molpack-system-panel {
- overflow: hidden;
- margin: 0;
- border: 1px solid var(--molpack-panel-border);
- border-radius: var(--molcrafts-radius);
- background:
- linear-gradient(135deg, var(--molpack-panel-bg), color-mix(in srgb, var(--molpack-accent-soft) 56%, var(--md-default-bg-color))),
- var(--md-default-bg-color);
- box-shadow: var(--molpack-panel-shadow);
-}
-
-.md-typeset .molpack-system-panel__header {
- display: grid;
- gap: 0.25rem;
- padding: 0.85rem 1rem;
- border-bottom: 1px solid color-mix(in srgb, var(--md-primary-fg-color) 12%, transparent);
-}
-
-.md-typeset .molpack-system-panel__header span {
- color: var(--md-default-fg-color--lighter);
- font-size: 0.62rem;
- font-weight: 850;
- line-height: 1.35;
- letter-spacing: 0;
- text-transform: uppercase;
-}
-
-.md-typeset .molpack-system-panel__header strong {
- color: var(--md-default-fg-color);
- font-size: 0.92rem;
- line-height: 1.25;
-}
-
-.md-typeset .molpack-system-flow {
- position: relative;
- display: grid;
- grid-template-columns: repeat(4, minmax(0, 1fr));
- gap: 0.65rem;
- padding: 1.35rem 1rem 1.05rem;
-}
-
-.md-typeset .molpack-system-flow::before {
- content: "";
- position: absolute;
- top: 2.15rem;
- right: 1.25rem;
- left: 1.25rem;
- height: 0.12rem;
- background: linear-gradient(90deg, var(--molpack-accent), var(--molpack-secondary), var(--molpack-warm));
- opacity: 0.62;
-}
-
-.md-typeset .molpack-system-flow > div {
- position: relative;
- min-width: 0;
- min-height: 7.1rem;
- padding: 1.25rem 0.75rem 0.75rem;
- border: 1px solid color-mix(in srgb, var(--md-primary-fg-color) 13%, transparent);
- border-radius: var(--molcrafts-radius);
- background: color-mix(in srgb, var(--md-default-bg-color) 84%, transparent);
- transition: border-color 180ms ease, box-shadow 180ms ease, transform 180ms ease, background-color 180ms ease;
-}
-
-.md-typeset .molpack-system-flow > div:is(:focus-within, :hover) {
- border-color: color-mix(in srgb, var(--molpack-accent) 46%, transparent);
- box-shadow: 0 0.5rem 1.4rem color-mix(in srgb, var(--molpack-accent) 22%, transparent);
- background: color-mix(in srgb, var(--md-default-bg-color) 94%, var(--molpack-accent-soft));
- transform: translateY(-2px);
-}
-
-.md-typeset .molpack-system-flow > div::before {
- content: "";
- position: absolute;
- top: -0.32rem;
- left: 0.72rem;
- width: 0.62rem;
- height: 0.62rem;
- border: 0.14rem solid var(--molpack-accent);
- border-radius: 999px;
- background: var(--md-default-bg-color);
-}
-
-.md-typeset .molpack-system-flow > div:nth-child(3)::before {
- border-color: var(--molpack-secondary);
-}
-
-.md-typeset .molpack-system-flow span,
-.md-typeset .molcrafts-workflow-list.molpack-workflow-list .molcrafts-workflow-list__meta {
- font-family: var(--md-code-font);
- letter-spacing: 0;
-}
-
-.md-typeset .molpack-system-flow span {
- display: block;
- color: var(--md-default-fg-color--lighter);
- font-size: 0.58rem;
- font-weight: 850;
- line-height: 1.35;
- text-transform: uppercase;
-}
-
-.md-typeset .molpack-system-flow strong {
- display: block;
- margin-top: 0.38rem;
- color: var(--md-default-fg-color);
- font-size: 0.72rem;
- line-height: 1.35;
- overflow-wrap: anywhere;
-}
-
-.md-typeset .molpack-system-flow a {
- display: block;
- color: inherit;
- text-decoration: none;
-}
-
-.md-typeset .molpack-results-section {
- grid-template-columns: minmax(0, 1fr);
- gap: 1rem;
-}
-
-.md-typeset .molpack-results-section .molcrafts-manual-section__header {
- position: static;
- max-width: 50rem;
-}
-
-.md-typeset .molpack-result-gallery {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 0.85rem;
-}
-
-.md-typeset .molpack-result-figure {
- min-width: 0;
- margin: 0;
- padding: 0.65rem;
- border: 1px solid color-mix(in srgb, var(--md-primary-fg-color) 12%, transparent);
- border-radius: var(--molcrafts-radius);
- background: color-mix(in srgb, var(--md-default-bg-color) 92%, white);
-}
-
-[data-md-color-scheme="molcrafts-slate"] .md-typeset .molpack-result-figure {
- background: color-mix(in srgb, var(--md-default-bg-color) 92%, #23170f);
-}
-
-.md-typeset .molpack-result-figure--wide {
- grid-column: 1 / -1;
-}
-
-.md-typeset .molpack-result-figure img {
- display: block;
- width: 100%;
- max-height: 24rem;
- object-fit: contain;
- border-radius: calc(var(--molcrafts-radius) - 2px);
- background: #fff;
-}
-
-.md-typeset .molpack-result-figure--wide img {
- max-height: 34rem;
-}
-
-.md-typeset .molpack-result-figure figcaption {
- margin: 0.55rem 0 0;
- color: var(--md-default-fg-color--light);
- font-size: 0.66rem;
- line-height: 1.45;
-}
-
+/*
+ * Product-level docs tweaks only.
+ *
+ * Visual system (hero, manual-home, workflow-list, doc-map, feature-matrix,
+ * grids, light/dark palettes) is owned by molcrafts-zensical-theme.
+ * Product accent is set in zensical.toml under [project.extra.molcrafts].
+ *
+ * Do not re-skin manual components here — that fights the theme.
+ */
+
+/* Keep figure embeds readable when parity / extending pages show charts. */
.md-typeset img.molpack-figure-small {
display: block;
width: min(100%, 34rem);
margin-right: auto;
margin-left: auto;
}
-
-.md-typeset .molcrafts-workflow-list.molpack-workflow-list > article {
- grid-template-columns: 7.5rem minmax(0, 1fr);
- padding: 1.05rem 0.9rem;
- border-radius: var(--molcrafts-radius);
-}
-
-.md-typeset .molcrafts-workflow-list.molpack-workflow-list h3 {
- font-size: 0.9rem;
-}
-
-.md-typeset .molcrafts-workflow-list.molpack-workflow-list h3 a {
- color: inherit;
-}
-
-.md-typeset .molcrafts-workflow-list.molpack-workflow-list p {
- max-width: 44rem;
- font-size: 0.7rem;
-}
-
-.md-typeset .molcrafts-workflow-list.molpack-workflow-list pre > code {
- border-left-color: var(--molpack-accent);
- font-size: 0.61rem;
-}
-
-.md-typeset .molpack-doc-map h3 a {
- color: inherit;
- text-decoration: none;
-}
-
-.md-typeset .molpack-doc-map {
- grid-template-columns: repeat(5, minmax(0, 1fr));
- gap: 0.6rem;
-}
-
-.md-typeset .molpack-doc-map section {
- position: relative;
- padding: 0.85rem 0.9rem;
- border-bottom: 0;
- border-radius: var(--molcrafts-radius);
- background: color-mix(in srgb, var(--md-default-fg-color) 4%, transparent);
- transition: background-color 160ms ease, transform 160ms ease;
-}
-
-.md-typeset .molpack-doc-map section:is(:focus-within, :hover) {
- background: color-mix(in srgb, var(--molpack-accent-soft) 58%, transparent);
- transform: translateY(-1px);
-}
-
-.md-typeset .molpack-system-flow a::after,
-.md-typeset .molpack-doc-map h3 a::after {
- content: "";
- position: absolute;
- inset: 0;
- z-index: 1;
-}
-
-@media screen and (max-width: 76.2344em) {
- .md-typeset .molpack-system-flow {
- grid-template-columns: repeat(3, minmax(0, 1fr));
- }
-
- .md-typeset .molpack-system-flow::before {
- display: none;
- }
-
- .md-typeset .molpack-doc-map {
- grid-template-columns: repeat(2, minmax(0, 1fr));
- }
-
- .md-typeset .molpack-result-gallery {
- grid-template-columns: minmax(0, 1fr);
- }
-}
-
-@media screen and (max-width: 60em) {
- html[data-molcrafts-product="molpack"] .molcrafts-home-hero {
- min-height: 24rem;
- }
-
- .md-typeset .molcrafts-manual-home.molpack-home {
- padding-top: 1.2rem;
- }
-
- .md-typeset .molpack-system-flow {
- grid-template-columns: repeat(2, minmax(0, 1fr));
- }
-
- .md-typeset .molcrafts-workflow-list.molpack-workflow-list > article {
- grid-template-columns: minmax(0, 1fr);
- }
-}
-
-@media screen and (max-width: 36em) {
- html[data-molcrafts-product="molpack"] .molcrafts-home-hero h1 {
- max-width: none;
- }
-
- .md-typeset .molpack-system-panel__header,
- .md-typeset .molpack-system-flow {
- padding-right: 0.75rem;
- padding-left: 0.75rem;
- }
-
- .md-typeset .molpack-system-flow,
- .md-typeset .molpack-doc-map,
- .md-typeset .molpack-result-gallery {
- grid-template-columns: minmax(0, 1fr);
- }
-
- .md-typeset .molpack-system-flow > div {
- min-height: 5.8rem;
- }
-
- .md-typeset .molpack-result-figure {
- padding: 0.45rem;
- }
-
- .md-typeset .molpack-result-figure img,
- .md-typeset .molpack-result-figure--wide img {
- max-height: none;
- }
-}
-
-/* ── content pages (install, quickstart, guides) ─────────────────────── */
-
-.md-typeset .molpack-next-cards {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
- gap: 0.65rem;
- margin: 1rem 0 0.25rem;
-}
-
-.md-typeset .molpack-next-cards > ul {
- display: contents;
- list-style: none;
- margin: 0;
- padding: 0;
-}
-
-.md-typeset .molpack-next-cards li {
- margin: 0;
- padding: 0.85rem 0.95rem;
- border: 1px solid color-mix(in srgb, var(--molpack-accent) 18%, transparent);
- border-radius: var(--molcrafts-radius, 0.45rem);
- background:
- linear-gradient(145deg, color-mix(in srgb, var(--molpack-accent-soft) 40%, transparent), transparent 70%),
- color-mix(in srgb, var(--md-default-bg-color) 94%, var(--molpack-accent-soft));
- box-shadow: 0 0.35rem 1rem color-mix(in srgb, var(--molpack-accent) 8%, transparent);
- transition: border-color 160ms ease, transform 160ms ease, box-shadow 160ms ease;
-}
-
-.md-typeset .molpack-next-cards li:is(:hover, :focus-within) {
- border-color: color-mix(in srgb, var(--molpack-accent) 48%, transparent);
- box-shadow: 0 0.5rem 1.35rem color-mix(in srgb, var(--molpack-accent) 16%, transparent);
- transform: translateY(-1px);
-}
-
-.md-typeset .molpack-next-cards li::marker {
- content: "";
-}
-
-.md-typeset .molpack-next-cards a {
- color: var(--md-default-fg-color);
- font-weight: 650;
- text-decoration: none;
-}
-
-.md-typeset .molpack-next-cards strong a,
-.md-typeset .molpack-next-cards a strong {
- color: var(--molpack-accent-strong, var(--molpack-accent));
-}
-
-.md-typeset table:not([class]) {
- border-radius: var(--molcrafts-radius, 0.45rem);
- overflow: hidden;
- box-shadow: 0 0 0 1px color-mix(in srgb, var(--molpack-accent) 12%, transparent);
-}
-
-.md-typeset table:not([class]) th {
- background: color-mix(in srgb, var(--molpack-accent-soft) 55%, var(--md-default-bg-color));
- color: var(--md-default-fg-color);
- font-weight: 700;
-}
-
-.md-typeset .admonition,
-.md-typeset details {
- border-radius: var(--molcrafts-radius, 0.45rem);
- border-color: color-mix(in srgb, var(--molpack-accent) 28%, transparent);
-}
-
-.md-typeset .admonition.tip,
-.md-typeset details.tip {
- border-color: color-mix(in srgb, var(--molpack-secondary) 40%, transparent);
-}
-
-.md-typeset .tabbed-set {
- border-radius: var(--molcrafts-radius, 0.45rem);
- overflow: hidden;
-}
-
-/* Inline figures in tutorial / parity pages */
-.md-typeset figure.molpack-inline-figure {
- margin: 1.1rem 0;
- padding: 0.75rem;
- border: 1px solid color-mix(in srgb, var(--md-primary-fg-color) 12%, transparent);
- border-radius: var(--molcrafts-radius, 0.45rem);
- background: color-mix(in srgb, var(--md-default-bg-color) 92%, white);
-}
-
-.md-typeset figure.molpack-inline-figure img {
- display: block;
- width: 100%;
- border-radius: calc(var(--molcrafts-radius, 0.45rem) - 2px);
-}
-
-.md-typeset figure.molpack-inline-figure figcaption {
- margin-top: 0.5rem;
- color: var(--md-default-fg-color--light);
- font-size: 0.68rem;
- line-height: 1.45;
- text-align: center;
-}
diff --git a/docs/getting_started.md b/docs/getting_started.md
index c22a0e3..0f06c91 100644
--- a/docs/getting_started.md
+++ b/docs/getting_started.md
@@ -96,12 +96,25 @@ molrs.write_pdb(packed, "water_box.pdb")
## Where next
-
-
-- **[Concepts](concepts/)** — targets, restraints, phases of the packer
-- **[CLI](cli/)** — same job as a Packmol-style `.inp`
-- **[Python guide](python/)** — fixed solutes, PBC, collective restraints
-- **[Rust API](rust/)** — embed the engine in a native crate
-- **[Packmol parity](packmol_parity/)** — what matches Packmol, and what does not
-
+
diff --git a/docs/index.md b/docs/index.md
index 6415309..0b1ac62 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -44,109 +44,79 @@ hero:
molpack
-
+
-
+
-
-
-The [Quickstart](getting_started/) packs a first water box. Use
-[Packmol scripts](cli/) for checked-in input files, [Python](python/) for
-notebooks and pipelines, and [Rust](rust/) when packing is part of a native
-application.
+
+
+ 01
+ Install
+ CLI binary, crates.io crate, or PyPI wheel — pick a surface and verify it loads.
+
+
+ 02
+ Quickstart
+ Pack 100 waters in a 40 Å cube and read convergence diagnostics.
+
+
+ 03
+ Packmol script
+ Run Packmol-style `.inp` jobs with structure, number, inside, fixed, and pbc.
+
+
+ 04
+ Python API
+ Build targets from frames, pack in notebooks, keep results in memory.
+
+
+ 05
+ Rust API
+ Native Target and Molpack builders inside your crate.
+
+
+ 06
+ Handlers
+ Observe steps, dump trajectories, or stop a run early.
+
+
-
+
-
-
-
-
-Spherical confinement — mobile solvent held inside a geometric restraint around a fixed solute.
-
-
-
-Compatibility distributions — collective profile restraints match target density curves at an interface.
-
-
-
-Parallel evaluation — wall time vs system size with the rayon feature enabled on the objective path.
-
-
-
-
-
-
-
-
-
+
-01 · Packmol script
+Path 01 · Script
-### [Run a `.inp` job](cli/)
+### Run a `.inp` job
Use the CLI for reproducible packing jobs that already live as Packmol-style
-input files.
+input files. See the [CLI guide](cli/).
```text
structure water.pdb
@@ -159,12 +129,12 @@ end structure
-02 · Python API
+Path 02 · Python
-### [Pack from a notebook or pipeline](python/)
+### Pack from a notebook or pipeline
Load or build a frame, create immutable targets, and pass the packed frame to
-the writer or analysis code you already use.
+the writer or analysis code you already use. See the [Python API](python/).
```python
water = Target(frame, 100).with_restraint(
@@ -177,12 +147,12 @@ packed = Molpack().with_seed(42).pack([water])
-03 · Rust API
+Path 03 · Rust
-### [Embed the engine in a crate](rust/)
+### Embed the engine in a crate
-Use the native builder API for Rust applications, services, tests, and new
-engine features.
+Use the native builder API for applications, services, and tests. See the
+[Rust API](rust/).
```rust
let frame = Molpack::new()
@@ -194,12 +164,12 @@ let frame = Molpack::new()
-04 · Custom handler
+Path 04 · Handler
-### [Observe or stop a run](rust/handlers-relaxers/)
+### Observe or stop a run
-Handlers receive structured events from the packing loop, so you can log
-diagnostics, dump frames, or request early stop without changing the packer.
+Handlers receive structured events from the packing loop. See
+[Handlers and relaxers](rust/handlers-relaxers/).
```rust
impl Handler for WatchFdist {
@@ -215,41 +185,107 @@ impl Handler for WatchFdist {
-
+
-
-
-
-CLI binary, crates.io crate, and PyPI wheel — pick a surface and verify it loads.
-
-
-
-Pack 100 waters in a 40 Å cube end-to-end, then read convergence diagnostics.
-
-
-
-`.inp` compatibility, formats, path resolution, and CLI examples.
-
-
-
-Targets, restraints, packer options, PBC, examples, and API reference.
+
+
+
Targets
+ Molecule templates, copy counts, fixed placements, centering, and rotation bounds.
+
+
+
Restraints
+ Geometric boxes and spheres, planes, and collective distribution-matching terms.
+
+
+
Packer
+ GENCAN outer loop, pair-distance objective, optional rayon evaluation, seed control.
+
+
+
Scripts
+ Packmol-compatible `.inp` keywords, path resolution, and extra filetypes (SDF, LAMMPS).
+
+
+
Python
+ Frame-in / frame-out packing for notebooks and pipelines via molrs.
+
+
+
Rust
+ Native builders, handlers, relaxers, and feature-gated IO / FF / rayon.
+
+
+
-
-
-Builders, restraint scopes, periodic boxes, handlers, relaxers, examples.
+
+
+
+
+
+
+
+ Tutorial
+ Install, quickstart, concepts, examples, and Packmol parity notes.
+
+
+ Packmol Script
+ `.inp` overview, script notes, formats, and CLI examples.
+
+
+ Python
+ Installation, targets, restraints, packer options, PBC, and API reference.
+
+
+ Rust
+ Builders, restraints, PBC, handlers, relaxers, and example programs.
+
+
+ Development
+ Contributing, architecture, and extending the engine.
+
+
+
+
+
diff --git a/docs/python/index.md b/docs/python/index.md
index f910899..8c671ce 100644
--- a/docs/python/index.md
+++ b/docs/python/index.md
@@ -28,17 +28,39 @@ print(f"packed {frame['atoms'].nrows} atoms")
## Next steps
-
-
-- **[Installation](installation.md)** — pip install and verification
-- **[Quickstart](getting-started.md)** — first pack end-to-end
-- **[Targets](guide/targets.md)** — templates, counts, fixed placement
-- **[Restraints](guide/restraints.md)** — geometric and collective restraints
-- **[Packer](guide/packer.md)** — builder options, diagnostics, handlers
-- **[Periodic boundaries](guide/periodic-boundaries.md)** — PBC rules
-- **[Examples](examples.md)** — Packmol-equivalent workloads
-- **[API reference](api-reference.md)** — class-by-class summary
-
+
## See also
diff --git a/docs/zh/index.md b/docs/zh/index.md
deleted file mode 100644
index 300c0bc..0000000
--- a/docs/zh/index.md
+++ /dev/null
@@ -1,249 +0,0 @@
----
-title: molpack(中文)
-description: 纯 Rust 实现的 Packmol 级分子装填,提供命令行、Python API 和 Rust API。
-hide:
- - navigation
- - toc
-hero:
- kicker: molpack 手册
- title: molpack
- description: 纯 Rust 实现的 Packmol 兼容分子装填引擎 — `.inp` 脚本、Python 包与原生 Rust crate 共用同一套装填模型。
- install:
- label: 安装
- methods:
- - label: CLI
- command: cargo install molcrafts-molpack --features cli
- - label: Rust 库
- command: cargo add molcrafts-molpack
- - label: Python
- command: pip install molcrafts-molpack
- badges:
- - img: https://img.shields.io/crates/v/molcrafts-molpack?color=c2410c&label=crates.io
- href: https://crates.io/crates/molcrafts-molpack
- alt: crates.io 版本
- - img: https://img.shields.io/pypi/v/molcrafts-molpack?color=d97706&label=PyPI
- href: https://pypi.org/project/molcrafts-molpack/
- alt: PyPI 版本
- - img: https://github.com/MolCrafts/molpack/actions/workflows/ci.yml/badge.svg
- href: https://github.com/MolCrafts/molpack/actions/workflows/ci.yml
- alt: CI 状态
- - img: https://img.shields.io/badge/license-BSD--3--Clause-blue.svg
- href: https://github.com/MolCrafts/molpack/blob/master/LICENSE
- alt: BSD-3-Clause 许可证
- actions:
- - label: 快速开始
- href: ../getting_started/
- style: primary
- - label: Packmol 脚本
- href: ../cli/
- - label: Python API
- href: ../python/
- - label: Rust API
- href: ../rust/
----
-
-
molpack
-
-
-
-
-
-
-
-
-
-[快速开始](../getting_started/) 会装填第一个水盒子。有 `.inp` 时用
-[Packmol 脚本](../cli/),在 notebook 与流水线里用 [Python](../python/),嵌入
-原生应用时用 [Rust](../rust/)。
-
-
-
-
-
-
-
-
-
-
-球形约束 — 活动溶剂被限制在固定溶质周围的几何区域内。
-
-
-
-相容性分布 — 集体 profile 约束将界面密度曲线匹配到目标分布。
-
-
-
-并行评估 — 开启 rayon 后墙钟时间随体系规模的变化。
-
-
-
-
-
-
-
-
-
-
-
-
-
-01 · Packmol 脚本
-
-### [运行 `.inp` 任务](../cli/)
-
-CLI 适合已经写成 Packmol 风格输入、需要纳入版本控制的可复现任务。
-
-```text
-structure water.pdb
- number 1000
- inside box 0. 0. 0. 40. 40. 40.
-end structure
-```
-
-
-
-
-
-02 · Python API
-
-### [在 notebook 或流水线中装填](../python/)
-
-加载或构造 frame,创建不可变 target,再把结果交给已有的写出或分析代码。
-
-```python
-water = Target(frame, 100).with_restraint(
- InsideBoxRestraint([0, 0, 0], [40, 40, 40])
-)
-packed = Molpack().with_seed(42).pack([water])
-```
-
-
-
-
-
-03 · Rust API
-
-### [把引擎嵌入 crate](../rust/)
-
-适合原生应用、服务、测试,以及引擎本身的新功能。
-
-```rust
-let frame = Molpack::new()
- .with_seed(42)
- .pack(&[water], 200)?;
-```
-
-
-
-
-
-04 · 自定义 handler
-
-### [观测或停止一次运行](../rust/handlers-relaxers/)
-
-Handler 接收装填循环中的结构化事件,便于记录诊断、导出中间 frame 或请求提前停止。
-
-```rust
-impl Handler for WatchFdist {
- fn on_step(&mut self, info: &StepInfo, _sys: &PackContext) {
- eprintln!("fdist={}", info.fdist);
- }
-}
-```
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-CLI、crates.io 与 PyPI — 选一个入口并验证可导入 / 可运行。
-
-
-
-在 40 Å 立方体中装填 100 个水分子,并读取收敛诊断。
-
-
-
-兼容 `.inp`、文件格式、路径解析与 CLI 示例。
-
-
-
-Targets、restraints、packer、PBC、示例与 API 参考。
-
-
-
-Builder、约束作用域、周期边界、handlers、relaxers。
-
-
-
-
-
-
diff --git a/python/pyproject.toml b/python/pyproject.toml
index 1ab70f2..25964d9 100644
--- a/python/pyproject.toml
+++ b/python/pyproject.toml
@@ -58,11 +58,8 @@ dev = [
# `uv run --group dev tox -e py` from python/.
"tox>=4.23",
]
-# Documentation build. Zensical is the static-site generator; it reads the
-# single site config at ../zensical.toml (the Python binding docs live under
-# ../docs/python/ as the "Python" nav section). That config sets
-# `theme.name = "molcrafts"` — the shared MolCrafts brand extension, now on
-# PyPI, so the docs build is reproducible from this group alone.
+# Documentation build: `zensical build` reads zensical.toml (docs → site).
+# Theme: `theme.name = "molcrafts"` (molcrafts-zensical-theme on PyPI).
doc = [
"zensical>=0.0.51",
# The molcrafts docs theme (zensical.toml sets `theme.name = "molcrafts"`).
diff --git a/python/tests/test_examples_smoke.py b/python/tests/test_examples_smoke.py
deleted file mode 100644
index 55c9a0a..0000000
--- a/python/tests/test_examples_smoke.py
+++ /dev/null
@@ -1,84 +0,0 @@
-"""Smoke tests: every example script compiles and its PDB assets parse.
-
-Full workloads (the actual pack runs) are too large for the unit-test
-suite — run ``python examples/pack_
.py`` to exercise them.
-"""
-
-from __future__ import annotations
-
-import ast
-import sys
-from pathlib import Path
-
-import pytest
-
-HERE = Path(__file__).resolve().parent
-EXAMPLES = HERE.parent / "examples"
-DATA_ROOT = HERE.parent.parent / "examples"
-
-sys.path.insert(0, str(EXAMPLES))
-from _common import read_pdb_as_arrays # noqa: E402
-
-EXAMPLE_SCRIPTS = [
- "pack_water_cube.py",
- "pack_mixture.py",
- "pack_bilayer.py",
- "pack_interface.py",
- "pack_spherical.py",
- "pack_solvprotein.py",
-]
-
-# Maps every example script to the PDB fixtures it depends on.
-EXAMPLE_FIXTURES = {
- "pack_mixture.py": ["pack_mixture/water.pdb", "pack_mixture/urea.pdb"],
- "pack_bilayer.py": ["pack_bilayer/water.pdb", "pack_bilayer/palmitoil.pdb"],
- "pack_interface.py": [
- "pack_interface/water.pdb",
- "pack_interface/chloroform.pdb",
- "pack_interface/t3.pdb",
- ],
- "pack_spherical.py": ["pack_spherical/water.pdb", "pack_spherical/palmitoil.pdb"],
- "pack_solvprotein.py": [
- "pack_solvprotein/protein.pdb",
- "pack_solvprotein/water.pdb",
- "pack_solvprotein/sodium.pdb",
- "pack_solvprotein/chloride.pdb",
- ],
-}
-
-
-@pytest.mark.parametrize("script", EXAMPLE_SCRIPTS)
-def test_example_script_parses(script):
- source = (EXAMPLES / script).read_text(encoding="utf-8")
- ast.parse(source)
-
-
-@pytest.mark.parametrize("script", EXAMPLE_SCRIPTS)
-def test_example_script_has_main(script):
- source = (EXAMPLES / script).read_text(encoding="utf-8")
- tree = ast.parse(source)
- fns = {n.name for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)}
- assert "main" in fns
-
-
-@pytest.mark.parametrize(
- "script,fixtures",
- sorted(EXAMPLE_FIXTURES.items()),
-)
-def test_example_fixtures_exist(script, fixtures):
- for rel in fixtures:
- p = DATA_ROOT / rel
- assert p.exists(), f"{script}: missing fixture {p}"
-
-
-@pytest.mark.parametrize(
- "script,fixtures",
- sorted(EXAMPLE_FIXTURES.items()),
-)
-def test_example_fixtures_parse(script, fixtures):
- for rel in fixtures:
- pos, rad, els = read_pdb_as_arrays(DATA_ROOT / rel)
- assert pos.shape[0] > 0
- assert pos.shape[1] == 3
- assert rad.shape[0] == pos.shape[0]
- assert len(els) == pos.shape[0]
diff --git a/tests/cli.rs b/tests/cli.rs
index 52bae9d..6e91b50 100644
--- a/tests/cli.rs
+++ b/tests/cli.rs
@@ -118,102 +118,6 @@ end structure
);
}
-// ── smoke tests (run actual packing on canonical examples) ──────────────────
-
-/// Pack the mixture example and verify the output file is created.
-#[test]
-fn smoke_pack_mixture() {
- let dir = example_dir("pack_mixture");
- let out_path = dir.join("_ci_mixture.pdb");
- let _ = std::fs::remove_file(&out_path);
-
- // Write a temp .inp that redirects output so we don't touch the committed file.
- let inp = format!(
- "tolerance 2.0\nseed 1234567\nfiletype pdb\noutput {}\n\n\
- structure water.pdb\n number 50\n inside box 0. 0. 0. 20. 20. 20.\nend structure\n",
- out_path.display()
- );
-
- let out = Command::new(bin_path())
- .current_dir(&dir)
- .stdin(std::process::Stdio::piped())
- .stdout(std::process::Stdio::null())
- .stderr(std::process::Stdio::null())
- .spawn()
- .and_then(|mut child| {
- use std::io::Write;
- child.stdin.as_mut().unwrap().write_all(inp.as_bytes())?;
- child.wait_with_output()
- })
- .expect("run molpack");
-
- assert!(
- out.status.success(),
- "molpack exited {:?}\nstdout: {}\nstderr: {}",
- out.status,
- String::from_utf8_lossy(&out.stdout),
- String::from_utf8_lossy(&out.stderr)
- );
- assert!(
- out_path.exists(),
- "output file not created: {}",
- out_path.display()
- );
-
- // Clean up.
- let _ = std::fs::remove_file(&out_path);
-}
-
-/// Regression: a script with only a `pbc` directive and no `inside`
-/// restraint must pack in O(seconds). Before the parser fix, `pbc`
-/// was silently dropped and the packer fell back to a 2000 Å cell
-/// grid (~10⁸ cells, 42 GB). With `pbc` wired through, the cell grid
-/// is sized from the PBC box and the job finishes immediately.
-#[test]
-fn smoke_pack_pbc_only_finishes_quickly() {
- let dir = example_dir("pack_mixture");
- let out_path = dir.join("_ci_pbc_only.pdb");
- let _ = std::fs::remove_file(&out_path);
-
- let inp = format!(
- "tolerance 2.0\nseed 1234567\nfiletype pdb\noutput {}\n\
- pbc 30.0 30.0 30.0\n\n\
- structure water.pdb\n number 10\nend structure\n",
- out_path.display()
- );
-
- let start = std::time::Instant::now();
- let out = Command::new(bin_path())
- .current_dir(&dir)
- .stdin(std::process::Stdio::piped())
- .stdout(std::process::Stdio::null())
- .stderr(std::process::Stdio::piped())
- .spawn()
- .and_then(|mut child| {
- use std::io::Write;
- child.stdin.as_mut().unwrap().write_all(inp.as_bytes())?;
- child.wait_with_output()
- })
- .expect("run molpack");
- let elapsed = start.elapsed();
-
- let _ = std::fs::remove_file(&out_path);
-
- assert!(
- out.status.success(),
- "molpack exited {:?}\nstderr: {}",
- out.status,
- String::from_utf8_lossy(&out.stderr)
- );
- // Previously this configuration hung indefinitely allocating cells.
- // Giving ourselves a generous 30 s ceiling still catches a regression
- // without being flaky on a loaded CI runner.
- assert!(
- elapsed.as_secs() < 30,
- "pbc-only pack should finish fast; took {elapsed:?}"
- );
-}
-
/// Verify file-argument mode resolves paths relative to the .inp directory.
#[test]
fn file_arg_resolves_paths_from_inp_dir() {
diff --git a/zensical.toml b/zensical.toml
index ec031b4..c7d6afe 100644
--- a/zensical.toml
+++ b/zensical.toml
@@ -1,21 +1,17 @@
+# molpack docs — single English site served at the root.
+#
+# zensical build # docs → site
+
[project]
site_name = "molpack"
site_description = "Packmol-grade molecular packing in pure Rust, with Python bindings."
-# Site is built by Cloudflare Pages (pip install "molcrafts-molpack[doc]" + zensical).
-# Keep site_url in sync with the Cloudflare custom domain when one is set.
-site_url = "https://molcrafts.github.io/molpack/"
+site_url = "https://molpack.molcrafts.org/"
repo_url = "https://github.com/MolCrafts/molpack"
repo_name = "MolCrafts/molpack"
copyright = "Copyright © 2026 MolCrafts"
docs_dir = "docs"
site_dir = "site"
-# Markdown extensions are intentionally not listed: Zensical's built-in default
-# set (admonition, attr_list, def_list, footnotes, md_in_html, toc permalink,
-# pymdownx.{arithmatex,details,emoji,highlight,inlinehilite,superfences,tabbed,
-# tasklist}, and more) already covers everything these docs use — the CLI/Rust/
-# Python tabbed blocks, admonitions, and code annotations.
-
nav = [
{ "Home" = "index.md" },
{ "Tutorial" = [
@@ -58,32 +54,15 @@ nav = [
extra_css = ["assets/stylesheets/extra.css"]
-# Shared MolCrafts theme (github.com/MolCrafts/molcrafts-zensical-theme). Provides
-# the brand palette, light/dark schemes, navigation features, and the home-page
-# hero/manual-home component system used by docs/index.md. The package must be
-# installed to build (see the `doc` dependency group in python/pyproject.toml).
+# https://zensical.org/docs/setup/language/#site-language
[project.theme]
name = "molcrafts"
language = "en"
-# Language selector. English is the default and lives at the site root; a
-# Chinese (zh) tree lives under docs/zh/. Zensical renders the header dropdown
-# from `extra.alternate` (theme partial partials/alternate.html). The `url`
-# filter passes absolute paths through unchanged, so each `link` carries the
-# site's base path (`/molpack/`, matching site_url) to stay correct from every
-# page and depth. The zh pages are placeholders for now — no docs are
-# translated yet.
-[project.extra]
-alternate = [
- { name = "English", link = "/molpack/", lang = "en" },
- { name = "简体中文", link = "/molpack/zh/", lang = "zh" },
-]
-
-# Selects the molpack accent (rust orange) for links, hovers, and hero eyebrows.
[project.extra.molcrafts]
product = "molpack"
-accent = "#c2410c" # primary (主) — molpack signature colour
-accent_soft = "rgba(194, 65, 12, 0.14)" # secondary (副) — soft fill behind it
+accent = "#c2410c"
+accent_soft = "rgba(194, 65, 12, 0.14)"
[[project.extra.social]]
icon = "fontawesome/brands/github"