"""AI models and optimization calculations for the PFAS-SBEAD pipeline.""" from __future__ import annotations from typing import Any import numpy as np import pandas as pd from sklearn.ensemble import ( GradientBoostingClassifier, GradientBoostingRegressor, IsolationForest, RandomForestRegressor, ) from sklearn.model_selection import cross_val_score FEATURE_COLUMNS = [ "OLR_kg_m3_d", "HRT_days", "pH", "temperature_C", "COD_mg_L", "VFA_mg_L", "alkalinity_mg_CaCO3_L", "voltage_V", "current_A", "current_density_A_m2", "conductivity_mS_cm", "electrode_area_m2", "electrode_spacing_cm", "initial_PFAS_ug_L", ] AI_SCORE_WEIGHTS = { "pfas_degradation": 0.40, "fluoride_release": 0.30, "short_chain_risk": -0.15, "energy_input": -0.10, "instability": -0.05, } def compute_ai_score( degradation_norm: float, fluoride_norm: float, short_chain_risk: float, energy_norm: float, instability: float, ) -> float: """Compute the composite AI optimization score.""" score = ( AI_SCORE_WEIGHTS["pfas_degradation"] * degradation_norm + AI_SCORE_WEIGHTS["fluoride_release"] * fluoride_norm + AI_SCORE_WEIGHTS["short_chain_risk"] * short_chain_risk + AI_SCORE_WEIGHTS["energy_input"] * energy_norm + AI_SCORE_WEIGHTS["instability"] * instability ) return float(np.clip(score, 0, 1)) def train_degradation_model(df: pd.DataFrame) -> tuple[GradientBoostingRegressor, float]: """XGBoost-style regressor for PFAS degradation prediction.""" X = df[FEATURE_COLUMNS].values y = df["PFAS_degradation_pct"].values model = GradientBoostingRegressor( n_estimators=100, max_depth=4, learning_rate=0.1, random_state=42 ) scores = cross_val_score(model, X, y, cv=5, scoring="r2") model.fit(X, y) return model, float(scores.mean()) def train_fluoride_model(df: pd.DataFrame) -> tuple[RandomForestRegressor, float]: """Random Forest for fluoride release prediction.""" X = df[FEATURE_COLUMNS].values y = df["fluoride_release_mg_L"].values model = RandomForestRegressor(n_estimators=100, max_depth=5, random_state=42) scores = cross_val_score(model, X, y, cv=5, scoring="r2") model.fit(X, y) return model, float(scores.mean()) def train_short_chain_classifier(df: pd.DataFrame) -> tuple[GradientBoostingClassifier, float]: """XGBoost classifier for short-chain PFAS risk.""" X = df[FEATURE_COLUMNS].values y = (df["short_chain_formation_ratio"] > 0.3).astype(int).values model = GradientBoostingClassifier( n_estimators=80, max_depth=3, learning_rate=0.1, random_state=42 ) scores = cross_val_score(model, X, y, cv=5, scoring="accuracy") model.fit(X, y) return model, float(scores.mean()) def train_instability_detector(df: pd.DataFrame) -> IsolationForest: """Isolation Forest for reactor instability detection.""" stability_cols = ["pH_drop", "VFA_accumulation_mg_L", "ORP_drift_mV", "current_instability_index"] X = df[stability_cols].values model = IsolationForest(contamination=0.15, random_state=42) model.fit(X) return model def compute_shap_importance(model: Any, df: pd.DataFrame) -> pd.DataFrame: """Compute feature importance as SHAP-like proxy using tree-based importances.""" X = df[FEATURE_COLUMNS] importances = model.feature_importances_ imp_df = pd.DataFrame({ "feature": FEATURE_COLUMNS, "importance": importances, }).sort_values("importance", ascending=False).reset_index(drop=True) return imp_df def bayesian_next_recommendation(df: pd.DataFrame) -> dict[str, Any]: """ Simple Bayesian-inspired recommendation for the next experiment. Finds conditions that maximize expected AI score based on model trends. """ top_pct = df.nlargest(10, "AI_score") rec = {} for col in FEATURE_COLUMNS: rec[col] = float(np.round(top_pct[col].mean(), 3)) rec["predicted_degradation_pct"] = float(np.round(top_pct["PFAS_degradation_pct"].mean(), 1)) rec["predicted_fluoride_release"] = float(np.round(top_pct["fluoride_release_mg_L"].mean(), 1)) rec["expected_ai_score"] = float(np.round(top_pct["AI_score"].mean(), 3)) rec["confidence"] = float(np.round(1.0 - top_pct["AI_score"].std(), 2)) return rec def sensitivity_analysis(df: pd.DataFrame) -> pd.DataFrame: """Compute correlation-based sensitivity of AI score to each input feature.""" correlations = [] for col in FEATURE_COLUMNS: corr = df[col].corr(df["AI_score"]) correlations.append({"feature": col, "correlation_with_AI_score": round(corr, 4)}) sens_df = pd.DataFrame(correlations) sens_df["abs_correlation"] = sens_df["correlation_with_AI_score"].abs() return sens_df.sort_values("abs_correlation", ascending=False).reset_index(drop=True)