{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "ee453d40",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys, os\n",
    "sys.path.append(os.path.abspath(\"..\"))\n",
    "\n",
    "from datasets.datasets import SyntheticFACE, SyntheticMoons, Dataset, CaliforniaHousing, GermanCredit, GermanCreditv2, GiveMeSomeCredit, AdultIncome\n",
    "from visualisation import *\n",
    "\n",
    "from models.mlp_pytorch import PyTorchMLP\n",
    "\n",
    "from conformal.split_conformal import SplitConformalPrediction\n",
    "from conformal.localised_conformal_baselcp import BaseLCP\n",
    "from conformal.localised_conformal_tree import ConformalCONFEXTree\n",
    "\n",
    "from counterfactual_explanations.counterfactual_benchmarker import *\n",
    "from counterfactual_explanations.gradient_based.auxillary_models import *\n",
    "from counterfactual_explanations.gradient_based.cf_gradient_based import *\n",
    "from counterfactual_explanations.gradient_based.losses import *\n",
    "\n",
    "from counterfactual_explanations.milp_based.cf_conformal import *\n",
    "from counterfactual_explanations.milp_based.cf_mindist import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "f1292775",
   "metadata": {},
   "outputs": [],
   "source": [
    "%load_ext autoreload\n",
    "%autoreload 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "00a647ab",
   "metadata": {},
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'SyntheticMoons' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[1], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m Dataset \u001b[38;5;241m=\u001b[39m \u001b[43mSyntheticMoons\u001b[49m\n\u001b[1;32m      2\u001b[0m factual \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39marray([\u001b[38;5;241m1\u001b[39m, \u001b[38;5;241m1.5\u001b[39m])\n\u001b[1;32m      3\u001b[0m y_target \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m1\u001b[39m \n",
      "\u001b[0;31mNameError\u001b[0m: name 'SyntheticMoons' is not defined"
     ]
    }
   ],
   "source": [
    "Dataset = SyntheticMoons\n",
    "factual = np.array([1, 1.5])\n",
    "y_target = 1 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 492,
   "id": "134cef74",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Axes: >"
      ]
     },
     "execution_count": 492,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 163516 characters]",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "dataset = Dataset(0.6, 0.2, 0.2)\n",
    "X_train, y_train, X_calib, y_calib, X_test, y_test = dataset.get_X_y_split()\n",
    "plot_split_dataset(X_train, X_test, y_train, y_test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 493,
   "id": "a86e8297",
   "metadata": {},
   "outputs": [],
   "source": [
    "model = PyTorchMLP(config={}, input_properties=dataset.input_properties)\n",
    "model.load_or_train(Path(\"/Users/abilkhoo/CONFEX+ copy/method/model/\"), X_train, y_train, True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 494,
   "id": "3b99e94f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Axes: >"
      ]
     },
     "execution_count": 494,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 169956 characters]",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_decision_boundary(model, X_train, X_test, y_train, y_test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 495,
   "id": "b10fc3b8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Set parameter TimeLimit to value 60\n",
      "Gurobi Optimizer version 11.0.3 build v11.0.3rc0 (mac64[arm] - Darwin 23.6.0 23G80)\n",
      "\n",
      "CPU model: Apple M3 Pro\n",
      "Thread count: 12 physical cores, 12 logical processors, using up to 12 threads\n",
      "\n",
      "Academic license 2688232 - for non-commercial use only - registered to am___@kcl.ac.uk\n",
      "Optimize a model with 59 rows, 108 columns and 266 nonzeros\n",
      "Model fingerprint: 0x7597818e\n",
      "Model has 50 general constraints\n",
      "Variable types: 108 continuous, 0 integer (0 binary)\n",
      "Coefficient statistics:\n",
      "  Matrix range     [7e-04, 2e+00]\n",
      "  Objective range  [1e+00, 1e+00]\n",
      "  Bounds range     [0e+00, 0e+00]\n",
      "  RHS range        [1e-02, 2e+00]\n",
      "Presolve added 46 rows and 96 columns\n",
      "Presolve time: 0.00s\n",
      "Presolved: 105 rows, 204 columns, 358 nonzeros\n",
      "Presolved model has 100 SOS constraint(s)\n",
      "Variable types: 104 continuous, 100 integer (100 binary)\n",
      "\n",
      "Root relaxation: objective 0.000000e+00, 72 iterations, 0.00 seconds (0.00 work units)\n",
      "\n",
      "    Nodes    |    Current Node    |     Objective Bounds      |     Work\n",
      " Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time\n",
      "\n",
      "     0     0    0.00000    0   29          -    0.00000      -     -    0s\n",
      "     0     0    0.00000    0   29          -    0.00000      -     -    0s\n",
      "     0     2    0.00000    0   29          -    0.00000      -     -    0s\n",
      "*  510   132              27       2.6712955    0.00806   100%   3.3    0s\n",
      "*  548   132              26       2.6419830    0.00806   100%   3.2    0s\n",
      "*  549   132              26       2.6384237    0.00806   100%   3.2    0s\n",
      "H  587   134                       2.5089984    0.06688  97.3%   3.1    0s\n",
      "H  598   134                       1.6774923    0.06688  96.0%   3.0    0s\n",
      "H  648   134                       1.5802967    0.06688  95.8%   2.9    0s\n",
      "* 1054   108              25       1.4889776    0.06688  95.5%   2.1    0s\n",
      "* 1057   108              26       1.4022809    0.06688  95.2%   2.1    0s\n",
      "* 1058   108              26       1.2878551    0.06688  94.8%   2.1    0s\n",
      "* 1299    24              32       1.1333091    0.61383  45.8%   1.8    0s\n",
      "* 1334    24              28       0.7652372    0.72322  5.49%   1.8    0s\n",
      "* 1338    24              29       0.7232190    0.72322  0.00%   1.8    0s\n",
      "\n",
      "Explored 1387 nodes (2516 simplex iterations) in 0.05 seconds (0.02 work units)\n",
      "Thread count was 12 (of 12 available processors)\n",
      "\n",
      "Solution count 10: 0.723219 0.765237 1.13331 ... 2.63842\n",
      "\n",
      "Optimal solution found (tolerance 1.00e-04)\n",
      "Best objective 7.232189890427e-01, best bound 7.232189890427e-01, gap 0.0000%\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<Axes: >"
      ]
     },
     "execution_count": 495,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 172844 characters]",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#Min distance CF\n",
    "cf_gen = MinDistanceCF(model, dataset.input_properties, config={\"db_distance\":0.05})\n",
    "counterfactual = cf_gen.generate_counterfactual(factual, y_target)\n",
    "plot_counterfactual(factual, counterfactual, model, X_train, X_test, y_train, y_test, faded_background=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 230,
   "id": "c9589805",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Set parameter TimeLimit to value 60\n",
      "Gurobi Optimizer version 11.0.3 build v11.0.3rc0 (mac64[arm] - Darwin 23.6.0 23G80)\n",
      "\n",
      "CPU model: Apple M3 Pro\n",
      "Thread count: 12 physical cores, 12 logical processors, using up to 12 threads\n",
      "\n",
      "Academic license 2688232 - for non-commercial use only - registered to am___@kcl.ac.uk\n",
      "Optimize a model with 62 rows, 110 columns and 272 nonzeros\n",
      "Model fingerprint: 0x88fcc4a1\n",
      "Model has 50 general constraints\n",
      "Variable types: 110 continuous, 0 integer (0 binary)\n",
      "Coefficient statistics:\n",
      "  Matrix range     [7e-04, 2e+00]\n",
      "  Objective range  [1e+00, 1e+00]\n",
      "  Bounds range     [0e+00, 0e+00]\n",
      "  RHS range        [1e-02, 2e+00]\n",
      "Presolve added 43 rows and 94 columns\n",
      "Presolve time: 0.00s\n",
      "Presolved: 105 rows, 204 columns, 358 nonzeros\n",
      "Presolved model has 100 SOS constraint(s)\n",
      "Variable types: 104 continuous, 100 integer (100 binary)\n",
      "\n",
      "Root relaxation: objective 0.000000e+00, 72 iterations, 0.00 seconds (0.00 work units)\n",
      "\n",
      "    Nodes    |    Current Node    |     Objective Bounds      |     Work\n",
      " Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time\n",
      "\n",
      "     0     0    0.00000    0   29          -    0.00000      -     -    0s\n",
      "     0     0    0.00000    0   29          -    0.00000      -     -    0s\n",
      "     0     2    0.00000    0   29          -    0.00000      -     -    0s\n",
      "*  510   124              27       2.6712955    0.06688  97.5%   2.7    0s\n",
      "*  548   124              26       2.6419830    0.06688  97.5%   2.7    0s\n",
      "*  549   124              26       2.6384237    0.06688  97.5%   2.7    0s\n",
      "H  597   124                       1.7575491    0.06688  96.2%   2.5    0s\n",
      "H  843   109                       1.2424672    0.06688  94.6%   2.0    0s\n",
      "H 1110    35                       1.0208246    0.41429  59.4%   1.7    0s\n",
      "* 1163     0              20       0.9162058    0.68127  25.6%   1.7    0s\n",
      "\n",
      "Explored 1205 nodes (2026 simplex iterations) in 0.03 seconds (0.02 work units)\n",
      "Thread count was 12 (of 12 available processors)\n",
      "\n",
      "Solution count 7: 0.916206 1.02082 1.24247 ... 2.6713\n",
      "\n",
      "Optimal solution found (tolerance 1.00e-04)\n",
      "Best objective 9.162057678878e-01, best bound 9.162057678878e-01, gap 0.0000%\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<Axes: >"
      ]
     },
     "execution_count": 230,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 164892 characters]",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "conformalCF = ConformalCF(model, dataset.input_properties, config={\"conformal_class\": SplitConformalPrediction, \"conformal_config\": {\"alpha\": 0.01}})\n",
    "conformalCF.setup(X_train, y_train, X_calib, y_calib)\n",
    "counterfactual_conformal = conformalCF.generate_counterfactual(factual, y_target)\n",
    "plot_counterfactual(factual, counterfactual_conformal, model, X_train, X_test, y_train, y_test, conformal=conformalCF.conformal)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 325,
   "id": "20a1b6f1",
   "metadata": {},
   "outputs": [],
   "source": [
    "ccf = {\"alpha\": 0.05,\n",
    "        \"scorefn_name\":'linear',\n",
    "        \"kernel_name\":'box_l1', \n",
    "        \"kernel_bandwidth\": 0.7}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 326,
   "id": "5526a203",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.1830477714538574\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<Axes: >"
      ]
     },
     "execution_count": 326,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 92472 characters]",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "conformal = BaseLCP(model, dataset.input_properties, ccf)\n",
    "print(conformal.calibrate(X_calib, y_calib, np.array([0, 0.5])))\n",
    "plot_conformal_prediction(model, conformal, X_calib, X_calib, y_calib, y_calib)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 297,
   "id": "274457cf",
   "metadata": {},
   "outputs": [],
   "source": [
    "tree = conformal_cfxt.trees[()]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 302,
   "id": "2934ab03",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 26764 characters]",
      "text/plain": [
       "<Figure size 600x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "def plot_tree_splits(tree, domain=[-2, 2, -2, 2], ax=None, depth=0):\n",
    "    if ax is None:\n",
    "        fig, ax = plt.subplots(figsize=(6, 6))\n",
    "        ax.set_xlim(domain[0], domain[1])\n",
    "        ax.set_ylim(domain[2], domain[3])\n",
    "        ax.set_xlabel('Feature 0')\n",
    "        ax.set_ylabel('Feature 1')\n",
    "    \n",
    "    # Draw split line if not leaf\n",
    "    if not tree.is_leaf:\n",
    "        if tree.feature == 0:\n",
    "            ax.axvline(tree.threshold, color='k', linestyle='--', alpha=0.7)\n",
    "        elif tree.feature == 1:\n",
    "            ax.axhline(tree.threshold, color='k', linestyle='--', alpha=0.7)\n",
    "        \n",
    "        # Recursively plot left and right nodes\n",
    "        if hasattr(tree, 'left_node') and tree.left_node is not None:\n",
    "            plot_tree_splits(tree.left_node, domain, ax, depth+1)\n",
    "        if hasattr(tree, 'right_node') and tree.right_node is not None:\n",
    "            plot_tree_splits(tree.right_node, domain, ax, depth+1)\n",
    "    else:\n",
    "        # Leaf node: plot a marker for its value\n",
    "        if hasattr(tree, 'value'):\n",
    "            ax.plot(tree.centre[0], tree.centre[1], 'ro', markersize=6)\n",
    "    \n",
    "    if depth == 0:\n",
    "        plt.title(\"Tree Splits\")\n",
    "        plt.show()\n",
    "\n",
    "plot_tree_splits(tree, domain=[-2, 2, -2, 2])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 327,
   "id": "866444a6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.1830477714538574\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<Axes: >"
      ]
     },
     "execution_count": 327,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 92472 characters]",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "conformal = BaseLCP(model, dataset.input_properties, ccf)\n",
    "print(conformal.calibrate(X_calib, y_calib, np.array([0, 0.5])))\n",
    "plot_conformal_prediction(model, conformal, X_calib, X_calib, y_calib, y_calib)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 328,
   "id": "70bbcade",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'alpha': 0.05,\n",
       " 'scorefn_name': 'linear',\n",
       " 'kernel_name': 'box_l1',\n",
       " 'kernel_bandwidth': 0.7}"
      ]
     },
     "execution_count": 328,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ccf"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 490,
   "id": "b3b33f2e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Axes: >"
      ]
     },
     "execution_count": 490,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 91228 characters]",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "ccf2 = {\"alpha\": 0.01,\n",
    "        \"scorefn_name\":'linear',\n",
    "        \"kernel_name\":'box_l1', \n",
    "        \"kernel_bandwidth\": 0.2,\n",
    "        \"inf_quantile\": False,\n",
    "        \"global_quantile\": False,\n",
    "        \"incorporate_global\": True,\n",
    "        \"split_midpoint\": False}\n",
    "\n",
    "conformal_cfxt = ConformalCONFEXTree(model, dataset.input_properties, ccf2)\n",
    "conformal_cfxt.calibrate(X_calib, y_calib, None)\n",
    "plot_conformal_prediction(model, conformal_cfxt, X_calib, X_calib, y_calib, y_calib)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 471,
   "id": "d6ce2dd6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Axes: >"
      ]
     },
     "execution_count": 471,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 97832 characters]",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_decision_boundary(model, X_calib, X_calib, y_calib, y_calib)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 351,
   "id": "8291e31c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# conformalCF_cfxt = ConformalCF(model, dataset.input_properties, {\"conformal_class\": ConformalCONFEXTree, \"conformal_config\": ccf2})\n",
    "# conformalCF_cfxt.setup(X_train, y_train, X_calib, y_calib)\n",
    "# counterfactual_conformalcfxt = conformalCF_cfxt.generate_counterfactual(factual, y_target)\n",
    "# plot_counterfactual(factual, counterfactual_conformalcfxt, model, X_train, X_test, y_train, y_test, conformal=conformalCF_cfxt.conformal)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 472,
   "id": "88f5e35e",
   "metadata": {},
   "outputs": [],
   "source": [
    "x, y = 0.25, 0.25"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 473,
   "id": "809084ff",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "centre, [0.25208007 0.37457092], quantile, 0.2419645443558694\n",
      "0.1245709233525698\n",
      "0.2419645443558694\n"
     ]
    }
   ],
   "source": [
    "l = conformal_cfxt.trees[()]._predict_single(np.array([x, y]), v=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 474,
   "id": "9a4c323e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0.08566711, 0.53578649],\n",
       "       [0.28500568, 0.21878425],\n",
       "       [0.24392702, 0.13247589],\n",
       "       [0.35777059, 0.61666596],\n",
       "       [0.41849304, 0.18187794],\n",
       "       [0.25510582, 0.52792661]])"
      ]
     },
     "execution_count": 474,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "l.get_node_points()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 475,
   "id": "fab0aca0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([1, 1, 1, 0, 1, 0])"
      ]
     },
     "execution_count": 475,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "y_calib[l.points_mask]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 476,
   "id": "78b54404",
   "metadata": {},
   "outputs": [],
   "source": [
    "points = X_calib[l.points_mask]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 477,
   "id": "07c59fd4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.legend.Legend at 0x17c863970>"
      ]
     },
     "execution_count": 477,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 101244 characters]",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "points = X_calib[l.points_mask]\n",
    "ax = plot_decision_boundary(conformal_cfxt.trees[()], X_calib, X_calib, y_calib, y_calib, unary=True)\n",
    "\n",
    "ax.plot(np.array([x]),  np.array([y]), 'bx', markersize=13)\n",
    "ax.plot(points[:, 0], points[:, 1], 'gx', markersize=8, label='Selected Points')\n",
    "ax.legend()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "confexplus",
   "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.10.15"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
