carrotcake3's picture
Upload 49 files
6f5a0e0 verified
Raw History Blame Contribute Delete
1.73 kB
from dataclasses import dataclass, asdict, field
from typing import Optional, Dict
@dataclass
class Molecule:
smiles: str
cn: float
cn_error: float
cn_score: float = 0.0 # keep for backwards compat, will be set by fitness()
bp: Optional[float] = None
ysi: Optional[float] = None
density: Optional[float] = None
lhv: Optional[float] = None
dynamic_viscosity: Optional[float] = None
chemical_valid: bool = True
chemical_flags: str = 'OK'
ood_warning: bool = False
confidence_score: float = 100.0
mixture_dcn: Optional[float] = None
blend_ratio: Optional[float] = None
def fitness(self, config) -> float:
"""
Single scalar fitness for weighted parent selection.
Higher is always better (so we can use as selection weight).
- maximize_cn mode: raw CN value
- target_cn mode: inverted error (so lower error = higher fitness)
- YSI penalty: applied as soft penalty, not hard filter
"""
if config.maximize_cn:
base = self.cn
else:
# Avoid division by zero; small epsilon floor
base = 1.0 / (self.cn_error + 1e-6)
if config.minimize_ysi and self.ysi is not None:
# Soft penalty: YSI above 50 is penalised, below is neutral/rewarded
ysi_penalty = max(0.0, (self.ysi - 50) / 50) # 0 at ysi=50, 1 at ysi=100
base *= (1.0 - 0.3 * ysi_penalty) # up to 30% penalty
return max(base, 1e-9) # keep strictly positive for sampling weights
def to_dict(self) -> Dict:
return {k: v for k, v in asdict(self).items() if v is not None}