import numpy as np import pandas as pd from pathlib import Path METALS = ["Zn", "Cu", "Fe", "Co", "Ni", "Zr", "Al", "Cr", "Mn", "In", "Cd", "Mg"] LINKERS = ["BDC", "BTC", "BPDC", "NDC", "TATB", "BTB", "H2BDC", "TPDC", "ADB", "DPA", "BPYDC", "TCPP"] TOPOLOGIES = ["pcu", "dia", "soc", "rht", "hkust", "uio", "mof-5", "mil-53", "zif-8", "nott-112", "pcn-250", "BUT-66"] SOLVENTS = ["DMF", "DEF", "DMSO", "EtOH", "MeOH", "Water", "DMA", "THF"] APPLICATIONS = ["CO2 capture", "H2 storage", "CH4 storage", "Catalysis", "Water harvesting", "Drug delivery", "Battery", "Sensing"] DATABASES = ["CoRE-MOF", "QMOF", "CSD", "hMOF", "ToBaCCo", "Literature"] RNG = np.random.default_rng(42) def generate_mof_dataset(n: int = 8000) -> pd.DataFrame: metals = RNG.choice(METALS, n) linkers = RNG.choice(LINKERS, n) topologies = RNG.choice(TOPOLOGIES, n) solvents = RNG.choice(SOLVENTS, n) applications = RNG.choice(APPLICATIONS, n) databases = RNG.choice(DATABASES, n) structural_score = np.clip(RNG.beta(6, 1.5, n) * 100, 0, 100) chemical_score = np.clip(RNG.beta(5, 1.8, n) * 100, 0, 100) novelty_score = np.clip(RNG.beta(2.5, 2.5, n) * 100, 0, 100) fto_score = np.clip(RNG.beta(4, 2, n) * 100, 0, 100) synthesizability = np.clip(RNG.beta(3.5, 2, n) * 100, 0, 100) surface_area = np.clip(RNG.lognormal(7.0, 0.6, n), 200, 8000) pore_size = np.clip(RNG.lognormal(1.5, 0.5, n), 2, 40) void_fraction = np.clip(RNG.beta(3, 3, n), 0.15, 0.90) co2_uptake = np.clip(RNG.lognormal(4.0, 0.8, n), 20, 500) h2_uptake = np.clip(RNG.lognormal(2.0, 0.7, n), 1, 80) ch4_uptake = np.clip(RNG.lognormal(3.5, 0.7, n), 10, 250) band_gap = np.clip(RNG.normal(2.8, 1.2, n), 0.2, 6.5) thermal_stability = np.clip(RNG.normal(350, 80, n), 100, 600) water_stability = RNG.choice(["High", "Medium", "Low"], n, p=[0.3, 0.45, 0.25]) catalytic_activity = np.clip(RNG.beta(2.5, 3, n) * 100, 0, 100) ion_conductivity = np.clip(RNG.lognormal(-3, 1.5, n), 0.0001, 0.5) synthesis_steps = RNG.integers(1, 7, n) synthesis_difficulty = np.clip(RNG.beta(2, 3, n) * 10, 0.5, 9.5) synthesis_cost = np.clip(RNG.lognormal(4.5, 0.8, n), 50, 5000) green_score = np.clip(RNG.beta(3, 2, n) * 100, 0, 100) success_prob = np.clip(RNG.beta(5, 2, n) * 100, 0, 100) precursor_availability = RNG.choice(["High", "Medium", "Low"], n, p=[0.5, 0.35, 0.15]) similar_patents = RNG.integers(0, 20, n) patent_similarity = np.clip(RNG.beta(2, 4, n) * 100, 0, 100) claim_overlap = RNG.choice(["None", "Low", "Medium", "High"], n, p=[0.25, 0.35, 0.3, 0.1]) fto_risk = np.where(fto_score > 70, "Low", np.where(fto_score > 40, "Medium", "High")) perf_weight = 0.30 synth_weight = 0.20 struct_weight = 0.15 novelty_weight = 0.15 fto_weight = 0.10 cost_weight = 0.05 sustain_weight = 0.05 composite_score = ( np.clip(co2_uptake / 5, 0, 100) * perf_weight + synthesizability * synth_weight + structural_score * struct_weight + novelty_score * novelty_weight + fto_score * fto_weight + (100 - synthesis_difficulty * 10) * cost_weight + green_score * sustain_weight ) composite_score = np.clip(composite_score, 0, 100) mof_ids = [f"MOF-{i+1:05d}" for i in range(n)] years = RNG.integers(2010, 2026, n) df = pd.DataFrame({ "mof_id": mof_ids, "metal": metals, "linker": linkers, "topology": topologies, "solvent": solvents, "application": applications, "source_database": databases, "year": years, "structural_score": structural_score.round(2), "chemical_score": chemical_score.round(2), "novelty_score": novelty_score.round(2), "fto_score": fto_score.round(2), "synthesizability": synthesizability.round(2), "surface_area_m2g": surface_area.round(1), "pore_size_angstrom": pore_size.round(2), "void_fraction": void_fraction.round(3), "co2_uptake_cc_g": co2_uptake.round(2), "h2_uptake_cc_g": h2_uptake.round(2), "ch4_uptake_cc_g": ch4_uptake.round(2), "band_gap_eV": band_gap.round(3), "thermal_stability_C": thermal_stability.round(1), "water_stability": water_stability, "catalytic_activity": catalytic_activity.round(2), "ion_conductivity_S_cm": ion_conductivity.round(6), "synthesis_steps": synthesis_steps, "synthesis_difficulty": synthesis_difficulty.round(2), "synthesis_cost_USD": synthesis_cost.round(0).astype(int), "green_score": green_score.round(2), "success_probability": success_prob.round(2), "precursor_availability": precursor_availability, "similar_patents": similar_patents, "patent_similarity_pct": patent_similarity.round(2), "claim_overlap": claim_overlap, "fto_risk": fto_risk, "composite_score": composite_score.round(2), }) rank = df["composite_score"].rank(ascending=False).astype(int) df["rank"] = rank return df.sort_values("rank").reset_index(drop=True) def generate_pipeline_metrics(n_days: int = 365) -> pd.DataFrame: dates = pd.date_range("2024-01-01", periods=n_days, freq="D") ingested = RNG.integers(80, 200, n_days).cumsum() validated_struct = (ingested * RNG.uniform(0.85, 0.96, n_days)).astype(int) validated_chem = (validated_struct * RNG.uniform(0.88, 0.97, n_days)).astype(int) auto_repaired = (ingested - validated_struct) * RNG.integers(3, 8, n_days) auto_repaired = np.clip(auto_repaired.cumsum(), 0, None) generated = RNG.integers(200, 1000, n_days).cumsum() shortlisted = (generated * RNG.uniform(0.05, 0.15, n_days)).astype(int) return pd.DataFrame({ "date": dates, "total_ingested": ingested, "struct_validated": validated_struct, "chem_validated": validated_chem, "auto_repaired": auto_repaired, "generated_candidates": generated, "shortlisted": shortlisted, }) def save_datasets(output_dir: str = "data") -> None: path = Path(output_dir) path.mkdir(exist_ok=True) mof_df = generate_mof_dataset(8000) mof_df.to_csv(path / "mof_candidates.csv", index=False) pipeline_df = generate_pipeline_metrics(365) pipeline_df.to_csv(path / "pipeline_metrics.csv", index=False) print(f"Saved {len(mof_df)} MOF records and {len(pipeline_df)} pipeline daily records.") if __name__ == "__main__": save_datasets()