{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "active-ipynb",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "try:\n",
    "    from openmdao.utils.notebook_utils import notebook_mode\n",
    "except ImportError:\n",
    "    !python -m pip install openmdao[notebooks]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# File Wrapping\n",
    "\n",
    "\n",
    "## Communicating with External Codes in OpenMDAO\n",
    "\n",
    "\n",
    "The [ExternalCodeComp](../features/building_blocks/components/external_code_comp.ipynb) example describes how to\n",
    "define a component that calls an external program to perform its computation,\n",
    "passing input and output values via files.\n",
    "\n",
    "The input and output files were very simple in that basic example, containing only\n",
    "the values of interest.  In the general case however, you will probably need to\n",
    "generate an input file (with a specific format of rows and fields), and you'll also need to parse a\n",
    "similarly-formatted output file to get the output values. To facilitate working\n",
    "with these more complex input and output files, OpenMDAO provides a couple of utility\n",
    "classes: `InputFileGenerator` and `FileParser`.\n",
    "\n",
    "\n",
    "## Generating the Input File\n",
    "\n",
    "You can generate an input file for an external application in a few different ways.\n",
    "One way is to write the file completely from scratch using the new values that are\n",
    "contained in the component's variables. Not much can be done to aid with this task, as\n",
    "it requires knowledge of the file format and can be completed using Python's standard\n",
    "formatted output.\n",
    "\n",
    "A second way to generate an input file is by templating. A *template* file is\n",
    "a sample input file which can be processed by a templating engine to insert\n",
    "new values in the appropriate locations. Often the template file is a valid\n",
    "input file before being processed, although other times it contains directives\n",
    "or conditional logic to guide the generation. Obviously this method works well\n",
    "for cases where only a small number of the possible variables and settings are\n",
    "being manipulated.\n",
    "\n",
    "OpenMDAO includes a basic templating capability that allows a template file to\n",
    "be read, fields to be replaced with new values, and an input file to be\n",
    "generated so that the external application can read it. Suppose you have an\n",
    "input file that contains some integer, floating point, and string inputs:\n",
    "\n",
    "```\n",
    "    INPUT\n",
    "    1 2 3\n",
    "    INPUT\n",
    "    10.1 20.2 30.3\n",
    "    A B C\n",
    "```\n",
    "\n",
    "This is a valid input file for your application, and it can also be used as a\n",
    "template file. The templating object is called `InputFileGenerator`, and it\n",
    "includes methods that can replace specific fields as measured by their row\n",
    "and field numbers.\n",
    "\n",
    "To use the InputFileGenerator, first instantiate it and give it the name of\n",
    "the template file and the name of the output file that you want to produce. (Note\n",
    "that this code must be placed in the ``compute`` method of your component *before*\n",
    "the external code is run.) The code will generally look like this:\n",
    "\n",
    "```\n",
    "from openmdao.utils.file_wrap import InputFileGenerator\n",
    "\n",
    "parser = InputFileGenerator()\n",
    "parser.set_template_file('mytemplate.txt')\n",
    "parser.set_generated_file('myinput.txt')\n",
    "\n",
    "# (Call functions to poke new values here)\n",
    "\n",
    "parser.generate()\n",
    "```\n",
    "\n",
    "\n",
    "When the template file is set, it is read into memory so that all subsequent\n",
    "replacements are done without writing the intermediate file to the disk. Once\n",
    "all replacements have been made, the `generate` method is called to create the\n",
    "input file.  If you have not provided the name of an output file, then the\n",
    "generated file data will be returned as a string.  We will use this feature in\n",
    "the following examples.\n",
    "\n",
    "\n",
    "Let's say you want to replace the second integer in the input file above\n",
    "with a 7. The code would look like this."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "output_data = {\n",
    "        \"\": [\n",
    "            \"INPUT\",\n",
    "            \"1 2 3\",\n",
    "            \"INPUT\",\n",
    "            \"10.1 20.2 30.3\",\n",
    "            \"A B C\"\n",
    "        ],\n",
    "        \"test_transfer\": [\n",
    "            \"INPUT\",\n",
    "            \"1 7 3\",\n",
    "            \"INPUT\",\n",
    "            \"10.1 20.2 30.3\",\n",
    "            \"A B C\"\n",
    "        ],\n",
    "        \"test_transfer_2\": [\n",
    "            \"INPUT\",\n",
    "            \"1 7 3\",\n",
    "            \"INPUT\",\n",
    "            \"10.1 20.2 3.141592653589793\",\n",
    "            \"A B C\"\n",
    "        ],\n",
    "        \"test_transfer_minus2\": [\n",
    "            \"INPUT\",\n",
    "            \"99999 7 3\",\n",
    "            \"INPUT\",\n",
    "            \"10.1 20.2 3.141592653589793\",\n",
    "            \"A B C\"\n",
    "        ],\n",
    "        \"test_transfer_array\": [\n",
    "            \"INPUT\",\n",
    "            \"123 456 789\",\n",
    "            \"INPUT\",\n",
    "            \"10.1 20.2 3.141592653589793\",\n",
    "            \"A B C\"\n",
    "        ],\n",
    "        \"test_transfer_stretch\": [\n",
    "            \"INPUT\",\n",
    "            \"11 22 33 44 55 66\",\n",
    "            \"INPUT\",\n",
    "            \"10.1 20.2 3.141592653589793\",\n",
    "            \"A B C\"\n",
    "        ]\n",
    "    }\n",
    "\n",
    "prev_test = {\n",
    "        \"test_transfer\": \"\",\n",
    "        \"test_transfer_2\": \"test_transfer\",\n",
    "        \"test_transfer_minus2\": \"test_transfer_2\",\n",
    "        \"test_transfer_array\": \"test_transfer_minus2\",\n",
    "        \"test_transfer_stretch\": \"test_transfer_array\"\n",
    "    }"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "from openmdao.utils.file_wrap import InputFileGenerator\n",
    "\n",
    "global parser  # global so we don't need `self.` in feature doc\n",
    "def parser_setup(test_name):\n",
    "    parser = InputFileGenerator()\n",
    "\n",
    "    # the input data for each test is the output of the previous test\n",
    "    parser._data = output_data[test_name][:]\n",
    "    return parser\n",
    "\n",
    "parser = parser_setup(\"test_transfer\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "parser.mark_anchor(\"INPUT\")\n",
    "parser.transfer_var(\n",
    "    7, 1, 2)\n",
    "print(parser.generate())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The method ``mark_anchor`` is used to define an anchor, which becomes the\n",
    "starting point for the ``transfer_var`` method. Here you find the first line\n",
    "down from the anchor, then the second field on that line and replace it with\n",
    "the new value.\n",
    "\n",
    "Now, if you want to replace the third value of the floating point numbers\n",
    "after the second ``INPUT`` statement. An additional argument can be passed to the\n",
    "``mark_anchor`` method to tell it to start at the second instance of the text\n",
    "fragment ``\"INPUT\"``."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "parser = parser_setup(\"test_transfer_2\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "parser.mark_anchor(\"INPUT\", 2)\n",
    "\n",
    "my_var = 3.1415926535897932\n",
    "parser.transfer_var(my_var, 1, 3)\n",
    "\n",
    "print(parser.generate())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that you are able to pass a floating point value to ``transfer_var`` and still\n",
    "keep 15 digits of precision. See [A-Note-on-Precision](#a-note-on-precision) for a discussion of why this is important.\n",
    "\n",
    "Note also that we used the method ``reset_anchor`` to return the anchor to the\n",
    "beginning of the file before marking our new anchor. Subsequent calls to\n",
    "``mark_anchor`` start at the previous anchor and find the next instance of the\n",
    "anchor text. It is a good practice to reset your anchor unless you are looking for\n",
    "an instance of \"B\" that follows an instance of \"A\".\n",
    "\n",
    "You can also count backwards from the bottom of the file by passing a negative\n",
    "number. Here, the second instance of ``\"INPUT\"`` from the bottom brings you\n",
    "back to the first one."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "parser = parser_setup(\"test_transfer_minus2\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "parser.reset_anchor()\n",
    "parser.mark_anchor(\"INPUT\", -2)\n",
    "parser.transfer_var(\"99999\", 1, 1)\n",
    "\n",
    "print(parser.generate())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There is also a method for replacing an entire array of values. Try\n",
    "replacing the set of three integers as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "parser = parser_setup(\"test_transfer_array\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from numpy import array\n",
    "\n",
    "array_val = array([123, 456, 789])\n",
    "\n",
    "parser.reset_anchor()\n",
    "parser.mark_anchor(\"INPUT\")\n",
    "parser.transfer_array(array_val, 1, 1, 3)\n",
    "\n",
    "print(parser.generate())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The method ``transfer_array`` takes four required inputs. The first is an array\n",
    "of values that will become the new values in the file. The second is the\n",
    "starting row after the anchor. The third is the starting field that will be\n",
    "replaced, and the fourth is the ending field. The new array replaces the\n",
    "block of fields spanned by the starting field and the ending field.\n",
    "\n",
    "You can also use the ``transfer_array`` method to `stretch` an existing\n",
    "array in a template to add more terms."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "parser = parser_setup(\"test_transfer_stretch\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from numpy import array\n",
    "\n",
    "array_val = array([11, 22, 33, 44, 55, 66])\n",
    "\n",
    "parser.reset_anchor()\n",
    "parser.mark_anchor(\"INPUT\")\n",
    "parser.transfer_array(array_val, 1, 1, 3, sep=' ')\n",
    "\n",
    "print(parser.generate())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The named argument ``sep`` defines which separator to include between the\n",
    "additional terms of the array.\n",
    "\n",
    "The input file templating capability that comes with OpenMDAO is basic, but quite\n",
    "functional. If you need a more powerful templating engine, particularly one that\n",
    "allows the inclusion of logic in your template files, then you may want to consider\n",
    "one of the community-developed [templating](https://wiki.python.org/moin/Templating) engines."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Parsing the Output File\n",
    "\n",
    "When an external code is executed, it typically outputs the results into a\n",
    "file. OpenMDAO includes a utility called `FileParser`, which contains functions\n",
    "for parsing a file, extracting the fields you specify, and converting them to the\n",
    "appropriate data type.\n",
    "\n",
    "### *Basic Extraction*\n",
    "\n",
    "Consider an application that produces the following as part of its\n",
    "text file output:\n",
    "\n",
    "```\n",
    "    LOAD CASE 1\n",
    "    STRESS 1.3334e7 3.9342e7 NaN 2.654e5\n",
    "    DISPLACEMENT 2.1 4.6 3.1 2.22234\n",
    "    LOAD CASE 2\n",
    "    STRESS 11 22 33 44 55 66\n",
    "    DISPLACEMENT 1.0 2.0 3.0 4.0 5.0\n",
    "```\n",
    "\n",
    "As part of the file wrap, you need to extract the information from this file\n",
    "that is needed by downstream components in the model. For this to\n",
    "work, the file must have some general format that would allow you to locate the\n",
    "piece of data you need relative to some constant feature in the file. In other\n",
    "words, the main capability of the FileParser is to locate and extract a set of\n",
    "characters that is some number of lines and some number of fields away from an\n",
    "`anchor` point.\n",
    "\n",
    "```\n",
    "    from openmdao.utils.file_wrap import FileParser\n",
    "\n",
    "    parser = FileParser()\n",
    "    parser.set_file('output.txt')\n",
    "```\n",
    "\n",
    "To use the FileParser object, first instantiate it and give it the name of the\n",
    "output file. (Note that this code must be placed in your component's\n",
    "``compute`` function *after* the external code has been run.\n",
    "\n",
    "Say you want to extract the first ``STRESS`` value from each load case in the file\n",
    "snippet shown above. The code would look like this."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "from openmdao.utils.file_wrap import FileParser\n",
    "parser = FileParser()\n",
    "\n",
    "parser._data = [\n",
    "    \"LOAD CASE 1\",\n",
    "    \"STRESS 1.3334e7 3.9342e7 NaN 2.654e5\",\n",
    "    \"DISPLACEMENT 2.1 4.6 3.1 2.22234\",\n",
    "    \"LOAD CASE 2\",\n",
    "    \"STRESS 11 22 33 44 55 66\",\n",
    "    \"DISPLACEMENT 1.0 2.0 3.0 4.0 5.0\"\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "parser.mark_anchor(\"LOAD CASE\")\n",
    "var = parser.transfer_var(1, 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert(var == 1.3334e+07)\n",
    "assert(type(var) == float)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The method ``mark_anchor`` is used to define an anchor, which becomes the\n",
    "starting point for the ``transfer_var`` method. Here, you extract the value from the\n",
    "second field in the first line down from the anchor. The parser is smart enough to\n",
    "recognize the number as floating point and to create a Python float variable.\n",
    "\n",
    "The third value of ``STRESS`` is `NaN`. Python has built-in values for `nan`\n",
    "and `inf` that are valid for float variables. The parser recognizes them when it\n",
    "encounters them in a file. This allows you to catch numerical overflows,\n",
    "underflows, etc., and take action. NumPy includes the functions ``isnan`` and\n",
    "``isinf`` to test for `nan` and `inf` respectively.  In the following example,\n",
    "we extract that `nan` value:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "parser.reset_anchor()\n",
    "parser.mark_anchor(\"LOAD CASE\")\n",
    "var = parser.transfer_var(1, 4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "from numpy import isnan, isinf\n",
    "assert(isnan(var) == True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When the data is not a number, it is recognized as a string. For example, we can\n",
    "extract the word ``DISPLACEMENT``."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "parser.reset_anchor()\n",
    "parser.mark_anchor(\"LOAD CASE\")\n",
    "var = parser.transfer_var(2, 1)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert(var == \"DISPLACEMENT\")\n",
    "assert(type(var) == str)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, what if you want to extract the value of stress from the second load case? An\n",
    "additional argument can be passed to the ``mark_anchor`` method telling it to\n",
    "start at the second instance of the text fragment ``\"LOAD CASE\"``."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "parser.reset_anchor()\n",
    "parser.mark_anchor(\"LOAD CASE\", 2)\n",
    "var = parser.transfer_var(1, 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert(var == 11)\n",
    "assert(type(var) == int)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note also that we used the method ``reset_anchor`` to return the anchor to the\n",
    "beginning of the file before marking our new anchor. Subsequent calls to\n",
    "``mark_anchor`` start at the previous anchor and find the next instance of the\n",
    "anchor text. It is a good practice to reset your anchor unless you are looking for\n",
    "an instance of \"B\" that follows an instance of \"A\".\n",
    "\n",
    "You can also count backwards from the bottom of the file by passing a negative\n",
    "number. Here, the second instance of ``\"LOAD CASE\"`` from the bottom brings us\n",
    "back to the first one."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "parser.reset_anchor()\n",
    "parser.mark_anchor(\"LOAD CASE\", -2)\n",
    "var = parser.transfer_var(1, 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "from openmdao.utils.assert_utils import assert_near_equal\n",
    "assert_near_equal(var, 1.3334e+07)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There is a shortcut for extracting data that is stored as ``Key Value`` or\n",
    "``\"Key Value Value ...\"``. The method ``transfer_keyvar`` finds the first occurrence\n",
    "of the *key* string after the anchor (in this case, the word ``DISPLACEMENT``), and\n",
    "extracts the specified field value. This can be useful in cases where variables are\n",
    "found on lines that are uniquely named, particularly where you don't always know how\n",
    "many lines the key will occur past the anchor location. There are two optional\n",
    "arguments to ``transfer_keyvar``. The first lets you specify the `nth` occurrence\n",
    "of the key, and the second lets you specify a number of lines to offset from\n",
    "the line where the key is found (negative numbers are allowed)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "parser.reset_anchor()\n",
    "parser.mark_anchor(\"LOAD CASE 1\")\n",
    "var = parser.transfer_keyvar(\"DISPLACEMENT\", 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert(var == 2.1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### *Array Extraction*\n",
    "\n",
    "Now consider the same application that produces the following as part of its\n",
    "text file output:\n",
    "\n",
    "```\n",
    "    LOAD CASE 1\n",
    "    STRESS 1.3334e7 3.9342e7 NaN 2.654e5\n",
    "    DISPLACEMENT 2.1 4.6 3.1 2.22234\n",
    "    LOAD CASE 2\n",
    "    STRESS 11 22 33 44 55 66\n",
    "    DISPLACEMENT 1.0 2.0 3.0 4.0 5.0\n",
    "```\n",
    "\n",
    "This time, extract all of the displacements in one read and store\n",
    "them as an array. You can do this with the ``transfer_array`` method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "parser.reset_anchor()\n",
    "parser.mark_anchor(\"LOAD CASE\")\n",
    "var = parser.transfer_array(2, 2, 2, 5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "import numpy\n",
    "assert_near_equal(var, numpy.array([2.1, 4.6, 3.1, 2.22234]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ``transfer_array`` method takes four arguments: *starting row*, *starting field*,\n",
    "*ending row*, and *ending field*. The parser extracts all values from the starting\n",
    "row and field and continues until it hits the ending field in the ending row.\n",
    "These values are all placed in a 1D array. When extracting multiple lines, if\n",
    "a line break is hit, the parser continues reading from the next line until the\n",
    "last line is hit. The following extraction illustrates this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "parser.reset_anchor()\n",
    "parser.mark_anchor(\"LOAD CASE\")\n",
    "var = parser.transfer_array(1, 3, 2, 4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "from openmdao.utils.assert_utils import assert_equal_arrays\n",
    "assert_equal_arrays(var, numpy.array([\n",
    "    '39342000.0', 'nan', '265400.0',\n",
    "    'DISPLACEMENT', '2.1', '4.6', '3.1'\n",
    "]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "With the inclusion of ``'DISPLACEMENT'``, this is returned as an array of strings,\n",
    "so you must be careful.\n",
    "\n",
    "There is also a method to extract a 2-dimensional array from tabulated data.\n",
    "Consider an output table that looks like this:\n",
    "\n",
    "```\n",
    "    FREQ  DELTA   A     B     C     D     E     F     G     H     I     J\n",
    "     Hz\n",
    "     50.   1.0   30.0  34.8  36.3  36.1  34.6  32.0  28.4  23.9  18.5  12.2\n",
    "     63.   1.0   36.5  41.3  42.8  42.6  41.1  38.5  34.9  30.4  25.0  18.7\n",
    "     80.   1.0   42.8  47.6  49.1  48.9  47.4  44.8  41.2  36.7  31.3  25.0\n",
    "    100.   1.0   48.4  53.1  54.7  54.5  53.0  50.4  46.8  42.3  36.9  30.6\n",
    "```\n",
    "\n",
    "We would like to extract the relevant numerical data from this table, which\n",
    "amounts to all values contained in columns labeled \"A\" through \"J\" and rows\n",
    "labeled \"50 Hz\" through \"100 Hz.\" We would like to save these values in a\n",
    "two-dimensional numpy array. This can be accomplished using the\n",
    "``transfer_2Darray`` method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "parser = FileParser()\n",
    "\n",
    "# A way to \"cheat\" and do this without a file.\n",
    "parser._data = []\n",
    "parser._data.append('FREQ  DELTA  -8.5  -8.5  -8.5  -8.5  -8.5  -8.5  -8.5  -8.5  -8.5  -8.5')\n",
    "parser._data.append(' Hz')\n",
    "parser._data.append(' 50.   1.0   30.0  34.8  36.3  36.1  34.6  32.0  28.4  23.9  18.5  12.2')\n",
    "parser._data.append(' 63.   1.0   36.5  41.3  42.8  42.6  41.1  38.5  34.9  30.4  25.0  18.7')\n",
    "parser._data.append(' 80.   1.0   42.8  47.6  49.1  48.9  47.4  44.8  41.2  36.7  31.3  25.0')\n",
    "parser._data.append('100.   1.0   48.4  53.1  54.7  54.5  53.0  50.4  46.8  42.3  36.9  30.6')\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "parser.reset_anchor()\n",
    "parser.mark_anchor(\"Hz\")\n",
    "var = parser.transfer_2Darray(1, 3, 4, 12)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert_equal_arrays(var, numpy.array([\n",
    "    [30.0,  34.8,  36.3,  36.1,  34.6,  32.0,  28.4,  23.9,  18.5,  12.2],\n",
    "    [36.5,  41.3,  42.8,  42.6,  41.1,  38.5,  34.9,  30.4,  25.0,  18.7],\n",
    "    [42.8,  47.6,  49.1,  48.9,  47.4,  44.8,  41.2,  36.7,  31.3,  25.0],\n",
    "    [48.4,  53.1,  54.7,  54.5,  53.0,  50.4,  46.8,  42.3,  36.9,  30.6]\n",
    "]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The arguments to ``transfer_2Darray`` are the *starting row*, *starting field*,\n",
    "*ending row*, and *ending field*. If the ending field is omitted, then all values\n",
    "to the end of the line are extracted. In that case, care must be taken to make\n",
    "sure that all lines have the same number of values.\n",
    "\n",
    "Note that if the delimiter is set to ``'columns'``, then the column number should be\n",
    "entered instead of the field number. Delimiters are discussed in the next section.\n",
    "\n",
    "### *Delimiters*\n",
    "\n",
    "When the parser counts fields in a line of output, it determines the field\n",
    "boundaries by comparing against a set of delimiters. These delimiters can be\n",
    "changed using the ``set_delimiters`` method. By default, the delimiters are the\n",
    "general white space characters space (``\" \"``) and tab (``\"\\t\"``). The newline characters\n",
    "(``\"\\n\"`` and ``\"\\r\"``) are always removed regardless of the delimiter status.\n",
    "\n",
    "One common case that will require a change in the default delimiter is comma\n",
    "separated values (i.e. `csv`). Here's an example of such an output file:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "parser = FileParser()\n",
    "\n",
    "parser._data = [\n",
    "    \"CASE 1\",\n",
    "    \"3,7,2,4,5,6\"\n",
    "]\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "parser.reset_anchor()\n",
    "parser.mark_anchor(\"CASE\")\n",
    "var = parser.transfer_var(1, 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert(var == \",7,2,4,5,6\")\n",
    "assert(type(var) == str)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What happened here is slightly confusing, but the main point is that the parser\n",
    "did not handle this as expected because commas were not in the set of\n",
    "delimiters. Now specify commas as your delimiter."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "parser.reset_anchor()\n",
    "parser.mark_anchor(\"CASE\")\n",
    "parser.set_delimiters(\", \")\n",
    "var = parser.transfer_var(1, 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert(var == 7)\n",
    "assert(type(var) == int)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "With the correct delimiter set, you extract the second integer as expected.\n",
    "\n",
    "While the ability to set the delimiters adds flexibility for parsing many\n",
    "different types of input files, you may find cases that are too complex to\n",
    "parse (e.g., a field with separator characters inside of quotes.) In such cases\n",
    "you may need to read and extract the data manually.\n",
    "\n",
    "### *Special Case Delimiter - Columns*\n",
    "\n",
    "One special-case value of the delimiter, ``'columns'``, is useful when the\n",
    "data fields have defined column location, as is the case in certain formatted\n",
    "output from Fortran or C. When the delimiter is set to ``'columns'``, the\n",
    "behavior of some of the methods is slightly different. Consider the following\n",
    "output file:\n",
    "\n",
    "```\n",
    "    CASE 1\n",
    "    12345678901234567890\n",
    "    TTF    3.7-9.4434967\n",
    "```\n",
    "\n",
    "The second line is a comment that helps the reader identify the column\n",
    "number (particularly on a printout) and does not need to be parsed.\n",
    "\n",
    "In the third line, the first three columns contain flags that are either ``'T'``\n",
    "or ``'F'``. Columns 4-10 contain a floating point number, and columns 11\n",
    "through 20 contain another floating point number. Note that there isn't\n",
    "always a space between the two numbers in this format, particularly when the\n",
    "second number has a negative sign. We can't parse this with a regular\n",
    "separator, but we can use the special separator ``'columns'``.\n",
    "\n",
    "Let's parse this file to extract the third boolean flag and the two numbers.\n",
    "\n",
    "When the delimiters are in column mode, ``transfer_var`` takes the starting\n",
    "field and the ending field as its second and third arguments. Since we just\n",
    "want one column for the boolean flag, the starting field and ending field are\n",
    "the same. For the floating point values, we provide the appropriate column ranges:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "parser = FileParser()\n",
    "\n",
    "parser._data = [\n",
    "    \"CASE 1\",\n",
    "    \"12345678901234567890\",\n",
    "    \"TTF    3.7-9.4434967\"\n",
    "]\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "parser.reset_anchor()\n",
    "parser.mark_anchor(\"CASE\")\n",
    "parser.set_delimiters(\"columns\")\n",
    "\n",
    "var1 = parser.transfer_var(2, 3, 3)\n",
    "var2 = parser.transfer_var(2, 4, 10)\n",
    "var3 = parser.transfer_var(2, 11, 20)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert((var1, var2, var3) == ('F', 3.7, -9.4434967))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ``transfer_array`` method can also be used with columns, but it is used\n",
    "differently than ``transfer_var``. Consider this output file:\n",
    "\n",
    "```\n",
    "    CASE 2\n",
    "    123456789012345678901234567890\n",
    "    NODE 11 22 33 COMMENT\n",
    "    NODE 44 55 66 STUFF\n",
    "```\n",
    "\n",
    "In this example, we want to extract the six numerical values and place them in\n",
    "an array. When the delimiter is set to columns, we can define a rectangular\n",
    "box from which all elements are parsed into an array. Note that the numbers\n",
    "inside of the box are parsed assuming standard separator characters (``\" \\t\"``).\n",
    "\n",
    "So here we call ``transfer_array`` with four arguments: *starting row*,\n",
    "*starting column*, *ending row*, and *ending column*:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "parser._data = [\n",
    "    \"CASE 2\",\n",
    "    \"123456789012345678901234567890\",\n",
    "    \"NODE 11 22 33 COMMENT\",\n",
    "    \"NODE 44 55 66 STUFF\"\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "parser.reset_anchor()\n",
    "parser.mark_anchor(\"CASE 2\")\n",
    "\n",
    "parser.set_delimiters(\"columns\")\n",
    "var = parser.transfer_array(2, 6, 3, 13)\n",
    "\n",
    "parser.set_delimiters(\" \\t\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "remove-input",
     "remove-output"
    ]
   },
   "outputs": [],
   "source": [
    "assert_near_equal(var,\n",
    "                 numpy.array([11., 22., 33., 44., 55., 66.]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## A Special Case - Fortran Namelists\n",
    "\n",
    "Since legacy Fortran codes are expected to be frequent candidates for\n",
    "file wrapping, you may also consider using the [f90nml](https://f90nml.readthedocs.io/en/latest/) package for reading and writing files to wrap those codes. This package enables the creation and\n",
    "manipulation of namelist files using the common Python dictionary interface.\n",
    "\n",
    "## A Note on Precision\n",
    "\n",
    "In a file-wrapped component, all key inputs for the external code come from an intermediate file\n",
    "that must be written. When generating the input file, it is important to prevent the loss of\n",
    "precision. Consider a variable with 15 digits of precision.\n",
    "\n",
    "```\n",
    "    >>> # Python 3 compatibility\n",
    "    >>>     >>> val = 3.1415926535897932\n",
    "    >>>\n",
    "    >>> val\n",
    "    3.141592653589793...\n",
    "    >>>\n",
    "    >>> print(val)\n",
    "    3.14159265359\n",
    "    >>>\n",
    "    >>> print(\"%s\" % str(val))\n",
    "    3.14159265359\n",
    "    >>>\n",
    "    >>> print(\"%f\" % val)\n",
    "    3.141593\n",
    "    >>>\n",
    "    >>> print(\"%.16f\" % val)\n",
    "    3.141592653589793...\n",
    "```\n",
    "\n",
    "If the variable's value in the input file is created using the ``print``\n",
    "statement, only 11 digits of precision are in the generated output. The same\n",
    "is true if you convert the value to a string and use string output formatting.\n",
    "Printing the variable as a floating point number with no format string gives\n",
    "even less precision. To output the full precision of a variable, you must specify\n",
    "decimal precision using formatted output (i.e., ``\"%.16f\"``).\n",
    "\n",
    "Quibbling over the 11th--15th decimal place may sound unnecessary,\n",
    "but some applications are sensitive to changes of this magnitude. Moreover, it\n",
    "is important to consider how your component may be used during optimization. A\n",
    "gradient optimizer will often use a finite difference scheme to calculate the\n",
    "gradients for a model, and this means that some component inputs might be\n",
    "subjected to small increments and decrements. A loss of precision here can\n",
    "completely change the calculated gradient and prevent the optimizer from\n",
    "reaching a correct minimum value.\n",
    "\n",
    "The file-wrapping utilities in OpenMDAO use ``\"%.16g\"``. If you write your own\n",
    "custom input-file generator for a new component, you should use this format\n",
    "for the floating point variables.\n",
    "\n",
    "Precision is also important when parsing the output, although the file-parsing\n",
    "utilities always extract the entire number. However, some codes limit the number of\n",
    "digits of precision in their output files for human readability. In such a case,\n",
    "you should check your external application's manual to see if there is a flag for\n",
    "telling the code to output the full precision."
   ]
  }
 ],
 "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.8.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
