From 308fd1e690eac4d188275c2e862b9d3d6ee6f1fd Mon Sep 17 00:00:00 2001
From: Even Solbraa <41290109+EvenSol@users.noreply.github.com>
Date: Wed, 9 Sep 2026 22:55:17 +0200
Subject: [PATCH 1/7] Add fresh comparesimulations2 PFD and P&ID workflow
---
.../comparesimulations2_20260809.json | 161 +-
notebooks/notebook_maintenance_ledger.json | 2 +-
notebooks/process/comparesimulations2.ipynb | 4279 ++++++++++++++++-
3 files changed, 4096 insertions(+), 346 deletions(-)
diff --git a/notebooks/maintenance_ledger/comparesimulations2_20260809.json b/notebooks/maintenance_ledger/comparesimulations2_20260809.json
index 670b4239..c8e64c9e 100644
--- a/notebooks/maintenance_ledger/comparesimulations2_20260809.json
+++ b/notebooks/maintenance_ledger/comparesimulations2_20260809.json
@@ -1,29 +1,27 @@
{
"schema_version": 1,
- "updated_at": "2026-08-09T14:58:13Z",
+ "updated_at": "2026-09-09T20:32:20Z",
"notebooks": [
{
"path": "notebooks/process/comparesimulations2.ipynb",
- "verified_date": "2026-08-09",
- "verified_at_utc": "2026-08-09T14:58:13Z",
- "neqsim_version": "3.17.0",
- "python_version": "3.12.13",
- "java_version": "OpenJDK 17.0.19",
+ "verified_date": "2026-09-09",
+ "verified_at_utc": "2026-09-09T20:32:20Z",
+ "neqsim_version": "3.20.0 Python package with Java commit df2de508ffff3dbc881bdb6e796fe4bb27627498",
+ "python_version": "3.12.14",
+ "java_version": "OpenJDK 17.0.20",
"environment_resolution": {
- "mode": "online-new-snapshot",
- "bootstrap": "python scripts/bootstrap_neqsim_validation_env.py --venv /tmp/neqsim-validation-comparesimulations2-final7-20260809t1602 --neqsim-version latest --refresh --extra-package scipy --extra-package pydantic --extra-package scikit-learn --extra-package graphviz --extra-package jupyter --extra-package nbconvert --extra-package ipykernel --extra-package black",
- "snapshot": "/workspace/.cache/neqsim-validation-wheelhouse/schema-1/cpython-312-linux-x86_64/neqsim-3.17.0/20260809T145458Z-b0880834160e",
- "verified_public_pypi_wheels": 125,
- "manifest_created_at_utc": "2026-08-09T14:54:58Z",
- "integrity": "Complete public-PyPI inventory, size, SHA-256, runtime, platform, and requested-toolchain verification passed before fresh-environment installation."
+ "mode": "exact-source-commit runtime with packaged Python API",
+ "runtime_jar_sha256": "7e29f0e9dd424172812f9dd2c13f282a82ebb0f22f8870eb5fe91254ea2913a1",
+ "bootstrap": "The notebook accepts NEQSIM_EXACT_JAR for CI/developer validation; otherwise it fetches and builds public commit df2de508ffff3dbc881bdb6e796fe4bb27627498 before JVM startup.",
+ "integrity": "The retained provenance table records Python, Java, runtime JAR, SHA-256, and exact Java commit. No pre-release or stale packaged renderer was used."
},
"execution_status": "passed",
- "execution_method": "All 30 code cells executed sequentially from an empty in-process IPython state with execution counts 1 through 30, zero exceptions, zero error outputs, and zero stored stderr. The final clean run completed in 103.2 seconds and retained every table and inline figure.",
- "code_cells": 30,
- "substantive_code_cells": 30,
- "markdown_cells": 26,
+ "execution_method": "All 34 code cells executed sequentially from an empty in-process IPython state with execution counts 1 through 34, zero exceptions, zero error outputs, and zero stored stderr. The final clean run completed in 155.7 seconds and retained every table, equation, calculated figure, and eight-sheet PFD/P&ID visual set.",
+ "code_cells": 34,
+ "substantive_code_cells": 34,
+ "markdown_cells": 27,
"publication": {
- "branch": "automation/repair-comparesimulations2-20260809",
+ "branch": "automation/comparesimulations2-pfd-pid-20260909",
"mode": "atomic connector-backed draft pull request",
"commit": "the commit containing this ledger entry",
"files": [
@@ -33,99 +31,94 @@
]
},
"preservation": {
- "pre_existing_cells": 48,
- "preserved_cells": 48,
+ "pre_existing_cells": 56,
+ "preserved_cells": 56,
"pre_existing_substantive_examples": 16,
"preserved_substantive_examples": 16,
- "pre_existing_figures": 3,
- "preserved_figures": 3,
- "repairs": "Preserved every original cell and all fluid, Eclipse import, standard-condition, phase-envelope, connected-process, report, persistence, product, duty, rotating-equipment, 100-case, optimization, surrogate, direct-thread, and pool examples. Replaced mutable-network setup, multi-megabyte console output, stale package guidance, compressed code, uninitialized densities, and shared fluid state. Corrected pressure-drop mappings for 23-HA-03 and 25-HA-02 and corrected the final gas-cooler export-pressure mapping."
+ "pre_existing_calculated_png_figures": 7,
+ "preserved_calculated_png_figures": 7,
+ "repairs": "Preserved every prior comparison, process, persistence, sensitivity, optimization, surrogate, threading, and managed-pool example and every historical output. Named the three existing numerical recycle utilities and introduced two named placeholder streams as convergence destinations, matching the accepted Java reference without adding physical equipment. Retained the 0.01 percent mass-closure gate and added deterministic post-thread convergence qualification."
},
"neqsim_capabilities_demonstrated": [
"17-component PR assay with TBP fractions and an embedded Eclipse-format alternative import",
- "TP flash, initialized physical properties, phase-envelope tracing, and critical-region retrieval",
- "39-unit multistage separation, flash-gas recovery, recompression, cold process, fuel-gas split, and gas/oil export workflow",
- "validated Pydantic operating-condition contract with named-unit configuration",
- "native JSON report generation and NeqSim process save/open round trip",
- "product-rate, RVP, TVP, ISO 6976 gas-quality, mass-closure, exchanger-duty, compressor, and pump retrieval",
- "deterministic 100-case Latin-hypercube operating study",
- "fresh-process SLSQP optimization with mass and RVP validation",
- "random-forest surrogate training, held-out validation, differential-evolution search, and rigorous NeqSim candidate rerun",
- "independent ProcessSystem.runAsThread execution and managed NeqSimThreadPool futures"
+ "TP flash, phase-envelope tracing, standard-condition properties, and critical-region retrieval",
+ "multistage separation, flash-gas recovery, recompression, cold process, fuel-gas split, and gas/oil export",
+ "deterministic 100-case Latin-hypercube study plus direct and surrogate optimization",
+ "independent Java-thread and managed-pool execution with explicit convergence qualification",
+ "fresh PFD and distinct review-required P&ID from one solved canonical process",
+ "four A1 SVG sheets and one A1 PDF drawing set per profile",
+ "native DEXPI 2.0 Process and Plant/P&ID outputs kept distinct from the Proteus 4.1 compatibility companion",
+ "source-linked P&ID registers for nozzles, piping, valves, reducers, instruments, signals, and declared isolation/drain/vent/relief interfaces",
+ "independent byte-identical regeneration of all 25 delivered files"
],
- "practical_application": "Builds a reusable offshore oil-and-gas process from a governed fluid assay; configures pressures, temperatures, pressure drops, and export conditions; runs and audits a base case; compares published HYSYS responses; maps 100 operating scenarios; validates direct and surrogate optimization candidates; and demonstrates safe parallel execution with independently cloned thermodynamic systems.",
+ "practical_application": "Runs and optimizes the established separation/compression teaching case, then delivers a topology-equivalent PFD and a graphically distinct P&ID proposal with operating data, balances, tagged symbols, orthogonal routes, recycles, area boundaries, line identity, engineering registers, and exchange companions. The outputs remain review-required teaching artifacts, not a completed safety or construction design.",
"engineering_validation": {
- "assertions_passed": 43,
+ "assertions_passed": 58,
"assertions_failed": 0,
"checks": [
- "all 48 original cells, 16 substantive examples, and three original figures preserved",
- "all 30 code cells executed in order with no exceptions or stderr",
- "standard-condition gas and oil densities finite and physically plausible",
- "all required process units present and native DOT graph contains key nodes",
- "overall base-case and 100-case mass residuals below 0.01 percent",
- "heat-exchanger two-sided duty balance and rotating-equipment power checks passed",
- "direct optimizer converged with ordered separator pressures and RVP below 12 psia",
- "held-out surrogate R2 exceeded 0.5 and its candidate passed a fresh rigorous NeqSim run",
- "direct and managed parallel results agreed within 1e-6 kW",
- "valid nbformat JSON, sequential counts, public-PyPI setup, Colab badge, links, code readability, and stored outputs verified"
+ "all 56 pre-existing cells and 16 substantive examples preserved",
+ "all 34 code cells executed in order with no exceptions or stderr",
+ "base case, 100-case study, direct optimizer, and surrogate candidate passed the 0.01 percent mass-closure gate",
+ "20 direct-thread and 20 managed-pool cases were explicitly qualified after timing and agreed within 1e-6 kW",
+ "PFD and P&ID share source graph fingerprint 47a2e6d7a3331f1ea2717b018e28977d943450bbe8130409d2401d223358ccc6",
+ "each profile contains four A1 SVG sheets and a four-page A1 PDF",
+ "both profiles report zero DIAGRAM_RENDER warnings and errors",
+ "all 25 primary delivery files match an independently reconstructed and solved regeneration byte for byte",
+ "24-VB-01 is absent from the runnable process and both deliveries",
+ "all eight fresh sheets inspected at full-sheet and 200 dpi detail scales"
],
"key_results": {
- "base_total_power_kw": 10431.428486,
- "base_oil_export_m3_day": 19130.517773,
- "base_gas_export_msm3_day": 2.900716,
- "base_mass_residual_percent": 0.000408,
- "base_oil_rvp_psia": 9.5792,
- "standard_gas_density_kg_m3": 0.9675,
- "standard_oil_density_kg_m3": 708.9682,
+ "base_total_power_kw": 10431.534102,
+ "base_oil_export_m3_day": 19130.542443,
+ "base_gas_export_msm3_day": 2.900748,
+ "base_mass_residual_percent": 0.000076,
+ "base_oil_rvp_psia": 9.5793,
"scenario_count": 100,
- "maximum_scenario_mass_residual_percent": 0.001336,
- "direct_optimized_power_kw": 9909.8522,
- "direct_optimized_stage_1_pressure_barg": 35.0,
- "direct_optimized_stage_2_pressure_barg": 9.157728,
- "surrogate_held_out_r2": 0.796,
- "surrogate_candidate_power_kw": 9333.9807,
- "surrogate_candidate_rvp_psia": 10.5563
+ "maximum_scenario_mass_residual_percent": 0.003330,
+ "direct_optimized_power_kw": 9909.9486,
+ "surrogate_held_out_r2": 0.794,
+ "surrogate_candidate_power_kw": 9297.7136,
+ "surrogate_candidate_rvp_psia": 10.4109,
+ "pfd_pdf_sha256": "020aef2f04f3c9261e846da5ab3a1d66d3a64066a1f1abcb28f43702829c0ece",
+ "pid_pdf_sha256": "8aff1c847a9595592675f8667662c8237d4002bcd8a1b9d65a33b62d37b71240"
}
},
+ "diagram_acceptance": {
+ "status": "implementation and visual generation passed; accountable engineering review remains required",
+ "paper_size": "A1 landscape",
+ "profiles": ["PFD", "PID"],
+ "svg_sheets_per_profile": 4,
+ "pdf_pages_per_profile": 4,
+ "renderer_warnings": 0,
+ "renderer_errors": 0,
+ "stable_regeneration": true,
+ "visual_inspection": "All eight fresh pages were rendered and inspected at full-sheet and 200 dpi detail scales. Routes, arrows, tagged symbols, continuations, title blocks, area structure, and the PFD/P&ID distinction remain legible with no clipped sheet content or renderer-reported collision defect.",
+ "project_metadata_gaps": "Line classes, piping specifications, design pressure and temperature, materials, valve failure positions, instrument ranges, alarm/trip set points, hazardous-area data, relief sizing and disposal, drain/vent destinations, revision approvals, and accountable discipline sign-off remain missing and explicitly disclosed.",
+ "claims_boundary": "Standards-aligned teaching proposal only. No ISO 10628 or ANSI/ISA-5.1 conformance, certification, safety credit, construction fitness, or engineering approval is claimed. Native DEXPI 2.0 and Proteus 4.1 compatibility claims are distinct; external named-CAE round-trip qualification remains open."
+ },
"figures": {
- "previous_figures": 3,
- "previous_figures_refreshed": 1,
- "previous_figures_preserved_as_dated_provenance": 2,
- "new_figures": 6,
- "total_retained_figures": 9,
- "purposes": [
- "refreshed PR phase envelope with dew branch, bubble branch, and critical point",
- "exact preserved published process-flow image",
- "current connected NeqSim workflow overview",
- "exact preserved legacy native Graphviz rendering with historical-label warning",
- "separator-pressure power sensitivity and power-oil trade-off",
- "rigorous base-versus-direct-optimization power comparison",
- "surrogate parity validation and feature influence",
- "direct Java-thread completion timing",
- "direct-thread versus managed-pool execution comparison"
- ],
- "visual_inspection": "Passed at original resolution after the final clean execution. All nine figures were extracted and inspected for titles, labels, units, legends or color scales, accessible colors, clipping, overlap, scales, and consistency with nearby tables. The two wide legacy visuals are explicitly identified as provenance artifacts; the compact current workflow supplies the readable teaching view.",
- "numerical_refresh": "The reproducible phase envelope and all six new figures were recomputed with the validated inputs and NeqSim 3.17.0. The published process image and legacy Graphviz image were preserved exactly with source and historical-status notes; current DOT topology was exported and asserted separately."
+ "preserved_calculated_png_figures": 7,
+ "new_inline_svg_sheets": 8,
+ "fresh_profile_pdfs": 2,
+ "visual_inspection": "The final clean execution retained seven calculated PNG figures and all eight fresh SVG sheets. The PFD and P&ID PDFs were rendered at target A1 size and inspected at full-sheet and 200 dpi detail scales."
},
"equation_rendering": {
"status": "passed",
"display_equations": 3,
- "renderer": "Jupyter nbconvert MathJax-compatible HTML plus visual math-render inspection",
- "visual_inspection": "Fugacity equality, mass-closure fraction, comparison fraction, subscripts, superscripts, summation, dotted mass rates, Greek symbols, and bold composition vectors rendered correctly. All delimiters are balanced and commands use single backslashes."
+ "renderer": "Jupyter nbconvert MathJax-compatible HTML",
+ "visual_inspection": "Fugacity equality, mass closure, comparison fraction, subscripts, superscripts, summation, dotted mass rates, Greek symbols, and bold composition vectors remain balanced and renderable."
},
"notebook_quality": {
"valid_nbformat_json": true,
"maximum_code_line_length": 100,
- "one_statement_per_line": true,
- "semicolon_chains": 0,
- "one_line_compound_bodies": 0,
- "colab_badge_valid": true,
- "public_pypi_setup_cell": true,
+ "sequential_execution_counts": true,
+ "stored_error_outputs": 0,
+ "stored_stderr_outputs": 0,
+ "colab_reproducible_exact_commit_bootstrap": true,
"hidden_local_dependencies": false,
- "changed_links_checked": true,
"catalog_target_occurrences": 1
},
- "summary": "Modernized the complete simulator-comparison workflow without pinning the notebook to a NeqSim release. Added current PyPI setup and provenance, deterministic fresh-object construction, corrected pressure mappings, compact outputs, benchmark interpretation, engineering checks, rigorous scenario and optimization workflows, safe parallel APIs, nine retained figures, limitations, references, and exercises."
+ "summary": "Published-ready retained-output milestone for the comparesimulations2 case: exact accepted Java renderer provenance, explicit numerical recycle identities, complete scientific rerun, deterministic parallel qualification, fresh PFD and distinct P&ID, correct DEXPI/Proteus companions, zero renderer defects, full A1 inspection, and byte-identical independent regeneration."
}
]
}
diff --git a/notebooks/notebook_maintenance_ledger.json b/notebooks/notebook_maintenance_ledger.json
index 2c43cada..1f99ed1a 100644
--- a/notebooks/notebook_maintenance_ledger.json
+++ b/notebooks/notebook_maintenance_ledger.json
@@ -1,6 +1,6 @@
{
"schema_version": 3,
- "updated_at": "2026-09-01T15:17:29Z",
+ "updated_at": "2026-09-09T20:32:20Z",
"active_notebook_count": 297,
"shards_glob": "maintenance_ledger/*.json",
"retired_notebooks": [
diff --git a/notebooks/process/comparesimulations2.ipynb b/notebooks/process/comparesimulations2.ipynb
index 68f13ad1..6f0c649e 100644
--- a/notebooks/process/comparesimulations2.ipynb
+++ b/notebooks/process/comparesimulations2.ipynb
@@ -19,6 +19,81 @@
{
"cell_type": "code",
"execution_count": 1,
+ "id": "3c32df43",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Diagram runtime: supplied exact-commit validation artifact\n",
+ "Java commit: df2de508ffff3dbc881bdb6e796fe4bb27627498\n",
+ "JAR SHA-256: 7e29f0e9dd424172812f9dd2c13f282a82ebb0f22f8870eb5fe91254ea2913a1\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Pin the diagram runtime to the exact accepted NeqSim implementation.\n",
+ "# A supplied local JAR makes CI/developer validation fast. Colab builds the same public commit.\n",
+ "import hashlib\n",
+ "import os\n",
+ "import subprocess\n",
+ "from pathlib import Path\n",
+ "\n",
+ "NEQSIM_DIAGRAM_COMMIT = \"df2de508ffff3dbc881bdb6e796fe4bb27627498\"\n",
+ "supplied_jar = os.environ.get(\"NEQSIM_EXACT_JAR\", \"\").strip()\n",
+ "\n",
+ "if supplied_jar:\n",
+ " diagram_runtime_jar = Path(supplied_jar).expanduser().resolve()\n",
+ " if not diagram_runtime_jar.is_file():\n",
+ " raise FileNotFoundError(f\"NEQSIM_EXACT_JAR does not exist: {diagram_runtime_jar}\")\n",
+ " diagram_runtime_source = \"supplied exact-commit validation artifact\"\n",
+ "else:\n",
+ " source_dir = Path(\"neqsim-diagram-runtime\")\n",
+ " if not source_dir.exists():\n",
+ " subprocess.check_call([\"git\", \"init\", str(source_dir)])\n",
+ " subprocess.check_call(\n",
+ " [\n",
+ " \"git\",\n",
+ " \"-C\",\n",
+ " str(source_dir),\n",
+ " \"remote\",\n",
+ " \"add\",\n",
+ " \"origin\",\n",
+ " \"https://github.com/equinor/neqsim.git\",\n",
+ " ]\n",
+ " )\n",
+ " subprocess.check_call(\n",
+ " [\"git\", \"-C\", str(source_dir), \"fetch\", \"--depth\", \"1\", \"origin\", NEQSIM_DIAGRAM_COMMIT]\n",
+ " )\n",
+ " subprocess.check_call([\"git\", \"-C\", str(source_dir), \"checkout\", \"--detach\", \"FETCH_HEAD\"])\n",
+ " checked_out = subprocess.check_output(\n",
+ " [\"git\", \"-C\", str(source_dir), \"rev-parse\", \"HEAD\"], text=True\n",
+ " ).strip()\n",
+ " if checked_out != NEQSIM_DIAGRAM_COMMIT:\n",
+ " raise RuntimeError(f\"Expected {NEQSIM_DIAGRAM_COMMIT}, found {checked_out}\")\n",
+ " subprocess.check_call(\n",
+ " [\"./mvnw\", \"-q\", \"-DskipTests\", \"-Dmaven.javadoc.skip=true\", \"package\"], cwd=source_dir\n",
+ " )\n",
+ " jars = sorted((source_dir / \"target\").glob(\"neqsim-*.jar\"))\n",
+ " jars = [path for path in jars if not path.name.endswith((\"-sources.jar\", \"-javadoc.jar\"))]\n",
+ " if len(jars) != 1:\n",
+ " raise RuntimeError(\n",
+ " f\"Expected one executable NeqSim JAR, found {[str(path) for path in jars]}\"\n",
+ " )\n",
+ " diagram_runtime_jar = jars[0].resolve()\n",
+ " diagram_runtime_source = f\"source build of {NEQSIM_DIAGRAM_COMMIT}\"\n",
+ "\n",
+ "diagram_runtime_sha256 = hashlib.sha256(diagram_runtime_jar.read_bytes()).hexdigest()\n",
+ "os.environ[\"NEQSIM_JVM_AUTOSTART\"] = \"0\"\n",
+ "print(f\"Diagram runtime: {diagram_runtime_source}\")\n",
+ "print(f\"Java commit: {NEQSIM_DIAGRAM_COMMIT}\")\n",
+ "print(f\"JAR SHA-256: {diagram_runtime_sha256}\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
"id": "fef931a1",
"metadata": {},
"outputs": [
@@ -50,43 +125,49 @@
"
\n",
" \n",
" | 0 | \n",
- " NeqSim | \n",
- " 3.17.0 | \n",
+ " NeqSim Python package | \n",
+ " 3.20.0 | \n",
"
\n",
" \n",
" | 1 | \n",
" Python | \n",
- " 3.12.13 | \n",
+ " 3.12.14 | \n",
"
\n",
" \n",
" | 2 | \n",
" Java | \n",
- " 17.0.19 | \n",
+ " 17.0.20 | \n",
"
\n",
" \n",
" | 3 | \n",
- " Packaged JAR | \n",
- " neqsim-3.17.0.jar | \n",
+ " Runtime JAR | \n",
+ " neqsim-3.20.0.jar | \n",
"
\n",
" \n",
" | 4 | \n",
" JAR SHA-256 | \n",
- " f114c17bf99a7b02be9f8c49b66fd792c88fdcbfcb0434... | \n",
+ " 7e29f0e9dd424172812f9dd2c13f282a82ebb0f22f8870... | \n",
+ "
\n",
+ " \n",
+ " | 5 | \n",
+ " Java commit | \n",
+ " df2de508ffff3dbc881bdb6e796fe4bb27627498 | \n",
"
\n",
" \n",
"\n",
""
],
"text/plain": [
- " Item Value\n",
- "0 NeqSim 3.17.0\n",
- "1 Python 3.12.13\n",
- "2 Java 17.0.19\n",
- "3 Packaged JAR neqsim-3.17.0.jar\n",
- "4 JAR SHA-256 f114c17bf99a7b02be9f8c49b66fd792c88fdcbfcb0434..."
+ " Item Value\n",
+ "0 NeqSim Python package 3.20.0\n",
+ "1 Python 3.12.14\n",
+ "2 Java 17.0.20\n",
+ "3 Runtime JAR neqsim-3.20.0.jar\n",
+ "4 JAR SHA-256 7e29f0e9dd424172812f9dd2c13f282a82ebb0f22f8870...\n",
+ "5 Java commit df2de508ffff3dbc881bdb6e796fe4bb27627498"
]
},
- "execution_count": 1,
+ "execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
@@ -129,12 +210,17 @@
" )\n",
"\n",
"import jpype\n",
+ "\n",
+ "jpype.addClassPath(str(diagram_runtime_jar))\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"import neqsim as neqsim_package\n",
"from neqsim import jneqsim as neqsim\n",
+ "from neqsim.neqsimpython import init_jvm\n",
+ "\n",
+ "init_jvm()\n",
"\n",
"neqsim_version = importlib.metadata.version(\"neqsim\")\n",
"neqsim_directory = Path(neqsim_package.__file__).resolve().parent\n",
@@ -143,19 +229,28 @@
"if len(neqsim_jars) != 1:\n",
" raise RuntimeError(f\"Expected one packaged NeqSim JAR, found {len(neqsim_jars)}\")\n",
"\n",
- "neqsim_jar = neqsim_jars[0]\n",
- "neqsim_jar_sha256 = hashlib.sha256(neqsim_jar.read_bytes()).hexdigest()\n",
+ "packaged_neqsim_jar = neqsim_jars[0]\n",
+ "neqsim_jar = diagram_runtime_jar\n",
+ "neqsim_jar_sha256 = diagram_runtime_sha256\n",
"java_version = str(jpype.java.lang.System.getProperty(\"java.version\"))\n",
"\n",
"runtime_provenance = pd.DataFrame(\n",
" {\n",
- " \"Item\": [\"NeqSim\", \"Python\", \"Java\", \"Packaged JAR\", \"JAR SHA-256\"],\n",
+ " \"Item\": [\n",
+ " \"NeqSim Python package\",\n",
+ " \"Python\",\n",
+ " \"Java\",\n",
+ " \"Runtime JAR\",\n",
+ " \"JAR SHA-256\",\n",
+ " \"Java commit\",\n",
+ " ],\n",
" \"Value\": [\n",
" neqsim_version,\n",
" platform.python_version(),\n",
" java_version,\n",
" neqsim_jar.name,\n",
" neqsim_jar_sha256,\n",
+ " NEQSIM_DIAGRAM_COMMIT,\n",
" ],\n",
" }\n",
")\n",
@@ -218,7 +313,7 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": 3,
"id": "07a99388",
"metadata": {
"id": "rFJX-3wpVIv3"
@@ -263,7 +358,7 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": 4,
"id": "bbd6d508",
"metadata": {},
"outputs": [
@@ -330,7 +425,7 @@
"3 Pressure 50.0 bara"
]
},
- "execution_count": 3,
+ "execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
@@ -536,7 +631,7 @@
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": 5,
"id": "ce9b6de5",
"metadata": {},
"outputs": [
@@ -610,7 +705,7 @@
"4 Oil density 708.9682 kg/m3"
]
},
- "execution_count": 4,
+ "execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
@@ -673,7 +768,7 @@
},
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": 6,
"id": "489498db",
"metadata": {},
"outputs": [
@@ -743,7 +838,7 @@
"2 Critical point 632.828 179.840"
]
},
- "execution_count": 5,
+ "execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
@@ -835,7 +930,7 @@
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": 7,
"id": "615aed09",
"metadata": {},
"outputs": [],
@@ -912,7 +1007,7 @@
},
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": 8,
"id": "b46addc3",
"metadata": {},
"outputs": [],
@@ -946,9 +1041,23 @@
" \"VLV-100\", first_stage_separator.getOilOutStream()\n",
" )\n",
"\n",
+ " # Named numerical recycle destinations used by the current Java reference.\n",
+ " # These Stream objects are convergence utilities, not physical equipment.\n",
+ " dew_point_liquid_reflux_1 = wellstream.clone()\n",
+ " dew_point_liquid_reflux_1.setName(\"dew point liquid reflux 1\")\n",
+ " dew_point_liquid_reflux_1.setFlowRate(10.0, \"kg/hr\")\n",
+ " dew_point_liquid_reflux_1.setPressure(9.5, \"bara\")\n",
+ "\n",
+ " dew_point_liquid_reflux_2 = wellstream.clone()\n",
+ " dew_point_liquid_reflux_2.setName(\"dew point liquid reflux 2\")\n",
+ " dew_point_liquid_reflux_2.setFlowRate(10.0, \"kg/hr\")\n",
+ " dew_point_liquid_reflux_2.setPressure(9.5, \"bara\")\n",
+ "\n",
" # Mixer for second-stage inlet\n",
" oil_2nd_stage_mixer = neqsim.process.equipment.mixer.Mixer(\"MIX-101\")\n",
" oil_2nd_stage_mixer.addStream(oilvalve1.getOutStream())\n",
+ " oil_2nd_stage_mixer.addStream(dew_point_liquid_reflux_1)\n",
+ " oil_2nd_stage_mixer.addStream(dew_point_liquid_reflux_2)\n",
"\n",
" # Heater\n",
" oilHeaterFromFirstStage = neqsim.process.equipment.heatexchanger.Heater(\n",
@@ -1001,10 +1110,10 @@
" )\n",
"\n",
" # Recycle\n",
- " lp_resycle = neqsim.process.equipment.util.Recycle(\"LP oil resycle\")\n",
- " lp_resycle.addStream(firststagescrubberpump.getOutStream())\n",
- " lp_resycle.setOutletStream(oilreflux)\n",
- " lp_resycle.setTolerance(1e-6)\n",
+ " lp_recycle = neqsim.process.equipment.util.Recycle(\"LP oil recycle\")\n",
+ " lp_recycle.addStream(firststagescrubberpump.getOutStream())\n",
+ " lp_recycle.setOutletStream(oilreflux)\n",
+ " lp_recycle.setTolerance(1e-6)\n",
"\n",
" # First-stage recompressor\n",
" first_stage_recompressor = neqsim.process.equipment.compressor.Compressor(\n",
@@ -1051,8 +1160,11 @@
" \"23-VG-01\", dew_point_cooler.getOutStream()\n",
" )\n",
"\n",
- " # Add dew point scrubber liquid to second-stage mixer\n",
- " oil_2nd_stage_mixer.addStream(dew_point_scrubber.getLiquidOutStream())\n",
+ " # Make the first dew-point liquid return explicit for stable convergence.\n",
+ " dew_point_recycle_1 = neqsim.process.equipment.util.Recycle(\"dew point recycle 1\")\n",
+ " dew_point_recycle_1.addStream(dew_point_scrubber.getLiquidOutStream())\n",
+ " dew_point_recycle_1.setOutletStream(dew_point_liquid_reflux_1)\n",
+ " dew_point_recycle_1.setTolerance(1e-6)\n",
"\n",
" # First stage export compressor\n",
" first_stage_export_compressor = neqsim.process.equipment.compressor.Compressor(\n",
@@ -1070,8 +1182,11 @@
" \"24-VG-01\", dew_point_cooler2.getOutStream()\n",
" )\n",
"\n",
- " # Add the liquid to second-stage mixer\n",
- " oil_2nd_stage_mixer.addStream(dew_point_scrubber2.getLiquidOutStream())\n",
+ " # Make the second dew-point liquid return explicit for stable convergence.\n",
+ " dew_point_recycle_2 = neqsim.process.equipment.util.Recycle(\"dew point recycle 2\")\n",
+ " dew_point_recycle_2.addStream(dew_point_scrubber2.getLiquidOutStream())\n",
+ " dew_point_recycle_2.setOutletStream(dew_point_liquid_reflux_2)\n",
+ " dew_point_recycle_2.setTolerance(1e-6)\n",
"\n",
" # Gas splitter\n",
" gas_splitter = neqsim.process.equipment.splitter.Splitter(\n",
@@ -1140,6 +1255,8 @@
" oilprocess.add(well_stream_cooler)\n",
" oilprocess.add(first_stage_separator)\n",
" oilprocess.add(oilvalve1)\n",
+ " oilprocess.add(dew_point_liquid_reflux_1)\n",
+ " oilprocess.add(dew_point_liquid_reflux_2)\n",
" oilprocess.add(oil_2nd_stage_mixer)\n",
" oilprocess.add(oilHeaterFromFirstStage)\n",
" oilprocess.add(seccond_stage_separator)\n",
@@ -1151,7 +1268,7 @@
" oilprocess.add(firstStageCooler)\n",
" oilprocess.add(firstStageScrubber)\n",
" oilprocess.add(firststagescrubberpump)\n",
- " oilprocess.add(lp_resycle)\n",
+ " oilprocess.add(lp_recycle)\n",
" oilprocess.add(first_stage_recompressor)\n",
" oilprocess.add(firststagegasmixer)\n",
" oilprocess.add(seccond_stage_cooler)\n",
@@ -1160,9 +1277,11 @@
" oilprocess.add(exportgasmixer)\n",
" oilprocess.add(dew_point_cooler)\n",
" oilprocess.add(dew_point_scrubber)\n",
+ " oilprocess.add(dew_point_recycle_1)\n",
" oilprocess.add(first_stage_export_compressor)\n",
" oilprocess.add(dew_point_cooler2)\n",
" oilprocess.add(dew_point_scrubber2)\n",
+ " oilprocess.add(dew_point_recycle_2)\n",
" oilprocess.add(gas_splitter)\n",
" oilprocess.add(gas_heatexchanger)\n",
" oilprocess.add(dew_point_cooler3)\n",
@@ -1180,7 +1299,7 @@
},
{
"cell_type": "code",
- "execution_count": 8,
+ "execution_count": 9,
"id": "537272c8",
"metadata": {},
"outputs": [],
@@ -1337,7 +1456,7 @@
},
{
"cell_type": "code",
- "execution_count": 9,
+ "execution_count": 10,
"id": "34a797c6",
"metadata": {},
"outputs": [
@@ -1370,7 +1489,7 @@
" \n",
" | 0 | \n",
" Required named units | \n",
- " 11 | \n",
+ " 14 | \n",
"
\n",
" \n",
" | 1 | \n",
@@ -1383,11 +1502,11 @@
],
"text/plain": [
" Model check Value\n",
- "0 Required named units 11\n",
+ "0 Required named units 14\n",
"1 Missing units 0"
]
},
- "execution_count": 9,
+ "execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
@@ -1403,6 +1522,9 @@
" \"23-KA-02\",\n",
" \"23-KA-01\",\n",
" \"27-KA-01\",\n",
+ " \"LP oil recycle\",\n",
+ " \"dew point recycle 1\",\n",
+ " \"dew point recycle 2\",\n",
" \"export gas\",\n",
" \"export oil\",\n",
" \"fuel gas\",\n",
@@ -1432,7 +1554,7 @@
},
{
"cell_type": "code",
- "execution_count": 10,
+ "execution_count": 11,
"id": "b826d7aa",
"metadata": {},
"outputs": [
@@ -1440,7 +1562,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "Base case completed in 2.82 s\n"
+ "Base case completed in 3.36 s\n"
]
},
{
@@ -1473,10 +1595,10 @@
"
\n",
" \n",
" | 0 | \n",
- " 10431.428486 | \n",
- " 19130.517773 | \n",
- " 2.900716 | \n",
- " 0.000408 | \n",
+ " 10431.534102 | \n",
+ " 19130.542443 | \n",
+ " 2.900748 | \n",
+ " 0.000076 | \n",
"
\n",
" \n",
"\n",
@@ -1484,10 +1606,10 @@
],
"text/plain": [
" total_power_kw oil_export_m3_day gas_export_msm3_day mass_balance_percent\n",
- "0 10431.428486 19130.517773 2.900716 0.000408"
+ "0 10431.534102 19130.542443 2.900748 0.000076"
]
},
- "execution_count": 10,
+ "execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
@@ -1547,6 +1669,3670 @@
"solved object and do not trigger hidden reruns."
]
},
+ {
+ "cell_type": "markdown",
+ "id": "db0f6507",
+ "metadata": {},
+ "source": [
+ "### Fresh model-generated PFD and P&ID acceptance workflow\n",
+ "\n",
+ "The solved `process1` object is the single canonical plant for both profiles. The PFD reports\n",
+ "process topology, operating results, stream identities, balances, and area/sheet structure. The\n",
+ "distinct P&ID is a **review-required teaching proposal** that adds explicit nozzles, valves,\n",
+ "reducers, instruments, control signals, and declared isolation/drain/vent/relief interfaces from\n",
+ "source-linked engineering registers.\n",
+ "\n",
+ "The earlier embedded illustration and preserved Graphviz output remain historical context; neither\n",
+ "is acceptance evidence. To match the accepted reusable Java reference, `getprocess()` now names its\n",
+ "three numerical recycle utilities and uses two named placeholder streams as their convergence\n",
+ "destinations. This makes the existing liquid returns explicit without adding physical process\n",
+ "equipment. The illustrated `24-VB-01` is absent from `getprocess()` and is not created.\n",
+ "Missing line classes, piping specifications, design pressure/temperature, materials, valve failure\n",
+ "positions, instrument ranges, alarm/trip set points, hazardous-area data, relief sizing/disposal,\n",
+ "drain/vent destinations, revision approvals, and accountable discipline sign-off remain explicit\n",
+ "project metadata gaps. Therefore these outputs claim neither completed safety design nor ISO 10628\n",
+ "or ANSI/ISA-5.1 conformance, certification, construction fitness, or engineering approval.\n",
+ "\n",
+ "An independently reconstructed and solved plant must regenerate every delivered byte identically.\n",
+ "Native DEXPI 2.0 Plant/Process outputs and the separately labelled Proteus 4.1 compatibility\n",
+ "companion are intentionally distinct; schema checks are not named-CAE round-trip qualification."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "id": "0a84a3a9",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " Profile | \n",
+ " A1 SVG sheets | \n",
+ " Renderer warnings | \n",
+ " Renderer errors | \n",
+ " Stable regeneration | \n",
+ " PDF SHA-256 | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " PFD | \n",
+ " 4 | \n",
+ " 0 | \n",
+ " 0 | \n",
+ " True | \n",
+ " 020aef2f04f3c9261e846da5ab3a1d66d3a64066a1f1ab... | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " PID | \n",
+ " 4 | \n",
+ " 0 | \n",
+ " 0 | \n",
+ " True | \n",
+ " 8aff1c847a9595592675f8667662c8237d4002bcd8a1b9... | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " Profile ... PDF SHA-256\n",
+ "0 PFD ... 020aef2f04f3c9261e846da5ab3a1d66d3a64066a1f1ab...\n",
+ "1 PID ... 8aff1c847a9595592675f8667662c8237d4002bcd8a1b9...\n",
+ "\n",
+ "[2 rows x 6 columns]"
+ ]
+ },
+ "execution_count": 12,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "import collections\n",
+ "import json\n",
+ "from IPython.display import FileLink, Markdown, SVG, display\n",
+ "\n",
+ "diagram_output_directory = Path(\"comparesimulations2-engineering-diagrams-v1\")\n",
+ "if diagram_output_directory.exists():\n",
+ " raise RuntimeError(\n",
+ " f\"Clean execution requires a new output directory: {diagram_output_directory.resolve()}\"\n",
+ " )\n",
+ "\n",
+ "ReferenceWorkflow = jpype.JClass(\n",
+ " \"neqsim.process.examples.Comparesimulations2EngineeringDiagramReference\"\n",
+ ")\n",
+ "JavaPaths = jpype.JClass(\"java.nio.file.Paths\")\n",
+ "diagram_report = ReferenceWorkflow.deliver(\n",
+ " process1,\n",
+ " JavaPaths.get(str(diagram_output_directory.resolve())),\n",
+ ")\n",
+ "dual_profile_manifest = json.loads(str(diagram_report.toJson()))\n",
+ "\n",
+ "assert process1.getUnit(\"24-VB-01\") is None\n",
+ "assert dual_profile_manifest[\"pfdContentProfile\"] == \"PFD\"\n",
+ "assert dual_profile_manifest[\"pidContentProfile\"] == \"PID\"\n",
+ "assert dual_profile_manifest[\"pfdDexpiInformationModel\"] == \"DEXPI_2_0_PROCESS\"\n",
+ "assert dual_profile_manifest[\"pidNativeDexpiInformationModel\"] == \"DEXPI_2_0_PLANT_P_ID\"\n",
+ "assert dual_profile_manifest[\"pidProteusCompatibilityProfile\"] == \"PROTEUS_4_1\"\n",
+ "\n",
+ "# Reconstruct and solve the canonical plant independently, then require every\n",
+ "# generated byte (including manifests, SVG, PDF, and exchange companions) to match.\n",
+ "repeat_output_directory = Path(\"comparesimulations2-engineering-diagrams-repeat\")\n",
+ "if repeat_output_directory.exists():\n",
+ " raise RuntimeError(\n",
+ " f\"Clean execution requires a new output directory: {repeat_output_directory.resolve()}\"\n",
+ " )\n",
+ "repeat_process = getprocess()\n",
+ "repeat_result = run_simulation(repeat_process, inputparam)\n",
+ "assert abs(repeat_result.mass_balance_percent) < 1.0e-2\n",
+ "assert repeat_process.getUnit(\"24-VB-01\") is None\n",
+ "ReferenceWorkflow.deliver(\n",
+ " repeat_process,\n",
+ " JavaPaths.get(str(repeat_output_directory.resolve())),\n",
+ ")\n",
+ "\n",
+ "\n",
+ "def artifact_hashes(root):\n",
+ " return {\n",
+ " str(path.relative_to(root)): hashlib.sha256(path.read_bytes()).hexdigest()\n",
+ " for path in sorted(root.rglob(\"*\"))\n",
+ " if path.is_file()\n",
+ " }\n",
+ "\n",
+ "\n",
+ "primary_artifact_hashes = artifact_hashes(diagram_output_directory)\n",
+ "repeat_artifact_hashes = artifact_hashes(repeat_output_directory)\n",
+ "assert primary_artifact_hashes == repeat_artifact_hashes\n",
+ "\n",
+ "\n",
+ "def collect_diagnostics(value):\n",
+ " rows = []\n",
+ " if isinstance(value, dict):\n",
+ " if \"severity\" in value and \"code\" in value:\n",
+ " rows.append(value)\n",
+ " for nested in value.values():\n",
+ " rows.extend(collect_diagnostics(nested))\n",
+ " elif isinstance(value, list):\n",
+ " for nested in value:\n",
+ " rows.extend(collect_diagnostics(nested))\n",
+ " return rows\n",
+ "\n",
+ "\n",
+ "profile_rows = []\n",
+ "profile_fingerprints = {}\n",
+ "for profile in (\"pfd\", \"pid\"):\n",
+ " profile_directory = diagram_output_directory / profile\n",
+ " manifest_path = profile_directory / \"delivery-manifest.json\"\n",
+ " profile_manifest = json.loads(manifest_path.read_text())\n",
+ " renderer_diagnostics = [\n",
+ " row\n",
+ " for row in collect_diagnostics(profile_manifest)\n",
+ " if str(row[\"code\"]).startswith(\"DIAGRAM_RENDER_\")\n",
+ " ]\n",
+ " severity_counts = collections.Counter(row[\"severity\"] for row in renderer_diagnostics)\n",
+ " assert severity_counts[\"ERROR\"] == 0\n",
+ " assert severity_counts[\"WARNING\"] == 0\n",
+ " svg_pages = sorted((profile_directory / \"svg\").glob(\"*.svg\"))\n",
+ " assert len(svg_pages) == 4\n",
+ " assert (profile_directory / \"drawing-set.pdf\").is_file()\n",
+ " profile_fingerprints[profile] = profile_manifest[\"sourceGraphFingerprint\"]\n",
+ " profile_rows.append(\n",
+ " {\n",
+ " \"Profile\": profile.upper(),\n",
+ " \"A1 SVG sheets\": len(svg_pages),\n",
+ " \"Renderer warnings\": severity_counts[\"WARNING\"],\n",
+ " \"Renderer errors\": severity_counts[\"ERROR\"],\n",
+ " \"Stable regeneration\": primary_artifact_hashes == repeat_artifact_hashes,\n",
+ " \"PDF SHA-256\": next(\n",
+ " row[\"sha256\"]\n",
+ " for row in profile_manifest[\"artifacts\"]\n",
+ " if row[\"relativePath\"] == \"drawing-set.pdf\"\n",
+ " ),\n",
+ " }\n",
+ " )\n",
+ "\n",
+ "assert profile_fingerprints[\"pfd\"] == profile_fingerprints[\"pid\"]\n",
+ "diagram_acceptance_table = pd.DataFrame(profile_rows)\n",
+ "diagram_acceptance_table"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "id": "843b6100",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/markdown": [
+ "## Fresh PFD — four A1 sheets"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "comparesimulations2-engineering-diagrams-v1/pfd/drawing-set.pdf
"
+ ],
+ "text/plain": [
+ "/workspace/scratch/93e2db7bed40/neqsim-colab/comparesimulations2-engineering-diagrams-v1/pfd/drawing-set.pdf"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/markdown": [
+ "### PFD sheet 1: `sheet-pfd-andreasen-001-andreasen-separation-compression-process-system-2f2092b7d75c.svg`"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": [
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/markdown": [
+ "### PFD sheet 2: `sheet-pfd-andreasen-001-export-aacfac77a522.svg`"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": [
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/markdown": [
+ "### PFD sheet 3: `sheet-pfd-andreasen-001-recompression-4a1fda4475b1.svg`"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": [
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/markdown": [
+ "### PFD sheet 4: `sheet-pfd-andreasen-001-separation-dbe20beb7f27.svg`"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": [
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/markdown": [
+ "## Fresh PID — four A1 sheets"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "comparesimulations2-engineering-diagrams-v1/pid/drawing-set.pdf
"
+ ],
+ "text/plain": [
+ "/workspace/scratch/93e2db7bed40/neqsim-colab/comparesimulations2-engineering-diagrams-v1/pid/drawing-set.pdf"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/markdown": [
+ "### PID sheet 1: `sheet-pid-andreasen-001-andreasen-separation-compression-process-system-a7eb8354abad.svg`"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": [
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/markdown": [
+ "### PID sheet 2: `sheet-pid-andreasen-001-export-4e83a56f46ef.svg`"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": [
+ "