{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Optimization\n",
    "\n",
    "Let's consider the following code:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# position of flower in 2D\n",
    "flower = np.array([29.1, 19.1])\n",
    "\n",
    "def make_bee_track(t):\n",
    "    \"\"\"Find bee position at time t\"\"\"\n",
    "    pos0 = (13.21, 4.56)\n",
    "    velocity = (3.1, 0.25)\n",
    "    pos_x = pos0[0] + t*velocity[0]\n",
    "    pos_y = pos0[1] + t*velocity[1]\n",
    "    return np.array([pos_x,pos_y])\n",
    "\n",
    "t = np.linspace(0,15,10)\n",
    "bee_track = make_bee_track(t)\n",
    "\n",
    "fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(4,3))\n",
    "ax.plot( [flower[0]], [flower[1]], 'or', label='flower' )\n",
    "ax.plot( bee_track[0], bee_track[1], '.-k', label='bee')\n",
    "ax.axis('equal')\n",
    "ax.legend()\n",
    "ax.set_xlabel('x')\n",
    "ax.set_ylabel('y')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the above code, we *parameterized* the bee trajectory by the variable `t` in the function `make_bee_track()`. This means we could get a new point on the track by choosing a new value of `t`. For example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(make_bee_track(0.0))\n",
    "print(make_bee_track(0.1))\n",
    "print(make_bee_track(0.2))\n",
    "print(make_bee_track(1.0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's measure the distance between the bee and the flower."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def compute_distance(a,b):\n",
    "    a = np.array(a)\n",
    "    b = np.array(b)\n",
    "    return np.sqrt(np.sum((a-b)**2))\n",
    "\n",
    "n_time_steps = bee_track.shape[1]\n",
    "distance = np.zeros(n_time_steps)\n",
    "for i in range(n_time_steps):\n",
    "    bee_pos = bee_track[:,i]\n",
    "    distance[i] = compute_distance(bee_pos, flower)\n",
    "display(distance)\n",
    "\n",
    "fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(4,3))\n",
    "ax.plot( t, distance )\n",
    "ax.set_xlabel('t')\n",
    "ax.set_ylabel('distance')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Given the plot of distance versus t above, we can see the distance is minimized when t is near 5. What is the bee's position when t is 5?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(make_bee_track(5))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can check back to the xy plot to see, indeed, this point is pretty close to the flower.\n",
    "\n",
    "What if we want to know, however, exactly the closest point? There are several ways to find this. Here we are going to use a \"brute force\" approach which will work on many different problems. Specifically, we will use [scipy.optimize.minimize_scalar](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize_scalar.html). The overall idea of this kind of *numerical optimization* is that we find the best fitting parameter to minimize our \"error\".\n",
    "\n",
    "In this example, we are not so much concerned with the exact algorithm being used, but with the way we call this algorithm.\n",
    "\n",
    "Let's create a function which uses the `flower` location to compute distance:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def calc_distance_func(t):\n",
    "    # assume variable 'flower' in our global scope\n",
    "    x1, y1 = flower\n",
    "    # calculate bee position (also assuming 'make_bee_track' in scope)\n",
    "    x2, y2 = make_bee_track(t)\n",
    "    dist = compute_distance((x1,y1), (x2,y2))\n",
    "    print(f't: {t} -> dist: {dist}')\n",
    "    return dist"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "calc_distance_func(0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "calc_distance_func(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import scipy.optimize\n",
    "result = scipy.optimize.minimize_scalar(calc_distance_func)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scipy.optimize.minimize_scalar?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "result"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "type(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "result.x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(calc_distance_func(5))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "calc_distance_func(5.468493134264144)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Where is the bee for this value of `t`?\n",
    "make_bee_track(5.468493134264144)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(4,3))\n",
    "ax.plot( [flower[0]], [flower[1]], 'or', label='flower' )\n",
    "ax.plot( bee_track[0], bee_track[1], '.-k', label='bee')\n",
    "\n",
    "x,y = make_bee_track(5.468493134264144)\n",
    "ax.plot( [x], [y] ,'x', label='closest')\n",
    "ax.axis('equal')\n",
    "ax.legend()\n",
    "ax.set_xlabel('x')\n",
    "ax.set_ylabel('y')\n"
   ]
  }
 ],
 "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.11.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
