Skip to content

Latest commit

 

History

39 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

================================================================================
  Template-free fullerene fitting for segmented cryo-ET meshes
  (HIV-1 capsid core -> best-fitting TRUE fullerene cage)
================================================================================

PROBLEM
-------
Given a low-resolution triangle mesh from segmented cryo-ET data of an HIV-1
capsid core, produce the best-fitting TRUE fullerene: a closed cage of exactly
12 pentamers + N hexamers in which every tile is a rigid, near-regular polygon
(equal edges, equal internal angles). The pentamer (5-fold) positions are
dictated by the mesh curvature; the hexamer count H is chosen from a range; and
the absolute tile size is a FREE parameter, because the cryo-ET magnification is
not known a priori. Fitting is therefore a 7-DOF alignment (rotation +
translation + uniform scale) of a self-consistent, intrinsically-shaped cage
onto the mesh -- NOT a warp of a cage onto the surface.

The method is deliberately TEMPLATE-FREE so it generalises to many segmented
meshes: nothing about a specific known fullerene is assumed. Global optimality
is not claimed; the deliverable is the best structure found under a systematic
search, together with the reported plateau of near-ties.


APPROACH (why it works)
-----------------------
1. Topology is fixed by Gauss-Bonnet. For a genus-0 surface the total Gaussian
   curvature is 4*pi. In a trivalent hexagonal tiling every pentagon is a +60deg
   (pi/3) disclination and every hexagon is intrinsically flat, so a closed
   fullerene ALWAYS has exactly 12 pentagons (Euler): 12 * pi/3 = 4*pi. The cage
   size varies (number of hexagons), the pentagon count never does.

2. Shape is set by WHERE the 12 disclinations sit. Because hexagons carry no
   Gaussian curvature, the gross shape of a regular-tiled fullerene is determined
   by the placement of its 12 pentamers; the hexamers merely tile the flat
   regions between them. We therefore place the 12 pentamers at the mesh's 12
   "bowl" points -- local elliptic maxima where both principal curvatures are
   positive -- and let the tiling fill in around them.

3. The tiling itself comes from field-aligned remeshing. A curvature-aligned
   6-way (hexagonal) orientation field over the mesh, extracted at a chosen
   target face count, yields a triangulation whose vertices are the capsomers;
   its dual is the fullerene cage. Valence-5 vertices are pentamers, valence-6
   are hexamers.

4. Selection + regularisation + fit. Because the remesher is stochastic, we run
   a search over hexamer count H and multiple random starts, score each candidate
   against the bowl targets, dualise + relax the best to rigid regular tiles, and
   align to the mesh with a 7-DOF (orientation-resolved) similarity transform.


ALGORITHM & SOURCES
-------------------
Stage 0  Mesh prep (prep_mesh.py [+ subdivide_mesh.py])
    Read binary STL (per-triangle soup -> welded index) or binary PLY (already
    indexed). Verified via Euler characteristic V-E+F = 2 and zero non-manifold
    edges. COARSE inputs (< ~1000 verts) are auto-Loop-subdivided to >= ~4000
    verts: on a coarse mesh each vertex carries a big lump of curvature, so the
    pi/3 charge partition overshoots (imprecise bowl markers) and the surface is
    faceted; Loop subdivision (subdivide_mesh.py) refines + smooths toward the
    limit surface, fixing both. Fine meshes are left untouched.

Stage 1a Per-vertex curvature (principal_curvature.py)
    Discrete Gaussian curvature K = angle deficit / mixed area; mean curvature H
    from the cotangent Laplacian (signed by the outward normal); principal
    curvatures k1,k2 = H +/- sqrt(H^2 - K); "bowl" = min(k1,k2) (>0 iff elliptic).
    Ref: Meyer, Desbrun, Schroeder, Barr, "Discrete Differential-Geometry
    Operators for Triangulated 2-Manifolds", VisMath III (2003).

Stage 1b Bowl-anchored pentamer placement (charge_partition_bowl.py)
    Partition the surface into 12 geodesic basins each accumulating pi/3 of
    Gaussian curvature (topology-safe "charge quantisation"); place each pentamer
    site at its basin's BOWL PEAK (min(k1,k2)-weighted centroid, saddles clamped
    to 0). Deterministic: identical sites every run. This is the scoring/selection
    target for the search.
    Concept ref (disclinations & crystalline order on curved surfaces):
    D. R. Nelson, "Defects and Geometry in Condensed Matter Physics" (2002);
    Bowick & Giomi, Adv. Phys. 58 (2009) 449.

Stage 2  Field-aligned tiling + search (full_search.py + InstantMeshesBatch)
    For each H in [220,250] and several random starts, run the field-aligned
    remesher at face count 2H+20 (rosy=6, posy=6). Cheap metrics on each raw
    tiling: number of valence defects (!=5,6) and pentamer-placement error
    pent_err = mean over the 12 bowl targets of the distance to the nearest
    valence-5 capsomer, in units of hexamer diameter. Shortlist (gate defects,
    then pent_err), then for each shortlisted candidate build the dual cage,
    relax to regular tiles, and 7-DOF fit; composite rank prefers defect-free,
    then low pent_err, then low Hausdorff. Reports the representative and the
    near-tie band.
    Ref: W. Jakob, M. Tarini, D. Panozzo, O. Sorkine-Hornung, "Instant
    Field-Aligned Meshes", ACM TOG 34(6), SIGGRAPH Asia 2015.
    Binary: instant-meshes/InstantMeshesBatch  (headless batch driver built from
    github.com/wjakob/instant-meshes; native arm64, real oneTBB).

Stage 3  Finalise (finalize_rep.py)
    Dual of the winning tiling -> cage. Harmonic force-field relaxation
    (bond-stretch to a common edge length L0 + angle-bend to 120deg hexagons /
    108deg pentagons; 8000 vectorised steps) makes the tiles rigid and near-
    regular. 7-DOF fit to the mesh WITH ORIENTATION RESOLUTION: because the two
    short principal axes of a spindle are near-degenerate and PCA axes are only
    defined up to sign/reflection, the fit searches the short-axis swap x signs
    and keeps the assignment minimising cage->mesh distance (see NOTES). Reports
    regularity (bond CV, angle deviations) and fit (mean %, Hausdorff95 %).

Stage 4  Roll alignment (roll_align.py)
    The similarity fit leaves the roll about the long axis unconstrained; this
    optimises that 1 DOF to register the cage pentamers onto the bowl sites, then
    writes viewer_data.json + /tmp/render_data.json for visualisation.

Stage 5  Canonical RSPI (optional; run_fullerene.sh + Fullerene v4.5)
    Feeds the relaxed cage to the Fullerene program to obtain the canonical
    ring-spiral pentagon indices (RSPI) and independent topology validation.
    Ref: P. Schwerdtfeger, L. N. Wirz, J. Avery, "Program Fullerene: A software
    package for constructing and analyzing structures of regular fullerenes",
    J. Comput. Chem. 34 (2013) 1508. RSPI: Fowler & Manolopoulos, "An Atlas of
    Fullerenes" (Oxford, 1995).

Background / motivation:
    HIV-1 capsid fullerene-cone model: Ganser, Li, Klishko, Finch, Sundquist,
    "Assembly and Analysis of Conical Models for the HIV-1 Core", Science 283
    (1999) 80; Zhao et al., Nature 497 (2013) 643.
    Why extra 5-7 "scars" appear on curved crystals (and why we reject them for a
    TRUE fullerene): Bausch et al., "Grain Boundary Scars and Spherical
    Crystallography", Science 299 (2003) 1716.


METRICS
-------
  pent_err     mean bowl-target -> nearest pentamer distance, in hexamer diameters
               (hexamer diameter estimated from mesh area assuming ~242 hexamers)
  fit mean     mean cage->mesh nearest-point distance / mean mesh radius, in %
  Hausdorff95  95th percentile of two-sided nearest distances / mean radius, in %
  defects      count of capsomers with valence != 5 or 6 (must be 0 for a TRUE
               fullerene; a nonzero value = a 5-7 scar pair -> REJECT)
  IPR          isolated-pentagon rule: no two pentamers share an atom/edge


HOW TO USE
----------
Dependencies:
  * Python venv (.venv) with numpy + scipy.
  * instant-meshes/InstantMeshesBatch  (prebuilt headless binary; see that dir).
  * (optional) Fullerene v4.5 + the conda "fort" env, driven by run_fullerene.sh,
    for canonical RSPI. Skipped automatically if unavailable.
  * (optional) Blender + Blender-MCP for visualisation.

Single-mesh run (end to end):
    ./run_all.sh mesh4a.stl
  Produces:
    /tmp/mesh4a.npz, /tmp/mesh4a.obj      prepared mesh
    /tmp/curv.npz                         per-vertex curvature
    /tmp/pentamers.npz                    12 bowl-anchored pentamer sites
    /tmp/search_result.json               winning tiling + near-tie band
    viewer_data.json                      final cage (atoms, bonds, faces, fit)
    mesh4a_C<N>_representative.xyz         relaxed cage coordinates (N carbons)
  Typical wall time: ~1-2 min (search ~20-60s, relax/fit a few s).

Variation study (N independent instances):
    NRUNS=10 python ensemble_run.py
  Placement is deterministic; the variation source is the stochastic remesher.
  Writes /tmp/ensemble/run_1..N.json + summary.json with per-run C, H, defects,
  IPR, fit, Hausdorff, pent_err (belly/tip), aspect. Auto-flags scar rejects.

To run on a DIFFERENT mesh: pass its STL to run_all.sh. prep_mesh.py welds and
manifold-checks it; everything downstream is generic. (For non-spindle shapes,
widen the H grid in full_search.py so 2H+20 spans the expected carbon count.)

Pseudo-atomic capsid (mesh -> atomic model):
    python build_atomic_capsid.py mesh4a_C<N>_representative.xyz mesh4a.stl
  Decorates the fitted cage with real CA capsomers (RCSB 3H47 hexamer + 3P05
  pentamer, auto-fetched to ./capsomer_templates/): one per cage face, symmetry
  axis on the local facet normal, subunits rotated toward the inter-capsomer
  2-fold edges. All steric overlap is then removed by tethered rigid-body
  relaxation (Calpha pass then all-heavy-atom pass; capsomers move only ~1-2 A /
  a few degrees). Result: no inter-capsomer heavy-atom contact < 2.6 A at native
  ~95.5 A spacing. The whole model is finally reframed into the SOURCE-MESH frame
  (original cryo-ET coordinates) so the input mesh overlays it directly. Writes,
  in that shared Angstrom frame:
    <prefix>_capsid_atomic.pdb    all-atom (~2.3M atoms; hybrid-36 serials,
                                  segID cols 73-76 = capsomer Hnnn/Pnnn)
    <prefix>_capsid_atomic.cif    same as mmCIF (unique chain id per subunit)
    <prefix>_capsid_backbone.pdb  N,CA,C,O only; pentamers flagged segID P* and
                                  B-factor=100 (color-by-B shows the 12 pentamers)
    <mesh>_surface.obj            the source mesh (same frame; overlays the model)
  Uses the repo .venv (numpy+scipy); needs network on first run for the templates.
  The multi-hundred-MB PDB/CIF are .gitignore'd (>GitHub's 100MB limit) -- they
  regenerate deterministically from this one command.

  Space-saving instance form (--emit transforms): all capsomers are copies of the
  same two canonical capsomers, so instead of ~180MB of coordinates you can keep a
  ~50KB <prefix>_capsid_transforms.json (one rigid operator per capsomer,
  world = R @ canonical_template + t) plus the shared canonical templates. Rebuild
  the full PDB/CIF/backbone on demand:
      python build_atomic_capsid.py <cage>.xyz <mesh> --emit transforms
      python expand_capsid.py <prefix>_capsid_transforms.json [--cif] [--backbone]
  (--emit both, the default, writes the full coordinates AND the transforms.)


RESULTS ON mesh4a (HIV-1 capsid core, spindle, asphericity 2.60)
----------------------------------------------------------------
Over a 10-instance ensemble through the finalised pipeline:
  * fit mean       3.7 - 4.0 %   (mean 3.83, std 0.08)
  * Hausdorff95   ~10 %          (~6.5 % one-sided)
  * pent_err       0.49 - 0.70 hexamer-dia (mean ~0.6); belly comparable to tips
  * shape          aspect ~ 0.50 / 0.47 EVERY run (set by deterministic bowl
                   placement; reproducible independent of the stochastic tiling)
  * size           a bimodal plateau, C ~ 482 - 516 (H 231 - 242); no unique
                   winner -- a resolution-limited band of near-equivalent cages
  * topology       ~85 % of runs are TRUE fullerenes (12 isolated pentagons,
                   defect-free); the rest carry a 5-7 scar pair and are rejected
                   on defects>0.

Shape-dependent difficulty (observed across meshes): pentamer-placement precision
and defect-free yield both scale with how strongly the shape LOCALIZES curvature.
Cones/spindles with sharp tips (mesh4a, mesh5) pin the disclinations tightly
(pent_err ~0.4-0.6) and tile defect-free easily (~85%). Near-SPHERICAL targets
(cage_7, aspect ~0.96/0.85) are the hard case: curvature is nearly uniform so
disclinations barely localize (pent_err ~1.2) AND the field-aligned tiling tends
to nucleate 5-7 grain-boundary scars (Bausch-Bowick regime), making a clean
12-disclination (true) fullerene tiling rare -- it may take many multistart draws
to find one, and the search escalates automatically (full_search.py, cap via argv).
The gross-shape fit stays good (~3.4%) regardless; it is the strict pentagon-only
TOPOLOGY that is hard on a sphere.

Structural ceiling: a regular pentagon+hexagon-only fullerene can represent only
non-negative Gaussian curvature and relaxes slightly rounder than the mesh
(aspect 0.50 vs 0.43); the belly of the capsid carries some negative (saddle)
curvature (~21 % of vertices) that no TRUE fullerene can reproduce. This caps the
SURFACE fit at ~4 %, but does NOT displace the pentamers off the bowls. Matching
the belly exactly would require 5-7 scars (a defected cage), which is outside the
"true fullerene" definition adopted here.


FILE MAP
--------
Working pipeline (in run order):
    prep_mesh.py            0   STL/PLY -> manifold npz/obj (+ auto-subdivide coarse)
    subdivide_mesh.py       0b  Loop subdivision (refine+smooth); used by prep + CLI
    principal_curvature.py  1a  per-vertex K, H, k1, k2, bowl
    charge_partition_bowl.py 1b 12 bowl-anchored pentamer sites (deterministic)
    full_search.py          2   H-scan x multistart search + composite ranking
    finalize_rep.py         3   dual + FF relax + orientation-resolved 7-DOF fit
    roll_align.py           4   close roll gauge freedom; write viewer/render data
    run_fullerene.sh        5   canonical RSPI via Fullerene v4.5 (optional)
    build_atomic_capsid.py  6   decorate cage with CA capsomers (3H47/3P05) ->
                                clash-free pseudo-atomic capsid PDB/mmCIF/backbone
    expand_capsid.py        6b  rebuild the full capsid from a --emit transforms JSON
    ensemble_run.py             N-instance variation study
    run_all.sh                  orchestrates stages 0-5
    instant-meshes/             field-aligned remesher (see build notes there)

Support / earlier exploration (not in the critical path):
    charge_partition.py         equal-charge-CENTROID placement (superseded by
                                the bowl-peak version)
    orient_field.py             trivial-connections 6-RoSy field with prescribed
                                singularities (Crane-Desbrun-Schroeder, SGP 2010);
                                explored, not used by full_search
    heal_final.py               flip-based defect annealer (worked around by
                                defect-free multistart)
    search.py, scan_H.py, geo_fullerene.py, cap_tube*.py, build_*.py,
    field_tile*.py, relax_*.py, scratch_*.py, ...   dead-end / diagnostic scripts

Visualisation note: cages saved by the FINALISED pipeline are correctly oriented
(align to the mesh under identity). Cages saved by pre-fix runs need a short-axis
swap (equivalently, in Blender, scale Y by -1 then rotate X by -90deg, which
composes to (x,y,z)->(x,z,y)) to align -- see the orientation discussion in NOTES.


NOTES
-----
Orientation resolution (important). The 7-DOF fit aligns principal axes and
uniformly scales. Two ambiguities can otherwise corrupt the result silently:
(1) the spindle's two short axes are near-degenerate (0.50 vs 0.47), so their
ORDER can come out swapped versus the mesh; (2) PCA axes are defined only up to
sign, and a fullerene fits its mirror image equally well. finalize_rep.py resolves
this by searching the short-axis swap x sign assignments and keeping the minimum
cage->mesh distance. Skipping this lands on a reflected/rolled fit that scores
almost as well on nearest-distance but is visibly rotated and inflates every
metric (observed: fit 5.7% vs the correct 3.8%; belly pent_err 1.34 vs 0.6).
================================================================================

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages