{ "cells": [ { "cell_type": "markdown", "id": "full-detail-00", "metadata": {}, "source": [ "# AirfRANS RANS simulation full-detail microscope\n", "\n", "This notebook is a full-detail companion to `airfrans_stepper_learning_tool.ipynb` for the same real AirfRANS OpenFOAM case:\n", "\n", "`airFoil2D_SST_93.213_3.79_0.418_0.0_9.665`\n", "\n", "The goal is to expose the simulation without silently hiding large structures:\n", "\n", "1. identify the physical problem encoded by the AirfRANS case;\n", "2. inspect how OpenFOAM stores mesh topology, patches, fields, and boundary conditions;\n", "3. display full tensor shapes, dtypes, entity counts, and truncation policy every time values are too large for a notebook cell;\n", "4. visualize every large field or mesh object through histograms, binned spatial maps, and sparse-graph density plots so every entity contributes even when raw value text is truncated;\n", "5. step through one steady SIMPLE iteration with the Python OpenFOAM stepper;\n", "6. inspect each intermediate object: `MeshView`, `FieldView`, `PatchFieldView`, `MatrixView`, `SolveResult`, and `TransformResult`;\n", "7. connect each object back to finite-volume RANS mechanics.\n", "\n", "Output contract for this full-detail edition: raw value displays may truncate values to keep cells readable, but every display keeps the exact shape, dtype, size, and entity count. Visualizations aggregate complete arrays unless explicitly labeled as a selected-row view for ragged OpenFOAM lists.\n" ] }, { "cell_type": "code", "execution_count": 1, "id": "full-detail-01", "metadata": { "execution": { "iopub.execute_input": "2026-07-22T20:00:17.543442Z", "iopub.status.busy": "2026-07-22T20:00:17.543233Z", "iopub.status.idle": "2026-07-22T20:00:17.550312Z", "shell.execute_reply": "2026-07-22T20:00:17.549370Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "cwd=/home/aaron/data/openFOAM-RANS-to-GPU/notebooks\n", "repo=/home/aaron/data/openFOAM-RANS-to-GPU\n", "kernel_python=/home/aaron/data/openFOAM-RANS-to-GPU/.venv/bin/python\n", "project_python=/home/aaron/data/openFOAM-RANS-to-GPU/.venv/bin/python\n", "raw_case=/home/aaron/data/airfrans/data/raw/OF_dataset/airFoil2D_SST_93.213_3.79_0.418_0.0_9.665\n", "raw_case_exists=True\n" ] } ], "source": [ "from pathlib import Path\n", "import os\n", "import re\n", "import shutil\n", "import subprocess\n", "import sys\n", "\n", "\n", "def find_repo_root(start: Path) -> Path:\n", " \"\"\"Find the repo even when Jupyter starts in ./notebooks.\"\"\"\n", " for candidate in (start.resolve(), *start.resolve().parents):\n", " if (candidate / \"pyproject.toml\").exists() and (candidate / \"OpenFOAM-14\").exists() and (candidate / \"scripts\").exists():\n", " return candidate\n", " raise RuntimeError(f\"Could not find openFOAM-RANS-to-GPU repo root from {start}\")\n", "\n", "\n", "def drop_ambient_pythonpath() -> None:\n", " \"\"\"Avoid accidentally importing packages from an external PYTHONPATH.\"\"\"\n", " pythonpath = os.environ.pop(\"PYTHONPATH\", \"\")\n", " for entry in pythonpath.split(os.pathsep):\n", " if not entry:\n", " continue\n", " while entry in sys.path:\n", " sys.path.remove(entry)\n", "\n", "\n", "ROOT = find_repo_root(Path.cwd())\n", "PYTHON = ROOT / \".venv/bin/python\"\n", "if not PYTHON.exists():\n", " PYTHON = Path(sys.executable)\n", "SCRIPTS = ROOT / \"scripts\"\n", "if str(SCRIPTS) not in sys.path:\n", " sys.path.insert(0, str(SCRIPTS))\n", "\n", "drop_ambient_pythonpath()\n", "\n", "RAW_CASE = ROOT.parent / \"airfrans/data/raw/OF_dataset/airFoil2D_SST_93.213_3.79_0.418_0.0_9.665\"\n", "WORK = ROOT / \"tmp/airfrans_stepper_full_detail_learning_tool\"\n", "CASE = WORK / \"split_case\"\n", "HIGH_LEVEL_CASE = WORK / \"high_level_case\"\n", "\n", "print(f\"cwd={Path.cwd()}\")\n", "print(f\"repo={ROOT}\")\n", "print(f\"kernel_python={sys.executable}\")\n", "print(f\"project_python={PYTHON}\")\n", "print(f\"raw_case={RAW_CASE}\")\n", "print(f\"raw_case_exists={RAW_CASE.exists()}\")\n", "assert RAW_CASE.exists(), RAW_CASE\n" ] }, { "cell_type": "markdown", "id": "full-detail-02", "metadata": {}, "source": [ "## Notebook map\n", "\n", "The cells are ordered like the solver:\n", "\n", "```mermaid\n", "flowchart TD\n", " A[Raw AirfRANS case] --> B[v14 migrated working case]\n", " B --> C[MeshView and boundary patches]\n", " C --> D[FieldRegistryView: U, p, phi, nut, k, omega]\n", " D --> E[preSolve and SIMPLE loop]\n", " E --> F[Momentum terms and UEqn]\n", " F --> G[Solve U]\n", " G --> H[Pressure inputs and pEqn]\n", " H --> I[Solve p and update phi]\n", " I --> J[Correct U, p, phi]\n", " J --> K[Correct turbulence: k, omega, nut]\n", " K --> L[Parity sanity check]\n", "```\n", "\n", "The key idea: OpenFOAM advances the simulation by repeatedly transforming named fields and matrices. The stepper lets Python stop after each transformation and inspect the exact native OpenFOAM objects.\n" ] }, { "cell_type": "code", "execution_count": 2, "id": "full-detail-03", "metadata": { "execution": { "iopub.execute_input": "2026-07-22T20:00:17.552136Z", "iopub.status.busy": "2026-07-22T20:00:17.551999Z", "iopub.status.idle": "2026-07-22T20:00:18.009382Z", "shell.execute_reply": "2026-07-22T20:00:18.008990Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "foam_stepper version=0.2.0\n" ] } ], "source": [ "try:\n", " import json\n", " import math\n", " from dataclasses import asdict\n", "\n", " import numpy as np\n", " from IPython.display import Markdown, display\n", "\n", " from prepare_airfrans_stepper_case import (\n", " assignment,\n", " prepare_case,\n", " read_text,\n", " vector_assignment,\n", " )\n", " from openfoam_env import apply_openfoam_env\n", "\n", " apply_openfoam_env()\n", " import foam_stepper as foam\n", "except Exception as exc:\n", " raise RuntimeError(\n", " \"Notebook setup failed. Use the repo .venv kernel if possible: \"\n", " f\"{PYTHON}. The first cell resolved paths correctly, but the current \"\n", " \"kernel could not import the numerical/OpenFOAM bindings.\"\n", " ) from exc\n", "\n", "\n", "def fmt(value, digits: int = 4) -> str:\n", " if value is None:\n", " return \"\"\n", " if isinstance(value, (bool, np.bool_)):\n", " return str(bool(value))\n", " if isinstance(value, (int, np.integer)):\n", " return str(int(value))\n", " if isinstance(value, (float, np.floating)):\n", " if not np.isfinite(value):\n", " return str(value)\n", " return f\"{float(value):.{digits}g}\"\n", " return str(value)\n", "\n", "\n", "def md_table(headers, rows) -> Markdown:\n", " def clean(value):\n", " return fmt(value).replace(\"|\", \"\\\\|\").replace(\"\\n\", \"
\")\n", " clean_headers = [clean(header) for header in headers]\n", " lines = [\"| \" + \" | \".join(clean_headers) + \" |\", \"| \" + \" | \".join([\"---\"] * len(clean_headers)) + \" |\"]\n", " for row in rows:\n", " lines.append(\"| \" + \" | \".join(clean(value) for value in row) + \" |\")\n", " return Markdown(\"\\n\".join(lines))\n", "\n", "\n", "def show_table(title, headers, rows):\n", " display(Markdown(f\"**{title}**\"))\n", " display(md_table(headers, rows))\n", "\n", "\n", "def arr_stats(array) -> dict[str, object]:\n", " arr = np.asarray(array)\n", " if arr.size == 0:\n", " return {\"shape\": tuple(arr.shape), \"min\": None, \"max\": None, \"mean\": None, \"l2\": 0.0}\n", " flat = arr.astype(float, copy=False).reshape(-1)\n", " return {\n", " \"shape\": tuple(arr.shape),\n", " \"min\": float(np.nanmin(flat)),\n", " \"max\": float(np.nanmax(flat)),\n", " \"mean\": float(np.nanmean(flat)),\n", " \"l2\": float(np.linalg.norm(flat)),\n", " }\n", "\n", "\n", "def stats_row(name, array):\n", " stats = arr_stats(array)\n", " return [name, stats[\"shape\"], stats[\"min\"], stats[\"max\"], stats[\"mean\"], stats[\"l2\"]]\n", "\n", "\n", "def source_text(result) -> str:\n", " source = result.source\n", " if not source.file:\n", " return \"native wrapper / OpenFOAM runtime\"\n", " lines = \"\" if source.lines is None else f\":{source.lines[0]}-{source.lines[1]}\"\n", " return f\"{source.file}{lines}::{source.function}\"\n", "\n", "\n", "def transform_row(result):\n", " return [\n", " result.name,\n", " result.phase,\n", " \", \".join(result.changed_fields) or \"—\",\n", " \", \".join(result.inputs.keys()) or \"—\",\n", " \", \".join(result.outputs.keys()) or \"—\",\n", " source_text(result),\n", " ]\n", "\n", "\n", "def field_summary_row(field):\n", " stats = arr_stats(field.internal)\n", " return [field.name, field.kind, field.dimensions, field.entity_kind, field.entity_count, stats[\"shape\"], stats[\"min\"], stats[\"max\"], stats[\"mean\"]]\n", "\n", "\n", "def boundary_field_rows(field):\n", " rows = []\n", " for patch_name, patch_field in field.boundary.items():\n", " stats = arr_stats(patch_field.values)\n", " rows.append([\n", " field.name,\n", " patch_name,\n", " patch_field.type,\n", " stats[\"shape\"],\n", " stats[\"min\"],\n", " stats[\"max\"],\n", " patch_field.fixes_value,\n", " patch_field.assignable,\n", " patch_field.coupled,\n", " ])\n", " return rows\n", "\n", "\n", "def matrix_summary_row(label, matrix):\n", " diag = arr_stats(matrix.diag)\n", " source = arr_stats(matrix.source)\n", " upper_shape = None if matrix.upper is None else tuple(np.asarray(matrix.upper).shape)\n", " lower_shape = None if matrix.lower is None else tuple(np.asarray(matrix.lower).shape)\n", " return [\n", " label,\n", " matrix.field_name,\n", " matrix.value_rank,\n", " matrix.dimensions,\n", " diag[\"shape\"],\n", " diag[\"min\"],\n", " diag[\"max\"],\n", " source[\"shape\"],\n", " source[\"l2\"],\n", " upper_shape,\n", " lower_shape,\n", " matrix.diagonal,\n", " matrix.symmetric,\n", " matrix.asymmetric,\n", " ]\n", "\n", "\n", "def field_snapshot(registry, names=(\"U\", \"p\", \"phi\", \"nut\", \"k\", \"omega\")):\n", " return {name: np.array(registry[name].internal, copy=True) for name in names if name in registry}\n", "\n", "\n", "def delta_rows(before, after, names=None):\n", " rows = []\n", " names = names or sorted(set(before) & set(after))\n", " for name in names:\n", " if name not in before or name not in after:\n", " continue\n", " diff = np.asarray(after[name]) - np.asarray(before[name])\n", " stats = arr_stats(diff)\n", " rows.append([name, stats[\"shape\"], stats[\"min\"], stats[\"max\"], stats[\"mean\"], stats[\"l2\"], float(np.max(np.abs(diff))) if diff.size else 0.0])\n", " return rows\n", "\n", "\n", "def matrix_delta_row(label, before, after):\n", " diag = np.asarray(after.diag) - np.asarray(before.diag)\n", " source = np.asarray(after.source) - np.asarray(before.source)\n", " return [\n", " label,\n", " float(np.max(np.abs(diag))) if diag.size else 0.0,\n", " float(np.linalg.norm(diag.reshape(-1))) if diag.size else 0.0,\n", " float(np.max(np.abs(source))) if source.size else 0.0,\n", " float(np.linalg.norm(source.reshape(-1))) if source.size else 0.0,\n", " ]\n", "\n", "\n", "def solve_result_row(result):\n", " return [\n", " result.field_name,\n", " result.solver_name,\n", " result.initial_residual,\n", " result.final_residual,\n", " result.n_iterations,\n", " result.converged,\n", " result.singular,\n", " ]\n", "\n", "print(f\"foam_stepper version={foam.version()}\")\n" ] }, { "cell_type": "markdown", "id": "full-detail-04", "metadata": {}, "source": [ "## Full-detail output policy\n", "\n", "Large OpenFOAM arrays can be hundreds of thousands of rows long. This notebook does not pretend those arrays are small:\n", "\n", "- every raw-array panel prints exact `shape`, `dtype`, `ndim`, and `size` before any value text;\n", "- value text uses NumPy edge truncation only when a cell would flood the notebook;\n", "- ragged mesh lists show exact row counts and selected rows with their full row lengths;\n", "- charts and heatmaps are computed from the full input arrays, not from the displayed edge values;\n", "- spatial heatmaps use cell or face centres as coordinates, so large tensors can be seen as geometry instead of scrollback.\n", "\n", "Tune `FULL_DETAIL_MAX_VALUES`, `FULL_DETAIL_EDGE_ITEMS`, and `FULL_DETAIL_GRID` below if you want denser text or finer binned plots.\n" ] }, { "cell_type": "code", "execution_count": 3, "id": "full-detail-05", "metadata": {}, "outputs": [], "source": [ "from IPython.display import HTML, SVG\n", "import html\n", "\n", "FULL_DETAIL_MAX_VALUES = 256\n", "FULL_DETAIL_EDGE_ITEMS = 4\n", "FULL_DETAIL_ROW_ITEMS = 48\n", "FULL_DETAIL_GRID = 96\n", "\n", "np.set_printoptions(\n", " threshold=FULL_DETAIL_MAX_VALUES,\n", " edgeitems=FULL_DETAIL_EDGE_ITEMS,\n", " linewidth=180,\n", " suppress=False,\n", ")\n", "\n", "\n", "def _html(value) -> str:\n", " return html.escape(str(value), quote=False)\n", "\n", "\n", "def shape_text(shape) -> str:\n", " shape = tuple(int(dim) for dim in shape)\n", " if not shape:\n", " return \"scalar\"\n", " return \" × \".join(f\"{dim:,}\" for dim in shape)\n", "\n", "\n", "def exact_array_meta(array) -> dict[str, object]:\n", " arr = np.asarray(array)\n", " return {\n", " \"shape\": tuple(int(dim) for dim in arr.shape),\n", " \"shape text\": shape_text(arr.shape),\n", " \"ndim\": int(arr.ndim),\n", " \"dtype\": str(arr.dtype),\n", " \"size\": int(arr.size),\n", " }\n", "\n", "\n", "def array_panel(name, array, *, max_values=FULL_DETAIL_MAX_VALUES, edgeitems=FULL_DETAIL_EDGE_ITEMS, open=True):\n", " \"\"\"Display exact array metadata plus truncated value text when needed.\"\"\"\n", " arr = np.asarray(array)\n", " meta = exact_array_meta(arr)\n", " status = \"complete value text\"\n", " if arr.size > max_values:\n", " status = (\n", " f\"TRUNCATED value text; exact shape retained; \"\n", " f\"size={arr.size:,}; threshold={max_values:,}; edgeitems={edgeitems}\"\n", " )\n", " try:\n", " text = np.array2string(arr, threshold=max_values, edgeitems=edgeitems, max_line_width=180)\n", " except Exception as exc:\n", " text = f\"\\nrepr={arr!r}\"\n", " attr = \" open\" if open else \"\"\n", " summary = (\n", " f\"{_html(name)} — shape={_html(meta['shape'])} \"\n", " f\"({meta['shape text']}), dtype={_html(meta['dtype'])}, size={meta['size']:,}, {status}\"\n", " )\n", " display(HTML(\n", " f\"\"\n", " f\"{summary}\"\n", " f\"
\"\n",
    "        f\"{_html(text)}
\"\n", " ))\n", "\n", "\n", "def safe_array_panel(name, producer, *, open=False):\n", " \"\"\"Show an array if an optional view exists; otherwise show the missing reason.\"\"\"\n", " try:\n", " value = producer()\n", " except Exception as exc:\n", " display(Markdown(f\"`{name}` unavailable: `{type(exc).__name__}: {exc}`\"))\n", " return None\n", " if value is None:\n", " display(Markdown(f\"`{name}` is `None`.\"))\n", " return None\n", " array_panel(name, value, open=open)\n", " return value\n", "\n", "\n", "def selected_indices(n: int, edge: int = 4) -> list[int]:\n", " n = int(n)\n", " if n <= 0:\n", " return []\n", " if n <= 2 * edge + 3:\n", " return list(range(n))\n", " middle = n // 2\n", " picks = set(range(edge))\n", " picks.update([middle - 1, middle, middle + 1])\n", " picks.update(range(max(0, n - edge), n))\n", " return sorted(i for i in picks if 0 <= i < n)\n", "\n", "\n", "def truncate_sequence(values, *, max_items=FULL_DETAIL_ROW_ITEMS):\n", " values = list(values)\n", " if len(values) <= max_items:\n", " return values, False\n", " edge = max(1, max_items // 3)\n", " return values[:edge] + [\"...\"] + values[-edge:], True\n", "\n", "\n", "def ragged_panel(name, ragged, n_rows: int, *, edge=4, max_items=FULL_DETAIL_ROW_ITEMS, open=True):\n", " \"\"\"Display exact row count and selected ragged rows without pretending the list is rectangular.\"\"\"\n", " n_rows = int(n_rows)\n", " rows = []\n", " for i in selected_indices(n_rows, edge=edge):\n", " row = [int(x) for x in ragged.row(i)]\n", " shown, truncated = truncate_sequence(row, max_items=max_items)\n", " rows.append((i, len(row), shown, truncated))\n", " lines = [\n", " f\"{name}: ragged OpenFOAM label list\",\n", " f\"exact row count={n_rows:,}\",\n", " f\"selected rows={len(rows):,}; selected row values keep exact row length; long rows use ellipsis only in the middle\",\n", " \"\",\n", " ]\n", " for i, length, shown, truncated in rows:\n", " suffix = \" (row value text truncated)\" if truncated else \"\"\n", " lines.append(f\"row {i:,}: length={length:,}{suffix}\")\n", " lines.append(\" \" + repr(shown))\n", " attr = \" open\" if open else \"\"\n", " display(HTML(\n", " f\"\"\n", " f\"{_html(name)} — ragged rows={n_rows:,}\"\n", " f\"
\"\n",
    "        f\"{_html(chr(10).join(lines))}
\"\n", " ))\n", "\n", "\n", "_PALETTE = np.array(\n", " [\n", " [68, 1, 84],\n", " [59, 82, 139],\n", " [33, 145, 140],\n", " [94, 201, 98],\n", " [253, 231, 37],\n", " ],\n", " dtype=float,\n", ")\n", "\n", "\n", "def color_map(t) -> str:\n", " t = float(np.clip(t, 0.0, 1.0))\n", " pos = t * (len(_PALETTE) - 1)\n", " i = int(np.floor(pos))\n", " if i >= len(_PALETTE) - 1:\n", " rgb = _PALETTE[-1]\n", " else:\n", " frac = pos - i\n", " rgb = (1.0 - frac) * _PALETTE[i] + frac * _PALETTE[i + 1]\n", " r, g, b = [int(round(x)) for x in rgb]\n", " return f\"rgb({r},{g},{b})\"\n", "\n", "\n", "def _finite_flat(data):\n", " arr = np.asarray(data, dtype=float).reshape(-1)\n", " return arr[np.isfinite(arr)]\n", "\n", "\n", "def histogram_svg(title, data, *, bins=80, width=760, height=230, log_y=False):\n", " values = _finite_flat(data)\n", " if values.size == 0:\n", " display(Markdown(f\"**{title}**: no finite values.\"))\n", " return\n", " lo = float(values.min())\n", " hi = float(values.max())\n", " if lo == hi:\n", " pad = 0.5 if lo == 0 else abs(lo) * 0.01\n", " lo -= pad\n", " hi += pad\n", " counts, edges = np.histogram(values, bins=bins, range=(lo, hi))\n", " y_values = np.log1p(counts) if log_y else counts.astype(float)\n", " y_max = float(y_values.max()) if y_values.size else 1.0\n", " y_max = y_max if y_max > 0 else 1.0\n", " left, right, top, bottom = 70, 18, 38, 48\n", " plot_w = width - left - right\n", " plot_h = height - top - bottom\n", " bar_w = plot_w / len(counts)\n", " rects = []\n", " for i, y in enumerate(y_values):\n", " h = 0.0 if y_max == 0 else (float(y) / y_max) * plot_h\n", " x = left + i * bar_w\n", " y0 = top + plot_h - h\n", " rects.append(f\"\")\n", " mean = float(values.mean())\n", " std = float(values.std())\n", " label = \"log1p(count)\" if log_y else \"count\"\n", " svg = f\"\"\"\n", " \n", " \n", " {_html(title)}\n", " n={values.size:,} finite, min={lo:.6g}, max={hi:.6g}, mean={mean:.6g}, std={std:.6g}\n", " x=value, y={label}, bins={bins}\n", " \n", " \n", " {''.join(rects)}\n", " \n", " \"\"\"\n", " display(SVG(svg))\n", "\n", "\n", "def bar_chart_svg(title, labels, values, *, width=760, height=None):\n", " labels = [str(x) for x in labels]\n", " values = np.asarray(values, dtype=float)\n", " if height is None:\n", " height = max(220, 48 + 30 * len(labels))\n", " if values.size == 0:\n", " display(Markdown(f\"**{title}**: no values.\"))\n", " return\n", " vmax = float(values.max()) if float(values.max()) > 0 else 1.0\n", " left, right, top, row_h = 170, 30, 34, 26\n", " plot_w = width - left - right\n", " rows = []\n", " for i, (label, value) in enumerate(zip(labels, values)):\n", " y = top + i * row_h\n", " w = plot_w * float(value) / vmax\n", " rows.append(\n", " f\"{_html(label)}\"\n", " f\"\"\n", " f\"{value:,.0f}\"\n", " )\n", " svg = f\"\"\"\n", " \n", " \n", " {_html(title)}\n", " {''.join(rows)}\n", " \n", " \"\"\"\n", " display(SVG(svg))\n", "\n", "\n", "def density_svg(title, x, y, values=None, *, grid=FULL_DETAIL_GRID, width=700, height=560, statistic=\"mean\", log_counts=True):\n", " x = np.asarray(x, dtype=float).reshape(-1)\n", " y = np.asarray(y, dtype=float).reshape(-1)\n", " if x.size != y.size:\n", " raise ValueError(f\"x/y length mismatch: {x.size} != {y.size}\")\n", " mask = np.isfinite(x) & np.isfinite(y)\n", " v = None\n", " if values is not None:\n", " v = np.asarray(values, dtype=float).reshape(-1)\n", " if v.size != x.size:\n", " raise ValueError(f\"values length mismatch: {v.size} != {x.size}\")\n", " mask &= np.isfinite(v)\n", " v = v[mask]\n", " x = x[mask]\n", " y = y[mask]\n", " if x.size == 0:\n", " display(Markdown(f\"**{title}**: no finite coordinates.\"))\n", " return\n", " x_range = (float(x.min()), float(x.max()))\n", " y_range = (float(y.min()), float(y.max()))\n", " if x_range[0] == x_range[1]:\n", " x_range = (x_range[0] - 0.5, x_range[1] + 0.5)\n", " if y_range[0] == y_range[1]:\n", " y_range = (y_range[0] - 0.5, y_range[1] + 0.5)\n", " counts, x_edges, y_edges = np.histogram2d(x, y, bins=grid, range=[x_range, y_range])\n", " if v is None:\n", " Z = np.log1p(counts) if log_counts else counts\n", " occupied = counts > 0\n", " legend = \"log1p(count)\" if log_counts else \"count\"\n", " raw_min = float(counts[occupied].min()) if np.any(occupied) else 0.0\n", " raw_max = float(counts.max()) if counts.size else 0.0\n", " else:\n", " sums, _, _ = np.histogram2d(x, y, bins=grid, range=[x_range, y_range], weights=v)\n", " with np.errstate(invalid=\"ignore\", divide=\"ignore\"):\n", " Z = sums / counts\n", " occupied = counts > 0\n", " legend = statistic\n", " raw_min = float(np.nanmin(Z[occupied])) if np.any(occupied) else 0.0\n", " raw_max = float(np.nanmax(Z[occupied])) if np.any(occupied) else 0.0\n", " if not np.any(occupied):\n", " display(Markdown(f\"**{title}**: no occupied bins.\"))\n", " return\n", " z_values = Z[occupied]\n", " z_min = float(np.nanmin(z_values))\n", " z_max = float(np.nanmax(z_values))\n", " if z_min == z_max:\n", " denom = 1.0\n", " else:\n", " denom = z_max - z_min\n", " left, right, top, bottom = 72, 18, 38, 58\n", " plot_w = width - left - right\n", " plot_h = height - top - bottom\n", " cell_w = plot_w / grid\n", " cell_h = plot_h / grid\n", " rects = []\n", " for ix, iy in np.argwhere(occupied):\n", " z = float(Z[ix, iy])\n", " t = 0.5 if z_min == z_max else (z - z_min) / denom\n", " px = left + ix * cell_w\n", " py = top + (grid - 1 - iy) * cell_h\n", " rects.append(\n", " f\"\")\n", " svg = f\"\"\"\n", " \n", " \n", " {_html(title)}\n", " \n", " {''.join(rects)}\n", " points={x.size:,}; bins={grid}×{grid}; color={_html(legend)}; occupied bins={int(occupied.sum()):,}\n", " x=[{x_range[0]:.6g}, {x_range[1]:.6g}], y=[{y_range[0]:.6g}, {y_range[1]:.6g}]\n", " raw color range=[{raw_min:.6g}, {raw_max:.6g}]\n", " \n", " \"\"\"\n", " display(SVG(svg))\n", "\n", "\n", "def entity_scalar_values(array, n_entities: int):\n", " arr = np.asarray(array)\n", " if arr.shape[0] != int(n_entities):\n", " raise ValueError(f\"first dimension {arr.shape[0]} does not match entity count {n_entities}\")\n", " if arr.ndim == 1:\n", " return arr.astype(float, copy=False)\n", " flat = arr.reshape((int(n_entities), -1)).astype(float, copy=False)\n", " if flat.shape[1] == 1:\n", " return flat[:, 0]\n", " return np.linalg.norm(flat, axis=1)\n", "\n", "\n", "def show_field_histograms(field, *, bins=80):\n", " arr = np.asarray(field.internal)\n", " array_panel(f\"{field.name}.internal\", arr, open=False)\n", " if arr.ndim == 1:\n", " histogram_svg(f\"{field.name} internal values\", arr, bins=bins)\n", " return\n", " flat = arr.reshape((arr.shape[0], -1))\n", " for j in range(min(flat.shape[1], 6)):\n", " histogram_svg(f\"{field.name} component {j} internal values\", flat[:, j], bins=bins)\n", " if flat.shape[1] > 1:\n", " histogram_svg(f\"{field.name} internal vector/tensor magnitude\", np.linalg.norm(flat, axis=1), bins=bins)\n", "\n", "\n", "def show_cell_field_map(mesh, field, *, title=None, grid=FULL_DETAIL_GRID):\n", " coords = np.asarray(mesh.C)\n", " values = entity_scalar_values(field.internal, mesh.n_cells)\n", " density_svg(title or f\"{field.name} on cell centres\", coords[:, 0], coords[:, 1], values, grid=grid, statistic=\"bin mean\")\n", "\n", "\n", "def show_face_field_map(mesh, field, *, title=None, grid=FULL_DETAIL_GRID):\n", " coords = np.asarray(mesh.Cf[:mesh.n_internal_faces])\n", " values = entity_scalar_values(field.internal, mesh.n_internal_faces)\n", " density_svg(title or f\"{field.name} on internal face centres\", coords[:, 0], coords[:, 1], values, grid=grid, statistic=\"bin mean\")\n", "\n", "\n", "def show_delta_map(mesh, name, before, after, *, grid=FULL_DETAIL_GRID):\n", " diff = np.asarray(after[name]) - np.asarray(before[name])\n", " values = entity_scalar_values(diff, diff.shape[0])\n", " coords = np.asarray(mesh.C if diff.shape[0] == mesh.n_cells else mesh.Cf[:diff.shape[0]])\n", " density_svg(f\"|Δ{name}| spatial distribution\", coords[:, 0], coords[:, 1], np.abs(values), grid=grid, statistic=\"bin mean |Δ|\")\n", "\n", "\n", "def show_matrix_arrays(label, matrix, *, open_arrays=False):\n", " array_panel(f\"{label}.diag\", matrix.diag, open=open_arrays)\n", " array_panel(f\"{label}.source\", matrix.source, open=open_arrays)\n", " if matrix.upper is not None:\n", " array_panel(f\"{label}.upper\", matrix.upper, open=open_arrays)\n", " if matrix.lower is not None:\n", " array_panel(f\"{label}.lower\", matrix.lower, open=open_arrays)\n", " histogram_svg(f\"{label}.diag coefficient distribution\", matrix.diag, bins=90, log_y=True)\n", " histogram_svg(f\"{label}.source distribution\", matrix.source, bins=90, log_y=True)\n", " if matrix.upper is not None:\n", " histogram_svg(f\"{label}.upper coefficient distribution\", matrix.upper, bins=90, log_y=True)\n", " if matrix.lower is not None:\n", " histogram_svg(f\"{label}.lower coefficient distribution\", matrix.lower, bins=90, log_y=True)\n", "\n", "\n", "def show_ldu_density(mesh, title=\"LDU owner-neighbour sparse graph density\", *, grid=FULL_DETAIL_GRID):\n", " density_svg(title, np.asarray(mesh.owner), np.asarray(mesh.neighbour), grid=grid, statistic=\"count\", log_counts=True)\n" ] }, { "cell_type": "markdown", "id": "full-detail-06", "metadata": {}, "source": [ "## The Python objects this notebook will use\n", "\n", "The stepper facade turns native OpenFOAM objects into small Python views. These are not reimplementations of OpenFOAM; they are snapshots of OpenFOAM-owned state.\n" ] }, { "cell_type": "code", "execution_count": 4, "id": "full-detail-07", "metadata": { "execution": { "iopub.execute_input": "2026-07-22T20:00:18.010909Z", "iopub.status.busy": "2026-07-22T20:00:18.010785Z", "iopub.status.idle": "2026-07-22T20:00:18.015422Z", "shell.execute_reply": "2026-07-22T20:00:18.014909Z" } }, "outputs": [ { "data": { "text/markdown": [ "**Stepper data model**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| Object | What it represents | Why it matters for the simulation |\n", "| --- | --- | --- |\n", "| MeshView | points, faces, owner/neighbour addressing, cell volumes, patch geometry | the finite-volume control-volume graph |\n", "| PatchView | one boundary patch: wall, farfield, empty plane | where boundary conditions attach to mesh faces |\n", "| FieldRegistryView | mapping of registered fields | what OpenFOAM currently knows: U, p, phi, turbulence fields |\n", "| FieldView | internal field array plus boundary patch fields | cell or face values being solved/updated |\n", "| PatchFieldView | boundary-condition type, values, coefficients | how wall/freestream/empty constraints enter equations |\n", "| MatrixView | fvMatrix/lduMatrix diag, upper, lower, source | the sparse linear system OpenFOAM solves |\n", "| SolveResult | solver residuals, iteration count, convergence flag | how hard the linear solve was |\n", "| TransformResult | one named OpenFOAM operation with inputs, outputs, source location | the breadcrumb for every state transition |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "show_table(\n", " \"Stepper data model\",\n", " [\"Object\", \"What it represents\", \"Why it matters for the simulation\"],\n", " [\n", " [\"MeshView\", \"points, faces, owner/neighbour addressing, cell volumes, patch geometry\", \"the finite-volume control-volume graph\"],\n", " [\"PatchView\", \"one boundary patch: wall, farfield, empty plane\", \"where boundary conditions attach to mesh faces\"],\n", " [\"FieldRegistryView\", \"mapping of registered fields\", \"what OpenFOAM currently knows: U, p, phi, turbulence fields\"],\n", " [\"FieldView\", \"internal field array plus boundary patch fields\", \"cell or face values being solved/updated\"],\n", " [\"PatchFieldView\", \"boundary-condition type, values, coefficients\", \"how wall/freestream/empty constraints enter equations\"],\n", " [\"MatrixView\", \"fvMatrix/lduMatrix diag, upper, lower, source\", \"the sparse linear system OpenFOAM solves\"],\n", " [\"SolveResult\", \"solver residuals, iteration count, convergence flag\", \"how hard the linear solve was\"],\n", " [\"TransformResult\", \"one named OpenFOAM operation with inputs, outputs, source location\", \"the breadcrumb for every state transition\"],\n", " ],\n", ")\n" ] }, { "cell_type": "markdown", "id": "full-detail-08", "metadata": {}, "source": [ "## 1. Prepare the real AirfRANS case\n", "\n", "The sibling AirfRANS raw case is OpenFOAM.com v2112 syntax. This repo runs OpenFOAM Foundation v14. The helper copies the raw case into `tmp/` and migrates only the dictionaries needed for a one-iteration microscope run:\n", "\n", "- `controlDict`: uses `solver incompressibleFluid`, one time step, no function objects;\n", "- `physicalProperties`: carries `rho` and molecular `nu`;\n", "- `momentumTransport`: selects `RAS { model kOmegaSST; turbulence on; }`;\n", "- mesh and initial fields are copied unchanged.\n" ] }, { "cell_type": "code", "execution_count": 5, "id": "full-detail-09", "metadata": { "execution": { "iopub.execute_input": "2026-07-22T20:00:18.016773Z", "iopub.status.busy": "2026-07-22T20:00:18.016585Z", "iopub.status.idle": "2026-07-22T20:00:18.031003Z", "shell.execute_reply": "2026-07-22T20:00:18.030338Z" } }, "outputs": [ { "data": { "text/markdown": [ "**Selected case metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| Quantity | Value | Meaning |\n", "| --- | --- | --- |\n", "| simulation | airFoil2D_SST_93.213_3.79_0.418_0.0_9.665 | AirfRANS raw OpenFOAM case directory |\n", "| Uinf | 93.21 | freestream speed magnitude [m/s] |\n", "| velocity | (93.00914503999995, 6.161356013756317, 0.0) | initial/freestream velocity vector [m/s] |\n", "| nu | 1.56e-05 | kinematic viscosity [m^2/s] |\n", "| rhoInf | 1.204 | reference density [kg/m^3] |\n", "| Re | 5.975e+06 | Re = Uinf * chord / nu; AirfRANS chord is 1 m |\n", "| Mach | 0.2693 | Uinf / 346.1, archived diagnostic |\n", "| angle | 3.79 | angle from dragDir |\n", "| raw endTime | 20000 | original AirfRANS run length |\n", "| notebook endTime | 1 | one iteration for inspection |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "working_case=/home/aaron/data/openFOAM-RANS-to-GPU/tmp/airfrans_stepper_full_detail_learning_tool/split_case\n" ] } ], "source": [ "if WORK.exists():\n", " shutil.rmtree(WORK)\n", "WORK.mkdir(parents=True)\n", "\n", "meta = prepare_case(RAW_CASE, CASE, end_time=1)\n", "meta_dict = asdict(meta)\n", "\n", "show_table(\n", " \"Selected case metadata\",\n", " [\"Quantity\", \"Value\", \"Meaning\"],\n", " [\n", " [\"simulation\", meta.simulation, \"AirfRANS raw OpenFOAM case directory\"],\n", " [\"Uinf\", meta.u_inf, \"freestream speed magnitude [m/s]\"],\n", " [\"velocity\", meta.velocity, \"initial/freestream velocity vector [m/s]\"],\n", " [\"nu\", meta.nu, \"kinematic viscosity [m^2/s]\"],\n", " [\"rhoInf\", meta.rho_inf, \"reference density [kg/m^3]\"],\n", " [\"Re\", meta.reynolds, \"Re = Uinf * chord / nu; AirfRANS chord is 1 m\"],\n", " [\"Mach\", meta.mach, \"Uinf / 346.1, archived diagnostic\"],\n", " [\"angle\", meta.alpha_deg, \"angle from dragDir\"],\n", " [\"raw endTime\", meta.source_end_time, \"original AirfRANS run length\"],\n", " [\"notebook endTime\", meta.migrated_end_time, \"one iteration for inspection\"],\n", " ],\n", ")\n", "print(f\"working_case={CASE}\")\n" ] }, { "cell_type": "markdown", "id": "full-detail-10", "metadata": {}, "source": [ "## 2. What the raw OpenFOAM dictionaries say\n", "\n", "These dictionaries are the simulation contract. They encode the solver, numerical schemes, SIMPLE controls, turbulence model, force directions, and output objects used by AirfRANS.\n" ] }, { "cell_type": "code", "execution_count": 6, "id": "full-detail-11", "metadata": { "execution": { "iopub.execute_input": "2026-07-22T20:00:18.032543Z", "iopub.status.busy": "2026-07-22T20:00:18.032363Z", "iopub.status.idle": "2026-07-22T20:00:18.039450Z", "shell.execute_reply": "2026-07-22T20:00:18.038878Z" } }, "outputs": [ { "data": { "text/markdown": [ "**Raw dictionary facts**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| Dictionary | Entry | Value | Interpretation |\n", "| --- | --- | --- | --- |\n", "| controlDict | application | simpleFoam | original OpenFOAM.com solver |\n", "| controlDict | Uinf | 93.213 | freestream magnitude |\n", "| controlDict | endTime | 20000 | original run has 20k steady iterations |\n", "| controlDict | liftDir | (-0.06609975018244577, 0.9978130200723071, 0.0) | force projection direction |\n", "| controlDict | dragDir | (0.9978130200723071, 0.06609975018244577, 0.0) | force projection direction |\n", "| transportProperties | nu | 1.56e-5 | molecular kinematic viscosity |\n", "| turbulenceProperties | simulationType | RAS | RAS turbulence closure active |\n", "| fvSolution | SIMPLE.consistent | yes | SIMPLEC-style pressure/velocity coupling |\n", "| fvSolution | SIMPLE.nNonOrthogonalCorrectors | 3 | four pressure solves in this v14 path: initial + 3 correctors |\n", "| fvSchemes | ddtSchemes.default | steadyState | pseudo-time iteration, not physical transient integration |\n", "| fvSchemes | div(phi,U) | bounded Gauss linearUpwind grad(U) | convective momentum flux discretization |\n", "| fvSchemes | wallDist.method | meshWave | wall distance for k-omega SST near-wall handling |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "raw_control = read_text(RAW_CASE / \"system/controlDict\")\n", "raw_solution = read_text(RAW_CASE / \"system/fvSolution\")\n", "raw_schemes = read_text(RAW_CASE / \"system/fvSchemes\")\n", "raw_turbulence = read_text(RAW_CASE / \"constant/turbulenceProperties\")\n", "raw_transport = read_text(RAW_CASE / \"constant/transportProperties\")\n", "\n", "show_table(\n", " \"Raw dictionary facts\",\n", " [\"Dictionary\", \"Entry\", \"Value\", \"Interpretation\"],\n", " [\n", " [\"controlDict\", \"application\", assignment(raw_control, \"application\"), \"original OpenFOAM.com solver\"],\n", " [\"controlDict\", \"Uinf\", assignment(raw_control, \"Uinf\"), \"freestream magnitude\"],\n", " [\"controlDict\", \"endTime\", assignment(raw_control, \"endTime\"), \"original run has 20k steady iterations\"],\n", " [\"controlDict\", \"liftDir\", vector_assignment(raw_control, \"liftDir\"), \"force projection direction\"],\n", " [\"controlDict\", \"dragDir\", vector_assignment(raw_control, \"dragDir\"), \"force projection direction\"],\n", " [\"transportProperties\", \"nu\", assignment(raw_transport, \"nu\"), \"molecular kinematic viscosity\"],\n", " [\"turbulenceProperties\", \"simulationType\", assignment(raw_turbulence, \"simulationType\"), \"RAS turbulence closure active\"],\n", " [\"fvSolution\", \"SIMPLE.consistent\", \"yes\", \"SIMPLEC-style pressure/velocity coupling\"],\n", " [\"fvSolution\", \"SIMPLE.nNonOrthogonalCorrectors\", \"3\", \"four pressure solves in this v14 path: initial + 3 correctors\"],\n", " [\"fvSchemes\", \"ddtSchemes.default\", \"steadyState\", \"pseudo-time iteration, not physical transient integration\"],\n", " [\"fvSchemes\", \"div(phi,U)\", \"bounded Gauss linearUpwind grad(U)\", \"convective momentum flux discretization\"],\n", " [\"fvSchemes\", \"wallDist.method\", \"meshWave\", \"wall distance for k-omega SST near-wall handling\"],\n", " ],\n", ")\n" ] }, { "cell_type": "markdown", "id": "full-detail-12", "metadata": {}, "source": [ "## 3. Construct the OpenFOAM stepper and inspect the finite-volume mesh\n", "\n", "A finite-volume mesh is a graph:\n", "\n", "- cells are control volumes;\n", "- faces connect cells or lie on boundary patches;\n", "- each internal face has an owner cell and a neighbour cell;\n", "- each boundary face belongs to exactly one patch;\n", "- fields live either on cells (`vol*`) or faces (`surface*`).\n", "\n", "The AirfRANS case is a 2D airfoil extrusion. The `frontAndBack` patch is `empty`, which tells OpenFOAM the third direction is not solved as a real 3D thickness.\n" ] }, { "cell_type": "code", "execution_count": 7, "id": "full-detail-13", "metadata": { "execution": { "iopub.execute_input": "2026-07-22T20:00:18.041099Z", "iopub.status.busy": "2026-07-22T20:00:18.040919Z", "iopub.status.idle": "2026-07-22T20:00:19.340747Z", "shell.execute_reply": "2026-07-22T20:00:19.340216Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "/*---------------------------------------------------------------------------*\\\n", " ========= |\n", " \\\\ / F ield | OpenFOAM: The Open Source CFD Toolbox\n", " \\\\ / O peration | Website: https://openfoam.org\n", " \\\\ / A nd | Version: 14\n", " \\\\/ M anipulation |\n", "\\*---------------------------------------------------------------------------*/\n", "Build : 14-0db507470b5e\n", "Exec : foam_stepper -case /home/aaron/data/openFOAM-RANS-to-GPU/tmp/airfrans_stepper_full_detail_learning_tool/split_case -noFunctionObjects -solver incompressibleFluid\n", "Date : Jul 23 2026\n", "Time : 11:44:59\n", "Host : \"devuan-hpz\"\n", "PID : 28708\n", "I/O : uncollated\n", "Case : /home/aaron/data/openFOAM-RANS-to-GPU/tmp/airfrans_stepper_full_detail_learning_tool/split_case\n", "nProcs : 1\n", "fileModificationChecking : Monitoring run-time modified files using timeStampMaster (fileModificationSkew 10)\n", "allowSystemOperations : Allowing user-supplied system call operations\n", "\n", "// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //\n", "\n", "Selecting viscosity model constant\n", "\n", "Selecting momentum transport model type RAS\n", " Selecting RAS turbulence model kOmegaSST\n", " bounding k, min: 0 max: 3.6353069999999998e-09 average: 3.6353069999910849e-09\n", "\n", "Selecting patchDistMethod meshWave\n", "\n", "SIMPLE: Convergence criteria found\n", " p: tolerance 0\n", " U: tolerance 0\n", " nuTilda: tolerance 0\n", " k: tolerance 0\n", " omega: tolerance 0\n", " h: tolerance 0\n", "\n", "\n", "SIMPLE: Operating solver in steady-state mode with 1 outer corrector\n", "SIMPLE: Operating solver in SIMPLE mode\n", "\n", "\n" ] }, { "data": { "text/markdown": [ "**Initial solver state**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| Key | Value |\n", "| --- | --- |\n", "| case_path | /home/aaron/data/openFOAM-RANS-to-GPU/tmp/airfrans_stepper_full_detail_learning_tool/split_case |\n", "| solver_name | incompressibleFluid |\n", "| time_name | 0 |\n", "| time_index | 0 |\n", "| time_value | 0 |\n", "| delta_t | 1 |\n", "| pimple_iteration_open | False |\n", "| has_momentum_matrix | False |\n", "| has_pressure_inputs | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**Mesh counts**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| Quantity | Value | Meaning |\n", "| --- | --- | --- |\n", "| points | 569562 | mesh vertices |\n", "| faces | 1134981 | internal + boundary faces |\n", "| internal faces | 565419 | faces with owner and neighbour cells |\n", "| cells | 283400 | finite-volume control volumes |\n", "| ldu owner size | (565419,) | owner addressing for lower/upper matrix coefficients |\n", "| ldu neighbour size | (565419,) | neighbour addressing for lower/upper matrix coefficients |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**Boundary patches from MeshView**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| name | type | index | start | size | coupled | constraint | min area | max area | mean area |\n", "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n", "| aerofoil | wall | 0 | 565419 | 1026 | False | False | 5.949e-06 | 0.0076 | 0.001976 |\n", "| freestream | patch | 1 | 566445 | 1736 | False | False | 0.0001 | 11.11 | 0.8227 |\n", "| frontAndBack | empty | 2 | 568181 | 0 | False | True | | | |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "case = foam.Case(CASE)\n", "stepper = case.make_stepper()\n", "state0 = stepper.state()\n", "mesh = stepper.mesh()\n", "fields = stepper.fields()\n", "\n", "show_table(\"Initial solver state\", [\"Key\", \"Value\"], list(state0.items()))\n", "show_table(\n", " \"Mesh counts\",\n", " [\"Quantity\", \"Value\", \"Meaning\"],\n", " [\n", " [\"points\", mesh.n_points, \"mesh vertices\"],\n", " [\"faces\", mesh.n_faces, \"internal + boundary faces\"],\n", " [\"internal faces\", mesh.n_internal_faces, \"faces with owner and neighbour cells\"],\n", " [\"cells\", mesh.n_cells, \"finite-volume control volumes\"],\n", " [\"ldu owner size\", np.asarray(mesh.owner).shape, \"owner addressing for lower/upper matrix coefficients\"],\n", " [\"ldu neighbour size\", np.asarray(mesh.neighbour).shape, \"neighbour addressing for lower/upper matrix coefficients\"],\n", " ],\n", ")\n", "\n", "patch_rows = []\n", "for patch in mesh.boundary:\n", " mag_stats = arr_stats(patch.magSf)\n", " patch_rows.append([\n", " patch.name,\n", " patch.type,\n", " patch.index,\n", " patch.start,\n", " patch.size,\n", " patch.coupled,\n", " patch.constraint,\n", " mag_stats[\"min\"],\n", " mag_stats[\"max\"],\n", " mag_stats[\"mean\"],\n", " ])\n", "show_table(\"Boundary patches from MeshView\", [\"name\", \"type\", \"index\", \"start\", \"size\", \"coupled\", \"constraint\", \"min area\", \"max area\", \"mean area\"], patch_rows)\n" ] }, { "cell_type": "markdown", "id": "full-detail-14", "metadata": {}, "source": [ "### Full-shape mesh arrays and geometry visualizations\n", "\n", "The tables above give counts. The panels below expose the arrays behind those counts. Text panels may truncate values, but they preserve exact shapes and sizes. The charts use the complete coordinate, volume, face-area, and addressing arrays.\n" ] }, { "cell_type": "code", "execution_count": 8, "id": "full-detail-15", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
mesh.points — shape=(569562, 3) (569,562 × 3), dtype=float64, size=1,708,686, TRUNCATED value text; exact shape retained; size=1,708,686; threshold=256; edgeitems=4
[[ 200.           13.15385029    0.        ]\n",
       " [ 186.1155879    12.23609412    0.        ]\n",
       " [ 173.1998558    11.38236745    0.        ]\n",
       " [ 161.1852212    10.5882031     0.        ]\n",
       " ...\n",
       " [-199.3376715   -16.12037607    1.        ]\n",
       " [-199.6203798   -12.17269963    1.        ]\n",
       " [-199.8270894    -8.16952639    1.        ]\n",
       " [-199.9546882    -4.11165538    1.        ]]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
mesh.C cell centres — shape=(283400, 3) (283,400 × 3), dtype=float64, size=850,200, TRUNCATED value text; exact shape retained; size=850,200; threshold=256; edgeitems=4
[[ 193.10515307   12.69805363    0.5       ]\n",
       " [ 179.70132112   11.81206564    0.5       ]\n",
       " [ 167.23269792   10.98789461    0.5       ]\n",
       " [ 155.63378164   10.22121091    0.5       ]\n",
       " ...\n",
       " [-192.71874543  -13.56339867    0.5       ]\n",
       " [-192.92770495   -9.75145316    0.5       ]\n",
       " [-193.06140841   -5.88666928    0.5       ]\n",
       " [-193.11681557   -1.96987964    0.5       ]]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
mesh.Cf face centres — shape=(565419, 3) (565,419 × 3), dtype=float64, size=1,696,257, TRUNCATED value text; exact shape retained; size=1,696,257; threshold=256; edgeitems=4
[[ 186.1155888    12.23604618    0.5       ]\n",
       " [ 193.05779485   12.69487427    0.5       ]\n",
       " [ 193.05779395   12.6949722     0.5       ]\n",
       " [ 173.1998576    11.38232148    0.5       ]\n",
       " ...\n",
       " [-192.4947848   -15.44731136    0.5       ]\n",
       " [-192.7404734   -11.66449536    0.5       ]\n",
       " [-192.9124636    -7.82846933    0.5       ]\n",
       " [-193.0077474    -3.94000887    0.5       ]]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
mesh.Sf face area vectors — shape=(565419, 3) (565,419 × 3), dtype=float64, size=1,696,257, TRUNCATED value text; exact shape retained; size=1,696,257; threshold=256; edgeitems=4
[[-9.58700000e-05 -1.79999998e-06  0.00000000e+00]\n",
       " [ 9.17752040e-01 -1.38844103e+01  0.00000000e+00]\n",
       " [-9.17756170e-01  1.38844121e+01 -4.44089210e-16]\n",
       " [-9.19400000e-05 -3.60000001e-06  0.00000000e+00]\n",
       " ...\n",
       " [-1.34612941e+00  1.36857734e+01  0.00000000e+00]\n",
       " [-1.01640854e+00  1.37598128e+01  4.44089210e-16]\n",
       " [-6.82114115e-01  1.38292516e+01  0.00000000e+00]\n",
       " [-3.43293021e-01  1.38938816e+01  0.00000000e+00]]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
mesh.V cell volumes — shape=(283400,) (283,400), dtype=float64, size=283,400, TRUNCATED value text; exact shape retained; size=283,400; threshold=256; edgeitems=4
[1.36059579e-03 1.21515680e-03 1.08571142e-03 9.70557105e-04 ... 5.22010257e+01 5.30622492e+01 5.39490060e+01 5.48621082e+01]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
mesh.owner internal-face owner cells — shape=(565419,) (565,419), dtype=int64, size=565,419, TRUNCATED value text; exact shape retained; size=565,419; threshold=256; edgeitems=4
[     0      0      0      1 ... 283395 283396 283397 283398]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
mesh.neighbour internal-face neighbour cells — shape=(565419,) (565,419), dtype=int64, size=565,419, TRUNCATED value text; exact shape retained; size=565,419; threshold=256; edgeitems=4
[     1    137  29866      2 ... 283396 283397 283398 283399]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
mesh.faces point labels per face — ragged rows=1,134,981
mesh.faces point labels per face: ragged OpenFOAM label list\n",
       "exact row count=1,134,981\n",
       "selected rows=11; selected row values keep exact row length; long rows use ellipsis only in the middle\n",
       "\n",
       "row 0: length=4\n",
       "  [1, 139, 30361, 30223]\n",
       "row 1: length=4\n",
       "  [138, 30360, 30361, 139]\n",
       "row 2: length=4\n",
       "  [0, 1, 30223, 30222]\n",
       "row 3: length=4\n",
       "  [2, 140, 30362, 30224]\n",
       "row 567,489: length=4\n",
       "  [173873, 227309, 227310, 173874]\n",
       "row 567,490: length=4\n",
       "  [173874, 227310, 227311, 173875]\n",
       "row 567,491: length=4\n",
       "  [173875, 227311, 227312, 173876]\n",
       "row 1,134,977: length=4\n",
       "  [59890, 59891, 60029, 60028]\n",
       "row 1,134,978: length=4\n",
       "  [60028, 60029, 60167, 60166]\n",
       "row 1,134,979: length=4\n",
       "  [60166, 60167, 60305, 60304]\n",
       "row 1,134,980: length=4\n",
       "  [60304, 60305, 60443, 60442]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
mesh.cells face labels per cell — ragged rows=283,400
mesh.cells face labels per cell: ragged OpenFOAM label list\n",
       "exact row count=283,400\n",
       "selected rows=11; selected row values keep exact row length; long rows use ellipsis only in the middle\n",
       "\n",
       "row 0: length=6\n",
       "  [0, 1, 2, 567201, 821715, 1105115]\n",
       "row 1: length=6\n",
       "  [3, 4, 5, 821933, 1105333, 0]\n",
       "row 2: length=6\n",
       "  [6, 7, 8, 822151, 1105551, 3]\n",
       "row 3: length=6\n",
       "  [9, 10, 11, 822369, 1105769, 6]\n",
       "row 141,699: length=6\n",
       "  [283017, 283018, 818126, 1101526, 282530, 283015]\n",
       "row 141,700: length=6\n",
       "  [283019, 283020, 818344, 1101744, 282532, 283017]\n",
       "row 141,701: length=6\n",
       "  [283021, 283022, 818562, 1101962, 282534, 283019]\n",
       "row 283,396: length=6\n",
       "  [565416, 566979, 767868, 1051268, 565145, 565415]\n",
       "row 283,397: length=6\n",
       "  [565417, 566980, 768086, 1051486, 565147, 565416]\n",
       "row 283,398: length=6\n",
       "  [565418, 566981, 768304, 1051704, 565149, 565417]\n",
       "row 283,399: length=6\n",
       "  [566982, 768522, 1051922, 448621, 565150, 565418]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " Boundary patch sizes\n", " aerofoil1,026freestream1,736frontAndBack0\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " Cell-volume distribution V\n", " n=283,400 finite, min=1.27546e-11, max=57.9661, mean=0.503898, std=2.81004\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " Face-area magnitude distribution |Sf|\n", " n=565,419 finite, min=1.72865e-06, max=17.5268, mean=0.636312, std=2.03571\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " All cell centres: occupancy density\n", " \n", " \n", " points=283,400; bins=96×96; color=log1p(count); occupied bins=4,847\n", " x=[-193.117, 197.702], y=[-194.298, 195.038]\n", " raw color range=[1, 170642]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " All face centres: occupancy density\n", " \n", " \n", " points=565,419; bins=96×96; color=log1p(count); occupied bins=7,291\n", " x=[-193.023, 197.582], y=[-194.361, 195.068]\n", " raw color range=[1, 340226]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " LDU owner-neighbour sparse graph density\n", " \n", " \n", " points=565,419; bins=96×96; color=log1p(count); occupied bins=360\n", " x=[0, 283398], y=[1, 283399]\n", " raw color range=[1, 5749]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
patch aerofoil.Cf face centres — shape=(0, 3) (0 × 3), dtype=float64, size=0, complete value text
[]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
patch aerofoil.Sf face area vectors — shape=(0, 3) (0 × 3), dtype=float64, size=0, complete value text
[]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**patch aerofoil: |Sf|**: no finite values." ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**patch aerofoil: face-centre occupancy**: no finite coordinates." ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
patch freestream.Cf face centres — shape=(0, 3) (0 × 3), dtype=float64, size=0, complete value text
[]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
patch freestream.Sf face area vectors — shape=(0, 3) (0 × 3), dtype=float64, size=0, complete value text
[]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**patch freestream: |Sf|**: no finite values." ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**patch freestream: face-centre occupancy**: no finite coordinates." ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
patch frontAndBack.Cf face centres — shape=(0, 3) (0 × 3), dtype=float64, size=0, complete value text
[]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
patch frontAndBack.Sf face area vectors — shape=(0, 3) (0 × 3), dtype=float64, size=0, complete value text
[]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**patch frontAndBack: |Sf|**: no finite values." ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**patch frontAndBack: face-centre occupancy**: no finite coordinates." ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "safe_array_panel(\"mesh.points\", lambda: mesh.points, open=False)\n", "array_panel(\"mesh.C cell centres\", mesh.C, open=False)\n", "array_panel(\"mesh.Cf face centres\", mesh.Cf, open=False)\n", "array_panel(\"mesh.Sf face area vectors\", mesh.Sf, open=False)\n", "array_panel(\"mesh.V cell volumes\", mesh.V, open=False)\n", "array_panel(\"mesh.owner internal-face owner cells\", mesh.owner, open=False)\n", "array_panel(\"mesh.neighbour internal-face neighbour cells\", mesh.neighbour, open=False)\n", "ragged_panel(\"mesh.faces point labels per face\", mesh.faces, mesh.n_faces, open=False)\n", "ragged_panel(\"mesh.cells face labels per cell\", mesh.cells, mesh.n_cells, open=False)\n", "\n", "bar_chart_svg(\n", " \"Boundary patch sizes\",\n", " [patch.name for patch in mesh.boundary],\n", " [patch.size for patch in mesh.boundary],\n", ")\n", "histogram_svg(\"Cell-volume distribution V\", mesh.V, bins=90, log_y=True)\n", "histogram_svg(\"Face-area magnitude distribution |Sf|\", np.linalg.norm(np.asarray(mesh.Sf), axis=1), bins=90, log_y=True)\n", "density_svg(\"All cell centres: occupancy density\", np.asarray(mesh.C)[:, 0], np.asarray(mesh.C)[:, 1], grid=FULL_DETAIL_GRID, log_counts=True)\n", "density_svg(\"All face centres: occupancy density\", np.asarray(mesh.Cf)[:, 0], np.asarray(mesh.Cf)[:, 1], grid=FULL_DETAIL_GRID, log_counts=True)\n", "show_ldu_density(mesh)\n", "\n", "for patch in mesh.boundary:\n", " start = int(patch.start)\n", " stop = start + int(patch.size)\n", " patch_cf = np.asarray(mesh.Cf[start:stop])\n", " patch_sf = np.asarray(mesh.Sf[start:stop])\n", " array_panel(f\"patch {patch.name}.Cf face centres\", patch_cf, open=False)\n", " array_panel(f\"patch {patch.name}.Sf face area vectors\", patch_sf, open=False)\n", " histogram_svg(f\"patch {patch.name}: |Sf|\", np.linalg.norm(patch_sf, axis=1), bins=60, log_y=True)\n", " density_svg(f\"patch {patch.name}: face-centre occupancy\", patch_cf[:, 0], patch_cf[:, 1], grid=64, log_counts=True)\n" ] }, { "cell_type": "markdown", "id": "full-detail-16", "metadata": {}, "source": [ "### Mesh topology samples\n", "\n", "The arrays below are the core OpenFOAM finite-volume addressing:\n", "\n", "- `mesh.faces` is ragged: each face has a variable number of point labels;\n", "- `mesh.owner[f]` is the cell on one side of internal face `f`;\n", "- `mesh.neighbour[f]` is the cell on the other side;\n", "- the same addressing is later reused by `MatrixView.upper` and `MatrixView.lower`.\n" ] }, { "cell_type": "code", "execution_count": 9, "id": "full-detail-17", "metadata": { "execution": { "iopub.execute_input": "2026-07-22T20:00:19.342450Z", "iopub.status.busy": "2026-07-22T20:00:19.342284Z", "iopub.status.idle": "2026-07-22T20:00:19.351503Z", "shell.execute_reply": "2026-07-22T20:00:19.350958Z" } }, "outputs": [ { "data": { "text/markdown": [ "**Internal face samples**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| face | owner | neighbour | n points | point labels sample | face centre Cf | area vector Sf |\n", "| --- | --- | --- | --- | --- | --- | --- |\n", "| 0 | 0 | 1 | 4 | (1, 139, 30361, 30223) | (186.11558879999995, 12.236046185, 0.49999999999999994) | (-9.587000000088608e-05, -1.7999999784024112e-06, 0.0) |\n", "| 1 | 0 | 137 | 4 | (138, 30360, 30361, 139) | (193.05779484999996, 12.694874269999998, 0.5) | (0.9177520399999999, -13.884410300000013, 0.0) |\n", "| 2 | 0 | 29866 | 4 | (0, 1, 30223, 30222) | (193.05779395, 12.694972204999997, 0.49999999999999994) | (-0.9177561699999988, 13.884412099999992, -4.440892098500626e-16) |\n", "| 565418 | 283398 | 283399 | 4 | (510601, 510869, 569561, 569293) | (-193.0077474, -3.9400088735, 0.5) | (-0.3432930209999996, 13.893881599999986, 0.0) |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**Cell samples**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| cell | n faces | face labels sample | cell centre C | volume V |\n", "| --- | --- | --- | --- | --- |\n", "| 0 | 6 | (0, 1, 2, 567201, 821715, 1105115) | (193.10515306703107, 12.698053626715934, 0.4999999999999795) | 0.001361 |\n", "| 1 | 6 | (3, 4, 5, 821933, 1105333, 0) | (179.70132111689765, 11.812065637500481, 0.49999999999999994) | 0.001215 |\n", "| 141700 | 6 | (283019, 283020, 818344, 1101744, 282532, 283017) | (0.4132102425057547, -0.18515406274195162, 0.5) | 6.852e-05 |\n", "| 283399 | 6 | (566982, 768522, 1051922, 448621, 565150, 565418) | (-193.1168155735373, -1.9698796355742973, 0.5) | 54.86 |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "face_indices = [0, 1, 2, mesh.n_internal_faces - 1]\n", "face_rows = []\n", "for face_i in face_indices:\n", " points = mesh.faces.row(face_i)\n", " face_rows.append([\n", " face_i,\n", " int(mesh.owner[face_i]),\n", " int(mesh.neighbour[face_i]),\n", " len(points),\n", " tuple(int(x) for x in points[:8]),\n", " tuple(float(x) for x in mesh.Cf[face_i]),\n", " tuple(float(x) for x in mesh.Sf[face_i]),\n", " ])\n", "show_table(\"Internal face samples\", [\"face\", \"owner\", \"neighbour\", \"n points\", \"point labels sample\", \"face centre Cf\", \"area vector Sf\"], face_rows)\n", "\n", "cell_rows = []\n", "for cell_i in [0, 1, mesh.n_cells // 2, mesh.n_cells - 1]:\n", " faces_i = mesh.cells.row(cell_i)\n", " cell_rows.append([\n", " cell_i,\n", " len(faces_i),\n", " tuple(int(x) for x in faces_i[:10]),\n", " tuple(float(x) for x in mesh.C[cell_i]),\n", " float(mesh.V[cell_i]),\n", " ])\n", "show_table(\"Cell samples\", [\"cell\", \"n faces\", \"face labels sample\", \"cell centre C\", \"volume V\"], cell_rows)\n" ] }, { "cell_type": "markdown", "id": "full-detail-18", "metadata": {}, "source": [ "## 4. Inspect registered fields and boundary conditions\n", "\n", "The initial state contains six important fields:\n", "\n", "- `U`: velocity, a cell vector field;\n", "- `p`: kinematic pressure-like pressure used by incompressible OpenFOAM, a cell scalar field;\n", "- `phi`: face flux, a surface scalar field on internal faces plus boundary patches;\n", "- `nut`: turbulent viscosity from the RAS model;\n", "- `k`: turbulent kinetic energy;\n", "- `omega`: specific dissipation rate for k-omega SST.\n", "\n", "The internal arrays are what the linear systems update. The boundary patch fields tell OpenFOAM how to close the equations at the wall, freestream, and empty extrusion planes.\n" ] }, { "cell_type": "code", "execution_count": 10, "id": "full-detail-19", "metadata": { "execution": { "iopub.execute_input": "2026-07-22T20:00:19.353167Z", "iopub.status.busy": "2026-07-22T20:00:19.352859Z", "iopub.status.idle": "2026-07-22T20:00:19.399841Z", "shell.execute_reply": "2026-07-22T20:00:19.399456Z" } }, "outputs": [ { "data": { "text/markdown": [ "**Initial field registry**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| name | kind | dimensions | entity | count | shape | min | max | mean |\n", "| --- | --- | --- | --- | --- | --- | --- | --- | --- |\n", "| p | volScalar | [0 2 -2 0 0 0 0] | cell | 283400 | (283400,) | 0 | 0 | 0 |\n", "| U | volVector | [0 1 -1 0 0 0 0] | cell | 283400 | (283400, 3) | 0 | 93.01 | 33.06 |\n", "| phi | surfaceScalar | [0 3 -1 0 0 0 0] | internal_face | 565419 | (565419,) | -1632 | 53.68 | -41.24 |\n", "| nut | volScalar | [0 2 -1 0 0 0 0] | cell | 283400 | (283400,) | 1.574e-17 | 3.12e-09 | 3.109e-09 |\n", "| k | volScalar | [0 2 -2 0 0 0 0] | cell | 283400 | (283400,) | 3.635e-09 | 3.635e-09 | 3.635e-09 |\n", "| omega | volScalar | [0 0 -1 0 0 0 0] | cell | 283400 | (283400,) | 1.165 | 1.165 | 1.165 |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**Boundary-condition views**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| field | patch | BC type | values shape | min | max | fixes value | assignable | coupled |\n", "| --- | --- | --- | --- | --- | --- | --- | --- | --- |\n", "| p | aerofoil | zeroGradient | (1026,) | 0 | 0 | False | True | False |\n", "| p | freestream | freestreamPressure | (1736,) | 0 | 0 | True | False | False |\n", "| p | frontAndBack | empty | (0,) | | | False | True | False |\n", "| U | aerofoil | noSlip | (1026, 3) | 0 | 0 | True | False | False |\n", "| U | freestream | freestreamVelocity | (1736, 3) | 0 | 93.01 | True | False | False |\n", "| U | frontAndBack | empty | (0, 3) | | | False | True | False |\n", "| phi | aerofoil | calculated | (1026,) | 0 | 0 | False | True | False |\n", "| phi | freestream | calculated | (1736,) | -382.7 | 1034 | False | True | False |\n", "| phi | frontAndBack | empty | (0,) | | | False | True | False |\n", "| nut | aerofoil | nutLowReWallFunction | (1026,) | 0 | 0 | True | False | False |\n", "| nut | freestream | freestream | (1736,) | 3.12e-09 | 1.56e-05 | True | True | False |\n", "| nut | frontAndBack | empty | (0,) | | | False | True | False |\n", "| k | aerofoil | fixedValue | (1026,) | 3.635e-09 | 3.635e-09 | True | False | False |\n", "| k | freestream | freestream | (1736,) | 3.635e-09 | 3.635e-09 | True | True | False |\n", "| k | frontAndBack | empty | (0,) | | | False | True | False |\n", "| omega | aerofoil | omegaWallFunction | (1026,) | 1.165 | 1.165 | True | False | False |\n", "| omega | freestream | freestream | (1736,) | 1.165 | 1.165 | True | True | False |\n", "| omega | frontAndBack | empty | (0,) | | | False | True | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "show_table(\n", " \"Initial field registry\",\n", " [\"name\", \"kind\", \"dimensions\", \"entity\", \"count\", \"shape\", \"min\", \"max\", \"mean\"],\n", " [field_summary_row(fields[name]) for name in fields],\n", ")\n", "\n", "boundary_rows = []\n", "for name in fields:\n", " boundary_rows.extend(boundary_field_rows(fields[name]))\n", "show_table(\n", " \"Boundary-condition views\",\n", " [\"field\", \"patch\", \"BC type\", \"values shape\", \"min\", \"max\", \"fixes value\", \"assignable\", \"coupled\"],\n", " boundary_rows,\n", ")\n" ] }, { "cell_type": "markdown", "id": "full-detail-20", "metadata": {}, "source": [ "### Full-shape field and boundary-condition arrays\n", "\n", "This replaces the small-sample field dump from the original notebook. Each internal field and each boundary patch field is shown with exact shape metadata. Histograms and spatial maps use the complete arrays.\n" ] }, { "cell_type": "code", "execution_count": 11, "id": "full-detail-21", "metadata": { "execution": { "iopub.execute_input": "2026-07-22T20:00:19.410758Z", "iopub.status.busy": "2026-07-22T20:00:19.410543Z", "iopub.status.idle": "2026-07-22T20:00:19.422292Z", "shell.execute_reply": "2026-07-22T20:00:19.420159Z" } }, "outputs": [ { "data": { "text/markdown": [ "#### Field `U`: `volVector` on `cell`" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**U exact field metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| dimensions | [0 1 -1 0 0 0 0] |\n", "| entity_kind | cell |\n", "| entity_count | 283400 |\n", "| internal_shape | (283400, 3) |\n", "| internal_dtype | float64 |\n", "| boundary_patch_count | 3 |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
U.internal — shape=(283400, 3) (283,400 × 3), dtype=float64, size=850,200, TRUNCATED value text; exact shape retained; size=850,200; threshold=256; edgeitems=4
[[93.00914504  6.16135601  0.        ]\n",
       " [93.00914504  6.16135601  0.        ]\n",
       " [93.00914504  6.16135601  0.        ]\n",
       " [93.00914504  6.16135601  0.        ]\n",
       " ...\n",
       " [93.00914504  6.16135601  0.        ]\n",
       " [93.00914504  6.16135601  0.        ]\n",
       " [93.00914504  6.16135601  0.        ]\n",
       " [93.00914504  6.16135601  0.        ]]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " U component 0 internal values\n", " n=283,400 finite, min=92.0791, max=93.9392, mean=93.0091, std=1.42109e-14\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " U component 1 internal values\n", " n=283,400 finite, min=6.09974, max=6.22297, mean=6.16136, std=1.77636e-15\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " U component 2 internal values\n", " n=283,400 finite, min=-0.5, max=0.5, mean=0, std=0\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " U internal vector/tensor magnitude\n", " n=283,400 finite, min=92.2809, max=94.1451, mean=93.213, std=2.84217e-14\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " U: complete internal field mapped on cell centres\n", " \n", " \n", " points=283,400; bins=96×96; color=bin mean; occupied bins=4,847\n", " x=[-193.117, 197.702], y=[-194.298, 195.038]\n", " raw color range=[93.213, 93.213]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**U boundary patch `aerofoil` metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| BC type | noSlip |\n", "| values shape | (1026, 3) |\n", "| values dtype | float64 |\n", "| fixes value | True |\n", "| assignable | False |\n", "| coupled | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
U.boundary['aerofoil'].values — shape=(1026, 3) (1,026 × 3), dtype=float64, size=3,078, TRUNCATED value text; exact shape retained; size=3,078; threshold=256; edgeitems=4
[[0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " ...\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " U boundary `aerofoil` values\n", " n=3,078 finite, min=-0.5, max=0.5, mean=0, std=0\n", " x=value, y=log1p(count), bins=60\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**U boundary patch `freestream` metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| BC type | freestreamVelocity |\n", "| values shape | (1736, 3) |\n", "| values dtype | float64 |\n", "| fixes value | True |\n", "| assignable | False |\n", "| coupled | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
U.boundary['freestream'].values — shape=(1736, 3) (1,736 × 3), dtype=float64, size=5,208, TRUNCATED value text; exact shape retained; size=5,208; threshold=256; edgeitems=4
[[93.00914504  6.16135601  0.        ]\n",
       " [93.00914504  6.16135601  0.        ]\n",
       " [93.00914504  6.16135601  0.        ]\n",
       " [93.00914504  6.16135601  0.        ]\n",
       " ...\n",
       " [93.00914504  6.16135601  0.        ]\n",
       " [93.00914504  6.16135601  0.        ]\n",
       " [93.00914504  6.16135601  0.        ]\n",
       " [93.00914504  6.16135601  0.        ]]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " U boundary `freestream` values\n", " n=5,208 finite, min=0, max=93.0091, mean=33.0568, std=42.4672\n", " x=value, y=log1p(count), bins=60\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**U boundary patch `frontAndBack` metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| BC type | empty |\n", "| values shape | (0, 3) |\n", "| values dtype | float64 |\n", "| fixes value | False |\n", "| assignable | True |\n", "| coupled | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
U.boundary['frontAndBack'].values — shape=(0, 3) (0 × 3), dtype=float64, size=0, complete value text
[]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "#### Field `p`: `volScalar` on `cell`" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**p exact field metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| dimensions | [0 2 -2 0 0 0 0] |\n", "| entity_kind | cell |\n", "| entity_count | 283400 |\n", "| internal_shape | (283400,) |\n", "| internal_dtype | float64 |\n", "| boundary_patch_count | 3 |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
p.internal — shape=(283400,) (283,400), dtype=float64, size=283,400, TRUNCATED value text; exact shape retained; size=283,400; threshold=256; edgeitems=4
[0. 0. 0. 0. ... 0. 0. 0. 0.]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " p internal values\n", " n=283,400 finite, min=-0.5, max=0.5, mean=0, std=0\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " p: complete internal field mapped on cell centres\n", " \n", " \n", " points=283,400; bins=96×96; color=bin mean; occupied bins=4,847\n", " x=[-193.117, 197.702], y=[-194.298, 195.038]\n", " raw color range=[0, 0]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**p boundary patch `aerofoil` metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| BC type | zeroGradient |\n", "| values shape | (1026,) |\n", "| values dtype | float64 |\n", "| fixes value | False |\n", "| assignable | True |\n", "| coupled | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
p.boundary['aerofoil'].values — shape=(1026,) (1,026), dtype=float64, size=1,026, TRUNCATED value text; exact shape retained; size=1,026; threshold=256; edgeitems=4
[0. 0. 0. 0. ... 0. 0. 0. 0.]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " p boundary `aerofoil` values\n", " n=1,026 finite, min=-0.5, max=0.5, mean=0, std=0\n", " x=value, y=log1p(count), bins=60\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**p boundary patch `freestream` metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| BC type | freestreamPressure |\n", "| values shape | (1736,) |\n", "| values dtype | float64 |\n", "| fixes value | True |\n", "| assignable | False |\n", "| coupled | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
p.boundary['freestream'].values — shape=(1736,) (1,736), dtype=float64, size=1,736, TRUNCATED value text; exact shape retained; size=1,736; threshold=256; edgeitems=4
[0. 0. 0. 0. ... 0. 0. 0. 0.]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " p boundary `freestream` values\n", " n=1,736 finite, min=-0.5, max=0.5, mean=0, std=0\n", " x=value, y=log1p(count), bins=60\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**p boundary patch `frontAndBack` metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| BC type | empty |\n", "| values shape | (0,) |\n", "| values dtype | float64 |\n", "| fixes value | False |\n", "| assignable | True |\n", "| coupled | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
p.boundary['frontAndBack'].values — shape=(0,) (0), dtype=float64, size=0, complete value text
[]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "#### Field `phi`: `surfaceScalar` on `internal_face`" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**phi exact field metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| dimensions | [0 3 -1 0 0 0 0] |\n", "| entity_kind | internal_face |\n", "| entity_count | 565419 |\n", "| internal_shape | (565419,) |\n", "| internal_dtype | float64 |\n", "| boundary_patch_count | 3 |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
phi.internal — shape=(565419,) (565,419), dtype=float64, size=565,419, TRUNCATED value text; exact shape retained; size=565,419; threshold=256; edgeitems=4
[-8.92787718e-03 -1.87462300e-01  1.87089263e-01 -8.57344168e-03 ... -4.08794233e+01 -9.75618397e+00  2.17640919e+01  5.36757606e+01]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " phi internal values\n", " n=565,419 finite, min=-1631.95, max=53.6758, mean=-41.2404, std=161.171\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " phi: complete internal field mapped on internal face centres\n", " \n", " \n", " points=565,419; bins=96×96; color=bin mean; occupied bins=7,291\n", " x=[-193.023, 197.582], y=[-194.361, 195.068]\n", " raw color range=[-1631.79, 53.6758]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**phi boundary patch `aerofoil` metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| BC type | calculated |\n", "| values shape | (1026,) |\n", "| values dtype | float64 |\n", "| fixes value | False |\n", "| assignable | True |\n", "| coupled | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
phi.boundary['aerofoil'].values — shape=(1026,) (1,026), dtype=float64, size=1,026, TRUNCATED value text; exact shape retained; size=1,026; threshold=256; edgeitems=4
[ 0.  0.  0.  0. ...  0. -0.  0. -0.]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " phi boundary `aerofoil` values\n", " n=1,026 finite, min=-0.5, max=0.5, mean=0, std=0\n", " x=value, y=log1p(count), bins=60\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**phi boundary patch `freestream` metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| BC type | calculated |\n", "| values shape | (1736,) |\n", "| values dtype | float64 |\n", "| fixes value | False |\n", "| assignable | True |\n", "| coupled | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
phi.boundary['freestream'].values — shape=(1736,) (1,736), dtype=float64, size=1,736, TRUNCATED value text; exact shape retained; size=1,736; threshold=256; edgeitems=4
[ 0.83994191  0.84201829  0.84387795  0.8455505  ... -0.90038459 -0.87774364 -0.85567199 -0.83415537]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " phi boundary `freestream` values\n", " n=1,736 finite, min=-382.701, max=1033.56, mean=2.09561e-15, std=123.688\n", " x=value, y=log1p(count), bins=60\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**phi boundary patch `frontAndBack` metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| BC type | empty |\n", "| values shape | (0,) |\n", "| values dtype | float64 |\n", "| fixes value | False |\n", "| assignable | True |\n", "| coupled | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
phi.boundary['frontAndBack'].values — shape=(0,) (0), dtype=float64, size=0, complete value text
[]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "#### Field `nut`: `volScalar` on `cell`" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**nut exact field metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| dimensions | [0 2 -1 0 0 0 0] |\n", "| entity_kind | cell |\n", "| entity_count | 283400 |\n", "| internal_shape | (283400,) |\n", "| internal_dtype | float64 |\n", "| boundary_patch_count | 3 |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
nut.internal — shape=(283400,) (283,400), dtype=float64, size=283,400, TRUNCATED value text; exact shape retained; size=283,400; threshold=256; edgeitems=4
[3.12e-09 3.12e-09 3.12e-09 3.12e-09 ... 3.12e-09 3.12e-09 3.12e-09 3.12e-09]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " nut internal values\n", " n=283,400 finite, min=1.57427e-17, max=3.12e-09, mean=3.1087e-09, std=1.87388e-10\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " nut: complete internal field mapped on cell centres\n", " \n", " \n", " points=283,400; bins=96×96; color=bin mean; occupied bins=4,847\n", " x=[-193.117, 197.702], y=[-194.298, 195.038]\n", " raw color range=[3.10124e-09, 3.12e-09]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**nut boundary patch `aerofoil` metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| BC type | nutLowReWallFunction |\n", "| values shape | (1026,) |\n", "| values dtype | float64 |\n", "| fixes value | True |\n", "| assignable | False |\n", "| coupled | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
nut.boundary['aerofoil'].values — shape=(1026,) (1,026), dtype=float64, size=1,026, TRUNCATED value text; exact shape retained; size=1,026; threshold=256; edgeitems=4
[0. 0. 0. 0. ... 0. 0. 0. 0.]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " nut boundary `aerofoil` values\n", " n=1,026 finite, min=-0.5, max=0.5, mean=0, std=0\n", " x=value, y=log1p(count), bins=60\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**nut boundary patch `freestream` metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| BC type | freestream |\n", "| values shape | (1736,) |\n", "| values dtype | float64 |\n", "| fixes value | True |\n", "| assignable | True |\n", "| coupled | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
nut.boundary['freestream'].values — shape=(1736,) (1,736), dtype=float64, size=1,736, TRUNCATED value text; exact shape retained; size=1,736; threshold=256; edgeitems=4
[3.12e-09 3.12e-09 3.12e-09 3.12e-09 ... 1.56e-05 1.56e-05 1.56e-05 1.56e-05]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " nut boundary `freestream` values\n", " n=1,736 finite, min=3.12e-09, max=1.56e-05, mean=7.68476e-06, std=7.79757e-06\n", " x=value, y=log1p(count), bins=60\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**nut boundary patch `frontAndBack` metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| BC type | empty |\n", "| values shape | (0,) |\n", "| values dtype | float64 |\n", "| fixes value | False |\n", "| assignable | True |\n", "| coupled | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
nut.boundary['frontAndBack'].values — shape=(0,) (0), dtype=float64, size=0, complete value text
[]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "#### Field `k`: `volScalar` on `cell`" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**k exact field metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| dimensions | [0 2 -2 0 0 0 0] |\n", "| entity_kind | cell |\n", "| entity_count | 283400 |\n", "| internal_shape | (283400,) |\n", "| internal_dtype | float64 |\n", "| boundary_patch_count | 3 |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
k.internal — shape=(283400,) (283,400), dtype=float64, size=283,400, TRUNCATED value text; exact shape retained; size=283,400; threshold=256; edgeitems=4
[3.635307e-09 3.635307e-09 3.635307e-09 3.635307e-09 ... 3.635307e-09 3.635307e-09 3.635307e-09 3.635307e-09]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " k internal values\n", " n=283,400 finite, min=3.59895e-09, max=3.67166e-09, mean=3.63531e-09, std=1.24077e-24\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " k: complete internal field mapped on cell centres\n", " \n", " \n", " points=283,400; bins=96×96; color=bin mean; occupied bins=4,847\n", " x=[-193.117, 197.702], y=[-194.298, 195.038]\n", " raw color range=[3.63531e-09, 3.63531e-09]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**k boundary patch `aerofoil` metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| BC type | fixedValue |\n", "| values shape | (1026,) |\n", "| values dtype | float64 |\n", "| fixes value | True |\n", "| assignable | False |\n", "| coupled | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
k.boundary['aerofoil'].values — shape=(1026,) (1,026), dtype=float64, size=1,026, TRUNCATED value text; exact shape retained; size=1,026; threshold=256; edgeitems=4
[3.635307e-09 3.635307e-09 3.635307e-09 3.635307e-09 ... 3.635307e-09 3.635307e-09 3.635307e-09 3.635307e-09]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " k boundary `aerofoil` values\n", " n=1,026 finite, min=3.59895e-09, max=3.67166e-09, mean=3.63531e-09, std=8.27181e-25\n", " x=value, y=log1p(count), bins=60\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**k boundary patch `freestream` metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| BC type | freestream |\n", "| values shape | (1736,) |\n", "| values dtype | float64 |\n", "| fixes value | True |\n", "| assignable | True |\n", "| coupled | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
k.boundary['freestream'].values — shape=(1736,) (1,736), dtype=float64, size=1,736, TRUNCATED value text; exact shape retained; size=1,736; threshold=256; edgeitems=4
[3.635307e-09 3.635307e-09 3.635307e-09 3.635307e-09 ... 3.635307e-09 3.635307e-09 3.635307e-09 3.635307e-09]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " k boundary `freestream` values\n", " n=1,736 finite, min=3.59895e-09, max=3.67166e-09, mean=3.63531e-09, std=4.1359e-25\n", " x=value, y=log1p(count), bins=60\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**k boundary patch `frontAndBack` metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| BC type | empty |\n", "| values shape | (0,) |\n", "| values dtype | float64 |\n", "| fixes value | False |\n", "| assignable | True |\n", "| coupled | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
k.boundary['frontAndBack'].values — shape=(0,) (0), dtype=float64, size=0, complete value text
[]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "#### Field `omega`: `volScalar` on `cell`" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**omega exact field metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| dimensions | [0 0 -1 0 0 0 0] |\n", "| entity_kind | cell |\n", "| entity_count | 283400 |\n", "| internal_shape | (283400,) |\n", "| internal_dtype | float64 |\n", "| boundary_patch_count | 3 |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
omega.internal — shape=(283400,) (283,400), dtype=float64, size=283,400, TRUNCATED value text; exact shape retained; size=283,400; threshold=256; edgeitems=4
[1.1651625 1.1651625 1.1651625 1.1651625 ... 1.1651625 1.1651625 1.1651625 1.1651625]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " omega internal values\n", " n=283,400 finite, min=1.15351, max=1.17681, mean=1.16516, std=2.22045e-16\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " omega: complete internal field mapped on cell centres\n", " \n", " \n", " points=283,400; bins=96×96; color=bin mean; occupied bins=4,847\n", " x=[-193.117, 197.702], y=[-194.298, 195.038]\n", " raw color range=[1.16516, 1.16516]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**omega boundary patch `aerofoil` metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| BC type | omegaWallFunction |\n", "| values shape | (1026,) |\n", "| values dtype | float64 |\n", "| fixes value | True |\n", "| assignable | False |\n", "| coupled | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
omega.boundary['aerofoil'].values — shape=(1026,) (1,026), dtype=float64, size=1,026, TRUNCATED value text; exact shape retained; size=1,026; threshold=256; edgeitems=4
[1.1651625 1.1651625 1.1651625 1.1651625 ... 1.1651625 1.1651625 1.1651625 1.1651625]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " omega boundary `aerofoil` values\n", " n=1,026 finite, min=1.15351, max=1.17681, mean=1.16516, std=2.22045e-16\n", " x=value, y=log1p(count), bins=60\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**omega boundary patch `freestream` metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| BC type | freestream |\n", "| values shape | (1736,) |\n", "| values dtype | float64 |\n", "| fixes value | True |\n", "| assignable | True |\n", "| coupled | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
omega.boundary['freestream'].values — shape=(1736,) (1,736), dtype=float64, size=1,736, TRUNCATED value text; exact shape retained; size=1,736; threshold=256; edgeitems=4
[1.1651625 1.1651625 1.1651625 1.1651625 ... 1.1651625 1.1651625 1.1651625 1.1651625]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " omega boundary `freestream` values\n", " n=1,736 finite, min=1.15351, max=1.17681, mean=1.16516, std=2.22045e-16\n", " x=value, y=log1p(count), bins=60\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**omega boundary patch `frontAndBack` metadata**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| property | value |\n", "| --- | --- |\n", "| BC type | empty |\n", "| values shape | (0,) |\n", "| values dtype | float64 |\n", "| fixes value | False |\n", "| assignable | True |\n", "| coupled | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
omega.boundary['frontAndBack'].values — shape=(0,) (0), dtype=float64, size=0, complete value text
[]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "for name in [\"U\", \"p\", \"phi\", \"nut\", \"k\", \"omega\"]:\n", " field = fields[name]\n", " display(Markdown(f\"#### Field `{name}`: `{field.kind}` on `{field.entity_kind}`\"))\n", " show_table(\n", " f\"{name} exact field metadata\",\n", " [\"property\", \"value\"],\n", " [\n", " [\"dimensions\", field.dimensions],\n", " [\"entity_kind\", field.entity_kind],\n", " [\"entity_count\", field.entity_count],\n", " [\"internal_shape\", np.asarray(field.internal).shape],\n", " [\"internal_dtype\", np.asarray(field.internal).dtype],\n", " [\"boundary_patch_count\", len(field.boundary)],\n", " ],\n", " )\n", " show_field_histograms(field)\n", " if field.entity_kind == \"cell\" or np.asarray(field.internal).shape[0] == mesh.n_cells:\n", " show_cell_field_map(mesh, field, title=f\"{name}: complete internal field mapped on cell centres\")\n", " elif field.entity_kind == \"face\" or np.asarray(field.internal).shape[0] == mesh.n_internal_faces:\n", " show_face_field_map(mesh, field, title=f\"{name}: complete internal field mapped on internal face centres\")\n", " for patch_name, patch_field in field.boundary.items():\n", " values = np.asarray(patch_field.values)\n", " show_table(\n", " f\"{name} boundary patch `{patch_name}` metadata\",\n", " [\"property\", \"value\"],\n", " [\n", " [\"BC type\", patch_field.type],\n", " [\"values shape\", values.shape],\n", " [\"values dtype\", values.dtype],\n", " [\"fixes value\", patch_field.fixes_value],\n", " [\"assignable\", patch_field.assignable],\n", " [\"coupled\", patch_field.coupled],\n", " ],\n", " )\n", " array_panel(f\"{name}.boundary[{patch_name!r}].values\", values, open=False)\n", " if values.size:\n", " histogram_svg(f\"{name} boundary `{patch_name}` values\", values, bins=60, log_y=True)\n" ] }, { "cell_type": "markdown", "id": "full-detail-22", "metadata": {}, "source": [ "## 5. The equations behind the objects\n", "\n", "For this steady incompressible RANS run, the solver is seeking a fixed point of the discretized equations, not integrating real physical time.\n", "\n", "Continuity enforces mass conservation:\n", "\n", "$$\\nabla \\cdot U = 0$$\n", "\n", "OpenFOAM stores the face-normal volume flux as `phi`. The momentum equation, after RANS modelling and finite-volume discretization, becomes a sparse linear system for velocity:\n", "\n", "$$A_U U = H(U, \\phi, \\nu_\\mathrm{eff}) - \\nabla p$$\n", "\n", "where `nuEff = nu + nut` is affected by the k-omega SST turbulence model. SIMPLE/SIMPLEC alternates between:\n", "\n", "1. assemble/solve a momentum predictor for `U`;\n", "2. assemble/solve a pressure equation for `p` so corrected `phi` satisfies continuity;\n", "3. correct `U`, `p`, and `phi`;\n", "4. update turbulence quantities `k`, `omega`, and `nut`.\n" ] }, { "cell_type": "code", "execution_count": 12, "id": "full-detail-23", "metadata": { "execution": { "iopub.execute_input": "2026-07-22T20:00:19.425612Z", "iopub.status.busy": "2026-07-22T20:00:19.425445Z", "iopub.status.idle": "2026-07-22T20:00:19.438238Z", "shell.execute_reply": "2026-07-22T20:00:19.431531Z" } }, "outputs": [ { "data": { "text/markdown": [ "**Split-step execution plan**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| Step | Phase | What to watch |\n", "| --- | --- | --- |\n", "| pre_solve | time | OpenFOAM preSolve hook: mesh/field housekeeping before the loop |\n", "| advance_time | time | increment pseudo-time from 0 to 1 |\n", "| begin_pimple_iteration | pimple | enter the SIMPLE/PIMPLE outer corrector |\n", "| fv_models_correct | pimple | apply fvModels if present; none are active here |\n", "| pre_predictor | pimple | module hook before momentum/turbulence predictor |\n", "| momentum_transport_predictor | momentum | predict turbulence/momentum-transport state if enabled |\n", "| assemble_momentum_terms | momentum | split the pieces that will become UEqn |\n", "| assemble_UEqn | momentum | build the sparse fvVectorMatrix for U |\n", "| relax_UEqn | momentum | apply equation relaxation from fvSolution |\n", "| constrain_UEqn | momentum | apply constraints/boundary equation changes |\n", "| solve_UEqn | momentum | linear solve for U components |\n", "| compute_pressure_inputs | pressure | compute rAU, HbyA, phiHbyA, rAtU |\n", "| assemble_pEqn | pressure | build the sparse fvScalarMatrix for p |\n", "| solve_pEqn | pressure | pressure solves and flux update |\n", "| correct_velocity_pressure_flux | pressure | final SIMPLEC correction of U, p, phi |\n", "| momentum_transport_correct | momentum | solve/correct k, omega, nut |\n", "| end_pimple_iteration | pimple | leave the outer corrector |\n", "| post_solve | time | postSolve hook; optional write disabled here |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "step_plan = [\n", " [\"pre_solve\", \"time\", \"OpenFOAM preSolve hook: mesh/field housekeeping before the loop\"],\n", " [\"advance_time\", \"time\", \"increment pseudo-time from 0 to 1\"],\n", " [\"begin_pimple_iteration\", \"pimple\", \"enter the SIMPLE/PIMPLE outer corrector\"],\n", " [\"fv_models_correct\", \"pimple\", \"apply fvModels if present; none are active here\"],\n", " [\"pre_predictor\", \"pimple\", \"module hook before momentum/turbulence predictor\"],\n", " [\"momentum_transport_predictor\", \"momentum\", \"predict turbulence/momentum-transport state if enabled\"],\n", " [\"assemble_momentum_terms\", \"momentum\", \"split the pieces that will become UEqn\"],\n", " [\"assemble_UEqn\", \"momentum\", \"build the sparse fvVectorMatrix for U\"],\n", " [\"relax_UEqn\", \"momentum\", \"apply equation relaxation from fvSolution\"],\n", " [\"constrain_UEqn\", \"momentum\", \"apply constraints/boundary equation changes\"],\n", " [\"solve_UEqn\", \"momentum\", \"linear solve for U components\"],\n", " [\"compute_pressure_inputs\", \"pressure\", \"compute rAU, HbyA, phiHbyA, rAtU\"],\n", " [\"assemble_pEqn\", \"pressure\", \"build the sparse fvScalarMatrix for p\"],\n", " [\"solve_pEqn\", \"pressure\", \"pressure solves and flux update\"],\n", " [\"correct_velocity_pressure_flux\", \"pressure\", \"final SIMPLEC correction of U, p, phi\"],\n", " [\"momentum_transport_correct\", \"momentum\", \"solve/correct k, omega, nut\"],\n", " [\"end_pimple_iteration\", \"pimple\", \"leave the outer corrector\"],\n", " [\"post_solve\", \"time\", \"postSolve hook; optional write disabled here\"],\n", "]\n", "show_table(\"Split-step execution plan\", [\"Step\", \"Phase\", \"What to watch\"], step_plan)\n" ] }, { "cell_type": "markdown", "id": "full-detail-24", "metadata": {}, "source": [ "## 6. Start the split-step walk\n", "\n", "`TransformResult` is the central learning object. Every step returns:\n", "\n", "- `name` and `phase`;\n", "- `inputs`: fields/matrices consumed by that step;\n", "- `outputs`: fields/matrices/results produced by that step;\n", "- `changed_fields`: high-level names that changed;\n", "- `source`: OpenFOAM source file/function/line provenance when known.\n" ] }, { "cell_type": "code", "execution_count": 13, "id": "full-detail-25", "metadata": { "execution": { "iopub.execute_input": "2026-07-22T20:00:19.443811Z", "iopub.status.busy": "2026-07-22T20:00:19.443591Z", "iopub.status.idle": "2026-07-22T20:00:19.482622Z", "shell.execute_reply": "2026-07-22T20:00:19.481846Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Initial split-step snapshot captured for: ['U', 'k', 'nut', 'omega', 'p', 'phi']\n" ] } ], "source": [ "transform_results = []\n", "initial_snapshot = field_snapshot(stepper.fields())\n", "\n", "\n", "def record(result):\n", " transform_results.append(result)\n", " show_table(\n", " f\"TransformResult: {result.name}\",\n", " [\"name\", \"phase\", \"changed\", \"inputs\", \"outputs\", \"source\"],\n", " [transform_row(result)],\n", " )\n", " return result\n", "\n", "print(\"Initial split-step snapshot captured for:\", sorted(initial_snapshot))\n" ] }, { "cell_type": "markdown", "id": "full-detail-26", "metadata": {}, "source": [ "### Time and SIMPLE loop setup\n", "\n", "These steps move OpenFOAM from case time `0` to pseudo-time `1` and open the outer SIMPLE/PIMPLE corrector. No velocity/pressure solve has happened yet.\n" ] }, { "cell_type": "code", "execution_count": 14, "id": "full-detail-27", "metadata": { "execution": { "iopub.execute_input": "2026-07-22T20:00:19.484224Z", "iopub.status.busy": "2026-07-22T20:00:19.484011Z", "iopub.status.idle": "2026-07-22T20:00:19.496615Z", "shell.execute_reply": "2026-07-22T20:00:19.496029Z" } }, "outputs": [ { "data": { "text/markdown": [ "**TransformResult: pre_solve**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| name | phase | changed | inputs | outputs | source |\n", "| --- | --- | --- | --- | --- | --- |\n", "| pre_solve | time | mesh, Uf | — | case_path, solver_name, time_name, time_index, time_value, delta_t, pimple_iteration_open, has_momentum_matrix, has_pressure_inputs | OpenFOAM-14/applications/modules/incompressibleFluid/incompressibleFluid.C:165-201::preSolve |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**TransformResult: advance_time**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| name | phase | changed | inputs | outputs | source |\n", "| --- | --- | --- | --- | --- | --- |\n", "| advance_time | time | — | — | case_path, solver_name, time_name, time_index, time_value, delta_t, pimple_iteration_open, has_momentum_matrix, has_pressure_inputs | native wrapper / OpenFOAM runtime |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**TransformResult: begin_pimple_iteration**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| name | phase | changed | inputs | outputs | source |\n", "| --- | --- | --- | --- | --- | --- |\n", "| begin_pimple_iteration | pimple | — | — | active, state | native wrapper / OpenFOAM runtime |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**TransformResult: fv_models_correct**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| name | phase | changed | inputs | outputs | source |\n", "| --- | --- | --- | --- | --- | --- |\n", "| fv_models_correct | pimple | — | — | case_path, solver_name, time_name, time_index, time_value, delta_t, pimple_iteration_open, has_momentum_matrix, has_pressure_inputs | native wrapper / OpenFOAM runtime |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**TransformResult: pre_predictor**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| name | phase | changed | inputs | outputs | source |\n", "| --- | --- | --- | --- | --- | --- |\n", "| pre_predictor | pimple | — | — | case_path, solver_name, time_name, time_index, time_value, delta_t, pimple_iteration_open, has_momentum_matrix, has_pressure_inputs | native wrapper / OpenFOAM runtime |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**TransformResult: momentum_transport_predict**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| name | phase | changed | inputs | outputs | source |\n", "| --- | --- | --- | --- | --- | --- |\n", "| momentum_transport_predict | momentum | — | — | case_path, solver_name, time_name, time_index, time_value, delta_t, pimple_iteration_open, has_momentum_matrix, has_pressure_inputs | OpenFOAM-14/applications/modules/incompressibleFluid/incompressibleFluid.C:208-211::momentumTransportPredictor |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**State after loop setup**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| Key | Value |\n", "| --- | --- |\n", "| case_path | /home/aaron/data/openFOAM-RANS-to-GPU/tmp/airfrans_stepper_full_detail_learning_tool/split_case |\n", "| solver_name | incompressibleFluid |\n", "| time_name | 1 |\n", "| time_index | 1 |\n", "| time_value | 1 |\n", "| delta_t | 1 |\n", "| pimple_iteration_open | True |\n", "| has_momentum_matrix | False |\n", "| has_pressure_inputs | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "for label, fn in [\n", " (\"pre_solve\", stepper.pre_solve),\n", " (\"advance_time\", stepper.advance_time),\n", " (\"begin_pimple_iteration\", stepper.begin_pimple_iteration),\n", " (\"fv_models_correct\", stepper.fv_models_correct),\n", " (\"pre_predictor\", stepper.pre_predictor),\n", " (\"momentum_transport_predictor\", stepper.momentum_transport_predictor),\n", "]:\n", " record(fn())\n", "\n", "show_table(\"State after loop setup\", [\"Key\", \"Value\"], list(stepper.state().items()))\n" ] }, { "cell_type": "markdown", "id": "full-detail-28", "metadata": {}, "source": [ "## 7. Momentum equation terms\n", "\n", "Before building the complete velocity matrix, the wrapper asks OpenFOAM for the pieces of the momentum equation. This is the best place to learn what `UEqn` is made of.\n", "\n", "The implicit pieces become matrix coefficients. The explicit pieces become source-like fields.\n" ] }, { "cell_type": "code", "execution_count": 15, "id": "full-detail-29", "metadata": { "execution": { "iopub.execute_input": "2026-07-22T20:00:19.498401Z", "iopub.status.busy": "2026-07-22T20:00:19.498073Z", "iopub.status.idle": "2026-07-22T20:00:19.699758Z", "shell.execute_reply": "2026-07-22T20:00:19.699342Z" } }, "outputs": [ { "data": { "text/markdown": [ "**TransformResult: assemble_momentum_terms**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| name | phase | changed | inputs | outputs | source |\n", "| --- | --- | --- | --- | --- | --- |\n", "| assemble_momentum_terms | momentum | — | p, U, phi, nut, k, omega | terms, source | OpenFOAM-14/applications/modules/incompressibleFluid/momentumPredictor.C:36-53::Foam::solvers::incompressibleFluid::momentumPredictor |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**Momentum-term decomposition**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| name | OpenFOAM expression | implicit | inputs | payload |\n", "| --- | --- | --- | --- | --- |\n", "| assemble_momentum_ddt | fvm::ddt(U) | True | U, phi, p | diag (283400,), source (283400, 3), upper None |\n", "| assemble_momentum_div_phi_U | fvm::div(phi,U) | True | U, phi, p | diag (283400,), source (283400, 3), upper (565419,) |\n", "| assemble_momentum_divDevSigma | momentumTransport->divDevSigma(U) | True | U, phi, p | diag (283400,), source (283400, 3), upper (565419,) |\n", "| assemble_momentum_sources | fvModels().source(U) | True | U, phi, p | diag (283400,), source (283400, 3), upper None |\n", "| assemble_momentum_MRF_DDt | MRF.DDt(U) | False | U | field MRFZoneList:DDt, shape (283400, 3), l2 0 |\n", "| assemble_momentum_pressure_rhs_grad_p | -fvc::grad(p) | False | p | field grad(p), shape (283400, 3), l2 0 |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "terms_result = record(stepper.assemble_momentum_terms())\n", "terms = terms_result.outputs[\"terms\"]\n", "term_rows = []\n", "for term in terms:\n", " if \"matrix\" in term:\n", " matrix = term[\"matrix\"]\n", " diag = arr_stats(matrix.diag)\n", " source = arr_stats(matrix.source)\n", " upper_shape = None if matrix.upper is None else tuple(np.asarray(matrix.upper).shape)\n", " payload = f\"diag {diag['shape']}, source {source['shape']}, upper {upper_shape}\"\n", " else:\n", " field = term[\"field\"]\n", " stats = arr_stats(field.internal)\n", " payload = f\"field {field.name}, shape {stats['shape']}, l2 {fmt(stats['l2'])}\"\n", " term_rows.append([\n", " term[\"name\"],\n", " term[\"kind\"],\n", " term[\"implicit\"],\n", " \", \".join(term[\"inputs\"]),\n", " payload,\n", " ])\n", "show_table(\"Momentum-term decomposition\", [\"name\", \"OpenFOAM expression\", \"implicit\", \"inputs\", \"payload\"], term_rows)\n" ] }, { "cell_type": "markdown", "id": "full-detail-30", "metadata": {}, "source": [ "### Full-shape momentum-term payloads\n", "\n", "Each term below is either a full matrix payload or a field payload. The raw panels keep complete shape metadata; histograms consume all coefficients or field values.\n" ] }, { "cell_type": "code", "execution_count": 16, "id": "full-detail-31", "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "#### Momentum term `assemble_momentum_ddt` — `fvm::ddt(U)`" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
term assemble_momentum_ddt.diag — shape=(283400,) (283,400), dtype=float64, size=283,400, TRUNCATED value text; exact shape retained; size=283,400; threshold=256; edgeitems=4
[0. 0. 0. 0. ... 0. 0. 0. 0.]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
term assemble_momentum_ddt.source — shape=(283400, 3) (283,400 × 3), dtype=float64, size=850,200, TRUNCATED value text; exact shape retained; size=850,200; threshold=256; edgeitems=4
[[0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " ...\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " term assemble_momentum_ddt.diag coefficient distribution\n", " n=283,400 finite, min=-0.5, max=0.5, mean=0, std=0\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " term assemble_momentum_ddt.source distribution\n", " n=850,200 finite, min=-0.5, max=0.5, mean=0, std=0\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "#### Momentum term `assemble_momentum_div_phi_U` — `fvm::div(phi,U)`" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
term assemble_momentum_div_phi_U.diag — shape=(283400,) (283,400), dtype=float64, size=283,400, TRUNCATED value text; exact shape retained; size=283,400; threshold=256; edgeitems=4
[1.87089263e-01 1.82963849e-01 1.70467373e-01 1.58836418e-01 ... 3.78668061e+02 3.73605332e+02 3.99969387e+02 4.36376495e+02]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
term assemble_momentum_div_phi_U.source — shape=(283400, 3) (283,400 × 3), dtype=float64, size=850,200, TRUNCATED value text; exact shape retained; size=850,200; threshold=256; edgeitems=4
[[-7.05168044e-13 -4.34221328e-15  0.00000000e+00]\n",
       " [-1.27219059e-13 -4.18512726e-14  0.00000000e+00]\n",
       " [ 6.58276063e-13  4.17470685e-14  0.00000000e+00]\n",
       " [ 4.47464866e-14  3.70092311e-14  0.00000000e+00]\n",
       " ...\n",
       " [ 2.60823561e-12 -1.30219992e-13  0.00000000e+00]\n",
       " [ 2.62017246e-13  2.56321219e-14  0.00000000e+00]\n",
       " [ 1.78859210e-12  1.15501967e-13  0.00000000e+00]\n",
       " [ 6.98628865e-13 -1.22370729e-13  0.00000000e+00]]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
term assemble_momentum_div_phi_U.upper — shape=(565419,) (565,419), dtype=float64, size=565,419, TRUNCATED value text; exact shape retained; size=565,419; threshold=256; edgeitems=4
[-8.92787718e-03 -1.87462300e-01  0.00000000e+00 -8.57344168e-03 ... -4.08794233e+01 -9.75618397e+00  0.00000000e+00  0.00000000e+00]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
term assemble_momentum_div_phi_U.lower — shape=(565419,) (565,419), dtype=float64, size=565,419, TRUNCATED value text; exact shape retained; size=565,419; threshold=256; edgeitems=4
[  0.           0.          -0.18708926   0.         ...   0.           0.         -21.76409186 -53.67576057]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " term assemble_momentum_div_phi_U.diag coefficient distribution\n", " n=283,400 finite, min=-7.43849e-15, max=1639.56, mean=82.3761, std=224.185\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " term assemble_momentum_div_phi_U.source distribution\n", " n=850,200 finite, min=-2.83813, max=2.83813, mean=1.29282e-19, std=0.0453185\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " term assemble_momentum_div_phi_U.upper coefficient distribution\n", " n=565,419 finite, min=-1631.95, max=0, mean=-41.2645, std=161.165\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " term assemble_momentum_div_phi_U.lower coefficient distribution\n", " n=565,419 finite, min=-53.6758, max=0, mean=-0.0240981, std=0.278605\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "#### Momentum term `assemble_momentum_divDevSigma` — `momentumTransport->divDevSigma(U)`" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
term assemble_momentum_divDevSigma.diag — shape=(283400,) (283,400), dtype=float64, size=283,400, TRUNCATED value text; exact shape retained; size=283,400; threshold=256; edgeitems=4
[4.38031234e+00 4.24314830e+00 4.10839121e+00 3.97601895e+00 ... 1.17597557e-04 1.16564118e-04 1.15567290e-04 1.14997131e-04]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
term assemble_momentum_divDevSigma.source — shape=(283400, 3) (283,400 × 3), dtype=float64, size=850,200, TRUNCATED value text; exact shape retained; size=850,200; threshold=256; edgeitems=4
[[ 3.44131100e-18 -4.72770839e-16  4.64136331e-19]\n",
       " [-6.78603060e-19 -2.03523451e-17 -4.88457210e-19]\n",
       " [ 5.18358783e-17  3.43732527e-16 -9.50705393e-19]\n",
       " [-1.83101493e-17  5.34586731e-16  5.21049275e-19]\n",
       " ...\n",
       " [-3.00758949e-20  2.36330044e-20 -3.69113144e-20]\n",
       " [-4.27221240e-20  1.11161689e-19  6.85057839e-20]\n",
       " [ 1.85703851e-20 -3.45231393e-19 -1.88776643e-22]\n",
       " [ 9.30099529e-21  1.46540804e-19 -1.26750121e-21]]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
term assemble_momentum_divDevSigma.upper — shape=(565419,) (565,419), dtype=float64, size=565,419, TRUNCATED value text; exact shape retained; size=565,419; threshold=256; edgeitems=4
[-1.11501221e-10 -2.16066526e+00 -2.21964708e+00 -1.14931800e-10 ... -5.69477417e-05 -5.64008384e-05 -5.58727140e-05 -5.53628040e-05]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " term assemble_momentum_divDevSigma.diag coefficient distribution\n", " n=283,400 finite, min=4.22094e-05, max=4.38031, mean=0.0174517, std=0.138364\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " term assemble_momentum_divDevSigma.source distribution\n", " n=850,200 finite, min=-0.23184, max=0.10648, mean=-0.000101892, std=0.00384959\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " term assemble_momentum_divDevSigma.upper coefficient distribution\n", " n=565,419 finite, min=-2.21965, max=-1.11501e-10, mean=-0.00437359, std=0.0491913\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "#### Momentum term `assemble_momentum_sources` — `fvModels().source(U)`" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
term assemble_momentum_sources.diag — shape=(283400,) (283,400), dtype=float64, size=283,400, TRUNCATED value text; exact shape retained; size=283,400; threshold=256; edgeitems=4
[0. 0. 0. 0. ... 0. 0. 0. 0.]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
term assemble_momentum_sources.source — shape=(283400, 3) (283,400 × 3), dtype=float64, size=850,200, TRUNCATED value text; exact shape retained; size=850,200; threshold=256; edgeitems=4
[[0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " ...\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " term assemble_momentum_sources.diag coefficient distribution\n", " n=283,400 finite, min=-0.5, max=0.5, mean=0, std=0\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " term assemble_momentum_sources.source distribution\n", " n=850,200 finite, min=-0.5, max=0.5, mean=0, std=0\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "#### Momentum term `assemble_momentum_MRF_DDt` — `MRF.DDt(U)`" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
MRFZoneList:DDt.internal — shape=(283400, 3) (283,400 × 3), dtype=float64, size=850,200, TRUNCATED value text; exact shape retained; size=850,200; threshold=256; edgeitems=4
[[0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " ...\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " MRFZoneList:DDt component 0 internal values\n", " n=283,400 finite, min=-0.5, max=0.5, mean=0, std=0\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " MRFZoneList:DDt component 1 internal values\n", " n=283,400 finite, min=-0.5, max=0.5, mean=0, std=0\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " MRFZoneList:DDt component 2 internal values\n", " n=283,400 finite, min=-0.5, max=0.5, mean=0, std=0\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " MRFZoneList:DDt internal vector/tensor magnitude\n", " n=283,400 finite, min=-0.5, max=0.5, mean=0, std=0\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " term assemble_momentum_MRF_DDt: field mapped on cell centres\n", " \n", " \n", " points=283,400; bins=96×96; color=bin mean; occupied bins=4,847\n", " x=[-193.117, 197.702], y=[-194.298, 195.038]\n", " raw color range=[0, 0]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "#### Momentum term `assemble_momentum_pressure_rhs_grad_p` — `-fvc::grad(p)`" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
grad(p).internal — shape=(283400, 3) (283,400 × 3), dtype=float64, size=850,200, TRUNCATED value text; exact shape retained; size=850,200; threshold=256; edgeitems=4
[[0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " ...\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]\n",
       " [0. 0. 0.]]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " grad(p) component 0 internal values\n", " n=283,400 finite, min=-0.5, max=0.5, mean=0, std=0\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " grad(p) component 1 internal values\n", " n=283,400 finite, min=-0.5, max=0.5, mean=0, std=0\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " grad(p) component 2 internal values\n", " n=283,400 finite, min=-0.5, max=0.5, mean=0, std=0\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " grad(p) internal vector/tensor magnitude\n", " n=283,400 finite, min=-0.5, max=0.5, mean=0, std=0\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " term assemble_momentum_pressure_rhs_grad_p: field mapped on cell centres\n", " \n", " \n", " points=283,400; bins=96×96; color=bin mean; occupied bins=4,847\n", " x=[-193.117, 197.702], y=[-194.298, 195.038]\n", " raw color range=[0, 0]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "for term in terms:\n", " display(Markdown(f\"#### Momentum term `{term['name']}` — `{term['kind']}`\"))\n", " if \"matrix\" in term:\n", " show_matrix_arrays(f\"term {term['name']}\", term[\"matrix\"], open_arrays=False)\n", " else:\n", " field = term[\"field\"]\n", " show_field_histograms(field)\n", " if np.asarray(field.internal).shape[0] == mesh.n_cells:\n", " show_cell_field_map(mesh, field, title=f\"term {term['name']}: field mapped on cell centres\")\n", " elif np.asarray(field.internal).shape[0] == mesh.n_internal_faces:\n", " show_face_field_map(mesh, field, title=f\"term {term['name']}: field mapped on internal face centres\")\n" ] }, { "cell_type": "markdown", "id": "full-detail-32", "metadata": {}, "source": [ "## 8. Assemble, relax, and constrain `UEqn`\n", "\n", "`UEqn` is a `MatrixView` over an OpenFOAM `fvVectorMatrix`:\n", "\n", "- `diag`: one diagonal coefficient per cell;\n", "- `upper`/`lower`: off-diagonal coefficients for internal owner-neighbour faces;\n", "- `source`: vector right-hand side, one 3-vector per cell;\n", "- boundary coefficient lists carry patch contributions.\n", "\n", "Relaxation and constraints mutate the matrix before the linear solver sees it.\n" ] }, { "cell_type": "code", "execution_count": 17, "id": "full-detail-33", "metadata": { "execution": { "iopub.execute_input": "2026-07-22T20:00:19.706933Z", "iopub.status.busy": "2026-07-22T20:00:19.706754Z", "iopub.status.idle": "2026-07-22T20:00:20.047584Z", "shell.execute_reply": "2026-07-22T20:00:20.046319Z" } }, "outputs": [ { "data": { "text/markdown": [ "**TransformResult: assemble_UEqn**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| name | phase | changed | inputs | outputs | source |\n", "| --- | --- | --- | --- | --- | --- |\n", "| assemble_UEqn | momentum | — | p, U, phi, nut, k, omega | UEqn | OpenFOAM-14/applications/modules/incompressibleFluid/momentumPredictor.C:36-44::momentumPredictor |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**TransformResult: relax_UEqn**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| name | phase | changed | inputs | outputs | source |\n", "| --- | --- | --- | --- | --- | --- |\n", "| relax_UEqn | momentum | — | — | UEqn | native wrapper / OpenFOAM runtime |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**TransformResult: constrain_UEqn**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| name | phase | changed | inputs | outputs | source |\n", "| --- | --- | --- | --- | --- | --- |\n", "| constrain_UEqn | momentum | — | — | UEqn | native wrapper / OpenFOAM runtime |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**UEqn MatrixView summaries**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| label | field | rank | dimensions | diag shape | diag min | diag max | source shape | source l2 | upper | lower | diagonal | symmetric | asymmetric |\n", "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n", "| assembled | U | vector | [0 4 -2 0 0 0 0] | (283400,) | 4.221e-05 | 1640 | (283400, 3) | 42.07 | (565419,) | (565419,) | False | False | True |\n", "| relaxed | U | vector | [0 4 -2 0 0 0 0] | (283400,) | 0.0006976 | 1829 | (283400, 3) | 1.329e+06 | (565419,) | (565419,) | False | False | True |\n", "| constrained | U | vector | [0 4 -2 0 0 0 0] | (283400,) | 0.0006976 | 1829 | (283400, 3) | 1.329e+06 | (565419,) | (565419,) | False | False | True |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**How relaxation/constraints changed UEqn**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| change | diag max_abs | diag l2 | source max_abs | source l2 |\n", "| --- | --- | --- | --- | --- |\n", "| relaxed - assembled | 199.4 | 1.426e+04 | 1.855e+04 | 1.329e+06 |\n", "| constrained - relaxed | 0 | 0 | 0 | 0 |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "UEqn_assembled_result = record(stepper.assemble_momentum_matrix())\n", "UEqn_assembled = UEqn_assembled_result.outputs[\"UEqn\"]\n", "UEqn_relaxed_result = record(stepper.relax_matrix())\n", "UEqn_relaxed = UEqn_relaxed_result.outputs[\"UEqn\"]\n", "UEqn_constrained_result = record(stepper.constrain_matrix())\n", "UEqn_constrained = UEqn_constrained_result.outputs[\"UEqn\"]\n", "\n", "show_table(\n", " \"UEqn MatrixView summaries\",\n", " [\"label\", \"field\", \"rank\", \"dimensions\", \"diag shape\", \"diag min\", \"diag max\", \"source shape\", \"source l2\", \"upper\", \"lower\", \"diagonal\", \"symmetric\", \"asymmetric\"],\n", " [\n", " matrix_summary_row(\"assembled\", UEqn_assembled),\n", " matrix_summary_row(\"relaxed\", UEqn_relaxed),\n", " matrix_summary_row(\"constrained\", UEqn_constrained),\n", " ],\n", ")\n", "show_table(\n", " \"How relaxation/constraints changed UEqn\",\n", " [\"change\", \"diag max_abs\", \"diag l2\", \"source max_abs\", \"source l2\"],\n", " [\n", " matrix_delta_row(\"relaxed - assembled\", UEqn_assembled, UEqn_relaxed),\n", " matrix_delta_row(\"constrained - relaxed\", UEqn_relaxed, UEqn_constrained),\n", " ],\n", ")\n" ] }, { "cell_type": "markdown", "id": "full-detail-34", "metadata": {}, "source": [ "### Full-shape `UEqn` coefficient arrays and sparse graph view\n", "\n", "The `UEqn` panels expose diagonal, source, and off-diagonal arrays before and after relaxation/constraints. The sparse-graph density plot bins the full owner-neighbour addressing used by `upper` and `lower` coefficients.\n" ] }, { "cell_type": "code", "execution_count": 18, "id": "full-detail-35", "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "#### UEqn assembled" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
UEqn assembled.diag — shape=(283400,) (283,400), dtype=float64, size=283,400, TRUNCATED value text; exact shape retained; size=283,400; threshold=256; edgeitems=4
[  4.5674016    4.42611215   4.27885859   4.13485536 ... 378.66817863 373.60544841 399.9695021  436.37660966]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
UEqn assembled.source — shape=(283400, 3) (283,400 × 3), dtype=float64, size=850,200, TRUNCATED value text; exact shape retained; size=850,200; threshold=256; edgeitems=4
[[-7.05164602e-13 -4.81498412e-15  4.64136331e-19]\n",
       " [-1.27219738e-13 -4.18716250e-14 -4.88457210e-19]\n",
       " [ 6.58327899e-13  4.20908010e-14 -9.50705393e-19]\n",
       " [ 4.47281764e-14  3.75438178e-14  5.21049275e-19]\n",
       " ...\n",
       " [ 2.60823558e-12 -1.30219968e-13 -3.69113144e-20]\n",
       " [ 2.62017204e-13  2.56322331e-14  6.85057839e-20]\n",
       " [ 1.78859212e-12  1.15501622e-13 -1.88776643e-22]\n",
       " [ 6.98628874e-13 -1.22370583e-13 -1.26750121e-21]]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
UEqn assembled.upper — shape=(565419,) (565,419), dtype=float64, size=565,419, TRUNCATED value text; exact shape retained; size=565,419; threshold=256; edgeitems=4
[-8.92787729e-03 -2.34812756e+00 -2.21964708e+00 -8.57344179e-03 ... -4.08794802e+01 -9.75624037e+00 -5.58727140e-05 -5.53628040e-05]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
UEqn assembled.lower — shape=(565419,) (565,419), dtype=float64, size=565,419, TRUNCATED value text; exact shape retained; size=565,419; threshold=256; edgeitems=4
[-1.11501221e-10 -2.16066526e+00 -2.40673634e+00 -1.14931800e-10 ... -5.69477417e-05 -5.64008384e-05 -2.17641477e+01 -5.36758159e+01]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " UEqn assembled.diag coefficient distribution\n", " n=283,400 finite, min=4.22094e-05, max=1639.56, mean=82.3935, std=224.179\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " UEqn assembled.source distribution\n", " n=850,200 finite, min=-2.84873, max=2.83447, mean=-0.000101892, std=0.0456304\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " UEqn assembled.upper coefficient distribution\n", " n=565,419 finite, min=-1631.95, max=-1.43895e-07, mean=-41.2689, std=161.164\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " UEqn assembled.lower coefficient distribution\n", " n=565,419 finite, min=-53.6758, max=-1.11501e-10, mean=-0.0284717, std=0.283\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "#### UEqn relaxed" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
UEqn relaxed.diag — shape=(283400,) (283,400), dtype=float64, size=283,400, TRUNCATED value text; exact shape retained; size=283,400; threshold=256; edgeitems=4
[  5.07593427   4.91790239   4.75428732   4.59428374 ... 420.7479423  415.15891126 444.65102908 485.47654639]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
UEqn relaxed.source — shape=(283400, 3) (283,400 × 3), dtype=float64, size=850,200, TRUNCATED value text; exact shape retained; size=850,200; threshold=256; edgeitems=4
[[ 4.72981890e+01  3.13325084e+00  4.64136331e-19]\n",
       " [ 4.57409897e+01  3.03009475e+00 -4.88457210e-19]\n",
       " [ 4.42192199e+01  2.92928568e+00 -9.50705393e-19]\n",
       " [ 4.27310402e+01  2.83070177e+00  5.21049275e-19]\n",
       " ...\n",
       " [ 3.91380284e+03  2.59268405e+02 -3.69113144e-20]\n",
       " [ 3.86485205e+03  2.56025678e+02  6.85057839e-20]\n",
       " [ 4.15579062e+03  2.75298795e+02 -1.88776643e-22]\n",
       " [ 4.56674314e+03  3.02522190e+02 -1.26750121e-21]]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
UEqn relaxed.upper — shape=(565419,) (565,419), dtype=float64, size=565,419, TRUNCATED value text; exact shape retained; size=565,419; threshold=256; edgeitems=4
[-8.92787729e-03 -2.34812756e+00 -2.21964708e+00 -8.57344179e-03 ... -4.08794802e+01 -9.75624037e+00 -5.58727140e-05 -5.53628040e-05]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
UEqn relaxed.lower — shape=(565419,) (565,419), dtype=float64, size=565,419, TRUNCATED value text; exact shape retained; size=565,419; threshold=256; edgeitems=4
[-1.11501221e-10 -2.16066526e+00 -2.40673634e+00 -1.14931800e-10 ... -5.69477417e-05 -5.64008384e-05 -2.17641477e+01 -5.36758159e+01]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " UEqn relaxed.diag coefficient distribution\n", " n=283,400 finite, min=0.000697607, max=1828.85, mean=91.5997, std=249.292\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " UEqn relaxed.source distribution\n", " n=850,200 finite, min=-1.62897, max=18549.2, mean=304.325, std=1408.6\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " UEqn relaxed.upper coefficient distribution\n", " n=565,419 finite, min=-1631.95, max=-1.43895e-07, mean=-41.2689, std=161.164\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " UEqn relaxed.lower coefficient distribution\n", " n=565,419 finite, min=-53.6758, max=-1.11501e-10, mean=-0.0284717, std=0.283\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "#### UEqn constrained" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
UEqn constrained.diag — shape=(283400,) (283,400), dtype=float64, size=283,400, TRUNCATED value text; exact shape retained; size=283,400; threshold=256; edgeitems=4
[  5.07593427   4.91790239   4.75428732   4.59428374 ... 420.7479423  415.15891126 444.65102908 485.47654639]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
UEqn constrained.source — shape=(283400, 3) (283,400 × 3), dtype=float64, size=850,200, TRUNCATED value text; exact shape retained; size=850,200; threshold=256; edgeitems=4
[[ 4.72981890e+01  3.13325084e+00  4.64136331e-19]\n",
       " [ 4.57409897e+01  3.03009475e+00 -4.88457210e-19]\n",
       " [ 4.42192199e+01  2.92928568e+00 -9.50705393e-19]\n",
       " [ 4.27310402e+01  2.83070177e+00  5.21049275e-19]\n",
       " ...\n",
       " [ 3.91380284e+03  2.59268405e+02 -3.69113144e-20]\n",
       " [ 3.86485205e+03  2.56025678e+02  6.85057839e-20]\n",
       " [ 4.15579062e+03  2.75298795e+02 -1.88776643e-22]\n",
       " [ 4.56674314e+03  3.02522190e+02 -1.26750121e-21]]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
UEqn constrained.upper — shape=(565419,) (565,419), dtype=float64, size=565,419, TRUNCATED value text; exact shape retained; size=565,419; threshold=256; edgeitems=4
[-8.92787729e-03 -2.34812756e+00 -2.21964708e+00 -8.57344179e-03 ... -4.08794802e+01 -9.75624037e+00 -5.58727140e-05 -5.53628040e-05]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
UEqn constrained.lower — shape=(565419,) (565,419), dtype=float64, size=565,419, TRUNCATED value text; exact shape retained; size=565,419; threshold=256; edgeitems=4
[-1.11501221e-10 -2.16066526e+00 -2.40673634e+00 -1.14931800e-10 ... -5.69477417e-05 -5.64008384e-05 -2.17641477e+01 -5.36758159e+01]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " UEqn constrained.diag coefficient distribution\n", " n=283,400 finite, min=0.000697607, max=1828.85, mean=91.5997, std=249.292\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " UEqn constrained.source distribution\n", " n=850,200 finite, min=-1.62897, max=18549.2, mean=304.325, std=1408.6\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " UEqn constrained.upper coefficient distribution\n", " n=565,419 finite, min=-1631.95, max=-1.43895e-07, mean=-41.2689, std=161.164\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " UEqn constrained.lower coefficient distribution\n", " n=565,419 finite, min=-53.6758, max=-1.11501e-10, mean=-0.0284717, std=0.283\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " UEqn uses this complete owner-neighbour sparse graph\n", " \n", " \n", " points=565,419; bins=96×96; color=log1p(count); occupied bins=360\n", " x=[0, 283398], y=[1, 283399]\n", " raw color range=[1, 5749]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "for label, matrix in [\n", " (\"UEqn assembled\", UEqn_assembled),\n", " (\"UEqn relaxed\", UEqn_relaxed),\n", " (\"UEqn constrained\", UEqn_constrained),\n", "]:\n", " display(Markdown(f\"#### {label}\"))\n", " show_matrix_arrays(label, matrix, open_arrays=False)\n", "show_ldu_density(mesh, \"UEqn uses this complete owner-neighbour sparse graph\")\n" ] }, { "cell_type": "markdown", "id": "full-detail-36", "metadata": {}, "source": [ "## 9. Solve the momentum predictor\n", "\n", "The velocity solve updates `U` but it does not by itself guarantee continuity. It is a momentum prediction using the current pressure field. The following pressure correction will adjust `p`, `phi`, and `U` to enforce mass conservation more tightly.\n" ] }, { "cell_type": "code", "execution_count": 19, "id": "full-detail-37", "metadata": { "execution": { "iopub.execute_input": "2026-07-22T20:00:20.066676Z", "iopub.status.busy": "2026-07-22T20:00:20.066493Z", "iopub.status.idle": "2026-07-22T20:00:20.363147Z", "shell.execute_reply": "2026-07-22T20:00:20.362713Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "DILUPBiCGStab: Solving for Ux, Initial residual = 0.99999991606780358, Final residual = 6.4677780734704781e-09, No Iterations 11\n", "DILUPBiCGStab: Solving for Uy, Initial residual = 0.99999968448032772, Final residual = 7.4359526559359641e-09, No Iterations 11\n" ] }, { "data": { "text/markdown": [ "**TransformResult: solve_UEqn**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| name | phase | changed | inputs | outputs | source |\n", "| --- | --- | --- | --- | --- | --- |\n", "| solve_UEqn | momentum | U | U, UEqn | performance, matrix_before, field_before, field_after | OpenFOAM-14/applications/modules/incompressibleFluid/momentumPredictor.C:50-55::momentumPredictor |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**Momentum linear-solver performance**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| field | solver | initial residual | final residual | iterations | converged | singular |\n", "| --- | --- | --- | --- | --- | --- | --- |\n", "| U | DILUPBiCGStab | (0.9999999160678036, 0.9999996844803277, 0.0) | (6.467778073470478e-09, 7.435952655935964e-09, 0.0) | (11, 11, 0) | False | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**Field deltas caused by solve_UEqn**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| field | shape | min Δ | max Δ | mean Δ | l2 Δ | max \\|Δ\\| |\n", "| --- | --- | --- | --- | --- | --- | --- |\n", "| U | (283400, 3) | -107.7 | 1.559 | -0.2652 | 2940 | 107.7 |\n", "| p | (283400,) | 0 | 0 | 0 | 0 | 0 |\n", "| phi | (565419,) | 0 | 0 | 0 | 0 | 0 |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "pre_momentum_snapshot = field_snapshot(stepper.fields(), names=(\"U\", \"p\", \"phi\"))\n", "solve_U_result = record(stepper.solve_momentum())\n", "post_momentum_snapshot = field_snapshot(stepper.fields(), names=(\"U\", \"p\", \"phi\"))\n", "\n", "show_table(\n", " \"Momentum linear-solver performance\",\n", " [\"field\", \"solver\", \"initial residual\", \"final residual\", \"iterations\", \"converged\", \"singular\"],\n", " [solve_result_row(solve_U_result.outputs[\"performance\"])],\n", ")\n", "show_table(\n", " \"Field deltas caused by solve_UEqn\",\n", " [\"field\", \"shape\", \"min Δ\", \"max Δ\", \"mean Δ\", \"l2 Δ\", \"max |Δ|\"],\n", " delta_rows(pre_momentum_snapshot, post_momentum_snapshot, names=(\"U\", \"p\", \"phi\")),\n", ")\n" ] }, { "cell_type": "markdown", "id": "full-detail-38", "metadata": {}, "source": [ "## 10. Pressure-correction inputs\n", "\n", "SIMPLE pressure correction uses quantities derived from the momentum matrix:\n", "\n", "- `rAU`: reciprocal diagonal of the velocity matrix;\n", "- `HbyA`: non-pressure part of the predicted velocity;\n", "- `phiHbyA`: face flux implied by `HbyA`;\n", "- `rAtU`: SIMPLEC-consistent reciprocal coefficient.\n", "\n", "These are fields, not just temporary scalars, so we can inspect their shapes and magnitudes.\n" ] }, { "cell_type": "code", "execution_count": 20, "id": "full-detail-39", "metadata": { "execution": { "iopub.execute_input": "2026-07-22T20:00:20.371643Z", "iopub.status.busy": "2026-07-22T20:00:20.371435Z", "iopub.status.idle": "2026-07-22T20:00:20.492873Z", "shell.execute_reply": "2026-07-22T20:00:20.491123Z" } }, "outputs": [ { "data": { "text/markdown": [ "**TransformResult: compute_pressure_inputs**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| name | phase | changed | inputs | outputs | source |\n", "| --- | --- | --- | --- | --- | --- |\n", "| compute_pressure_inputs | pressure | — | — | rAU, HbyA, phiHbyA, rAtU, adjusted_phi_for_pressure_reference, consistent, source | OpenFOAM-14/applications/modules/incompressibleFluid/correctPressure.C:47-81::Foam::solvers::incompressibleFluid::correctPressure |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**Pressure-input fields**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| name | kind | dimensions | entity | count | shape | min | max | mean |\n", "| --- | --- | --- | --- | --- | --- | --- | --- | --- |\n", "| (1\\|A(U)) | volScalar | [0 0 1 0 0 0 0] | cell | 283400 | (283400,) | 1.388e-08 | 0.1278 | 0.002038 |\n", "| HbyA | volVector | [0 1 -1 0 0 0 0] | cell | 283400 | (283400, 3) | -14.68 | 93.01 | 32.79 |\n", "| phiHbyA | surfaceScalar | [0 3 -1 0 0 0 0] | internal_face | 565419 | (565419,) | -1632 | 53.68 | -41.24 |\n", "| (1\\|max(((1\\|(1\\|A(U)))-H(1)),(0.1\\|(1\\|A(U))))) | volScalar | [0 0 1 0 0 0 0] | cell | 283400 | (283400,) | 6.007e-08 | 1.191 | 0.02022 |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**Pressure-input switches**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| key | value | meaning |\n", "| --- | --- | --- |\n", "| consistent | True | SIMPLEC correction path is active |\n", "| adjusted_phi_for_pressure_reference | False | whether flux was adjusted for pressure reference |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "pressure_inputs_result = record(stepper.compute_pressure_inputs())\n", "pressure_inputs = pressure_inputs_result.outputs\n", "pressure_input_fields = [key for key in (\"rAU\", \"HbyA\", \"phiHbyA\", \"rAtU\") if key in pressure_inputs]\n", "show_table(\n", " \"Pressure-input fields\",\n", " [\"name\", \"kind\", \"dimensions\", \"entity\", \"count\", \"shape\", \"min\", \"max\", \"mean\"],\n", " [field_summary_row(pressure_inputs[name]) for name in pressure_input_fields],\n", ")\n", "show_table(\n", " \"Pressure-input switches\",\n", " [\"key\", \"value\", \"meaning\"],\n", " [\n", " [\"consistent\", pressure_inputs.get(\"consistent\"), \"SIMPLEC correction path is active\"],\n", " [\"adjusted_phi_for_pressure_reference\", pressure_inputs.get(\"adjusted_phi_for_pressure_reference\"), \"whether flux was adjusted for pressure reference\"],\n", " ],\n", ")\n" ] }, { "cell_type": "markdown", "id": "full-detail-40", "metadata": {}, "source": [ "### Full-shape pressure-input arrays and maps\n", "\n", "`rAU`, `HbyA`, `phiHbyA`, and `rAtU` are full OpenFOAM fields. The plots below show their distributions and geometry-aligned binned maps.\n" ] }, { "cell_type": "code", "execution_count": 21, "id": "full-detail-41", "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "#### Pressure input `rAU`" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
(1|A(U)).internal — shape=(283400,) (283,400), dtype=float64, size=283,400, TRUNCATED value text; exact shape retained; size=283,400; threshold=256; edgeitems=4
[0.00026756 0.00024709 0.00022836 0.00021125 ... 0.12406799 0.12781799 0.12135996 0.11307441]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " (1|A(U)) internal values\n", " n=283,400 finite, min=1.38844e-08, max=0.127818, mean=0.00203795, std=0.00809275\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " rAU: complete pressure input mapped on cell centres\n", " \n", " \n", " points=283,400; bins=96×96; color=bin mean; occupied bins=4,847\n", " x=[-193.117, 197.702], y=[-194.298, 195.038]\n", " raw color range=[2.86833e-05, 0.127818]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "#### Pressure input `HbyA`" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
HbyA.internal — shape=(283400, 3) (283,400 × 3), dtype=float64, size=850,200, TRUNCATED value text; exact shape retained; size=850,200; threshold=256; edgeitems=4
[[93.00914504  6.16135601  0.        ]\n",
       " [93.00914504  6.16135601  0.        ]\n",
       " [93.00914504  6.16135601  0.        ]\n",
       " [93.00914504  6.16135601  0.        ]\n",
       " ...\n",
       " [93.00914504  6.16135601  0.        ]\n",
       " [93.00914504  6.16135601  0.        ]\n",
       " [93.00914504  6.16135601  0.        ]\n",
       " [93.00914504  6.16135601  0.        ]]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " HbyA component 0 internal values\n", " n=283,400 finite, min=-14.6831, max=93.0091, mean=92.2819, std=5.44834\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " HbyA component 1 internal values\n", " n=283,400 finite, min=-4.63575, max=7.72001, mean=6.09312, std=0.534427\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " HbyA component 2 internal values\n", " n=283,400 finite, min=-0.5, max=0.5, mean=0, std=0\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " HbyA internal vector/tensor magnitude\n", " n=283,400 finite, min=2.35378, max=93.213, mean=92.4988, std=5.19794\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " HbyA: complete pressure input mapped on cell centres\n", " \n", " \n", " points=283,400; bins=96×96; color=bin mean; occupied bins=4,847\n", " x=[-193.117, 197.702], y=[-194.298, 195.038]\n", " raw color range=[92.0268, 93.213]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "#### Pressure input `phiHbyA`" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
phiHbyA.internal — shape=(565419,) (565,419), dtype=float64, size=565,419, TRUNCATED value text; exact shape retained; size=565,419; threshold=256; edgeitems=4
[-8.92787718e-03 -1.87462300e-01  1.87089263e-01 -8.57344168e-03 ... -4.08794233e+01 -9.75618397e+00  2.17640919e+01  5.36757606e+01]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " phiHbyA internal values\n", " n=565,419 finite, min=-1631.95, max=53.6758, mean=-41.2405, std=161.171\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " phiHbyA: complete pressure input mapped on internal face centres\n", " \n", " \n", " points=565,419; bins=96×96; color=bin mean; occupied bins=7,291\n", " x=[-193.023, 197.582], y=[-194.361, 195.068]\n", " raw color range=[-1631.79, 53.6758]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "#### Pressure input `rAtU`" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
(1|max(((1|(1|A(U)))-H(1)),(0.1|(1|A(U))))).internal — shape=(283400,) (283,400), dtype=float64, size=283,400, TRUNCATED value text; exact shape retained; size=283,400; threshold=256; edgeitems=4
[0.00267559 0.00247088 0.00228365 0.00211253 ... 0.12701319 0.12781802 0.12760753 0.12713983]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " (1|max(((1|(1|A(U)))-H(1)),(0.1|(1|A(U))))) internal values\n", " n=283,400 finite, min=6.00735e-08, max=1.19133, mean=0.0202164, std=0.0802763\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " rAtU: complete pressure input mapped on cell centres\n", " \n", " \n", " points=283,400; bins=96×96; color=bin mean; occupied bins=4,847\n", " x=[-193.117, 197.702], y=[-194.298, 195.038]\n", " raw color range=[0.000286831, 1.19133]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "for name in pressure_input_fields:\n", " field = pressure_inputs[name]\n", " display(Markdown(f\"#### Pressure input `{name}`\"))\n", " show_field_histograms(field)\n", " if np.asarray(field.internal).shape[0] == mesh.n_cells:\n", " show_cell_field_map(mesh, field, title=f\"{name}: complete pressure input mapped on cell centres\")\n", " elif np.asarray(field.internal).shape[0] == mesh.n_internal_faces:\n", " show_face_field_map(mesh, field, title=f\"{name}: complete pressure input mapped on internal face centres\")\n" ] }, { "cell_type": "markdown", "id": "full-detail-42", "metadata": {}, "source": [ "## 11. Assemble and solve `pEqn`\n", "\n", "The pressure equation is a scalar sparse matrix. Solving it updates pressure and the face flux `phi`. In this case `nNonOrthogonalCorrectors = 3`, so OpenFOAM performs multiple pressure solves inside `solve_pressure()`; the returned `SolveResult` is the final one.\n" ] }, { "cell_type": "code", "execution_count": 22, "id": "full-detail-43", "metadata": { "execution": { "iopub.execute_input": "2026-07-22T20:00:20.500348Z", "iopub.status.busy": "2026-07-22T20:00:20.500108Z", "iopub.status.idle": "2026-07-22T20:00:23.601973Z", "shell.execute_reply": "2026-07-22T20:00:23.601600Z" } }, "outputs": [ { "data": { "text/markdown": [ "**TransformResult: assemble_pEqn**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| name | phase | changed | inputs | outputs | source |\n", "| --- | --- | --- | --- | --- | --- |\n", "| assemble_pEqn | pressure | — | — | pEqn | OpenFOAM-14/applications/modules/incompressibleFluid/correctPressure.C:83-101::correctPressure |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**pEqn MatrixView summary**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| label | field | rank | dimensions | diag shape | diag min | diag max | source shape | source l2 | upper | lower | diagonal | symmetric | asymmetric |\n", "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n", "| assembled | p | scalar | [0 3 -1 0 0 0 0] | (283400,) | -8244 | -4.016e-07 | (283400,) | 0.4615 | (565419,) | | False | True | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "DICPCG: Solving for p, Initial residual = 1, Final residual = 0.030890897351242643, No Iterations 300\n" ] }, { "data": { "text/markdown": [ "**TransformResult: solve_pEqn**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "DICPCG: Solving for p, Initial residual = 7.2784937401803668e-05, Final residual = 9.4967147297221812e-07, No Iterations 199\n", "DICPCG: Solving for p, Initial residual = 1.9461086386417007e-05, Final residual = 9.8677378665869984e-07, No Iterations 160\n", "DICPCG: Solving for p, Initial residual = 8.7397651610059266e-06, Final residual = 9.9577380572382355e-07, No Iterations 125\n" ] }, { "data": { "text/markdown": [ "| name | phase | changed | inputs | outputs | source |\n", "| --- | --- | --- | --- | --- | --- |\n", "| solve_pEqn | pressure | p, phi | p, pEqn | performance, matrix_before, field_before, p, phi | OpenFOAM-14/applications/modules/incompressibleFluid/correctPressure.C:103-108::correctPressure |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**Pressure linear-solver performance, final non-orthogonal solve**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| field | solver | initial residual | final residual | iterations | converged | singular |\n", "| --- | --- | --- | --- | --- | --- | --- |\n", "| p | DICPCG | 8.74e-06 | 9.958e-07 | 125 | True | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**Field deltas caused by solve_pEqn**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| field | shape | min Δ | max Δ | mean Δ | l2 Δ | max \\|Δ\\| |\n", "| --- | --- | --- | --- | --- | --- | --- |\n", "| U | (283400, 3) | 0 | 0 | 0 | 0 | 0 |\n", "| p | (283400,) | -2.074e+04 | 1.393e+05 | 7713 | 1.433e+07 | 1.393e+05 |\n", "| phi | (565419,) | -0.12 | 0.07011 | -0.001635 | 16.37 | 0.12 |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "pEqn_result = record(stepper.assemble_pressure_matrix())\n", "pEqn = pEqn_result.outputs[\"pEqn\"]\n", "show_table(\n", " \"pEqn MatrixView summary\",\n", " [\"label\", \"field\", \"rank\", \"dimensions\", \"diag shape\", \"diag min\", \"diag max\", \"source shape\", \"source l2\", \"upper\", \"lower\", \"diagonal\", \"symmetric\", \"asymmetric\"],\n", " [matrix_summary_row(\"assembled\", pEqn)],\n", ")\n", "\n", "pre_pressure_snapshot = field_snapshot(stepper.fields(), names=(\"U\", \"p\", \"phi\"))\n", "solve_p_result = record(stepper.solve_pressure())\n", "post_pressure_snapshot = field_snapshot(stepper.fields(), names=(\"U\", \"p\", \"phi\"))\n", "\n", "show_table(\n", " \"Pressure linear-solver performance, final non-orthogonal solve\",\n", " [\"field\", \"solver\", \"initial residual\", \"final residual\", \"iterations\", \"converged\", \"singular\"],\n", " [solve_result_row(solve_p_result.outputs[\"performance\"])],\n", ")\n", "show_table(\n", " \"Field deltas caused by solve_pEqn\",\n", " [\"field\", \"shape\", \"min Δ\", \"max Δ\", \"mean Δ\", \"l2 Δ\", \"max |Δ|\"],\n", " delta_rows(pre_pressure_snapshot, post_pressure_snapshot, names=(\"U\", \"p\", \"phi\")),\n", ")\n" ] }, { "cell_type": "markdown", "id": "full-detail-44", "metadata": {}, "source": [ "### Full-shape `pEqn` arrays\n", "\n", "The pressure equation is scalar, but its diagonal/off-diagonal/source arrays still span the full cell graph.\n" ] }, { "cell_type": "code", "execution_count": 23, "id": "full-detail-45", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
pEqn assembled.diag — shape=(283400,) (283,400), dtype=float64, size=283,400, TRUNCATED value text; exact shape retained; size=283,400; threshold=256; edgeitems=4
[-768.51401632 -687.69317782 -615.60662007 -551.29512049 ...   -1.10414391   -1.10656942   -1.09019334   -1.07202383]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
pEqn assembled.source — shape=(283400,) (283,400), dtype=float64, size=283,400, TRUNCATED value text; exact shape retained; size=283,400; threshold=256; edgeitems=4
[ 1.02180417e-13 -7.38853423e-14 -5.16253706e-15  2.05391260e-15 ...  0.00000000e+00  0.00000000e+00  0.00000000e+00  0.00000000e+00]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
pEqn assembled.upper — shape=(565419,) (565,419), dtype=float64, size=565,419, TRUNCATED value text; exact shape retained; size=565,419; threshold=256; edgeitems=4
[1.83571656e-08 3.87796037e+02 3.80717980e+02 1.74812696e-08 ... 4.60832668e-01 4.60561132e-01 4.57325218e-01 4.51951588e-01]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " pEqn assembled.diag coefficient distribution\n", " n=283,400 finite, min=-8243.87, max=-4.01646e-07, mean=-25.5789, std=238.124\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " pEqn assembled.source distribution\n", " n=283,400 finite, min=-0.031998, max=0.0169861, mean=1.13639e-17, std=0.000866946\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " pEqn assembled.upper coefficient distribution\n", " n=565,419 finite, min=3.33494e-11, max=4122.53, mean=6.41034, std=84.5465\n", " x=value, y=log1p(count), bins=90\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " pEqn uses this complete owner-neighbour sparse graph\n", " \n", " \n", " points=565,419; bins=96×96; color=log1p(count); occupied bins=360\n", " x=[0, 283398], y=[1, 283399]\n", " raw color range=[1, 5749]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " |ΔU| spatial distribution\n", " \n", " \n", " points=283,400; bins=96×96; color=bin mean |Δ|; occupied bins=4,847\n", " x=[-193.117, 197.702], y=[-194.298, 195.038]\n", " raw color range=[0, 0]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " |Δp| spatial distribution\n", " \n", " \n", " points=283,400; bins=96×96; color=bin mean |Δ|; occupied bins=4,847\n", " x=[-193.117, 197.702], y=[-194.298, 195.038]\n", " raw color range=[0.000362124, 18846.4]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " |Δphi| spatial distribution\n", " \n", " \n", " points=565,419; bins=96×96; color=bin mean |Δ|; occupied bins=7,291\n", " x=[-193.023, 197.582], y=[-194.361, 195.068]\n", " raw color range=[7.31074e-07, 0.0709497]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "show_matrix_arrays(\"pEqn assembled\", pEqn, open_arrays=False)\n", "show_ldu_density(mesh, \"pEqn uses this complete owner-neighbour sparse graph\")\n", "for name in (\"U\", \"p\", \"phi\"):\n", " show_delta_map(mesh, name, pre_pressure_snapshot, post_pressure_snapshot)\n" ] }, { "cell_type": "markdown", "id": "full-detail-46", "metadata": {}, "source": [ "## 12. Correct velocity, pressure, and flux\n", "\n", "After solving pressure, OpenFOAM performs the final pressure/velocity/flux correction. This is where `U`, `p`, and `phi` are made consistent with the pressure equation products.\n" ] }, { "cell_type": "code", "execution_count": 24, "id": "full-detail-47", "metadata": { "execution": { "iopub.execute_input": "2026-07-22T20:00:23.608612Z", "iopub.status.busy": "2026-07-22T20:00:23.608438Z", "iopub.status.idle": "2026-07-22T20:00:23.673668Z", "shell.execute_reply": "2026-07-22T20:00:23.672454Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "time step continuity errors : sum local = 7.4676177118375265e-06, global = 9.9407679212193635e-07\n" ] }, { "data": { "text/markdown": [ "**TransformResult: correct_velocity_pressure_flux**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| name | phase | changed | inputs | outputs | source |\n", "| --- | --- | --- | --- | --- | --- |\n", "| correct_velocity_pressure_flux | pressure | p, U, phi | — | p, U, phi, source | native wrapper / OpenFOAM runtime |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**Field deltas caused by correct_velocity_pressure_flux**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| field | shape | min Δ | max Δ | mean Δ | l2 Δ | max \\|Δ\\| |\n", "| --- | --- | --- | --- | --- | --- | --- |\n", "| U | (283400, 3) | -142.8 | 59.18 | -2.593 | 1.342e+04 | 142.8 |\n", "| p | (283400,) | 0 | 0 | 0 | 0 | 0 |\n", "| phi | (565419,) | 0 | 0 | 0 | 0 | 0 |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**Boundary flux after correction**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| patch | phi BC type | shape | sum(phi) | max \\|phi\\| |\n", "| --- | --- | --- | --- | --- |\n", "| aerofoil | calculated | (1026,) | 0 | 0 |\n", "| freestream | calculated | (1736,) | 0.142 | 1034 |\n", "| frontAndBack | empty | (0,) | 0 | 0 |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "pre_correction_snapshot = field_snapshot(stepper.fields(), names=(\"U\", \"p\", \"phi\"))\n", "correction_result = record(stepper.correct_velocity_pressure_flux())\n", "post_correction_snapshot = field_snapshot(stepper.fields(), names=(\"U\", \"p\", \"phi\"))\n", "\n", "show_table(\n", " \"Field deltas caused by correct_velocity_pressure_flux\",\n", " [\"field\", \"shape\", \"min Δ\", \"max Δ\", \"mean Δ\", \"l2 Δ\", \"max |Δ|\"],\n", " delta_rows(pre_correction_snapshot, post_correction_snapshot, names=(\"U\", \"p\", \"phi\")),\n", ")\n", "\n", "corrected_fields = stepper.fields()\n", "flux_rows = []\n", "for patch_name, patch_field in corrected_fields[\"phi\"].boundary.items():\n", " values = np.asarray(patch_field.values)\n", " flux_rows.append([patch_name, patch_field.type, values.shape, float(values.sum()) if values.size else 0.0, float(np.max(np.abs(values))) if values.size else 0.0])\n", "show_table(\"Boundary flux after correction\", [\"patch\", \"phi BC type\", \"shape\", \"sum(phi)\", \"max |phi|\"], flux_rows)\n" ] }, { "cell_type": "markdown", "id": "full-detail-48", "metadata": {}, "source": [ "### Spatial view of the final pressure/velocity/flux correction\n", "\n", "These heatmaps use the complete delta arrays from `correct_velocity_pressure_flux()` and bin them by cell or face centre.\n" ] }, { "cell_type": "code", "execution_count": 25, "id": "full-detail-49", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", " \n", " |ΔU| spatial distribution\n", " \n", " \n", " points=283,400; bins=96×96; color=bin mean |Δ|; occupied bins=4,847\n", " x=[-193.117, 197.702], y=[-194.298, 195.038]\n", " raw color range=[3.80655e-05, 19.7646]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " |Δp| spatial distribution\n", " \n", " \n", " points=283,400; bins=96×96; color=bin mean |Δ|; occupied bins=4,847\n", " x=[-193.117, 197.702], y=[-194.298, 195.038]\n", " raw color range=[0, 0]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " |Δphi| spatial distribution\n", " \n", " \n", " points=565,419; bins=96×96; color=bin mean |Δ|; occupied bins=7,291\n", " x=[-193.023, 197.582], y=[-194.361, 195.068]\n", " raw color range=[0, 0]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "for name in (\"U\", \"p\", \"phi\"):\n", " show_delta_map(mesh, name, pre_correction_snapshot, post_correction_snapshot)\n" ] }, { "cell_type": "markdown", "id": "full-detail-50", "metadata": {}, "source": [ "## 13. Correct the turbulence model\n", "\n", "The k-omega SST model updates turbulence state after the pressure/velocity correction. Here the wrapper exposes the resulting fields (`nut`, `k`, `omega`) rather than splitting the turbulence equations into their own matrices. That is still enough to see how turbulence state changes during one iteration.\n" ] }, { "cell_type": "code", "execution_count": 26, "id": "full-detail-51", "metadata": { "execution": { "iopub.execute_input": "2026-07-22T20:00:23.675514Z", "iopub.status.busy": "2026-07-22T20:00:23.675348Z", "iopub.status.idle": "2026-07-22T20:00:24.126363Z", "shell.execute_reply": "2026-07-22T20:00:24.119094Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "DILUPBiCGStab: Solving for omega, Initial residual = " ] }, { "data": { "text/markdown": [ "**TransformResult: momentum_transport_correct**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "0.00071906933244685197, Final residual = 1.3458793712123469e-09, No Iterations 3\n", "DILUPBiCGStab: Solving for k, Initial residual = 0.99999999999345446, Final residual = 6.2532997654966409e-09, No Iterations 7\n" ] }, { "data": { "text/markdown": [ "| name | phase | changed | inputs | outputs | source |\n", "| --- | --- | --- | --- | --- | --- |\n", "| momentum_transport_correct | momentum | viscosity, momentumTransport | — | p, U, phi, nut, k, omega | OpenFOAM-14/applications/modules/incompressibleFluid/incompressibleFluid.C:233-237::momentumTransportCorrector |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**Field deltas caused by momentum_transport_correct**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| field | shape | min Δ | max Δ | mean Δ | l2 Δ | max \\|Δ\\| |\n", "| --- | --- | --- | --- | --- | --- | --- |\n", "| nut | (283400,) | -3.12e-09 | 1.597e-10 | -1.502e-09 | 1.144e-06 | 3.12e-09 |\n", "| k | (283400,) | -2.202e-09 | 2.902e-08 | 1.588e-09 | 3.252e-06 | 2.902e-08 |\n", "| omega | (283400,) | -0.06781 | 1.718e+09 | 1.314e+07 | 5.498e+10 | 1.718e+09 |\n", "| U | (283400, 3) | 0 | 0 | 0 | 0 | 0 |\n", "| p | (283400,) | 0 | 0 | 0 | 0 | 0 |\n", "| phi | (565419,) | 0 | 0 | 0 | 0 | 0 |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**Turbulence fields after correction**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| name | kind | dimensions | entity | count | shape | min | max | mean |\n", "| --- | --- | --- | --- | --- | --- | --- | --- | --- |\n", "| nut | volScalar | [0 2 -1 0 0 0 0] | cell | 283400 | (283400,) | 9.192e-19 | 3.28e-09 | 1.607e-09 |\n", "| k | volScalar | [0 2 -2 0 0 0 0] | cell | 283400 | (283400,) | 1.434e-09 | 3.266e-08 | 5.224e-09 |\n", "| omega | volScalar | [0 0 -1 0 0 0 0] | cell | 283400 | (283400,) | 1.097 | 1.718e+09 | 1.314e+07 |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "pre_turbulence_snapshot = field_snapshot(stepper.fields(), names=(\"nut\", \"k\", \"omega\", \"U\", \"p\", \"phi\"))\n", "turbulence_result = record(stepper.momentum_transport_corrector())\n", "post_turbulence_snapshot = field_snapshot(stepper.fields(), names=(\"nut\", \"k\", \"omega\", \"U\", \"p\", \"phi\"))\n", "\n", "show_table(\n", " \"Field deltas caused by momentum_transport_correct\",\n", " [\"field\", \"shape\", \"min Δ\", \"max Δ\", \"mean Δ\", \"l2 Δ\", \"max |Δ|\"],\n", " delta_rows(pre_turbulence_snapshot, post_turbulence_snapshot, names=(\"nut\", \"k\", \"omega\", \"U\", \"p\", \"phi\")),\n", ")\n", "show_table(\n", " \"Turbulence fields after correction\",\n", " [\"name\", \"kind\", \"dimensions\", \"entity\", \"count\", \"shape\", \"min\", \"max\", \"mean\"],\n", " [field_summary_row(stepper.fields()[name]) for name in (\"nut\", \"k\", \"omega\")],\n", ")\n" ] }, { "cell_type": "markdown", "id": "full-detail-52", "metadata": {}, "source": [ "### Full-shape turbulence-correction outputs\n", "\n", "The turbulence corrector updates `nut`, `k`, and `omega`. The raw panels and maps below expose the complete post-correction fields and complete one-iteration deltas.\n" ] }, { "cell_type": "code", "execution_count": 27, "id": "full-detail-53", "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "#### Turbulence field `nut` after correction" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
nut.internal — shape=(283400,) (283,400), dtype=float64, size=283,400, TRUNCATED value text; exact shape retained; size=283,400; threshold=256; edgeitems=4
[3.12299427e-09 3.12135019e-09 3.12274815e-09 3.12226472e-09 ... 3.11722210e-09 3.11720802e-09 3.11721107e-09 3.11721779e-09]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " nut internal values\n", " n=283,400 finite, min=9.19177e-19, max=3.2797e-09, mean=1.60719e-09, std=1.53864e-09\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " nut: post-correction field on cell centres\n", " \n", " \n", " points=283,400; bins=96×96; color=bin mean; occupied bins=4,847\n", " x=[-193.117, 197.702], y=[-194.298, 195.038]\n", " raw color range=[6.06727e-10, 3.13788e-09]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " |Δnut| spatial distribution\n", " \n", " \n", " points=283,400; bins=96×96; color=bin mean |Δ|; occupied bins=4,847\n", " x=[-193.117, 197.702], y=[-194.298, 195.038]\n", " raw color range=[3.99598e-14, 2.49499e-09]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "#### Turbulence field `k` after correction" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
k.internal — shape=(283400,) (283,400), dtype=float64, size=283,400, TRUNCATED value text; exact shape retained; size=283,400; threshold=256; edgeitems=4
[3.64046932e-09 3.63712021e-09 3.64010971e-09 3.63917170e-09 ... 3.58815537e-09 3.58786899e-09 3.58793063e-09 3.58806572e-09]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " k internal values\n", " n=283,400 finite, min=1.43373e-09, max=3.26561e-08, mean=5.22354e-09, std=5.89869e-09\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " k: post-correction field on cell centres\n", " \n", " \n", " points=283,400; bins=96×96; color=bin mean; occupied bins=4,847\n", " x=[-193.117, 197.702], y=[-194.298, 195.038]\n", " raw color range=[3.41962e-09, 6.28036e-09]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " |Δk| spatial distribution\n", " \n", " \n", " points=283,400; bins=96×96; color=bin mean |Δ|; occupied bins=4,847\n", " x=[-193.117, 197.702], y=[-194.298, 195.038]\n", " raw color range=[5.45733e-13, 2.6708e-09]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "#### Turbulence field `omega` after correction" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
omega.internal — shape=(283400,) (283,400), dtype=float64, size=283,400, TRUNCATED value text; exact shape retained; size=283,400; threshold=256; edgeitems=4
[1.16569837 1.1652394  1.16567508 1.16555514 ... 1.15107466 1.15098799 1.15100664 1.15104749]
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " omega internal values\n", " n=283,400 finite, min=1.09735, max=1.71823e+09, mean=1.31364e+07, std=1.02433e+08\n", " x=value, y=count, bins=80\n", " \n", " \n", " \n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " omega: post-correction field on cell centres\n", " \n", " \n", " points=283,400; bins=96×96; color=bin mean; occupied bins=4,847\n", " x=[-193.117, 197.702], y=[-194.298, 195.038]\n", " raw color range=[1.09838, 2.18167e+07]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", " \n", " |Δomega| spatial distribution\n", " \n", " \n", " points=283,400; bins=96×96; color=bin mean |Δ|; occupied bins=4,847\n", " x=[-193.117, 197.702], y=[-194.298, 195.038]\n", " raw color range=[0.000175349, 2.18167e+07]\n", " " ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "for name in (\"nut\", \"k\", \"omega\"):\n", " field = stepper.fields()[name]\n", " display(Markdown(f\"#### Turbulence field `{name}` after correction\"))\n", " show_field_histograms(field)\n", " show_cell_field_map(mesh, field, title=f\"{name}: post-correction field on cell centres\")\n", " show_delta_map(mesh, name, pre_turbulence_snapshot, post_turbulence_snapshot)\n" ] }, { "cell_type": "markdown", "id": "full-detail-54", "metadata": {}, "source": [ "## 14. Close the iteration and view the whole transform graph\n", "\n", "The final housekeeping steps close the SIMPLE/PIMPLE iteration and call `postSolve(write=False)`. The table then shows the whole path this notebook executed.\n" ] }, { "cell_type": "code", "execution_count": 28, "id": "full-detail-55", "metadata": { "execution": { "iopub.execute_input": "2026-07-22T20:00:24.139309Z", "iopub.status.busy": "2026-07-22T20:00:24.139042Z", "iopub.status.idle": "2026-07-22T20:00:24.209193Z", "shell.execute_reply": "2026-07-22T20:00:24.208452Z" } }, "outputs": [ { "data": { "text/markdown": [ "**TransformResult: end_pimple_iteration**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| name | phase | changed | inputs | outputs | source |\n", "| --- | --- | --- | --- | --- | --- |\n", "| end_pimple_iteration | pimple | — | — | case_path, solver_name, time_name, time_index, time_value, delta_t, pimple_iteration_open, has_momentum_matrix, has_pressure_inputs | native wrapper / OpenFOAM runtime |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**TransformResult: post_solve**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| name | phase | changed | inputs | outputs | source |\n", "| --- | --- | --- | --- | --- | --- |\n", "| post_solve | time | — | — | case_path, solver_name, time_name, time_index, time_value, delta_t, pimple_iteration_open, has_momentum_matrix, has_pressure_inputs | native wrapper / OpenFOAM runtime |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**Complete executed transform graph**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| name | phase | changed | inputs | outputs | source |\n", "| --- | --- | --- | --- | --- | --- |\n", "| pre_solve | time | mesh, Uf | — | case_path, solver_name, time_name, time_index, time_value, delta_t, pimple_iteration_open, has_momentum_matrix, has_pressure_inputs | OpenFOAM-14/applications/modules/incompressibleFluid/incompressibleFluid.C:165-201::preSolve |\n", "| advance_time | time | — | — | case_path, solver_name, time_name, time_index, time_value, delta_t, pimple_iteration_open, has_momentum_matrix, has_pressure_inputs | native wrapper / OpenFOAM runtime |\n", "| begin_pimple_iteration | pimple | — | — | active, state | native wrapper / OpenFOAM runtime |\n", "| fv_models_correct | pimple | — | — | case_path, solver_name, time_name, time_index, time_value, delta_t, pimple_iteration_open, has_momentum_matrix, has_pressure_inputs | native wrapper / OpenFOAM runtime |\n", "| pre_predictor | pimple | — | — | case_path, solver_name, time_name, time_index, time_value, delta_t, pimple_iteration_open, has_momentum_matrix, has_pressure_inputs | native wrapper / OpenFOAM runtime |\n", "| momentum_transport_predict | momentum | — | — | case_path, solver_name, time_name, time_index, time_value, delta_t, pimple_iteration_open, has_momentum_matrix, has_pressure_inputs | OpenFOAM-14/applications/modules/incompressibleFluid/incompressibleFluid.C:208-211::momentumTransportPredictor |\n", "| assemble_momentum_terms | momentum | — | p, U, phi, nut, k, omega | terms, source | OpenFOAM-14/applications/modules/incompressibleFluid/momentumPredictor.C:36-53::Foam::solvers::incompressibleFluid::momentumPredictor |\n", "| assemble_UEqn | momentum | — | p, U, phi, nut, k, omega | UEqn | OpenFOAM-14/applications/modules/incompressibleFluid/momentumPredictor.C:36-44::momentumPredictor |\n", "| relax_UEqn | momentum | — | — | UEqn | native wrapper / OpenFOAM runtime |\n", "| constrain_UEqn | momentum | — | — | UEqn | native wrapper / OpenFOAM runtime |\n", "| solve_UEqn | momentum | U | U, UEqn | performance, matrix_before, field_before, field_after | OpenFOAM-14/applications/modules/incompressibleFluid/momentumPredictor.C:50-55::momentumPredictor |\n", "| compute_pressure_inputs | pressure | — | — | rAU, HbyA, phiHbyA, rAtU, adjusted_phi_for_pressure_reference, consistent, source | OpenFOAM-14/applications/modules/incompressibleFluid/correctPressure.C:47-81::Foam::solvers::incompressibleFluid::correctPressure |\n", "| assemble_pEqn | pressure | — | — | pEqn | OpenFOAM-14/applications/modules/incompressibleFluid/correctPressure.C:83-101::correctPressure |\n", "| solve_pEqn | pressure | p, phi | p, pEqn | performance, matrix_before, field_before, p, phi | OpenFOAM-14/applications/modules/incompressibleFluid/correctPressure.C:103-108::correctPressure |\n", "| correct_velocity_pressure_flux | pressure | p, U, phi | — | p, U, phi, source | native wrapper / OpenFOAM runtime |\n", "| momentum_transport_correct | momentum | viscosity, momentumTransport | — | p, U, phi, nut, k, omega | OpenFOAM-14/applications/modules/incompressibleFluid/incompressibleFluid.C:233-237::momentumTransportCorrector |\n", "| end_pimple_iteration | pimple | — | — | case_path, solver_name, time_name, time_index, time_value, delta_t, pimple_iteration_open, has_momentum_matrix, has_pressure_inputs | native wrapper / OpenFOAM runtime |\n", "| post_solve | time | — | — | case_path, solver_name, time_name, time_index, time_value, delta_t, pimple_iteration_open, has_momentum_matrix, has_pressure_inputs | native wrapper / OpenFOAM runtime |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "**Final solver state**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| Key | Value |\n", "| --- | --- |\n", "| case_path | /home/aaron/data/openFOAM-RANS-to-GPU/tmp/airfrans_stepper_full_detail_learning_tool/split_case |\n", "| solver_name | incompressibleFluid |\n", "| time_name | 1 |\n", "| time_index | 1 |\n", "| time_value | 1 |\n", "| delta_t | 1 |\n", "| pimple_iteration_open | False |\n", "| has_momentum_matrix | False |\n", "| has_pressure_inputs | False |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "record(stepper.end_pimple_iteration())\n", "record(stepper.post_solve(write=False))\n", "\n", "show_table(\n", " \"Complete executed transform graph\",\n", " [\"name\", \"phase\", \"changed\", \"inputs\", \"outputs\", \"source\"],\n", " [transform_row(result) for result in transform_results],\n", ")\n", "show_table(\"Final solver state\", [\"Key\", \"Value\"], list(stepper.state().items()))\n" ] }, { "cell_type": "markdown", "id": "full-detail-56", "metadata": {}, "source": [ "## 15. Sanity check: split walk versus high-level `run_one_pimple_iteration()`\n", "\n", "The educational walk above called each step explicitly. The production-style convenience call `run_one_pimple_iteration()` should produce the same `U`, `p`, and `phi` for one iteration because both paths call the same OpenFOAM operations in the same order.\n", "\n", "The high-level call is run in a fresh Python subprocess to avoid OpenFOAM global `jobInfo` warnings from constructing a second independent case inside the same process. The resulting arrays are saved to `tmp/` and loaded back for comparison." ] }, { "cell_type": "code", "execution_count": 29, "id": "full-detail-57", "metadata": { "execution": { "iopub.execute_input": "2026-07-22T20:00:24.210779Z", "iopub.status.busy": "2026-07-22T20:00:24.210628Z", "iopub.status.idle": "2026-07-22T20:00:30.292997Z", "shell.execute_reply": "2026-07-22T20:00:30.292487Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "subprocess returncode= 0\n", "high-level subprocess log tail:\n", "_full_detail_learning_tool/high_level_case\n", "nProcs : 1\n", "fileModificationChecking : Monitoring run-time modified files using timeStampMaster (fileModificationSkew 10)\n", "allowSystemOperations : Allowing user-supplied system call operations\n", "\n", "// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //\n", "\n", "Selecting viscosity model constant\n", "\n", "Selecting momentum transport model type RAS\n", " Selecting RAS turbulence model kOmegaSST\n", " bounding k, min: 0 max: 3.6353069999999998e-09 average: 3.6353069999910849e-09\n", "\n", "Selecting patchDistMethod meshWave\n", "\n", "SIMPLE: Convergence criteria found\n", " p: tolerance 0\n", " U: tolerance 0\n", " nuTilda: tolerance 0\n", " k: tolerance 0\n", " omega: tolerance 0\n", " h: tolerance 0\n", "\n", "\n", "SIMPLE: Operating solver in steady-state mode with 1 outer corrector\n", "SIMPLE: Operating solver in SIMPLE mode\n", "\n", "\n", "DILUPBiCGStab: Solving for Ux, Initial residual = 0.99999991606780358, Final residual = 6.4677780734704781e-09, No Iterations 11\n", "DILUPBiCGStab: Solving for Uy, Initial residual = 0.99999968448032772, Final residual = 7.4359526559359641e-09, No Iterations 11\n", "DICPCG: Solving for p, Initial residual = 1, Final residual = 0.030890897351242643, No Iterations 300\n", "DICPCG: Solving for p, Initial residual = 7.2784937401803668e-05, Final residual = 9.4967147297221812e-07, No Iterations 199\n", "DICPCG: Solving for p, Initial residual = 1.9461086386417007e-05, Final residual = 9.8677378665869984e-07, No Iterations 160\n", "DICPCG: Solving for p, Initial residual = 8.7397651610059266e-06, Final residual = 9.9577380572382355e-07, No Iterations 125\n", "time step continuity errors : sum local = 7.4676177118375265e-06, global = 9.9407679212193635e-07, cumulative = 9.9407679212193635e-07\n", "DILUPBiCGStab: Solving for omega, Initial residual = 0.00071906933244685197, Final residual = 1.3458793712123469e-09, No Iterations 3\n", "DILUPBiCGStab: Solving for k, Initial residual = 0.99999999999345446, Final residual = 6.2532997654966409e-09, No Iterations 7\n", "\n" ] }, { "data": { "text/markdown": [ "**Split-step parity against run_one_pimple_iteration**" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/markdown": [ "| field | split shape | run_one shape | max \\|diff\\| | allclose |\n", "| --- | --- | --- | --- | --- |\n", "| U | (283400, 3) | (283400, 3) | 0 | True |\n", "| p | (283400,) | (283400,) | 0 | True |\n", "| phi | (565419,) | (565419,) | 0 | True |" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "run_one graph:\n", "['pre_solve', 'advance_time', 'begin_pimple_iteration', 'fv_models_correct', 'pre_predictor', 'momentum_transport_predict', 'assemble_UEqn', 'relax_UEqn', 'constrain_UEqn', 'solve_UEqn', 'constrain_U_after_momentum', 'compute_pressure_inputs', 'assemble_pEqn', 'solve_pEqn', 'update_phi_from_pEqn_flux', 'correct_velocity_pressure_flux', 'momentum_transport_correct', 'end_pimple_iteration', 'post_solve']\n" ] } ], "source": [ "high_level_npz = WORK / \"high_level_fields.npz\"\n", "subprocess_code = f\"\"\"\n", "from pathlib import Path\n", "import os\n", "import sys\n", "os.environ.pop('PYTHONPATH', None)\n", "root = Path({str(ROOT)!r})\n", "sys.path.insert(0, str(root / 'scripts'))\n", "from prepare_airfrans_stepper_case import prepare_case\n", "from openfoam_env import apply_openfoam_env\n", "apply_openfoam_env()\n", "import numpy as np\n", "import foam_stepper as foam\n", "raw_case = Path({str(RAW_CASE)!r})\n", "high_level_case = Path({str(HIGH_LEVEL_CASE)!r})\n", "out = Path({str(high_level_npz)!r})\n", "prepare_case(raw_case, high_level_case, end_time=1)\n", "result = foam.Case(high_level_case).make_stepper().run_one_pimple_iteration()\n", "fields = result.outputs['fields']\n", "np.savez(\n", " out,\n", " U=np.asarray(fields['U'].internal),\n", " p=np.asarray(fields['p'].internal),\n", " phi=np.asarray(fields['phi'].internal),\n", " graph=np.asarray([node.name for node in result.outputs['graph']], dtype=object),\n", ")\n", "\"\"\"\n", "completed = subprocess.run([str(PYTHON), \"-c\", subprocess_code], cwd=ROOT, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=600)\n", "print(\"subprocess returncode=\", completed.returncode)\n", "if completed.returncode != 0:\n", " print(completed.stdout[-8000:])\n", " raise SystemExit(completed.returncode)\n", "print(\"high-level subprocess log tail:\")\n", "print(completed.stdout[-2000:])\n", "\n", "high = np.load(high_level_npz, allow_pickle=True)\n", "split_final_fields = stepper.fields()\n", "\n", "parity_rows = []\n", "for name in (\"U\", \"p\", \"phi\"):\n", " split_arr = np.asarray(split_final_fields[name].internal)\n", " high_arr = np.asarray(high[name])\n", " diff = split_arr - high_arr\n", " parity_rows.append([\n", " name,\n", " split_arr.shape,\n", " high_arr.shape,\n", " float(np.max(np.abs(diff))) if diff.size else 0.0,\n", " bool(np.allclose(split_arr, high_arr, rtol=1e-8, atol=1e-8)),\n", " ])\n", "show_table(\"Split-step parity against run_one_pimple_iteration\", [\"field\", \"split shape\", \"run_one shape\", \"max |diff|\", \"allclose\"], parity_rows)\n", "\n", "run_one_graph = list(high[\"graph\"])\n", "print(\"run_one graph:\")\n", "print(run_one_graph)\n", "assert all(row[-1] for row in parity_rows)" ] }, { "cell_type": "markdown", "id": "full-detail-58", "metadata": {}, "source": [ "## What this one-iteration microscope teaches\n", "\n", "The AirfRANS simulation is not a black box once viewed through these intermediate objects:\n", "\n", "- The **mesh** is a finite-volume graph: cells, faces, owner/neighbour addressing, and boundary patches.\n", "- The **fields** are typed OpenFOAM fields with dimensions, entity locations, and boundary conditions.\n", "- The **momentum predictor** creates a vector sparse matrix `UEqn` from convection, turbulence stress, sources, and pressure-gradient terms.\n", "- **Relaxation and constraints** alter matrix coefficients before solving.\n", "- The **velocity solve** updates `U` but does not complete incompressibility.\n", "- The **pressure correction** builds scalar matrix `pEqn`, updates `p` and `phi`, then corrects `U`/`p`/`phi` consistently.\n", "- The **k-omega SST corrector** updates `k`, `omega`, and `nut`, changing the effective viscosity used by future momentum equations.\n", "- `TransformResult.source` ties many Python-visible transitions back to specific OpenFOAM source files and functions.\n", "\n", "A next notebook can go deeper by exposing the turbulence equations as separate `MatrixView` objects, matching the already exposed momentum and pressure matrices.\n" ] } ], "metadata": { "kernelspec": { "display_name": ".venv", "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.12.12" } }, "nbformat": 4, "nbformat_minor": 5 }