Skip to content

Latest commit

 

History

History
232 lines (178 loc) · 7.39 KB

File metadata and controls

232 lines (178 loc) · 7.39 KB

HilbertBalance for Python

简体中文 | English

HilbertBalance computes all indecomposable nonnegative-integer balances of a single- or multi-stage chemical reaction. The runtime is pure Python plus the open-source Normaliz executable.

What changed in v1.5

Version 1.5 presents constrained and nonunique balancing as the main Web workflow. The redesigned responsive interface keeps the coefficient lower bound at 0 by default, lets users raise selected minima to 1 or more, and provides separate collapsible libraries with 21 balancing examples and 4 reaction- discovery examples. It also improves formula typesetting, ionic charge display, charge-list guidance, and discovery-filter layout in both Chinese and English.

Reaction discovery introduced in v1.4 remains available: the same species pool is placed on both sides internally; identity transfers are removed, reverse directions are merged, and every candidate reports fixed-support coefficient uniqueness. Arbitrary per-compound lower bounds and exact positive integer ratios remain available for explicitly specified reactions.

The default equations method sends the conservation matrix directly to Normaliz and computes

{x in nonnegative integers : M x = 0}

in one Normaliz invocation. The package also retains the one-call saturation method and the two-step two_stage decomposition for independent regression checks. All rational linear algebra outside Normaliz uses Python's exact Fraction type; no floating-point null-space computation is involved.

Local installation

Python 3.9 or newer and Normaliz are required. Normaliz 3.11.1 is recommended; the regression suite also covers 3.9.4.

cd python
python3 -m venv .venv
.venv/bin/pip install -e '.[web]'
export HILBERTBALANCE_NORMALIZ_PATH=/full/path/to/normaliz

If the environment variable is omitted, the package resolves normaliz from PATH.

Python API

from hilbertbalance import balance_reaction

result = balance_reaction(
    "ClO3+Cl+H->ClO2+Cl2+H2O",
    charges=[-1, -1, 1, 0, 0, 0],
)

print(result.equations)
print(result.hilbert_basis.basis)

Require every initial reactant:

result = balance_reaction(
    "H2+O2+N2->H2O+NH3",
    require_all_reactants=True,
)

assert result.hilbert_basis.solution_type == "module_generators"
assert result.hilbert_basis.basis == [[5, 1, 1, 2, 2]]

Set arbitrary zero-based coefficient minima and exact ratios:

result = balance_reaction(
    "H2+O2+N2->H2O+NH3",
    coefficient_lower_bounds={0: 5},
    coefficient_ratios=[([1, 2], [2, 2])],
)

assert result.hilbert_basis.basis == [[5, 1, 1, 2, 2]]
assert result.hilbert_basis.coefficient_ratios == [([1, 2], [1, 1])]

For a conservation matrix that is already available:

from hilbertbalance import hilbert_basis_from_matrix

result = hilbert_basis_from_matrix(matrix, method="equations")

Supported methods are equations, saturation, and two_stage.

Discover stoichiometric transformations without preassigning reactants and products:

from hilbertbalance import discover_reactions

discovery = discover_reactions(["H2", "O2", "H2O", "CO2", "C"])

assert [candidate.equation for candidate in discovery.candidates] == [
    "O2 + C <-> CO2",
    "2H2 + CO2 <-> 2H2O + C",
    "2H2 + O2 <-> 2H2O",
]
assert discovery.identity_transfer_count == 5
assert discovery.reverse_duplicate_count == 3

Optional zero-based required_species and forbidden_species filters refer to the original pool regardless of which side a species occupies. The discovery API also accepts maximum_coefficient, unique_coefficients_only, and a charge list containing one integer per pool entry. reaction_candidates(result) is the lower-level API for an already partitioned, unconstrained two-stage BalanceResult.

Command line

hilbertbalance \
  'ClO3+Cl+H->ClO2+Cl2+H2O' \
  --charges=-1,-1,1,0,0,0 \
  --json

Add --require-all-reactants to request the all-reactant shortcut. General constraints use one-based CLI columns:

hilbertbalance 'H2+O2+N2->H2O+NH3' \
  --lower-bound 1=5 \
  --ratio 2,3=1,1 \
  --json

Reaction discovery uses a comma-separated pool; CLI filter indices are one-based:

hilbertbalance \
  --discover 'H2,O2,H2O,CO2,C' \
  --required-species 2,4 \
  --maximum-coefficient 20 \
  --unique-coefficients-only \
  --json

Web app

HILBERTBALANCE_NORMALIZ_PATH=/full/path/to/normaliz \
  .venv/bin/uvicorn hilbertbalance.web.app:app \
  --host 127.0.0.1 --port 8000

Open http://127.0.0.1:8000. Interactive API documentation is available at http://127.0.0.1:8000/api/docs. The Web interface can switch between Chinese and English and remembers the selected language in the browser. The solver card switches between equation balancing and species-pool discovery. Discovery mode provides per-species required/forbidden selectors, a maximum-coefficient limit, and coefficient-uniqueness filtering; candidate equations use chemical subscripts, charge superscripts, and a direction-neutral double arrow.

Environment variables:

  • HILBERTBALANCE_NORMALIZ_PATH: executable path; otherwise use PATH.
  • HILBERTBALANCE_TIMEOUT: per-request Normaliz timeout in seconds; default 30.

The balance API accepts at most 500 reaction characters, 64 compounds, and 64 charge values. The discovery API accepts up to 32 pool species. Normaliz is invoked with an argument list rather than a shell command, and every request receives a private temporary directory.

Tests

HILBERTBALANCE_NORMALIZ_PATH=/full/path/to/normaliz \
  .venv/bin/python -m unittest discover -s tests -v

The regression suite compares all three methods on both paper matrices, including the complex example with 17 Hilbert-basis elements, arbitrary lower bounds, exact ratios, species-pool discovery, identity cancellation, reverse merging, charged candidates, and both uniqueness levels.

Current formula syntax

The first release intentionally matches the Mathematica package's formula syntax: element symbols followed by optional positive integer subscripts, such as H2SO4 or CH3COOH. Parentheses, hydration dots, isotope notation, and inline ionic charge notation are not parsed yet. Charges are supplied as a separate integer list.

Citation

If HilbertBalance supports your research, please cite:

Zeying Zhang, Guifu Su, Xueqin Zhang, Yuxin Zhao, Zhenghang Zhang, and Shengyuan A. Yang, “Balancing Chemical Equations: From the Perspective of Hilbert Basis,” MATCH Communications in Mathematical and in Computer Chemistry 95 (2026), 589–601. doi:10.46793/match.95-3.24225 · arXiv:2410.06023

@article{Zhang2026HilbertBasis,
  author = {Zhang, Zeying and Su, Guifu and Zhang, Xueqin and Zhao, Yuxin and Zhang, Zhenghang and Yang, Shengyuan A.},
  title = {Balancing Chemical Equations: From the Perspective of Hilbert Basis},
  journal = {MATCH Communications in Mathematical and in Computer Chemistry},
  year = {2026},
  volume = {95},
  number = {3},
  pages = {589--601},
  doi = {10.46793/match.95-3.24225},
  eprint = {2410.06023},
  archivePrefix = {arXiv},
  primaryClass = {physics.chem-ph}
}

Licensing

HilbertBalance and Normaliz are distributed under GPL-3.0-or-later. The Web application calls Normaliz as a separate command-line process. See THIRD_PARTY_NOTICES.md before distributing a Docker image or binary bundle.