Error Handling¶
PHOENIX provides a hierarchy of exceptions for precise error handling.
Exception Hierarchy¶
PhoenixError (base)
├── InvalidSmilesError
├── UnsupportedElementError
├── UnsupportedStructureError
├── MissingGroupError
├── DecompositionError
└── BalanceError
├── OverconstrainedError
└── UnderconstrainedError
PhoenixError¶
Base exception for all PHOENIX errors:
from phoenix import Compound, PhoenixError
try:
compound = Compound.from_smiles("invalid")
except PhoenixError as e:
print(f"PHOENIX error: {e}")
Use this for catching all PHOENIX-specific errors while letting other exceptions propagate.
InvalidSmilesError¶
Raised when a SMILES string cannot be parsed:
from phoenix import Compound, InvalidSmilesError
try:
compound = Compound.from_smiles("not-a-valid-smiles")
except InvalidSmilesError as e:
print(f"Invalid SMILES: {e.smiles}")
print(f"Message: {e}")
Attributes¶
| Attribute | Type | Description |
|---|---|---|
smiles |
str | The invalid SMILES string |
Common Causes¶
- Typos in SMILES string
- Unbalanced parentheses or brackets
- Invalid atom symbols
- Malformed ring closures
# Examples of invalid SMILES
invalid_examples = [
"CCO(", # Unbalanced parenthesis
"C1CC", # Unclosed ring
"CXC", # Invalid atom 'X'
"random", # Not SMILES at all
]
UnsupportedElementError¶
Raised when a compound contains elements not supported by PHOENIX:
from phoenix import Compound, UnsupportedElementError
try:
compound = Compound.from_smiles("[Fe](C)(C)(C)(C)C") # Ferrocene-like
except UnsupportedElementError as e:
print(f"Unsupported elements: {e.elements}")
Attributes¶
| Attribute | Type | Description |
|---|---|---|
elements |
list[str] | List of unsupported element symbols |
Supported Elements¶
PHOENIX supports: C, H, N, O, S, P, F, Cl, Br
Unsupported include: - Metals (Fe, Cu, Zn, etc.) - Iodine (I) - Silicon (Si) - Boron (B) - Selenium (Se)
UnsupportedStructureError¶
Raised for valid SMILES with unsupported molecular structures:
from phoenix import Compound, UnsupportedStructureError
# Charged species
try:
compound = Compound.from_smiles("[NH4+]")
except UnsupportedStructureError as e:
print(f"Unsupported: {e.reason}")
print(f"SMILES: {e.smiles}")
# Radical species
try:
compound = Compound.from_smiles("[CH3]") # Methyl radical
except UnsupportedStructureError as e:
print(f"Unsupported: {e.reason}")
Attributes¶
| Attribute | Type | Description |
|---|---|---|
reason |
str | Why the structure is unsupported |
smiles |
str | None |
Unsupported Structures¶
- Charged species (ions)
- Radical species
- Coordination complexes
MissingGroupError¶
Raised when Benson Group Additivity lacks data for a molecule:
from phoenix import Compound, MissingGroupError
try:
compound = Compound.from_smiles("complex-molecule")
hf = compound.enthalpy_of_formation
except MissingGroupError as e:
print(f"Missing groups: {e.groups}")
Attributes¶
| Attribute | Type | Description |
|---|---|---|
groups |
list[str] | List of missing group names |
Common Causes¶
- Unusual bonding patterns
- Rare functional groups
- Strained ring systems not in database
DecompositionError¶
Raised when decomposition calculation fails:
from phoenix import Compound, DecompositionError
try:
compound = Compound.from_smiles("CCO")
decomp = compound.max_decomposition()
except DecompositionError as e:
print(f"Decomposition failed: {e.reason}")
if e.formula:
print(f"Formula: {e.formula}")
Attributes¶
| Attribute | Type | Description |
|---|---|---|
reason |
str | Why decomposition failed |
formula |
str | None |
BalanceError¶
Base exception for reaction balancing errors:
from phoenix import Reaction, BalanceError
try:
rxn = Reaction.from_smiles(["CH4"], ["CO2"])
rxn.balance()
except BalanceError as e:
print(f"Balance error: {e}")
OverconstrainedError¶
Raised when reaction constraints are inconsistent:
from phoenix import Reaction, OverconstrainedError
try:
# Impossible: 1 CH4 + 1 O2 cannot give these products
rxn = Reaction.from_smiles(
reactants=[("CH4", 1), ("O=O", 1)],
products=[("O=C=O", 1), ("O", 2)]
)
rxn.balance()
except OverconstrainedError as e:
print(f"Over-constrained: {e}")
print(f"Imbalances: {e.imbalances}")
Attributes¶
| Attribute | Type | Description |
|---|---|---|
imbalances |
dict[str, float] | Element imbalances (positive = excess product) |
Understanding Imbalances¶
except OverconstrainedError as e:
for element, imbalance in e.imbalances.items():
if imbalance > 0:
print(f"{element}: {imbalance:.2f} excess on product side")
else:
print(f"{element}: {-imbalance:.2f} excess on reactant side")
UnderconstrainedError¶
Raised when multiple valid solutions exist:
from phoenix import Reaction, UnderconstrainedError
try:
rxn = Reaction.from_smiles(
reactants=["C", "O=O"],
products=["O=C=O", "C=O"] # Both CO2 and CO possible
)
rxn.balance()
except UnderconstrainedError as e:
print(f"Under-constrained: {e}")
print(f"Degrees of freedom: {e.degrees_of_freedom}")
print(f"Suggestion: {e.suggestion}")
Attributes¶
| Attribute | Type | Description |
|---|---|---|
degrees_of_freedom |
int | Additional coefficients needed |
suggestion |
str | None |
Resolving Under-Constrained Reactions¶
from phoenix import Reaction, Auto
# Fix one more coefficient
rxn = Reaction.from_smiles(
reactants=[("C", 1), ("O=O", Auto)],
products=[("O=C=O", 1), ("C=O", 0)] # Force no CO
)
rxn.balance()
Best Practices¶
Catch Specific Exceptions¶
from phoenix import (
Compound,
InvalidSmilesError,
UnsupportedElementError,
UnsupportedStructureError,
PhoenixError,
)
def safe_create_compound(smiles):
try:
return Compound.from_smiles(smiles)
except InvalidSmilesError:
print(f"Invalid SMILES: {smiles}")
except UnsupportedElementError as e:
print(f"Unsupported elements: {e.elements}")
except UnsupportedStructureError as e:
print(f"Unsupported structure: {e.reason}")
except PhoenixError as e:
print(f"Other PHOENIX error: {e}")
return None
Batch Processing with Error Recovery¶
from phoenix import Compound, PhoenixError
smiles_list = ["CCO", "invalid", "[Fe]", "c1ccccc1"]
results = []
errors = []
for smiles in smiles_list:
try:
compound = Compound.from_smiles(smiles)
result = compound.evaluate_hazard()
results.append((smiles, result))
except PhoenixError as e:
errors.append((smiles, type(e).__name__, str(e)))
print(f"Processed: {len(results)}")
print(f"Errors: {len(errors)}")
Logging Errors¶
import logging
from phoenix import Compound, PhoenixError
logging.basicConfig(level=logging.WARNING)
logger = logging.getLogger(__name__)
def process_smiles(smiles):
try:
return Compound.from_smiles(smiles)
except PhoenixError as e:
logger.warning(f"Failed to process {smiles}: {type(e).__name__}: {e}")
return None
Using the screen() Function¶
The screen() function handles errors gracefully:
from phoenix import screen
results = screen(["CCO", "invalid", "[Fe]", "c1ccccc1"])
# Check for errors in DataFrame
df = results.dataframe
failed = df[df['error'].notna()]
for _, row in failed.iterrows():
print(f"SMILES: {row['smiles']}")
print(f"Error: {row['error']}: {row['error_message']}")
Error Messages¶
PHOENIX provides informative error messages:
# InvalidSmilesError
"Invalid SMILES string: 'not-valid'"
# UnsupportedElementError
"Unsupported elements: Fe, Cu"
"Iodine (I) is not supported in MVP. Unsupported elements: I"
# UnsupportedStructureError
"Unsupported structure: Charged species not supported (formal charge: 1)"
"Unsupported structure: Radical species not supported"
# OverconstrainedError
"Reaction is over-constrained: atom conservation cannot be satisfied. Imbalances: O: +1.0000"
# UnderconstrainedError
"Reaction is under-constrained: multiple solutions exist (2 degree(s) of freedom). Specify 2 more coefficient(s) to get a unique solution."
Next Steps¶
- Compounds: Creating compounds
- Batch Processing: Handling errors in batch
- API Reference: Complete exceptions reference