Spaces:
Running
Running
| """ | |
| 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 | |
| ) | |