cnt-ai-platform / utils /data_generator.py
shrut27's picture
Upload folder using huggingface_hub
41a1ded verified
Raw
History Blame Contribute Delete
8.25 kB
import numpy as np
import pandas as pd
from pathlib import Path
np.random.seed(42)
def fecp_bond_order(T: float) -> float:
return float(np.maximum(0.02, 0.589 - np.maximum(0, (T - 600) / 1400) * 0.57))
def cc_bond_order(T: float) -> float:
return float(np.maximum(0.05, 1.22 - np.maximum(0, (T - 1100) / 900) * 1.05))
def ch_bond_order(T: float) -> float:
return float(np.maximum(0.04, 0.93 - np.maximum(0, (T - 900) / 1000) * 0.80))
def generate_bond_order_data() -> pd.DataFrame:
temps = np.arange(200, 2001, 50)
return pd.DataFrame({
"temperature_K": temps,
"fecp_bond_order": [fecp_bond_order(T) for T in temps],
"cc_bond_order": [cc_bond_order(T) for T in temps],
"ch_bond_order": [ch_bond_order(T) for T in temps],
"fecp_survival_pct": [min(100, max(2, (fecp_bond_order(T) / 0.589) * 100)) for T in temps],
"cc_survival_pct": [min(100, max(3, (cc_bond_order(T) / 1.22) * 100)) for T in temps],
"ch_survival_pct": [min(100, max(4, (ch_bond_order(T) / 0.93) * 100)) for T in temps],
})
def generate_master_dataset(n: int = 8000) -> pd.DataFrame:
"""
Synthetic DI-FCCVD reactor runs. Each row = one synthesis experiment.
Features span catalyst chemistry → reactor conditions → CNT quality.
Includes multiple catalyst types: Fe, Fe-C, Fe-S, Fe-Mo-C, Fe-Co-C, Fe-Ni-C
"""
rng = np.random.default_rng(42)
# CNT product type selection
cnt_types = rng.choice(['SWCNT', 'DWCNT', 'MWCNT'], size=n, p=[0.4, 0.3, 0.3])
# Catalyst composition
catalyst_types = rng.choice(['Fe', 'Fe-C', 'Fe-S', 'Fe-Mo-C', 'Fe-Co-C', 'Fe-Ni-C'],
size=n, p=[0.2, 0.15, 0.2, 0.15, 0.15, 0.15])
temp_C = rng.uniform(600, 1100, n)
H2_sccm = rng.uniform(50, 500, n)
Ar_sccm = rng.uniform(100, 1000, n)
ferrocene_wt = rng.uniform(0.5, 5.0, n)
sulfur_wt = rng.uniform(0.0, 1.0, n)
# Add promoter metals
mo_ppm = np.where(np.char.find(catalyst_types.astype(str), 'Mo') >= 0,
rng.uniform(10, 200, n), 0.0)
co_ppm = np.where(np.char.find(catalyst_types.astype(str), 'Co') >= 0,
rng.uniform(50, 500, n), 0.0)
ni_ppm = np.where(np.char.find(catalyst_types.astype(str), 'Ni') >= 0,
rng.uniform(50, 500, n), 0.0)
injection_depth_cm = rng.uniform(5, 25, n)
temp_norm = (temp_C - 600) / 500
h2_norm = H2_sccm / 500
fe_norm = ferrocene_wt / 5.0
s_norm = sulfur_wt / 1.0
# Decomposition rate — higher T, more ferrocene → higher rate
decomposition_rate = (
0.3 + 0.5 * temp_norm + 0.2 * fe_norm
+ rng.normal(0, 0.03, n)
).clip(0.05, 1.0)
# NP size — optimal ~1300°C, sulfur reduces size
np_size_nm = (
2.5 + 2.0 * np.sin(temp_norm * np.pi)
- 0.8 * s_norm
+ 0.3 * fe_norm
+ rng.normal(0, 0.2, n)
).clip(0.5, 8.0)
# Residence time — depends on flow rates and injection depth
residence_time_s = (
(injection_depth_cm / 15) * (800 / (H2_sccm + Ar_sccm)) * 10
+ rng.normal(0, 0.5, n)
).clip(0.5, 20.0)
# CNT diameter ≈ 1.8 * NP radius (empirical scaling)
CNT_diameter_nm = (np_size_nm * 0.85 + rng.normal(0, 0.15, n)).clip(0.5, 7.0)
# Purity — high T, high H2, sulfur additive all help
purity_percent = (
40 + 25 * temp_norm + 15 * h2_norm + 10 * s_norm
- 5 * np.abs(np_size_nm - 2.0)
+ rng.normal(0, 3, n)
).clip(20, 99)
# Yield — high ferrocene, high T, good residence time
yield_mg_hr = (
50 + 120 * fe_norm + 80 * temp_norm + 30 * (residence_time_s / 20)
+ rng.normal(0, 10, n)
).clip(5, 400)
# Aspect ratio — long CNTs need right temp + H2 + injection depth
aspect_ratio = (
500 + 2000 * temp_norm * h2_norm + 300 * (injection_depth_cm / 25)
- 100 * CNT_diameter_nm
+ rng.normal(0, 200, n)
).clip(100, 10000)
# CNT growth probability score (target variable)
cnt_growth_prob = (
0.1 + 0.3 * temp_norm + 0.2 * h2_norm + 0.15 * s_norm
+ 0.15 * (1 - np.abs(np_size_nm - 2.0) / 6)
+ 0.1 * fe_norm
+ rng.normal(0, 0.04, n)
).clip(0.02, 0.98)
# Wall thickness (for DWCNT and MWCNT)
wall_layers = np.where(cnt_types == 'SWCNT', 1,
np.where(cnt_types == 'DWCNT', 2,
rng.integers(3, 15, n)))
# Nucleation energy barrier (catalyst-dependent)
nucleation_barrier_eV = np.where(catalyst_types == 'Fe', 2.1,
np.where(catalyst_types == 'Fe-S', 1.8,
np.where(catalyst_types == 'Fe-Mo-C', 1.6,
np.where(catalyst_types == 'Fe-Co-C', 1.7,
np.where(catalyst_types == 'Fe-Ni-C', 1.75, 1.9)))))
df = pd.DataFrame({
"cnt_type": cnt_types,
"catalyst_type": catalyst_types,
"temp_C": np.round(temp_C, 1),
"H2_sccm": np.round(H2_sccm, 1),
"Ar_sccm": np.round(Ar_sccm, 1),
"ferrocene_wt": np.round(ferrocene_wt, 3),
"sulfur_wt": np.round(sulfur_wt, 3),
"mo_ppm": np.round(mo_ppm, 1),
"co_ppm": np.round(co_ppm, 1),
"ni_ppm": np.round(ni_ppm, 1),
"injection_depth_cm": np.round(injection_depth_cm, 1),
"decomposition_rate": np.round(decomposition_rate, 4),
"NP_size_nm": np.round(np_size_nm, 3),
"residence_time_s": np.round(residence_time_s, 2),
"CNT_diameter_nm": np.round(CNT_diameter_nm, 3),
"wall_layers": wall_layers,
"nucleation_barrier_eV": np.round(nucleation_barrier_eV, 3),
"purity_percent": np.round(purity_percent, 1),
"yield_mg_hr": np.round(yield_mg_hr, 1),
"aspect_ratio": np.round(aspect_ratio).astype(int),
"cnt_growth_prob": np.round(cnt_growth_prob, 4),
})
return df
def get_ferrocene_atoms(T_K: float):
"""
Returns atom positions for two ferrocene molecules in a simulation box.
Positions jitter with temperature.
"""
rng = np.random.default_rng(int(T_K * 1000) % 2**31)
f = max(0.0, min(1.0, (T_K - 200) / 1800))
atoms = []
for mol_offset, cx, cz in [("A", -4, 0), ("B", 4, 2)]:
# Fe displacement increases with T above 1100 K
fe_disp = f * 2.5 if T_K > 1100 else 0
amp_fe = 0.08 + f * 0.15
fe_pos = np.array([
cx + (rng.random() - 0.5) * amp_fe * 2 + fe_disp * (rng.random() - 0.5),
0 + (rng.random() - 0.5) * amp_fe * 2 + fe_disp * (rng.random() - 0.5),
cz + (rng.random() - 0.5) * amp_fe * 2 + fe_disp * (rng.random() - 0.5),
])
atoms.append({"type": "Fe", "x": fe_pos[0], "y": fe_pos[1], "z": fe_pos[2], "mol": mol_offset})
for ring_y in [1.65, -1.65]:
for i in range(5):
angle = i * 2 * np.pi / 5
r = 2.0
amp_c = 0.08 + f * 0.25
cx_c = cx + r * np.cos(angle)
cz_c = cz + r * np.sin(angle)
atoms.append({
"type": "C",
"x": cx_c + (rng.random() - 0.5) * amp_c * 2,
"y": ring_y + (rng.random() - 0.5) * amp_c * 2,
"z": cz_c + (rng.random() - 0.5) * amp_c * 2,
"mol": mol_offset,
})
amp_h = 0.08 + f * 0.45
hx = cx + (r + 1.1) * np.cos(angle)
hz = cz + (r + 1.1) * np.sin(angle)
atoms.append({
"type": "H",
"x": hx + (rng.random() - 0.5) * amp_h * 2,
"y": ring_y + (rng.random() - 0.5) * amp_h * 2,
"z": hz + (rng.random() - 0.5) * amp_h * 2,
"mol": mol_offset,
})
return atoms
def save_dataset(output_path: str = "data/master_dataset.csv"):
df = generate_master_dataset()
Path(output_path).parent.mkdir(parents=True, exist_ok=True)
df.to_csv(output_path, index=False)
return df