carrotcake3's picture
Upload 65 files
a48faac verified
Raw
History Blame Contribute Delete
10.2 kB
"""
Updated CLI with Mixture Optimization Support - INCLUDING CUSTOM BASE FUEL
"""
from core.config import EvolutionConfig, MixtureConfig
def get_user_config() -> EvolutionConfig:
"""Interactive CLI to get configuration from user."""
print("="*70)
print("MIXTURE-AWARE MOLECULE GENERATOR")
print("="*70)
return get_mixture_config()
def get_custom_base_fuel():
"""
Interactive configuration for custom base fuel composition.
Returns:
tuple: (smiles_list, mole_fractions_list) or (None, None) if cancelled
"""
from rdkit import Chem
from rdkit.Chem import Descriptors
print("\n" + "="*70)
print("CUSTOM BASE FUEL CONFIGURATION")
print("="*70)
# Get number of components
while True:
num_comp_str = input("\nHow many components in your base fuel? (2-10): ").strip()
if not num_comp_str.isdigit():
print("❌ Please enter a positive integer.")
continue
num_comp = int(num_comp_str)
if num_comp < 2:
print("❌ Base fuel must have at least 2 components.")
continue
if num_comp > 10:
print("⚠ Warning: Using many components may slow down predictions.")
print(" Model supports max 11 total (base + additive).")
confirm = input(" Continue? (y/n): ").strip().lower()
if confirm != 'y':
continue
break
print(f"\nβœ“ Configuring {num_comp} components")
print("-" * 70)
# Collect components
smiles_list = []
fraction_list = []
total_fraction = 0.0
for i in range(num_comp):
print(f"\nBase Fuel Component {i+1}/{num_comp}:")
print("-" * 40)
# Get SMILES
while True:
smiles = input("Enter SMILES: ").strip()
if not smiles:
print(" SMILES cannot be empty.")
continue
# Validate SMILES
mol = Chem.MolFromSmiles(smiles)
if mol is None:
print(f" Invalid SMILES: '{smiles}'")
print(" Please enter a valid SMILES string.")
continue
# Show molecule info
formula = Chem.rdMolDescriptors.CalcMolFormula(mol)
mol_weight = Descriptors.MolWt(mol)
print(f" βœ“ Valid molecule: {formula} (MW: {mol_weight:.2f} g/mol)")
break
# Get mole fraction
while True:
# For last component, can auto-calculate or allow input
if i == num_comp - 1:
remaining = 1.0 - total_fraction
print(f" Remaining mole fraction: {remaining:.4f}")
auto = input(" Use remaining fraction? (y/n): ").strip().lower()
if auto == 'y':
mole_frac = remaining
print(f" βœ“ Mole fraction: {mole_frac:.4f}")
break
frac_input = input(f" Enter mole fraction (0-1, remaining: {1.0 - total_fraction:.4f}): ").strip()
try:
mole_frac = float(frac_input)
except ValueError:
print(" Invalid input. Please enter a number between 0 and 1.")
continue
if mole_frac <= 0 or mole_frac > 1:
print(" Mole fraction must be between 0 and 1 (exclusive of 0).")
continue
if total_fraction + mole_frac > 1.0001: # Small tolerance
print(f" Total would exceed 1.0 (current total: {total_fraction:.4f})")
continue
break
smiles_list.append(smiles)
fraction_list.append(mole_frac)
total_fraction += mole_frac
print(f" βœ“ Component added (Total fraction: {total_fraction:.4f})")
# Normalize fractions to exactly 1.0
if abs(total_fraction - 1.0) > 1e-6:
print(f"\n⚠ Normalizing mole fractions (sum was {total_fraction:.6f})")
fraction_list = [f / total_fraction for f in fraction_list]
# Display summary
print("\n" + "="*70)
print("CUSTOM BASE FUEL SUMMARY")
print("="*70)
print(f"\n{'#':<5} {'Formula':<15} {'SMILES':<35} {'Mole Frac':<10}")
print("-" * 70)
for i, (smiles, frac) in enumerate(zip(smiles_list, fraction_list), 1):
mol = Chem.MolFromSmiles(smiles)
formula = Chem.rdMolDescriptors.CalcMolFormula(mol)
smiles_display = smiles if len(smiles) <= 33 else smiles[:30] + "..."
print(f"{i:<5} {formula:<15} {smiles_display:<35} {frac:.4f}")
print("-" * 70)
print(f"{'TOTAL':<5} {'':<15} {'':<35} {sum(fraction_list):.4f}")
print("="*70)
# Confirm
confirm = input("\nUse this custom base fuel? (y/n): ").strip().lower()
if confirm != 'y':
print("⚠ Cancelled. Using fossil diesel as default.")
return None, None
return smiles_list, fraction_list
def get_mixture_config() -> EvolutionConfig:
"""Get config for mixture optimization."""
print("\nβœ“ Mode: Mixture/Blend Optimization")
print("\nOptimization Mode:")
print("1. Target a specific CN value (minimize error from target)")
print("2. Maximize CN (find highest possible CN)")
mode = input("Select mode (1 or 2): ").strip()
maximize_cn = (mode == "2")
while mode not in ["1", "2"]:
print("Invalid selection. Please choose 1 or 2.")
mode = input("Select mode (1 or 2): ").strip()
maximize_cn = (mode == "2")
if maximize_cn:
print("\nβœ“ Mode: Maximize Cetane Number")
target_dcn = 100.0
else:
print("\nβœ“ Mode: Target Cetane Number")
while True:
try:
target_dcn = float(input("Enter target mixture DCN (30-80): ").strip())
break
except ValueError:
print("Invalid input. Please enter a number.")
# Base fuel selection
print("\nBase Fuel Selection:")
print("1. Fossil diesel (typical petroleum diesel)")
print("2. Biodiesel (FAME)")
print("3. Custom (enter your own composition)")
print(" β†’ Define your own SMILES and mole fractions")
base_fuel_choice = input("\nSelect base fuel (1-3): ").strip()
if base_fuel_choice == "1":
base_fuel_type = "fossil_diesel"
base_fuel_smiles = None
base_fuel_fractions = None
print("βœ“ Using fossil diesel as base fuel")
print(" Components: C16, C17, C18 n-alkanes + branched + aromatics + cyclic")
elif base_fuel_choice == "2":
base_fuel_type = "biodiesel"
base_fuel_smiles = None
base_fuel_fractions = None
print("βœ“ Using biodiesel as base fuel")
print(" Components: Stearate, Oleate, Linoleate, Palmitate (methyl esters)")
elif base_fuel_choice == "3":
base_fuel_smiles, base_fuel_fractions = get_custom_base_fuel()
# If user cancelled, use default
if base_fuel_smiles is None:
base_fuel_type = "fossil_diesel"
print(" β†’ Defaulting to fossil diesel")
else:
base_fuel_type = "custom"
print(f"\nβœ“ Custom base fuel configured")
print(f" Total components: {len(base_fuel_smiles)}")
print(f" Total mole fraction: {sum(base_fuel_fractions):.4f}")
else:
print(f"⚠ Invalid choice '{base_fuel_choice}', using fossil diesel as default")
base_fuel_type = "fossil_diesel"
base_fuel_smiles = None
base_fuel_fractions = None
while True:
try:
additive_fraction_str = input("\nEnter additive molar fraction (0.01-0.30): ").strip()
additive_fraction = float(additive_fraction_str)
if additive_fraction <= 0 or additive_fraction >= 1:
print("❌ Additive fraction must be between 0 and 1 (exclusive).")
continue
break
except ValueError:
print("❌ Invalid input. Please enter a number (e.g., 0.1 for 10%).")
# YSI minimization (multi-objective NSGA-II)
print("\nMulti-Objective Optimization:")
print(" Minimize mixture YSI alongside DCN? (enables NSGA-II Pareto search)")
minimize_ysi_input = input("Minimize mixture YSI? (y/n) [default: n]: ").strip().lower()
minimize_ysi = minimize_ysi_input == "y"
if minimize_ysi:
print("βœ“ Multi-objective mode: optimize DCN + minimize mixture YSI (NSGA-II)")
else:
print("βœ“ Single-objective mode: optimize DCN only")
# Summary
print("\n" + "="*70)
print("CONFIGURATION SUMMARY:")
print("="*70)
print(f" β€’ Mode: Mixture Optimization")
print(f" β€’ Target Mixture DCN: {target_dcn}")
print(f" β€’ Base Fuel Type: {base_fuel_type}")
if base_fuel_smiles:
print(f" β€’ Base Fuel Components: {len(base_fuel_smiles)}")
print(f" β€’ Additive Fraction: {additive_fraction * 100:.1f}%")
print(f" β€’ Base Fuel Fraction: {(1-additive_fraction) * 100:.1f}%")
print(f" β€’ Minimize Mixture YSI: {'Yes (NSGA-II)' if minimize_ysi else 'No'}")
print("="*70)
# Create mixture config
mixture_cfg = MixtureConfig(
additive_fraction=additive_fraction,
base_fuel_fraction=1.0 - additive_fraction,
base_fuel_type=base_fuel_type,
base_fuel_smiles=base_fuel_smiles,
base_fuel_mole_fractions=base_fuel_fractions,
target_mixture_dcn=target_dcn
)
return EvolutionConfig(
target_cn=target_dcn,
maximize_cn=maximize_cn,
minimize_ysi=minimize_ysi,
mixture_mode=True,
mixture_config=mixture_cfg
)