{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "9f876127",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:21.601706Z",
     "iopub.status.busy": "2026-10-02T14:41:21.601516Z",
     "iopub.status.idle": "2026-10-02T14:41:21.605843Z",
     "shell.execute_reply": "2026-10-02T14:41:21.605109Z"
    },
    "papermill": {
     "duration": 0.006935,
     "end_time": "2026-10-02T14:41:21.606339+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:21.599404+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": "202048ea",
   "metadata": {
    "papermill": {
     "duration": 0.000873,
     "end_time": "2026-10-02T14:41:21.608257+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:21.607384+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "# ExternalCodeImplicitComp\n",
    "\n",
    "`ExternalCodeImplicitComp` is very similar to `ExternalCodeComp` in that it runs an external program in a subprocess on your operating system. But it treats the `Component` as an `ImplicitComponent` rather than an `ExplicitComponent`. See [ExternalCodeComp](external_code_comp.ipynb) for basic information about how `ExternalCodeComp` works.\n",
    "\n",
    "`ExternalCodeImplicitComp` has most of the same options as `ExternalCodeComp`, but there is one major difference."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "6a9de7a5",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:21.610832Z",
     "iopub.status.busy": "2026-10-02T14:41:21.610664Z",
     "iopub.status.idle": "2026-10-02T14:41:22.763499Z",
     "shell.execute_reply": "2026-10-02T14:41:22.762892Z"
    },
    "papermill": {
     "duration": 1.154949,
     "end_time": "2026-10-02T14:41:22.764140+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:21.609191+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;\">assembled_jac_type</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;csc&#x27;, &#x27;csr&#x27;, &#x27;dense&#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;\">Linear solver(s) in this group or implicit component, if using an assembled jacobian, will use this type.</td></tr>\n",
       "       <tr style=\"background-color: #F3F3F3;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">command_apply</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;\">command to be executed for apply_nonlinear</td></tr>\n",
       "       <tr style=\"background-color: ghostwhite;\"><td style=\"border: 1px solid #999; border-collapse: collapse; padding: 5px; text-align: left;\">command_solve</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;\">command to be executed for solve_nonlinear</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.ExternalCodeImplicitComp\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1069cded",
   "metadata": {
    "papermill": {
     "duration": 0.000935,
     "end_time": "2026-10-02T14:41:22.766207+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:22.765272+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "source": [
    "When using an `ExternalCodeImplicitComp`, you have the option to define two external programs rather than one. The\n",
    "first of these is \"command_apply\", which is the command that you want to run to evaluate the residuals. You should\n",
    "always specify a value for this option. The second is \"command_solve\", which is the command that you want to run\n",
    "to let the external program solve its own states. This is optional, but you should specify it if your code can\n",
    "solve itself, and if you want it to do so (for example, while using a Newton solver with \"solve_subsystems\" turned\n",
    "on in a higher-level `Group`.)\n",
    "\n",
    "## ExternalCodeImplicitComp Constructor\n",
    "\n",
    "The call signature for the `ExternalCodeImplicitComp` constructor is:\n",
    "\n",
    "```{eval-rst}\n",
    "    .. automethod:: openmdao.components.external_code_comp.ExternalCodeImplicitComp.__init__\n",
    "        :noindex:\n",
    "```\n",
    "\n",
    "## ExternalCodeImplicitComp Example\n",
    "\n",
    "Here is a simple example of the use of an `ExternalCodeImplicitComp` Component. The external code in the example\n",
    "is a Python script that evaluates the output and residual for the implicit relationship between the area ratio and\n",
    "mach number in an isentropic flow. We use the same external code for both \"command_apply\" and \"command_solve\", but\n",
    "in each case we pass it different flags.\n",
    "\n",
    "```python\n",
    "#!/usr/bin/env python\n",
    "#\n",
    "# usage: extcode_mach.py input_filename output_filename\n",
    "#\n",
    "# Evaluates the output and residual for the implicit relationship\n",
    "#     between the area ratio and mach number.\n",
    "#\n",
    "# Read the value of `area_ratio` from input file\n",
    "# and writes the values or residuals of `mach` to output file depending on what is requested.\n",
    "# What is requested is given by the first line in the file read. It can be either 'residuals' or\n",
    "# 'outputs'.\n",
    "\n",
    "def area_ratio_explicit(mach):\n",
    "    \"\"\"Explicit isentropic relationship between area ratio and Mach number\"\"\"\n",
    "    gamma = 1.4\n",
    "    gamma_p_1 = gamma + 1\n",
    "    gamma_m_1 = gamma - 1\n",
    "    exponent = gamma_p_1 / (2 * gamma_m_1)\n",
    "    return (gamma_p_1 / 2.) ** -exponent * (\n",
    "            (1 + gamma_m_1 / 2. * mach ** 2) ** exponent) / mach\n",
    "\n",
    "def mach_residual(mach, area_ratio_target):\n",
    "    \"\"\"If area_ratio is known, then finding Mach is an implicit relationship\"\"\"\n",
    "    return area_ratio_target - area_ratio_explicit(mach)\n",
    "\n",
    "def mach_solve(area_ratio, super_sonic=False):\n",
    "    \"\"\"Solve for mach, given area ratio\"\"\"\n",
    "    if super_sonic:\n",
    "        initial_guess = 4\n",
    "    else:\n",
    "        initial_guess = .1\n",
    "    mach = fsolve(func=mach_residual, x0=initial_guess, args=(area_ratio,))[0]\n",
    "    return mach\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    import sys\n",
    "    from scipy.optimize import fsolve\n",
    "\n",
    "    input_filename = sys.argv[1]\n",
    "    output_filename = sys.argv[2]\n",
    "\n",
    "    with open(input_filename, 'r') as input_file:\n",
    "        output_or_resids = input_file.readline().strip()\n",
    "        area_ratio = float(input_file.readline())\n",
    "        if output_or_resids == 'residuals':\n",
    "            mach = float(input_file.readline())\n",
    "        else: # outputs\n",
    "            super_sonic = (input_file.readline().strip() == \"True\")\n",
    "\n",
    "    if output_or_resids == 'outputs':\n",
    "        mach_output = mach_solve(area_ratio, super_sonic=super_sonic)\n",
    "        with open(output_filename, 'w') as output_file:\n",
    "            output_file.write('%.16f\\n' % mach_output)\n",
    "\n",
    "    elif output_or_resids == 'residuals':\n",
    "        mach_resid = mach_residual(mach, area_ratio)\n",
    "        with open(output_filename, 'w') as output_file:\n",
    "            output_file.write('%.16f\\n' % mach_resid)\n",
    "\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "73057d8d",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:22.768861Z",
     "iopub.status.busy": "2026-10-02T14:41:22.768568Z",
     "iopub.status.idle": "2026-10-02T14:41:22.771564Z",
     "shell.execute_reply": "2026-10-02T14:41:22.771098Z"
    },
    "papermill": {
     "duration": 0.004974,
     "end_time": "2026-10-02T14:41:22.772098+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:22.767124+00:00",
     "status": "completed"
    },
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "from scipy.optimize import fsolve\n",
    "\n",
    "def area_ratio_explicit(Mach):\n",
    "    \"\"\"isentropic relationship between area ratio and Mach number\"\"\"\n",
    "    gamma = 1.4\n",
    "    gamma_p_1 = gamma + 1\n",
    "    gamma_m_1 = gamma - 1\n",
    "    exponent = gamma_p_1 / (2 * gamma_m_1)\n",
    "\n",
    "    return (gamma_p_1 / 2.) ** -exponent * ((1 + gamma_m_1 / 2. * Mach ** 2) ** exponent) / Mach\n",
    "\n",
    "def mach_residual(Mach, area_ratio_target):\n",
    "    return area_ratio_target - area_ratio_explicit(Mach)\n",
    "\n",
    "def mach_solve(area_ratio, super_sonic=False):\n",
    "    if super_sonic:\n",
    "        initial_guess = 4\n",
    "    else:\n",
    "        initial_guess = .1\n",
    "\n",
    "    mach = fsolve(func=mach_residual, x0=initial_guess, args=(area_ratio,))[0]\n",
    "\n",
    "    return mach"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "ac0e02ad",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:22.829431Z",
     "iopub.status.busy": "2026-10-02T14:41:22.829247Z",
     "iopub.status.idle": "2026-10-02T14:41:24.897146Z",
     "shell.execute_reply": "2026-10-02T14:41:24.896335Z"
    },
    "papermill": {
     "duration": 2.070543,
     "end_time": "2026-10-02T14:41:24.897733+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:22.827190+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1790952084.058866] [runnervm8df0l:6496 :0]        ib_iface.c:1269 UCX  ERROR mana_0: iface 0x5608b2672a80 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.059115] [runnervm8df0l:6496 :0]      ucp_worker.c:1412 UCX  ERROR uct_iface_open(ud_verbs/mana_0:1) failed: Input/output error\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[runnervm8df0l:06496] pml_ucx.c:313  Error: Failed to create UCP worker\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.52196203]\n"
     ]
    }
   ],
   "source": [
    "import sys\n",
    "import openmdao.api as om\n",
    "\n",
    "\n",
    "class MachExternalCodeComp(om.ExternalCodeImplicitComp):\n",
    "\n",
    "    def initialize(self):\n",
    "        self.options.declare('super_sonic', types=bool)\n",
    "\n",
    "    def setup(self):\n",
    "        self.add_input('area_ratio', val=1.0, units=None)\n",
    "        self.add_output('mach', val=1., units=None)\n",
    "\n",
    "        self.input_file = 'mach_input.dat'\n",
    "        self.output_file = 'mach_output.dat'\n",
    "\n",
    "        # providing these are 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",
    "\n",
    "        self.options['command_apply'] = [\n",
    "            sys.executable, 'extcode_mach.py', self.input_file, self.output_file,\n",
    "        ]\n",
    "        self.options['command_solve'] = [\n",
    "            sys.executable, 'extcode_mach.py', self.input_file, self.output_file,\n",
    "        ]\n",
    "\n",
    "        # If you want to write your own string command, the code below will also work.\n",
    "        # self.options['command_apply'] = ('python extcode_mach.py {} {}').format(self.input_file, self.output_file)\n",
    "\n",
    "    def setup_partials(self):\n",
    "        self.declare_partials(of='mach', wrt='area_ratio', method='fd')\n",
    "\n",
    "    def apply_nonlinear(self, inputs, outputs, residuals):\n",
    "        with open(self.input_file, 'w') as input_file:\n",
    "            input_file.write('residuals\\n')\n",
    "            input_file.write('{}\\n'.format(inputs['area_ratio'][0]))\n",
    "            input_file.write('{}\\n'.format(outputs['mach'][0]))\n",
    "\n",
    "        # the parent apply_nonlinear function actually runs the external code\n",
    "        super().apply_nonlinear(inputs, outputs, residuals)\n",
    "\n",
    "        # parse the output file from the external code and set the value of mach\n",
    "        with open(self.output_file, 'r') as output_file:\n",
    "            mach = float(output_file.read())\n",
    "        residuals['mach'] = mach\n",
    "\n",
    "    def solve_nonlinear(self, inputs, outputs):\n",
    "        with open(self.input_file, 'w') as input_file:\n",
    "            input_file.write('outputs\\n')\n",
    "            input_file.write('{}\\n'.format(inputs['area_ratio'][0]))\n",
    "            input_file.write('{}\\n'.format(self.options['super_sonic']))\n",
    "        # the parent apply_nonlinear function actually runs the external code\n",
    "        super().solve_nonlinear(inputs, outputs)\n",
    "\n",
    "        # parse the output file from the external code and set the value of mach\n",
    "        with open(self.output_file, 'r') as output_file:\n",
    "            mach = float(output_file.read())\n",
    "        outputs['mach'] = mach\n",
    "\n",
    "group = om.Group()\n",
    "mach_comp = group.add_subsystem('comp', MachExternalCodeComp(), promotes=['*'])\n",
    "prob = om.Problem(model=group)\n",
    "group.nonlinear_solver = om.NewtonSolver()\n",
    "group.nonlinear_solver.options['solve_subsystems'] = True\n",
    "group.nonlinear_solver.options['iprint'] = 0\n",
    "group.nonlinear_solver.options['maxiter'] = 20\n",
    "group.linear_solver = om.DirectSolver()\n",
    "\n",
    "prob.setup()\n",
    "\n",
    "area_ratio = 1.3\n",
    "super_sonic = False\n",
    "prob.set_val('area_ratio', area_ratio)\n",
    "mach_comp.options['super_sonic'] = super_sonic\n",
    "prob.run_model()\n",
    "print(prob.get_val('mach'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "5f48005a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:24.901328Z",
     "iopub.status.busy": "2026-10-02T14:41:24.901132Z",
     "iopub.status.idle": "2026-10-02T14:41:24.907388Z",
     "shell.execute_reply": "2026-10-02T14:41:24.906456Z"
    },
    "papermill": {
     "duration": 0.008792,
     "end_time": "2026-10-02T14:41:24.907902+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:24.899110+00:00",
     "status": "completed"
    },
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "np.float64(0.0)"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "\n",
    "assert_near_equal(prob.get_val('mach'), mach_solve(area_ratio, super_sonic=super_sonic), 1e-8)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "5bbe6167",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:24.912341Z",
     "iopub.status.busy": "2026-10-02T14:41:24.912126Z",
     "iopub.status.idle": "2026-10-02T14:41:25.748937Z",
     "shell.execute_reply": "2026-10-02T14:41:25.748176Z"
    },
    "papermill": {
     "duration": 0.83999,
     "end_time": "2026-10-02T14:41:25.749676+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:24.909686+00:00",
     "status": "completed"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1.65884779]\n"
     ]
    }
   ],
   "source": [
    "area_ratio = 1.3\n",
    "super_sonic = True\n",
    "prob.set_val('area_ratio', area_ratio)\n",
    "mach_comp.options['super_sonic'] = super_sonic\n",
    "prob.run_model()\n",
    "print(prob.get_val('mach'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "d6c0a487",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-10-02T14:41:25.753997Z",
     "iopub.status.busy": "2026-10-02T14:41:25.753756Z",
     "iopub.status.idle": "2026-10-02T14:41:25.758452Z",
     "shell.execute_reply": "2026-10-02T14:41:25.757652Z"
    },
    "papermill": {
     "duration": 0.0078,
     "end_time": "2026-10-02T14:41:25.758934+00:00",
     "exception": false,
     "start_time": "2026-10-02T14:41:25.751134+00:00",
     "status": "completed"
    },
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "np.float64(0.0)"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "assert_near_equal(prob.get_val('mach'), mach_solve(area_ratio, super_sonic=super_sonic), 1e-8)"
   ]
  }
 ],
 "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": 5.334199,
   "end_time": "2026-10-02T14:41:26.276826+00:00",
   "environment_variables": {},
   "exception": null,
   "input_path": "/home/runner/work/OpenMDAO/OpenMDAO/openmdao/docs/openmdao_book/features/building_blocks/components/external_code_implicit_comp.ipynb",
   "output_path": "/home/runner/work/OpenMDAO/OpenMDAO/openmdao/docs/_executed_book/features/building_blocks/components/external_code_implicit_comp.ipynb",
   "parameters": {},
   "start_time": "2026-10-02T14:41:20.942627+00:00",
   "version": "2.7.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}