{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "5d58304b-e94b-428b-91c8-35d649d97dd7",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "usage: ipykernel_launcher.py [-h] --input INPUT [--outdir OUTDIR]\n",
      "                             [--response RESPONSE] [--area_col AREA_COL]\n",
      "                             [--fm_col FM_COL] [--recompute_sigma]\n",
      "                             [--sn_type {LB}]\n",
      "ipykernel_launcher.py: error: the following arguments are required: --input\n"
     ]
    },
    {
     "ename": "SystemExit",
     "evalue": "2",
     "output_type": "error",
     "traceback": [
      "An exception has occurred, use %tb to see the full traceback.\n",
      "\u001b[0;31mSystemExit\u001b[0m\u001b[0;31m:\u001b[0m 2\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/lib/python3.13/site-packages/IPython/core/interactiveshell.py:3585: UserWarning: To exit: use 'exit', 'quit', or Ctrl-D.\n",
      "  warn(\"To exit: use 'exit', 'quit', or Ctrl-D.\", stacklevel=1)\n"
     ]
    }
   ],
   "source": [
    "\n",
    "#!/usr/bin/env python3\n",
    "# -*- coding: utf-8 -*-\n",
    "\"\"\"\n",
    "Taguchi analysis pipeline for FDM experiment (per user's thesis)\n",
    "- Reads a CSV with columns similar to:\n",
    "  'Eksperiment','Orijentacija','Visina sloja','Širina ekstruzije','Postotak ispune',\n",
    "  'Broj slojeva stijenke','A_ekv [mm^2]','Fm kN]','Sigma [Mpa]','SNR [dB]'\n",
    "- Cleans units to numeric, recomputes Sigma (optional) and SNR (LB, n=1),\n",
    "- Builds response tables (means, Δ), ranks factors, selects optimal levels by SNR,\n",
    "- Predicts response at optimal combination (additive model),\n",
    "- Runs Taguchi-style ANOVA on Sigma,\n",
    "- Saves CSV outputs + main-effects plots + LaTeX snippet.\n",
    "Usage:\n",
    "    python taguchi_from_csv.py --input ispitni_rezultati.csv --outdir out_tlak\n",
    "\"\"\"\n",
    "import argparse, os, re, json\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "def norm_num(x):\n",
    "    if pd.isna(x):\n",
    "        return np.nan\n",
    "    if isinstance(x, (int, float, np.number)):\n",
    "        return float(x)\n",
    "    s = str(x).strip()\n",
    "    s = s.replace(',', '.')\n",
    "    s = s.replace('%','')\n",
    "    s = s.replace(' mm','')\n",
    "    s = s.replace('MPa','').replace('Mpa','')\n",
    "    s = s.replace('kN','').replace('kN]','').replace('[','').replace(']','')\n",
    "    try:\n",
    "        return float(s)\n",
    "    except:\n",
    "        return np.nan\n",
    "\n",
    "def compute_snr_lb(y):\n",
    "    # larger-the-better; handles n=1 case\n",
    "    y = pd.to_numeric(y, errors='coerce')\n",
    "    return 20.0*np.log10(y.clip(lower=1e-12))\n",
    "\n",
    "def response_table(df, factor, col):\n",
    "    t = df.groupby(factor, as_index=False)[col].mean()\n",
    "    t[\"Delta (max-min)\"] = t[col].max() - t[col].min()\n",
    "    t[\"Faktor\"] = factor\n",
    "    return t\n",
    "\n",
    "def taguchi_anova(df, response, factors):\n",
    "    y = df[response].astype(float)\n",
    "    mu = y.mean()\n",
    "    total_ss = ((y - mu)**2).sum()\n",
    "    rows = []\n",
    "    dof_used = 0\n",
    "    ss_used = 0.0\n",
    "    for f in factors:\n",
    "        grp = df.groupby(f)[response].agg(['mean','count'])\n",
    "        ss_f = (grp['count']*(grp['mean']-mu)**2).sum()\n",
    "        dof_f = grp.shape[0]-1\n",
    "        rows.append([f, ss_f, dof_f])\n",
    "        dof_used += dof_f\n",
    "        ss_used += ss_f\n",
    "    err_ss = max(total_ss - ss_used, 0.0)\n",
    "    err_dof = max(len(df)-1 - dof_used, 0)\n",
    "    an = pd.DataFrame(rows, columns=[\"Factor\",\"SS\",\"DOF\"])\n",
    "    an[\"MS\"] = an[\"SS\"]/an[\"DOF\"]\n",
    "    an[\"Pct_contrib_%\"] = (an[\"SS\"]/total_ss*100.0) if total_ss>0 else np.nan\n",
    "    err_row = pd.DataFrame([[\"Error\", err_ss, err_dof, (err_ss/err_dof) if err_dof>0 else np.nan, (err_ss/total_ss*100.0) if total_ss>0 else np.nan]],\n",
    "                           columns=[\"Factor\",\"SS\",\"DOF\",\"MS\",\"Pct_contrib_%\"])\n",
    "    an = pd.concat([an, err_row], ignore_index=True)\n",
    "    return an, mu, total_ss\n",
    "\n",
    "def main():\n",
    "    ap = argparse.ArgumentParser()\n",
    "    ap.add_argument(\"--input\", required=True, help=\"Path to CSV with results\")\n",
    "    ap.add_argument(\"--outdir\", default=None, help=\"Output directory\")\n",
    "    ap.add_argument(\"--response\", default=\"Sigma [Mpa]\", help=\"Response column to analyze (default Sigma [Mpa])\")\n",
    "    ap.add_argument(\"--area_col\", default=\"A_ekv [mm^2]\", help=\"Area column if Sigma should be recomputed from Fm/Area\")\n",
    "    ap.add_argument(\"--fm_col\", default=\"Fm kN]\", help=\"Force column (kN)\")\n",
    "    ap.add_argument(\"--recompute_sigma\", action=\"store_true\", help=\"If set, recompute Sigma = Fm*1000/Area\")\n",
    "    ap.add_argument(\"--sn_type\", default=\"LB\", choices=[\"LB\"], help=\"S/N type (only LB supported here)\")\n",
    "    args = ap.parse_args()\n",
    "\n",
    "    in_path = args.input\n",
    "    outdir = args.outdir or (os.path.splitext(os.path.basename(in_path))[0] + \"_taguchi_out\")\n",
    "    os.makedirs(outdir, exist_ok=True)\n",
    "\n",
    "    df = pd.read_csv(in_path)\n",
    "\n",
    "    # Standard column mapping / cleanup for known names\n",
    "    rename_map = {\n",
    "        \"Visina sloja\":\"Visina sloja [mm]\",\n",
    "        \"Širina ekstruzije\":\"Širina ekstruzije [mm]\",\n",
    "        \"Postotak ispune\":\"Postotak ispune [%]\",\n",
    "        \"Broj slojeva stijenke\":\"Broj stijenki\",\n",
    "        \"Sigma [MPa]\":\"Sigma [Mpa]\",\n",
    "        \"Fm [kN]\":\"Fm kN]\",\n",
    "    }\n",
    "    df = df.rename(columns={k:v for k,v in rename_map.items() if k in df.columns})\n",
    "\n",
    "    # Ensure numeric for relevant columns\n",
    "    if \"Visina sloja [mm]\" in df.columns:\n",
    "        df[\"Visina sloja [mm]\"] = df[\"Visina sloja [mm]\"].apply(norm_num)\n",
    "    if \"Širina ekstruzije [mm]\" in df.columns:\n",
    "        df[\"Širina ekstruzije [mm]\"] = df[\"Širina ekstruzije [mm]\"].apply(norm_num)\n",
    "    if \"Postotak ispune [%]\" in df.columns:\n",
    "        df[\"Postotak ispune [%]\"] = df[\"Postotak ispune [%]\"].apply(norm_num)\n",
    "    if \"Broj stijenki\" in df.columns:\n",
    "        df[\"Broj stijenki\"] = df[\"Broj stijenki\"].apply(norm_num)\n",
    "    if args.area_col in df.columns:\n",
    "        df[args.area_col] = df[args.area_col].apply(norm_num)\n",
    "    if args.fm_col in df.columns:\n",
    "        df[args.fm_col] = df[args.fm_col].apply(norm_num)\n",
    "    if args.response in df.columns:\n",
    "        df[args.response] = df[args.response].apply(norm_num)\n",
    "\n",
    "    # Compute Sigma if asked or missing\n",
    "    if args.recompute_sigma or args.response not in df.columns or df[args.response].isna().all():\n",
    "        if args.fm_col in df.columns and args.area_col in df.columns:\n",
    "            df[args.response] = (df[args.fm_col] * 1000.0) / df[args.area_col]\n",
    "        else:\n",
    "            raise SystemExit(\"Cannot recompute Sigma: missing Fm or Area columns\")\n",
    "\n",
    "    # Compute SNR (LB)\n",
    "    df[\"SNR_LB [dB]\"] = compute_snr_lb(df[args.response])\n",
    "\n",
    "    # Save cleaned raw\n",
    "    raw_out = os.path.join(outdir, \"0_raw_with_SNR.csv\")\n",
    "    df.to_csv(raw_out, index=False)\n",
    "\n",
    "    # Factors to analyze (auto detect from known list)\n",
    "    candidate_factors = [\"Orijentacija\",\"Visina sloja [mm]\",\"Širina ekstruzije [mm]\",\"Postotak ispune [%]\",\"Broj stijenki\"]\n",
    "    factors = [f for f in candidate_factors if f in df.columns]\n",
    "    if len(factors) == 0:\n",
    "        raise SystemExit(\"No known factor columns found. Expected some of: \" + \", \".join(candidate_factors))\n",
    "\n",
    "    # Response tables and deltas\n",
    "    resp_mu = pd.concat([response_table(df, f, args.response) for f in factors], ignore_index=True)\n",
    "    resp_sn = pd.concat([response_table(df, f, \"SNR_LB [dB]\") for f in factors], ignore_index=True)\n",
    "    resp_mu.to_csv(os.path.join(outdir, \"1_response_means_Sigma.csv\"), index=False)\n",
    "    resp_sn.to_csv(os.path.join(outdir, \"2_response_means_SNR.csv\"), index=False)\n",
    "\n",
    "    # Ranking (by Delta)\n",
    "    rank_mu = resp_mu.groupby(\"Faktor\")[\"Delta (max-min)\"].max().sort_values(ascending=False).reset_index().rename(columns={\"Delta (max-min)\":\"Rang delta (Sigma)\"})\n",
    "    rank_sn = resp_sn.groupby(\"Faktor\")[\"Delta (max-min)\"].max().sort_values(ascending=False).reset_index().rename(columns={\"Delta (max-min)\":\"Rang delta (SNR)\"})\n",
    "    ranking = pd.merge(rank_mu, rank_sn, on=\"Faktor\")\n",
    "    ranking.to_csv(os.path.join(outdir, \"3_factor_ranking.csv\"), index=False)\n",
    "\n",
    "    # Optimal levels by SNR\n",
    "    opt_levels = {f: df.groupby(f)[\"SNR_LB [dB]\"].mean().sort_values(ascending=False).index[0] for f in factors}\n",
    "    opt_table = pd.DataFrame({\"Faktor\": list(opt_levels.keys()), \"Optimalna razina (po S/N)\": list(opt_levels.values())})\n",
    "    opt_table.to_csv(os.path.join(outdir, \"4_optimal_levels.csv\"), index=False)\n",
    "\n",
    "    # Prediction at optimal combo (additive model) on response\n",
    "    grand_mean = df[args.response].mean()\n",
    "    k = len(factors)\n",
    "    pred_sigma = sum(df.groupby(f)[args.response].mean().loc[opt_levels[f]] for f in factors) - (k-1)*grand_mean\n",
    "    grand_mean_snr = df[\"SNR_LB [dB]\"].mean()\n",
    "    pred_snr = sum(df.groupby(f)[\"SNR_LB [dB]\"].mean().loc[opt_levels[f]] for f in factors) - (k-1)*grand_mean_snr\n",
    "    pred_df = pd.DataFrame({\n",
    "        \"Predikcija\": [\"Sigma_opt [MPa]\",\"SNR_opt [dB]\",\"Grand mean Sigma [MPa]\",\"Grand mean SNR [dB]\"],\n",
    "        \"Vrijednost\": [pred_sigma, pred_snr, grand_mean, grand_mean_snr]\n",
    "    })\n",
    "    pred_df.to_csv(os.path.join(outdir, \"5_prediction.csv\"), index=False)\n",
    "\n",
    "    # ANOVA (Taguchi-style) on response\n",
    "    anova_df, mu_sigma, totss = taguchi_anova(df, args.response, factors)\n",
    "    anova_df.to_csv(os.path.join(outdir, \"6_anova_sigma.csv\"), index=False)\n",
    "\n",
    "    # Plots: main effects for SNR\n",
    "    for f in factors:\n",
    "        means = df.groupby(f)[\"SNR_LB [dB]\"].mean().reset_index()\n",
    "        # numeric sort if possible\n",
    "        try:\n",
    "            means[f] = pd.to_numeric(means[f], errors=\"ignore\")\n",
    "            means = means.sort_values(by=f)\n",
    "        except:\n",
    "            pass\n",
    "        plt.figure()\n",
    "        plt.plot(means[f], means[\"SNR_LB [dB]\"], marker=\"o\")\n",
    "        plt.xlabel(f)\n",
    "        plt.ylabel(\"S/N (LB) [dB]\")\n",
    "        plt.title(f\"Main effect (S/N): {f}\")\n",
    "        plt.tight_layout()\n",
    "        plt.savefig(os.path.join(outdir, f\"main_effect_SNR_{f}.png\"), dpi=150)\n",
    "        plt.close()\n",
    "\n",
    "    # LaTeX snippet\n",
    "    latex_lines = []\n",
    "    latex_lines.append(r\"% --- Taguchi rezultati (S = Sigma [MPa], S/N larger-the-better) ---\")\n",
    "    latex_lines.append(r\"\\subsection{Rezultati Taguchijeve metode}\")\n",
    "    latex_lines.append(r\"U skladu s ortogonalnom matricom provedena je analiza s kriterijem \\textbf{što-veće-to-bolje}. Za svaku kombinaciju izračunat je S/N omjer \\((\\mathrm{S/N}=20\\log_{10}(\\sigma))\\) te su određeni glavni učinci po razinama i optimalna kombinacija.\")\n",
    "\n",
    "    # Optimal levels\n",
    "    latex_lines.append(r\"\\paragraph{Optimalne razine (po S/N).}\")\n",
    "    latex_lines.append(opt_table.to_latex(index=False, escape=False))\n",
    "    # Prediction\n",
    "    latex_lines.append(r\"\\paragraph{Predikcija odziva na optimalnoj kombinaciji.}\")\n",
    "    latex_lines.append(pred_df.to_latex(index=False, escape=False, float_format='%.2f'))\n",
    "    # Ranking\n",
    "    latex_lines.append(r\"\\paragraph{Rang utjecaja faktora.}\")\n",
    "    latex_lines.append(ranking.to_latex(index=False, escape=False, float_format='%.3f'))\n",
    "    # ANOVA\n",
    "    an_fmt = anova_df.copy()\n",
    "    for c in [\"SS\",\"MS\",\"Pct_contrib_%\"]:\n",
    "        if c in an_fmt.columns:\n",
    "            an_fmt[c] = an_fmt[c].astype(float).round(3)\n",
    "    latex_lines.append(r\"\\paragraph{ANOVA (Taguchi).}\")\n",
    "    latex_lines.append(an_fmt.to_latex(index=False, escape=False))\n",
    "    latex_lines.append(r\"Napomena: budući da je \\(n{=}1\\), pogreška (Error) procijenjena je iz preostalih stupnjeva slobode (Taguchi pooling).\")\n",
    "\n",
    "    with open(os.path.join(outdir, \"taguchi_results.tex\"), \"w\", encoding=\"utf-8\") as f:\n",
    "        f.write(\"\\n\\n\".join(latex_lines))\n",
    "\n",
    "    # Small JSON summary\n",
    "    summary = {\n",
    "        \"outdir\": outdir,\n",
    "        \"factors\": factors,\n",
    "        \"opt_levels\": opt_levels,\n",
    "        \"pred_sigma\": pred_sigma,\n",
    "        \"grand_mean_sigma\": grand_mean,\n",
    "    }\n",
    "    with open(os.path.join(outdir, \"summary.json\"), \"w\", encoding=\"utf-8\") as f:\n",
    "        json.dump(summary, f, ensure_ascii=False, indent=2)\n",
    "\n",
    "    print(\"Done. Outputs in:\", outdir)\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    main()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "56399c17-5135-4fa3-8809-358fef72570b",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
