Skip to content

Changelog

17.0 · October 2026

A lab server. The same code now runs on a private server with many cores (built for 22 cores and 128 GB), with one setting, --profile:

  • vps (the default): the public 2-core server, unchanged.
  • lab: the chemistry runs in worker processes, one per core minus two. Fragments go up to 12 000 atoms with hydrogens, SMILES to 250 atoms, files to 256 MB, ligand pockets to 12 Å. Batches are on.
  • auto: lab with at least 8 cores and 32 GB.

Every limit can still be set by a flag. Forecast first, then the measurements: the forecast, the experiment, the guide.

Requests in parallel. One Python process runs one thread of Python at a time, so the old server used one core however many it had. Under lab:

  • The front process handles HTTP. /api/site, /api/model, /api/pi-model and the SVG render go to worker processes (compute_pool.py).
  • Routing. A structure (by its SHA-256) or a SMILES goes to the worker that already holds it, when that worker is free (rendezvous hashing, two choices); otherwise to any free worker.
  • Answers. The worker returns the answer compressed, the same as one process would give apart from the timing.
  • Two requests at once took 1.02–1.07× the time of one, on 2 cores.

Batch surveys of ligands.

  • POST /api/batch and, for a folder of files without the server, tools/lab/batch_ligands.py. Each ligand gets its route, whether the other routes agree, its links, metals and pocket, and optionally its π systems (Hückel x of HOMO and LUMO). Then a summary per component.
  • On /sites: "Survey every ligand in this file", and "Survey these N binding sites" in the GL1X index. A row opens its ligand; the survey downloads as CSV or JSON.
  • Speed: the 156 ligands of the 23 biotite test files take 2.95 s on one core and 1.72 s on two.
  • One molecule, many tautomers: the summary counts molecules by heavy-atom graph and total hydrogens, so the 18 haems of 19HC, read in six tautomeric forms, are one molecule.

A local PDB dictionary. --ccd-file components.cif.gz reads the wwPDB's whole Chemical Component Dictionary, so the dictionary route needs no network. The file is indexed once (about 4 s for 400 MB) and memory-mapped, so 20 workers share one copy; a component is read in about 0.1 ms.

For everyone, the public server included.

  • A file is sent once. Answers carry its SHA-256, and /sites sends structure_ref instead of the file from then on: 142 bytes per click instead of the whole structure. If the server has let it go (structure_unknown), the page sends it again.
  • The pocket radius of a ligand can be set (3.5–6 Å; 12 Å on a lab server), in the page and as pocket_radius.
  • GET /api/server-status reports the profile, the machine, the workers and the caches.
  • Large answers are compressed at gzip level 1 above 4 MB.
  • Fix. A long bound SMILES in the ligand report no longer widens the page on a phone.

16.5.1 · October 2026

A quick first view on /sites, and the settings of a ligand no longer leak into a motif.

  • A ligand opens on its quick view:

    • its lone pairs (with a zinc: the donor pairs and the zinc's open site);
    • the π HOMO of each of its π systems.

    For XK2 in 1HVR that is about 60 000 triangles in 0.2 s, instead of all 184 orbitals (about 630 000 triangles). Those had made the page drop to low quality for good.

  • Each mode keeps its own orbital set and π field. Before, going back from a ligand to a motif drew every orbital of the fragment: 2.5 million triangles, which Mol* then refused.

  • New π fields: the HOMO and LUMO of each π system, as on /molviewer. A switch "π of the bound molecule only" replaces the old coupling to the orbital menu.

  • Mesh quality: when the surfaces would overrun Mol*, that one draw uses low quality and says so; the control keeps its value.

  • The legend shows only what is drawn. "Ligand" now means only the bound molecule; the metal's residues are "metal-binding".

Server.

  • OPSIN reads a ligand's name only when no dictionary fits (name_to_smiles: "always" for the check). Each new name starts Java.
  • The RCSB fetch waits at most 5 s and, after a failure, is not retried for 10 minutes. It used to hold one of the two compute slots for up to 25 s.
  • The caches count memory as measured: about 700 B per structure atom and 9 kB per fragment atom. Before it was 200 B and a flat 1.5 MB per site, which let them grow past their budget on the 3 GB server.

Tidying.

  • Documentation: ARCHITECTURE.md shows /sites and /api/site and lists the ligand tests; the API reference names the ligand fields and error codes.
  • Dead code removed: unused wrappers in molviewer.js and unused imports.
  • Versions: each browser module carries the release in which it last changed.

16.5 · October 2026

Ligands on /sites. Pick a bound ligand as you pick a motif. The page cuts it out with its pocket and a zinc it binds, and draws its orbitals where it sits in the protein. It also says which molecule it is.

  • Four routes to its SMILES, each matched onto the atoms in the file:

    • the file's own component dictionary;
    • the PDB's Chemical Component Dictionary, fetched by your browser;
    • the name, read by OPSIN;
    • the geometry alone.

    The report shows where they agree. The dictionary's tautomer is used; the geometry is used where no dictionary fits (a wrong code, a "LIG", an incomplete model).

  • The free molecule opens on the other pages: /molviewer, /molecule, /pka-density, /catalogue, with stereo read from the coordinates.

  • Bonds to the protein are found: N-linked sugars, acyl-enzymes, thioester chromophores, haem c. The dictionary's leaving atoms are then accounted for, and incomplete models are named.

  • GL1X's ligand index: search a ligand, pick a binding site, and its MolNFT record opens on that ligand (/sites?gl1x=…&ligand=HEM:A:154).

  • Three new examples: HIV protease with a cyclic urea (1HVR), the progesterone receptor (1A28) and a nine-haem cytochrome c (19HC).

  • POST /api/site takes ligand, smiles, pocket; every answer now lists the structure's ligands. See Ligands.

The experiment (forecast, results).

  • Test set: 104 ligand instances against the PDB dictionary.
    • The first run missed the forecast for aromatic ligands: 6/11 against ≥ 75 %. Amino groups on rings were read as imines.
    • After a chemical rule and links to the protein: 78/78 complete instances right, and 151/151 stereocentres as in the file.
  • Held-out set: 156 instances, scored against the bond tables in biotite's test files.
    • Components new to the rules: 87/92, then 90/92 after amide/lactam/keto preferences.
    • The two misses: FMN, whose deposited geometry is the lactim, and a corrin.
  • Haem: every test haem turned out to be haem c, which the forecast had not foreseen.

Also:

  • The structure readers keep component names, formulas and bond tables (mmCIF/BinaryCIF _chem_comp*, PDB HETNAM/FORMUL) and the declared covalent links (LINK, _struct_conn).
  • The SMILES reader keeps @/@@ with the neighbour order they refer to.
  • In the Mol* tab, mesh quality drops to "low" by itself when the surfaces would be too large.
  • New server option: --no-ccd-fetch.

Not tested here: data.rcsb.org, GL1X's ligand index, OPSIN and the real viewers. Stand-ins were used, in the formats the code expects. See DEPLOYMENT.md.

16.4 · October 2026

Orbitals at a protein site: /sites.

  • Find a motif in any structure and cut out the matched stretch, capped and protonated, with its metal. Every orbital of the fragment is drawn on the whole protein, in Mol* or 3Dmol.js, in the protein's own frame.
    • Structures: a file, or a GL1X MolNFT record (PDB, mmCIF, BinaryCIF; gzip and base64 handled).
    • Motifs: PROSITE patterns with ranges (x(3,5)), which GL1X does not take. ADR1's zinc finger is H-x(4)-H, so C-x(2)-C-x(12)-H-x(3)-H misses it and C-x(2,4)-C-x(12)-H-x(3,5)-H finds it.
  • The metal bond is drawn from both sides:
    • each ligand's donated pair, a Coulson hybrid toward the zinc;
    • zinc's empty 4s/4p acceptor orbitals, by maximum overlap with those pairs;
    • with three ligands, the leftover open-site orbital, pointing where the catalytic water binds.
  • The site report gives:
    • each ligand's distance against Harding's targets, its angles, the donor–acceptor overlap and the confidence (B-factor or pLDDT);
    • protonation states;
    • for a zinc in the file, where the ligands would hold one at ideal distances.
  • Without a metal in the file (every AlphaFold model), zinc is placed by least squares from the motif's Cys/His/Asp/Glu. On ACE2 (7DDO) the placed zinc lands 0.30 Å from the deposited one.
  • Links in both directions:
    • into /sites: /sites?gl1x=<contract>/<token>&motif=…&chain=…;
    • back out: "Open on GL1X" opens the record with its find bar set.
  • POST /api/site and GET /api/site-health; up to 1500 atoms with hydrogens (--max-site-atoms), two sites at a time.

Zinc.

  • Zn²⁺ is on the ion buttons of /molviewer.
  • Its radial functions are the virtual 4s/4p of Hartree–Fock Zn²⁺: ε = −0.605 and −0.408 hartree against the measured −0.660 and −0.437. Five forecasts on zinc, written before the calculation, all held. See the experiment.

Scale.

  • A 384-atom zinc finger took 71 s in the orbital layer and now takes 1.1 s. Three parts grew faster than the atom count, and all three are fixed:
    • the ring search;
    • a coordinate check asked once per atom;
    • the π layer's neighbour search.
    • 5000 atoms now take 5.5 s on one core.
  • In the browser, surfaces are welded by grid edge and radial functions come from tables: every orbital of the zinc finger (752) draws in 1.9 s instead of 4.4 s.
  • /molviewer shares the code (qo-orbitals.js), and its limit rose from 120 to 1500 orbitals.

Not tested here: the real Mol* and 3Dmol.js bundles, and gl1x.com itself (both unreachable from the sandbox). Stand-ins checked what the page sends and in which frame; the first-run checklist is in DEPLOYMENT.md in the package.

16.3.1 · October 2026

Orbital surfaces: outward-facing, closed, smooth.

The problem: each triangle's winding followed the order of the tetrahedron corners, so about half of the triangles faced into their lobe (54% and 46% outward on a p orbital), and each carried a flat normal.

  • 3Dmol lit that half from behind, which looked like twisted bands.
  • Mol* hides back faces, so it showed shells of shards.

The fix: the isosurfaces now come from qo-mesh.js.

  • Every triangle faces out of its lobe.
  • Shared corners are welded, with smooth normals.
  • Every surface is closed (Euler characteristic 2).

Finer grids where the surface is.

  • Each orbital is sampled on a box that just holds its lobe, at a fixed spacing (0.35 / 0.25 / 0.15 Å for low / medium / high), instead of 13³ points across ±2–3.5 Å.
  • Each π system gets its own grid (0.24 / 0.16 / 0.11 Å), so a long molecule with several separate π systems is no longer covered by one coarse box. Mol* receives one cube per π system.

node tests/test_mesh.js checks closure, orientation and normals on a sphere, both signs of a p orbital and a real π HOMO. Details: Spherical-harmonic orbital surfaces.

16.3 · October 2026

Mol* next to 3Dmol.js.

  • /molviewer has a second tab: the Mol* viewer (Sehnal et al., 2021), for proteins and volumes: PDB entries, sequence view, selections, measurements.
  • The molecule arrives in the same coordinates, with the same surfaces:
    • π density, interference and π MOs as Gaussian cube volumes, which Mol* contours itself;
    • orbital surfaces and the potential-coloured π surface as one PLY mesh.
  • Mol* atoms are read back and compared with /api/model (the R pill). A measured offset would move every surface with the atoms.
  • The bundle loads only when the tab is opened. install_molstar.sh self-hosts it; until then the pinned 4.5.0 comes from cdn.jsdelivr.net.

Files. Three buttons write what the viewer shows, in the MolBlock's frame, so the files line up in Mol*, VMD, PyMOL, Jmol or Blender:

  • π field .cube: bohr, atomic units, atoms included;
  • surfaces .ply: Å, RGBA per vertex, welded vertices;
  • molecule .mol: the conformer.

See Mol* and files.

Tests. node tests/test_export.js (cube and PLY round trips on real π fields) and node tests/test_molstar_bridge.js (what the tab sends to Mol*, including an engine that moves the molecule). Not yet run against the real Mol* bundle.

16.2 · October 2026

Every proton, at every pH.

  • /pka-density titrates all ionisable sites of a molecule together (up to six, all 64 protonation microstates). Each charged group makes it harder for its neighbours to lose a proton, or easier for opposite charges, as Bjerrum explained in 1923.
  • Below the map:
    • the fraction of each net charge from pH 0 to 14 (click or drag to set the pH);
    • every deprotonation step, with the sites that take part;
    • each site's charge at the chosen pH.
  • In solution mode the density and potential maps are the population-weighted mixture over every site. See Solution pH.
  • Site pKa values and the pair term are fitted on 2832 molecules of the IUPAC Digitized pKa Dataset (reproduced by permission of IUPAC, CC BY-NC 4.0; the data is not shipped). Held-out error, against today's textbook-value model:
    • one site: 2.33 → 1.71;
    • several sites: 2.21 → 1.40 per step;
    • textbook parents (acetic acid, phenol, methylamine, pyridine…) keep their measured values.
    • Forecast first, then the test: the forecast and pKa of every site.
  • /api/pi-model returns a speciation block: site pKa ± held-out error, pair terms, macroscopic steps. /api/model's protonation uses the same model; /api/pi-density-health reports it.

Chemistry fixes.

  • Pyrrole- and indole-type N–H is no longer typed as a base. Tryptophan had a spurious "aniline" site at pKa 4.6.
  • Imidazole means a 1,3-diazole only: pyrazole, triazoles, oxazole and thiazole are no longer imidazoles.
  • Tetrazole is recognised as an N–H acid.
  • qo_chem.smiles_lite reads aromatic SMILES (kekulized), stereo marks and bracket atoms, so the chemistry layers run without RDKit on real-world SMILES.

Page views are counted with GoatCounter (lcjoe.goatcounter.com). No cookies, no /api/; switch off with --goatcounter off or QO_GOATCOUNTER=off.

Layout. No horizontal scroll on phones on /pka-density. The molecule rail stays in its own scroll strip.

Tools. tools/iupac/fit_site_model.py (fit and test, no RDKit) and tools/iupac/pi_line_benchmark.py (the π lines against the same data, on the server's conformers).

16.1 · October 2026

One radial function per orbital, everywhere.

  • The valence radial functions of 16 elements are now free-atom Hartree–Fock orbitals fitted by short Slater sums (qo_chem/data/radial_fits.json). The orbital layer's overlaps, the π maps and the 3D viewer all read this one table. Before, the orbital layer used Slater's rules and the viewer hydrogenic functions, with bromine and iodine copying chlorine's charge. See Radial functions: an experiment.
  • The 3D viewer draws every surface at a fixed |ψ| in atomic units. The old rule, a fraction of each orbital's maximum, breaks for realistic s functions, which peak at the nucleus.
  • Unchanged, as predicted: Hückel energies, charges, bond orders and every pKa descriptor and forecast.

π fields in the 3D viewer. π electron density, interference, any π molecular orbital, and the electric potential on the π density surface, from the same conformer (/api/model now includes pi_field) and the same engine as the pKa maps.

One colour convention. Amber/teal is the sign of ψ, red/blue is electron-rich/ electron-poor, on every page (viewer, studio, overlap, molecule, pKa maps, catalogue). See Phase, charge and colour.

pKa page. Sites are named by their atom in the SMILES ("carboxylic acid · O10"), so equal groups such as citric acid's three carboxyl groups can be told apart; the orbital view opens on the π system that holds the selected site; the charge-shift caption says when a molecule has several separate π systems and why the others show no change.

Fixes. /api/model's MolBlock is in the same centred frame as atoms[].position, so the scene check of mol3d.js reads Δ ≈ 10⁻⁵ Å. The viewer reports an error page from the server instead of a JSON parse error. The charge-sensitive radial extent (a display heuristic) is off by default.

Built on 16.0.3: the π fields are drawn through the mol3d.js scene adapter like every other overlay, shifted into the 3Dmol frame if the API frame ever differs.

16.0.3 · adaptive map window and solution pH

  • /pka-density: arbitrary molecules get a map window that fits their heavy atoms (curated molecules keep the 10 Å window).
  • Solution pH slider (0–14): density and potential maps become the Henderson–Hasselbalch population-weighted mixture of acid and base for the selected site; orbitals show the dominant microspecies. See SCREEN_AND_SOLUTION_PH.md.
  • Compact readout for custom molecules (formula as the title).

16.0.2 · scene-owned orbital overlays

  • Added static/js/mol3d.js: 3Dmol explicitly owns molecular coordinates.
  • Orbital meshes and lone-pair arrows read their centres from the loaded 3Dmol model.
  • Overlay cleanup removes only overlay-owned shapes when supported.
  • No qo_chem, server API, or radial-model change.

16.0.1 · October 2026

  • Fix: the server did not start on Python 3.10 and 3.11. One f-string in site_meta.py used syntax that only Python 3.12 accepts. Before release every file was compiled with Python 3.10, 3.11 and 3.12.
  • /pka-density explains what went wrong when the server answers with an error page instead of data, instead of showing a JSON parse error.
  • Safer upgrade procedure in DEPLOYMENT.md: test on a second port first.
  • New experiment in tools/radial/: the app's radial functions measured against Hartree–Fock atoms (forecast, solver, results).

16.0 · October 2026

One orbital layer under everything.

  • New package qo_chem: lone-pair typing from the Lewis structure, Coulson σ hybrids from the real bond angles, Hückel π with heteroatom parameters and geometric p axes, Slater exponents, three-dimensional p overlaps, Löwdin densities, protonation by pH, Hammett constants, σ* and π* acceptor orbitals, normalisation of hypervalent atoms and metal salts, and a provenance hash. See the orbital layer.
  • /api/model now takes its orbitals from that layer: hybrids such as sp\(^{3.99}\) instead of a fixed sp³, σ and π lone pairs, π charges and bond orders, and a complete chemistry block. Existing fields are unchanged.
  • New POST /api/pi-model: the π system and the acid/base pairs of any molecule, in the format the browser field engine draws.
  • /pka-density works for any SMILES ("Your molecule"), including non-planar molecules, which are drawn in the best plane through their π atoms. The 20 reference molecules are regenerated from the same code.
  • pKa forecast lines can be refitted on the server's own conformers with tools/calibrate_pka.py; the server picks up the result at start-up.

A site that looks and indexes like one site.

  • One registry of pages (site_meta.py) gives every page a title, meta description, canonical URL, Open Graph and Twitter cards and schema.org JSON-LD, and one shared header with grouped menus and a mobile menu.
  • /sitemap.xml, /robots.txt, 301 redirects from every alias to one canonical URL, and a real 404 page.
  • The documentation is now part of the site: these pages under /docs/, rendered from the Markdown files in the package, with equations typeset by a self-hosted KaTeX.
  • gzip, ETags, cache rules per asset type, security headers, and no directory listings. The server is now multi-threaded.
  • install_fonts.sh self-hosts the web fonts, so no page contacts Google.
  • New social-sharing card, /icons/og-card.png.

Tests. python -m unittest discover -s tests -t . covers the orbital layer, the RDKit adapter, the API and every page's metadata.

15.6

OpenAPI 3.1 description of the API, Swagger UI at /api/docs.

15.5

The π electron density and pKa study became a reusable browser engine (pi-field-engine.js) with a separate dataset and page.

15.4.1

The server starts on headless machines without native drawing libraries.

15.2

Red/blue orbital phase separated from charge; heuristic partial charges in /api/model; optional charge-sensitive radial display; the catalogue grew from 20 to 63 rules.

15.1

Radial phase convention that keeps the outer lobe of every basis function positive, so s/p hybrids point along their bonds.

15

True R(r)·Y(θ,φ) orbital surfaces in the 3D viewer.

14

Peptide sequence ⇄ SMILES converter.

13

3D viewer built on 3Dmol.js.

11 and earlier

Element coverage extended to H, C, N, O, F, Cl, Br, I, S, P, Na, K, Mg and Ca; bearer-protected SVG rendering; the functional-group catalogue with its polarity overlay and substituent effects.

Source: CHANGELOG.md in the server package · updated