File size: 2,997 Bytes
bc2a98e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
"""Benzene/Toluene adsorption prediction using pre-trained models."""
from pathlib import Path

import joblib
import numpy as np
import yaml


def _load_config() -> dict:
    config_path = Path(__file__).resolve().parent.parent / "configs" / "paths.yaml"
    with open(config_path, "r", encoding="utf-8") as f:
        return yaml.safe_load(f)


def _validate_model_path(path_str: str, label: str) -> Path:
    p = Path(path_str)
    if not p.exists():
        raise FileNotFoundError(f"{label} model file not found: {p}")
    return p


def predict_benzene(eigenvalues: np.ndarray) -> dict:
    """Predict benzene adsorption uptake from SCM eigenvalues.

    Args:
        eigenvalues: np.ndarray of shape (520,) — SCM eigenvalues.

    Returns:
        {"uptake_mg_g": float, "model_version": str, "applicability_warning": str | None}

    Raises:
        ValueError: if input shape is not (520,).
        FileNotFoundError: if model file is missing.
    """
    cfg = _load_config()["adsorption_models"]
    expected_dim = cfg["benzene_target_dim"]

    if eigenvalues.shape != (expected_dim,):
        raise ValueError(f"Expected shape ({expected_dim},), got {eigenvalues.shape}")

    model_path = _validate_model_path(cfg["benzene_model"], "Benzene")
    model = joblib.load(str(model_path))

    X = eigenvalues.reshape(1, -1)
    prediction = float(model.predict(X)[0])

    warning = None
    if prediction < 0:
        warning = f"Predicted negative uptake ({prediction:.2f}), clipped to 0."
        prediction = 0.0

    return {
        "uptake_mg_g": round(prediction, 2),
        "model_version": "RF_tuned_pipeline_seed8",
        "applicability_warning": warning,
    }


def predict_toluene(eigenvalues: np.ndarray) -> dict:
    """Predict toluene adsorption uptake from SCM eigenvalues.

    Args:
        eigenvalues: np.ndarray of shape (584,) — SCM eigenvalues.

    Returns:
        {"uptake_mg_g": float, "model_version": str, "applicability_warning": str | None}

    Raises:
        ValueError: if input shape is not (584,).
        FileNotFoundError: if model file is missing.
    """
    cfg = _load_config()["adsorption_models"]
    expected_dim = cfg["toluene_target_dim"]

    if eigenvalues.shape != (expected_dim,):
        raise ValueError(f"Expected shape ({expected_dim},), got {eigenvalues.shape}")

    model_path = _validate_model_path(cfg["toluene_model"], "Toluene")
    scaler_path = _validate_model_path(cfg["toluene_scaler"], "Toluene scaler")

    scaler = joblib.load(str(scaler_path))
    model = joblib.load(str(model_path))

    X = eigenvalues.reshape(1, -1)
    X_scaled = scaler.transform(X)
    prediction = float(model.predict(X_scaled)[0])

    warning = None
    if prediction < 0:
        warning = f"Predicted negative uptake ({prediction:.2f}), clipped to 0."
        prediction = 0.0

    return {
        "uptake_mg_g": round(prediction, 2),
        "model_version": "XGBoost_seed42",
        "applicability_warning": warning,
    }