atomecon 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,196 @@
1
+ Metadata-Version: 2.4
2
+ Name: atomecon
3
+ Version: 0.1.0
4
+ Summary: Lightweight green chemistry metrics (atom economy, E-factor, yield) from plain chemical formulas - no RDKit, no SMILES.
5
+ Author-email: Your Name <you@example.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/YOUR_USERNAME/atomecon
8
+ Project-URL: Issues, https://github.com/YOUR_USERNAME/atomecon/issues
9
+ Keywords: chemistry,green-chemistry,atom-economy,stoichiometry,e-factor
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Intended Audience :: Education
14
+ Classifier: Topic :: Scientific/Engineering :: Chemistry
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+
18
+ # atomecon
19
+
20
+ 🧪 **[Try it live in your browser](https://atomecon.streamlit.app/)** - no install needed.
21
+
22
+ Lightweight green chemistry metrics - atom economy, theoretical yield, percent yield, E-factor, and automatic equation balancing - computed from **plain chemical formulas**. No RDKit, no SMILES, no heavy dependencies.
23
+
24
+ ## Who this is for
25
+
26
+ - Chemistry students taking green chemistry coursework, tired of redoing molar-mass arithmetic by hand for every reaction
27
+ - Anyone comparing multiple synthesis routes for the same product, who wants to score them programmatically instead of recalculating each one manually
28
+ - Developers who want atom economy and equation balancing without installing RDKit or learning SMILES notation
29
+
30
+ ## Why this exists
31
+
32
+ Existing chemistry packages either don't cover green chemistry metrics at all (they focus on general stoichiometry), or require RDKit and SMILES notation to calculate even a single metric like atom economy. `atomecon` bundles the standard metrics together, balances equations automatically, takes formulas the way you'd write them in a chemistry class (`"C2H5OH"`, not `"CCO"`), and has zero dependencies.
33
+
34
+ ## Try it without installing anything
35
+
36
+ **[atomecon.streamlit.app](https://atomecon.streamlit.app/)** - type in a reaction's formulas, get it balanced and analyzed instantly in your browser.
37
+
38
+ ## Install (to use in your own Python code)
39
+
40
+ ```bash
41
+ pip install -e .
42
+ ```
43
+
44
+ Or install directly from this repo:
45
+ ```bash
46
+ pip install git+https://github.com/JvenDeepak0203/atomecon.git
47
+ ```
48
+
49
+ ## Core concepts
50
+
51
+ - **Atom economy** is *theoretical* - it only depends on the balanced equation and molar masses. It's always computable, with no lab data.
52
+ - **Theoretical yield**, **percent yield**, and **E-factor** are *experimental* - they depend on the real masses of reactants you used and the real mass of product you isolated. These vary run to run.
53
+
54
+ `atomecon` keeps this distinction explicit: `atom_economy()` takes no arguments beyond the reaction itself, while `e_factor()` and `percent_yield()` require you to supply real measured masses.
55
+
56
+ ## Quick start - the one-line version
57
+
58
+ ```python
59
+ from atomecon import analyze
60
+
61
+ analyze(["CH4", "O2"], ["CO2", "H2O"], desired_product="CO2")
62
+ ```
63
+ Balances the equation automatically and prints a full report - no coefficients, no separate method calls needed.
64
+
65
+ ## Full walkthrough
66
+
67
+ ```python
68
+ from atomecon import Reaction
69
+
70
+ # Aspirin synthesis: salicylic acid + acetic anhydride -> aspirin + acetic acid
71
+ rxn = Reaction(
72
+ reactants={"C7H6O3": 1, "C4H6O3": 1},
73
+ products={"C9H8O4": 1, "C2H4O2": 1},
74
+ desired_product="C9H8O4",
75
+ )
76
+
77
+ print(rxn.atom_economy())
78
+ # 75.0 (theoretical - no lab data needed)
79
+
80
+ masses = {"C7H6O3": 5.0, "C4H6O3": 5.0}
81
+ print(rxn.theoretical_yield_g(masses))
82
+ print(rxn.percent_yield(masses, actual_yield_g=4.2))
83
+ print(rxn.e_factor(masses, actual_yield_g=4.2))
84
+ print(rxn.green_grade(masses, actual_yield_g=4.2))
85
+
86
+ print(rxn.summary_table(reactant_masses_g=masses, actual_yield_g=4.2))
87
+ ```
88
+
89
+ ## Automatic equation balancing
90
+
91
+ Don't want to work out coefficients yourself? Just give formulas:
92
+
93
+ ```python
94
+ from atomecon import Reaction
95
+
96
+ rxn = Reaction.auto(["N2", "H2"], ["NH3"], desired_product="NH3")
97
+ print(rxn)
98
+ # <Reaction N2 + 3H2 -> 2NH3>
99
+ ```
100
+
101
+ This uses real linear algebra (Gaussian elimination over exact fractions) to solve for the smallest whole-number coefficients - the same process you'd do by hand, automated.
102
+
103
+ **Known limitation:** a small number of equations have more than one valid balancing ratio (a genuine mathematical ambiguity, not a bug) and will raise a clear error asking you to specify coefficients manually instead of guessing.
104
+
105
+ ## Learning mode: see the calculation, not just the answer
106
+
107
+ ```python
108
+ print(rxn.explain_atom_economy())
109
+ ```
110
+
111
+ ## Green grade: one number to compare reactions at a glance
112
+
113
+ ```python
114
+ print(rxn.green_grade(masses, actual_yield_g=4.2))
115
+ # B (Atom economy: 75% | E-factor: 1.4)
116
+ ```
117
+
118
+ A simple, transparent scoring rule (not a scientific standard) - combines atom economy and E-factor into a single A-F grade.
119
+
120
+ ## Comparing multiple reactions
121
+
122
+ ```python
123
+ from atomecon import Reaction, ReactionLog
124
+
125
+ log = ReactionLog()
126
+ log.add("Aspirin route", rxn, reactant_masses_g=masses, actual_yield_g=4.2)
127
+
128
+ combustion = Reaction.auto(["CH4", "O2"], ["CO2", "H2O"], desired_product="CO2")
129
+ log.add("Methane combustion", combustion) # lab data is optional per entry
130
+
131
+ print(log.comparison_table())
132
+ ```
133
+ Builds a table sorted by atom economy (greenest first). `ReactionLog` is in-memory only - it resets each time your program runs.
134
+
135
+ ## Formula plausibility checking
136
+
137
+ Before balancing, `atomecon` can flag formulas that are chemically impossible using valence-parity math (every bond connects exactly 2 atoms, so a molecule's total valence must be even):
138
+
139
+ ```python
140
+ from atomecon import is_formula_plausible
141
+
142
+ is_formula_plausible("C8H18") # True (real octane)
143
+ is_formula_plausible("C8H23") # False (impossible - odd total valence)
144
+ is_formula_plausible("Fe2O3") # True (iron has variable valence - not checked, benefit of the doubt)
145
+ ```
146
+
147
+ **Important limitation:** this can only rule out formulas as impossible - it cannot prove a formula is real, and it deliberately skips elements with variable real-world valence (iron, sulfur, phosphorus, nitrogen, most transition metals) rather than risk a wrong answer. This is a long way from full molecular validity checking (which is what RDKit does using real molecular structure) - it's one useful mathematical shortcut, not a replacement for it.
148
+
149
+ ## Formula syntax
150
+
151
+ Supports condensed formulas and nested parentheses:
152
+
153
+ ```python
154
+ from atomecon import parse_formula, molar_mass
155
+
156
+ parse_formula("Ca(OH)2") # {"Ca": 1, "O": 2, "H": 2}
157
+ parse_formula("Fe3(Fe(CN)6)2") # {"Fe": 5, "C": 12, "N": 12}
158
+ molar_mass("C6H12O6") # 180.156
159
+ ```
160
+
161
+ ## Reaction balance checking
162
+
163
+ `Reaction` verifies the equation is atom-balanced on construction and raises a clear error if it isn't (atom economy is not meaningful for an unbalanced equation). Pass `allow_unbalanced=True` to override.
164
+
165
+ ## What this library does NOT do (known scope limits)
166
+
167
+ Being upfront about the boundaries:
168
+ - **Does not verify a formula represents a real molecule** beyond the basic valence-parity check above - no bonding/structure model like RDKit
169
+ - **Does not verify a reaction is chemically real** - if you give it atom-balanced but chemically implausible reactants/products (e.g. a reaction that wouldn't actually occur), it will still calculate metrics for it. Verifying real reaction mechanisms is a much larger problem (closer to quantum chemistry / reaction databases) that's out of scope here.
170
+ - **No charge/ionic support** - formulas are tracked by atoms only, not electric charge, so redox half-reactions and charged species aren't supported
171
+
172
+ ## Try the demo script
173
+
174
+ ```bash
175
+ python demo.py
176
+ ```
177
+ Runs through every feature of the library end to end.
178
+
179
+ ## Web app
180
+
181
+ The `app.py` file is a Streamlit interface to the library - see it live at [atomecon.streamlit.app](https://atomecon.streamlit.app/), or run it yourself:
182
+ ```bash
183
+ pip install streamlit
184
+ streamlit run app.py
185
+ ```
186
+
187
+ ## Running tests
188
+
189
+ ```bash
190
+ pip install pytest
191
+ pytest
192
+ ```
193
+
194
+ ## License
195
+
196
+ MIT
@@ -0,0 +1,179 @@
1
+ # atomecon
2
+
3
+ 🧪 **[Try it live in your browser](https://atomecon.streamlit.app/)** - no install needed.
4
+
5
+ Lightweight green chemistry metrics - atom economy, theoretical yield, percent yield, E-factor, and automatic equation balancing - computed from **plain chemical formulas**. No RDKit, no SMILES, no heavy dependencies.
6
+
7
+ ## Who this is for
8
+
9
+ - Chemistry students taking green chemistry coursework, tired of redoing molar-mass arithmetic by hand for every reaction
10
+ - Anyone comparing multiple synthesis routes for the same product, who wants to score them programmatically instead of recalculating each one manually
11
+ - Developers who want atom economy and equation balancing without installing RDKit or learning SMILES notation
12
+
13
+ ## Why this exists
14
+
15
+ Existing chemistry packages either don't cover green chemistry metrics at all (they focus on general stoichiometry), or require RDKit and SMILES notation to calculate even a single metric like atom economy. `atomecon` bundles the standard metrics together, balances equations automatically, takes formulas the way you'd write them in a chemistry class (`"C2H5OH"`, not `"CCO"`), and has zero dependencies.
16
+
17
+ ## Try it without installing anything
18
+
19
+ **[atomecon.streamlit.app](https://atomecon.streamlit.app/)** - type in a reaction's formulas, get it balanced and analyzed instantly in your browser.
20
+
21
+ ## Install (to use in your own Python code)
22
+
23
+ ```bash
24
+ pip install -e .
25
+ ```
26
+
27
+ Or install directly from this repo:
28
+ ```bash
29
+ pip install git+https://github.com/JvenDeepak0203/atomecon.git
30
+ ```
31
+
32
+ ## Core concepts
33
+
34
+ - **Atom economy** is *theoretical* - it only depends on the balanced equation and molar masses. It's always computable, with no lab data.
35
+ - **Theoretical yield**, **percent yield**, and **E-factor** are *experimental* - they depend on the real masses of reactants you used and the real mass of product you isolated. These vary run to run.
36
+
37
+ `atomecon` keeps this distinction explicit: `atom_economy()` takes no arguments beyond the reaction itself, while `e_factor()` and `percent_yield()` require you to supply real measured masses.
38
+
39
+ ## Quick start - the one-line version
40
+
41
+ ```python
42
+ from atomecon import analyze
43
+
44
+ analyze(["CH4", "O2"], ["CO2", "H2O"], desired_product="CO2")
45
+ ```
46
+ Balances the equation automatically and prints a full report - no coefficients, no separate method calls needed.
47
+
48
+ ## Full walkthrough
49
+
50
+ ```python
51
+ from atomecon import Reaction
52
+
53
+ # Aspirin synthesis: salicylic acid + acetic anhydride -> aspirin + acetic acid
54
+ rxn = Reaction(
55
+ reactants={"C7H6O3": 1, "C4H6O3": 1},
56
+ products={"C9H8O4": 1, "C2H4O2": 1},
57
+ desired_product="C9H8O4",
58
+ )
59
+
60
+ print(rxn.atom_economy())
61
+ # 75.0 (theoretical - no lab data needed)
62
+
63
+ masses = {"C7H6O3": 5.0, "C4H6O3": 5.0}
64
+ print(rxn.theoretical_yield_g(masses))
65
+ print(rxn.percent_yield(masses, actual_yield_g=4.2))
66
+ print(rxn.e_factor(masses, actual_yield_g=4.2))
67
+ print(rxn.green_grade(masses, actual_yield_g=4.2))
68
+
69
+ print(rxn.summary_table(reactant_masses_g=masses, actual_yield_g=4.2))
70
+ ```
71
+
72
+ ## Automatic equation balancing
73
+
74
+ Don't want to work out coefficients yourself? Just give formulas:
75
+
76
+ ```python
77
+ from atomecon import Reaction
78
+
79
+ rxn = Reaction.auto(["N2", "H2"], ["NH3"], desired_product="NH3")
80
+ print(rxn)
81
+ # <Reaction N2 + 3H2 -> 2NH3>
82
+ ```
83
+
84
+ This uses real linear algebra (Gaussian elimination over exact fractions) to solve for the smallest whole-number coefficients - the same process you'd do by hand, automated.
85
+
86
+ **Known limitation:** a small number of equations have more than one valid balancing ratio (a genuine mathematical ambiguity, not a bug) and will raise a clear error asking you to specify coefficients manually instead of guessing.
87
+
88
+ ## Learning mode: see the calculation, not just the answer
89
+
90
+ ```python
91
+ print(rxn.explain_atom_economy())
92
+ ```
93
+
94
+ ## Green grade: one number to compare reactions at a glance
95
+
96
+ ```python
97
+ print(rxn.green_grade(masses, actual_yield_g=4.2))
98
+ # B (Atom economy: 75% | E-factor: 1.4)
99
+ ```
100
+
101
+ A simple, transparent scoring rule (not a scientific standard) - combines atom economy and E-factor into a single A-F grade.
102
+
103
+ ## Comparing multiple reactions
104
+
105
+ ```python
106
+ from atomecon import Reaction, ReactionLog
107
+
108
+ log = ReactionLog()
109
+ log.add("Aspirin route", rxn, reactant_masses_g=masses, actual_yield_g=4.2)
110
+
111
+ combustion = Reaction.auto(["CH4", "O2"], ["CO2", "H2O"], desired_product="CO2")
112
+ log.add("Methane combustion", combustion) # lab data is optional per entry
113
+
114
+ print(log.comparison_table())
115
+ ```
116
+ Builds a table sorted by atom economy (greenest first). `ReactionLog` is in-memory only - it resets each time your program runs.
117
+
118
+ ## Formula plausibility checking
119
+
120
+ Before balancing, `atomecon` can flag formulas that are chemically impossible using valence-parity math (every bond connects exactly 2 atoms, so a molecule's total valence must be even):
121
+
122
+ ```python
123
+ from atomecon import is_formula_plausible
124
+
125
+ is_formula_plausible("C8H18") # True (real octane)
126
+ is_formula_plausible("C8H23") # False (impossible - odd total valence)
127
+ is_formula_plausible("Fe2O3") # True (iron has variable valence - not checked, benefit of the doubt)
128
+ ```
129
+
130
+ **Important limitation:** this can only rule out formulas as impossible - it cannot prove a formula is real, and it deliberately skips elements with variable real-world valence (iron, sulfur, phosphorus, nitrogen, most transition metals) rather than risk a wrong answer. This is a long way from full molecular validity checking (which is what RDKit does using real molecular structure) - it's one useful mathematical shortcut, not a replacement for it.
131
+
132
+ ## Formula syntax
133
+
134
+ Supports condensed formulas and nested parentheses:
135
+
136
+ ```python
137
+ from atomecon import parse_formula, molar_mass
138
+
139
+ parse_formula("Ca(OH)2") # {"Ca": 1, "O": 2, "H": 2}
140
+ parse_formula("Fe3(Fe(CN)6)2") # {"Fe": 5, "C": 12, "N": 12}
141
+ molar_mass("C6H12O6") # 180.156
142
+ ```
143
+
144
+ ## Reaction balance checking
145
+
146
+ `Reaction` verifies the equation is atom-balanced on construction and raises a clear error if it isn't (atom economy is not meaningful for an unbalanced equation). Pass `allow_unbalanced=True` to override.
147
+
148
+ ## What this library does NOT do (known scope limits)
149
+
150
+ Being upfront about the boundaries:
151
+ - **Does not verify a formula represents a real molecule** beyond the basic valence-parity check above - no bonding/structure model like RDKit
152
+ - **Does not verify a reaction is chemically real** - if you give it atom-balanced but chemically implausible reactants/products (e.g. a reaction that wouldn't actually occur), it will still calculate metrics for it. Verifying real reaction mechanisms is a much larger problem (closer to quantum chemistry / reaction databases) that's out of scope here.
153
+ - **No charge/ionic support** - formulas are tracked by atoms only, not electric charge, so redox half-reactions and charged species aren't supported
154
+
155
+ ## Try the demo script
156
+
157
+ ```bash
158
+ python demo.py
159
+ ```
160
+ Runs through every feature of the library end to end.
161
+
162
+ ## Web app
163
+
164
+ The `app.py` file is a Streamlit interface to the library - see it live at [atomecon.streamlit.app](https://atomecon.streamlit.app/), or run it yourself:
165
+ ```bash
166
+ pip install streamlit
167
+ streamlit run app.py
168
+ ```
169
+
170
+ ## Running tests
171
+
172
+ ```bash
173
+ pip install pytest
174
+ pytest
175
+ ```
176
+
177
+ ## License
178
+
179
+ MIT
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "atomecon"
7
+ version = "0.1.0"
8
+ description = "Lightweight green chemistry metrics (atom economy, E-factor, yield) from plain chemical formulas - no RDKit, no SMILES."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "Your Name", email = "you@example.com" }
14
+ ]
15
+ keywords = ["chemistry", "green-chemistry", "atom-economy", "stoichiometry", "e-factor"]
16
+ classifiers = [
17
+ "Programming Language :: Python :: 3",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent",
20
+ "Intended Audience :: Education",
21
+ "Topic :: Scientific/Engineering :: Chemistry",
22
+ ]
23
+
24
+ [project.urls]
25
+ Homepage = "https://github.com/YOUR_USERNAME/atomecon"
26
+ Issues = "https://github.com/YOUR_USERNAME/atomecon/issues"
27
+
28
+ [tool.setuptools.packages.find]
29
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,24 @@
1
+ """
2
+ atomecon - lightweight green chemistry metrics from plain chemical formulas.
3
+
4
+ No RDKit, no SMILES - just formulas and molar masses.
5
+ """
6
+
7
+ from .formula import parse_formula, molar_mass
8
+ from .reaction import Reaction
9
+ from .reaction_log import ReactionLog
10
+ from .balance import balance_equation
11
+ from .convenience import analyze
12
+ from .valence import is_formula_plausible, explain_formula_plausibility
13
+
14
+ __all__ = [
15
+ "parse_formula",
16
+ "molar_mass",
17
+ "Reaction",
18
+ "ReactionLog",
19
+ "balance_equation",
20
+ "analyze",
21
+ "is_formula_plausible",
22
+ "explain_formula_plausibility",
23
+ ]
24
+ __version__ = "0.1.0"
@@ -0,0 +1,163 @@
1
+ """
2
+ Automatic chemical equation balancing.
3
+
4
+ Given just the reactant and product formulas (no coefficients), this
5
+ works out the smallest whole-number coefficients that balance the
6
+ equation - the same thing you'd do by hand in intro chemistry, done
7
+ here with linear algebra instead of trial and error.
8
+
9
+ HOW IT WORKS (high level):
10
+ Every element must appear in equal total amounts on both sides. That
11
+ gives one equation per element, with the coefficients as the unknowns.
12
+ Solving that system of equations (its "null space") gives the ratio of
13
+ coefficients; scaling that ratio up to whole numbers gives the answer.
14
+
15
+ This file is more mathematically advanced than the rest of the library
16
+ (it uses exact fractions and row reduction). If you're reading through
17
+ line by line, it's fine to treat this as "trusted machinery" - the way
18
+ you'd use a library function without re-deriving how it works - rather
19
+ than trace every line the way we did with formula.py.
20
+ """
21
+
22
+ import math
23
+ from fractions import Fraction
24
+ from typing import Dict, List, Tuple
25
+
26
+ from .formula import parse_formula
27
+
28
+
29
+ def balance_equation(
30
+ reactant_formulas: List[str], product_formulas: List[str]
31
+ ) -> Tuple[Dict[str, int], Dict[str, int]]:
32
+ species = reactant_formulas + product_formulas
33
+ num_species = len(species)
34
+
35
+ element_counts_per_species = []
36
+ all_elements = []
37
+ for formula in species:
38
+ counts = parse_formula(formula)
39
+ element_counts_per_species.append(counts)
40
+ for element in counts:
41
+ if element not in all_elements:
42
+ all_elements.append(element)
43
+
44
+ num_reactants = len(reactant_formulas)
45
+
46
+ matrix = []
47
+ for element in all_elements:
48
+ row = []
49
+ for i, counts in enumerate(element_counts_per_species):
50
+ count = counts.get(element, 0)
51
+ if i >= num_reactants:
52
+ count = -count
53
+ row.append(Fraction(count))
54
+ matrix.append(row)
55
+
56
+ coefficients = _solve_null_space(matrix, num_species)
57
+
58
+ reactant_coeffs = {}
59
+ for i in range(num_reactants):
60
+ reactant_coeffs[reactant_formulas[i]] = coefficients[i]
61
+
62
+ product_coeffs = {}
63
+ for i in range(num_reactants, num_species):
64
+ product_index = i - num_reactants
65
+ product_coeffs[product_formulas[product_index]] = coefficients[i]
66
+
67
+ return reactant_coeffs, product_coeffs
68
+
69
+
70
+ def _solve_null_space(matrix, num_species):
71
+ num_rows = len(matrix)
72
+
73
+ working = []
74
+ for row in matrix:
75
+ working.append(list(row))
76
+
77
+ pivot_columns = []
78
+ current_row = 0
79
+
80
+ for col in range(num_species):
81
+ pivot_row = None
82
+ for r in range(current_row, num_rows):
83
+ if working[r][col] != 0:
84
+ pivot_row = r
85
+ break
86
+
87
+ if pivot_row is None:
88
+ continue
89
+
90
+ working[current_row], working[pivot_row] = working[pivot_row], working[current_row]
91
+
92
+ pivot_value = working[current_row][col]
93
+ for c in range(num_species):
94
+ working[current_row][c] = working[current_row][c] / pivot_value
95
+
96
+ for r in range(num_rows):
97
+ if r != current_row and working[r][col] != 0:
98
+ factor = working[r][col]
99
+ for c in range(num_species):
100
+ working[r][c] = working[r][c] - factor * working[current_row][c]
101
+
102
+ pivot_columns.append(col)
103
+ current_row += 1
104
+ if current_row == num_rows:
105
+ break
106
+
107
+ free_columns = []
108
+ for col in range(num_species):
109
+ if col not in pivot_columns:
110
+ free_columns.append(col)
111
+
112
+ if len(free_columns) != 1:
113
+ raise ValueError(
114
+ "Could not automatically balance this equation (expected exactly "
115
+ "one degree of freedom, found "
116
+ f"{len(free_columns)}). Try specifying coefficients manually "
117
+ "with Reaction(reactants={...}, products={...})."
118
+ )
119
+
120
+ free_col = free_columns[0]
121
+
122
+ values = [Fraction(0)] * num_species
123
+ values[free_col] = Fraction(1)
124
+
125
+ for i, col in enumerate(pivot_columns):
126
+ values[col] = -working[i][free_col]
127
+
128
+ lcm_of_denominators = 1
129
+ for v in values:
130
+ lcm_of_denominators = _lcm(lcm_of_denominators, v.denominator)
131
+
132
+ whole_numbers = []
133
+ for v in values:
134
+ whole_numbers.append(int(v * lcm_of_denominators))
135
+
136
+ all_non_positive = True
137
+ for w in whole_numbers:
138
+ if w > 0:
139
+ all_non_positive = False
140
+ if all_non_positive:
141
+ whole_numbers = [-w for w in whole_numbers]
142
+
143
+ for w in whole_numbers:
144
+ if w <= 0:
145
+ raise ValueError(
146
+ "Could not automatically balance this equation into all "
147
+ "positive coefficients. Try specifying coefficients "
148
+ "manually with Reaction(reactants={...}, products={...})."
149
+ )
150
+
151
+ overall_gcd = whole_numbers[0]
152
+ for w in whole_numbers[1:]:
153
+ overall_gcd = math.gcd(overall_gcd, w)
154
+
155
+ simplified = []
156
+ for w in whole_numbers:
157
+ simplified.append(w // overall_gcd)
158
+
159
+ return simplified
160
+
161
+
162
+ def _lcm(a, b):
163
+ return a * b // math.gcd(a, b)
@@ -0,0 +1,27 @@
1
+ """
2
+ One-call convenience function for the common case: you just have
3
+ formulas (no coefficients) and, optionally, real lab masses, and you
4
+ want the full picture without creating objects and calling several
5
+ methods yourself.
6
+ """
7
+
8
+ from typing import Dict, List, Optional
9
+
10
+ from .reaction import Reaction
11
+
12
+
13
+ def analyze(
14
+ reactants: List[str],
15
+ products: List[str],
16
+ desired_product: str,
17
+ reactant_masses_g: Optional[Dict[str, float]] = None,
18
+ actual_yield_g: Optional[float] = None,
19
+ ) -> Reaction:
20
+ """
21
+ Auto-balance the equation, build the Reaction, print a full summary
22
+ table, and return the Reaction object in case you want to do more
23
+ with it afterward.
24
+ """
25
+ rxn = Reaction.auto(reactants, products, desired_product)
26
+ print(rxn.summary_table(reactant_masses_g, actual_yield_g))
27
+ return rxn
@@ -0,0 +1,34 @@
1
+ """
2
+ Standard atomic weights for elements 1-118 (approximate, IUPAC-style averages).
3
+
4
+ These values are sufficient for typical stoichiometry and green-chemistry
5
+ calculations in an educational context. They are not intended for
6
+ high-precision analytical work.
7
+ """
8
+
9
+ ATOMIC_MASSES = {
10
+ "H": 1.008, "He": 4.0026, "Li": 6.94, "Be": 9.0122, "B": 10.81,
11
+ "C": 12.011, "N": 14.007, "O": 15.999, "F": 18.998, "Ne": 20.180,
12
+ "Na": 22.990, "Mg": 24.305, "Al": 26.982, "Si": 28.085, "P": 30.974,
13
+ "S": 32.06, "Cl": 35.45, "Ar": 39.948, "K": 39.098, "Ca": 40.078,
14
+ "Sc": 44.956, "Ti": 47.867, "V": 50.942, "Cr": 51.996, "Mn": 54.938,
15
+ "Fe": 55.845, "Co": 58.933, "Ni": 58.693, "Cu": 63.546, "Zn": 65.38,
16
+ "Ga": 69.723, "Ge": 72.630, "As": 74.922, "Se": 78.971, "Br": 79.904,
17
+ "Kr": 83.798, "Rb": 85.468, "Sr": 87.62, "Y": 88.906, "Zr": 91.224,
18
+ "Nb": 92.906, "Mo": 95.95, "Tc": 98.0, "Ru": 101.07, "Rh": 102.91,
19
+ "Pd": 106.42, "Ag": 107.87, "Cd": 112.41, "In": 114.82, "Sn": 118.71,
20
+ "Sb": 121.76, "Te": 127.60, "I": 126.90, "Xe": 131.29, "Cs": 132.91,
21
+ "Ba": 137.33, "La": 138.91, "Ce": 140.12, "Pr": 140.91, "Nd": 144.24,
22
+ "Pm": 145.0, "Sm": 150.36, "Eu": 151.96, "Gd": 157.25, "Tb": 158.93,
23
+ "Dy": 162.50, "Ho": 164.93, "Er": 167.26, "Tm": 168.93, "Yb": 173.05,
24
+ "Lu": 174.97, "Hf": 178.49, "Ta": 180.95, "W": 183.84, "Re": 186.21,
25
+ "Os": 190.23, "Ir": 192.22, "Pt": 195.08, "Au": 196.97, "Hg": 200.59,
26
+ "Tl": 204.38, "Pb": 207.2, "Bi": 208.98, "Po": 209.0, "At": 210.0,
27
+ "Rn": 222.0, "Fr": 223.0, "Ra": 226.0, "Ac": 227.0, "Th": 232.04,
28
+ "Pa": 231.04, "U": 238.03, "Np": 237.0, "Pu": 244.0, "Am": 243.0,
29
+ "Cm": 247.0, "Bk": 247.0, "Cf": 251.0, "Es": 252.0, "Fm": 257.0,
30
+ "Md": 258.0, "No": 259.0, "Lr": 262.0, "Rf": 267.0, "Db": 270.0,
31
+ "Sg": 271.0, "Bh": 270.0, "Hs": 277.0, "Mt": 278.0, "Ds": 281.0,
32
+ "Rg": 282.0, "Cn": 285.0, "Nh": 286.0, "Fl": 289.0, "Mc": 290.0,
33
+ "Lv": 293.0, "Ts": 294.0, "Og": 294.0,
34
+ }