{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "ebc7825f",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:34.361123Z",
     "iopub.status.busy": "2026-10-02T14:41:34.360848Z",
     "iopub.status.idle": "2026-10-02T14:41:34.366039Z",
     "shell.execute_reply": "2026-10-02T14:41:34.365430Z"
    },
    "papermill": {
     "duration": 0.008983,
     "end_time": "2026-10-02T14:41:34.366622+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:34.357639+00:00",
     "status": "completed"
    },
    "tags": [
     "remove-input",
     "active-ipynb",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "try:\n",
    "    from openmdao.utils.notebook_utils import notebook_mode  # noqa: F401\n",
    "except ImportError:\n",
    "    !python -m pip install openmdao[notebooks]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2decd46e",
   "metadata": {
    "papermill": {
     "duration": 0.001425,
     "end_time": "2026-10-02T14:41:34.414140+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:34.412715+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "# MetaModelSemiStructuredComp\n",
    "\n",
    "Semi-structued training data occurs frequently in engineering applications like performance maps for turbine engine performance, propeller performance, aerodyanmic drag, etc. For example, a simple semi-structured grid could be a mostly structured grid, but with a few data points missing on one area of the grid. Another more general kind of semi-structured grid is a set of data where the first dimension is regular, but the other dimensions are irregular. An example of this is the figure below, where the 'x' dimension is regular, and the data points fall on distinct values, but the 'y' dimension is not regular.\n",
    "\n",
    "![semi_example](images/semi_example.jpg)\n",
    "\n",
    "The [MetaModelStructured](metamodelstructured_comp) cannot be used if any points are missing, or if any dimension is not regular. You could use [MetaModelUnStructured](metamodelunstructured_comp) for this data, but you would lose the performance and flexibility you can gain from exploiting the semi structured nature of the data.\n",
    "\n",
    "`MetaModelSemiStructuredComp` handles semi-structured data by treating every \"column\" of data beyond the first dimension independently.  In the figure above, it doesn't matter that two adjacent columns have different points, as the interpolation in 'y' happens independently in each column before those values are subsequently interpolated in 'x'. \n",
    "\n",
    "The following interpolation methods are supported in `MetaModelSemiStructuredComp`:\n",
    "\n",
    "```{eval-rst}\n",
    "+---------------+--------+------------------------------------------------------------------+\n",
    "| Method        | Order  | Description                                                      |\n",
    "+===============+========+==================================================================+\n",
    "| slinear       | 1      | Basic linear interpolation                                       |\n",
    "+---------------+--------+------------------------------------------------------------------+\n",
    "| lagrange2     | 2      | Second order Lagrange polynomial                                 |\n",
    "+---------------+--------+------------------------------------------------------------------+\n",
    "| lagrange3     | 3      | Third order Lagrange polynomial                                  |\n",
    "+---------------+--------+------------------------------------------------------------------+\n",
    "| akima         | 3      | Interpolation using Akima splines                                |\n",
    "+---------------+--------+------------------------------------------------------------------+\n",
    "```\n",
    "\n",
    "Note that `MetaModelSemiStructuredComp` only accepts scalar inputs and outputs. If you have a multivariable function, each input variable needs its own named OpenMDAO input.\n",
    "\n",
    "For multi-dimensional data, fits are computed on a separable per-axis basis. A single interpolation method is used for all dimensions, so the minimum table dimension must be high enough to use the chosen interpolate. Further, the minimum dimension will potentially be driven by the sparsest area of your data, so care should be taken to choose the smallest regular dimension as the first dimension.\n",
    "\n",
    "Extrapolation is supported, and is enabled by default. Given the more sparse nature of semi-structured data, we expect that extrapolation will occur more often. When evaluating a point, if any of the requested values falls beyond the first or last point in a row, you are extrapolating. There is always a risk when extrapolating, so it is important to be careful. Extrapolation can be disabled via the `extrapolate` option (see below).\n",
    "\n",
    "## MetaModelSemiStructuredComp Options\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "c9b666a6",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:34.417990Z",
     "iopub.status.busy": "2026-10-02T14:41:34.417805Z",
     "iopub.status.idle": "2026-10-02T14:41:35.620962Z",
     "shell.execute_reply": "2026-10-02T14:41:35.620340Z"
    },
    "papermill": {
     "duration": 1.206024,
     "end_time": "2026-10-02T14:41:35.621710+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:34.415686+00:00",
     "status": "completed"
    },
    "tags": [
     "remove-input"
    ]
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<!DOCTYPE html>\n",
       "<html lang=\"en\">\n",
       "<head>\n",
       "    <style>\n",
       "        h2 {\n",
       "            text-align: center;\n",
       "        }\n",
       "    </style>\n",
       "</head>\n",
       "<body>\n",
       "    <h2></h2>\n",
       "        <table style=\"border: 1px solid #999; border-collapse: collapse;\">\n",
       "        <tr><th style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; background-color: #E9E9E9; text-align: left;\">Option</th><th style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; background-color: #E9E9E9; text-align: left;\">Default</th><th style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; background-color: #E9E9E9; text-align: left;\">Acceptable Values</th><th style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; background-color: #E9E9E9; text-align: left;\">Acceptable Types</th><th style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; background-color: #E9E9E9; text-align: left;\">Description</th></tr>\n",
       "       <tr style=\"background-color: ghostwhite;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">always_opt</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">False</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[True, False]</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[&#x27;bool&#x27;]</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">If True, force nonlinear operations on this component to be included in the optimization loop even if this component is not relevant to the design variables and responses.</td></tr>\n",
       "       <tr style=\"background-color: #F3F3F3;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">default_shape</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">(1,)</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">N/A</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[&#x27;tuple&#x27;]</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">Default shape for variables that do not set val to a non-scalar value or set shape, shape_by_conn, copy_shape, or compute_shape. Default is (1,).</td></tr>\n",
       "       <tr style=\"background-color: ghostwhite;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">derivs_method</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">N/A</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[&#x27;jax&#x27;, &#x27;cs&#x27;, &#x27;fd&#x27;, None]</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">N/A</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">The method to use for computing derivatives</td></tr>\n",
       "       <tr style=\"background-color: #F3F3F3;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">distributed</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">False</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[True, False]</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[&#x27;bool&#x27;]</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">If True, set all variables in this component as distributed across multiple processes</td></tr>\n",
       "       <tr style=\"background-color: ghostwhite;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">extrapolate</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">True</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[True, False]</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[&#x27;bool&#x27;]</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">Sets whether extrapolation should be performed when an input is out of bounds.</td></tr>\n",
       "       <tr style=\"background-color: #F3F3F3;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">method</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">slinear</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[&#x27;slinear&#x27;, &#x27;lagrange2&#x27;, &#x27;lagrange3&#x27;, &#x27;akima&#x27;]</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">N/A</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">Spline interpolation method to use for all outputs.</td></tr>\n",
       "       <tr style=\"background-color: ghostwhite;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">run_root_only</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">False</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[True, False]</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[&#x27;bool&#x27;]</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">If True, call compute, compute_partials, linearize, apply_linear, apply_nonlinear, solve_linear, solve_nonlinear, and compute_jacvec_product only on rank 0 and broadcast the results to the other ranks.</td></tr>\n",
       "       <tr style=\"background-color: #F3F3F3;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">training_data_gradients</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">False</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[True, False]</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[&#x27;bool&#x27;]</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">When True, compute gradients with respect to training data values.</td></tr>\n",
       "       <tr style=\"background-color: ghostwhite;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">use_jit</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">True</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[True, False]</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[&#x27;bool&#x27;]</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">If True, attempt to use jit on compute_primal, assuming jax or some other AD package capable of jitting is active.</td></tr>\n",
       "       <tr style=\"background-color: #F3F3F3;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">vec_size</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">1</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">N/A</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[&#x27;int&#x27;]</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">Number of points to evaluate at once.</td></tr>\n",
       "    </table>\n",
       "</body>\n",
       "</html>\n"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import openmdao.api as om\n",
    "om.show_options_table(\"openmdao.components.meta_model_semi_structured_comp.MetaModelSemiStructuredComp\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "86a9dcaa",
   "metadata": {
    "papermill": {
     "duration": 0.034106,
     "end_time": "2026-10-02T14:41:35.657120+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:35.623014+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "## MetaModelSemiStructuredComp Constructor\n",
    "\n",
    "The call signature for the `MetaModelSemiStructuredComp` constructor is:\n",
    "\n",
    "```{eval-rst}\n",
    "    .. automethod:: openmdao.components.meta_model_semi_structured_comp.MetaModelSemiStructuredComp.__init__\n",
    "        :noindex:\n",
    "```\n",
    "\n",
    "## MetaModelSemiStructuredComp Examples\n",
    "\n",
    "A simple quick-start example is fitting a 2-dimensional data set that has been evaluated on a small selection of semi structured points:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "6f1cfd08",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:35.662359Z",
     "iopub.status.busy": "2026-10-02T14:41:35.661944Z",
     "iopub.status.idle": "2026-10-02T14:41:35.667007Z",
     "shell.execute_reply": "2026-10-02T14:41:35.666314Z"
    },
    "papermill": {
     "duration": 0.00757,
     "end_time": "2026-10-02T14:41:35.667471+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:35.659901+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "data_x = np.array([\n",
    "    1.0,\n",
    "    1.0,\n",
    "    1.0,\n",
    "    1.0,\n",
    "    1.0,\n",
    "\n",
    "    1.3,\n",
    "    1.3,\n",
    "    1.3,\n",
    "    1.3,\n",
    "    1.3,\n",
    "\n",
    "    1.6,\n",
    "    1.6,\n",
    "    1.6,\n",
    "    1.6,\n",
    "    1.6,\n",
    "\n",
    "    2.1,\n",
    "    2.1,\n",
    "    2.1,\n",
    "    2.1,\n",
    "    2.1,\n",
    "\n",
    "    2.5,\n",
    "    2.5,\n",
    "    2.5,\n",
    "    2.5,\n",
    "    2.5,\n",
    "    2.5,\n",
    "\n",
    "    2.9,\n",
    "    2.9,\n",
    "    2.9,\n",
    "    2.9,\n",
    "    2.9,\n",
    "\n",
    "    3.2,\n",
    "    3.2,\n",
    "    3.2,\n",
    "    3.2,\n",
    "\n",
    "    3.6,\n",
    "    3.6,\n",
    "    3.6,\n",
    "    3.6,\n",
    "    3.6,\n",
    "    3.6,\n",
    "\n",
    "    4.3,\n",
    "    4.3,\n",
    "    4.3,\n",
    "    4.3,\n",
    "\n",
    "    4.6,\n",
    "    4.6,\n",
    "    4.6,\n",
    "    4.6,\n",
    "    4.6,\n",
    "    4.6,\n",
    "\n",
    "    4.9,\n",
    "    4.9,\n",
    "    4.9,\n",
    "    4.9,\n",
    "    4.9,\n",
    "])\n",
    "\n",
    "data_y = np.array([\n",
    "    1.0,\n",
    "    1.5,\n",
    "    1.6,\n",
    "    1.7,\n",
    "    1.9,\n",
    "\n",
    "    1.0,\n",
    "    1.5,\n",
    "    1.6,\n",
    "    1.7,\n",
    "    1.9,\n",
    "\n",
    "    1.0,\n",
    "    1.5,\n",
    "    1.6,\n",
    "    1.7,\n",
    "    1.9,\n",
    "\n",
    "    1.0,\n",
    "    1.6,\n",
    "    1.7,\n",
    "    1.9,\n",
    "    2.4,\n",
    "\n",
    "    1.3,\n",
    "    1.7,\n",
    "    1.9,\n",
    "    2.4,\n",
    "    2.6,\n",
    "    2.9,\n",
    "\n",
    "    1.9,\n",
    "    2.1,\n",
    "    2.3,\n",
    "    2.5,\n",
    "    3.1,\n",
    "\n",
    "    2.3,\n",
    "    2.5,\n",
    "    3.1,\n",
    "    3.7,\n",
    "\n",
    "    2.3,\n",
    "    3.1,\n",
    "    3.3,\n",
    "    3.7,\n",
    "    4.1,\n",
    "    4.2,\n",
    "\n",
    "    3.3,\n",
    "    3.6,\n",
    "    4.0,\n",
    "    4.5,\n",
    "\n",
    "    3.9,\n",
    "    4.2,\n",
    "    4.4,\n",
    "    4.5,\n",
    "    4.6,\n",
    "    4.7,\n",
    "\n",
    "    4.4,\n",
    "    4.5,\n",
    "    4.6,\n",
    "    4.7,\n",
    "    4.9,\n",
    "])\n",
    "\n",
    "data_values = 3.0 + np.sin(data_x*0.2) * np.cos(data_y*0.3)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e42f8099",
   "metadata": {
    "papermill": {
     "duration": 0.001538,
     "end_time": "2026-10-02T14:41:35.670872+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:35.669334+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "Notice that the table input data is expressed as flat ndarrays (or lists). They should be sorted in ascending coordinate order starting with the lowest n-tuple. The table output values are also in a flat array that corresponds to the input arrays."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "c8be8381",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:35.674561Z",
     "iopub.status.busy": "2026-10-02T14:41:35.674427Z",
     "iopub.status.idle": "2026-10-02T14:41:36.884230Z",
     "shell.execute_reply": "2026-10-02T14:41:36.883733Z"
    },
    "papermill": {
     "duration": 1.212541,
     "end_time": "2026-10-02T14:41:36.884951+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:35.672410+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1790952096.870893] [runnervm8df0l:6678 :0]        ib_iface.c:1269 UCX  ERROR mana_0: iface 0x560b4ed66a90 failed to create UD QP TX wr:256 sge:6 inl:64 resp:0 RX wr:4096 sge:1 resp:0 failed: Operation not supported\n",
      "[1790952096.871158] [runnervm8df0l:6678 :0]      ucp_worker.c:1412 UCX  ERROR uct_iface_open(ud_verbs/mana_0:1) failed: Input/output error\n",
      "[3.39415716] [6.78831431]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[runnervm8df0l:06678] pml_ucx.c:313  Error: Failed to create UCP worker\n"
     ]
    }
   ],
   "source": [
    "import openmdao.api as om\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "interp = om.MetaModelSemiStructuredComp(method='lagrange2')\n",
    "\n",
    "# The order that the inputs are added matters. The first table dimension is 'x'.\n",
    "interp.add_input('x', data_x)\n",
    "interp.add_input('y', data_y)\n",
    "\n",
    "interp.add_output('f', training_data=data_values)\n",
    "\n",
    "# Including a second output to show that multiple outputs are supported.\n",
    "# All outputs use the same input grid but use different values at each point.\n",
    "interp.add_output('g', training_data=2.0 * data_values)\n",
    "        \n",
    "model.add_subsystem('interp', interp)\n",
    "\n",
    "prob.setup()\n",
    "\n",
    "prob.set_val('interp.x', np.array([3.1]))\n",
    "prob.set_val('interp.y', np.array([2.75]))\n",
    "\n",
    "prob.run_model()\n",
    "\n",
    "print(prob.get_val('interp.f'), prob.get_val('interp.g'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "9bbd76fe",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:36.890084Z",
     "iopub.status.busy": "2026-10-02T14:41:36.889921Z",
     "iopub.status.idle": "2026-10-02T14:41:36.893807Z",
     "shell.execute_reply": "2026-10-02T14:41:36.893234Z"
    },
    "papermill": {
     "duration": 0.007641,
     "end_time": "2026-10-02T14:41:36.894425+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:36.886784+00:00",
     "status": "completed"
    },
    "tags": [
     "remove-output",
     "remove-input"
    ]
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "np.float64(9.811090972574418e-10)"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "assert_near_equal(prob.get_val('interp.f'), 3.39415716, 1e-7)\n",
    "assert_near_equal(prob.get_val('interp.g'), 2.0 * 3.39415716, 1e-7)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "675905cc",
   "metadata": {
    "papermill": {
     "duration": 0.001285,
     "end_time": "2026-10-02T14:41:36.899378+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:36.898093+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "You can also predict multiple independent output points by setting the `vec_size` argument to be equal to the number of points you want to predict. Here, we set it to 3 and predict 3 points with `MetaModelSemiStructuredComp`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "d3892ce2",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:36.903738Z",
     "iopub.status.busy": "2026-10-02T14:41:36.903529Z",
     "iopub.status.idle": "2026-10-02T14:41:36.912358Z",
     "shell.execute_reply": "2026-10-02T14:41:36.911828Z"
    },
    "papermill": {
     "duration": 0.012087,
     "end_time": "2026-10-02T14:41:36.913113+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:36.901026+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[3.39415716 3.31511313 3.2608656 ]\n"
     ]
    }
   ],
   "source": [
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "interp = om.MetaModelSemiStructuredComp(method='lagrange2', vec_size=3)\n",
    "\n",
    "# The order that the inputs are added matters. The first table dimension is 'x'.\n",
    "interp.add_input('x', data_x)\n",
    "interp.add_input('y', data_y)\n",
    "\n",
    "interp.add_output('f', training_data=data_values)\n",
    "\n",
    "model.add_subsystem('interp', interp)\n",
    "\n",
    "prob.setup()\n",
    "\n",
    "prob.set_val('interp.x', np.array([3.1, 4.7, 5.1]))\n",
    "prob.set_val('interp.y', np.array([2.75, 3.9, 4.2]))\n",
    "\n",
    "prob.run_model()\n",
    "\n",
    "print(prob.get_val('interp.f'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "12b81d82",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:36.925165Z",
     "iopub.status.busy": "2026-10-02T14:41:36.924977Z",
     "iopub.status.idle": "2026-10-02T14:41:36.928248Z",
     "shell.execute_reply": "2026-10-02T14:41:36.927653Z"
    },
    "papermill": {
     "duration": 0.014024,
     "end_time": "2026-10-02T14:41:36.928945+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:36.914921+00:00",
     "status": "completed"
    },
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "np.float64(9.388138709288902e-10)"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "assert_near_equal(prob.get_val('interp.f'), np.array([3.39415716, 3.31511313, 3.2608656 ]), 1e-7)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6ebfae0d",
   "metadata": {
    "papermill": {
     "duration": 0.001972,
     "end_time": "2026-10-02T14:41:36.932850+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:36.930878+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "Finally, it is possible to compute gradients with respect to the given\n",
    "output training data. These gradients are not computed by default, but\n",
    "can be enabled by setting the option `training_data_gradients` to `True`.\n",
    "When this is done, for each output that is added to the component, a\n",
    "corresponding input is added to the component with the same name but with an\n",
    "`_train` suffix. This allows you to connect in the training data as an input\n",
    "array, if desired.\n",
    "\n",
    "The following simple example shows the preceding model with training inputs enabled."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "9b357582",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:36.937444Z",
     "iopub.status.busy": "2026-10-02T14:41:36.937248Z",
     "iopub.status.idle": "2026-10-02T14:41:36.944965Z",
     "shell.execute_reply": "2026-10-02T14:41:36.944576Z"
    },
    "papermill": {
     "duration": 0.010628,
     "end_time": "2026-10-02T14:41:36.945371+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:36.934743+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[3.39415716]\n"
     ]
    }
   ],
   "source": [
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "interp = om.MetaModelSemiStructuredComp(method='lagrange2', training_data_gradients=True)\n",
    "interp.add_input('x', data_x)\n",
    "interp.add_input('y', data_y)\n",
    "\n",
    "# Initialize all trable values to zero.\n",
    "interp.add_output('f', training_data=np.zeros(len(data_x)))\n",
    "\n",
    "model.add_subsystem('interp', interp)\n",
    "\n",
    "prob.setup(force_alloc_complex=True)\n",
    "\n",
    "prob.set_val('interp.x', np.array([3.1]))\n",
    "prob.set_val('interp.y', np.array([2.75]))\n",
    "\n",
    "# The table values come from somewhere in OpenMDAO, in this case, from the indepvarcomp output.\n",
    "# They could also come from an upstream component if you connect the input to another source.\n",
    "prob.set_val('interp.f_train', data_values)\n",
    "\n",
    "prob.run_model()\n",
    "\n",
    "print(prob.get_val('interp.f'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "8b8235e5",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:36.956035Z",
     "iopub.status.busy": "2026-10-02T14:41:36.955891Z",
     "iopub.status.idle": "2026-10-02T14:41:36.960593Z",
     "shell.execute_reply": "2026-10-02T14:41:36.960029Z"
    },
    "papermill": {
     "duration": 0.007947,
     "end_time": "2026-10-02T14:41:36.961290+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:36.953343+00:00",
     "status": "completed"
    },
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "np.float64(9.811090972574418e-10)"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "assert_near_equal(prob.get_val('interp.f'), 3.39415716, 1e-7)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8407b9ab",
   "metadata": {
    "papermill": {
     "duration": 0.002004,
     "end_time": "2026-10-02T14:41:36.965280+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:36.963276+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "## Standalone Interface for Table Interpolation\n",
    "\n",
    "The underlying interpolation algorithms can be used standalone (i.e., outside of the\n",
    "MetaModelSemiStructuredComp) through the `InterpNDSemi` class. This can be useful for inclusion in another\n",
    "component.  The following component shows how to perform interpolation on the a much simpler table than before. We only have 2 points at x=1.0, so we can only choose 'slinear'."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "20578eae",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:37.020740Z",
     "iopub.status.busy": "2026-10-02T14:41:37.020585Z",
     "iopub.status.idle": "2026-10-02T14:41:37.024443Z",
     "shell.execute_reply": "2026-10-02T14:41:37.023876Z"
    },
    "papermill": {
     "duration": 0.006938,
     "end_time": "2026-10-02T14:41:37.025490+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:37.018552+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[1. 1.]\n",
      " [1. 2.]\n",
      " [2. 1.]\n",
      " [2. 2.]\n",
      " [2. 3.]]\n",
      "value [2.25]\n",
      "derivative [[1. 2.]]\n"
     ]
    }
   ],
   "source": [
    "from openmdao.components.interp_util.interp_semi import InterpNDSemi\n",
    "\n",
    "# Simple grid with one point missing.\n",
    "x = [1.0, 1.0, 2.0, 2.0, 2.0]\n",
    "y = [1.0, 2.0, 1.0, 2.0, 3.0]\n",
    "values = [1.0, 2.5, 1.5, 4.0, 4.5]\n",
    "\n",
    "grid = np.array([x, y]).T\n",
    "print(grid)\n",
    "\n",
    "# We only have 2 points at x=1.0, so 'slinear' is the only one we can use.\n",
    "interp = InterpNDSemi(grid, values, method='slinear')\n",
    "\n",
    "x = np.array([1.5, 1.5])\n",
    "f, df_dx = interp.interpolate(x, compute_derivative=True)\n",
    "\n",
    "print('value', f)\n",
    "print('derivative', df_dx)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "bea1cb9f",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:37.029569Z",
     "iopub.status.busy": "2026-10-02T14:41:37.029435Z",
     "iopub.status.idle": "2026-10-02T14:41:37.033291Z",
     "shell.execute_reply": "2026-10-02T14:41:37.032804Z"
    },
    "papermill": {
     "duration": 0.006363,
     "end_time": "2026-10-02T14:41:37.033673+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:37.027310+00:00",
     "status": "completed"
    },
    "tags": [
     "remove-output",
     "remove-input"
    ]
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "np.float64(0.0)"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "\n",
    "actual = np.array([2.25])\n",
    "deriv_actual = np.array([[1.0, 2.0]])\n",
    "\n",
    "assert_near_equal(f, actual, tolerance=1e-7)\n",
    "assert_near_equal(df_dx, deriv_actual, tolerance=1e-7)"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "kernelspec": {
   "display_name": "Python 3",
   "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.14"
  },
  "orphan": true,
  "papermill": {
   "default_parameters": {},
   "duration": 3.771843,
   "end_time": "2026-10-02T14:41:37.449420+00:00",
   "environment_variables": {},
   "exception": null,
   "input_path": "/home/runner/work/OpenMDAO/OpenMDAO/openmdao/docs/openmdao_book/features/building_blocks/components/metamodelsemistructured_comp.ipynb",
   "output_path": "/home/runner/work/OpenMDAO/OpenMDAO/openmdao/docs/_executed_book/features/building_blocks/components/metamodelsemistructured_comp.ipynb",
   "parameters": {},
   "start_time": "2026-10-02T14:41:33.677577+00:00",
   "version": "2.7.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}