{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "4fa93f54",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:21.492836Z",
     "iopub.status.busy": "2026-10-02T14:41:21.492641Z",
     "iopub.status.idle": "2026-10-02T14:41:21.496496Z",
     "shell.execute_reply": "2026-10-02T14:41:21.496088Z"
    },
    "papermill": {
     "duration": 0.007035,
     "end_time": "2026-10-02T14:41:21.497297+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:21.490262+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": "9004c892",
   "metadata": {
    "papermill": {
     "duration": 0.001331,
     "end_time": "2026-10-02T14:41:21.516009+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:21.514678+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "# ExternalCodeComp\n",
    "\n",
    "ExternalCodeComp is a component that runs an external program in a subprocess on your operating system.\n",
    "\n",
    "If external programs do not have Python APIs, it is necessary to \"file wrap\" them.\n",
    "`ExternalCodeComp` is a utility component that makes file wrapping easier by\n",
    "taking care of the mundane tasks associated with executing the external application.\n",
    "These include:\n",
    "\n",
    "- Making the system call using the Subprocess module\n",
    "- Redirecting `stdin, stdout,` and `stderr` to the user's specification\n",
    "- Capturing error codes\n",
    "- Defining environment variables\n",
    "- Handling timeout and polling\n",
    "- Running the code on a remote server if required\n",
    "\n",
    "## ExternalCodeComp Options\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "4a9d083a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:21.519778Z",
     "iopub.status.busy": "2026-10-02T14:41:21.519624Z",
     "iopub.status.idle": "2026-10-02T14:41:22.936777Z",
     "shell.execute_reply": "2026-10-02T14:41:22.936217Z"
    },
    "papermill": {
     "duration": 1.420498,
     "end_time": "2026-10-02T14:41:22.937773+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:21.517275+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;\">allowed_return_codes</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[0]</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;\">N/A</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">List of return codes that are considered successful.</td></tr>\n",
       "       <tr style=\"background-color: #F3F3F3;\"><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: ghostwhite;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">command</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[]</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;list&#x27;, &#x27;str&#x27;]</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">Command to be executed. If it is a string, then this is the command line to execute and the &#x27;shell&#x27; argument to &#x27;subprocess.Popen()&#x27;  is set to True. If it is a list; the first entry is the command to execute.</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;\">env_vars</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">{}</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;\">N/A</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">Environment variables required by the command.</td></tr>\n",
       "       <tr style=\"background-color: #F3F3F3;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">external_input_files</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[]</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;\">N/A</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">List of input files that must exist before execution, otherwise an Exception is raised.</td></tr>\n",
       "       <tr style=\"background-color: ghostwhite;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">external_output_files</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">[]</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;\">N/A</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">List of output files that must exist after execution, otherwise an Exception is raised.</td></tr>\n",
       "       <tr style=\"background-color: #F3F3F3;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">fail_hard</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, external code errors raise a &#x27;hard&#x27; exception (RuntimeError), otherwise errors raise a &#x27;soft&#x27; exception (AnalysisError).</td></tr>\n",
       "       <tr style=\"background-color: ghostwhite;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">poll_delay</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">0.0</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;\">N/A</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">Delay between polling for command completion. A value of zero will use an internally computed default.</td></tr>\n",
       "       <tr style=\"background-color: #F3F3F3;\"><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: ghostwhite;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">timeout</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">0.0</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;\">N/A</td><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">Maximum time to wait for command completion. A value of zero implies an infinite wait.</td></tr>\n",
       "       <tr style=\"background-color: #F3F3F3;\"><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",
       "    </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.external_code_comp.ExternalCodeComp\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "82e28e8b",
   "metadata": {
    "papermill": {
     "duration": 0.008041,
     "end_time": "2026-10-02T14:41:22.948294+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:22.940253+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "\n",
    "## ExternalCodeComp Constructor\n",
    "\n",
    "The call signature for the `ExternalCodeComp` constructor is:\n",
    "\n",
    "```{eval-rst}\n",
    "    .. automethod:: openmdao.components.external_code_comp.ExternalCodeComp.__init__\n",
    "        :noindex:\n",
    "```\n",
    "\n",
    "## ExternalCodeComp Example\n",
    "\n",
    "In this example we will give an example based on a common scenario of a code that takes\n",
    "its inputs from an input file, performs some computations, and then writes the results\n",
    "to an output file. `ExternalCodeComp` supports multiple input and output files but\n",
    "for simplicity, this example only uses one of each.  Also, for the purposes of this\n",
    "example we have kept the input and output files as simple as possible. In practice,\n",
    "the data will likely be organized in some defined way and thus some care must be taken\n",
    "to read and write the data as dictated by the file format. OpenMDAO provides a set\n",
    "of [File Wrapping](../../../other_useful_docs/file_wrap.ipynb) tools to help with this.\n",
    "\n",
    "```{Note}\n",
    "To make it easy for you to run our example external code in any operating system or environment,\n",
    "we built it as a Python script that evaluates the paraboloid\n",
    "equation. We'll just call this script like any other executable, even though it is a Python script,\n",
    "and could be turned directly an OpenMDAO `Component`. Just keep in mind that any external code will\n",
    "work here, not just python scripts!\n",
    "```\n",
    "Here is the script for this external code. It simply reads its inputs, `x` and `y`, from an external file,\n",
    "does the same computation as the [Paraboloid Tutorial](../../../basic_user_guide/single_disciplinary_optimization/first_analysis) and writes the output, `f_xy`, to an output file."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "457c0fe7",
   "metadata": {
    "papermill": {
     "duration": 0.001363,
     "end_time": "2026-10-02T14:41:22.951802+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:22.950439+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "```python\n",
    "#!/usr/bin/env python\n",
    "#\n",
    "# usage: extcode_paraboloid.py input_filename output_filename\n",
    "#\n",
    "# Evaluates the equation f(x,y) = (x-3)^2 + xy + (y+4)^2 - 3.\n",
    "#\n",
    "# Read the values of `x` and `y` from input file\n",
    "# and write the value of `f_xy` to output file.\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    import sys\n",
    "\n",
    "    input_filename = sys.argv[1]\n",
    "    output_filename = sys.argv[2]\n",
    "\n",
    "    with open(input_filename, 'r') as input_file:\n",
    "        file_contents = input_file.readlines()\n",
    "\n",
    "    x, y = [float(f) for f in file_contents]\n",
    "\n",
    "    f_xy = (x-3.0)**2 + x*y + (y+4.0)**2 - 3.0\n",
    "\n",
    "    with open(output_filename, 'w') as output_file:\n",
    "        output_file.write('%.16f\\n' % f_xy)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "61bbfac2",
   "metadata": {
    "papermill": {
     "duration": 0.001243,
     "end_time": "2026-10-02T14:41:22.954450+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:22.953207+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "The following example demonstrates how to build an OpenMDAO component that makes use of this external code.\n",
    "\n",
    "```{Note}\n",
    "If you pass a string as a command, OpenMDAO sets `shell=True` which can add [overhead](https://docs.python.org/3/library/subprocess.html#frequently-used-arguments) leading to\n",
    "a decrease in performance and a security [loophole](https://docs.python.org/3/library/subprocess.html#security-considerations). Use list when possible.\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "9526c681",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:23.004861Z",
     "iopub.status.busy": "2026-10-02T14:41:23.004486Z",
     "iopub.status.idle": "2026-10-02T14:41:23.008846Z",
     "shell.execute_reply": "2026-10-02T14:41:23.008393Z"
    },
    "papermill": {
     "duration": 0.007299,
     "end_time": "2026-10-02T14:41:23.009234+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:23.001935+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "import openmdao.api as om\n",
    "\n",
    "\n",
    "class ParaboloidExternalCodeComp(om.ExternalCodeComp):\n",
    "    def setup(self):\n",
    "        self.add_input('x', val=0.0)\n",
    "        self.add_input('y', val=0.0)\n",
    "\n",
    "        self.add_output('f_xy', val=0.0)\n",
    "\n",
    "        self.input_file = 'paraboloid_input.dat'\n",
    "        self.output_file = 'paraboloid_output.dat'\n",
    "\n",
    "        # providing these is optional; the component will verify that any input\n",
    "        # files exist before execution and that the output files exist after.\n",
    "        self.options['external_input_files'] = [self.input_file]\n",
    "        self.options['external_output_files'] = [self.output_file]\n",
    "\n",
    "        # If you want to write your command as a list, the code below will also work.\n",
    "        # self.options['command'] = [\n",
    "        #     sys.executable, 'extcode_paraboloid.py', self.input_file, self.output_file\n",
    "        # ]\n",
    "\n",
    "        self.options['command'] = ('python extcode_paraboloid.py {} {}').format(self.input_file, self.output_file)\n",
    "\n",
    "    def compute(self, inputs, outputs):\n",
    "        x = inputs['x']\n",
    "        y = inputs['y']\n",
    "\n",
    "        # generate the input file for the paraboloid external code\n",
    "        with open(self.input_file, 'w') as input_file:\n",
    "            input_file.write('%.16f\\n%.16f\\n' % (x.item(), y.item()))\n",
    "\n",
    "        # the parent compute function actually runs the external code\n",
    "        super().compute(inputs, outputs)\n",
    "\n",
    "        # parse the output file from the external code and set the value of f_xy\n",
    "        with open(self.output_file, 'r') as output_file:\n",
    "            f_xy = float(output_file.read())\n",
    "\n",
    "        outputs['f_xy'] = f_xy"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "86d809b1",
   "metadata": {
    "papermill": {
     "duration": 0.001613,
     "end_time": "2026-10-02T14:41:23.012592+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:23.010979+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "We will go through each section and explain how this code works.\n",
    "\n",
    "OpenMDAO provides a base class, `ExternalCodeComp`, which you should inherit from to\n",
    "build your wrapper components. Just like any other component, you will define the\n",
    "necessary inputs and outputs in the `setup` method.\n",
    "If you want the component to check to make sure any files exist before/after you run,\n",
    "then you can set the `external_input_files` and `external_output_files`, respectively.\n",
    "You'll also define the command that should be called by the external code."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "ffaca440",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:23.016750Z",
     "iopub.status.busy": "2026-10-02T14:41:23.016573Z",
     "iopub.status.idle": "2026-10-02T14:41:23.019764Z",
     "shell.execute_reply": "2026-10-02T14:41:23.019165Z"
    },
    "papermill": {
     "duration": 0.005883,
     "end_time": "2026-10-02T14:41:23.020174+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:23.014291+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "def setup(self):\n",
    "    self.add_input('x', val=0.0)\n",
    "    self.add_input('y', val=0.0)\n",
    "\n",
    "    self.add_output('f_xy', val=0.0)\n",
    "\n",
    "    self.input_file = 'paraboloid_input.dat'\n",
    "    self.output_file = 'paraboloid_output.dat'\n",
    "\n",
    "    # providing these is optional; the component will verify that any input\n",
    "    # files exist before execution and that the output files exist after.\n",
    "    self.options['external_input_files'] = [self.input_file]\n",
    "    self.options['external_output_files'] = [self.output_file]\n",
    "\n",
    "    # If you want to write your command as a list, the code below will also work.\n",
    "    # self.options['command'] = [\n",
    "    #     sys.executable, 'extcode_paraboloid.py', self.input_file, self.output_file\n",
    "    # ]\n",
    "\n",
    "    self.options['command'] = ('python extcode_paraboloid.py {} {}').format(self.input_file, self.output_file)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6f3250b0",
   "metadata": {
    "papermill": {
     "duration": 0.001658,
     "end_time": "2026-10-02T14:41:23.023592+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:23.021934+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "The `compute` method is responsible for calculating outputs for a given set of inputs. When running an external code, this means you have to take the parameter values and push them down into files, run your code, then pull the output values back up. So there is some Python code needed to do all that file writing, reading, and parsing."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "71f9b601",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:23.027740Z",
     "iopub.status.busy": "2026-10-02T14:41:23.027572Z",
     "iopub.status.idle": "2026-10-02T14:41:23.030474Z",
     "shell.execute_reply": "2026-10-02T14:41:23.030029Z"
    },
    "papermill": {
     "duration": 0.005482,
     "end_time": "2026-10-02T14:41:23.030816+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:23.025334+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "def compute(self, inputs, outputs):\n",
    "    x = inputs['x']\n",
    "    y = inputs['y']\n",
    "\n",
    "    # generate the input file for the paraboloid external code\n",
    "    with open(self.input_file, 'w') as input_file:\n",
    "        input_file.write('%.16f\\n%.16f\\n' % (x.item(), y.item()))\n",
    "\n",
    "    # the parent compute function actually runs the external code\n",
    "    super().compute(inputs, outputs)\n",
    "\n",
    "    # parse the output file from the external code and set the value of f_xy\n",
    "    with open(self.output_file, 'r') as output_file:\n",
    "        f_xy = float(output_file.read())\n",
    "\n",
    "    outputs['f_xy'] = f_xy"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b63d4d33",
   "metadata": {
    "papermill": {
     "duration": 0.001335,
     "end_time": "2026-10-02T14:41:23.033974+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:23.032639+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "`ParaboloidExternalCodeComp` is now complete. All that is left is to actually use it in a model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "d4842cda",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:23.037997Z",
     "iopub.status.busy": "2026-10-02T14:41:23.037844Z",
     "iopub.status.idle": "2026-10-02T14:41:24.351498Z",
     "shell.execute_reply": "2026-10-02T14:41:24.350749Z"
    },
    "papermill": {
     "duration": 1.316706,
     "end_time": "2026-10-02T14:41:24.352208+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:23.035502+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1790952084.226841] [runnervm8df0l:6490 :0]        ib_iface.c:1269 UCX  ERROR mana_0: iface 0x560aed8d2480 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",
      "[1790952084.227092] [runnervm8df0l:6490 :0]      ucp_worker.c:1412 UCX  ERROR uct_iface_open(ud_verbs/mana_0:1) failed: Input/output error\n",
      "[-15.]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[runnervm8df0l:06490] pml_ucx.c:313  Error: Failed to create UCP worker\n"
     ]
    }
   ],
   "source": [
    "import sys\n",
    "\n",
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "model.add_subsystem('p', ParaboloidExternalCodeComp(), promotes_inputs=['x', 'y'])\n",
    "\n",
    "# run the ExternalCodeComp Component\n",
    "prob.setup()\n",
    "\n",
    "# Set input values\n",
    "prob.set_val('p.x', 3.0)\n",
    "prob.set_val('p.y', -4.0)\n",
    "\n",
    "prob.run_model()\n",
    "\n",
    "# print the output\n",
    "print(prob.get_val('p.f_xy'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "b5b20200",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:24.356723Z",
     "iopub.status.busy": "2026-10-02T14:41:24.356520Z",
     "iopub.status.idle": "2026-10-02T14:41:24.359210Z",
     "shell.execute_reply": "2026-10-02T14:41:24.358679Z"
    },
    "papermill": {
     "duration": 0.005806,
     "end_time": "2026-10-02T14:41:24.359970+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:24.354164+00:00",
     "status": "completed"
    },
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert(prob.get_val('p.f_xy') == -15.0)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "53469841",
   "metadata": {
    "papermill": {
     "duration": 0.001636,
     "end_time": "2026-10-02T14:41:24.363409+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:24.361773+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "## Using ExternalCodeComp in an Optimization\n",
    "\n",
    "If you are going to use an ExternalCodeComp component in a gradient based optimization, you'll need to\n",
    "get its [partial derivatives](../../../advanced_user_guide/analytic_derivatives/partial_derivs_explicit) somehow.\n",
    "One way would be just to use [finite-difference approximations](../../core_features/working_with_derivatives/approximating_partial_derivatives) for the partials.\n",
    "\n",
    "In the following example, the `ParaboloidExternalCodeComp` component has been modified to specify\n",
    "that partial derivatives are approximiated via finite difference."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "101e97b3",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:24.367189Z",
     "iopub.status.busy": "2026-10-02T14:41:24.367039Z",
     "iopub.status.idle": "2026-10-02T14:41:24.370628Z",
     "shell.execute_reply": "2026-10-02T14:41:24.370121Z"
    },
    "papermill": {
     "duration": 0.006142,
     "end_time": "2026-10-02T14:41:24.371035+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:24.364893+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "class ParaboloidExternalCodeCompFD(om.ExternalCodeComp):\n",
    "    def setup(self):\n",
    "        self.add_input('x', val=0.0)\n",
    "        self.add_input('y', val=0.0)\n",
    "\n",
    "        self.add_output('f_xy', val=0.0)\n",
    "\n",
    "        self.input_file = 'paraboloid_input.dat'\n",
    "        self.output_file = 'paraboloid_output.dat'\n",
    "\n",
    "        # providing these is optional; the component will verify that any input\n",
    "        # files exist before execution and that the output files exist after.\n",
    "        self.options['external_input_files'] = [self.input_file]\n",
    "        self.options['external_output_files'] = [self.output_file]\n",
    "\n",
    "        self.options['command'] = [\n",
    "            sys.executable, 'extcode_paraboloid.py', self.input_file, self.output_file\n",
    "        ]\n",
    "\n",
    "    def setup_partials(self):\n",
    "        # this external code does not provide derivatives, use finite difference\n",
    "        self.declare_partials(of='*', wrt='*', method='fd')\n",
    "\n",
    "    def compute(self, inputs, outputs):\n",
    "        x = inputs['x']\n",
    "        y = inputs['y']\n",
    "\n",
    "        # generate the input file for the paraboloid external code\n",
    "        with open(self.input_file, 'w') as input_file:\n",
    "            input_file.write('%.16f\\n%.16f\\n' % (x.item(), y.item()))\n",
    "\n",
    "        # the parent compute function actually runs the external code\n",
    "        super().compute(inputs, outputs)\n",
    "\n",
    "        # parse the output file from the external code and set the value of f_xy\n",
    "        with open(self.output_file, 'r') as output_file:\n",
    "            f_xy = float(output_file.read())\n",
    "\n",
    "        outputs['f_xy'] = f_xy"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "81628313",
   "metadata": {
    "papermill": {
     "duration": 0.001381,
     "end_time": "2026-10-02T14:41:24.373945+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:24.372564+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "Now we can perform an optimization using the external code, as shown here:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "657ef1ab",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:24.377518Z",
     "iopub.status.busy": "2026-10-02T14:41:24.377373Z",
     "iopub.status.idle": "2026-10-02T14:41:26.436841Z",
     "shell.execute_reply": "2026-10-02T14:41:26.436033Z"
    },
    "papermill": {
     "duration": 2.062005,
     "end_time": "2026-10-02T14:41:26.437438+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:24.375433+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Optimization terminated successfully    (Exit mode 0)\n",
      "            Current function value: -27.333333333333\n",
      "            Iterations: 5\n",
      "            Function evaluations: 6\n",
      "            Gradient evaluations: 5\n",
      "Optimization Complete\n",
      "-----------------------------------\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "Problem: problem2\n",
       "Driver:  ScipyOptimizeDriver\n",
       "  success     : True\n",
       "  iterations  : 7\n",
       "  runtime     : 2.0499E+00 s\n",
       "  model_evals : 7\n",
       "  model_time  : 8.1147E-01 s\n",
       "  deriv_evals : 5\n",
       "  deriv_time  : 1.2284E+00 s\n",
       "  exit_status : SUCCESS"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "model.add_subsystem('p', ParaboloidExternalCodeCompFD())\n",
    "\n",
    "# find optimal solution with SciPy optimize\n",
    "# solution (minimum): x = 6.6667; y = -7.3333\n",
    "prob.driver = om.ScipyOptimizeDriver()\n",
    "prob.driver.options['optimizer'] = 'SLSQP'\n",
    "\n",
    "prob.model.add_design_var('p.x', lower=-50, upper=50)\n",
    "prob.model.add_design_var('p.y', lower=-50, upper=50)\n",
    "\n",
    "prob.model.add_objective('p.f_xy')\n",
    "\n",
    "prob.driver.options['tol'] = 1e-9\n",
    "prob.driver.options['disp'] = True\n",
    "\n",
    "prob.setup()\n",
    "\n",
    "# Set input values\n",
    "prob.set_val('p.x', 3.0)\n",
    "prob.set_val('p.y', -4.0)\n",
    "\n",
    "prob.run_driver()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "58d3faf2",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:26.441984Z",
     "iopub.status.busy": "2026-10-02T14:41:26.441525Z",
     "iopub.status.idle": "2026-10-02T14:41:26.444992Z",
     "shell.execute_reply": "2026-10-02T14:41:26.444405Z"
    },
    "papermill": {
     "duration": 0.006425,
     "end_time": "2026-10-02T14:41:26.445602+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:26.439177+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[6.66666633]\n",
      "[-7.33333367]\n"
     ]
    }
   ],
   "source": [
    "print(prob.get_val('p.x'))\n",
    "print(prob.get_val('p.y'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "c5343091",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:26.451656Z",
     "iopub.status.busy": "2026-10-02T14:41:26.451521Z",
     "iopub.status.idle": "2026-10-02T14:41:26.455370Z",
     "shell.execute_reply": "2026-10-02T14:41:26.454716Z"
    },
    "papermill": {
     "duration": 0.006413,
     "end_time": "2026-10-02T14:41:26.455805+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:26.449392+00:00",
     "status": "completed"
    },
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "np.float64(5.00642948768913e-08)"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "\n",
    "assert_near_equal(prob.get_val('p.x'), 6.66666667, 1e-6)\n",
    "assert_near_equal(prob.get_val('p.y'), -7.3333333, 1e-6)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "15be866c",
   "metadata": {
    "papermill": {
     "duration": 0.001484,
     "end_time": "2026-10-02T14:41:26.458886+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:26.457402+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "Alternatively, if the code you are wrapping happens to provide analytic derivatives you could\n",
    "have those written out to a file and then parse that file in the\n",
    "[compute_partials](../../core_features/working_with_components/explicit_component) method.\n",
    "\n",
    "Here is a version of our external script that writes its derivatives to a second output file:"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "323cfecb",
   "metadata": {
    "papermill": {
     "duration": 0.00143,
     "end_time": "2026-10-02T14:41:26.461841+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:26.460411+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "```python\n",
    "#!/usr/bin/env python\n",
    "#\n",
    "# usage: extcode_paraboloid_derivs.py input_filename output_filename derivs_filename\n",
    "#\n",
    "# Evaluates the equation f(x,y) = (x-3)^2 + xy + (y+4)^2 - 3.\n",
    "#\n",
    "# Read the values of `x` and `y` from input file\n",
    "# and write the value of `f_xy` to output file.\n",
    "#\n",
    "# Also write derivatives to another output file.\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    import sys\n",
    "\n",
    "    input_filename = sys.argv[1]\n",
    "    output_filename = sys.argv[2]\n",
    "    derivs_filename = sys.argv[3]\n",
    "\n",
    "    with open(input_filename, 'r') as input_file:\n",
    "        file_contents = input_file.readlines()\n",
    "\n",
    "    x, y = [float(f) for f in file_contents]\n",
    "\n",
    "    f_xy = (x-3.0)**2 + x*y + (y+4.0)**2 - 3.0\n",
    "\n",
    "    with open(output_filename, 'w') as output_file:\n",
    "        output_file.write('%.16f\\n' % f_xy)\n",
    "\n",
    "    with open(derivs_filename, 'w') as derivs_file:\n",
    "        # partials['f_xy', 'x']\n",
    "        derivs_file.write('%.16f\\n' % (2.0*x - 6.0 + y))\n",
    "        # partials['f_xy', 'y']\n",
    "        derivs_file.write('%.16f\\n' % (2.0*y + 8.0 + x))\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "165757b2",
   "metadata": {
    "papermill": {
     "duration": 0.001483,
     "end_time": "2026-10-02T14:41:26.464847+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:26.463364+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "And the corresponding `ParaboloidExternalCodeCompDerivs` component:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "78b99eaf",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:26.468633Z",
     "iopub.status.busy": "2026-10-02T14:41:26.468463Z",
     "iopub.status.idle": "2026-10-02T14:41:26.473665Z",
     "shell.execute_reply": "2026-10-02T14:41:26.473068Z"
    },
    "papermill": {
     "duration": 0.007853,
     "end_time": "2026-10-02T14:41:26.474201+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:26.466348+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "class ParaboloidExternalCodeCompDerivs(om.ExternalCodeComp):\n",
    "    def setup(self):\n",
    "        self.add_input('x', val=0.0)\n",
    "        self.add_input('y', val=0.0)\n",
    "\n",
    "        self.add_output('f_xy', val=0.0)\n",
    "\n",
    "        self.input_file = 'paraboloid_input.dat'\n",
    "        self.output_file = 'paraboloid_output.dat'\n",
    "        self.derivs_file = 'paraboloid_derivs.dat'\n",
    "\n",
    "        # providing these is optional; the component will verify that any input\n",
    "        # files exist before execution and that the output files exist after.\n",
    "        self.options['external_input_files'] = [self.input_file]\n",
    "        self.options['external_output_files'] = [self.output_file, self.derivs_file]\n",
    "\n",
    "        self.options['command'] = [\n",
    "            sys.executable, 'extcode_paraboloid_derivs.py',\n",
    "            self.input_file, self.output_file, self.derivs_file\n",
    "        ]\n",
    "\n",
    "    def setup_partials(self):\n",
    "        # this external code does provide derivatives\n",
    "        self.declare_partials(of='*', wrt='*')\n",
    "\n",
    "    def compute(self, inputs, outputs):\n",
    "        x = inputs['x']\n",
    "        y = inputs['y']\n",
    "\n",
    "        # generate the input file for the paraboloid external code\n",
    "        with open(self.input_file, 'w') as input_file:\n",
    "            input_file.write('%.16f\\n%.16f\\n' % (x.item(), y.item()))\n",
    "\n",
    "        # the parent compute function actually runs the external code\n",
    "        super().compute(inputs, outputs)\n",
    "\n",
    "        # parse the output file from the external code and set the value of f_xy\n",
    "        with open(self.output_file, 'r') as output_file:\n",
    "            f_xy = float(output_file.read())\n",
    "\n",
    "        outputs['f_xy'] = f_xy\n",
    "\n",
    "    def compute_partials(self, inputs, partials):\n",
    "        outputs = {}\n",
    "\n",
    "        # the parent compute function actually runs the external code\n",
    "        super().compute(inputs, outputs)\n",
    "\n",
    "        # parse the derivs file from the external code and set partials\n",
    "        with open(self.derivs_file, 'r') as derivs_file:\n",
    "            partials['f_xy', 'x'] = float(derivs_file.readline())\n",
    "            partials['f_xy', 'y'] = float(derivs_file.readline())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c5220cf9",
   "metadata": {
    "papermill": {
     "duration": 0.001489,
     "end_time": "2026-10-02T14:41:26.477266+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:26.475777+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "Again, we can perform an optimization using the external code with derivatives:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "85ef27b2",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:26.481137Z",
     "iopub.status.busy": "2026-10-02T14:41:26.481005Z",
     "iopub.status.idle": "2026-10-02T14:41:27.860864Z",
     "shell.execute_reply": "2026-10-02T14:41:27.860054Z"
    },
    "papermill": {
     "duration": 1.382703,
     "end_time": "2026-10-02T14:41:27.861542+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:26.478839+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Optimization terminated successfully    (Exit mode 0)\n",
      "            Current function value: -27.333333333333336\n",
      "            Iterations: 5\n",
      "            Function evaluations: 6\n",
      "            Gradient evaluations: 5\n",
      "Optimization Complete\n",
      "-----------------------------------\n"
     ]
    }
   ],
   "source": [
    "prob = om.Problem()\n",
    "model = prob.model\n",
    "\n",
    "model.add_subsystem('p', ParaboloidExternalCodeCompDerivs())\n",
    "\n",
    "# find optimal solution with SciPy optimize\n",
    "# solution (minimum): x = 6.6667; y = -7.3333\n",
    "prob.driver = om.ScipyOptimizeDriver()\n",
    "prob.driver.options['optimizer'] = 'SLSQP'\n",
    "\n",
    "prob.model.add_design_var('p.x', lower=-50, upper=50)\n",
    "prob.model.add_design_var('p.y', lower=-50, upper=50)\n",
    "\n",
    "prob.model.add_objective('p.f_xy')\n",
    "\n",
    "prob.driver.options['tol'] = 1e-9\n",
    "prob.driver.options['disp'] = True\n",
    "\n",
    "prob.setup()\n",
    "\n",
    "# Set input values\n",
    "prob.set_val('p.x', 3.0)\n",
    "prob.set_val('p.y', -4.0)\n",
    "\n",
    "prob.run_driver();"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "02d2eea5",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:27.867703Z",
     "iopub.status.busy": "2026-10-02T14:41:27.867532Z",
     "iopub.status.idle": "2026-10-02T14:41:27.870816Z",
     "shell.execute_reply": "2026-10-02T14:41:27.870164Z"
    },
    "papermill": {
     "duration": 0.006871,
     "end_time": "2026-10-02T14:41:27.871267+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:27.864396+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[6.66666667]\n",
      "[-7.33333333]\n"
     ]
    }
   ],
   "source": [
    "print(prob.get_val('p.x'))\n",
    "print(prob.get_val('p.y'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "b670f208",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:27.875725Z",
     "iopub.status.busy": "2026-10-02T14:41:27.875549Z",
     "iopub.status.idle": "2026-10-02T14:41:27.879347Z",
     "shell.execute_reply": "2026-10-02T14:41:27.878833Z"
    },
    "papermill": {
     "duration": 0.00688,
     "end_time": "2026-10-02T14:41:27.879816+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:27.872936+00:00",
     "status": "completed"
    },
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "np.float64(4.545454578862581e-09)"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "assert_near_equal(prob.get_val('p.x'), 6.66666667, 1e-6)\n",
    "assert_near_equal(prob.get_val('p.y'), -7.3333333, 1e-6)"
   ]
  }
 ],
 "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": 7.705674,
   "end_time": "2026-10-02T14:41:28.524489+00:00",
   "environment_variables": {},
   "exception": null,
   "input_path": "/home/runner/work/OpenMDAO/OpenMDAO/openmdao/docs/openmdao_book/features/building_blocks/components/external_code_comp.ipynb",
   "output_path": "/home/runner/work/OpenMDAO/OpenMDAO/openmdao/docs/_executed_book/features/building_blocks/components/external_code_comp.ipynb",
   "parameters": {},
   "start_time": "2026-10-02T14:41:20.818815+00:00",
   "version": "2.7.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}