|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | +""" |
| 3 | +TDD Test Suite: D4 — Química Computacional e Estrutural (N1 - Básico) |
| 4 | +CORA-Eval Benchmark Tasks: D4-N1-01, D4-N1-02, D4-N1-03 |
| 5 | +
|
| 6 | +Cada teste implementa uma verificação real com ground truth conhecido. |
| 7 | +""" |
| 8 | + |
| 9 | +import math |
| 10 | +import sys |
| 11 | +import os |
| 12 | + |
| 13 | +# ─── Massas atômicas (IUPAC 2021) ──────────────────────────────────── |
| 14 | +ATOMIC_MASSES = { |
| 15 | + "H": 1.008, "He": 4.0026, "C": 12.011, "N": 14.007, "O": 15.999, |
| 16 | + "F": 18.998, "Na": 22.990, "Mg": 24.305, "Al": 26.982, "Si": 28.085, |
| 17 | + "P": 30.974, "S": 32.06, "Cl": 35.45, "K": 39.098, "Ca": 40.078, |
| 18 | + "Fe": 55.845, "Cu": 63.546, "Zn": 65.38, "Br": 79.904, "Ag": 107.87, |
| 19 | + "I": 126.90, "Ba": 137.33, "Pt": 195.08, "Au": 196.97, "Hg": 200.59, |
| 20 | + "Pb": 207.2, |
| 21 | +} |
| 22 | + |
| 23 | +def parse_formula(formula: str) -> dict: |
| 24 | + """Parseia fórmula química como 'C6H12O6' em dicionário {elemento: contagem}.""" |
| 25 | + import re |
| 26 | + result = {} |
| 27 | + pattern = re.compile(r"([A-Z][a-z]?)(\d*)") |
| 28 | + for match in pattern.finditer(formula): |
| 29 | + elem = match.group(1) |
| 30 | + count = int(match.group(2)) if match.group(2) else 1 |
| 31 | + result[elem] = result.get(elem, 0) + count |
| 32 | + return result |
| 33 | + |
| 34 | +def molar_mass(formula: str) -> float: |
| 35 | + """Calcula massa molar (g/mol) a partir da fórmula química.""" |
| 36 | + parsed = parse_formula(formula) |
| 37 | + total = 0.0 |
| 38 | + for elem, count in parsed.items(): |
| 39 | + if elem not in ATOMIC_MASSES: |
| 40 | + raise ValueError(f"Elemento desconhecido: {elem}") |
| 41 | + total += ATOMIC_MASSES[elem] * count |
| 42 | + return round(total, 3) |
| 43 | + |
| 44 | +# ══════════════════════════════════════════════════════════════════════ |
| 45 | +# TEST 1: D4-N1-01 — Balanceamento de Equação Química |
| 46 | +# ══════════════════════════════════════════════════════════════════════ |
| 47 | + |
| 48 | +def balance_combustion(formula: str) -> tuple: |
| 49 | + """ |
| 50 | + Balanceia combustão completa: CxHyOz + a O2 → b CO2 + c H2O |
| 51 | + Retorna (a, b, c, x, y, z). |
| 52 | + """ |
| 53 | + parsed = parse_formula(formula) |
| 54 | + x = parsed.get("C", 0) |
| 55 | + y = parsed.get("H", 0) |
| 56 | + z = parsed.get("O", 0) |
| 57 | + b = x # CO2: 1 C por molécula |
| 58 | + c = y // 2 # H2O: 2 H por molécula (assume y par) |
| 59 | + a = (2*x + y//2 - z) / 2 # balanceamento de O |
| 60 | + return (a, b, c, x, y, z) |
| 61 | + |
| 62 | +def test_balance_h2_o2(): |
| 63 | + """D4-N1-01: 2 H2 + O2 → 2 H2O""" |
| 64 | + # Verificação manual dos coeficientes |
| 65 | + # 2 H2 + 1 O2 → 2 H2O |
| 66 | + # H: 2*2=4 → 2*2=4 ✓ |
| 67 | + # O: 1*2=2 → 2*1=2 ✓ |
| 68 | + coef_H2, coef_O2, coef_H2O = 2, 1, 2 |
| 69 | + assert coef_H2 * 2 == coef_H2O * 2 # balanceamento H |
| 70 | + assert coef_O2 * 2 == coef_H2O * 1 # balanceamento O |
| 71 | + print(" [D4-N1-01] 2 H2 + O2 → 2 H2O... PASS (balanceamento manual)") |
| 72 | + return True |
| 73 | + |
| 74 | +def test_balance_ch4(): |
| 75 | + """D4-N1-01: CH4 + 2 O2 → CO2 + 2 H2O""" |
| 76 | + # Metano: CH4 + 2 O2 → CO2 + 2 H2O |
| 77 | + # C: 1 = 1 ✓, H: 4 = 2*2 ✓, O: 2*2 = 2+2 ✓ |
| 78 | + a, b, c, x, y, z = balance_combustion("CH4") |
| 79 | + assert (a, b, c) == (2.0, 1, 2), f"Esperado (2,1,2), obtido {a,b,c}" |
| 80 | + print(" [D4-N1-01] CH4 + 2 O2 → CO2 + 2 H2O... PASS (algorítmico)") |
| 81 | + return True |
| 82 | + |
| 83 | +# ══════════════════════════════════════════════════════════════════════ |
| 84 | +# TEST 2: D4-N1-02 — Massa Molar |
| 85 | +# ══════════════════════════════════════════════════════════════════════ |
| 86 | + |
| 87 | +def test_molar_mass_glicose(): |
| 88 | + """D4-N1-02: C6H12O6 = 180.156 g/mol ± 0.01""" |
| 89 | + expected = 180.156 |
| 90 | + result = molar_mass("C6H12O6") |
| 91 | + assert abs(result - expected) < 0.01, f"Esperado {expected}, obtido {result}" |
| 92 | + print(f" [D4-N1-02] C6H12O6 = {result} g/mol... PASS (±{abs(result-expected):.3f})") |
| 93 | + return True |
| 94 | + |
| 95 | +def test_molar_mass_h2o(): |
| 96 | + """D4-N1-02: H2O = 18.015 g/mol ± 0.01""" |
| 97 | + expected = 18.015 |
| 98 | + result = molar_mass("H2O") |
| 99 | + assert abs(result - expected) < 0.01, f"Esperado {expected}, obtido {result}" |
| 100 | + print(f" [D4-N1-02] H2O = {result} g/mol... PASS") |
| 101 | + return True |
| 102 | + |
| 103 | +def test_molar_mass_nacl(): |
| 104 | + """D4-N1-02: NaCl = 58.440 g/mol ± 0.01""" |
| 105 | + expected = 58.440 |
| 106 | + result = molar_mass("NaCl") |
| 107 | + assert abs(result - expected) < 0.01 |
| 108 | + print(f" [D4-N1-02] NaCl = {result} g/mol... PASS") |
| 109 | + return True |
| 110 | + |
| 111 | +def test_molar_mass_caco3(): |
| 112 | + """D4-N1-02: CaCO3 = 100.087 g/mol ± 0.01""" |
| 113 | + expected = 100.087 |
| 114 | + result = molar_mass("CaCO3") |
| 115 | + assert abs(result - expected) < 0.01 |
| 116 | + print(f" [D4-N1-02] CaCO3 = {result} g/mol... PASS") |
| 117 | + return True |
| 118 | + |
| 119 | +# ══════════════════════════════════════════════════════════════════════ |
| 120 | +# TEST 3: D4-N1-03 — Conversão de Concentração |
| 121 | +# ══════════════════════════════════════════════════════════════════════ |
| 122 | + |
| 123 | +def percent_to_molarity(percent_mv: float, molar_mass_gmol: float) -> float: |
| 124 | + """ |
| 125 | + Converte % (m/v) para mol/L. |
| 126 | + % (m/v) = g soluto / 100 mL solução |
| 127 | + mol/L = (% * 10) / massa_molar |
| 128 | + """ |
| 129 | + return round((percent_mv * 10) / molar_mass_gmol, 4) |
| 130 | + |
| 131 | +def molarity_to_percent(molarity: float, molar_mass_gmol: float) -> float: |
| 132 | + """Converte mol/L para % (m/v).""" |
| 133 | + return round((molarity * molar_mass_gmol) / 10, 2) |
| 134 | + |
| 135 | +def test_percent_to_molarity_glucose(): |
| 136 | + """D4-N1-03: Glicose 5% (m/v) → mol/L""" |
| 137 | + # 5% glicose = 5 g / 100 mL = 50 g / L |
| 138 | + # mol/L = 50 / 180.156 = 0.2775 |
| 139 | + expected = 0.2775 |
| 140 | + result = percent_to_molarity(5.0, molar_mass("C6H12O6")) |
| 141 | + assert abs(result - expected) < 0.001 |
| 142 | + print(f" [D4-N1-03] Glicose 5% = {result} mol/L... PASS") |
| 143 | + return True |
| 144 | + |
| 145 | +def test_percent_to_molarity_nacl(): |
| 146 | + """D4-N1-03: NaCl 0.9% (m/v) → mol/L (soro fisiológico)""" |
| 147 | + # 0.9% NaCl = 0.9 g / 100 mL = 9 g / L |
| 148 | + # mol/L = 9 / 58.44 = 0.1540 |
| 149 | + expected = 0.1540 |
| 150 | + result = percent_to_molarity(0.9, molar_mass("NaCl")) |
| 151 | + assert abs(result - expected) < 0.001 |
| 152 | + print(f" [D4-N1-03] NaCl 0.9% = {result} mol/L... PASS") |
| 153 | + return True |
| 154 | + |
| 155 | +def test_molarity_to_percent_roundtrip(): |
| 156 | + """D4-N1-03: Roundtrip mol/L → % (m/v) → mol/L""" |
| 157 | + original = 0.5 # mol/L |
| 158 | + mm = molar_mass("NaCl") |
| 159 | + percent = molarity_to_percent(original, mm) |
| 160 | + back = percent_to_molarity(percent, mm) |
| 161 | + assert abs(back - original) < 0.01 |
| 162 | + print(f" [D4-N1-03] Roundtrip 0.5 mol/L NaCl = {percent}% = {back} mol/L... PASS") |
| 163 | + return True |
| 164 | + |
| 165 | +# ══════════════════════════════════════════════════════════════════════ |
| 166 | +# RUNNER |
| 167 | +# ══════════════════════════════════════════════════════════════════════ |
| 168 | + |
| 169 | +def main(): |
| 170 | + tests = [ |
| 171 | + ("D4-N1-01a", test_balance_h2_o2), |
| 172 | + ("D4-N1-01b", test_balance_ch4), |
| 173 | + ("D4-N1-02a", test_molar_mass_glicose), |
| 174 | + ("D4-N1-02b", test_molar_mass_h2o), |
| 175 | + ("D4-N1-02c", test_molar_mass_nacl), |
| 176 | + ("D4-N1-02d", test_molar_mass_caco3), |
| 177 | + ("D4-N1-03a", test_percent_to_molarity_glucose), |
| 178 | + ("D4-N1-03b", test_percent_to_molarity_nacl), |
| 179 | + ("D4-N1-03c", test_molarity_to_percent_roundtrip), |
| 180 | + ] |
| 181 | + |
| 182 | + print("=" * 60) |
| 183 | + print(" TDD TEST SUITE: D4 — Química Computacional (N1)") |
| 184 | + print("=" * 60) |
| 185 | + |
| 186 | + passed = 0 |
| 187 | + failed = 0 |
| 188 | + for name, test_fn in tests: |
| 189 | + try: |
| 190 | + test_fn() |
| 191 | + passed += 1 |
| 192 | + except AssertionError as e: |
| 193 | + print(f" [{name}] FAIL: {e}") |
| 194 | + failed += 1 |
| 195 | + |
| 196 | + print(f"\n RESULT: {passed}/{passed+failed} passed, {failed} failed") |
| 197 | + print("=" * 60) |
| 198 | + |
| 199 | + return failed == 0 |
| 200 | + |
| 201 | +if __name__ == "__main__": |
| 202 | + success = main() |
| 203 | + sys.exit(0 if success else 1) |
0 commit comments