Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
494 changes: 494 additions & 0 deletions PLANO_PARIDADE_PYGLENN.md

Large diffs are not rendered by default.

495 changes: 495 additions & 0 deletions PYGLENN_PARITY_PLAN.md

Large diffs are not rendered by default.

24 changes: 17 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Computes **Cp(T)**, **H°(T)**, **S°(T)** from NASA polynomial coefficients sto
- Enthalpy of formation lookup
- Enthalpy change between two temperatures
- Command-line interface
- ~2030 species, 3772 temperature intervals
- 2,035 species, 3,779 temperature intervals

## Installation

Expand Down Expand Up @@ -60,10 +60,10 @@ species = calc.get_available_species('O2', exact_match=True)
o2 = species[0]

# Calculate properties at 1000 K
props = calc.calculate_properties(o2['id'], 1000.0)
print(f"Cp = {props['cp']:.2f} J/(mol·K)")
print(f"H° = {props['h_relative']:.1f} J/mol")
print(f"S° = {props['s']:.3f} J/(mol·K)")
props = calc.calculate_properties(o2.id, 1000.0)
print(f"Cp = {props.cp:.2f} J/(mol·K)")
print(f"H° = {props.h_relative:.1f} J/mol")
print(f"S° = {props.s:.3f} J/(mol·K)")

calc.close()
```
Expand All @@ -76,8 +76,8 @@ from pyglenn import ThermochemicalCalculator
with ThermochemicalCalculator() as calc:
# exact_match=True guarantees the correct species (case-insensitive)
species = calc.get_available_species('CH4', exact_match=True)
props = calc.calculate_properties(species[0]['id'], 500.0)
print(f"Cp = {props['cp']:.2f} J/(mol·K)")
props = calc.calculate_properties(species[0].id, 500.0)
print(f"Cp = {props.cp:.2f} J/(mol·K)")
```

### CLI
Expand All @@ -102,6 +102,16 @@ pyglenn build -i thermo.inp -o thermo.db
| `temperature_intervals` | Valid T ranges per species |
| `coefficients` | NASA-7 polynomial coefficients (a1–a7, b1, b2) |
| `file_metadata` | Global file metadata |
| `metadata` | Dataset metadata, including its reference gas constant |

## Gas Constants

`R_UNIVERSAL = 8.31446261815324 J/(mol·K)` is the universal CODATA value and
remains available through the compatibility alias `R`. NASA Glenn/CEA
polynomials in newly built databases declare their fitted reference constant as
`R_GLENN = 8.314510 J/(mol·K)` in SQLite metadata. The calculator reads that
value automatically when it connects; legacy databases fall back to
`R_UNIVERSAL` until explicitly migrated.

## Citing

Expand Down
93 changes: 93 additions & 0 deletions docs/COMPATIBILITY_CONTRACT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Contrato de Compatibilidade — pyglenn 0.2.0

**Data:** 2026-08-22
**Base:** auditoria Glenn.jl v0.4.0 → pyglenn (`PYGLENN_PARITY_PLAN.md` / `PLANO_PARIDADE_PYGLENN.md`)

Este documento registra as decisões da **Etapa 0** (contrato de compatibilidade). Ele
é autoritativo sobre as etapas seguintes: qualquer implementação que contradiga uma
decisão aqui deve ser considerada bug.

---

## D1. API tipada — BREAKING (G1)

Os métodos públicos passam a retornar **dataclasses** (`src/pyglenn/models.py`) em
vez de `dict`. Não haverá camada de compatibilidade que mantenha `dict` como retorno
padrão.

| Método atual | Retorno novo |
|---|---|
| `ThermochemicalCalculator.calculate_properties` | `ThermoProperties` |
| `ThermochemicalCalculator.calculate_properties_range` (novo) | `list[ThermoProperties]` |
| `ThermochemicalCalculator.get_properties_range` | `dict[float, ThermoProperties]` |
| `ThermochemicalCalculator.get_available_species` | `list[SpeciesInfo]` |
| `ThermoDBQuery.find_species` | `list[SpeciesInfo]` |
| `ThermoDBQuery.get_species_data` | `SpeciesData` (espécie + `list[IntervalData]`) |
| `ThermoDBQuery.get_species_info` (novo) | `SpeciesInfo | None` |
| `ThermoDBQuery.get_species_for_temperature` | `IntervalData | None` |
| `ThermoDBQuery.list_species_page` | `tuple[list[SpeciesInfo], int]` |
| `ThermoDBQuery.list_all_species` (novo) | `list[SpeciesInfo]` |
| `ThermoDBQuery.get_statistics` | `DatabaseStats` |

- Dataclasses `frozen` e com `Optional` preciso para campos nulos (`formula`,
`molecular_weight`, `heat_of_formation_298K`, `h_298_to_0`).
- Campos nulos permanecem `None`; nunca viram `0.0` implicitamente.
- `dataclasses.asdict()` é o mecanismo documentado para JSON/serialização.
- **Entradas** de coeficientes (ex.: `calculate_cp/h/s`) continuam aceitando
`dict` **ou** `NASACoefficients` — a mudança é só no **retorno**.

## D2. Política de compatibilidade para resultados dict

- Resultados `dict` são **removidos** dos métodos públicos; não há período de
deprecação por retorno duplo.
- Migração de consumidores via `CHANGELOG` + guia de migração na documentação
(exemplos "antes/depois" com `dict` e com dataclass).
- `dict` continua existindo apenas como formato de **entrada** aceito (coeficientes).

## D3. Migração de metadados — AUTOMÁTICA no connect (G2/G6)

- `ThermoDBQuery.connect()` detecta banco legado (sem a tabela `metadata`) e aplica
a migração **automaticamente**, gravando as chaves canônicas.
- Escopo da escrita: **somente** a tabela aditiva `metadata`. Nunca altera
`species`, `temperature_intervals` ou `coefficients`.
- O fallback para `R_UNIVERSAL` passa a ocorrer **apenas** quando o valor de
`gas_constant_ref` está ausente, inválido ou implausível — não mais por ausência
da tabela.
- `migrate_metadata()` permanece público e idempotente (a automação o invoca).
- **Impacto em testes existentes:** `test_reference_gas_constant_legacy_fallback_and_migration`
deve ser atualizado (o fallback legado por ausência de tabela deixa de existir).

## D4. Tolerâncias numéricas cross-language

| Grandeza | Tolerância |
|---|---|
| Cp, S (J/(mol·K)) | `rel_tol=1e-9`, `abs_tol=1e-6` |
| H (J/mol) | `rel_tol=1e-9`, `abs_tol=1e-6` |
| ΔH°f de O2 (= 0) | `abs_tol=1.0` J/mol |

- Comparações usam `math.isclose(..., rel_tol=..., abs_tol=...)`; nunca comparam
saída arredondada de display.
- `rel_tol=1e-9` é folgado o bastante para diferenças de ordem de avaliação entre
Julia e Python, e estrito o bastante para detectar o viés de ~5,7 ppm (5,7e-6).

## D5. Valor canônico da constante de referência

- A chave `gas_constant_ref` deve ser gravada com a string literal **`'8.314510'`**
(não `str(R_GLENN)`, que produziria `'8.31451'`).
- `gas_constant_ref_source = 'NASA Glenn/CEA thermo.inp (CODATA 1986)'`.
- `R_GLENN = 8.314510` (float) continua sendo o valor numérico usado nos cálculos;
a decisão aqui é sobre a **representação textual persistida**.

## Versionamento

- Pré-1.0: mudança de retorno é **breaking** → bump de minor: `0.1.13 → 0.2.0`.
- `pyproject.toml` (dynamic via `__init__.__version__`), `conda.recipe/meta.yaml` e
distribuições geradas sobem juntos.

## Critérios de aceite do contrato

1. Métodos públicos retornam as dataclasses listadas em D1.
2. `dataclasses.asdict()` reconstrói o `dict` equivalente antigo (mesmas chaves).
3. `connect()` migra automaticamente banco legado sem tocar dados termoquímicos.
4. Tolerâncias D4 aplicadas nos testes cross-language.
5. `gas_constant_ref` persistida como `'8.314510'`.
8 changes: 4 additions & 4 deletions docs/DATABASE_DOCUMENTATION.txt
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,10 @@ SQL QUERY EXAMPLES:

LOADING INFORMATION:

• Total Species Loaded: 2030
• Total Temperature Intervals: 3772
• Total Coefficient Sets: 3772
Skipped Data Lines: 100 (malformed or inconsistent lines)
• Total Species Loaded: 2035
• Total Temperature Intervals: 3779
• Total Coefficient Sets: 3779
Duplicate Species Skipped: 11 (already present)

================================================================================

Expand Down
28 changes: 14 additions & 14 deletions docs/README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ PROJECT FILES:

3. thermo.inp
- Original file with thermochemical data (FORTRAN format)
- Contains ~2030 species and their polynomial coefficients
- Contains 2,035 species and their polynomial coefficients

4. thermo_to_sqlite.py
- Python script that converts thermo.inp to SQLite3
Expand All @@ -30,8 +30,8 @@ PROJECT FILES:

5. thermo.db
- Generated SQLite3 database
- 2030 species, 3772 temperature intervals
- 3772 polynomial coefficient sets
- 2,035 species, 3,779 temperature intervals
- 3,779 polynomial coefficient sets

6. query_thermo_db.py
- Example script with queries and calculations
Expand All @@ -53,17 +53,17 @@ DATABASE STRUCTURE:
└─────────────────┘
┌─────────────────┐
│ species │ (2030 chemical species)
│ species │ (2,035 chemical species)
│ (id, name...) │
└─────────────────┘
┌────────────────────────────┐
│ temperature_intervals │ (3772 intervals)
│ temperature_intervals │ (3,779 intervals)
│ (species_id, temp_min/max) │
└────────────────────────────┘
┌──────────────────────────────┐
│ coefficients │ (3772 sets)
│ coefficients │ (3,779 sets)
│ (a1-a7, b1, b2 coefficients) │
└──────────────────────────────┘

Expand Down Expand Up @@ -101,15 +101,15 @@ HOW TO USE:

DATA LOADED:

✓ Species: 2030
- Gaseous: 1264
- Condensed: 766
✓ Species: 2035
- Gaseous: 1266
- Condensed: 769

✓ Temperature Intervals: 3772
✓ Temperature Intervals: 3779
- Global range: 200 K to 20000 K
- Multiple intervals per species for better accuracy

✓ Polynomial Coefficients: 3772 sets
✓ Polynomial Coefficients: 3779 sets
- 7 coefficients (a1-a7) for Cp
- 2 integration constants (b1, b2)

Expand Down Expand Up @@ -162,13 +162,13 @@ db.connect()

# Search for species
species = db.find_species('O2')
print(species[0]['name']) # O2
print(species[0].name) # O2

# Get species data
data = db.get_species_data(species[0]['id'])
data = db.get_species_data(species[0].id)

# Calculate Cp at 1000 K
coeffs = data['intervals'][0]['coefficients']
coeffs = data.intervals[0].coefficients
cp_r = db.calculate_cp(coeffs, 1000)
print(f"Cp(1000K)/R = {cp_r}")

Expand Down
12 changes: 6 additions & 6 deletions docs/audit/audit_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def collect_data() -> dict[str, dict[str, Any]]:
log.info('Connected to pyglenn database (bundled thermo.db)')
stats = calc.db.get_statistics()
log.info('DB stats: %d species, %d intervals',
stats['total_species'], stats['total_intervals'])
stats.total_species, stats.total_intervals)

for py_name, plot_label, _ in SPECIES:
log.info('▶ Processing %s (%s)', py_name, plot_label)
Expand All @@ -175,14 +175,14 @@ def collect_data() -> dict[str, dict[str, Any]]:
found = calc.get_available_species(py_name, exact_match=True)
# Filter by phase in case multiple phases exist (e.g. gas + liquid)
target = next(
(sp for sp in found if sp['phase'] == 'gas'), None
(sp for sp in found if sp.phase == 'gas'), None
)
if target is None:
log.warning(' ✗ %s not found in pyglenn database!', py_name)
continue

sid = target['id']
log.info(' ✓ ID: %d | MW: %s', sid, target['molecular_weight'])
sid = target.id
log.info(' ✓ ID: %d | MW: %s', sid, target.molecular_weight)

for T in TEMPERATURES:
Tk = round(float(T), 2)
Expand All @@ -196,11 +196,11 @@ def collect_data() -> dict[str, dict[str, Any]]:
except ThermoCalcError:
continue

dh_py = props['h_relative'] - ref['coef']['H'] * 1000.0
dh_py = props.h_relative - ref['coef']['H'] * 1000.0

results[py_name][Tk] = {
'nist': {'cp': ref['cp'], 'dh_298': ref['dh_298'], 's': ref['s']},
'pyglenn': {'cp': props['cp'], 'dh_298': dh_py, 's': props['s']},
'pyglenn': {'cp': props.cp, 'dh_298': dh_py, 's': props.s},
}

n_pts = len(results[py_name])
Expand Down
Loading
Loading