diff --git a/.gitignore b/.gitignore index 8336a20..af3f25d 100644 --- a/.gitignore +++ b/.gitignore @@ -173,21 +173,22 @@ dune-* opm-* # Examples outputs -debugging -developing -playground -project +/debugging +/developing +/playground +/project +/lab # Tests -test_outputs +/test_outputs # Paper figures -figure3 -figure4 -figures5-8 -figuresb2-3 -figures5d-f -figuresb2d-f +/figure3 +/figure4 +/figures5-8 +/figuresb2-3 +/figures5d-f +/figuresb2d-f **/publication/*/ -publication/random_seed.toml -publication/base.toml +/publication/random_seed.toml +/publication/base.toml diff --git a/README.md b/README.md index 7bcb828..c5f2d23 100644 --- a/README.md +++ b/README.md @@ -27,15 +27,13 @@ To install the _pofff_ executable from the development version: pip install git+https://github.com/cssr-tools/pofff.git ``` -If you are interested in a specific version (e.g., v2026.04) or in modifying the source code, then you can clone the repository and install the Python requirements in a virtual environment with the following commands: +If you are interested in modifying the source code, then you can clone the repository and install the Python requirements in a virtual environment with the following commands: ```bash # Clone the repo git clone https://github.com/cssr-tools/pofff.git # Get inside the folder cd pofff -# For a specific version (e.g., v2026.04), or skip this step (i.e., edge version) -git checkout v2026.04 # Create virtual environment (to specific Python, python3.13 -m venv vpofff) python3 -m venv vpofff # Activate virtual environment diff --git a/dev-requirements.txt b/dev-requirements.txt index e4cd733..aff1908 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,9 +1,13 @@ black +numpydoc mypy +pydata_sphinx_theme pylint pytest-cov pytest-xdist ruff sphinx +sphinx_copybutton +sphinx_design sphinx-rtd-theme sphinxcontrib-video diff --git a/docs/Makefile b/docs/Makefile index 08a5949..4ecdc1a 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,25 +1,21 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. SPHINXOPTS = SPHINXBUILD = sphinx-build SOURCEDIR = text BUILDDIR = _build +APIDIR = $(SOURCEDIR)/api -# Put it first so that "make" without argument is like "make help". +.PHONY: help clean api html linkcheck github help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - sphinx-apidoc --private -e -f -o text ../src/pofff - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -github: - @make html - cp -a _build/html/. . +clean: + rm -rf "$(BUILDDIR)" "$(APIDIR)" +api: + rm -rf "$(APIDIR)" + mkdir -p "$(APIDIR)" + sphinx-apidoc --private -e -f -o "$(APIDIR)" ../src/pofff +html: api + @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) +linkcheck: api + @$(SPHINXBUILD) -M linkcheck "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) +github: html + cp -a "$(BUILDDIR)/html/." . diff --git a/docs/_modules/index.html b/docs/_modules/index.html new file mode 100644 index 0000000..c85aaf3 --- /dev/null +++ b/docs/_modules/index.html @@ -0,0 +1,593 @@ + + + + + + + + + + Overview: module code — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + +
+ +
+ + + + + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/docs/_modules/pofff/config/config.html b/docs/_modules/pofff/config/config.html new file mode 100644 index 0000000..e4ac1f0 --- /dev/null +++ b/docs/_modules/pofff/config/config.html @@ -0,0 +1,833 @@ + + + + + + + + + + pofff.config.config — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for pofff.config.config

+# SPDX-FileCopyrightText: 2023-2026 NORCE Research AS
+# SPDX-License-Identifier: GPL-3.0
+
+"""Configuration and runtime settings shared across pofff workflows.
+
+The data classes combine raw command-line selections, validated TOML input,
+FluidFlower geometry, OPM deck arrays, history-matching options, and values
+derived while grids, simulations, and benchmark products are created.
+``PofffConfig`` is mutable because paths, cell indices, properties, and runtime
+flags are populated progressively."""
+
+from collections.abc import Callable, Iterable, Sequence
+from dataclasses import dataclass, field
+from pathlib import Path
+from typing import Any
+
+import numpy as np
+
+
+
+[docs] +@dataclass(slots=True) +class CliConfig: + """Store command-line values before TOML normalization. + + Attributes + ---------- + fol + Base output directory. + deck + Directory used for generated OPM deck files. + jobs + Directory used for generated ERT and Everest job scripts. + experiment + FluidFlower experimental realization, normalized as ``run1`` through ``run5``. + times + Comma-separated benchmark evaluation times in hours. + msat + Minimum gas-saturation threshold used for segmentation. + mcon + Minimum dissolved-CO2 concentration threshold used for segmentation. + mode + Selected simulation, file-generation, history-matching, or FAIR workflow. + figures + Figure mode: ``all``, ``basic``, or ``none``. + location + Directory containing comparison or precomputed benchmark data. + use + Whether precomputed Wasserstein distances may be used.""" + + fol: Path + deck: Path + jobs: Path + experiment: str + times: str + msat: str + mcon: str + mode: str + figures: str + location: str + use: str
+ + + +
+[docs] +@dataclass(slots=True) +class PofffConfig: + """Store TOML input, CLI options, and derived pofff runtime state. + + TOML-backed attributes retain the spelling used by existing configuration files. + Derived arrays, grid dimensions, feature indices, paths, and history-matching + flags are populated while input is normalized and model files are generated. + + Attributes + ---------- + path + Package root containing geology, templates, jobs, and benchmark resources. + fol, deck, jobs + Base output, generated deck, and generated job-script directories. + experiment, times, msat, mcon + Experimental realization, evaluation times [h], and segmentation thresholds. + mode, figures, location, use + Workflow, figure selection, comparison-data location, and reuse selection. + flow + OPM Flow command and command-line options. + grid + Grid representation: ``cartesian``, ``tensor``, or ``corner-point``. + thickness + ``initial`` or ``final`` thickness map, or a positive physical thickness [m]. + mult_thickness + Positive multiplier applied to the selected thickness map. + x, z + Horizontal and vertical refinement counts. + temperature + Initial and boundary temperatures used by the deck. + pressure + Positive reference pressure used for initialization and boundary properties. + diffusion + Two molecular diffusion coefficients, converted from m²/s to m²/day. + sources + Two injection-source coordinates as ``[x, z]`` rows [m]. + inj + Injection rows containing times, rates, and optional TUNING text. + krw, krn, cap + Python expressions used to generate saturation-function tables. + cores, maxtime, delete + Shared ERT/Everest resources, run timeout, and cleanup selection. + ertargs, ensembles, enkf_alpha, errors, random_seed + ERT command options, ensemble controls, observation errors, and random seed. + min_realizations_success, max_function_evaluations, max_batch_num + Shared success requirement and Everest evaluation limits. + strategy, maxiter, popsize, tol, mutation, recombination + Differential-evolution strategy and convergence settings. + rng, callback, disp, polish, init, atol, updating, workers + Additional differential-evolution options passed to Everest. + constraints, x0, integrality, vectorized + Optional differential-evolution constraints and evaluation controls. + facies, fluxnum, fipnum, porv, multpv, dx, dz + Generated facies, region, pore-volume, and grid-size arrays for OPM input. + dims + FluidFlower dimensions in x, y, and z order [m]. + sensors, sensor_ik + Sensor coordinates [m] and zero-based grid indices. + source_ik + One-based grid indices of the two injection sources. + boxa, boxb, boxc + Opposite ``[x, z]`` corners of the benchmark reporting boxes [m]. + hm, para + History-matching definitions and fixed facies properties. + monotonic, hascellmaps, everert, tuning + Derived workflow and file-generation flags. + nxz + Numbers of simulation cells in x and z order. + data + Uppercase OPM deck base name derived from the output directory.""" + + # ------------------------------------------------------------------ + # CLI configuration (normalized) + # ------------------------------------------------------------------ + path: Path + fol: Path = Path("output") + deck: Path = Path("output") + jobs: Path = Path("output") + experiment: str = "run2" + times: str = "0.25" + msat: str = "1e-2" + mcon: str = "1e-1" + mode: str = "single" + figures: str = "basic" + location: str = "" + use: str = "1" + + # ------------------------------------------------------------------ + # TOML configuration (simulation setup) + # ------------------------------------------------------------------ + flow: str = "flow" + grid: str = "corner-point" + thickness: str = "final" + mult_thickness: float = 1.0 + x: list[int] = field(default_factory=list) + z: list[int] = field(default_factory=list) + temperature: list[float] = field(default_factory=lambda: [20, 20]) + pressure: float = 0.0 + diffusion: np.ndarray = field(default_factory=lambda: np.array([1e-9, 2e-8])) + sources: list[list[float]] = field(default_factory=lambda: [[0, 0], [0, 0]]) + inj: list[list[Any]] = field(default_factory=lambda: [[]]) + krw: str = "(max(0, (sw - swi) / (1 - swi))) ** nkrw" + krn: str = "(max(0, (1 - sw - sni) / (1 - sni))) ** nkrn" + cap: str = "pen * ((sw-swi) / (1-swi)) ** (-(1.0 / npen))" + + # ------------------------------------------------------------------ + # TOML configuration (ERT / everest runtime) + # ------------------------------------------------------------------ + cores: int | None = None + maxtime: float | None = None + delete: bool | None = None + + # ------------------------------------------------------------------ + # TOML configuration (ERT) + # ------------------------------------------------------------------ + ertargs: str | None = None + ensembles: int | None = None + enkf_alpha: float | None = None + errors: np.ndarray | None = None + random_seed: int | None = None # Used by scipy differential evolution + + # ------------------------------------------------------------------ + # TOML configuration (everest) + # ------------------------------------------------------------------ + min_realizations_success: int | None = None + max_function_evaluations: int | None = None + max_batch_num: int | None = None + + # ------------------------------------------------------------------ + # TOML differential evolution parameters (via everest) + # ------------------------------------------------------------------ + args: tuple[Any, ...] | None = None + strategy: str | None = None + maxiter: int | None = None + popsize: int | None = None + tol: float | None = None + mutation: float | tuple[float, float] | None = None + recombination: float | None = None + rng: Any | None = None + callback: Callable | None = None + disp: bool | None = None + polish: bool | None = None + init: str | None = None + atol: float | None = None + updating: str | None = None + workers: int | None = None + constraints: Iterable[Any] | None = None + x0: Sequence[float] | None = None + integrality: Sequence[bool] | None = None + vectorized: bool | None = None + + # ------------------------------------------------------------------ + # OPM input arrays + # ------------------------------------------------------------------ + facies: list[int] = field(default_factory=list) + fluxnum: list[str] = field(default_factory=list) + fipnum: list[str] = field(default_factory=list) + porv: list[str] = field(default_factory=list) + multpv: list[str] = field(default_factory=list) + dx: list | None = field(default_factory=list) + dz: list | None = field(default_factory=list) + + # ------------------------------------------------------------------ + # FluidFlower geometry and observation setup + # ------------------------------------------------------------------ + dims: list[float] = field(default_factory=lambda: [2.8, 0.019, 1.2]) + sensors: list[list[float]] = field(default_factory=lambda: [[1.5, 0.5], [1.7, 1.1]]) + sensor_ik: list[list[int]] = field(default_factory=lambda: [[0, 0], [0, 0]]) + source_ik: list[list[int]] = field(default_factory=lambda: [[0, 0], [0, 0]]) + boxa: list[list[float]] = field(default_factory=lambda: [[1.1, 0.0], [2.8, 0.6]]) + boxb: list[list[float]] = field(default_factory=lambda: [[0.0, 0.6], [1.1, 1.2]]) + boxc: list[list[float]] = field(default_factory=lambda: [[1.1, 0.1], [2.6, 0.4]]) + + # ------------------------------------------------------------------ + # Miscellaneous runtime flags and metadata + # ------------------------------------------------------------------ + hm: dict[str, Any] = field(default_factory=dict) + monotonic: bool = False + hascellmaps: bool = False + everert: bool = False + tuning: bool = False + para: dict[str, Any] = field(default_factory=dict) + nxz: list[int] = field(default_factory=lambda: [0, 0]) + data: str | None = None
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/docs/_modules/pofff/core/pofff.html b/docs/_modules/pofff/core/pofff.html new file mode 100644 index 0000000..93b2412 --- /dev/null +++ b/docs/_modules/pofff/core/pofff.html @@ -0,0 +1,1009 @@ + + + + + + + + + + pofff.core.pofff — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for pofff.core.pofff

+# SPDX-FileCopyrightText: 2025-2026 NORCE Research AS
+# SPDX-License-Identifier: GPL-3.0
+# pylint: disable=R0912
+
+"""Command-line entry point and top-level workflow coordination for pofff.
+
+pofff supports connected workflows for FluidFlower history matching:
+
+* File generation creates OPM Flow, ERT, and Everest input.
+* Single execution runs OPM Flow and generates benchmark data.
+* History matching runs ERT ensemble studies or Everest optimization.
+* Postprocessing creates benchmark maps, metrics, and comparison figures.
+
+This module parses and validates CLI arguments, builds the shared configuration,
+prepares output folders, dispatches the selected workflows, and reports results.
+Grid construction, file writing, execution, and visualization are implemented in
+the utility and visualization modules."""
+
+import argparse
+import math
+import os
+import shutil
+from pathlib import Path
+
+from pofff.config.config import CliConfig, PofffConfig
+from pofff.utils.inputvalues import build_config, load_toml, postprocess_config
+from pofff.utils.mapproperties import grid_and_properties
+from pofff.utils.runs import benchmark, data, ert, everest, flow, postprocess
+from pofff.utils.terminal import (
+    cli_correct_value,
+    cli_error_value,
+    pofff_error,
+    pofff_info,
+    pofff_success,
+    pofff_tip,
+)
+from pofff.utils.writefile import write_files
+
+
+
+[docs] +def main(argv: list[str] | None = None) -> None: + """Main pofff execution routine. + + The selected file-generation, simulation, data, history-matching, and + plotting stages are executed in dependency order. + + Parameters + ---------- + argv : list[str] | None, optional + Arguments to parse instead of the process command line.""" + args = _parse_arguments(argv) + _validate_arguments(args) + pofff_path = Path(__file__).resolve().parents[1] + + cli = _build_cli_config(args, pofff_path=pofff_path) + + if cli.figures == "none" and cli.mode == "none": + pofff_info("nothing to do because -m none and -f none were selected.") + return + + cli.fol.mkdir(parents=True, exist_ok=True) + + if cli.mode == "fair": + cfg = build_config(pofff_path=pofff_path, cli=cli, toml={}) + cfg.path = pofff_path + cfg.figures = "all" + cfg.times = "24,48,72,96,120" + cfg.experiment = "run2" + + elif cli.mode == "none": + cfg = build_config(pofff_path=pofff_path, cli=cli, toml={}) + _prepare_simulation(cfg) + + else: + toml = load_toml(args.input, args.mode) + cfg = build_config(pofff_path=pofff_path, cli=cli, toml=toml.copy()) + postprocess_config(cfg, toml) + _prepare_simulation(cfg) + + _run_simulation_steps(cfg) + + if cfg.figures in {"basic", "all"} and cfg.mode != "files": + _generate_figures(cfg) + + msg = ( + "The files have been written to" + if cfg.mode == "files" + else "The results have been written to" + ) + pofff_success(f"{msg.lower()} ", str(cfg.fol), [])
+ + + +
+[docs] +def _build_cli_config(args: argparse.Namespace, *, pofff_path: Path) -> CliConfig: + """Build normalized CLI configuration object. + + Parameters + ---------- + args : argparse.Namespace + Parsed command-line arguments. + pofff_path : Path + Package root containing templates, geology, jobs, and benchmark data. + + Returns + ------- + CliConfig + Normalized command-line configuration.""" + output = Path(args.output).absolute() + + location = ( + pofff_path + / "fluidflower" + / "cssr" + / f"conmin{'5e-2' if float(args.minimumconcentration) == 5e-2 else '1e-1'}" + ) + + return CliConfig( + fol=output, + deck=output, + jobs=output, + experiment=f"run{args.experiment[-1]}", + times=args.times, + msat=args.minimumsaturation, + mcon=args.minimumconcentration, + mode=args.mode, + figures=args.figures, + location=str(location), + use=args.use, + )
+ + + +
+[docs] +def _prepare_simulation(cfg: PofffConfig) -> None: + """Prepare directories and generate all input files. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" + if cfg.mode == "none": + cfg.deck = cfg.deck / "deck" + cfg.jobs = cfg.jobs / "jobs" + elif cfg.mode in {"files", "ert", "everest", "data"} and cfg.everert: + cfg.deck = cfg.deck / "deck" + cfg.jobs = cfg.jobs / "jobs" + for sub in ("deck", "jobs"): + (cfg.fol / sub).mkdir(parents=True, exist_ok=True) + + if cfg.mode in ["everest", "ert"] or (cfg.everert and cfg.mode == "files"): + _prepare_jobs_folder(cfg) + + if cfg.mode not in {"data", "none"}: + pofff_info("generating the input files, please wait...") + grid_and_properties(cfg) + write_files(cfg)
+ + + +
+[docs] +def _prepare_jobs_folder(cfg: PofffConfig) -> None: + """Copy job scripts into output/jobs and make them executable. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" + src = cfg.path / "jobs" + dst = cfg.fol / "jobs" + shutil.copytree(src, dst, dirs_exist_ok=True) + + for script in ("data", "delete", "metric"): + script_path = dst / f"{script}.py" + if script_path.exists(): + script_path.chmod(0o755)
+ + + +
+[docs] +def _run_simulation_steps(cfg: PofffConfig) -> None: + """Run the selected execution mode. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" + os.chdir(cfg.fol) + + if cfg.mode == "single": + pofff_info("running the simulation, please wait...") + flow(cfg) + pofff_info("generating the data, please wait...") + data(cfg) + elif cfg.mode == "data": + data(cfg) + elif cfg.mode == "ert": + ert(cfg) + elif cfg.mode == "everest": + everest() + elif cfg.mode in {"files", "fair", "none"}: + pass + else: + pofff_error(f"unknown mode {cli_error_value(f'-m {cfg.mode}')}.")
+ + + +
+[docs] +def _generate_figures(cfg: PofffConfig) -> None: + """Generate benchmark plots and comparison figures. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" + if shutil.which("latex") is None: + pofff_tip( + "LaTeX is recommended for high-quality figures. " + "See the pofff documentation for installation instructions." + ) + if (cfg.fol / "jobs").exists(): + postprocess(cfg) + + figures_dir = cfg.fol / "figures" / "best_simulation" + if figures_dir.exists(): + os.chdir(figures_dir) + else: + os.chdir(cfg.fol) + + pofff_info("generating the benchmark files, please wait...") + benchmark(cfg)
+ + + +
+[docs] +def _parse_arguments(argv: list[str] | None = None) -> argparse.Namespace: + """Define and parse command-line arguments. + + Parameters + ---------- + argv : list[str] | None, optional + Arguments to parse instead of the process command line. + + Returns + ------- + argparse.Namespace + Parsed command-line arguments or the corresponding runtime configuration.""" + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description=( + "pofff, a Python tool for history matching FluidFlower images " + "using OPM Flow, ERT, and Everest." + ), + ) + parser.add_argument( + "-i", + "--input", + type=str.strip, + default="input.toml", + help="Input TOML configuration file", + ) + parser.add_argument( + "-o", + "--output", + type=str.strip, + default="output", + help="Output directory name", + ) + parser.add_argument( + "-f", + "--figures", + type=str.strip, + choices=["all", "basic", "none"], + default="basic", + help="Figure generation mode: 'all', 'basic', or 'none' " + "('basic' skips Wasserstein plots)", + ) + parser.add_argument( + "-m", + "--mode", + type=str.strip, + choices=["single", "files", "data", "everest", "ert", "fair", "none"], + default="single", + help="Execution mode", + ) + parser.add_argument( + "-t", + "--times", + type=str.strip, + default="0.25", + help="Evaluation times in hours, comma-separated", + ) + parser.add_argument( + "-e", + "--experiment", + type=str.strip, + choices=["C1", "C2", "C3", "C4", "C5"], + default="C2", + help="Experimental dataset", + ) + parser.add_argument( + "-s", + "--minimumsaturation", + type=str.strip, + default="1e-2", + help="Minimum gas saturation threshold", + ) + parser.add_argument( + "-c", + "--minimumconcentration", + type=str.strip, + default="1e-1", + help="Minimum dissolved CO2 concentration threshold", + ) + parser.add_argument( + "-u", + "--use", + type=str.strip, + choices=["0", "1"], + default="1", + help="Use precomputed Wasserstein distances if available " + "(set to '0' to recompute)", + ) + return parser.parse_args(argv)
+ + + +
+[docs] +def _validate_arguments(cmdargs: argparse.Namespace) -> None: + """Validate command-line arguments and incompatible operations. + + Parameters + ---------- + cmdargs : argparse.Namespace + Parsed command-line arguments. + + Raises + ------ + SystemExit + If an argument is invalid or incompatible with the selected workflow.""" + if not cmdargs.output: + pofff_error( + f"invalid value {cli_error_value('-o')}, the output directory cannot be empty." + ) + mode = cmdargs.mode + if mode not in {"fair", "none"}: + if not cmdargs.input: + pofff_error( + f"invalid value {cli_error_value('-i')}, the input file cannot be empty." + ) + if not cmdargs.input.lower().endswith(".toml"): + pofff_error( + f"invalid extension {cli_error_value(f'-i {cmdargs.input}')}, " + f"expected {cli_correct_value('.toml')}." + ) + times = cmdargs.times + try: + time_values = [float(value.strip()) for value in times.split(",")] + except ValueError: + time_values = [] + if not time_values or any( + not math.isfinite(value) or value <= 0 for value in time_values + ): + pofff_error( + f"invalid value {cli_error_value(f'-t {times}')}, expected positive " + f"finite numbers separated by commas, {cli_correct_value('e.g., -t 24,48,72')}." + ) + minimum_saturation = cmdargs.minimumsaturation + try: + minimum_saturation_value = float(minimum_saturation) + except ValueError: + minimum_saturation_value = float("nan") + if ( + not math.isfinite(minimum_saturation_value) + or minimum_saturation_value < 0 + or minimum_saturation_value > 1 + ): + pofff_error( + f"invalid value {cli_error_value(f'-s {minimum_saturation}')}, expected a " + f"finite number in {cli_correct_value('[0, 1]')}." + ) + minimum_concentration = cmdargs.minimumconcentration + try: + minimum_concentration_value = float(minimum_concentration) + except ValueError: + minimum_concentration_value = float("nan") + valid_concentrations = [5e-2, 1e-1] + if ( + not math.isfinite(minimum_concentration_value) + or minimum_concentration_value not in valid_concentrations + ): + pofff_error( + f"invalid value {cli_error_value(f'-c {minimum_concentration}')}, expected " + f"{cli_correct_value('5e-2')} or {cli_correct_value('1e-1')}." + ) + if mode == "fair": + ignored_options = { + "-i": ("input", "input.toml"), + "-f": ("figures", "basic"), + "-t": ("times", "0.25"), + "-e": ("experiment", "C2"), + } + invalid_options = [ + option + for option, (name, default) in ignored_options.items() + if getattr(cmdargs, name) != default + ] + if invalid_options: + pofff_error( + f"invalid combination {cli_error_value('-m fair; ' + ', '.join(invalid_options))}; " + "the FAIR workflow uses its own input data, figure mode, evaluation " + "times, and experiment." + ) + if mode == "files" and cmdargs.figures == "all": + pofff_error( + f"invalid combination {cli_error_value('-m files -f all')}; file generation " + f"does not create figures, use {cli_correct_value('-f none')} or omit -f." + )
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/docs/_modules/pofff/jobs/data.html b/docs/_modules/pofff/jobs/data.html new file mode 100644 index 0000000..cfac404 --- /dev/null +++ b/docs/_modules/pofff/jobs/data.html @@ -0,0 +1,1244 @@ + + + + + + + + + + pofff.jobs.data — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for pofff.jobs.data

+#!/usr/bin/env python3
+# SPDX-FileCopyrightText: 2023-2026 NORCE Research AS
+# SPDX-License-Identifier: GPL-3.0
+# pylint: disable=R0914
+
+"""Generate FluidFlower benchmark data from OPM Flow results.
+
+The job locates the current simulation, reads restart, grid, initialization, and
+summary files through the OPM Python bindings, calculates sparse benchmark time
+series, maps saturation and dissolved CO2 to the reporting grid, and writes CSV
+files at the requested evaluation times."""
+
+import argparse
+import sys
+from collections.abc import Iterable
+from dataclasses import dataclass, field
+from pathlib import Path
+
+import numpy as np
+from numpy.typing import NDArray
+from opm.io.ecl import EclFile as OpmFile
+from opm.io.ecl import EGrid as OpmGrid
+from opm.io.ecl import ERst as OpmRestart
+from opm.io.ecl import ESmry as OpmSummary
+from scipy.interpolate import interp1d
+
+from pofff.utils.terminal import cli_error_value, pofff_error
+
+SECONDS_IN_DAY = 86400.0
+GAS_DEN_REF = 1.86843
+WAT_DEN_REF = 998.108
+KMOL_TO_KG = 44.0
+FIP_BOXC = (4, 12, 17, 18)
+FIP_DISS_A = (2, 4, 5, 8)
+FIP_SEAL_A = (5, 8)
+FIP_DISS_B = (3, 6)
+FIP_SEAL_B = (6,)
+
+ArrayLike = float | NDArray
+
+
+
+[docs] +@dataclass(slots=True) +class SimulationContext: + """Store simulation files, time grids, and OPM grid metadata. + + The object is initialized from command-line selections. OPM handles, report + times, active-cell indices, pore volumes, and grid dimensions are populated + after the current simulation base name is located. + + Attributes + ---------- + resolution + Numbers of benchmark reporting cells in x and z order. + dense_t + Requested dense-output times in seconds. + maps + Path to the NumPy mapping from reporting cells to simulation cells. + where + Directory containing simulation files and receiving generated CSV data. + sparse_t + Uniform sparse-output interval in seconds. + dims + FluidFlower dimensions in x, y, and z order, in metres. + sim + Simulation base path without an OPM file extension. + unrst + Open OPM restart result, or ``None`` before results are loaded. + ini + Open OPM initialization result, or ``None`` before results are loaded. + egrid + Open OPM grid result, or ``None`` before results are loaded. + smspec + Open OPM summary result, or ``None`` before results are loaded. + times + Restart times relative to the start of CO2 injection, in seconds. + times_summary + Summary times relative to the start of CO2 injection, in seconds. + time_initial + Absolute simulator time at the start of CO2 injection, in seconds. + porv + Pore volumes in global cell order. + actind + Global indices of cells with positive pore volume. + gxyz + Simulation-grid dimensions in x, y, and z order. + norst + Number of restart report steps.""" + + resolution: NDArray + dense_t: NDArray + maps: Path + where: Path = Path(".") + sparse_t: float = 600.0 + dims: NDArray = field(default_factory=lambda: np.array([2.8, 1.0, 1.2])) + sim: Path | None = None + unrst: OpmRestart | None = None + ini: OpmFile | None = None + egrid: OpmGrid | None = None + smspec: OpmSummary | None = None + times: list[float] = field(default_factory=list) + times_summary: NDArray | None = None + time_initial: float = 0.0 + porv: NDArray | None = None + actind: NDArray | None = None + gxyz: tuple[int, int, int] | None = None + norst: int | None = None
+ + + +
+[docs] +@dataclass(slots=True) +class SparseResults: + """Store sparse FluidFlower quantities before writing the time series. + + The state is initialized by :func:`_initialize_sparse_results` with the uniform + time grid, region identifiers, and cell dimensions. Pressure, mobile, immobile, + dissolved, seal, and migration quantities are then populated from OPM summary + and restart results and interpolated to ``times_data``. + + Attributes + ---------- + ctx + Shared simulation context and OPM file handles. + times_data + Uniform sparse-output times in seconds. + fipnum + FIPNUM values in global cell order. + dx, dz + Cell dimensions in x and z order, in metres. + pop1, pop2 + Pressure time series at the two observation points, in pascals. + moba, imma, dissa, seala + Mobile, immobile, dissolved, and seal CO2 masses in box A, in kilograms. + mobb, immb, dissb, sealb + Mobile, immobile, dissolved, and seal CO2 masses in box B, in kilograms. + sealt + Total CO2 mass in the seal regions, in kilograms. + m_c_series + Mass-center migration values calculated at restart times. + m_c + Mass-center migration values interpolated to ``times_data``, in metres.""" + + ctx: SimulationContext + times_data: NDArray = field(default_factory=lambda: np.empty(0)) + fipnum: NDArray = field(default_factory=lambda: np.empty(0)) + dx: NDArray = field(default_factory=lambda: np.empty(0)) + dz: NDArray = field(default_factory=lambda: np.empty(0)) + pop1: NDArray = field(default_factory=lambda: np.empty(0)) + pop2: NDArray = field(default_factory=lambda: np.empty(0)) + moba: NDArray = field(default_factory=lambda: np.empty(0)) + imma: NDArray = field(default_factory=lambda: np.empty(0)) + dissa: NDArray = field(default_factory=lambda: np.empty(0)) + seala: NDArray = field(default_factory=lambda: np.empty(0)) + mobb: NDArray = field(default_factory=lambda: np.empty(0)) + immb: NDArray = field(default_factory=lambda: np.empty(0)) + dissb: NDArray = field(default_factory=lambda: np.empty(0)) + sealb: NDArray = field(default_factory=lambda: np.empty(0)) + sealt: NDArray = field(default_factory=lambda: np.empty(0)) + m_c_series: list[float] = field(default_factory=list) + m_c: NDArray | None = None
+ + + +
+[docs] +def main(argv: list[str] | None = None) -> None: + """Generate sparse time-series and dense spatial benchmark data. + + Parse reporting-grid settings, locate the OPM simulation, load its results, and + write the benchmark time-series and spatial-map CSV files. + + Parameters + ---------- + argv : list[str] | None, optional + Arguments to parse instead of ``sys.argv[1:]``.""" + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description="Benchmark postprocessing", + ) + parser.add_argument("-r", "--resolution", default="280,120") + parser.add_argument("-t", "--time", default="24,48,72,96,120") + parser.add_argument("-m", "--maps", default="cellmap.npy") + args = vars(parser.parse_args(argv)) + + if Path("NOMONOTONIC").exists(): + sys.exit(1) + + ctx = SimulationContext( + resolution=np.fromstring(args["resolution"], sep=",", dtype=int), + dense_t=np.fromstring(args["time"], sep=",") * 3600.0, + maps=Path(args["maps"]), + ) + + ctx.sim = _find_simulation_base(ctx.where) + _read_opm_results(ctx) + _write_sparse_outputs(ctx) + _write_dense_outputs(ctx)
+ + + +
+[docs] +def _find_simulation_base(path: Path) -> Path: + """Locate the simulation base name from an OPM restart file. + + Parameters + ---------- + path : Path + Directory to search for a ``.UNRST`` file. + + Returns + ------- + Path + Restart-file path without its extension. + + Raises + ------ + SystemExit + If the directory contains no ``.UNRST`` result.""" + for f in path.iterdir(): + if f.suffix == ".UNRST": + return f.with_suffix("") + pofff_error( + f"no {cli_error_value('.UNRST')} simulation result was found in " + f"{cli_error_value(str(path))}." + )
+ + + +
+[docs] +def _read_opm_results(ctx: SimulationContext) -> None: + """Load OPM restart, initialization, grid, and summary results. + + Populate OPM handles, injection-relative times, pore volumes, active-cell + indices, and grid dimensions on ``ctx``. + + Parameters + ---------- + ctx : SimulationContext + Mutable simulation context with an initialized simulation base path.""" + assert ctx.sim + ctx.unrst = OpmRestart(f"{ctx.sim}.UNRST") + + t0: float | None = None + for i in range(len(ctx.unrst.report_steps)): + t = SECONDS_IN_DAY * float(ctx.unrst["DOUBHEAD", i][0]) + + if t0 is None and np.any(ctx.unrst["RSW", i] > 0): + t0 = SECONDS_IN_DAY * float(ctx.unrst["DOUBHEAD", i - 1][0]) + ctx.times.append(0.0) + + if t0 is not None: + ctx.times.append(t - t0) + + ctx.time_initial = t0 or 0.0 + + ini = OpmFile(f"{ctx.sim}.INIT") + egrid = OpmGrid(f"{ctx.sim}.EGRID") + smspec = OpmSummary(f"{ctx.sim}.SMSPEC") + ctx.ini = ini + ctx.egrid = egrid + ctx.smspec = smspec + + porv = np.asarray(ini["PORV"]) + ctx.porv = porv + ctx.actind = np.flatnonzero(porv > 0) + + ctx.times_summary = np.r_[0.0, smspec["TIME"] * SECONDS_IN_DAY] + ctx.gxyz = tuple(egrid.dimension) + ctx.norst = len(ctx.unrst.report_steps)
+ + + +
+[docs] +def _initialize_sparse_results(ctx: SimulationContext) -> SparseResults: + """Initialize sparse time coordinates and OPM cell-property arrays. + + Parameters + ---------- + ctx : SimulationContext + Loaded simulation context with initialization data and restart times. + + Returns + ------- + SparseResults + Sparse state initialized with times, FIPNUM, DX, and DZ arrays. + + Raises + ------ + SystemExit + If initialization data or restart times are unavailable.""" + ini = ctx.ini + if ini is None: + pofff_error("OPM initialization data are unavailable for sparse processing.") + if not ctx.times: + pofff_error("no restart times are available for sparse processing.") + return SparseResults( + ctx=ctx, + times_data=np.arange(0.0, ctx.times[-1] + ctx.sparse_t, ctx.sparse_t), + fipnum=np.asarray(ini["FIPNUM"]), + dx=np.asarray(ini["DX"]), + dz=np.asarray(ini["DZ"]), + )
+ + + +
+[docs] +def _write_sparse_outputs(ctx: SimulationContext) -> None: + """Calculate and write the sparse benchmark time series. + + Parameters + ---------- + ctx : SimulationContext + Loaded simulation context and OPM results.""" + res = _initialize_sparse_results(ctx) + + _extract_summary_quantities(ctx, res) + _compute_mass_center_metric(ctx, res) + _interpolate_sparse_quantities(ctx, res) + _write_time_series_csv(res)
+ + + +
+[docs] +def _get_initial_water_pressure( + unrst: OpmRestart, + fipnum: NDArray, + region: int, +) -> float: + """Return the initial water pressure for one FIP region. + + Parameters + ---------- + unrst : OpmRestart + Open OPM restart result containing pressure and capillary pressure. + fipnum : NDArray + FIPNUM values in global cell order. + region : int + FIPNUM identifying the observation-point region. + + Returns + ------- + float + Water pressure corrected for capillary pressure, in pascals.""" + index = list(fipnum).index(region) + pressure = unrst["PRESSURE", 0][index] + return float((pressure - unrst["PCGW", 0][index]) * 1e5)
+ + + +
+[docs] +def _sum_summary_vectors( + smry: OpmSummary, + expressions: Iterable[str], +) -> NDArray: + """Sum selected OPM summary vectors and convert CO2 to kilograms. + + Parameters + ---------- + smry : OpmSummary + Open OPM summary result containing the requested vectors. + expressions : Iterable[str] + OPM summary-vector names to add. + + Returns + ------- + NDArray + Total mass time series in kilograms.""" + return np.asarray(sum(smry[expression] for expression in expressions)) * KMOL_TO_KG
+ + + +
+[docs] +def _extract_summary_quantities(ctx: SimulationContext, res: SparseResults) -> None: + """Extract pressures and regional CO2 masses from OPM summary vectors. + + Parameters + ---------- + ctx : SimulationContext + Loaded simulation context and OPM summary data. + res : SparseResults + Sparse result arrays populated in place. + + Raises + ------ + SystemExit + If summary or restart results are unavailable.""" + smry = ctx.smspec + unrst = ctx.unrst + if smry is None or unrst is None: + pofff_error("OPM summary and restart data are required for sparse processing.") + smry_keys = smry.keys() + + bwpr = sorted(k for k in smry_keys if k.startswith("BWPR") and "," in k)[:2] + + res.pop1 = np.r_[ + _get_initial_water_pressure(unrst, res.fipnum, 8), + smry[bwpr[0]] * 1e5, + ] + res.pop2 = np.r_[ + _get_initial_water_pressure(unrst, res.fipnum, 9), + smry[bwpr[1]] * 1e5, + ] + + res.moba = _sum_summary_vectors(smry, (f"RGKDM:{i}" for i in FIP_DISS_A)) + res.imma = _sum_summary_vectors(smry, (f"RGKDI:{i}" for i in FIP_DISS_A)) + res.dissa = _sum_summary_vectors(smry, (f"RWCD:{i}" for i in FIP_DISS_A)) + + res.seala = _sum_summary_vectors( + smry, + ( + f"{keyword}:{region}" + for region in FIP_SEAL_A + for keyword in ("RWCD", "RGKDM", "RGKDI") + ), + ) + + res.mobb = _sum_summary_vectors(smry, (f"RGKDM:{i}" for i in FIP_DISS_B)) + res.immb = _sum_summary_vectors(smry, (f"RGKDI:{i}" for i in FIP_DISS_B)) + res.dissb = _sum_summary_vectors(smry, (f"RWCD:{i}" for i in FIP_DISS_B)) + + res.sealb = _sum_summary_vectors( + smry, + ( + f"{keyword}:{region}" + for region in FIP_SEAL_B + for keyword in ("RWCD", "RGKDM", "RGKDI") + ), + ) + + res.sealt = ( + res.seala + + res.sealb + + _sum_summary_vectors( + smry, + ( + f"{keyword}:{region}" + for region in (7, 9) + for keyword in ("RWCD", "RGKDM", "RGKDI") + ), + ) + )
+ + + +
+[docs] +def _compute_mass_center_metric(ctx: SimulationContext, res: SparseResults) -> None: + """Calculate the box-C mass-center migration series. + + Parameters + ---------- + ctx : SimulationContext + Loaded simulation context and restart metadata. + res : SparseResults + Sparse result state receiving migration values. + + Raises + ------ + SystemExit + If restart results or grid metadata are unavailable.""" + unrst = ctx.unrst + if unrst is None or ctx.gxyz is None or ctx.norst is None: + pofff_error("OPM restart metadata are required for the mass-center metric.") + + boxc = np.array([fip in FIP_BOXC for fip in res.fipnum]) + nx, ny, _ = ctx.gxyz + + boxc_x = np.roll(boxc, 1) + boxc_z = np.roll(boxc, -nx * ny) + + for t_n in range(1, ctx.norst): + rss = np.asarray(unrst["RSW", t_n]) + rssat = np.asarray(unrst["RSWSAT", t_n]) + + xcw = rss / (rss + WAT_DEN_REF / GAS_DEN_REF) + xcw /= rssat / (rssat + WAT_DEN_REF / GAS_DEN_REF) + + mc = np.sum( + np.abs(xcw[boxc_x] - xcw[boxc]) * res.dz[boxc] + + np.abs(xcw[boxc_z] - xcw[boxc]) * res.dx[boxc] + ) + res.m_c_series.append(mc)
+ + + +
+[docs] +def _interpolate_sparse_quantities(ctx: SimulationContext, res: SparseResults) -> None: + """Interpolate sparse quantities to a uniform time grid. + + Parameters + ---------- + ctx : SimulationContext + Simulation and summary time coordinates. + res : SparseResults + Sparse quantities replaced by their interpolated arrays.""" + assert ctx.times_summary is not None + + res.m_c = interp1d( + ctx.times, + np.r_[0.0, res.m_c_series], + fill_value="extrapolate", + )(res.times_data) + + for name in ( + "pop1", + "pop2", + "moba", + "imma", + "dissa", + "seala", + "mobb", + "immb", + "dissb", + "sealb", + "sealt", + ): + data = getattr(res, name) + x = ctx.times_summary + y = data if name.startswith("pop") else np.r_[0.0, data] + setattr(res, name, interp1d(x, y, fill_value="extrapolate")(res.times_data))
+ + + +
+[docs] +def _write_time_series_csv(res: SparseResults) -> None: + """Write sparse benchmark quantities to ``time_series.csv``. + + Parameters + ---------- + res : SparseResults + Interpolated pressure, mass, and migration series.""" + assert res.m_c is not None + + header = ( + "# t [s], p1 [Pa], p2 [Pa], mobA [kg], immA [kg], dissA [kg], " + "sealA [kg], mobB [kg], immB [kg], dissB [kg], sealB [kg], " + "MC [m], sealTot [kg]" + ) + lines = [header] + + for j, t in enumerate(res.times_data[1:]): + lines.append( + f"{t:.3e},{res.pop1[j]:.5e},{res.pop2[j]:.5e}," + f"{res.moba[j]:.3e},{res.imma[j]:.3e},{res.dissa[j]:.3e}," + f"{res.seala[j]:.3e},{res.mobb[j]:.3e},{res.immb[j]:.3e}," + f"{res.dissb[j]:.3e},{res.sealb[j]:.3e}," + f"{res.m_c[j]:.3e},{res.sealt[j]:.3e}" + ) + + Path("time_series.csv").write_text("\n".join(lines), encoding="utf-8")
+ + + +
+[docs] +def _write_dense_outputs(ctx: SimulationContext) -> None: + """Write spatial maps at the requested dense-output times. + + Map active-cell saturation and dissolved-CO2 concentration to the reporting + grid and write one CSV file for every requested restart time that is available. + + Parameters + ---------- + ctx : SimulationContext + Loaded restart data, reporting-grid resolution, and cell mapping. + + Raises + ------ + SystemExit + If restart or initialization data are unavailable.""" + unrst = ctx.unrst + porv = ctx.porv + actind = ctx.actind + if unrst is None or porv is None or actind is None: + pofff_error( + "OPM restart and initialization data are required for dense output." + ) + + dx, _, dz = ctx.dims + + refx = 0.5 * ( + np.linspace(0, dx, ctx.resolution[0] + 1)[:-1] + + np.linspace(0, dx, ctx.resolution[0] + 1)[1:] + ) + refz = 0.5 * ( + np.linspace(0, dz, ctx.resolution[1] + 1)[:-1] + + np.linspace(0, dz, ctx.resolution[1] + 1)[1:] + ) + + cell_cent = np.load(ctx.maps).astype(int) + + for t in ctx.dense_t: + if t not in ctx.times: + continue + t_n = ctx.times.index(t) + + sgas = np.abs(unrst["SGAS", t_n]) + rhow = unrst["WAT_DEN", t_n] + rsw = unrst["RSW", t_n] + xlco2 = rsw / (rsw + WAT_DEN_REF / GAS_DEN_REF) + + s_full = np.zeros(len(porv)) + c_full = np.zeros(len(porv)) + s_full[actind] = sgas + c_full[actind] = xlco2 * rhow + + hours = int(t / 3600.0) if t % 3600 == 0 else t / 3600.0 + _write_spatial_map_csv( + ctx, [refx, refz], s_full[cell_cent], c_full[cell_cent], hours + )
+ + + +
+[docs] +def _write_spatial_map_csv( + ctx: SimulationContext, + refxz: list[NDArray], + sgas: NDArray, + cco2: NDArray, + hours: float, +) -> None: + """Write one saturation and concentration reporting-grid map. + + Parameters + ---------- + ctx : SimulationContext + Output location and reporting-grid resolution. + refxz : list[NDArray] + Reporting-cell centre coordinates in x and z order, in metres. + sgas : NDArray + Gas saturation in reporting-cell order. + cco2 : NDArray + Dissolved-CO2 concentration in reporting-cell order. + hours : int | float + Evaluation time in hours used in the output filename.""" + nx, nz = ctx.resolution + lines = ["x,z,saturation,concentration"] + + for k, z in enumerate(refxz[1]): + for i, x in enumerate(refxz[0]): + idx = -nx * (nz - k) + i + lines.append(f"{x:.3f},{z:.3f},{sgas[idx]:.3f},{cco2[idx]:.3f}") + + (ctx.where / f"spatial_map_{hours}h.csv").write_text("\n".join(lines))
+ + + +if __name__ == "__main__": + main() +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/docs/_modules/pofff/jobs/metric.html b/docs/_modules/pofff/jobs/metric.html new file mode 100644 index 0000000..140a31c --- /dev/null +++ b/docs/_modules/pofff/jobs/metric.html @@ -0,0 +1,851 @@ + + + + + + + + + + pofff.jobs.metric — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for pofff.jobs.metric

+#!/usr/bin/env python3
+# Modified from https://github.com/fluidflower/general/blob/main/evaluation/emd.py and
+# https://github.com/fluidflower/general/blob/main/evaluation/calculate_segmented_emds.py
+
+"""Calculate segmented FluidFlower Wasserstein-distance metrics.
+
+The job converts simulated saturation and dissolved-CO2 fields into benchmark
+segmentation classes, compares them with experimental segmentations using Earth
+Mover's Distance, and writes per-observation metrics and the Everest objective."""
+
+import argparse
+import os
+import sys
+
+import numpy as np
+import ot
+from numpy.typing import NDArray
+from PIL import Image
+
+
+
+[docs] +def _calculate_wasserstein_distance(file_1: str, file_2: str) -> float: + """Calculate the Earth Mover's Distance between two segmented images. + + Both images are converted to grayscale, resized to 140 by 60 pixels, normalized, + and compared on the physical FluidFlower domain. + + Parameters + ---------- + file_1 : str + Path to the first segmented PNG image. + file_2 : str + Path to the second segmented PNG image, resized in place. + + Returns + ------- + float + Wasserstein distance in physical x-z coordinates.""" + + im1 = Image.open(file_1).convert("L") + im1 = im1.resize((140, 60), Image.Resampling.LANCZOS) + + n_x, n_z = im1.size + a_1 = np.array(im1.get_flattened_data()).reshape(n_x, n_z) + + im2 = Image.open(file_2).convert("L") + im2 = im2.resize(im1.size, Image.Resampling.LANCZOS) + b_1 = np.array(im2.get_flattened_data()).reshape(n_x, n_z) + + im2.save(file_2) + + a_1 = a_1 / np.sum(a_1) + b_1 = b_1 / np.sum(b_1) + + a_flat = a_1.flatten(order="F") + b_flat = b_1.flatten(order="F") + + cc_x, cc_y = np.meshgrid(np.arange(n_x), np.arange(n_z), indexing="ij") + + cc_x_flat = cc_x.flatten("F") / n_x * 2.8 + 5e-3 * 280 / n_x + cc_y_flat = cc_y.flatten("F") / n_z * 1.2 + 5e-3 * 120 / n_z + + cost = ot.dist( + np.vstack((cc_x_flat, cc_y_flat)).T, + np.vstack((cc_x_flat, cc_y_flat)).T, + metric="euclidean", + ) + + return ot.emd2( + a_flat, + b_flat, + cost, + numItermax=500000, + )
+ + + +
+[docs] +def _segment_spatial_map( + file_name: str, + xlim: tuple[float, float], + zlim: tuple[float, float], + satmin: float, + conmin: float, +) -> NDArray[np.int64]: + """Convert saturation and concentration fields to benchmark classes. + + Parameters + ---------- + file_name : str + Spatial-map CSV containing x, z, saturation, and concentration columns. + xlim : tuple[float, float] + Minimum and maximum x coordinates, in metres. + zlim : tuple[float, float] + Minimum and maximum z coordinates, in metres. + satmin : float + Minimum gas saturation for class 2. + conmin : float + Minimum dissolved-CO2 concentration for class 1. + + Returns + ------- + NDArray[np.int64] + Segmentation on the 120 by 280 benchmark reporting grid.""" + + n_x = np.arange(xlim[0], xlim[1] + 5.0e-3, 1.0e-2).size - 1 + n_z = np.arange(zlim[0], zlim[1] + 5.0e-3, 1.0e-2).size - 1 + + tmp = 0 + with open(file_name, "r", encoding="utf8") as file: + if not file.readline()[0].isnumeric(): + tmp = 1 + + saturation = np.zeros([n_z, n_x]) + concentration = np.zeros([n_z, n_x]) + + csv_data = np.genfromtxt(file_name, delimiter=",", skip_header=tmp) + + for i in np.arange(0, n_z): + saturation[i, :] = csv_data[i * n_x : (i + 1) * n_x, 2] + concentration[i, :] = csv_data[i * n_x : (i + 1) * n_x, 3] + + segmentmap = np.zeros((120, 280), dtype=int) + tmp = 119 + + for i in np.arange(0, n_z): + for j in np.arange(0, n_x): + if saturation[i, j] > satmin: + segmentmap[tmp - i, j] = 2 + elif concentration[i, j] > conmin: + segmentmap[tmp - i, j] = 1 + + return segmentmap
+ + + +
+[docs] +def _prepare_distance_images( + model_result: NDArray[np.int64], + experimental_data: NDArray[np.int64], + indx: int, +) -> float: + """Write segmentation images and calculate their Wasserstein distance. + + The PNG round trip is retained to preserve the established metric calculation. + + Parameters + ---------- + model_result : NDArray[np.int64] + Simulated benchmark classes on the 120 by 280 grid. + experimental_data : NDArray[np.int64] + Experimental benchmark classes on the same grid. + indx : int | str + Evaluation-time identifier used in temporary filenames. + + Returns + ------- + float + Wasserstein distance between the generated images.""" + + mod_image = Image.new("L", (280, 120)) + mod_pixels = mod_image.load() + assert mod_pixels is not None + + for i in range(mod_image.size[0]): + for j in range(mod_image.size[1]): + if model_result[j, i] == 1: + mod_pixels[i, j] = 128 + elif model_result[j, i] == 2: + mod_pixels[i, j] = 255 + else: + mod_pixels[i, j] = 0 + + mod_image.save(f"mod_{indx}.png") + + exp_image = Image.new("L", (280, 120)) + exp_pixels = exp_image.load() + assert exp_pixels is not None + + for i in range(exp_image.size[0]): + for j in range(exp_image.size[1]): + if experimental_data[j, i] == 1: + exp_pixels[i, j] = 128 + elif experimental_data[j, i] == 2: + exp_pixels[i, j] = 255 + else: + exp_pixels[i, j] = 0 + + exp_image.save(f"exp_{indx}.png") + + return _calculate_wasserstein_distance(f"mod_{indx}.png", f"exp_{indx}.png")
+ + + +
+[docs] +def main(argv=None) -> None: + """Calculate segmented metrics and the Everest objective. + + Parameters + ---------- + argv : list[str] | None, optional + Arguments to parse instead of the process command line.""" + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description="Segment maps at requested times and compute total EMD score.", + ) + parser.add_argument("-s", "--minimumsaturation", default="1e-2") + parser.add_argument("-c", "--minimumconcentration", default="1e-1") + parser.add_argument("-e", "--experiment", default="run2") + parser.add_argument("-t", "--times", default="24,48,72,96,120") + parser.add_argument("-p", "--path", default=".") + + if os.path.exists("NOMONOTONIC"): + with open("func", "w", encoding="utf8") as file: + file.write("-1") + sys.exit() + + args = vars(parser.parse_args(argv)) + times = [row.strip() for row in args["times"].split(",")] + + everest = 0.0 + nobs = len(times) + + with open("sim_metrics_0.txt", "w", encoding="utf8") as file: + for time in times: + file_i = f"spatial_map_{time}h.csv" + + model_result = _segment_spatial_map( + file_i, + (0.0, 2.8), + (0.0, 1.2), + float(args["minimumsaturation"]), + float(args["minimumconcentration"]), + ) + + name = str(int(float(time) * 3600)).zfill(6) + + filename = ( + f"{args['path']}/fluidflower/experiment/benchmarkdata/" + f"spatial_maps/{args['experiment']}/segmentation_{name}s.csv" + ) + + experimental_data = np.loadtxt(filename, dtype="int", delimiter=",") + experimental_data = experimental_data[30:, :] + + dist = ( + 8.5 + * 100 + * _prepare_distance_images( + model_result, + experimental_data, + time, + ) + ) + + file.write(f"{dist}\n") + everest += dist + + with open("func", "w", encoding="utf8") as file: + file.write(f"{-everest/(8.5*100*nobs)}")
+ + + +if __name__ == "__main__": + main() +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/docs/_modules/pofff/utils/inputvalues.html b/docs/_modules/pofff/utils/inputvalues.html new file mode 100644 index 0000000..9bfbe4d --- /dev/null +++ b/docs/_modules/pofff/utils/inputvalues.html @@ -0,0 +1,1952 @@ + + + + + + + + + + pofff.utils.inputvalues — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for pofff.utils.inputvalues

+# SPDX-FileCopyrightText: 2025-2026 NORCE Research AS
+# SPDX-License-Identifier: GPL-3.0
+# pylint: disable=R0912,R0913,R0914,R0915,R0917,C0302
+
+"""Load, validate, and normalize pofff configuration values.
+
+This module combines command-line settings with TOML input, validates common
+simulation variables and workflow-specific ERT or Everest options, extracts
+facies and history-matching definitions, and populates derived runtime settings.
+Validation reports accepted values through the shared terminal helpers."""
+
+import ast
+import math
+import tomllib
+from pathlib import Path
+from typing import Any
+
+import numpy as np
+
+from pofff.config.config import CliConfig, PofffConfig
+from pofff.utils.terminal import (
+    cli_correct_value,
+    cli_error_value,
+    cli_warning_value,
+    pofff_error,
+    pofff_warning,
+)
+
+FACIES_KEYS = {
+    "poro",
+    "perm",
+    "permx",
+    "permy",
+    "permz",
+    "disperc",
+    "swi",
+    "sni",
+    "pen",
+    "nkrw",
+    "nkrn",
+    "npe",
+    "thre",
+    "npnt",
+}
+
+
+
+[docs] +def load_toml(filename: str, mode: str) -> dict: + """Load and validate a TOML configuration file. + + Parameters + ---------- + filename : str + Path to the input file. + mode : str + Selected pofff execution mode. + + Returns + ------- + dict + Validated and normalized TOML configuration values. + + Raises + ------ + SystemExit + If the TOML configuration is invalid.""" + with open(filename, "rb") as f: + toml = tomllib.load(f) + return _validate_toml(toml, mode)
+ + + +
+[docs] +def postprocess_config(cfg: PofffConfig, toml: dict) -> None: + """Enrich configuration with derived values and runtime flags. + + The function converts diffusion to m²/day and populates derived grid, + history-matching, and workflow flags on ``cfg``. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + toml : dict + Validated TOML values used to populate the runtime configuration.""" + cfg.para.update( + { + "permx7": 0, + "permz7": 0, + "poro7": 0, + "disperc7": 0, + "swi7": 0, + "sni7": 0, + "pen7": 0, + "nkrw7": 2, + "nkrn7": 2, + "npe7": 2, + "thre7": 5e-2, + "npnt7": 2, + } + ) + cfg.nxz = [int(np.sum(cfg.x)), int(np.sum(cfg.z))] + cfg.diffusion = 86400 * np.array(cfg.diffusion) # Convert to m²/day + cfg.data = cfg.fol.name.upper() + process_tuning(cfg) + cfg.hascellmaps = ( + cfg.x != [140] + or cfg.z != [7, 5, 5, 5, 5, 5, 5, 8, 10, 9, 5] + or cfg.grid != "corner-point" + ) + cfg.everert = cfg.cores is not None and cfg.mode != "single" + if cfg.everert: + for i in range(1, 8): + for name in FACIES_KEYS: + key = f"{name}{i}" + if key in toml: + cfg.hm[key] = toml[key] + if "thicknessmult" in toml: + cfg.hm["thicknessmult"] = toml["thicknessmult"]
+ + + +
+[docs] +def process_tuning(cfg: PofffConfig) -> None: + """Enable tuning and normalize injection specifications if requested. + + Injection rows with TUNING text are expanded in place and ``cfg.tuning`` is enabled. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" + for token in cfg.flow.split(): + if "--enable-tuning" not in token: + continue + if token[16:] not in {"true", "True", "1"}: + continue + cfg.tuning = True + for i, inj in enumerate(cfg.inj): + if len(inj) != 5: + continue + parts = inj[-1].split("/") + cfg.inj[i] = [ + *inj[:-1], + parts[0].strip(), + *(p.strip() for p in parts[1:]), + ]
+ + + +
+[docs] +def extract_facies(data: dict) -> dict: + """Extract definitions and remove them from the main config dict. + + Parameters + ---------- + data : dict + Configuration dictionary modified while extracting definitions. + + Returns + ------- + dict + Facies tables removed from the input dictionary.""" + facies = {k: data.pop(k) for k in list(data) if k.startswith("facie")} + for i in range(1, 8): + for name in FACIES_KEYS: + data.pop(f"{name}{i}", None) + data.pop("thicknessmult", None) + return facies
+ + + +
+[docs] +def build_config( + *, + pofff_path: Path, + cli: CliConfig, + toml: dict, +) -> PofffConfig: + """Build and return a fully initialized PofffConfig object. + + Parameters + ---------- + pofff_path : Path + Package root containing templates, geology, jobs, and benchmark data. + cli : CliConfig + Cli used by this operation. + toml : dict + Validated TOML values used to populate the runtime configuration. + + Returns + ------- + PofffConfig + Runtime configuration populated from CLI and TOML values.""" + facies = extract_facies(toml) + cfg = PofffConfig( + path=pofff_path, + fol=cli.fol, + deck=cli.deck, + jobs=cli.jobs, + experiment=cli.experiment, + times=cli.times, + msat=cli.msat, + mcon=cli.mcon, + mode=cli.mode, + figures=cli.figures, + location=cli.location, + use=cli.use, + **toml, + ) + for values in facies.values(): + cfg.para.update(values) + return cfg
+ + + +
+[docs] +def _is_finite_number(value: Any) -> bool: + """Return whether a value is a finite non-Boolean number. + + Parameters + ---------- + value : Any + Value to inspect or format. + + Returns + ------- + bool + Whether the value is a finite, non-Boolean number.""" + return ( + isinstance(value, (int, float)) + and not isinstance(value, bool) + and math.isfinite(value) + )
+ + + +
+[docs] +def _is_integer(value: Any) -> bool: + """Return whether a value is a non-Boolean integer. + + Parameters + ---------- + value : Any + Value to inspect or format. + + Returns + ------- + bool + Whether the value is a non-Boolean integer.""" + return isinstance(value, int) and not isinstance(value, bool)
+ + + +
+[docs] +def _add_validation_error(errors: list[str], message: str) -> None: + """Add a TOML validation error. + + Parameters + ---------- + errors : list[str] + Validation messages collected during the current validation pass. + message : str + Human-readable message to display or append.""" + errors.append(message)
+ + + +
+[docs] +def _validate_string(cfg_file: dict[str, Any], key: str, errors: list[str]) -> bool: + """Check that a TOML variable is a non-empty string. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + key : str + Configuration variable or history-matching parameter name. + errors : list[str] + Validation messages collected during the current validation pass. + + Returns + ------- + bool + Whether the variable is present and is a non-empty string.""" + if key not in cfg_file: + return False + value = cfg_file[key] + if not isinstance(value, str) or not value.strip(): + _add_validation_error( + errors, + f"variable {cli_error_value(key)} has invalid value " + f"{cli_error_value(str(value))}, expected " + f"{cli_correct_value('a non-empty string')}.", + ) + return False + return True
+ + + +
+[docs] +def _validate_number( + cfg_file: dict[str, Any], + key: str, + errors: list[str], + minimum: float | None = None, + maximum: float | None = None, + strict: bool = False, + integer: bool = False, +) -> bool: + """Check that a TOML variable is numeric and in the expected range. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + key : str + Configuration variable or history-matching parameter name. + errors : list[str] + Validation messages collected during the current validation pass. + minimum : float | None, optional + Optional lower bound for accepted values. + maximum : float | None, optional + Optional upper bound for accepted values. + strict : bool, optional + Whether the lower bound is exclusive. + integer : bool, optional + Whether accepted entries must be non-Boolean integers. + + Returns + ------- + bool + Whether the variable is present and satisfies the numeric constraints.""" + if key not in cfg_file: + return False + value = cfg_file[key] + valid_type = _is_integer(value) if integer else _is_finite_number(value) + if not valid_type: + expected = "an integer" if integer else "a finite number" + _add_validation_error( + errors, + f"variable {cli_error_value(key)} has invalid value " + f"{cli_error_value(str(value))}, expected {cli_correct_value(expected)}.", + ) + return False + valid = True + if minimum is not None and (value <= minimum if strict else value < minimum): + condition = ( + f"a value greater than {minimum}" + if strict + else f"a value greater than or equal to {minimum}" + ) + _add_validation_error( + errors, + f"variable {cli_error_value(key)} has invalid value " + f"{cli_error_value(str(value))}, expected {cli_correct_value(condition)}.", + ) + valid = False + if maximum is not None and value > maximum: + _add_validation_error( + errors, + f"variable {cli_error_value(key)} has invalid value " + f"{cli_error_value(str(value))}, expected " + f"{cli_correct_value(f'a value less than or equal to {maximum}')}.", + ) + valid = False + return valid
+ + + +
+[docs] +def _validate_array( + cfg_file: dict[str, Any], + key: str, + errors: list[str], + length: int | None = None, + integer: bool = False, + minimum: float | None = None, + strict: bool = False, + nonempty: bool = False, +) -> bool: + """Check the type, length, and numeric values of a TOML array. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + key : str + Configuration variable or history-matching parameter name. + errors : list[str] + Validation messages collected during the current validation pass. + length : int | None, optional + Required array length, when specified. + integer : bool, optional + Whether accepted entries must be non-Boolean integers. + minimum : float | None, optional + Optional lower bound for accepted values. + strict : bool, optional + Whether the lower bound is exclusive. + nonempty : bool, optional + Whether the array must contain at least one entry. + + Returns + ------- + bool + Whether the variable is present and satisfies the array constraints.""" + if key not in cfg_file: + return False + value = cfg_file[key] + if not isinstance(value, list): + _add_validation_error( + errors, + f"variable {cli_error_value(key)} has invalid type " + f"{cli_error_value(type(value).__name__)}, expected " + f"{cli_correct_value('an array')}.", + ) + return False + if length is not None and len(value) != length: + _add_validation_error( + errors, + f"variable {cli_error_value(key)} has {cli_error_value(str(len(value)))} " + f"entries, expected {cli_correct_value(str(length))}.", + ) + return False + if nonempty and not value: + _add_validation_error( + errors, + f"variable {cli_error_value(key)} must contain " + f"{cli_correct_value('at least one entry')}.", + ) + return False + valid = True + for index, entry in enumerate(value): + correct_type = _is_integer(entry) if integer else _is_finite_number(entry) + expected = "an integer" if integer else "a finite number" + if not correct_type: + _add_validation_error( + errors, + f"variable {cli_error_value(f'{key}[{index}]')} has invalid value " + f"{cli_error_value(str(entry))}, expected " + f"{cli_correct_value(expected)}.", + ) + valid = False + continue + if minimum is not None and (entry <= minimum if strict else entry < minimum): + condition = ( + f"a value greater than {minimum}" + if strict + else f"a value greater than or equal to {minimum}" + ) + _add_validation_error( + errors, + f"variable {cli_error_value(f'{key}[{index}]')} has invalid value " + f"{cli_error_value(str(entry))}, expected " + f"{cli_correct_value(condition)}.", + ) + valid = False + return valid
+ + + +
+[docs] +def _validate_matrix( + cfg_file: dict[str, Any], + key: str, + errors: list[str], + rows: int, + columns: int, +) -> bool: + """Check the dimensions and numeric values of a TOML matrix. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + key : str + Configuration variable or history-matching parameter name. + errors : list[str] + Validation messages collected during the current validation pass. + rows : int + Required number of matrix rows. + columns : int + Required number of entries in each matrix row. + + Returns + ------- + bool + Whether the variable is present and has the required numeric shape.""" + if key not in cfg_file: + return False + value = cfg_file[key] + if not isinstance(value, list): + _add_validation_error( + errors, + f"variable {cli_error_value(key)} has invalid type " + f"{cli_error_value(type(value).__name__)}, expected " + f"{cli_correct_value('an array of arrays')}.", + ) + return False + if len(value) != rows: + _add_validation_error( + errors, + f"variable {cli_error_value(key)} has {cli_error_value(str(len(value)))} " + f"rows, expected {cli_correct_value(str(rows))}.", + ) + return False + valid = True + for row_index, row in enumerate(value): + if not isinstance(row, list): + _add_validation_error( + errors, + f"variable {cli_error_value(f'{key}[{row_index}]')} has invalid " + f"type {cli_error_value(type(row).__name__)}, expected " + f"{cli_correct_value('an array')}.", + ) + valid = False + continue + if len(row) != columns: + _add_validation_error( + errors, + f"variable {cli_error_value(f'{key}[{row_index}]')} has " + f"{cli_error_value(str(len(row)))} entries, expected " + f"{cli_correct_value(str(columns))}.", + ) + valid = False + continue + for column_index, entry in enumerate(row): + if not _is_finite_number(entry): + _add_validation_error( + errors, + f"variable " + f"{cli_error_value(f'{key}[{row_index}][{column_index}]')} " + f"has invalid value {cli_error_value(str(entry))}, expected " + f"{cli_correct_value('a finite number')}.", + ) + valid = False + return valid
+ + + +
+[docs] +def _validate_expression(cfg_file: dict[str, Any], key: str, errors: list[str]) -> None: + """Check that a TOML variable contains a valid Python expression. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + key : str + Configuration variable or history-matching parameter name. + errors : list[str] + Validation messages collected during the current validation pass.""" + if not _validate_string(cfg_file, key, errors): + return + try: + ast.parse(cfg_file[key], mode="eval") + except SyntaxError as err: + _add_validation_error( + errors, + f"variable {cli_error_value(key)} contains invalid Python expression " + f"{cli_error_value(cfg_file[key])}: {err.msg}.", + )
+ + + +
+[docs] +def _validate_thickness(cfg_file: dict[str, Any], errors: list[str]) -> None: + """Check the thickness-map selection or numeric thickness. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + errors : list[str] + Validation messages collected during the current validation pass.""" + if "thickness" not in cfg_file: + return + value = cfg_file["thickness"] + if isinstance(value, str) and value in {"initial", "final"}: + return + if _is_finite_number(value) and value > 0: + return + _add_validation_error( + errors, + f"variable {cli_error_value('thickness')} has invalid value " + f"{cli_error_value(str(value))}, expected " + f"{cli_correct_value('initial, final, or a positive finite number')}.", + )
+ + + +
+[docs] +def _validate_facies( + cfg_file: dict[str, Any], key: str, index: int, errors: list[str] +) -> None: + """Check one fixed facies-property definition. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + key : str + Configuration variable or history-matching parameter name. + index : int + One-based facies index. + errors : list[str] + Validation messages collected during the current validation pass.""" + if key not in cfg_file: + return + facies = cfg_file[key] + if not isinstance(facies, dict): + _add_validation_error( + errors, + f"variable {cli_error_value(key)} has invalid type " + f"{cli_error_value(type(facies).__name__)}, expected " + f"{cli_correct_value('a table of facies properties')}.", + ) + return + required_names = { + "permx", + "permz", + "poro", + "disperc", + "swi", + "sni", + "pen", + "nkrw", + "nkrn", + "npe", + "thre", + "npnt", + } + required = {f"{name}{index}" for name in required_names} + allowed = {f"{name}{index}" for name in FACIES_KEYS} + for name in sorted(required - facies.keys()): + _add_validation_error( + errors, + f"missing required facies property {cli_error_value(name)} in " + f"{cli_error_value(key)}.", + ) + for name in sorted(facies.keys() - allowed): + _add_validation_error( + errors, + f"unknown or mismatched facies property {cli_error_value(name)} in " + f"{cli_error_value(key)}.", + ) + valid_values: dict[str, float | int] = {} + for name, value in facies.items(): + base = name.removesuffix(str(index)) + if name not in allowed: + continue + if base == "npnt": + valid = _is_integer(value) and value >= 2 + expected = "an integer greater than or equal to 2" + elif base in {"poro", "swi", "sni"}: + valid = _is_finite_number(value) and 0 <= value <= 1 + expected = "a value between 0 and 1" + elif base in {"nkrw", "nkrn", "npe"}: + valid = _is_finite_number(value) and value > 0 + expected = "a positive finite number" + else: + valid = _is_finite_number(value) and value >= 0 + expected = "a non-negative finite number" + if not valid: + _add_validation_error( + errors, + f"variable {cli_error_value(name)} has invalid value " + f"{cli_error_value(str(value))}, expected " + f"{cli_correct_value(expected)}.", + ) + else: + valid_values[base] = value + if "swi" in valid_values and "sni" in valid_values: + total = valid_values["swi"] + valid_values["sni"] + if total > 1: + _add_validation_error( + errors, + f"variables {cli_error_value(f'swi{index}')} and " + f"{cli_error_value(f'sni{index}')} have a combined value " + f"{cli_error_value(str(total))}, expected a combined value less " + f"than or equal to {cli_correct_value('1')}.", + )
+ + + +
+[docs] +def _validate_injection_schedule(cfg_file: dict[str, Any], errors: list[str]) -> bool: + """Check injection values and return whether TUNING values are defined. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + errors : list[str] + Validation messages collected during the current validation pass. + + Returns + ------- + bool + Whether at least one injection row defines TUNING values.""" + if "inj" not in cfg_file: + return False + inj = cfg_file["inj"] + if not isinstance(inj, list): + _add_validation_error( + errors, + f"variable {cli_error_value('inj')} has invalid type " + f"{cli_error_value(type(inj).__name__)}, expected " + f"{cli_correct_value('an array of injection rows')}.", + ) + return False + if not inj: + _add_validation_error( + errors, + f"variable {cli_error_value('inj')} must contain " + f"{cli_correct_value('at least one injection row')}.", + ) + return False + tuning_defined = False + for row_index, row in enumerate(inj): + if not isinstance(row, list): + _add_validation_error( + errors, + f"variable {cli_error_value(f'inj[{row_index}]')} has invalid type " + f"{cli_error_value(type(row).__name__)}, expected " + f"{cli_correct_value('an array')}.", + ) + continue + if len(row) not in {4, 5}: + _add_validation_error( + errors, + f"variable {cli_error_value(f'inj[{row_index}]')} has " + f"{cli_error_value(str(len(row)))} entries, expected " + f"{cli_correct_value('4 entries, or 5 with TUNING values')}.", + ) + continue + for column_index in range(4): + value = row[column_index] + variable = f"inj[{row_index}][{column_index}]" + if not _is_finite_number(value): + _add_validation_error( + errors, + f"variable {cli_error_value(variable)} has invalid value " + f"{cli_error_value(str(value))}, expected " + f"{cli_correct_value('a finite number')}.", + ) + elif column_index in {0, 1} and value <= 0: + _add_validation_error( + errors, + f"variable {cli_error_value(variable)} has invalid value " + f"{cli_error_value(str(value))}, expected " + f"{cli_correct_value('a positive value')}.", + ) + elif column_index in {2, 3} and value < 0: + _add_validation_error( + errors, + f"variable {cli_error_value(variable)} has invalid value " + f"{cli_error_value(str(value))}, expected " + f"{cli_correct_value('a non-negative injection rate')}.", + ) + if len(row) == 5: + tuning_defined = True + if not isinstance(row[4], str) or not row[4].strip(): + _add_validation_error( + errors, + f"variable {cli_error_value(f'inj[{row_index}][4]')} has " + f"invalid value {cli_error_value(str(row[4]))}, expected " + f"{cli_correct_value('a non-empty TUNING string')}.", + ) + return tuning_defined
+ + + +
+[docs] +def _validate_history_parameter( + cfg_file: dict[str, Any], key: str, workflow: str, errors: list[str] +) -> None: + """Check an ERT distribution or Everest interval definition. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + key : str + Configuration variable or history-matching parameter name. + workflow : str + Effective simulation, ERT, or Everest workflow. + errors : list[str] + Validation messages collected during the current validation pass.""" + value = cfg_file[key] + if not isinstance(value, list): + _add_validation_error( + errors, + f"history-matching variable {cli_error_value(key)} has invalid type " + f"{cli_error_value(type(value).__name__)}, expected " + f"{cli_correct_value('an array')}.", + ) + return + if workflow == "ert": + if len(value) != 3: + _add_validation_error( + errors, + f"history-matching variable {cli_error_value(key)} has " + f"{cli_error_value(str(len(value)))} entries, expected " + f"{cli_correct_value('a distribution name and two parameters')}.", + ) + return + if not isinstance(value[0], str) or not value[0].strip(): + _add_validation_error( + errors, + f"variable {cli_error_value(f'{key}[0]')} has invalid value " + f"{cli_error_value(str(value[0]))}, expected " + f"{cli_correct_value('a non-empty distribution name')}.", + ) + for index in (1, 2): + if not _is_finite_number(value[index]): + _add_validation_error( + errors, + f"variable {cli_error_value(f'{key}[{index}]')} has invalid " + f"value {cli_error_value(str(value[index]))}, expected " + f"{cli_correct_value('a finite number')}.", + ) + if ( + isinstance(value[0], str) + and value[0].lower() == "uniform" + and all(_is_finite_number(value[index]) for index in (1, 2)) + and value[1] >= value[2] + ): + _add_validation_error( + errors, + f"history-matching variable {cli_error_value(key)} has invalid " + f"uniform bounds {cli_error_value(str(value[1:]))}, expected the " + f"lower bound to be less than {cli_correct_value('the upper bound')}.", + ) + return + if len(value) != 4: + _add_validation_error( + errors, + f"history-matching variable {cli_error_value(key)} has " + f"{cli_error_value(str(len(value)))} entries, expected " + f"{cli_correct_value('initial, minimum, maximum, and scale values')}.", + ) + return + if not all(_is_finite_number(entry) for entry in value): + for index, entry in enumerate(value): + if not _is_finite_number(entry): + _add_validation_error( + errors, + f"variable {cli_error_value(f'{key}[{index}]')} has invalid " + f"value {cli_error_value(str(entry))}, expected " + f"{cli_correct_value('a finite number')}.", + ) + return + initial, minimum, maximum, scale = value + if minimum >= maximum: + _add_validation_error( + errors, + f"history-matching variable {cli_error_value(key)} has invalid bounds " + f"{cli_error_value(str([minimum, maximum]))}, expected the minimum " + f"to be less than {cli_correct_value('the maximum')}.", + ) + elif not minimum <= initial <= maximum: + _add_validation_error( + errors, + f"variable {cli_error_value(f'{key}[0]')} has invalid value " + f"{cli_error_value(str(initial))}, expected a value between " + f"{cli_correct_value(str(minimum))} and " + f"{cli_correct_value(str(maximum))}.", + ) + if not _is_integer(scale) or scale <= 0: + _add_validation_error( + errors, + f"variable {cli_error_value(f'{key}[3]')} has invalid value " + f"{cli_error_value(str(scale))}, expected " + f"{cli_correct_value('a positive integer scale')}.", + )
+ + + +
+[docs] +def _is_tuning_enabled(flow: Any) -> bool: + """Return whether TUNING is enabled in the Flow command. + + Parameters + ---------- + flow : Any + Flow used by this operation. + + Returns + ------- + bool + Whether the Flow command explicitly enables TUNING.""" + if not isinstance(flow, str): + return False + return any( + token.lower() in {"--enable-tuning=true", "--enable-tuning=1"} + for token in flow.split() + )
+ + + +
+[docs] +def _effective_workflow(cfg_file: dict[str, Any], mode: str) -> str: + """Return the effective simulation, ERT, or Everest workflow. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + mode : str + Selected pofff execution mode. + + Returns + ------- + str + Effective workflow name used for validation.""" + if mode in {"ert", "everest"}: + return mode + if mode == "files" and "cores" in cfg_file: + return "everest" if "popsize" in cfg_file else "ert" + return "simulation"
+ + + +
+[docs] +def _warn_and_remove(cfg_file: dict[str, Any], keys: set[str], message: str) -> None: + """Warn about ineffective variables and remove them. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + keys : set[str] + Configuration variables to remove when they are ineffective. + message : str + Human-readable message to display or append.""" + present = sorted(keys & cfg_file.keys()) + if not present: + return + formatted = ", ".join(cli_warning_value(key) for key in present) + plural = len(present) != 1 + pofff_warning( + f"variable{'s' if plural else ''} {formatted} " + f"{'are' if plural else 'is'} only effective for " + f"{cli_correct_value(message)} and will be ignored." + ) + for key in present: + cfg_file.pop(key)
+ + + +
+[docs] +def _validate_toml(cfg_file: dict[str, Any], mode: str) -> dict[str, Any]: + """Validate and normalize TOML configuration values. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + mode : str + Selected pofff execution mode. + + Returns + ------- + dict[str, Any] + Validated configuration values suitable for ``PofffConfig``. + + Raises + ------ + SystemExit + If one or more configuration values are invalid.""" + if not isinstance(cfg_file, dict): + pofff_error( + f"invalid TOML content {cli_error_value(type(cfg_file).__name__)}, " + f"expected {cli_correct_value('a dictionary of configuration variables')}." + ) + cfg_file = cfg_file.copy() + errors: list[str] = [] + common_required = { + "flow", + "grid", + "thickness", + "mult_thickness", + "x", + "z", + "temperature", + "pressure", + "diffusion", + "sources", + "inj", + "krw", + "krn", + "cap", + "facie1", + "facie2", + "facie3", + "facie4", + "facie5", + "facie6", + } + common_optional = {"cores", "maxtime", "delete", "monotonic"} + ert_only = {"ertargs", "ensembles", "enkf_alpha", "errors", "random_seed"} + everest_only = { + "max_function_evaluations", + "max_batch_num", + "strategy", + "maxiter", + "popsize", + "tol", + "mutation", + "recombination", + "rng", + "callback", + "disp", + "polish", + "init", + "atol", + "updating", + "workers", + "constraints", + "x0", + "integrality", + "vectorized", + } + shared_history = {"min_realizations_success"} + internal = { + "path", + "fol", + "deck", + "jobs", + "experiment", + "times", + "msat", + "mcon", + "mode", + "figures", + "location", + "use", + "facies", + "fluxnum", + "fipnum", + "porv", + "multpv", + "dx", + "dz", + "dims", + "sensors", + "sensor_ik", + "source_ik", + "boxa", + "boxb", + "boxc", + "hm", + "hascellmaps", + "everert", + "tuning", + "para", + "nxz", + "data", + } + history_keys = { + f"{name}{index}" for name in FACIES_KEYS for index in range(1, 8) + } | {"thicknessmult"} + configurable = ( + common_required + | common_optional + | ert_only + | everest_only + | shared_history + | history_keys + ) + internal_values = sorted(internal & cfg_file.keys()) + if internal_values: + formatted = ", ".join(cli_error_value(key) for key in internal_values) + plural = len(internal_values) != 1 + pofff_warning( + f"variable{'s' if plural else ''} {formatted} " + f"{'are' if plural else 'is'} managed internally and will be ignored." + ) + for key in internal_values: + cfg_file.pop(key) + unknown_values = sorted(cfg_file.keys() - configurable) + if unknown_values: + formatted = ", ".join(cli_warning_value(key) for key in unknown_values) + plural = len(unknown_values) != 1 + pofff_warning( + f"unknown TOML variable{'s' if plural else ''} {formatted} " + "will be ignored." + ) + for key in unknown_values: + cfg_file.pop(key) + workflow = _effective_workflow(cfg_file, mode) + if workflow == "simulation": + _warn_and_remove(cfg_file, ert_only, "mode = ert") + _warn_and_remove(cfg_file, everest_only, "mode = everest") + _warn_and_remove( + cfg_file, + common_optional | shared_history, + "an ERT or Everest workflow", + ) + present_history = sorted(history_keys & cfg_file.keys()) + if present_history: + formatted = ", ".join(cli_warning_value(key) for key in present_history) + plural = len(present_history) != 1 + pofff_warning( + f"history-matching variable{'s' if plural else ''} {formatted} " + f"{'are' if plural else 'is'} not effective for " + f"{cli_warning_value(f'mode = {mode}')} and will be ignored." + ) + for key in present_history: + cfg_file.pop(key) + elif workflow == "ert": + _warn_and_remove(cfg_file, everest_only, "mode = everest") + else: + _warn_and_remove(cfg_file, ert_only, "mode = ert") + required = set(common_required) + if workflow in {"ert", "everest"}: + required |= {"cores", "min_realizations_success"} + if workflow == "ert": + required |= {"ertargs", "ensembles", "enkf_alpha", "errors"} + elif workflow == "everest": + required |= {"max_function_evaluations", "popsize"} + for key in sorted(required - cfg_file.keys()): + _add_validation_error( + errors, + f"missing required TOML variable {cli_error_value(key)}.", + ) + _validate_string(cfg_file, "flow", errors) + if _validate_string(cfg_file, "grid", errors): + grid = cfg_file["grid"] + if grid not in {"cartesian", "tensor", "corner-point"}: + _add_validation_error( + errors, + f"variable {cli_error_value('grid')} has invalid value " + f"{cli_error_value(grid)}, expected one of " + f"{cli_correct_value('cartesian, corner-point, tensor')}.", + ) + else: + grid = cfg_file.get("grid") + _validate_thickness(cfg_file, errors) + _validate_number(cfg_file, "mult_thickness", errors, minimum=0, strict=True) + refinement_valid = { + key: _validate_array( + cfg_file, + key, + errors, + integer=True, + minimum=0, + strict=True, + nonempty=True, + ) + for key in ("x", "z") + } + if grid == "cartesian": + for key in ("x", "z"): + value = cfg_file.get(key) + if refinement_valid[key] and isinstance(value, list) and len(value) != 1: + _add_validation_error( + errors, + f"variable {cli_error_value(key)} has " + f"{cli_error_value(str(len(value)))} entries, expected " + f"{cli_correct_value('one entry for a Cartesian grid')}.", + ) + elif grid == "corner-point": + value = cfg_file.get("z") + if refinement_valid["z"] and isinstance(value, list) and len(value) != 11: + _add_validation_error( + errors, + f"variable {cli_error_value('z')} has " + f"{cli_error_value(str(len(value)))} entries, expected " + f"{cli_correct_value('11 entries for a corner-point grid')}.", + ) + _validate_array(cfg_file, "temperature", errors, length=2) + _validate_number(cfg_file, "pressure", errors, minimum=0, strict=True) + _validate_array(cfg_file, "diffusion", errors, length=2, minimum=0) + sources_valid = _validate_matrix(cfg_file, "sources", errors, 2, 2) + if sources_valid: + dims = [2.8, 1.2] + for source_index, source in enumerate(cfg_file["sources"]): + for axis, value in enumerate(source): + if value < 0 or value > dims[axis]: + _add_validation_error( + errors, + f"variable " + f"{cli_error_value(f'sources[{source_index}][{axis}]')} has " + f"invalid value {cli_error_value(str(value))}, expected a " + f"value between {cli_correct_value('0')} and " + f"{cli_correct_value(str(dims[axis]))}.", + ) + tuning_defined = _validate_injection_schedule(cfg_file, errors) + for key in ("krw", "krn", "cap"): + _validate_expression(cfg_file, key, errors) + for index in range(1, 7): + _validate_facies(cfg_file, f"facie{index}", index, errors) + if tuning_defined and not _is_tuning_enabled(cfg_file.get("flow")): + pofff_warning( + f"TUNING values are defined in {cli_warning_value('inj')}, but " + f"{cli_warning_value('--enable-tuning=true')} is not set in " + f"{cli_correct_value('flow')}. The TUNING values may not be effective." + ) + if "cores" in cfg_file: + _validate_number( + cfg_file, "cores", errors, minimum=0, strict=True, integer=True + ) + if "maxtime" in cfg_file: + _validate_number(cfg_file, "maxtime", errors, minimum=0) + if "delete" in cfg_file and not isinstance(cfg_file["delete"], bool): + _add_validation_error( + errors, + f"variable {cli_error_value('delete')} has invalid value " + f"{cli_error_value(str(cfg_file['delete']))}, expected " + f"{cli_correct_value('a Boolean')}.", + ) + if "monotonic" in cfg_file and not isinstance(cfg_file["monotonic"], bool): + _add_validation_error( + errors, + f"variable {cli_error_value('monotonic')} has invalid value " + f"{cli_error_value(str(cfg_file['monotonic']))}, expected " + f"{cli_correct_value('a Boolean')}.", + ) + if "min_realizations_success" in cfg_file: + _validate_number( + cfg_file, "min_realizations_success", errors, minimum=0, integer=True + ) + present_history = sorted(history_keys & cfg_file.keys()) + if workflow in {"ert", "everest"} and not present_history: + _add_validation_error( + errors, + f"missing {cli_error_value('history-matching variables')}, expected " + f"{cli_correct_value('at least one facies or thickness parameter')}.", + ) + for key in present_history: + _validate_history_parameter(cfg_file, key, workflow, errors) + if workflow == "ert": + _validate_string(cfg_file, "ertargs", errors) + _validate_number( + cfg_file, "ensembles", errors, minimum=0, strict=True, integer=True + ) + _validate_number(cfg_file, "enkf_alpha", errors, minimum=0) + _validate_array( + cfg_file, "errors", errors, minimum=0, strict=True, nonempty=True + ) + if "random_seed" in cfg_file: + _validate_number(cfg_file, "random_seed", errors, minimum=0, integer=True) + elif workflow == "everest": + _validate_number( + cfg_file, + "max_function_evaluations", + errors, + minimum=0, + strict=True, + integer=True, + ) + _validate_number( + cfg_file, "popsize", errors, minimum=0, strict=True, integer=True + ) + if "max_batch_num" in cfg_file: + _validate_number( + cfg_file, + "max_batch_num", + errors, + minimum=0, + strict=True, + integer=True, + ) + if "rng" in cfg_file: + _validate_number(cfg_file, "rng", errors, minimum=0, integer=True) + for key in ("strategy", "init", "updating"): + if key in cfg_file: + _validate_string(cfg_file, key, errors) + if "maxiter" in cfg_file: + _validate_number( + cfg_file, "maxiter", errors, minimum=0, strict=True, integer=True + ) + if "workers" in cfg_file: + _validate_number(cfg_file, "workers", errors, integer=True) + for key in ("tol", "atol"): + if key in cfg_file: + _validate_number(cfg_file, key, errors, minimum=0) + if "recombination" in cfg_file: + _validate_number(cfg_file, "recombination", errors, minimum=0, maximum=1) + for key in ("disp", "polish", "vectorized"): + if key in cfg_file and not isinstance(cfg_file[key], bool): + _add_validation_error( + errors, + f"variable {cli_error_value(key)} has invalid value " + f"{cli_error_value(str(cfg_file[key]))}, expected " + f"{cli_correct_value('a Boolean')}.", + ) + if "mutation" in cfg_file: + mutation = cfg_file["mutation"] + if _is_finite_number(mutation): + if mutation < 0: + _add_validation_error( + errors, + f"variable {cli_error_value('mutation')} has invalid value " + f"{cli_error_value(str(mutation))}, expected " + f"{cli_correct_value('a non-negative value')}.", + ) + elif isinstance(mutation, list) and len(mutation) == 2: + if not all(_is_finite_number(value) for value in mutation): + _add_validation_error( + errors, + f"variable {cli_error_value('mutation')} has invalid value " + f"{cli_error_value(str(mutation))}, expected " + f"{cli_correct_value('two finite numbers')}.", + ) + elif mutation[0] < 0 or mutation[0] >= mutation[1]: + _add_validation_error( + errors, + f"variable {cli_error_value('mutation')} has invalid range " + f"{cli_error_value(str(mutation))}, expected non-negative " + f"increasing {cli_correct_value('mutation bounds')}.", + ) + else: + _add_validation_error( + errors, + f"variable {cli_error_value('mutation')} has invalid value " + f"{cli_error_value(str(mutation))}, expected a finite number " + f"or {cli_correct_value('an array of two finite numbers')}.", + ) + unsupported = {"callback", "constraints"} & cfg_file.keys() + for key in sorted(unsupported): + _add_validation_error( + errors, + f"variable {cli_error_value(key)} cannot be configured through " + f"{cli_correct_value('TOML')}.", + ) + if cfg_file.get("monotonic") is True and workflow in {"ert", "everest"}: + groups = [] + for name in FACIES_KEYS - {"thre", "npnt"}: + groups.append( + [ + f"{name}{index}" + for index in range(1, 8) + if f"{name}{index}" in cfg_file + ] + ) + if not any(len(group) >= 2 for group in groups): + pofff_warning( + f"variable {cli_warning_value('monotonic')} is enabled, but the " + "selected history-matching variables do not define a comparable " + "facies sequence. The monotonicity constraint may have no effect." + ) + if errors: + details = "\n".join(f" - {error}" for error in errors) + pofff_error(f"invalid TOML configuration:\n{details}") + return cfg_file
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/docs/_modules/pofff/utils/mapproperties.html b/docs/_modules/pofff/utils/mapproperties.html new file mode 100644 index 0000000..4cdd677 --- /dev/null +++ b/docs/_modules/pofff/utils/mapproperties.html @@ -0,0 +1,1362 @@ + + + + + + + + + + pofff.utils.mapproperties — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for pofff.utils.mapproperties

+# SPDX-FileCopyrightText: 2025-2026 NORCE Research AS
+# SPDX-License-Identifier: GPL-3.0
+# pylint: disable=R0913, R0914, R0917, E1102
+
+"""Generate FluidFlower grids and assign spatial model properties.
+
+The module builds Cartesian, tensor, and corner-point grids; reads geological
+points, horizons, and facies polygons; applies thickness maps; locates sensors
+and injection sources; classifies reporting boxes; and writes mappings from the
+simulation grid to the benchmark reporting grid."""
+
+import csv
+import shutil
+import sys
+from contextlib import nullcontext
+from pathlib import Path
+from typing import Any
+
+import numpy as np
+from alive_progress import alive_bar
+from numpy.typing import NDArray
+from shapely.geometry import Point, Polygon
+
+from pofff.config.config import PofffConfig
+from pofff.utils.writefile import create_corner_point_grid
+
+
+
+[docs] +def grid_and_properties(cfg: PofffConfig) -> None: + """Generate the selected grid and assign spatial model properties. + + The function updates grid dimensions, generated OPM arrays, sensor and source + indices, and the benchmark cell map on ``cfg``. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" + polygons, points = _read_geological_polygons(cfg) + if cfg.grid == "corner-point": + xyz, xcoord, zcoord = _build_corner_point_coordinates(cfg, points) + _assign_spatial_properties(cfg, polygons, xyz=xyz, xcoord=xcoord, zcoord=zcoord) + return + dims = np.asarray(cfg.dims, float) + if cfg.grid == "cartesian": + xvert = np.linspace(0, dims[0], cfg.nxz[0] + 1) + zvert = np.linspace(0, dims[2], cfg.nxz[1] + 1) + else: + xvert = _tensor(cfg.x, dims[0]) + zvert = _tensor(cfg.z, dims[2]) + cfg.nxz = [len(xvert) - 1, len(zvert) - 1] + xcent = 0.5 * (xvert[:-1] + xvert[1:]) + zcent = 0.5 * (zvert[:-1] + zvert[1:]) + cfg.dx = list(xvert[1:] - xvert[:-1]) + cfg.dz = list(zvert[1:] - zvert[:-1]) + _assign_spatial_properties(cfg, polygons, xcent=xcent, zcent=zcent) + if cfg.grid == "tensor": + cfg.dx = list(map(str, xvert[1:] - xvert[:-1])) + dz = list(map(str, zvert[1:] - zvert[:-1])) + cfg.dz = [dz[0]] * cfg.nxz[0] + for i in range(cfg.nxz[1] - 1): + cfg.dx.extend(cfg.dx[-cfg.nxz[0] :]) + cfg.dz.extend([dz[i + 1]] * cfg.nxz[0])
+ + + +
+[docs] +def _tensor(parts: list[int], dimension: float) -> NDArray[np.float64]: + """Construct tensor-grid edges from per-region refinement counts. + + Divide the physical dimension into equally sized regions, then subdivide + each region according to its corresponding refinement count. + + Parameters + ---------- + parts : list[int] + Positive numbers of cells assigned to consecutive equal-sized regions. + dimension : float + Total physical length of the grid axis, in metres. + + Returns + ------- + NDArray[np.float64] + Ordered grid-edge coordinates from zero through ``dimension``, in + metres. + """ + return np.concatenate( + [ + np.linspace( + index * dimension / len(parts), + (index + 1) * dimension / len(parts), + cells + 1, + endpoint=True, + )[:-1] + for index, cells in enumerate(parts) + ] + + [np.array([dimension], dtype=np.float64)] + )
+ + + +
+[docs] +def _build_corner_point_coordinates( + cfg: PofffConfig, + points: list[list[float]], +) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]: + """Build refined corner-point coordinates and cell centroids. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + points : list[list[float]] + Geological points represented by x and z coordinates in metres. + + Returns + ------- + tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]] + Cell centroids followed by refined corner x and z coordinates.""" + horizonts = _read_horizon_lines(cfg, points) + xvert = np.concatenate( + [ + np.linspace( + i * cfg.dims[0] / len(cfg.x), + (i + 1) * cfg.dims[0] / len(cfg.x), + n + 1, + endpoint=True, + )[:-1] + for i, n in enumerate(cfg.x) + ] + + [np.array([cfg.dims[0]])] + ) + xcoor, zcoor = [], [] + for x in xvert: + for layer in horizonts: + xs = np.array([p[0] for p in layer]) + zs = np.array([p[1] for p in layer]) + i = np.argmin(np.abs(xs - x)) + if i == 0: + z = zs[0] + (zs[1] - zs[0]) / (xs[1] - xs[0]) * (x - xs[0]) + elif i == len(xs) - 1: + z = zs[i - 1] + (zs[i] - zs[i - 1]) / (xs[i] - xs[i - 1]) * ( + x - xs[i - 1] + ) + elif xs[i] < x: + z = zs[i] + (zs[i + 1] - zs[i]) / (xs[i + 1] - xs[i]) * (x - xs[i]) + else: + z = zs[i - 1] + (zs[i] - zs[i - 1]) / (xs[i] - xs[i - 1]) * ( + x - xs[i - 1] + ) + xcoor.append(x) + zcoor.append(z) + xcoord, zcoord = np.asarray(xcoor), np.asarray(zcoor) + + stride = len(cfg.z) + 1 + cfg.nxz[0] = len(xcoord) // stride - 1 + cfg.nxz[1] = stride - 1 + + xcoord, zcoord, cfg.nxz[0], cfg.nxz[1] = _refine_vertical_coordinates( + xcoord, zcoord, cfg.nxz[1], np.array(cfg.z) + ) + + xyz = np.zeros((cfg.nxz[0] * cfg.nxz[1], 3)) + stride = cfg.nxz[1] + 1 + idx = 0 + + for k in range(cfg.nxz[1]): + for o in range(cfg.nxz[0]): + n = o * stride + k + m = (o + 1) * stride + k + + x = np.array([xcoord[n], xcoord[m], xcoord[m + 1], xcoord[n + 1]]) + z = np.array([zcoord[n], zcoord[m], zcoord[m + 1], zcoord[n + 1]]) + + a = x * np.roll(z, -1) - np.roll(x, -1) * z + area = 0.5 * np.sum(a) + + if abs(area) > 1e-12: + cx = np.sum((x + np.roll(x, -1)) * a) / (6 * area) + cz = np.sum((z + np.roll(z, -1)) * a) / (6 * area) + else: + cx, cz = np.mean(x), np.mean(z) + + xyz[idx] = [cx, 0.0, cz] + idx += 1 + + return xyz, xcoord, zcoord
+ + + +
+[docs] +def _load_thickness_map(cfg: PofffConfig) -> tuple[float, NDArray, NDArray, NDArray]: + """Load the selected FluidFlower thickness map and multipliers. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + + Returns + ------- + tuple[float, NDArray, NDArray, NDArray] + Minimum thickness, map x and z coordinates, and normalized multipliers.""" + geology = cfg.path / "geology" + + if cfg.thickness == "final": + thickness = np.load(geology / "final_thickness.npy") + ydim = float(np.min(thickness)) + mult = cfg.mult_thickness * thickness.reshape(-1) / np.min(thickness) + + refx = np.arange(0, 2.8 + 5.0e-3, 1.0e-2) + refz = 1.5 - np.arange(0, 1.5 + 5.0e-3, 1.0e-2) + refx = 0.5 * (refx[1:] + refx[:-1]) + refz = 0.5 * (refz[1:] + refz[:-1]) + x, z = np.meshgrid(refx, refz, indexing="xy") + + return ydim, x.ravel(), z.ravel(), mult + + thickness = np.genfromtxt(geology / "initial_thickness.csv", delimiter=",") + return ( + float(np.min(thickness[:, 1])), + thickness[:, 0] - 0.03, + 1.34 - thickness[:, 2], + cfg.mult_thickness * thickness[:, 1] / np.min(thickness[:, 1]), + )
+ + + +
+[docs] +def _assign_structured_grid_properties( + cfg: PofffConfig, + xcent: NDArray[np.float64], + zcent: NDArray[np.float64], + polygons: list[Polygon], +) -> None: + """Assign properties and feature indices on a structured grid. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + xcent : NDArray[np.float64] + Cell-centre x coordinates in metres. + zcent : NDArray[np.float64] + Cell-centre z coordinates in metres. + polygons : list[Polygon] + FluidFlower facies polygons in model coordinates.""" + nx, nz = cfg.nxz + x = np.tile(xcent, nz) + z = np.repeat(zcent, nx) + ztop = cfg.dims[2] - z + + flux = np.full(len(x), -1) + + if cfg.thickness in {"initial", "final"}: + cfg.dims[1], xth, zth, mult = _load_thickness_map(cfg) + else: + cfg.dims[1] = float(cfg.thickness) + show_progress = sys.stdout.isatty() + if show_progress: + bar_ctx = alive_bar(len(x), bar="fish") + else: + bar_ctx = nullcontext() + with bar_ctx as bar_animation: + for i, (xi, zi) in enumerate(zip(x, z)): + if show_progress: + bar_animation() + pt = Point(xi, zi) + for j, poly in enumerate(polygons): + if poly.contains(pt): + flux[i] = cfg.facies[j] + break + + if cfg.thickness in {"initial", "final"}: + idx = np.argmin((xth - xi) ** 2 + (zth - (cfg.dims[2] - zi)) ** 2) + cfg.multpv.append(str(mult[idx])) + + cfg.fluxnum = flux.astype(str).tolist() + cfg.fipnum = _classify_reporting_boxes(cfg, x, ztop, cfg.fluxnum) + + s1, s2 = _locate_structured_sensors(cfg, xcent, zcent) + cfg.fipnum[s1], cfg.fipnum[s2] = "8", "9" + + _locate_structured_sources(cfg, xcent, zcent) + + if cfg.hascellmaps: + _write_cell_map(cfg, x.tolist(), ztop.tolist()) + else: + shutil.copy(cfg.path / "geology/cellmap.npy", cfg.deck)
+ + + +
+[docs] +def _assign_corner_point_properties( + cfg: PofffConfig, + xyz: NDArray[np.float64], + polygons: list[Polygon], + xcoord: NDArray[np.float64], + zcoord: NDArray[np.float64], +) -> None: + """Assign properties and feature indices on a corner-point grid. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + xyz : NDArray[np.float64] + Cell-centroid coordinates with columns x, y, and z. + polygons : list[Polygon] + FluidFlower facies polygons in model coordinates. + xcoord : NDArray[np.float64] + Corner-point x coordinates arranged by pillar and vertical interface. + zcoord : NDArray[np.float64] + Corner-point z coordinates arranged by pillar and vertical interface.""" + x = xyz[:, 0] + ztop = cfg.dims[2] - xyz[:, 2] + + if cfg.thickness in {"initial", "final"}: + cfg.dims[1], xth, zth, mult = _load_thickness_map(cfg) + else: + cfg.dims[1] = float(cfg.thickness) + + flux = np.full(len(x), -1) + show_progress = sys.stdout.isatty() + if show_progress: + bar_ctx = alive_bar(len(x), bar="fish") + else: + bar_ctx = nullcontext() + with bar_ctx as bar_animation: + for i, (xi, zi) in enumerate(zip(x, ztop)): + if show_progress: + bar_animation() + pt = Point(xi, cfg.dims[2] - zi) + for j, poly in enumerate(polygons): + if poly.contains(pt): + flux[i] = cfg.facies[j] + break + + if cfg.thickness in {"initial", "final"}: + idx = np.argmin((xth - xi) ** 2 + (zth - zi) ** 2) + cfg.multpv.append(str(mult[idx])) + + cfg.fluxnum = flux.astype(str).tolist() + cfg.fipnum = _classify_reporting_boxes(cfg, x, ztop, cfg.fluxnum) + + s1, s2 = _locate_corner_point_sensors(cfg, x, ztop) + cfg.fipnum[s1], cfg.fipnum[s2] = "8", "9" + + _locate_corner_point_sources(cfg, x, ztop) + + if cfg.hascellmaps: + _write_cell_map(cfg, x.tolist(), ztop.tolist()) + else: + shutil.copy(cfg.path / "geology/cellmap.npy", cfg.deck) + + create_corner_point_grid(cfg, xcoord, zcoord)
+ + + +
+[docs] +def _assign_spatial_properties( + cfg: PofffConfig, + polygons: list[Polygon], + xcent: NDArray[np.float64] | None = None, + zcent: NDArray[np.float64] | None = None, + xyz: NDArray[np.float64] | None = None, + xcoord: NDArray[np.float64] | None = None, + zcoord: NDArray[np.float64] | None = None, +) -> None: + """Dispatch property assignment for the selected grid representation. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + polygons : list[Polygon] + FluidFlower facies polygons in model coordinates. + xcent : NDArray[np.float64] | None, optional + Cell-centre x coordinates in metres. + zcent : NDArray[np.float64] | None, optional + Cell-centre z coordinates in metres. + xyz : NDArray[np.float64] | None, optional + Cell-centroid coordinates with columns x, y, and z. + xcoord : NDArray[np.float64] | None, optional + Corner-point x coordinates arranged by pillar and vertical interface. + zcoord : NDArray[np.float64] | None, optional + Corner-point z coordinates arranged by pillar and vertical interface.""" + if cfg.grid == "corner-point": + assert xyz is not None + assert xcoord is not None + assert zcoord is not None + _assign_corner_point_properties(cfg, xyz, polygons, xcoord, zcoord) + else: + assert xcent is not None + assert zcent is not None + _assign_structured_grid_properties(cfg, xcent, zcent, polygons)
+ + + +
+[docs] +def _locate_structured_sensors( + cfg: PofffConfig, xcent: NDArray[np.float64], zcent: NDArray[np.float64] +) -> tuple[int, int]: + """Locate both observation sensors on a structured grid. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + xcent : NDArray[np.float64] + Cell-centre x coordinates in metres. + zcent : NDArray[np.float64] + Cell-centre z coordinates in metres. + + Returns + ------- + tuple[int, int] + Global zero-based indices of the two sensor cells.""" + sx1, sz1 = cfg.sensors[0] + sx2, sz2 = cfg.sensors[1] + dimz = cfg.dims[2] + + d1, d2 = [], [] + + for k in range(cfg.nxz[1]): + for i in range(cfg.nxz[0]): + d1.append((xcent[i] - sx1) ** 2 + (zcent[k] + sz1 - dimz) ** 2) + d2.append((xcent[i] - sx2) ** 2 + (zcent[k] + sz2 - dimz) ** 2) + + i1, i2 = int(np.argmin(d1)), int(np.argmin(d2)) + + for j, s in enumerate(cfg.sensors): + cfg.sensor_ik[j] = [ + int(np.argmin(np.abs(xcent - s[0]))), + int(np.argmin(np.abs(dimz - s[1] - zcent))), + ] + + return i1, i2
+ + + +
+[docs] +def _locate_structured_sources( + cfg: PofffConfig, xcent: NDArray[np.float64], zcent: NDArray[np.float64] +) -> None: + """Locate both injection sources on a structured grid. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + xcent : NDArray[np.float64] + Cell-centre x coordinates in metres. + zcent : NDArray[np.float64] + Cell-centre z coordinates in metres.""" + for i, w in enumerate(cfg.sources): + cfg.source_ik[i] = [ + int(np.argmin(np.abs(xcent - w[0]))) + 1, + int(np.argmin(np.abs(cfg.dims[2] - w[1] - zcent))) + 1, + ]
+ + + +
+[docs] +def _locate_corner_point_sensors( + cfg: PofffConfig, x: NDArray[np.float64], ztop: NDArray[np.float64] +) -> tuple[int, int]: + """Locate both observation sensors on a corner-point grid. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + x : NDArray[np.float64] + Cell or plotting x coordinates in metres. + ztop : NDArray[np.float64] + Cell-centre depth coordinates measured from the model top. + + Returns + ------- + tuple[int, int] + Global zero-based indices of the two sensor cells.""" + coords = np.column_stack((x, ztop)) + sensors = np.array(cfg.sensors) + d = ((coords[:, None] - sensors) ** 2).sum(axis=2) + + s1, s2 = int(np.argmin(d[:, 0])), int(np.argmin(d[:, 1])) + nx = cfg.nxz[0] + + cfg.sensor_ik[0] = [s1 % nx, s1 // nx] + cfg.sensor_ik[1] = [s2 % nx, s2 // nx] + + return s1, s2
+ + + +
+[docs] +def _locate_corner_point_sources( + cfg: PofffConfig, x: NDArray[np.float64], ztop: NDArray[np.float64] +) -> None: + """Locate both injection sources on a corner-point grid. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + x : NDArray[np.float64] + Cell or plotting x coordinates in metres. + ztop : NDArray[np.float64] + Cell-centre depth coordinates measured from the model top.""" + coords = np.column_stack((x, ztop)) + wells = np.array(cfg.sources) + d = ((coords[:, None] - wells) ** 2).sum(axis=2) + + for i in range(2): + idx = np.argmin(d[:, i]) + cfg.source_ik[i] = [ + int(idx % cfg.nxz[0] + 1), + int(idx // cfg.nxz[0] + 1), + ]
+ + + +
+[docs] +def _assign_box_region( + bounds: list[list[float]], + sand_region: str, + other_region: str, + x: NDArray[np.float64], + z: NDArray[np.float64], + fluxnum: NDArray[Any], + fipnum: NDArray[Any], +) -> None: + """Assign FIPNUM values inside one benchmark reporting box. + + Cells belonging to facies 1 receive ``sand_region``. All other cells + inside the box receive ``other_region``. The ``fipnum`` array is modified + in place. + + Parameters + ---------- + bounds : list[list[float]] + Lower-left and upper-right box corners as + ``[[xmin, zmin], [xmax, zmax]]`` in metres. + sand_region : str + FIPNUM assigned to cells in facies 1. + other_region : str + FIPNUM assigned to all other facies inside the box. + x : NDArray[np.float64] + Cell-centre x coordinates in global cell order, in metres. + z : NDArray[np.float64] + Cell-centre z coordinates in global cell order, in metres. + fluxnum : list[str] + Facies identifiers in global cell order. + fipnum : list[str] + FIPNUM values in global cell order, modified in place. + """ + inside = ( + (x >= bounds[0][0]) + & (x <= bounds[1][0]) + & (z >= bounds[0][1]) + & (z <= bounds[1][1]) + ) + unassigned = inside & (fipnum == "1") + fipnum[unassigned & (fluxnum == "1")] = sand_region + fipnum[unassigned & (fluxnum != "1")] = other_region
+ + + +
+[docs] +def _classify_reporting_boxes( + cfg: PofffConfig, + x: NDArray[np.float64], + z: NDArray[np.float64], + flux: list[str], +) -> list[str]: + """Assign benchmark reporting-region FIPNUM values. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + x : NDArray[np.float64] + Cell or plotting x coordinates in metres. + z : NDArray[np.float64] + Cell or plotting z coordinates in metres. + flux : list[str] + Facies identifiers in global cell order. + + Returns + ------- + list[str] + FIPNUM values in global cell order.""" + x, z, flux0 = map(np.asarray, (x, z, flux)) + fip = np.full(len(x), "1", dtype=object) + + _assign_box_region(cfg.boxb, "6", "3", x, z, flux0, fip) + _assign_box_region(cfg.boxc, "12", "4", x, z, flux0, fip) + _assign_box_region(cfg.boxa, "5", "2", x, z, flux0, fip) + + fip[(flux0 == "1") & (fip == "1")] = "7" + return fip.tolist()
+ + + +
+[docs] +def _write_cell_map( + cfg: PofffConfig, simxcent: NDArray[np.float64], simzcent: NDArray[np.float64] +) -> None: + """Map benchmark reporting cells to their nearest simulation cells. + + The mapping is written as ``cellmap.npy`` below ``cfg.deck``. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + simxcent : NDArray[np.float64] + Simulation-cell x coordinates in global cell order. + simzcent : NDArray[np.float64] + Simulation-cell z coordinates in global cell order.""" + refx = np.arange(0, 2.8 + 5.0e-3, 1.0e-2) + refz = np.arange(0, 1.2 + 5.0e-3, 1.0e-2) + refx = 0.5 * (refx[1:] + refx[:-1]) + refz = 0.5 * (refz[1:] + refz[:-1]) + + x, z = np.meshgrid(refx, refz, indexing="xy") + ref = np.column_stack((x.ravel(), z.ravel())) + sim = np.column_stack((simxcent, simzcent)) + + d = np.abs(sim[:, None] - ref).sum(axis=2) + np.save(Path(cfg.deck) / "cellmap.npy", np.argmin(d, axis=0))
+ + + +
+[docs] +def _read_horizon_lines( + cfg: PofffConfig, points: list[list[float]] +) -> list[list[list[float]]]: + """Read and order geological horizon polylines. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + points : list[list[float]] + Geological points represented by x and z coordinates in metres. + + Returns + ------- + list[list[list[float]]] + Geological horizons ordered from bottom to top.""" + horizonts: list[list[list[float]]] = [] + with open(cfg.path / "geology/horizonts.geo", encoding="utf8") as file: + for r in csv.reader(file, delimiter=" "): + if r[0].startswith("Line"): + if not horizonts[-1]: + horizonts[-1].append(points[int(r[2][1:-1]) - 1]) + horizonts[-1].append(points[int(r[3][:-2]) - 1]) + if len(r) > 1 and r[1] == "Horizont": + horizonts.append([]) + return horizonts[::-1]
+ + + +
+[docs] +def _read_geological_polygons( + cfg: PofffConfig, +) -> tuple[list[Polygon], list[list[float]]]: + """Read geological points, line connectivity, and facies polygons. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + + Returns + ------- + tuple[list[Polygon], list[list[float]]] + Facies polygons and the geological point coordinates.""" + polygons, points, lines, curves, facie = [], [], [], [], 0 + h_ref, l_ref = 1.5 / 1490, 2.8 / 2594 + + with open(cfg.path / "geology/points.geo", encoding="utf8") as file: + for r in csv.reader(file, delimiter=" "): + if r[0].startswith("Point"): + points.append( + [l_ref * float(r[2][1:-1]), cfg.dims[2] - h_ref * float(r[3][:-1])] + ) + + with open(cfg.path / "geology/lines.geo", encoding="utf8") as file: + for r in csv.reader(file, delimiter=" "): + if r[0].startswith("Line"): + lines.append([int(r[2][1:-1]), int(r[3][:-2])]) + + with open(cfg.path / "geology/polygons.geo", encoding="utf8") as file: + for r in csv.reader(file, delimiter=" "): + if r[0] == "Curve": + cfg.facies.append(facie) + curve = [] + for t in r[3:]: + t = t.strip(",;{}") + if t: + curve.append(int(t)) + curves.append(curve) + if len(r) > 1 and r[1] in {"Sand", "Water"}: + facie += 1 + + for c in curves: + poly = [] + for lid in c: + idx = 0 if lid < 0 else 1 + poly.append(points[lines[abs(lid) - 1][idx] - 1]) + poly.append(poly[0]) + polygons.append(Polygon(poly)) + + return polygons, points
+ + + +
+[docs] +def _refine_vertical_coordinates( + xci: NDArray[np.float64], + zci: NDArray[np.float64], + ncz: int, + znr: NDArray[np.float64], +) -> tuple[NDArray[np.float64], NDArray[np.float64], int, int]: + """Subdivide geological layers by their vertical refinement factors. + + Parameters + ---------- + xci : NDArray[np.float64] + Unrefined corner-point x coordinates. + zci : NDArray[np.float64] + Unrefined corner-point z coordinates. + ncz : int + Number of unrefined vertical cells. + znr : NDArray[np.float64] + Vertical refinement factor for each geological layer. + + Returns + ------- + tuple[NDArray[np.float64], NDArray[np.float64], int, int] + Refined x and z coordinates and the new x and z cell counts.""" + stride = ncz + 1 + ncols = len(xci) // stride + + xcr, zcr = [], [] + + for j in range(ncols): + b = j * stride + xcr.append(xci[b]) + zcr.append(zci[b]) + for i in range(ncz): + w = np.arange(1.0 / znr[i], 1.0 + 1.0 / znr[i], 1.0 / znr[i]) + xcr.extend(xci[b + i] + (xci[b + i + 1] - xci[b + i]) * w) + zcr.extend(zci[b + i] + (zci[b + i + 1] - zci[b + i]) * w) + + ncx_new = ncols - 1 + ncz_new = np.where(zcr == zcr[-1])[0][0] + + return np.asarray(xcr), np.asarray(zcr), ncx_new, ncz_new
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/docs/_modules/pofff/utils/runs.html b/docs/_modules/pofff/utils/runs.html new file mode 100644 index 0000000..fcc3cdc --- /dev/null +++ b/docs/_modules/pofff/utils/runs.html @@ -0,0 +1,814 @@ + + + + + + + + + + pofff.utils.runs — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for pofff.utils.runs

+# SPDX-FileCopyrightText: 2025-2026 NORCE Research AS
+# SPDX-License-Identifier: GPL-3.0
+
+"""Execute simulation, data, history-matching, and plotting workflows.
+
+The functions run OPM Flow and the maintained data scripts, launch ERT or
+Everest, postprocess ensemble results, and coordinate benchmark maps, sparse
+values, Wasserstein comparisons, and error tables."""
+
+import subprocess
+from collections.abc import Sequence
+from pathlib import Path
+
+from pofff.config.config import PofffConfig
+from pofff.utils.terminal import cli_correct_value, cli_error_value, pofff_error
+from pofff.visualization.benchmark import run_benchmark
+from pofff.visualization.error_table import run_error_table
+from pofff.visualization.everert import run_everert
+from pofff.visualization.maps import run_maps
+from pofff.visualization.sparse_values import main as sparse_values
+
+
+
+[docs] +def _run(cmd: Sequence[str]) -> None: + """Execute a command and abort if it fails. + + Parameters + ---------- + cmd : Sequence[str] + Command and arguments to execute without a shell. + + Raises + ------ + subprocess.CalledProcessError + If the command exits with a nonzero status.""" + subprocess.run(cmd, check=True)
+ + + +
+[docs] +def flow(cfg: PofffConfig) -> None: + """Run the OPM Flow simulator with configured options. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + + Raises + ------ + subprocess.CalledProcessError + If OPM Flow exits with a nonzero status.""" + _run( + cfg.flow.split(" ") + + [f"--output-dir={cfg.fol}", str(Path(cfg.fol) / f"{cfg.data}.DATA")] + )
+ + + +
+[docs] +def data(cfg: PofffConfig) -> None: + """Generate benchmark time-series and spatial data. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + + Raises + ------ + subprocess.CalledProcessError + If a data or metric script exits with a nonzero status.""" + _run( + [ + "python", + str(cfg.path / "jobs" / "data.py"), + "-m", + str(Path(cfg.deck) / "cellmap.npy"), + "-t", + cfg.times, + ] + ) + + _run( + [ + "python", + str(cfg.path / "jobs" / "metric.py"), + "-e", + cfg.experiment, + "-p", + str(cfg.path), + "-t", + cfg.times, + "-s", + cfg.msat, + "-c", + cfg.mcon, + ] + )
+ + + +
+[docs] +def benchmark(cfg: PofffConfig) -> None: + """Generate benchmark figures and comparisons. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" + args = [ + "-e", + cfg.experiment, + "-t", + cfg.times, + "-p", + str(cfg.path), + "-s", + cfg.msat, + "-c", + cfg.mcon, + "-l", + "." if cfg.mode != "fair" else cfg.location, + ] + run_maps(args) + + sparse_values(timeseries_file=f"{cfg.location}/time_series.csv") + + args = [ + "-f", + cfg.figures, + "-p", + str(cfg.path), + "-s", + cfg.msat, + "-t", + cfg.times, + "-c", + cfg.mcon, + "-l", + cfg.location, + "-a", + str(int(cfg.mode != "fair")), + "-u", + cfg.use, + ] + run_benchmark(args) + + if cfg.figures == "all": + if cfg.times != "24,48,72,96,120": + pofff_error( + "the error table requires a 120-hour simulation, no " + f"{cli_error_value(f'-t {cfg.times}')} but " + f"{cli_correct_value('-t 24,48,72,96,120')}." + ) + else: + args = [ + "-p", + str(cfg.path), + "-s", + cfg.msat, + "-c", + cfg.mcon, + "-l", + cfg.location, + "-a", + str(int(cfg.mode != "fair")), + ] + run_error_table(args)
+ + + +
+[docs] +def everest() -> None: + """Run Everest optimization and skip interactive prompts. + + Raises + ------ + subprocess.CalledProcessError + If Everest exits with a nonzero status.""" + _run(["everest", "run", "everest.yml", "--skip-prompt"])
+ + + +
+[docs] +def ert(cfg: PofffConfig) -> None: + """Run ERT with configured arguments. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + + Raises + ------ + subprocess.CalledProcessError + If ERT exits with a nonzero status.""" + _run(["ert", *(cfg.ertargs or "").split(), "ert.txt"])
+ + + +
+[docs] +def postprocess(cfg: PofffConfig) -> None: + """Postprocess ERT and Everest simulation results. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" + args = [ + "-e", + str(cfg.path), + "-s", + cfg.msat, + "-c", + cfg.mcon, + "-r", + cfg.experiment, + "-t", + cfg.times, + "-m", + str(Path(cfg.deck) / "cellmap.npy"), + ] + run_everert(args)
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/docs/_modules/pofff/utils/terminal.html b/docs/_modules/pofff/utils/terminal.html new file mode 100644 index 0000000..6c035e5 --- /dev/null +++ b/docs/_modules/pofff/utils/terminal.html @@ -0,0 +1,842 @@ + + + + + + + + + + pofff.utils.terminal — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for pofff.utils.terminal

+# SPDX-FileCopyrightText: 2026 NORCE Research AS
+# SPDX-License-Identifier: MIT
+
+"""Format command-line messages for pofff.
+
+ANSI colors are applied when supported by the selected stream. The module
+provides consistent formatting for invalid, accepted, deprecated, and
+informational values, plus fatal errors, warnings, tips, progress messages, and
+generated-file reports."""
+
+import os
+import sys
+from typing import NoReturn
+
+ANSI_BOLD_RED = "1;31"
+ANSI_BOLD_YELLOW = "1;33"
+ANSI_BOLD_GREEN = "1;32"
+ANSI_BOLD_BLUE = "1;34"
+ANSI_BOLD_MAGENTA = "1;35"
+ANSI_YELLOW = "1;33"
+ANSI_GREEN = "1;32"
+ANSI_CYAN = "36"
+ANSI_RED = "31"
+ANSI_BLUE = "1;34"
+
+
+
+[docs] +def _supports_color(stream: object = sys.stderr) -> bool: + """Check whether an output stream supports ANSI colors. + + Parameters + ---------- + stream : object, optional + Output stream used to determine ANSI-color support. + + Returns + ------- + bool + Whether ANSI color output is enabled for the stream.""" + return ( + hasattr(stream, "isatty") + and stream.isatty() + and os.environ.get("NO_COLOR") is None + and os.environ.get("TERM") != "dumb" + )
+ + + +
+[docs] +def _colorize( + text: str, + code: str, + stream: object = sys.stderr, +) -> str: + """Wrap text in an ANSI color sequence when supported. + + Parameters + ---------- + text : str + Text to format. + code : str + ANSI Select Graphic Rendition code. + stream : object, optional + Output stream used to determine ANSI-color support. + + Returns + ------- + str + Colored text, or unchanged text when colors are disabled.""" + if not _supports_color(stream): + return text + return f"\033[{code}m{text}\033[0m"
+ + + +
+[docs] +def cli_warning_value(value: str) -> str: + """Format a deprecated CLI option or value. + + Parameters + ---------- + value : str + Value to inspect or format. + + Returns + ------- + str + Quoted value colored as a warning when supported.""" + return _colorize(repr(value), ANSI_YELLOW)
+ + + +
+[docs] +def cli_correct_value(value: str) -> str: + """Format a correct CLI option or value. + + Parameters + ---------- + value : str + Value to inspect or format. + + Returns + ------- + str + Quoted value colored as a valid alternative when supported.""" + return _colorize(repr(value), ANSI_GREEN)
+ + + +
+[docs] +def cli_error_value(value: str) -> str: + """Format an invalid CLI option or value. + + Parameters + ---------- + value : str + Value to inspect or format. + + Returns + ------- + str + Quoted value colored as invalid when supported.""" + return _colorize(repr(value), ANSI_RED)
+ + + +
+[docs] +def cli_info_value(value: str) -> str: + """Format an informational CLI option or value. + + Parameters + ---------- + value : str + Value to inspect or format. + + Returns + ------- + str + Quoted value colored as information when supported.""" + return _colorize(repr(value), ANSI_BLUE)
+ + + +
+[docs] +def pofff_error(message: str) -> NoReturn: + """Raise a fatal command-line error. + + Parameters + ---------- + message : str + Human-readable message to display or append. + + Raises + ------ + SystemExit + Always raised with the formatted error message.""" + label = _colorize("error", ANSI_BOLD_RED) + raise SystemExit(f"{pofff_name()}: {label}: {message}")
+ + + +
+[docs] +def pofff_warning(message: str) -> None: + """Display a non-fatal command-line warning. + + Parameters + ---------- + message : str + Human-readable message to display or append.""" + label = _colorize("warning", ANSI_BOLD_YELLOW) + print(f"{pofff_name()}: {label}: {message}", file=sys.stderr)
+ + + +
+[docs] +def pofff_info(message: str) -> None: + """Display an informational command-line message. + + Parameters + ---------- + message : str + Human-readable message to display or append.""" + label = _colorize("info", ANSI_BOLD_BLUE, sys.stdout) + print(f"{pofff_name()}: {label}: {message}")
+ + + +
+[docs] +def pofff_tip(message: str) -> None: + """Display a command-line suggestion. + + Parameters + ---------- + message : str + Human-readable message to display or append.""" + label = _colorize("tip", ANSI_BOLD_MAGENTA, sys.stdout) + print(f"{pofff_name(sys.stdout)}: {label}: {message}")
+ + + +
+[docs] +def pofff_success(msg: str, output_dir: str, filenames: list[str]) -> None: + """Display the generated output location and filenames. + + Parameters + ---------- + msg : str + Optional success text printed before the output location. + output_dir : str + Directory containing the generated files. + filenames : list[str] + Generated filenames to report.""" + label = _colorize("success", ANSI_BOLD_GREEN, sys.stdout) + if not filenames: + print(f"{pofff_name()}: {label}: {msg}{output_dir}") + elif len(filenames) == 1: + print(f"{pofff_name()}: {label}: {msg}{output_dir}/{filenames[0]}") + elif len(filenames) <= 5: + print(f"{pofff_name()}: {label}{msg}") + print(f" Output directory: {output_dir}") + print(f" Files ({len(filenames)}): {', '.join(filenames)}") + else: + print(f"{pofff_name()}: {label}{msg}") + print(f" Output directory: {output_dir}") + print(f" Files ({len(filenames)}):") + for filename in filenames: + print(f" - {filename}")
+ + + +
+[docs] +def pofff_name(stream: object = sys.stderr) -> str: + """Format the pofff program name. + + Parameters + ---------- + stream : object, optional + Output stream used to determine ANSI-color support. + + Returns + ------- + str + Formatted program name.""" + characters = [("pofff", "1")] + return "".join( + _colorize(character, color, stream) for character, color in characters + )
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/docs/_modules/pofff/utils/writefile.html b/docs/_modules/pofff/utils/writefile.html new file mode 100644 index 0000000..24cda67 --- /dev/null +++ b/docs/_modules/pofff/utils/writefile.html @@ -0,0 +1,1373 @@ + + + + + + + + + + pofff.utils.writefile — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for pofff.utils.writefile

+# SPDX-FileCopyrightText: 2025-2026 NORCE Research AS
+# SPDX-License-Identifier: GPL-3.0
+# pylint: disable=R0912, R0914
+
+"""Write OPM Flow, ERT, Everest, and generated job files.
+
+The module serializes grid and region keywords, renders the main OPM deck, and
+creates helper scripts for boundary properties, saturation functions, regional
+properties, Flow execution, copying, scaling, and monotonicity checks. It also
+renders the ERT and Everest workflow configurations."""
+
+import os
+import subprocess
+from itertools import pairwise
+
+import numpy as np
+from mako.template import Template
+from numpy.typing import NDArray
+
+from pofff.config.config import PofffConfig
+
+HEADER = (
+    "-- Copyright (C) 2025-2026 NORCE Research AS\n"
+    "-- This deck was generated by pofff https://github.com/cssr-tools/pofff\n"
+)
+
+
+
+[docs] +def _compact_eclipse_values(values: list) -> list: + """Encode repeated numeric values with Eclipse run-length syntax. + + Parameters + ---------- + values : list + Values to encode in their existing global order. + + Returns + ------- + list + Eclipse-formatted strings using ``n*value`` compaction.""" + if len(values) == 0: + return [] + v = np.asarray(values, dtype=np.float64) + change = np.concatenate(([True], v[1:] != v[:-1])) + idx = np.flatnonzero(change) + counts = np.diff(np.append(idx, v.size)) + uniq = v[idx] + out = [] + for val, n in zip(uniq, counts): + if n > 1: + if val == 0: + out.append(f"{n}*0 ") + elif val.is_integer(): + out.append(f"{n}*{int(val)} ") + else: + out.append(f"{n}*{val} ") + else: + if val == 0: + out.append("0 ") + elif val.is_integer(): + out.append(f"{int(val)} ") + else: + out.append(f"{val} ") + return out
+ + + +
+[docs] +def create_corner_point_grid( + cfg: PofffConfig, xcoord: NDArray, zcoord: NDArray +) -> None: + """Write OPM COORD and ZCORN data for the corner-point grid. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + xcoord : NDArray + Corner-point x coordinates arranged by pillar and vertical interface. + zcoord : NDArray + Corner-point z coordinates arranged by pillar and vertical interface.""" + + nx, nz = cfg.nxz + nk = nz + 1 + grid = [] + grid.append("COORD\n") + tmp = [] + for y in [0, cfg.dims[1]]: + for i in range(nx + 1): + base = i * nk + tmp.append(f"{xcoord[base]:E} {y} 0 " f"{xcoord[base + nz]:E} {y:E} 0 ") + grid.extend(_compact_eclipse_values("".join(tmp).split())) + grid.append("/\n") + grid.append("ZCORN\n") + tmp = [] + for i in range(nx): + tmp.append(f"{zcoord[i*nk]:E} {zcoord[(i+1)*nk]:E} ") + tmp.extend(tmp[-nx:]) + for k in range(nz - 1): + for i in range(nx): + base = i * nk + k + 1 + tmp.append(f"{zcoord[base]:E} {zcoord[base + nk]:E} ") + tmp.extend(tmp[-nx:]) + tmp.extend(tmp[-2 * nx :]) + for i in range(nx): + tmp.append(f"{zcoord[(i+1)*nk - 1]:E} {zcoord[(i+2)*nk - 1]:E} ") + tmp.extend(tmp[-nx:]) + grid.extend(_compact_eclipse_values("".join(tmp).split())) + grid.append("/") + with open(f"{cfg.deck}/GRID.INC", "w", encoding="utf8") as f: + f.write(HEADER) + f.write("".join(grid)) + f.write("\n")
+ + + +
+[docs] +def _write_keyword_files(cfg: PofffConfig) -> None: + """Write generated OPM property and geometry include files. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" + keywords = ["fluxnum", "fipnum"] + if cfg.grid == "tensor": + keywords += ["dx", "dz"] + if cfg.thickness in ("initial", "final"): + keywords += ["multpv", "multx", "multx-", "multz", "multz-"] + for name in keywords: + values = getattr(cfg, "multpv" if name.startswith("mult") else name) + body = _compact_eclipse_values(values) + with open(f"{cfg.deck}/{name.upper()}.INC", "w", encoding="utf8") as f: + f.write(HEADER) + f.write(f"{name.upper()}\n") + f.writelines(body) + f.write("/\n")
+ + + +
+[docs] +def write_files(cfg: PofffConfig) -> None: + """Generate the OPM deck and optional ERT or Everest files. + + Generated files are written below ``cfg.deck``, ``cfg.jobs``, and ``cfg.fol`` + according to the selected workflow. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" + _write_keyword_files(cfg) + tmpl = Template(filename=f"{cfg.path}/templates/deck.mako") + mytemplate = tmpl.render( + nxz=cfg.nxz, + grid=cfg.grid, + deck=cfg.deck, + dims=cfg.dims, + diffusion=cfg.diffusion, + multpv=cfg.multpv, + hm=cfg.hm, + pressure=cfg.pressure, + temperature=cfg.temperature, + sensor_ik=cfg.sensor_ik, + source_ik=cfg.source_ik, + inj=cfg.inj, + tuning=cfg.tuning, + ) + with open(f"{cfg.deck}/{cfg.data}.DATA", "w", encoding="utf8") as f: + f.write(mytemplate) + job_names = ["bcprop", "equalreg", "satufunc"] + _render_boundary_properties_script(cfg) + _render_region_properties_script(cfg) + _render_saturation_functions_script(cfg) + if cfg.everert: + _render_flow_script(cfg) + _render_copy_script(cfg) + job_names += ["flow", "copyd"] + if cfg.popsize: + job_names += ["scale"] + _render_scale_script(cfg) + with open(f"{cfg.jobs}/METRIC", "w", encoding="utf8") as f: + f.write("EXECUTABLE metric.py") + + with open(f"{cfg.jobs}/DATA", "w", encoding="utf8") as f: + f.write("EXECUTABLE data.py") + _render_everest_configuration(cfg) + else: + with open(f"{cfg.jobs}/METRIC", "w", encoding="utf8") as f: + f.write("EXECUTABLE metric.py\n") + f.write( + f"ARGLIST -t {cfg.times} -e {cfg.experiment} " + f"-s {cfg.msat} -c {cfg.mcon} -p {cfg.path}" + ) + with open(f"{cfg.jobs}/DATA", "w", encoding="utf8") as f: + f.write("EXECUTABLE data.py\n") + f.write(f"ARGLIST -t {cfg.times} -m {cfg.deck}/cellmap.npy") + _render_ert_configuration(cfg) + if cfg.monotonic: + _render_monotonic_script(cfg) + job_names += ["monotonic"] + for name in job_names: + script_path = f"{cfg.jobs}/{name}.py" + os.chmod(script_path, 0o755) + if cfg.mode in ["single", "files"] and not cfg.everert: + subprocess.run(["python3", script_path], check=True) + os.remove(script_path) + if cfg.ertargs and cfg.everert: + assert cfg.errors + with open(f"{cfg.deck}/obs.txt", "w", encoding="utf8") as f: + f.write("\n".join(f"0 {error}" for error in cfg.errors) + "\n") + with open(f"{cfg.deck}/para.txt", "w", encoding="utf8") as f: + f.write( + "\n".join( + f"{para} {cfg.hm[para][0].upper()} {cfg.hm[para][1]} {cfg.hm[para][2]}" + for para in cfg.hm + ) + + "\n" + ) + content = "{\n" + ",\n".join(f'"{para}": <{para}>' for para in cfg.hm) + "\n}\n" + with open(f"{cfg.deck}/prior.tmpl", "w", encoding="utf8") as f: + f.write(content)
+ + + +
+[docs] +def _render_monotonic_script(cfg: PofffConfig) -> None: + """Render the generated monotonicity-check job script. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" + inc_names = ["swi", "sni", "pen", "nkrw", "nkrn", "npe"] + dec_names = ["perm", "permx", "permz", "disperc"] + + inc_lines = [] + for name in inc_names: + keys = [f"{name}{i}" for i in range(1, 8) if f"{name}{i}" in cfg.hm] + for prev, cur in pairwise(keys): + inc_lines.append( + f"if coef['{prev}'] < coef['{cur}']:\n" f" nomonotonic = True" + ) + + dec_lines = [] + for name in dec_names: + keys = [f"{name}{i}" for i in reversed(range(1, 8)) if f"{name}{i}" in cfg.hm] + for prev, cur in pairwise(keys): + dec_lines.append( + f"if coef['{prev}'] < coef['{cur}']:\n" f" nomonotonic = True" + ) + + increasing_block = "\n".join(inc_lines) + decreasing_block = "\n".join(dec_lines) + + tmpl = Template(filename=f"{cfg.path}/templates/monotonic.mako") + with open(f"{cfg.jobs}/monotonic.py", "w", encoding="utf8") as f: + f.write( + tmpl.render( + increasing_block=increasing_block, + decreasing_block=decreasing_block, + ) + )
+ + + +
+[docs] +def _render_scale_script(cfg: PofffConfig) -> None: + """Render the generated Everest parameter-scaling script. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" + parts = [] + + keys = list(cfg.hm.keys()) + + for i, p in enumerate(keys): + lo, hi, scale = cfg.hm[p][1], cfg.hm[p][2], cfg.hm[p][3] + + expr = f"{lo}+coef['{p}']*({hi}-{lo})/(1.0*{scale})" + + comma = "," if i < len(keys) - 1 else "" + + parts.append(f' para += f"\\"{p}\\":"\n' f' para += f"{{{expr}}}{comma}"') + + para_block = "\n".join(parts) + + tmpl = Template(filename=f"{cfg.path}/templates/scale.mako") + with open(f"{cfg.jobs}/scale.py", "w", encoding="utf8") as f: + f.write( + tmpl.render( + para_block=para_block, + ) + )
+ + + +
+[docs] +def _render_boundary_properties_script(cfg: PofffConfig) -> None: + """Render the generated boundary-property job script. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" + imports_block = "" + + if cfg.monotonic: + imports_block += "import sys\n" + if cfg.everert: + imports_block += "import os\n" + + if cfg.everert: + imports_block += "import json" + else: + imports_block += "import os" + + monotonic_block = ( + ' if os.path.exists("NOMONOTONIC"):\n' " sys.exit()\n" + if cfg.monotonic + else "" + ) + + if cfg.everert: + coef_block = ( + ' with open("para.json", "r", encoding="utf8") as f:\n' + " coef = json.load(f)\n" + ) + bcprop_path = '"BCPROP.INC"' + else: + coef_block = "" + bcprop_path = f"os.path.join('{cfg.fol}', \"BCPROP.INC\")" + + if cfg.everert and "pen1" in cfg.hm: + if cfg.popsize: + lo, hi, scale = cfg.hm["pen1"][1], cfg.hm["pen1"][2], cfg.hm["pen1"][3] + val = f"({cfg.pressure}+{lo}+coef['pen1']*({hi}-{lo})/(1.0*{scale}))" + else: + val = f"({cfg.pressure}+coef['pen1'])" + else: + val = f"({cfg.pressure}+{cfg.para['pen1']})" + + bc_line = f' file.write(f"1 DIRICHLET WATER 1* {{{val}/1.e5}} /\\n")\n' + + thickness_block = "" + + if cfg.everert and "thicknessmult" in cfg.hm: + if cfg.popsize: + lo, hi, scale = ( + cfg.hm["thicknessmult"][1], + cfg.hm["thicknessmult"][2], + cfg.hm["thicknessmult"][3], + ) + mult_val = f"{lo}+coef['thicknessmult']*({hi}-{lo})/(1.0*{scale})" + else: + mult_val = "coef['thicknessmult']" + + size = cfg.nxz[0] * cfg.nxz[1] + + lines = [' with open("THICKNESSMULT.INC", "w", encoding="utf8") as file:'] + + for name in ["PV", "X", "X-", "Z", "Z-"]: + lines.append(f' file.write("MULT{name}\\n")') + lines.append(f' file.write(f"{size}*{{{mult_val}}} /\\n")') + + thickness_block = "\n".join(lines) + + tmpl = Template(filename=f"{cfg.path}/templates/bcprop.mako") + with open(f"{cfg.jobs}/bcprop.py", "w", encoding="utf8") as f: + f.write( + tmpl.render( + imports_block=imports_block, + monotonic_block=monotonic_block, + coef_block=coef_block, + bcprop_path=bcprop_path, + bc_line=bc_line, + thickness_block=thickness_block, + ) + )
+ + + +
+[docs] +def _render_copy_script(cfg: PofffConfig) -> None: + """Render the generated deck-copy job script. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" + monotonic_block = ( + 'if os.path.exists("NOMONOTONIC"):\n' " sys.exit()\n" + if cfg.monotonic + else "" + ) + + tmpl = Template(filename=f"{cfg.path}/templates/copyd.mako") + with open(f"{cfg.jobs}/copyd.py", "w", encoding="utf8") as f: + f.write( + tmpl.render( + monotonic_block=monotonic_block, + deck=cfg.deck, + ) + )
+ + + +
+[docs] +def _render_flow_script(cfg: PofffConfig) -> None: + """Render the generated OPM Flow execution script. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" + monotonic_block = ( + 'if os.path.exists("NOMONOTONIC"):\n' " sys.exit()\n" + if cfg.monotonic + else "" + ) + + cmd_parts = [f"'{x}'" for x in cfg.flow.split(" ")] + cmd_str = f"[{', '.join(cmd_parts)}, '{cfg.data}']" + + if cfg.maxtime is not None: + run_block = f" p = subprocess.run({cmd_str}, timeout={cfg.maxtime})\n" + else: + run_block = f" p = subprocess.run({cmd_str})\n" + + delete_block = "" + if cfg.delete: + delete_block = ( + ' for suff in ["INC", "EGRID", "DBG", "PRT", "SMSPEC", "UNRST", ' + '"UNSMRY", "INIT", "ESMRY", "DATA"]:\n' + ' os.system(f"rm -rf *.{suff}")\n' + ' for pref in ["thickness_eval","metr_eval","safu_eval","poro_eval"]:\n' + ' os.system(f"rm -rf {pref}.*")' + ) + + tmpl = Template(filename=f"{cfg.path}/templates/flow.mako") + with open(f"{cfg.jobs}/flow.py", "w", encoding="utf8") as f: + f.write( + tmpl.render( + monotonic_block=monotonic_block, + run_block=run_block, + delete_block=delete_block, + ) + )
+ + + +
+[docs] +def _render_region_properties_script(cfg: PofffConfig) -> None: + """Render the generated regional rock-property script. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" + imports_block = "" + + if cfg.monotonic: + imports_block += "import sys\n" + if cfg.everert: + imports_block += "import os\n" + + if cfg.everert: + imports_block += "import json" + else: + imports_block += "import os" + + monotonic_block = ( + ' if os.path.exists("NOMONOTONIC"):\n' " sys.exit()\n" + if cfg.monotonic + else "" + ) + + if cfg.everert: + coef_block = ( + ' with open("para.json", "r", encoding="utf8") as f:\n' + " coef = json.load(f)\n" + ) + output_path = '"EQUALREG.INC"' + else: + coef_block = "" + output_path = f"os.path.join('{cfg.fol}', \"EQUALREG.INC\")" + + names = [ + ("poro", "poro"), + ("permx", "permx"), + ("permy", "permx"), + ("permz", "permz"), + ("disperc", "disperc"), + ] + + lines = [] + + for i in range(1, 8): + for name, key in names: + hm_key = f"{name}{i}" + perm_key = f"perm{i}" + val = None + + if cfg.everert: + if hm_key in cfg.hm: + if cfg.popsize: + lo, hi, scale = ( + cfg.hm[hm_key][1], + cfg.hm[hm_key][2], + cfg.hm[hm_key][3], + ) + val = f"{lo}+coef['{hm_key}']*({hi}-{lo})/(1.0*{scale})" + else: + val = f"coef['{hm_key}']" + + elif name in ("permx", "permy", "permz") and perm_key in cfg.hm: + if cfg.popsize: + lo, hi, scale = ( + cfg.hm[perm_key][1], + cfg.hm[perm_key][2], + cfg.hm[perm_key][3], + ) + val = f"{lo}+coef['{perm_key}']*({hi}-{lo})/(1.0*{scale})" + else: + val = f"coef['{perm_key}']" + + if val is None: + val = cfg.para[f"{key}{i}"] + + lines.append(f' file.write(f"{name.upper()} {{{val}}} {i} F /\\n")') + + body_block = "\n".join(lines) + + tmpl = Template(filename=f"{cfg.path}/templates/equalreg.mako") + with open(f"{cfg.jobs}/equalreg.py", "w", encoding="utf8") as f: + f.write( + tmpl.render( + imports_block=imports_block, + monotonic_block=monotonic_block, + coef_block=coef_block, + output_path=output_path, + body_block=body_block, + ) + )
+ + + +
+[docs] +def _render_saturation_functions_script(cfg: PofffConfig) -> None: + """Render the generated saturation-function script. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" + imports_block = "" + if cfg.monotonic: + imports_block += "import os\nimport sys\n" + if cfg.everert: + imports_block += "import json" + + monotonic_block = ( + ' if os.path.exists("NOMONOTONIC"):\n' " sys.exit()\n" + if cfg.monotonic + else "" + ) + + coef_block = ( + ' with open("para.json", "r", encoding="utf8") as f:\n' + " coef = json.load(f)\n" + if cfg.everert + else "" + ) + + tables_path = '"TABLES.INC"' if cfg.everert else f'"{cfg.fol}/TABLES.INC"' + + names = ["swi", "sni", "pen", "nkrw", "nkrn", "npe", "thre", "npnt"] + rows = [] + + for i in range(1, 8): + vals = [] + for name in names: + key = f"{name}{i}" + val = None + + if cfg.everert and key in cfg.hm: + if cfg.popsize: + lo, hi, scale = ( + cfg.hm[key][1], + cfg.hm[key][2], + cfg.hm[key][3], + ) + val = f"{lo}+coef['{key}']*({hi}-{lo})/(1.0*{scale})" + else: + val = f"coef['{key}']" + + if val is None: + val = cfg.para[key] + + vals.append(val) + + rows.append(f" safu[{i-1}] = [{', '.join(map(str, vals))}]") + + safu_block = "\n".join(rows) + + tmpl = Template(filename=f"{cfg.path}/templates/satufunc.mako") + with open(f"{cfg.jobs}/satufunc.py", "w", encoding="utf8") as f: + f.write( + tmpl.render( + imports_block=imports_block, + krw_expr=cfg.krw.strip(), + krn_expr=cfg.krn.strip(), + cap_expr=cfg.cap.strip(), + monotonic_block=monotonic_block, + coef_block=coef_block, + safu_block=safu_block, + tables_path=tables_path, + ) + )
+ + + +
+[docs] +def _render_ert_configuration(cfg: PofffConfig) -> None: + """Render the ERT configuration and forward-model sequence. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" + names = ["copyd", "equalreg", "satufunc", "bcprop", "flow"] + lines = [] + for name in names: + lines.append(f"INSTALL_JOB {name} ./jobs/{name.upper()}") + lines.append(f"FORWARD_MODEL {name}") + jobs_block = "\n".join(lines) + random_seed_block = ( + f"RANDOM_SEED {cfg.random_seed}" if cfg.random_seed is not None else "" + ) + delete_block = "" + if cfg.delete: + delete_block = "INSTALL_JOB delete ./jobs/DELETE\nFORWARD_MODEL delete" + tmpl = Template(filename=f"{cfg.path}/templates/ert.mako") + rendered = tmpl.render( + ensembles=cfg.ensembles, + min_realizations_success=cfg.min_realizations_success, + cores=cfg.cores, + random_seed=cfg.random_seed, + enkf_alpha=cfg.enkf_alpha, + delete=cfg.delete, + jobs_block=jobs_block, + random_seed_block=random_seed_block, + delete_block=delete_block, + ) + with open(f"{cfg.fol}/ert.txt", "w", encoding="utf8") as f: + f.write(rendered)
+ + + +
+[docs] +def _render_everest_configuration(cfg: PofffConfig) -> None: + """Render the Everest optimization configuration. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" + variables_block = "\n".join( + [ + f" - name: {p}\n" + f" max: {cfg.hm[p][3]}\n" + f" initial_guess: " + f"{round((cfg.hm[p][0]-cfg.hm[p][1])/((cfg.hm[p][2]-cfg.hm[p][1])/(1.0*cfg.hm[p][3])))}" + for p in cfg.hm + ] + ) + + options_names = [ + "strategy", + "maxiter", + "popsize", + "tol", + "mutation", + "recombination", + "rng", + "callback", + "disp", + "polish", + "init", + "atol", + "updating", + "workers", + "constraints", + "x0", + "integrality", + "vectorized", + ] + + options_block = "\n".join( + [f" {n}: {getattr(cfg, n)}" for n in options_names if getattr(cfg, n)] + ) + + install_jobs_block = ( + ( + " - name: scale\n" + " executable: jobs/scale.py\n" + " - name: monotonic\n" + " executable: jobs/monotonic.py\n" + ) + if cfg.monotonic + else "" + ) + "\n".join( + [ + f" - name: {n}\n executable: jobs/{n}.py" + for n in [ + "copyd", + "equalreg", + "satufunc", + "bcprop", + "flow", + "data", + "metric", + "delete", + ] + ] + ) + + forward_block = ( + (" - scale\n - monotonic\n" if cfg.monotonic else "") + + "\n".join( + [ + " - copyd", + " - equalreg", + " - satufunc", + " - bcprop", + " - flow", + ( + " - data" + f" -t {cfg.times}\n" + f" -m {cfg.deck}/cellmap.npy" + ), + ( + " - metric" + f" -t {cfg.times}\n" + f" -e {cfg.experiment}\n" + f" -p {cfg.path}\n" + f" -s {cfg.msat}\n" + f" -c {cfg.mcon}" + ), + ] + ) + + ("\n - delete" if cfg.delete else "") + ) + + max_batch_block = ( + f"\n max_batch_num: {cfg.max_batch_num}" if cfg.max_batch_num else "" + ) + + rng_block = f" random_seed: {cfg.rng}" if cfg.rng else "" + + tmpl = Template(filename=f"{cfg.path}/templates/everest.mako") + with open(f"{cfg.fol}/everest.yml", "w", encoding="utf8") as f: + f.write( + tmpl.render( + variables_block=variables_block, + max_function_evaluations=cfg.max_function_evaluations, + min_realizations_success=cfg.min_realizations_success, + max_batch_block=max_batch_block, + options_block=options_block, + install_jobs_block=install_jobs_block, + cores=cfg.cores, + forward_block=forward_block, + rng_block=rng_block, + ) + )
+ +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/docs/_modules/pofff/visualization/benchmark.html b/docs/_modules/pofff/visualization/benchmark.html new file mode 100644 index 0000000..39cfd4d --- /dev/null +++ b/docs/_modules/pofff/visualization/benchmark.html @@ -0,0 +1,782 @@ + + + + + + + + + + pofff.visualization.benchmark — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for pofff.visualization.benchmark

+#!/usr/bin/env python
+# SPDX-FileCopyrightText: 2025-2026 NORCE Research AS
+# SPDX-License-Identifier: GPL-3.0
+
+"""Generate FluidFlower benchmark figures and comparisons.
+
+The module configures plotting and dispatches the maintained comparison scripts
+for time series, sparse values, segmented Wasserstein distances, and mean
+distance summaries."""
+
+import argparse
+import shutil
+import subprocess
+from pathlib import Path
+
+import matplotlib
+import matplotlib.pyplot as plt
+
+from pofff.utils.terminal import cli_correct_value, pofff_error, pofff_info
+
+
+
+[docs] +def run_benchmark(argv=None): + """Configure and generate benchmark comparison figures. + + Parameters + ---------- + argv : object, optional + Arguments to parse instead of the process command line.""" + cfg = _parse_arguments(argv) + + cfg["s"] = cfg["minimumsaturation"] + cfg["c"] = cfg["minimumconcentration"] + cfg["p"] = Path(cfg["path"]) / "fluidflower" + cfg["u"] = bool(int(cfg["use"])) + cfg["f"] = cfg["figures"].strip() + + matplotlib.rc("font", family="monospace", weight="normal", size=14) + + plt.rcParams.update( + { + "text.usetex": shutil.which("latex") is not None, + "legend.columnspacing": 0.9, + "legend.handlelength": 3.5, + "legend.fontsize": 14, + "lines.linewidth": 4, + "axes.titlesize": 14, + "axes.grid": False, + "figure.figsize": (16, 8), + } + ) + + _generate_benchmark_comparisons(cfg)
+ + + +
+[docs] +def run(cmd): + """Execute external command with logging. + + Parameters + ---------- + cmd : object + Command and arguments to execute without a shell.""" + pofff_info(f"running {' '.join(map(str, cmd))}") + subprocess.run(cmd, check=True)
+ + + +
+[docs] +def _generate_benchmark_comparisons(cfg): + """Run the maintained benchmark comparison scripts. + + Parameters + ---------- + cfg : object + Shared pofff configuration and derived runtime state.""" + + run( + [ + "python3", + cfg["p"] / "general/evaluation/compare_time_series_pofff.py", + "-p", + str(cfg["p"]) + "/", + "-l", + cfg["location"], + "-a", + cfg["add"], + ] + ) + + if Path("sparse_data.csv").exists(): + run( + [ + "python3", + cfg["p"] / "general/evaluation/compare_sparse_data_pofff.py", + "-p", + str(cfg["p"]) + "/", + "-l", + cfg["location"], + "-a", + cfg["add"], + ] + ) + + if cfg["f"] == "all": + if cfg["times"] != "24,48,72,96,120": + pofff_error( + "Wasserstein distance figures require " + f"{cli_correct_value('-t 24,48,72,96,120')}." + ) + + if float(cfg["s"]) == 0.01 and float(cfg["c"]) in (0.05, 0.1) and cfg["u"]: + run( + [ + "python3", + cfg["p"] + / "general/evaluation/calculate_segmented_emds_simplified_pofff.py", + "-p", + str(cfg["p"]) + "/", + "-satmin", + cfg["s"], + "-conmin", + cfg["c"], + "-l", + cfg["location"], + "-a", + cfg["add"], + ] + ) + else: + run( + [ + "python3", + cfg["p"] / "general/evaluation/calculate_segmented_emds_pofff.py", + "-p", + str(cfg["p"]) + "/", + "-satmin", + cfg["s"], + "-conmin", + cfg["c"], + "-l", + cfg["location"], + "-a", + cfg["add"], + ] + ) + + run( + [ + "python3", + cfg["p"] / "general/evaluation/means_from_segmented_distances_pofff.py", + "-satmin", + cfg["s"], + "-conmin", + cfg["c"], + "-a", + cfg["add"], + ] + )
+ + + +
+[docs] +def _parse_arguments(argv): + """Define and parse command-line arguments. + + Parameters + ---------- + argv : object + Arguments to parse instead of the process command line.""" + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description="Manage the generation of _generate_benchmark_comparisons figures.", + ) + + parser.add_argument("-c", "--minimumconcentration", default=0.1) + parser.add_argument("-s", "--minimumsaturation", default=0.01) + parser.add_argument("-p", "--path", default="./") + parser.add_argument("-t", "--times", default="24,48,72,96,120") + parser.add_argument("-f", "--figures", default="basic") + parser.add_argument("-l", "--location", default=".") + parser.add_argument("-a", "--add", default="1") + parser.add_argument( + "-u", + "--use", + default="1", + help="Use precomputed Wasserstein distances (1=yes, 0=no)", + ) + + return vars(parser.parse_args(argv))
+ + + +if __name__ == "__main__": + run_benchmark() +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/docs/_modules/pofff/visualization/error_table.html b/docs/_modules/pofff/visualization/error_table.html new file mode 100644 index 0000000..6d8d2ed --- /dev/null +++ b/docs/_modules/pofff/visualization/error_table.html @@ -0,0 +1,856 @@ + + + + + + + + + + pofff.visualization.error_table — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for pofff.visualization.error_table

+#!/usr/bin/env python3
+# Modified from
+# https://github.com/fluidflower/general/blob/main/evaluation/compare_sparse_data.py
+# pylint: disable=R0914,R0915,C0103
+
+"""Compare simulation and experimental FluidFlower benchmark metrics.
+
+The module loads sparse quantities and segmented Wasserstein distances, computes
+relative errors for participating groups, ranks the reported metrics, and writes
+a formatted comparison table for the selected segmentation thresholds."""
+
+import argparse
+import os
+
+import numpy as np
+
+from pofff.utils.terminal import pofff_info
+
+NUM_EXPERIMENTS = 5  # Number of experimental realizations
+NUM_MEASURABLES = 13  # Number of reported sparse quantities
+SCALING = 1e3  # Unit scaling for mass values
+DISTANCE_SCALE = 850.0  # Scaling factor for Wasserstein distances
+LANL_INDEX = 5  # Index of LANL (excluded in distance stats)
+
+
+
+[docs] +def _parse_arguments(argv): + """Parse command-line arguments controlling paths and thresholds. + + Parameters + ---------- + argv : object + Arguments to parse instead of the process command line.""" + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description="This script compares the data " + "as published in the pofff paper error tables.", + ) + parser.add_argument( + "-p", "--path", default="../..", help="Path to the third-party folder." + ) + parser.add_argument( + "-satmin", + "--minimumsaturation", + type=float, + default=1e-2, + help="Minimum saturation threshold for gaseous CO2 segmentation.", + ) + parser.add_argument( + "-conmin", + "--minimumconcentration", + type=float, + default=1e-1, + help="Minimum concentration threshold for dissolved CO2 segmentation.", + ) + parser.add_argument( + "-l", + "--location", + default=".", + help="Location of the simulation CSV files.", + ) + parser.add_argument( + "-a", "--add", default="1", help="Whether to add the local result to plots." + ) + + return vars(parser.parse_args(argv))
+ + + +
+[docs] +def _load_sparse_csv(filename): + """Load a sparse CSV file, automatically handling optional headers. + + Parameters + ---------- + filename : object + Path to the input file.""" + skip_header = 0 + with open(filename, "r", encoding="utf8") as f: + # Detect header line by first character + if not f.readline()[0].isnumeric(): + skip_header = 1 + + return np.genfromtxt( + filename, + delimiter=",", + skip_header=skip_header, + skip_footer=0, + )
+ + + +
+[docs] +def run_error_table(argv=None): + """Compute and write the benchmark error comparison table. + + Parameters + ---------- + argv : object, optional + Arguments to parse instead of the process command line.""" + cmdargs = _parse_arguments(argv) + + path = cmdargs["path"] + "/fluidflower/" + + fileNames = [ + f"{path}austin/sparse_data.csv", + f"{path}csiro/sparse_data.csv", + f"{path}delft/delft-DARSim/sparse_data.csv", + f"{path}delft/delft-DARTS/sparse_data.csv", + f"{path}heriot-watt/HWU-sparsedata-final.csv", + f"{path}lanl/sparse_data.csv", + f"{path}melbourne/sparse_data.csv", + f"{path}stanford/sparse_data.csv", + f"{path}stuttgart/sparse_data.csv", + f"{path}mit/sparse_data.csv", + cmdargs["location"] + "/sparse_data.csv", + ] + + groups = [ + "Austin", + "CSIRO", + "Delft-DARSim", + "Delft-DARTS", + "Heriot-Watt", + "LANL", + "Melbourne", + "Stanford", + "Stuttgart", + "MIT_M1", + "CSSR", + ] + + if cmdargs["add"] == "1": + where = os.path.abspath(".").split("/") + if where[-1] == "best_simulation" and where[-2] == "figures": + groups += [where[-3]] + else: + groups += [where[-1]] + fileNames += ["sparse_data.csv"] + + numGroups = len(groups) + numGroupsPlusExps = numGroups + NUM_EXPERIMENTS + + means = np.zeros((NUM_MEASURABLES, numGroups)) + stddevs = np.zeros((NUM_MEASURABLES, numGroups)) + + for i, fileName in zip(range(numGroups), fileNames): + pofff_info(f"processing {fileName}.") + data = _load_sparse_csv(fileName) + means[:, i] = data[:, 2] + stddevs[:, i] = data[:, 5] + + distances = np.loadtxt( + f"segmented_distances_satmin-{cmdargs['minimumsaturation']}_" + f"conmin-{cmdargs['minimumconcentration']}.csv", + delimiter=",", + ) + + distTable = [0.0] * numGroups + distExp = [0.0] * (NUM_EXPERIMENTS * NUM_EXPERIMENTS) + + for k in range(NUM_EXPERIMENTS): + A = ( + DISTANCE_SCALE + * distances[ + k * numGroupsPlusExps : (k + 1) * numGroupsPlusExps, + k * numGroupsPlusExps : (k + 1) * numGroupsPlusExps, + ] + ) + + A[LANL_INDEX, :] = np.nan + A[:, LANL_INDEX] = np.nan + + meanA_exp = np.mean(A[numGroups:, :], axis=0) + + for i in range(numGroups): + distTable[i] += meanA_exp[i] / NUM_EXPERIMENTS + + for i in range(NUM_EXPERIMENTS): + distExp[NUM_EXPERIMENTS * k + i] = meanA_exp[numGroups + i] + + expName = f"{path}experiment/benchmarkdata/sparse_data/sparse_data.csv" + expData = np.genfromtxt(expName, delimiter=",", skip_header=1) + + expTable = [ + [np.mean(expData[2][1:6]), np.std(expData[2][1:6])], + [SCALING * np.mean(expData[3][1:6]), SCALING * np.std(expData[3][1:6])], + [SCALING * np.mean(expData[5][1:6]), SCALING * np.std(expData[5][1:6])], + [SCALING * np.mean(expData[6][1:6]), SCALING * np.std(expData[6][1:6])], + [SCALING * np.mean(expData[9][1:6]), SCALING * np.std(expData[9][1:6])], + [SCALING * np.mean(expData[13][1:6]), SCALING * np.std(expData[13][1:6])], + ] + + text = "" + text += "Parameter\t, 1 (s)\t\t, 2a (g), 2c (g), 2d (g), 3c (g), 5 (g) , " + text += "error, WD (g cm), Metric\n" + text += "Group \t, mean \t \t, mean \t, mean \t, mean \t, mean \t, " + text += "mean \t, mean, mean, sum\n" + text += ( + f"Experiment\t, {expTable[0][0]:.2E}\t, {expTable[1][0]:.2f}\t, " + f"{expTable[2][0]:.2f}\t, {expTable[3][0]:.2f}\t, {expTable[4][0]:.2f}\t, " + f"{expTable[5][0]:.2f}\t, nan, {np.mean(distExp):.2f}, nan\n" + ) + + error, w_d, metric = [], [], [] + + for i, name in enumerate(groups): + tab = "" + if len(name) < 12: + tab += "\t" + if len(name) < 7: + tab += "\t" + + errors = [ + 100.0 * abs(means[2, i] - expTable[0][0]) / expTable[0][0], + 100.0 * abs(SCALING * means[3, i] - expTable[1][0]) / expTable[1][0], + 100.0 * abs(SCALING * means[5, i] - expTable[2][0]) / expTable[2][0], + 100.0 * abs(SCALING * means[6, i] - expTable[3][0]) / expTable[3][0], + 100.0 * abs(SCALING * means[9, i] - expTable[4][0]) / expTable[4][0], + 100.0 * abs(SCALING * means[12, i] - expTable[5][0]) / expTable[5][0], + ] + + err = ( + f"{np.mean(errors):.0E}" + if name.upper() == "LANL" + else f"{np.mean(errors):.2f}" + ) + + was = f"{distTable[i]:.2f}" + tot = f"{np.mean(errors) + distTable[i]:.2f}" + + text += ( + f"{name.upper()}{tab}, {means[2,i]:.2E}\t, {SCALING*means[3,i]:.2f}\t, " + f"{SCALING*means[5,i]:.2f}\t, {SCALING*means[6,i]:.2f}\t, " + f"{SCALING*means[9,i]:.2f}\t, {SCALING*means[12,i]:.2f} , " + f"{' '*(6-len(err))}{err}, " + f"{' '*(9-len(was))}{was}, " + f"{' '*(6-len(tot))}{tot}\n" + ) + + error.append(np.mean(errors)) + w_d.append(distTable[i]) + metric.append(np.mean(errors) + distTable[i]) + + text += "\nLower to larger error:\n" + text += ", ".join(groups[i].upper() for i in np.argsort(error)) + "\n" + + text += "Lower to larger Wasserstein distance:\n" + text += ", ".join(groups[i].upper() for i in np.argsort(w_d)) + "\n" + + text += "Lower to larger sum of both quantities:\n" + text += ", ".join(groups[i].upper() for i in np.argsort(metric)) + "\n\n" + + text += ( + "Thresholds for the segmentation of simulation results: " + f"satmin = {cmdargs['minimumsaturation']} and " + f"conmin = {cmdargs['minimumconcentration']}\n" + ) + + with open( + f"error_table_satmin-{cmdargs['minimumsaturation']}_" + f"conmin-{cmdargs['minimumconcentration']}.csv", + "w", + encoding="utf8", + ) as file: + file.write(text)
+ + + +if __name__ == "__main__": + run_error_table() +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/docs/_modules/pofff/visualization/everert.html b/docs/_modules/pofff/visualization/everert.html new file mode 100644 index 0000000..b39a21f --- /dev/null +++ b/docs/_modules/pofff/visualization/everert.html @@ -0,0 +1,1555 @@ + + + + + + + + + + pofff.visualization.everert — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for pofff.visualization.everert

+#!/usr/bin/env python
+# SPDX-FileCopyrightText: 2025-2026 NORCE Research AS
+# SPDX-License-Identifier: GPL-3.0
+
+"""Postprocess ERT ensembles and Everest optimization studies.
+
+The module reads realizations and optimization batches, computes diagnostics,
+plots simulation and parameter distributions, reports failures and
+monotonicity rejections, and reconstructs the best simulation for benchmark
+postprocessing."""
+
+import argparse
+import csv
+import math
+import os
+import shutil
+import subprocess
+from dataclasses import dataclass, field
+from pathlib import Path
+
+import matplotlib
+import matplotlib.pyplot as plt
+import numpy as np
+from matplotlib.ticker import MaxNLocator
+
+from pofff.utils.terminal import pofff_info
+
+
+
+[docs] +@dataclass(slots=True) +class Config: + """Store options and paths for ERT and Everest postprocessing. + + The object is populated from command-line arguments and shared by ensemble + diagnostics, optimization processing, best-simulation extraction, and + benchmark-data generation. + + Attributes + ---------- + path + Root directory containing the ERT or Everest results and generated + figures. + times + Comma-separated benchmark evaluation times in hours. + jobs + Generated job scripts to execute when reconstructing the best + simulation. + external + Root directory containing the external FluidFlower benchmark data. + run_command + Experimental realization identifier, such as ``run2``. + maps + Path to the NumPy cell map used to generate benchmark spatial data. + min_saturation + Minimum gas-saturation threshold used to segment simulated CO2. + min_concentration + Minimum dissolved-CO2 concentration threshold used to segment simulated + CO2. + """ + + path: Path + times: str + jobs: list[str] + external: Path + run_command: str + maps: Path + min_saturation: float + min_concentration: float
+ + + +
+[docs] +@dataclass(slots=True) +class EnsembleState: + """Store ERT ensemble results and derived diagnostics. + + The object is initialized from the ERT output structure. Successful + realizations then populate its simulation values, misfits, parameter + distributions, realization identifiers, and per-observation diagnostics + for each iteration. + + Attributes + ---------- + observations + Observation values and uncertainties loaded from ``deck/obs.txt``. + A one-dimensional array represents one observation; otherwise, rows + contain the observed value and its uncertainty. + n_e + Number of ensemble realizations found in the simulation output. + n_i + Number of ERT iterations represented in the simulation output. + no_obs + Number of observations used to calculate the ensemble misfit. + no_para + Number of history-matching parameters found in ``parameters.txt``. + simulations + Simulated observable values grouped by iteration and realization. + sim_ens + Sum of the simulated observable values for each successful realization, + grouped by iteration. + miss_ens + Normalized least-squares misfit for each successful realization, + grouped by iteration. + par_dis + History-matching parameter values grouped by iteration. Values for all + parameters and realizations are stored in file order. + idrealisation + Original realization identifiers for successful simulations, grouped + by iteration. + num_ens + Number of successful realizations in each iteration. + cumulative + Simulated values grouped by iteration, observation, and successful + realization for cumulative-misfit plots. + para_file + Path to the most recently discovered ``parameters.txt`` file, or + ``None`` when parameter values are unavailable. + para_names + History-matching parameter names read from ``para_file``. + """ + + observations: np.ndarray + n_e: int + n_i: int + no_obs: int + no_para: int + simulations: list[list[list]] = field(default_factory=list) + sim_ens: list[list[float]] = field(default_factory=list) + miss_ens: list[list[float]] = field(default_factory=list) + par_dis: list[list[float]] = field(default_factory=list) + idrealisation: list[list[int]] = field(default_factory=list) + num_ens: list[int] = field(default_factory=list) + cumulative: list[list[list[float]]] = field(default_factory=list) + para_file: Path | None = None + para_names: list[str] = field(default_factory=list)
+ + + +
+[docs] +@dataclass(slots=True) +class OptimizationState: + """Store Everest optimization progress and the best evaluation. + + The object is populated while processing optimization batches. It tracks + the best objective value reached after each batch, counts successful and + unsuccessful evaluations, and identifies the evaluation from which the + optimal simulation should be reconstructed. + + Attributes + ---------- + optimization + Best objective value reached after each optimization batch. + optimal_value + Best objective value found across all processed batches. + ind_batch + Zero-based index of the batch containing the best evaluation. + ind_sim + Zero-based evaluation index of the best solution within ``ind_batch``. + tot_eval + Total number of processed optimization evaluations. + s + Evaluation counts grouped by status and batch. Rows contain successful, + failed, and nonmonotonic evaluation counts, respectively. + x + Temporary counters for successful, failed, and nonmonotonic evaluations + in the batch currently being processed. + """ + + optimization: list[float] = field(default_factory=list) + optimal_value: float = -np.inf + ind_batch: int = 0 + ind_sim: int = 0 + tot_eval: int = 0 + s: list[list[int]] = field(default_factory=lambda: [[], [], []]) + x: list[int] = field(default_factory=lambda: [0, 0, 0])
+ + + +
+[docs] +def _run_command(cmd: list[str]) -> None: + """Execute external command and abort on failure. + + Parameters + ---------- + cmd : list[str] + Command and arguments to execute without a shell. + + Raises + ------ + subprocess.CalledProcessError + If the command exits with a nonzero status.""" + subprocess.run(cmd, check=True)
+ + + +
+[docs] +def _ensure_directory(path: Path): + """Create directory if missing. + + Parameters + ---------- + path : Path + Input, output, or project path.""" + path.mkdir(parents=True, exist_ok=True)
+ + + +
+[docs] +def _save_figure(fig, path: Path, dpi=300): + """Save figure and release memory. + + Parameters + ---------- + fig : object + Matplotlib figure to save and close. + path : Path + Input, output, or project path. + dpi : object, optional + Output resolution in dots per inch.""" + fig.savefig(path, bbox_inches="tight", dpi=dpi) + plt.close(fig)
+ + + +
+[docs] +def _copy_tree_contents(src: Path, dst: Path): + """Copy contents of src into dst (cp -r src/. dst/). + + Parameters + ---------- + src : Path + Source file or directory. + dst : Path + Destination file or directory.""" + _ensure_directory(dst) + for item in src.iterdir(): + target = dst / item.name + if item.is_dir(): + shutil.copytree(item, target, dirs_exist_ok=True) + else: + shutil.copy2(item, target)
+ + + +
+[docs] +def _parse_arguments(argv) -> Config: + """Parse command-line arguments. + + Parameters + ---------- + argv : object + Arguments to parse instead of the process command line. + + Returns + ------- + Config + Parsed command-line arguments or the corresponding runtime configuration.""" + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description="Visualization of optimization studies using Everest (ERT)", + ) + parser.add_argument("-p", "--path", default=".") + parser.add_argument("-t", "--times", default="24") + parser.add_argument("-j", "--jobs", default="equalreg,bcprop,satufunc,flow") + parser.add_argument("-e", "--external", default="/home/ThirdParty") + parser.add_argument("-r", "--run_command", default="run2") + parser.add_argument("-s", "--minimumsaturation", default="1e-2") + parser.add_argument("-c", "--minimumconcentration", default="1e-1") + parser.add_argument("-m", "--maps", default="cellmap.npy") + + a = vars(parser.parse_args(argv)) + + return Config( + path=Path(a["path"]).resolve(), + times=a["times"], + jobs=[j.strip() for j in a["jobs"].split(",")], + external=Path(a["external"]), + run_command=a["run_command"], + maps=Path(a["maps"]), + min_saturation=float(a["minimumsaturation"]), + min_concentration=float(a["minimumconcentration"]), + )
+ + + +
+[docs] +def _configure_matplotlib(): + """Apply consistent matplotlib styling.""" + matplotlib.rc("font", family="monospace", size=14) + plt.rcParams.update( + { + "text.usetex": shutil.which("latex") is not None, + "axes.grid": True, + "figure.figsize": (16, 8), + } + ) + return matplotlib.colormaps["tab20"]
+ + + +
+[docs] +def _initialize_ensemble(cfg: Config) -> EnsembleState: + """Initialize ensemble from simulation folders. + + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + + Returns + ------- + EnsembleState + Initialized ensemble state sized from the result directories.""" + obs = np.genfromtxt(cfg.path / "deck/obs.txt") + no_obs = 1 if obs.ndim == 1 else len(obs) + + sim_root = cfg.path / "output/simulations" + n_e = len(list(sim_root.iterdir())) + n_i = max(len(list((sim_root / f"realisation-{i}").iterdir())) for i in range(n_e)) + + return EnsembleState( + observations=obs, + n_e=n_e, + n_i=n_i, + no_obs=no_obs, + no_para=0, + simulations=[[] for _ in range(n_i)], + sim_ens=[[] for _ in range(n_i)], + miss_ens=[[] for _ in range(n_i)], + par_dis=[[] for _ in range(n_i)], + idrealisation=[[] for _ in range(n_i)], + num_ens=[0 for _ in range(n_i)], + cumulative=[[[] for _ in range(no_obs)] for _ in range(n_i)], + )
+ + + +
+[docs] +def _read_realisation(cfg: Config, state: EnsembleState, i: int, j: int): + """Read one realization and update ensemble statistics. + + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + state : EnsembleState + Mutable ensemble state populated during postprocessing. + i : int + Iteration or row index. + j : int + Realization or column index.""" + base = cfg.path / f"output/simulations/realisation-{j}/iter-{i}" + if not (base / "OK").exists(): + return + + state.num_ens[i] += 1 + state.idrealisation[i].append(j) + + sim = np.atleast_1d(np.genfromtxt(base / "sim_metrics_0.txt")).astype(float) + state.simulations[i].append(sim.tolist()) + + obs = state.observations + + if obs.ndim == 1: + miss = ((sim[0] - obs[0]) / obs[1]) ** 2 + state.cumulative[i][0].append(sim[0]) + sim_sum = sim[0] + n = 1 + else: + miss = np.sum(((sim - obs[:, 0]) / obs[:, 1]) ** 2) + for k, v in enumerate(sim): + state.cumulative[i][k].append(v) + sim_sum = np.sum(sim) + n = len(sim) + + state.miss_ens[i].append(miss / (2 * n)) + state.sim_ens[i].append(sim_sum) + + para = base / "parameters.txt" + if para.exists(): + state.para_file = para + data = np.atleast_2d(np.genfromtxt(para)) + state.no_para = data.shape[0] + for row in data: + state.par_dis[i].append(row[1])
+ + + +
+[docs] +def _plot_simulation_ensemble(cfg: Config, state: EnsembleState, tab20): + """Plot initial and final ensemble simulations. + + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + state : EnsembleState + Mutable ensemble state populated during postprocessing. + tab20 : object + Matplotlib categorical colormap used for consistent series colors.""" + fig, ax = plt.subplots() + + x = range(1, state.no_obs + 1) + marker = "o" if state.observations.ndim == 1 else "" + + for vals in state.simulations[0][:-1]: + ax.plot(x, vals, color=tab20.colors[0], lw=0.5, marker=marker) + + ax.plot( + x, + state.simulations[0][-1], + color=tab20.colors[0], + lw=0.5, + marker=marker, + label="Initial ensemble", + ) + + if state.n_i > 1: + for vals in state.simulations[-1][:-1]: + ax.plot(x, vals, color=tab20.colors[2], lw=0.5, marker=marker) + + ax.plot( + x, + state.simulations[-1][-1], + color=tab20.colors[2], + lw=0.5, + marker=marker, + label="Final ensemble", + ) + + obs = state.observations + if obs.ndim == 1: + ax.errorbar(1, obs[0], yerr=obs[1], fmt="o", color="gray", label="Observation") + else: + for i, (v, e) in enumerate(obs[:-1]): + ax.errorbar(i + 1, v, yerr=e, fmt="o", color="gray") + ax.errorbar( + state.no_obs, + obs[-1][0], + yerr=obs[-1][1], + fmt="o", + color="gray", + label="Observation", + ) + + ax.set_xlabel(r"Observable [\#]") + ax.set_ylabel("Wasserstein distance [gr cm]") + ax.set_ylim(bottom=0) + ax.set_xticks(x) + ax.legend() + + _save_figure(fig, cfg.path / "figures/simulationensemble.png")
+ + + +
+[docs] +def _plot_misfit(cfg: Config, state: EnsembleState): + """Plot ensemble misfit per iteration. + + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + state : EnsembleState + Mutable ensemble state populated during postprocessing.""" + fig, ax = plt.subplots() + ax.boxplot(state.miss_ens) + ax.set_xlabel(r"Iteration [\#]") + ax.set_ylabel("Misfit [-]") + ax.set_title( + r"$O_{i,j}=\frac{1}{2N_{obs}}\sum_n^{N_{obs}}((d^{n}_{i,j}-d^{n})/\sigma_n)^2$" + ) + _save_figure(fig, cfg.path / "figures/dist_mismatch.png")
+ + + +
+[docs] +def _plot_parameter_distributions(cfg: Config, state: EnsembleState): + """Boxplots of parameter distributions. + + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + state : EnsembleState + Mutable ensemble state populated during postprocessing.""" + if not state.para_file: + return + + with open(state.para_file, encoding="utf8") as f: + state.para_names = [row[0] for row in csv.reader(f, delimiter=" ")] + + n_params = len(state.para_names) + rows = math.ceil(n_params / 3) + fig = plt.figure(figsize=(25, n_params)) + + for k in range(n_params): + ax = fig.add_subplot(rows, 3, k + 1) + ini_dist = state.par_dis[0][k::n_params] + + if state.n_i > 1: + fin_dist = state.par_dis[-1][k::n_params] + ax.boxplot([ini_dist, fin_dist], tick_labels=["Initial", "Final"]) + ax.set_title( + f"Box plot of initial and final {state.para_names[k].lower()} parameter" + ) + else: + ax.boxplot([ini_dist], tick_labels=["Initial"]) + ax.set_title(f"Box plot of initial {state.para_names[k]} parameter") + + _save_figure(fig, cfg.path / "figures/parameterdistributions.png")
+ + + +
+[docs] +def _extract_best_simulation(cfg: Config, state: EnsembleState): + """Extract best-fitting ensemble realization. + + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + state : EnsembleState + Mutable ensemble state populated during postprocessing.""" + sims = np.array(state.simulations[-1]) + obs = state.observations + + if obs.ndim == 1: + diff = np.abs(sims[:, 0] - obs[0]) + else: + diff = np.sum(np.abs(sims - obs[:, 0]), axis=1) + + idx = int(np.argmin(diff)) + best_id = state.idrealisation[-1][idx] + + src = cfg.path / f"output/simulations/realisation-{best_id}/iter-{state.n_i - 1}" + dst = cfg.path / "figures/best_simulation" + _copy_tree_contents(src, dst) + + old_cwd = Path.cwd() + os.chdir(dst) + try: + _run_command(["python3", str(cfg.path / "jobs/copyd.py")]) + for job in cfg.jobs: + _run_command(["python3", str(cfg.path / f"jobs/{job}.py")]) + _run_command( + [ + "python3", + str(cfg.path / "jobs/data.py"), + "-t", + cfg.times, + "-m", + str(cfg.maps), + ] + ) + _run_command( + [ + "python3", + str(cfg.path / "jobs/metric.py"), + "-t", + cfg.times, + "-e", + cfg.run_command, + "-p", + str(cfg.external), + "-s", + str(cfg.min_saturation), + "-c", + str(cfg.min_concentration), + ] + ) + finally: + os.chdir(old_cwd) + + pofff_info( + f"best: {cfg.path}/output/simulations/" + f"realisation-{best_id}/iter-{state.n_i - 1}" + ) + + best_vals = state.simulations[-1][idx] + pofff_info(f"values: {list(best_vals) if state.n_i > 1 else best_vals}")
+ + + +
+[docs] +def _process_optimization(cfg: Config) -> OptimizationState: + """Process Everest optimization results. + + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + + Returns + ------- + OptimizationState + Optimization history and location of the best evaluation.""" + root = cfg.path / "everest_output/sim_output" + opt = OptimizationState() + + improved = -np.inf + where = [] + + batches = sorted(root.iterdir(), key=lambda p: int(p.name.split("_")[1])) + + if (root / "batch_0/realization-0").is_dir(): + realization = "realization-0" + else: + realization = "realization_0" + + for batch in batches: + batch_index = int(batch.name.split("_")[1]) + evals = sorted( + (batch / realization).iterdir(), + key=lambda p: int(p.name.split("_")[1]), + ) + + for ev in evals: + fval = np.genfromtxt(ev / "func") + opt.tot_eval += 1 + + if np.isnan(fval): + opt.x[1] += 1 + elif fval == -1: + opt.x[2] += 1 + else: + opt.x[0] += 1 + if fval > improved: + improved = float(fval) + where.append((batch_index, int(ev.name.split("_")[1]))) + + opt.optimization.append(improved) + + for i in range(3): + opt.s[i].append(opt.x[i]) + opt.x[i] = 0 + + idx = int(np.nanargmax(opt.optimization)) + opt.optimal_value = opt.optimization[idx] + opt.ind_batch, opt.ind_sim = where[idx] + + return opt
+ + + +
+[docs] +def _plot_optimization(cfg: Config, opt: OptimizationState): + """Plot optimization progress. + + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + opt : OptimizationState + Processed Everest optimization state.""" + fig, ax = plt.subplots() + + scale = 8.5 * 100 + values = [-v * scale for v in opt.optimization] + steps = range(1, len(values) + 1) + + if len(values) > 1: + ax.step(steps, values, lw=5, color="b") + else: + ax.plot(1, values[0], marker="*", ms=20, color="k") + + ax.set_title("Optimization results") + ax.set_xlabel(r"Iteration [\#]") + ax.set_ylabel("Wasserstein distance [gr cm]") + + if len(values) < 20: + ax.xaxis.set_major_locator(MaxNLocator(integer=True)) + else: + ax.set_xticks(np.linspace(0, len(values) + 1, 11)) + + _save_figure(fig, cfg.path / "figures/optimization_results.png")
+ + + +
+[docs] +def _extract_optimal_solution(cfg: Config, opt: OptimizationState): + """Extract and postprocess optimal optimization result. + + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + opt : OptimizationState + Processed Everest optimization state.""" + dst = cfg.path / "figures/best_simulation" + _ensure_directory(dst) + + if (cfg.path / "everest_output/sim_output/batch_0/realization-0").is_dir(): + realization = "realization-0" + else: + realization = "realization_0" + + src = ( + cfg.path + / "everest_output/sim_output" + / f"batch_{opt.ind_batch}" + / realization + / f"evaluation_{opt.ind_sim}" + / "para.json" + ) + + shutil.copy2(src, dst / "para.json") + + old_cwd = Path.cwd() + os.chdir(dst) + try: + _run_command(["python3", str(cfg.path / "jobs/copyd.py")]) + for job in cfg.jobs: + _run_command(["python3", str(cfg.path / f"jobs/{job}.py")]) + _run_command( + [ + "python3", + str(cfg.path / "jobs/data.py"), + "-t", + cfg.times, + "-m", + str(cfg.maps), + ] + ) + _run_command( + [ + "python3", + str(cfg.path / "jobs/metric.py"), + "-t", + cfg.times, + "-e", + cfg.run_command, + "-p", + str(cfg.external), + "-s", + str(cfg.min_saturation), + "-c", + str(cfg.min_concentration), + ] + ) + _run_command(["python3", str(cfg.path / "jobs/scale.py")]) + finally: + os.chdir(old_cwd) + + if (cfg.path / "everest_output/sim_output/batch_0/realization-0").is_dir(): + realization = "realization-0" + else: + realization = "realization_0" + + pofff_info( + f"best: {cfg.path}/everest_output/sim_output/" + f"batch_{opt.ind_batch}/{realization}/" + f"evaluation_{opt.ind_sim}" + )
+ + + +
+[docs] +def _plot_observable_distribution(cfg: Config, state: EnsembleState): + """Plot observable sum distributions. + + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + state : EnsembleState + Mutable ensemble state populated during postprocessing.""" + fig, ax = plt.subplots() + ax.boxplot( + [state.sim_ens[i] for i in range(state.n_i)], + positions=list(range(state.n_i)), + ) + ax.set_xlabel(r"Iteration [\#]") + ax.set_ylabel("Wasserstein distance [gr cm]") + ax.set_title(r"$\sum_n^{N_{obs}} d^n_{i,j}$") + ax.set_xticks(range(state.n_i)) + + _save_figure(fig, cfg.path / "figures/dist_observable.png")
+ + + +
+[docs] +def _plot_history_matching_mismatch(cfg: Config, state: EnsembleState): + """Plot ensemble-mean misfit per iteration. + + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + state : EnsembleState + Mutable ensemble state populated during postprocessing.""" + fig, ax = plt.subplots() + + for i in range(state.n_i): + if not state.miss_ens[i]: + continue + mean_misfit = np.sum(state.miss_ens[i]) / len(state.miss_ens[i]) + ax.plot(i, mean_misfit, marker="o", label=rf"$N_{{ens}}={state.num_ens[i]}$") + + ax.set_title( + r"$O_i=\frac{1}{N_{ens}}\sum_{j=1}^{N_{ens}} O_{i,j}$, \#" + + f"HM parameters: {state.no_para}" + ) + ax.set_xlabel(r"Iteration [\#]") + ax.set_ylabel("Misfit [-]") + ax.set_xticks(range(state.n_i)) + ax.legend() + + _save_figure(fig, cfg.path / "figures/hm_mismatch.png")
+ + + +
+[docs] +def _plot_cumulative_misfit(cfg: Config, state: EnsembleState, tab20): + """Plot cumulative misfit contributions per observation. + + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + state : EnsembleState + Mutable ensemble state populated during postprocessing. + tab20 : object + Matplotlib categorical colormap used for consistent series colors.""" + for i in range(state.n_i): + if not state.cumulative[i] or not state.cumulative[i][0]: + continue + + fig, ax = plt.subplots() + + allw = np.zeros(len(state.cumulative[i][0])) + for j in range(state.no_obs): + allw += np.array(state.cumulative[i][j]) + + indc = np.argsort(allw) + allw_sorted = np.sort(allw) + + ax.axhline( + y=allw_sorted.mean() / state.no_obs, + color="black", + ls="--", + lw=1, + label=f"Total average {allw_sorted.mean() / state.no_obs:.2f}", + ) + + ax.bar(range(1, len(allw_sorted) + 1), allw_sorted, color=tab20.colors[0]) + + remainder = allw_sorted.copy() + for j in range(state.no_obs - 1): + vals = np.array([state.cumulative[i][j][r] for r in indc]) + remainder -= vals + ax.bar( + range(1, len(remainder) + 1), + remainder, + color=tab20.colors[j + 1], + label=f"obs-{j+1}", + ) + + ax.set_title(f"Realization (iter-{i})") + ax.set_xlabel(r"Realisation [\#]") + ax.set_ylabel("Cumulative misfit [-]") + ax.legend() + + _save_figure(fig, cfg.path / f"figures/cumulative_misfit_ite-{i}.png")
+ + + +
+[docs] +def _plot_optimization_details(cfg: Config, opt: OptimizationState): + """Plot optimization success statistics. + + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + opt : OptimizationState + Processed Everest optimization state.""" + colors = ["g", "r", "k"] + names = [ + f"Succeeded (no={sum(opt.s[0])})", + f"Failed (no={sum(opt.s[1])})", + f"Nonmonotonic (no={sum(opt.s[2])})", + ] + + fig, ax = plt.subplots() + + n_steps = len(opt.s[0]) + x = np.arange(1, n_steps + 1) + + total = np.array(opt.s[0]) + np.array(opt.s[1]) + np.array(opt.s[2]) + ax.bar(x, total, color=colors[0], label=names[0]) + + remainder = total.copy() + for i in range(2): + remainder -= np.array(opt.s[i]) + if i == 0 and sum(opt.s[1]) == 0: + continue + ax.bar(x, remainder, color=colors[i + 1], label=names[i + 1]) + + ax.set_title(f"Details on failed and succeeded simulations (Total={opt.tot_eval})") + ax.set_xlabel(r"Iteration [\#]") + ax.set_ylabel(r"Occurrence [\#]") + + if n_steps < 20: + ax.xaxis.set_major_locator(MaxNLocator(integer=True)) + else: + ax.set_xticks(np.linspace(1, n_steps, 11)) + ax.yaxis.set_major_locator(MaxNLocator(integer=True)) + + ax.legend() + _save_figure(fig, cfg.path / "figures/details.png", dpi=900)
+ + + +
+[docs] +def run_everert(argv=None): + """Generate diagnostics for an ERT ensemble or Everest optimization. + + Parameters + ---------- + argv : object, optional + Arguments to parse instead of the process command line.""" + cfg = _parse_arguments(argv) + tab20 = _configure_matplotlib() + + _ensure_directory(cfg.path / "figures") + + if (cfg.path / "everest_output").exists(): + opt = _process_optimization(cfg) + _plot_optimization(cfg, opt) + _plot_optimization_details(cfg, opt) + _extract_optimal_solution(cfg, opt) + else: + state = _initialize_ensemble(cfg) + for j in range(state.n_e): + for i in range(state.n_i): + _read_realisation(cfg, state, i, j) + + _plot_simulation_ensemble(cfg, state, tab20) + _plot_history_matching_mismatch(cfg, state) + _plot_misfit(cfg, state) + _plot_observable_distribution(cfg, state) + _plot_parameter_distributions(cfg, state) + _plot_cumulative_misfit(cfg, state, tab20) + _extract_best_simulation(cfg, state)
+ + + +if __name__ == "__main__": + run_everert() +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/docs/_modules/pofff/visualization/maps.html b/docs/_modules/pofff/visualization/maps.html new file mode 100644 index 0000000..561abac --- /dev/null +++ b/docs/_modules/pofff/visualization/maps.html @@ -0,0 +1,809 @@ + + + + + + + + + + pofff.visualization.maps — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for pofff.visualization.maps

+#!/usr/bin/env python3
+# Modified from
+# https://github.com/fluidflower/general/blob/main/visualization/generate_segmented_images.py
+# pylint: disable=R0913, R0914, R0917
+
+"""Create spatial benchmark overlays from simulation and experiment data.
+
+Simulation saturation and dissolved-CO2 fields are segmented on the FluidFlower
+reporting grid, combined with experimental contour boundaries and geological
+lines, and written as maps for the requested evaluation times."""
+
+import argparse
+import csv
+import shutil
+
+import matplotlib
+import matplotlib.pyplot as plt
+import numpy as np
+from matplotlib import colors
+
+DX = 1.0e-2  # Grid spacing [m]
+DOMAIN_X = 2.8  # Domain length in x-direction [m]
+DOMAIN_Z = 1.2  # Domain height in z-direction [m]
+NX_TARGET = 280  # Target grid size in x
+NZ_TARGET = 120  # Target grid size in z
+
+
+
+[docs] +def _write_overlay_map(simulations, experiment, x, z, points, lines, t): + """Write one simulation and experiment overlay map. + + The figure is written as ``map_<time>h.png`` in the current directory. + + Parameters + ---------- + simulations : object + Segmented simulation classes on the benchmark reporting grid. + experiment : object + Segmented experimental classes on the benchmark reporting grid. + x : object + Cell or plotting x coordinates in metres. + z : object + Cell or plotting z coordinates in metres. + points : object + Geological points represented by x and z coordinates in metres. + lines : object + Pairs of point indices defining geological line segments. + t : object + Evaluation time label in hours.""" + cmap = colors.ListedColormap( + ["#ffffff", "#f8a98c", "#faf7a1", "#df3a0c", "#B1B106"] + ) + + nz, nx = experiment.shape + + for i in range(1, nx - 1): + for j in range(1, nz - 1): + if experiment[j, i] == 1 and any( + experiment[j + dj, i + di] == 0 + for dj, di in [(-1, 0), (1, 0), (0, -1), (0, 1)] + ): + simulations[j, i] = 3 + elif experiment[j, i] == 2 and any( + experiment[j + dj, i + di] < 2 + for dj, di in [(-1, 0), (1, 0), (0, -1), (0, 1)] + ): + simulations[j, i] = 4 + + plt.pcolormesh(x, z, np.flip(simulations, axis=0), cmap=cmap) + + for line in lines: + plt.plot( + [points[line[0]][0], points[line[1]][0]], + [points[line[0]][1], points[line[1]][1]], + "k", + lw=0.25, + ) + + plt.ylim([0, DOMAIN_Z]) + plt.xticks([]) + plt.yticks([]) + plt.savefig(f"map_{t}h.png") + plt.clf()
+ + + +
+[docs] +def _segment_simulation_fields(filename, satmin, conmin): + """Segment saturation and concentration on the reporting grid. + + Parameters + ---------- + filename : object + Path to the input file. + satmin : object + Minimum gas-saturation threshold. + conmin : object + Minimum dissolved-CO2 concentration threshold.""" + xspace = np.arange(0, DOMAIN_X + 0.5 * DX, DX) + zspace = np.arange(0, DOMAIN_Z + 0.5 * DX, DX) + x, z = np.meshgrid(xspace, zspace) + + nx = xspace.size - 1 + nz = zspace.size - 1 + + with open(filename, encoding="utf8") as f: + skip_header = 0 if f.readline()[0].isnumeric() else 1 + + values = np.genfromtxt(filename, delimiter=",", skip_header=skip_header) + + saturation = values[:, 2].reshape(nz, nx) + concentration = values[:, 3].reshape(nz, nx) + + segmented = np.zeros((NZ_TARGET, NX_TARGET), dtype=int) + + offset = 3 if nx == 286 else 0 + + i_src = np.arange(offset, nz) + j_src = np.arange(offset, nx - offset) + + ii, jj = np.meshgrid(i_src, j_src, indexing="ij") + + zi = NZ_TARGET - 1 + offset - ii + xi = jj - offset + + gas_mask = saturation[ii, jj] > satmin + dissolved_mask = (concentration[ii, jj] > conmin) & (~gas_mask) + + segmented[zi[gas_mask], xi[gas_mask]] = 2 + segmented[zi[dissolved_mask], xi[dissolved_mask]] = 1 + + return segmented, x, z
+ + + +
+[docs] +def _load_geological_lines(path): + """Load geological points and line connectivity for plotting. + + Parameters + ---------- + path : object + Input, output, or project path.""" + points = [] + lines = [] + + h_ref = 1.5 / 1490 + l_ref = 2.8 / 2594 + + with open(f"{path}/geology/points.geo", encoding="utf8") as file: + for row in csv.reader(file, delimiter=" "): + if row and row[0].startswith("Point"): + points.append( + [ + l_ref * float(row[2][1:-1]), + h_ref * float(row[3][:-1]), + ] + ) + + with open(f"{path}/geology/lines.geo", encoding="utf8") as file: + for row in csv.reader(file, delimiter=" "): + if not row or row[0] == "//": + continue + if row[0].startswith("Line"): + lines.append([int(row[2][1:-1]) - 1, int(row[3][:-2]) - 1]) + + return points, lines
+ + + +
+[docs] +def run_maps(argv=None): + """Generate spatial overlay maps for the requested times. + + Parameters + ---------- + argv : object, optional + Arguments to parse instead of the process command line.""" + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description="Generate spatial overlay maps for selected times.", + ) + parser.add_argument("-e", "--experiment", default="run2") + parser.add_argument("-satmin", "--minimumsaturation", type=float, default=1e-2) + parser.add_argument("-conmin", "--minimumconcentration", type=float, default=1e-1) + parser.add_argument("-t", "--times", default="24,48,72,96,120") + parser.add_argument("-l", "--location", default=".") + parser.add_argument("-p", "--path", default=".") + args = vars(parser.parse_args(argv)) + + matplotlib.rc("font", family="normal", weight="normal", size=12) + plt.rcParams.update( + { + "text.usetex": shutil.which("latex") is not None, + "font.family": "monospace", + "figure.figsize": (10, 5), + } + ) + + points, lines = _load_geological_lines(args["path"]) + + for t in args["times"].split(","): + simulations, x, z = _segment_simulation_fields( + f"{args["location"]}/spatial_map_{t}h.csv", + args["minimumsaturation"], + args["minimumconcentration"], + ) + + name = str(int(float(t) * 3600)).zfill(6) + + experiment = np.loadtxt( + f"{args["path"]}/fluidflower/experiment/benchmarkdata/spatial_maps/" + f"{args["experiment"]}/segmentation_{name}s.csv", + dtype=int, + delimiter=",", + )[30:, :] + + _write_overlay_map(simulations, experiment, x, z, points, lines, t)
+ + + +if __name__ == "__main__": + run_maps() +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/docs/_modules/pofff/visualization/sparse_values.html b/docs/_modules/pofff/visualization/sparse_values.html new file mode 100644 index 0000000..6da52ee --- /dev/null +++ b/docs/_modules/pofff/visualization/sparse_values.html @@ -0,0 +1,682 @@ + + + + + + + + + + pofff.visualization.sparse_values — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +

Source code for pofff.visualization.sparse_values

+#!/usr/bin/env python
+# SPDX-FileCopyrightText: 2025-2026 NORCE Research AS
+# SPDX-License-Identifier: GPL-3.0
+
+"""Generate FluidFlower sparse benchmark values from time-series data.
+
+The module validates the input duration, extracts maxima, event times, and
+72-hour quantities from the benchmark columns, and writes the standard sparse
+CSV layout."""
+
+from pathlib import Path
+
+import numpy as np
+
+from pofff.utils.terminal import (
+    cli_correct_value,
+    cli_error_value,
+    pofff_error,
+    pofff_info,
+)
+
+SECONDS_PER_HOUR = 3600
+SECONDS_PER_DAY = 86400
+REQUIRED_TIME = 3 * SECONDS_PER_DAY  # 72 hours total
+
+
+
+[docs] +def main( + timeseries_file: str = "time_series.csv", + output_file: str = "sparse_data.csv", +) -> None: + """Read a time series CSV, extract benchmark values. + + The selected file-generation, simulation, data, history-matching, and + plotting stages are executed in dependency order. + + Parameters + ---------- + timeseries_file : str, optional + Time-series CSV containing the benchmark quantities. + output_file : str, optional + Sparse benchmark CSV to create.""" + + if not Path(timeseries_file).exists(): + pofff_error(f"file {cli_error_value(timeseries_file)} was not found.") + + values = np.genfromtxt(timeseries_file, delimiter=",", skip_header=1) + + if values.ndim != 2 or values.shape[0] == 0: + pofff_error(f"file {cli_error_value(timeseries_file)} contains no valid data.") + + time = values[:, 0] + + if time[-1] < REQUIRED_TIME: + sim_hours = time[-1] / SECONDS_PER_HOUR + pofff_info( + "the box quantities require at least 72 hours of simulation; " + f"the current simulation contains {sim_hours:.2f} h." + ) + return + + try: + idx_72h = np.where(time == REQUIRED_TIME)[0][0] + except IndexError: + pofff_error(f"missing data at {cli_correct_value('72 hours (259200 s)')}.") + + sparse = {} + + sparse["sparse1a"] = np.max(values[:, 1]) + sparse["sparse1b"] = np.max(values[:, 2]) + sparse["sparse2"] = time[np.argmax(values[:, 3])] + sparse["sparse3a"] = values[idx_72h, 3] + sparse["sparse3c"] = values[idx_72h, 5] + sparse["sparse3d"] = values[idx_72h, 6] + sparse["sparse4c"] = values[idx_72h, 9] + mask = values[:, 11] >= 1.65 + sparse["sparse5"] = time[mask][0] if np.any(mask) else 0.0 + sparse["sparse6"] = values[-1, 6] + + with open(output_file, "w", encoding="utf8") as f: + f.write("dx,p10_mean,p50_mean,p90_mean,p10_dev,p50_dev,p90_dev\n") + f.write(f"1a,0,{sparse['sparse1a']},0,0,0,0\n") + f.write(f"1b,0,{sparse['sparse1b']},0,0,0,0\n") + f.write(f"2,0,{sparse['sparse2']},0,0,0,0\n") + f.write(f"3a,0,{sparse['sparse3a']},0,0,0,0\n") + f.write("3b,0,0,0,0,0,0\n") + f.write(f"3c,0,{sparse['sparse3c']},0,0,0,0\n") + f.write(f"3d,0,{sparse['sparse3d']},0,0,0,0\n") + f.write("4a,0,0,0,0,0,0\n") + f.write("4b,0,0,0,0,0,0\n") + f.write(f"4c,0,{sparse['sparse4c']},0,0,0,0\n") + f.write("4d,0,0,0,0,0,0\n") + f.write(f"5,0,{sparse['sparse5']},0,0,0,0\n") + f.write(f"6,0,{sparse['sparse6']},0,0,0,0\n")
+ + + +if __name__ == "__main__": + main() +
+ +
+ + + + + +
+ +
+
+
+ +
+ + + + +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/docs/_sources/about.rst.txt b/docs/_sources/about.rst.txt deleted file mode 100644 index 93fdb65..0000000 --- a/docs/_sources/about.rst.txt +++ /dev/null @@ -1,15 +0,0 @@ -============ -About pofff -============ - -.. image:: ./figs/readme.png - -The **pofff** tool is funded by the `Center for Sustainable Subsurface Resources (CSSR) `_ [project no. 331841]. - -Contributions are more than welcome using the fork and pull request approach. - -For a new feature, please request this raising an issue. - -Citing ------- -Landa-Marbán, D., Sandve, T.H., Both, J.W., Nordbotten, J.M., and Gasda, S.E., 2026. Performance of an open-source image-based history matching framework for CO2 storage. Transp Porous Med 153, 21, https://doi.org/10.1007/s11242-025-02275-0. diff --git a/docs/_sources/api.rst.txt b/docs/_sources/api.rst.txt index 21a55f7..c61965d 100644 --- a/docs/_sources/api.rst.txt +++ b/docs/_sources/api.rst.txt @@ -1,17 +1,16 @@ -================= pofff Python API -================= +================ -The main script for the **pofff** executable is located in the core folder. The data from the -FluidFlower benchmark is located in the fluidflower forlder, which is downloaded and modified -from https://github.com/fluidflower. The geology folder contains the geometry to generate the -corner-point grid. The different jobs called by ERT and everest are located in the jobs folder. -The templeates folder contains mako files to generate the input decks. The utils folder has -scripts to comunicate all parts in the framework. Finally, the visualization folder hosts -scripts to postprocess the data. +The top-level executable coordinates configuration, grid and property generation, file writing, simulation, history matching, data processing, and visualization. ``fluidflower`` contains benchmark data, ``geology`` contains corner-point geometry, ``jobs`` contains ERT and Everest jobs, ``templates`` contains Mako inputs, ``utils`` connects the workflows, and ``visualization`` contains benchmark postprocessing. .. figure:: figs/content.png + :alt: Files and directories in the pofff package Files in the pofff package. -.. include:: modules.rst +API files are regenerated under ``docs/text/api`` before each build. + +.. toctree:: + :maxdepth: 2 + + api/modules diff --git a/docs/_sources/modules.rst.txt b/docs/_sources/api/modules.rst.txt similarity index 100% rename from docs/_sources/modules.rst.txt rename to docs/_sources/api/modules.rst.txt diff --git a/docs/_sources/pofff.config.config.rst.txt b/docs/_sources/api/pofff.config.config.rst.txt similarity index 100% rename from docs/_sources/pofff.config.config.rst.txt rename to docs/_sources/api/pofff.config.config.rst.txt diff --git a/docs/_sources/pofff.config.rst.txt b/docs/_sources/api/pofff.config.rst.txt similarity index 100% rename from docs/_sources/pofff.config.rst.txt rename to docs/_sources/api/pofff.config.rst.txt diff --git a/docs/_sources/pofff.core.pofff.rst.txt b/docs/_sources/api/pofff.core.pofff.rst.txt similarity index 100% rename from docs/_sources/pofff.core.pofff.rst.txt rename to docs/_sources/api/pofff.core.pofff.rst.txt diff --git a/docs/_sources/pofff.core.rst.txt b/docs/_sources/api/pofff.core.rst.txt similarity index 100% rename from docs/_sources/pofff.core.rst.txt rename to docs/_sources/api/pofff.core.rst.txt diff --git a/docs/_sources/pofff.jobs.data.rst.txt b/docs/_sources/api/pofff.jobs.data.rst.txt similarity index 100% rename from docs/_sources/pofff.jobs.data.rst.txt rename to docs/_sources/api/pofff.jobs.data.rst.txt diff --git a/docs/_sources/pofff.jobs.delete.rst.txt b/docs/_sources/api/pofff.jobs.delete.rst.txt similarity index 100% rename from docs/_sources/pofff.jobs.delete.rst.txt rename to docs/_sources/api/pofff.jobs.delete.rst.txt diff --git a/docs/_sources/pofff.jobs.metric.rst.txt b/docs/_sources/api/pofff.jobs.metric.rst.txt similarity index 100% rename from docs/_sources/pofff.jobs.metric.rst.txt rename to docs/_sources/api/pofff.jobs.metric.rst.txt diff --git a/docs/_sources/pofff.jobs.rst.txt b/docs/_sources/api/pofff.jobs.rst.txt similarity index 100% rename from docs/_sources/pofff.jobs.rst.txt rename to docs/_sources/api/pofff.jobs.rst.txt diff --git a/docs/_sources/pofff.rst.txt b/docs/_sources/api/pofff.rst.txt similarity index 100% rename from docs/_sources/pofff.rst.txt rename to docs/_sources/api/pofff.rst.txt diff --git a/docs/_sources/pofff.utils.inputvalues.rst.txt b/docs/_sources/api/pofff.utils.inputvalues.rst.txt similarity index 100% rename from docs/_sources/pofff.utils.inputvalues.rst.txt rename to docs/_sources/api/pofff.utils.inputvalues.rst.txt diff --git a/docs/_sources/pofff.utils.mapproperties.rst.txt b/docs/_sources/api/pofff.utils.mapproperties.rst.txt similarity index 100% rename from docs/_sources/pofff.utils.mapproperties.rst.txt rename to docs/_sources/api/pofff.utils.mapproperties.rst.txt diff --git a/docs/_sources/pofff.utils.rst.txt b/docs/_sources/api/pofff.utils.rst.txt similarity index 93% rename from docs/_sources/pofff.utils.rst.txt rename to docs/_sources/api/pofff.utils.rst.txt index b3c3fd6..9b29470 100644 --- a/docs/_sources/pofff.utils.rst.txt +++ b/docs/_sources/api/pofff.utils.rst.txt @@ -10,6 +10,7 @@ Submodules pofff.utils.inputvalues pofff.utils.mapproperties pofff.utils.runs + pofff.utils.terminal pofff.utils.writefile Module contents diff --git a/docs/_sources/pofff.utils.runs.rst.txt b/docs/_sources/api/pofff.utils.runs.rst.txt similarity index 100% rename from docs/_sources/pofff.utils.runs.rst.txt rename to docs/_sources/api/pofff.utils.runs.rst.txt diff --git a/docs/_sources/api/pofff.utils.terminal.rst.txt b/docs/_sources/api/pofff.utils.terminal.rst.txt new file mode 100644 index 0000000..5b2bdbc --- /dev/null +++ b/docs/_sources/api/pofff.utils.terminal.rst.txt @@ -0,0 +1,8 @@ +pofff.utils.terminal module +=========================== + +.. automodule:: pofff.utils.terminal + :members: + :private-members: + :show-inheritance: + :undoc-members: diff --git a/docs/_sources/pofff.utils.writefile.rst.txt b/docs/_sources/api/pofff.utils.writefile.rst.txt similarity index 100% rename from docs/_sources/pofff.utils.writefile.rst.txt rename to docs/_sources/api/pofff.utils.writefile.rst.txt diff --git a/docs/_sources/pofff.visualization.benchmark.rst.txt b/docs/_sources/api/pofff.visualization.benchmark.rst.txt similarity index 100% rename from docs/_sources/pofff.visualization.benchmark.rst.txt rename to docs/_sources/api/pofff.visualization.benchmark.rst.txt diff --git a/docs/_sources/pofff.visualization.error_table.rst.txt b/docs/_sources/api/pofff.visualization.error_table.rst.txt similarity index 100% rename from docs/_sources/pofff.visualization.error_table.rst.txt rename to docs/_sources/api/pofff.visualization.error_table.rst.txt diff --git a/docs/_sources/pofff.visualization.everert.rst.txt b/docs/_sources/api/pofff.visualization.everert.rst.txt similarity index 100% rename from docs/_sources/pofff.visualization.everert.rst.txt rename to docs/_sources/api/pofff.visualization.everert.rst.txt diff --git a/docs/_sources/pofff.visualization.maps.rst.txt b/docs/_sources/api/pofff.visualization.maps.rst.txt similarity index 100% rename from docs/_sources/pofff.visualization.maps.rst.txt rename to docs/_sources/api/pofff.visualization.maps.rst.txt diff --git a/docs/_sources/pofff.visualization.rst.txt b/docs/_sources/api/pofff.visualization.rst.txt similarity index 100% rename from docs/_sources/pofff.visualization.rst.txt rename to docs/_sources/api/pofff.visualization.rst.txt diff --git a/docs/_sources/pofff.visualization.sparse_values.rst.txt b/docs/_sources/api/pofff.visualization.sparse_values.rst.txt similarity index 100% rename from docs/_sources/pofff.visualization.sparse_values.rst.txt rename to docs/_sources/api/pofff.visualization.sparse_values.rst.txt diff --git a/docs/_sources/command-line.rst.txt b/docs/_sources/command-line.rst.txt new file mode 100644 index 0000000..6581ccd --- /dev/null +++ b/docs/_sources/command-line.rst.txt @@ -0,0 +1,10 @@ +Command-line reference +====================== + +The reference is derived from the current ``argparse`` parser. + +.. toctree:: + :maxdepth: 1 + + options/general + options/modes diff --git a/docs/_sources/configuration/complete.rst.txt b/docs/_sources/configuration/complete.rst.txt new file mode 100644 index 0000000..c1de8be --- /dev/null +++ b/docs/_sources/configuration/complete.rst.txt @@ -0,0 +1,54 @@ +Complete TOML example +===================== + +Here we use as an example one of the configuration files used in the tests +(see `input.toml `_). +The first input parameter is: + +.. code-block:: python + :linenos: + + # Set mpirun, the full path to the flow executable, and simulator flags (except --output-dir) + flow = "flow --newton-min-iterations=1 --enable-tuning=true --enable-opm-rst-file=true" + +If **flow** is not in your path, then write the full path to the executable, as well as adding mpirun +if this is supported in your machine (e.g., flow = "mpirun -np 8 /Users/dmar/Github/opm/build/opm-simulators/bin/flow \-\-newton-min-iterations=1"). + +The next entries define the following parameters: + +.. code-block:: python + :linenos: + :lineno-start: 4 + + # Set the model parameters + grid="corner-point" # Type of grid (cartesian, tensor, or corner-point) + thickness="final" # Thickness maps (measured 'initial', 'final', or a real positive value) + mult_thickness=1 # Thickness multiplier (a real positive value) + x=[140] # If cartesian, number of x cells [-]; otherwise, variable array of x-refinement + z=[7,5,5,5,5,5,5,8,10,9,5] # If cartesian, number of z cells [-]; if tensor, variable array of z-refinement; if corner-point, fix array of z-refinement (11 entries) + temperature=[20, 20] # Temperature bottom and top rig [C] + pressure=104900 # Pressure at the datum [Pa] + diffusion=[1e-9, 2e-8] # Diffusion (in liquid and gas) [m^2/s] + sources=[[0.9, 0.3], [1.7, 0.7]] # Source positions: x and z coordinates [m], source 1 to 2 + + # Schedule: 1) injection time [s], 2) time step size to write results [s], 3) injection rate [kg/s] (source1), and 4) injection rate [kg/s] (source2) + inj=[[900, 900, 3E-7, 0, '1e-2 3e-4 1e-20 1e-20 1.6 0.2 0.65 1.1']] + + # Facie Properties + facie1={"permx1"=50e3,"permz1"=50e3,"poro1"=0.37,"disperc1"=1e-1,"swi1"=0.32,"sni1"=0.3,"pen1"=1500,"nkrw1"=2,"nkrn1"=2,"npe1"=2,"thre1"=5e-2,"npnt1"=100} + facie2={"permx2"=100e3,"permz2"=100e3,"poro2"=0.38,"disperc2"=1e-1,"swi2"=0.14,"sni2"=0.3,"pen2"=800,"nkrw2"=2,"nkrn2"=2,"npe2"=2,"thre2"=5e-2,"npnt2"=100} + facie3={"permx3"=300e3,"permz3"=300e3,"poro3"=0.40,"disperc3"=1e-1,"swi3"=0.12,"sni3"=0.1,"pen3"=200,"nkrw3"=2,"nkrn3"=2,"npe3"=2,"thre3"=5e-2,"npnt3"=100} + facie4={"permx4"=800e3,"permz4"=800e3,"poro4"=0.39,"disperc4"=1e-1,"swi4"=0.12,"sni4"=0.1,"pen4"=150,"nkrw4"=2,"nkrn4"=2,"npe4"=2,"thre4"=5e-2,"npnt4"=100} + facie5={"permx5"=1500e3,"permz5"=1500e3,"poro5"=0.39,"disperc5"=1e-1,"swi5"=0.12,"sni5"=0.1,"pen5"=100,"nkrw5"=2,"nkrn5"=2,"npe5"=2,"thre5"=5e-2,"npnt5"=100} + facie6={"permx6"=3000e3,"permz6"=3000e3,"poro6"=0.42,"disperc6"=1e-1,"swi6"=0,"sni6"=0,"pen6"=1,"nkrw6"=2,"nkrn6"=2,"npe6"=2,"thre6"=5e-2,"npnt6"=100} + + # Set the saturation functions + krw="(max(0, (sw - swi) / (1 - swi))) ** nkrw" #Wetting rel perm saturation function [-] + krn="(max(0, (1 - sw - sni) / (1 - sni))) ** nkrn" #Non-wetting rel perm saturation function [-] + cap="pen * ((sw-swi) / (1-swi)) ** (-(1.0 / npen))" #Capillary pressure saturation function [Pa] + +Each line adds a description of the variables. For the facie properties, "THREN" is the threshold to evaluate the capillary pressure function to avoid dividing by 0, +and "NPNTN" is the number of points to generate the saturation tables. + +See the input files in the `examples `_ and `publication `_ +to set the history matchings. diff --git a/docs/_sources/configuration/facies.rst.txt b/docs/_sources/configuration/facies.rst.txt new file mode 100644 index 0000000..b785fa0 --- /dev/null +++ b/docs/_sources/configuration/facies.rst.txt @@ -0,0 +1,14 @@ +Facies and saturation functions +=============================== + +Six required ``facieN`` tables define permeability, porosity, dispersion, residual saturations, capillary entry pressure, Corey exponents, capillary exponent, threshold, and table resolution. + +Required keys +------------- + +For each facies ``N``: ``permxN``, ``permzN``, ``poroN``, ``dispercN``, ``swiN``, ``sniN``, ``penN``, ``nkrwN``, ``nkrnN``, ``npeN``, ``threN``, and ``npntN``. ``npntN`` is an integer of at least 2. Porosity and residual saturations lie in [0, 1], and ``swiN + sniN <= 1``. + +``krw``, ``krn``, and ``cap`` +----------------------------- + +These required strings are valid Python expressions used to generate wetting relative permeability, non-wetting relative permeability, and capillary-pressure tables. ``threN`` prevents evaluation too close to a singular endpoint, and ``npntN`` controls the number of table points. diff --git a/docs/_sources/configuration/history-matching.rst.txt b/docs/_sources/configuration/history-matching.rst.txt new file mode 100644 index 0000000..dbeff22 --- /dev/null +++ b/docs/_sources/configuration/history-matching.rst.txt @@ -0,0 +1,21 @@ +ERT and Everest settings +======================== + +History matching requires ``cores``, ``min_realizations_success``, and at least one facies or thickness parameter. + +ERT +--- + +ERT additionally requires ``ertargs``, ``ensembles``, ``enkf_alpha``, and positive ``errors``. Optional ``random_seed`` controls reproducibility. + +Everest +------- + +Everest requires ``max_function_evaluations`` and ``popsize``. Supported differential-evolution options include ``strategy``, ``maxiter``, ``tol``, ``mutation``, ``recombination``, ``rng``, ``disp``, ``polish``, ``init``, ``atol``, ``updating``, ``workers``, ``x0``, ``integrality``, and ``vectorized``. + +History-matching parameters +--------------------------- + +ERT parameters use a distribution name and two parameters. Everest parameters use ``[initial, minimum, maximum, scale]``. Facies properties may be selected by their numbered key, isotropic permeability may use ``permN``, and ``thicknessmult`` controls the thickness multiplier. + +``monotonic = true`` only has an effect when at least two optimized variables form a comparable same-property facies sequence. diff --git a/docs/_sources/configuration/simulation.rst.txt b/docs/_sources/configuration/simulation.rst.txt new file mode 100644 index 0000000..649bafd --- /dev/null +++ b/docs/_sources/configuration/simulation.rst.txt @@ -0,0 +1,32 @@ +Simulation setup +================ + +``flow`` +-------- + +Non-empty OPM Flow command and flags. pofff adds the output directory. Include ``--enable-tuning=true`` when injection rows contain TUNING values. + +``grid`` +-------- + +Accepted values are ``cartesian``, ``tensor``, and ``corner-point``. Cartesian grids require one entry in ``x`` and ``z``. Corner-point grids require 11 entries in ``z``. + +``thickness`` and ``mult_thickness`` +------------------------------------ + +Select ``initial`` or ``final`` measured thickness, or provide a positive physical thickness. ``mult_thickness`` is a positive multiplier. + +``x`` and ``z`` +--------------- + +Positive integer refinement arrays. Their interpretation depends on ``grid``. + +``temperature``, ``pressure``, and ``diffusion`` +------------------------------------------------ + +``temperature`` contains bottom and top values [°C]. ``pressure`` is positive [Pa]. ``diffusion`` contains liquid and gas coefficients [m²/s]. + +``sources`` and ``inj`` +----------------------- + +``sources`` has two ``[x, z]`` rows [m]. Each injection row contains injection time [s], write time step [s], rates for sources 1 and 2 [kg/s], and optionally one TUNING string. diff --git a/docs/_sources/configuration_file.rst.txt b/docs/_sources/configuration_file.rst.txt index a61c0f9..a055f08 100644 --- a/docs/_sources/configuration_file.rst.txt +++ b/docs/_sources/configuration_file.rst.txt @@ -1,55 +1,12 @@ -================== -Configuration file -================== +Configuration reference +======================= -Here we use as an example one of the configuration files used in the tests -(see `input.toml `_). -The first input parameter is: +pofff uses TOML for simulation, benchmark, ERT, and Everest settings. The parser and validation code define the accepted values and workflow restrictions. -.. code-block:: python - :linenos: +.. toctree:: + :maxdepth: 1 - # Set mpirun, the full path to the flow executable, and simulator flags (except --output-dir) - flow = "flow --newton-min-iterations=1 --enable-tuning=true --enable-opm-rst-file=true" - -If **flow** is not in your path, then write the full path to the executable, as well as adding mpirun -if this is supported in your machine (e.g., flow = "mpirun -np 8 /Users/dmar/Github/opm/build/opm-simulators/bin/flow \-\-newton-min-iterations=1"). - -The next entries define the following parameters: - -.. code-block:: python - :linenos: - :lineno-start: 4 - - # Set the model parameters - grid="corner-point" # Type of grid (cartesian, tensor, or corner-point) - thickness="final" # Thickness maps (measured 'initial', 'final', or a real positive value) - mult_thickness=1 # Thickness multiplier (a real positive value) - x=[140] # If cartesian, number of x cells [-]; otherwise, variable array of x-refinement - z=[7,5,5,5,5,5,5,8,10,9,5] # If cartesian, number of z cells [-]; if tensor, variable array of z-refinement; if corner-point, fix array of z-refinement (18 entries) - temperature=[20, 20] # Temperature bottom and top rig [C] - pressure=104900 # Pressure at the datum [Pa] - diffusion=[1e-9, 2e-8] # Diffusion (in liquid and gas) [m^2/s] - sources=[[0.9, 0.3], [1.7, 0.7]] # Source positions: x and z coordinates [m], source 1 to 2 - - # Schedule: 1) injection time [s], 2) time step size to write results [s], 3) injection rate [kg/s] (source1), and 4) injection rate [kg/s] (source2) - inj=[[900, 900, 3E-7, 0, '1e-2 3e-4 1e-20 1e-20 1.6 0.2 0.65 1.1']] - - # Facie Properties - facie1={"permx1"=50e3,"permz1"=50e3,"poro1"=0.37,"disperc1"=1e-1,"swi1"=0.32,"sni1"=0.3,"pen1"=1500,"nkrw1"=2,"nkrn1"=2,"npe1"=2,"thre1"=5e-2,"npnt1"=100} - facie2={"permx2"=100e3,"permz2"=100e3,"poro2"=0.38,"disperc2"=1e-1,"swi2"=0.14,"sni2"=0.3,"pen2"=800,"nkrw2"=2,"nkrn2"=2,"npe2"=2,"thre2"=5e-2,"npnt2"=100} - facie3={"permx3"=300e3,"permz3"=300e3,"poro3"=0.40,"disperc3"=1e-1,"swi3"=0.12,"sni3"=0.1,"pen3"=200,"nkrw3"=2,"nkrn3"=2,"npe3"=2,"thre3"=5e-2,"npnt3"=100} - facie4={"permx4"=800e3,"permz4"=800e3,"poro4"=0.39,"disperc4"=1e-1,"swi4"=0.12,"sni4"=0.1,"pen4"=150,"nkrw4"=2,"nkrn4"=2,"npe4"=2,"thre4"=5e-2,"npnt4"=100} - facie5={"permx5"=1500e3,"permz5"=1500e3,"poro5"=0.39,"disperc5"=1e-1,"swi5"=0.12,"sni5"=0.1,"pen5"=100,"nkrw5"=2,"nkrn5"=2,"npe5"=2,"thre5"=5e-2,"npnt5"=100} - facie6={"permx6"=3000e3,"permz6"=3000e3,"poro6"=0.42,"disperc6"=1e-1,"swi6"=0,"sni6"=0,"pen6"=1,"nkrw6"=2,"nkrn6"=2,"npe6"=2,"thre6"=5e-2,"npnt6"=100} - - # Set the saturation functions - krw="(max(0, (sw - swi) / (1 - swi))) ** nkrw" #Wetting rel perm saturation function [-] - krn="(max(0, (1 - sw - sni) / (1 - sni))) ** nkrn" #Non-wetting rel perm saturation function [-] - cap="pen * ((sw-swi) / (1-swi)) ** (-(1.0 / npen))" #Capillary pressure saturation function [Pa] - -Each line adds a description of the variables. For the facie properties, "THREN" is the threshold to evaluate the capillary pressure function to avoid dividing by 0, -and "NPNTN" is the number of points to generate the saturation tables. - -See the input files in the `examples `_ and `publication `_ -to set the history matchings. + configuration/simulation + configuration/facies + configuration/history-matching + configuration/complete diff --git a/docs/_sources/examples.rst.txt b/docs/_sources/examples.rst.txt index a4446a2..6d0a684 100644 --- a/docs/_sources/examples.rst.txt +++ b/docs/_sources/examples.rst.txt @@ -1,140 +1,42 @@ -******** Examples -******** +======== -=========== -Hello world -=========== +These examples complement the :doc:`tutorial`. The tutorial now contains the +complete *Add your results* workflow. This gallery focuses on the remaining +maintained workflows: a small first run, third-party simulator results, ERT and +Everest history matching, and visualization with plopm. -The `examples `_ folder contains a few configuration files -with low grid resolution and shorter simulation times (for initial testing of the framework). For example, by executing: +.. grid:: 1 2 2 2 + :gutter: 2 -.. code-block:: bash + .. grid-item-card:: Hello world + :class-card: example-card + :img-top: figs/map_24h.png + :link: examples/hello-world + :link-type: doc - pofff -i single.toml -t 24,48,72 + Run a low-resolution case and compare the generated 24-hour map. -The following is the figure `map_24h.png`. You can compare your example results to this figure to evaluate if your example ran correctly: + .. grid-item-card:: History matching + :class-card: example-card + :img-top: figs/everest.png + :link: examples/history-matching + :link-type: doc -.. figure:: figs/map_24h.png + Configure and run ERT ensemble studies or Everest optimization. -.. tip:: + .. grid-item-card:: Visualization with plopm + :class-card: example-card + :img-top: figs/pofff+plopm_xco2l.gif + :link: examples/visualization + :link-type: doc - All previous commands can be executed as (`docs_hello_world.sh `_): + Generate customized FluidFlower figures and animations from OPM results. - .. code-block:: bash +.. toctree:: + :hidden: + :maxdepth: 1 - . ./tests/scripts/docs_hello_world.sh - -=================== -Adding your results -=================== - -The `publication `_ folder contains the configuration files used for the results in the -`pofff paper `_ (see :doc:`publication <./publication>` for details in the steps to reproduce the figures in the paper). - -.. code-block:: bash - - pofff -i results.toml -o YOURS -m single -t 24,48,72,96,120 -f all - -This generates the figures in the paper in addition to the new simulation labeled as the name of the output folder ("YOURS" in this case), e.g., for the compare_all_time_series.png: - -.. figure:: figs/compare_all_time_series.png - -.. tip:: - - One can always modify the plotting scripts to change the colors, line styles, font sizes, etc. - -We welcome pull request with your configuration files to the examples folder for cases with better results (less error in the error_table_satmin-0.01_conmin-0.1.csv). - -.. tip:: - - All previous commands can be executed as (`docs_adding_your_results.sh `_): - - .. code-block:: bash - - . ./tests/scripts/docs_adding_your_results.sh - -.. note:: - - One can always also generate the sparse_data.csv, time_series.csv, and spatial_map_Xh.csv files using other simulators and use the pofff tool to compare - to the experimental data, forecast, CSIRO, MIT\_M1, and CSSR data. To this end, you can add those files to a folder call "MY\_SIMULATOR" and execute: - - .. code-block:: bash - - pofff -t 24,48,72,96,120 -m none -o MY_SIMULATOR -f all - - Note that the spatial maps csvs need to be given in the x range of 0 to 2.8 m and z range of 0 to 1.2 m. - -================ -History matching -================ - -The flag **-m** can be set to **everest** or **ert** to run the history matchings, which also requires to set additional -variables in the toml configuration file (e.g., parameters to history match, number of parallel runs, random seed). For example, to run the -last iteration of the history matching in the pofff paper corresponding to `everest_iter_3.toml `_: - -.. code-block:: bash - - pofff -i everest_iter_3.toml -o everest_iter_3 -m everest -t 24,48,72,96,120 -f all - -.. warning:: - - If you are running everest_iter_3.toml locally in your machine, then you might need to decrease the number of parallel runs (cores = 50 in line 39) and maximum function evaluations (max_function_evaluations = 200000) to your system capabilities. - -The possible parameters to hm are the 13 parameters for facie 1 to 6 (e.g., permx2, pen5, np6), in addition to consider isotropic permeability (e.g., perm3), and a constant to multiply the tickness (i.e., thicknessmult). -For additional examples on how to set the history matching studies using ert or everest, -see/run the scripts in the `tests `_ and `publication folder `_. - -.. note:: - - We refer to the documentation of `everest `_ and `ert `_ - for the description of the different keywords. While via the toml configuration files in pofff we have added the most common keywords, then one could always - add additional keywords to the generated (after execution of pofff) everest (everest.yml) and ert (ert.ert) configuration files, and after running the history matching directly using the everest/ert command line executables. - Details on all supported (and format types) variables that can be assigned via toml configuration files can be found in `config.py `_. - After the runs, one can always use pofff to postprocess the data and generate the figures, running with the flag **-m none** (see the - `profiling.py `_ for an example of splitting the generation of the files, running of everest, and postprocessing). - Please raise an issue for missing keywords in the toml configuration files that you would like to be added. - -============= -Visualization -============= -To postprocess the data, `plopm `_ can be used. - -.. tip:: - You can install `plopm `_ by executing in the terminal: - - .. code-block:: bash - - pip install git+https://github.com/cssr-tools/plopm.git - -For example, if you run the `appendixb.toml `_ configuration file and change the 'inj' variable in line 17 to: - -.. code-block:: bash - - inj=[[8100, 300, 3E-7, 0], - [10200, 300, 3E-7, 3E-7], - [3300, 300, 0, 0], - [64800, 3600, 0, 0], - [345600, 21600, 0, 0]] - -Then the following GIF is generated by: - - .. code-block:: bash - - pofff -i appendixb.toml -o pofff+plopm -m single -c '5e-2' -f none - plopm -v xco2l -i 'pofff+plopm/POFFF+PLOPM' -d 16,5 -mask satnum -m gif -dpi 1000 -f 20 -loop 1 -cformat .1e -cbsfax 0.30,0.01,0.4,0.02 -interval 437.5 -maskthr 1e-5 -tunits h -cnum 5 -clabel 'CO$_2$ mass fraction in liquid [-]' -t 'FluidFlower simulation (GitHub/cssr-tools/pofff),' - -.. figure:: figs/pofff+plopm_xco2l.gif - - The total simulation time for this GIF (five days of experiment) using eight CPUs for OPM Flow in a Mac is **LESS THAN TWO MINUTES!!!** - - -See the `plopm online docmunetation `_ for additional information of supported flag parameters to generate customized PNGs and GIFs. - -.. tip:: - - All previous commands can be executed as (`docs_visualization.sh `_): - - .. code-block:: bash - - . ./tests/scripts/docs_visualization.sh + examples/hello-world + examples/history-matching + examples/visualization diff --git a/docs/_sources/examples/hello-world.rst.txt b/docs/_sources/examples/hello-world.rst.txt new file mode 100644 index 0000000..6baf6cd --- /dev/null +++ b/docs/_sources/examples/hello-world.rst.txt @@ -0,0 +1,64 @@ +Hello world +=========== + +Overview +-------- + +The repository `examples folder +`_ contains configuration +files with low grid resolution and short simulation times for initial testing. +This example runs one of those small cases and generates benchmark products at +24, 48, and 72 hours. + +Command +------- + +.. code-block:: console + + pofff -i single.toml -t 24,48,72 + +Result +------ + +The following figure is ``map_24h.png``. Compare your generated result with this +figure to check that the example ran correctly. + +.. figure:: ../figs/map_24h.png + :alt: FluidFlower simulation and experimental contours after 24 hours + + Spatial benchmark map after 24 hours. + +Reproduce the example +--------------------- + +Run the maintained documentation script from the repository root: + +.. code-block:: console + + . ./tests/scripts/docs_hello_world.sh + +.. grid:: 1 1 2 2 + :gutter: 2 + + .. grid-item:: + + .. button-link:: https://github.com/cssr-tools/pofff/blob/main/tests/scripts/docs_hello_world.sh + :color: primary + :outline: + :expand: + + View script + + .. grid-item:: + + .. button-link:: https://raw.githubusercontent.com/cssr-tools/pofff/main/tests/scripts/docs_hello_world.sh + :color: primary + :outline: + :expand: + + View raw script + +.. button-ref:: ../examples + :color: primary + + Back to examples gallery diff --git a/docs/_sources/examples/history-matching.rst.txt b/docs/_sources/examples/history-matching.rst.txt new file mode 100644 index 0000000..0fbc762 --- /dev/null +++ b/docs/_sources/examples/history-matching.rst.txt @@ -0,0 +1,143 @@ +History matching with ERT and Everest +===================================== + +Overview +-------- + +Set ``--mode`` to ``ert`` or ``everest`` to run history matching. These modes +require additional TOML variables, including the parameters to history match, +the number of parallel runs, resource limits, and a random seed when +reproducibility is required. + +Refer to the external references for the underlying configuration keywords: + +* `Everest configuration reference + `__ +* `ERT configuration keyword reference + `__ + +Everest example +--------------- + +The following command runs the final history-matching iteration used in the +pofff paper with ``everest_iter_3.toml``: + +.. code-block:: console + + pofff -i everest_iter_3.toml -o everest_iter_3 -m everest -t 24,48,72,96,120 -f all + +.. grid:: 1 1 2 2 + :gutter: 2 + + .. grid-item:: + + .. button-link:: https://github.com/cssr-tools/pofff/blob/main/publication/everest_iter_3.toml + :color: primary + :outline: + :expand: + + View everest_iter_3.toml + + .. grid-item:: + + .. button-link:: https://raw.githubusercontent.com/cssr-tools/pofff/main/publication/everest_iter_3.toml + :color: primary + :outline: + :expand: + + View raw everest_iter_3.toml + +.. warning:: + + The publication configuration uses ``cores = 50`` and + ``max_function_evaluations = 200000``. When running it locally, decrease the + number of parallel runs and maximum function evaluations to match the CPU, + memory, and time available on your system. + +History-matching parameters +--------------------------- + +The possible history-matching variables include the supported facies properties +for facies 1 through 6, for example ``permx2`` and ``pen5``. The current +configuration model supports the numbered properties ``poroN``, ``permN``, +``permxN``, ``permyN``, ``permzN``, ``dispercN``, ``swiN``, ``sniN``, +``penN``, ``nkrwN``, ``nkrnN``, ``npeN``, ``threN``, and ``npntN`` where +applicable. Isotropic permeability can be selected with a variable such as +``perm3``. The ``thicknessmult`` variable scales the thickness map. + +.. note:: + + History-matching values use different layouts for the two workflows. ERT + parameters contain a distribution name and two parameters. Everest + parameters contain ``[initial, minimum, maximum, scale]``. See + :doc:`../configuration_file` for the maintained TOML reference. + +Additional examples +------------------- + +Additional ERT and Everest examples are available in the pofff repository: + +* `Tests `__ +* `Publication configurations + `__ +* `Configuration data class + `__ + +Generated configurations and direct execution +--------------------------------------------- + +pofff exposes the most commonly used ERT and Everest options through TOML. After +pofff generates ``everest.yml`` or ``ert.txt``, you may add supported keywords +that are not yet exposed by pofff and then run the corresponding Everest or ERT +command-line executable directly. + +Please raise a `pofff issue `_ when a +useful TOML keyword is missing, so it can be considered for the maintained +configuration model. + +Separate generation, execution, and postprocessing +-------------------------------------------------- + +After ERT or Everest has run, pofff can postprocess the existing results and +generate figures without starting another history-matching study: + +.. code-block:: console + + pofff -o HISTORY_MATCHING_OUTPUT -m none -t 24,48,72,96,120 -f all + +The publication ``profiling.py`` workflow demonstrates how to separate file +generation, Everest execution, and postprocessing: + +.. grid:: 1 1 2 2 + :gutter: 2 + + .. grid-item:: + + .. button-link:: https://github.com/cssr-tools/pofff/blob/main/publication/profiling.py + :color: primary + :outline: + :expand: + + View profiling.py + + .. grid-item:: + + .. button-link:: https://raw.githubusercontent.com/cssr-tools/pofff/main/publication/profiling.py + :color: primary + :outline: + :expand: + + View raw profiling.py + +Outputs +------- + +ERT and Everest produce different runtime directories, but both postprocessing +workflows create a ``figures/best_simulation`` folder containing the closest or +optimal simulation and its benchmark tables and figures. See +:doc:`../output_folder` for the output layouts. + +.. button-ref:: ../examples + :color: primary + + Back to examples gallery diff --git a/docs/_sources/examples/visualization.rst.txt b/docs/_sources/examples/visualization.rst.txt new file mode 100644 index 0000000..62449a6 --- /dev/null +++ b/docs/_sources/examples/visualization.rst.txt @@ -0,0 +1,92 @@ +Visualization with plopm +======================== + +Overview +-------- + +`plopm `_ can postprocess the OPM Flow +results generated by pofff and create customized PNG figures and GIF animations. + +Installation +------------ + +.. code-block:: console + + pip install git+https://github.com/cssr-tools/plopm.git + +Configuration +------------- + +Run ``publication/appendixb.toml`` after replacing its ``inj`` schedule with: + +.. code-block:: toml + + inj = [ + [8100, 300, 3E-7, 0], + [10200, 300, 3E-7, 3E-7], + [3300, 300, 0, 0], + [64800, 3600, 0, 0], + [345600, 21600, 0, 0], + ] + +Commands +-------- + +Generate the OPM Flow result without the pofff benchmark figures: + +.. code-block:: console + + pofff -i appendixb.toml -o pofff+plopm -m single -c 5e-2 -f none + +Generate the dissolved-CO2 animation: + +.. code-block:: console + + plopm -v xco2l -i 'pofff+plopm/POFFF+PLOPM' -fs 16,5 -mv satnum -m gif -dpi 1000 -fz 20 -gl 1 -cbf .1e -cbp 0.30,0.01,0.4,0.02 -gi 437.5 -mt 1e-5 -tu h -cbn 5 -cbl 'CO$_2$ mass fraction in liquid [-]' -t 'FluidFlower simulation (GitHub/cssr-tools/pofff),' + +Result +------ + +.. figure:: ../figs/pofff+plopm_xco2l.gif + :alt: Dissolved CO2 animation generated from a pofff simulation with plopm + + The documented five-day simulation completed in less than two minutes using + eight OPM Flow CPUs on a Mac. + +See the `plopm documentation `_ for the +complete plotting and animation reference. + +Reproduce the example +--------------------- + +Run the maintained documentation script from the repository root: + +.. code-block:: console + + . ./tests/scripts/docs_visualization.sh + +.. grid:: 1 1 2 2 + :gutter: 2 + + .. grid-item:: + + .. button-link:: https://github.com/cssr-tools/pofff/blob/main/tests/scripts/docs_visualization.sh + :color: primary + :outline: + :expand: + + View script + + .. grid-item:: + + .. button-link:: https://raw.githubusercontent.com/cssr-tools/pofff/main/tests/scripts/docs_visualization.sh + :color: primary + :outline: + :expand: + + View raw script + +.. button-ref:: ../examples + :color: primary + + Back to examples gallery diff --git a/docs/_sources/index.rst.txt b/docs/_sources/index.rst.txt index 7170253..400a454 100644 --- a/docs/_sources/index.rst.txt +++ b/docs/_sources/index.rst.txt @@ -1,25 +1,120 @@ -.. pofff documentation master file +pofff +===== -Welcome to pofff's documentation! -================================== +.. rst-class:: lead + + An open-source image-based history-matching framework for the FluidFlower + benchmark using OPM Flow, ERT, and Everest. + +**pofff** generates FluidFlower simulation models, runs OPM Flow, writes +benchmark CSV files, compares simulations with experimental and published +results, and supports history matching with ERT and Everest. + +.. grid:: 1 2 2 4 + :gutter: 3 + :margin: 4 0 4 0 + + .. grid-item-card:: :octicon:`rocket;1.2em` Get started + :link: introduction + :link-type: doc + + Learn the main simulation, benchmark, and history-matching workflows. + + .. grid-item-card:: :octicon:`download;1.2em` Install + :link: installation + :link-type: doc + + Install pofff, OPM Flow, ERT, Everest, and visualization tools. + + .. grid-item-card:: :octicon:`gear;1.2em` Configure a case + :link: configuration_file + :link-type: doc + + Define the grid, thickness, facies, sources, injection schedule, and + history-matching settings. + + .. grid-item-card:: :octicon:`book;1.2em` Follow the tutorial + :link: tutorial + :link-type: doc + + Run a simulation and add its results to the FluidFlower comparisons. + +Quick installation +------------------ + +Install the current development version: + +.. code-block:: console + + pip install git+https://github.com/cssr-tools/pofff.git + +See :doc:`installation` for virtual environments, OPM Flow, ERT, Everest, +ResInsight, plopm, optional LaTeX support, and installation from source. + +Quick start +----------- + +Run a FluidFlower simulation and generate benchmark results at 24, 48, and +72 hours: + +.. code-block:: console + + pofff -i examples/single.toml -o output -t 24,48,72 + +Generate only the OPM Flow and workflow input files: + +.. code-block:: console + + pofff -i examples/single.toml -o output -m files -f none + +Display the available command-line options: + +.. code-block:: console + + pofff --help + +See :doc:`tutorial` for a guided simulation and comparison workflow, +:doc:`examples` for focused applications, and :doc:`command-line` for exact +syntax, accepted values, defaults, and option compatibility. + +What can pofff do? +------------------ + +.. grid:: 1 2 2 2 + :gutter: 3 + + .. grid-item-card:: Generate FluidFlower models + + Create Cartesian, tensor, and corner-point grids with geological facies, + thickness maps, sources, observation sensors, and benchmark regions. + + .. grid-item-card:: Run configurable workflows + + Generate input files, run OPM Flow, process benchmark data, and create + figures independently or as a connected workflow. + + .. grid-item-card:: Write and compare benchmark results + + Export sparse time-series and dense spatial CSV data, then compare local + or external simulations with experimental and published results. + + .. grid-item-card:: Perform history matching + + Run ERT ensemble studies or Everest differential-evolution optimization, + then extract and postprocess the best simulation. .. toctree:: - :maxdepth: 4 + :hidden: + :maxdepth: 2 introduction installation configuration_file + tutorial examples publication + command-line api output_folder contributing related - about - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/_sources/installation.rst.txt b/docs/_sources/installation.rst.txt index ef29fab..b1330f0 100644 --- a/docs/_sources/installation.rst.txt +++ b/docs/_sources/installation.rst.txt @@ -1,4 +1,3 @@ -============ Installation ============ @@ -146,3 +145,19 @@ You can check if the installation of OPM Flow succeded by typing in the terminal Source build in macOS +++++++++++++++++++++ See `this repository `_ dedicated to build OPM Flow from source in the latest macOS (GitHub actions), and tested with **pycopm**, another repository in cssr-tools. + + +Verification +------------ + +.. code-block:: console + + pofff --help + flow --help + ert --help + everest --help + +Next steps +---------- + +Continue with :doc:`tutorial` or review :doc:`configuration_file`. diff --git a/docs/_sources/introduction.rst.txt b/docs/_sources/introduction.rst.txt index 9218256..24b5af7 100644 --- a/docs/_sources/introduction.rst.txt +++ b/docs/_sources/introduction.rst.txt @@ -1,36 +1,105 @@ -============ +.. _introduction: + Introduction ============ .. video:: figs/pofff_fluidflower.mp4 :width: 700 + :align: center -.. image:: ./figs/pofff.png +.. image:: figs/pofff.png + :alt: FluidFlower simulation and history matching with pofff + :align: center -This documentation describes the **pofff** tool hosted in `https://github.com/cssr-tools/pofff `_. +**pofff** is an open-source, image-based history-matching framework for the +`FluidFlower benchmark `_. It uses a +:doc:`configuration file ` to generate OPM Flow input, +run simulations, write benchmark data, compare simulations with experimental +observations, and create publication-quality figures. -Concept -------- -User-friendly open-source image-based history-matching framework for the FluidFlower benchmark study using OPM Flow. +Core workflows +-------------- -.. _overview: +* Generate Cartesian, tensor, or corner-point FluidFlower grids. +* Assign geological facies, thickness maps, source locations, reporting + regions, and observation sensors. +* Set rock, fluid, saturation-function, diffusion, and injection-schedule + properties. +* Generate OPM Flow decks without running a simulation. +* Run a complete FluidFlower simulation with OPM Flow. +* Write sparse time-series and dense spatial data in benchmark format. +* Compare simulation results with experimental, forecast, CSIRO, MIT_M1, CSSR, + and other published data. +* Run ensemble history matching with ERT. +* Run differential-evolution optimization with Everest. +* Create benchmark maps, time-series comparisons, Wasserstein-distance + metrics, error tables, and publication figures. +* Postprocess benchmark-format results generated by other simulators. -Overview --------- -The current implementation supports the following executable with the argument options: +Basic usage +----------- + +Run a FluidFlower simulation and generate benchmark results: + +.. code-block:: console + + pofff -i examples/single.toml -o output -t 24,48,72 + +Use :doc:`command-line` for exact option syntax and :doc:`examples` for +additional reproducible workflows. -.. code-block:: bash +To add a simulation to the published FluidFlower comparisons, follow the +:doc:`tutorial`. + +About the project +----------------- + +.. image:: figs/readme.png + :alt: Project context for pofff + :align: center + :width: 65% + +**pofff** is funded by the `Center for Sustainable Subsurface Resources +`_ under project number 331841. + +The framework was developed to support reproducible simulation, benchmark +processing, visualization, and history matching for the FluidFlower benchmark. +It integrates OPM Flow with ERT, Everest, FluidFlower experimental data, and +open-source visualization tools. + +See the `pofff paper +`_ for the +scientific description, benchmark results, history-matching studies, and +computational analysis. + +Citation +-------- - pofff -i name_of_input_file.toml +Landa-Marbán, D., Sandve, T. H., Both, J. W., Nordbotten, J. M., and +Gasda, S. E. (2026). Performance of an open-source image-based history +matching framework for CO2 storage. *Transport in Porous Media*, 153, 21. +https://doi.org/10.1007/s11242-025-02275-0 -where +Where to continue +----------------- --i The base name of the :doc:`toml configuration file <./configuration_file>`, ('input.toml' by default). --o The base name of the :doc:`output folder <./output_folder>` ('output' by default). --f Use 'all' to generate all benchmark figures, 'basic' to not generate the Wasserstain distance plot (it is slow), and 'none' for no figures ('basic' by default). --m Run a 'single' simulation, generate only the input 'files', generate only the csv 'data', 'everest', 'ert', 'fair', or 'none' ('none' is useful to generate only figures in combination to '-f') ('single' by default). --t Times in hours separated by commas to evaluate the metrics ('0.25' by default). --e Experimental data to history match, valid options are C1 to C5 ('C2' by default). --s The minimum saturation above which gaseous CO2 is considered for the segmentation ('1e-2' by default). --c The minimum concentration above which CO2 is considered to be dissolved for the segmentation ('1e-1' by default). --u Use the precomputed Wasserstein distance matrix values for minimum concentration of 1e-1 and 5e-2 (minimum saturation of 1e-2) to speed up the computations ('1' by default; set to '0' to compute all). +* Complete the :doc:`installation` and verify the Python package, OPM Flow, + ERT, Everest, and optional visualization tools. +* Use the :doc:`configuration_file` reference to understand simulation, + benchmark, ERT, and Everest TOML variables. +* Follow the :doc:`tutorial` to run a FluidFlower simulation and add its + results to the published benchmark comparisons. +* Browse :doc:`examples` for a small first run, external-simulator results, + history matching, and visualization with plopm. +* Use :doc:`publication` to reproduce the figures, tables, sensitivity + studies, profiling results, and accuracy-time analysis from the pofff paper. +* Review :doc:`output_folder` to understand the files generated by single-run, + ERT, and Everest workflows. +* Use :doc:`command-line` for exact CLI syntax, defaults, accepted values, and + option compatibility. +* Browse :doc:`api` for the Python modules, data classes, functions, and + internal workflow utilities. +* See :doc:`contributing` to report issues, request features, submit improved + configurations, or contribute to **pofff**. +* Explore :doc:`related` for complementary open-source simulation, + visualization, and modeling tools. diff --git a/docs/_sources/options/general.rst.txt b/docs/_sources/options/general.rst.txt new file mode 100644 index 0000000..7155d9e --- /dev/null +++ b/docs/_sources/options/general.rst.txt @@ -0,0 +1,135 @@ +General options +=============== + +The following options control the input configuration, output directory, +execution mode, benchmark evaluation, and segmentation thresholds. + +.. program:: pofff + +Input configuration +------------------- + +.. option:: -i , --input + + TOML configuration file. The default is ``input.toml``. + + This option is not used by the ``fair`` and ``none`` modes. + +Output directory +---------------- + +.. option:: -o , --output + + Output directory. The default is ``output``. + +Figure generation +----------------- + +.. option:: -f , --figures + + Select which benchmark figures to generate. + + Accepted values are: + + ``all`` + Generate all benchmark figures, including the Wasserstein-distance + comparisons. + + ``basic`` + Generate the basic comparison figures without the slower + Wasserstein-distance plots. This is the default. + + ``none`` + Do not generate figures. + +Execution mode +-------------- + +.. option:: -m , --mode + + Select the pofff workflow to execute. + + Accepted values are: + + ``single`` + Generate the input files, run OPM Flow, process the benchmark data, and + optionally generate figures. This is the default. + + ``files`` + Generate the input files without running OPM Flow. + + ``data`` + Generate benchmark data from existing simulation results. + + ``everest`` + Run an Everest optimization workflow. + + ``ert`` + Run an ERT ensemble history-matching workflow. + + ``fair`` + Generate the maintained FAIR benchmark comparisons using the + workflow-specific settings. + + ``none`` + Skip model generation and simulation. Use this mode to postprocess + existing benchmark-format results. + +Evaluation times +---------------- + +.. option:: -t , --times + + Comma-separated positive finite evaluation times in hours. + + The default is ``0.25``. For example: + + .. code-block:: console + + pofff -t 24,48,72,96,120 + +Experimental realization +------------------------ + +.. option:: -e , --experiment + + Experimental FluidFlower realization used for comparison or history + matching. + + Accepted values are ``C1``, ``C2``, ``C3``, ``C4``, and ``C5``. The default + is ``C2``. + +Minimum gas saturation +---------------------- + +.. option:: -s , --minimumsaturation + + Minimum gas saturation used when segmenting gaseous CO2. + + The value must be finite and lie between 0 and 1, inclusive. The default is + ``1e-2``. + +Minimum dissolved concentration +------------------------------- + +.. option:: -c , --minimumconcentration + + Minimum concentration used when segmenting dissolved CO2. + + Accepted values are ``5e-2`` and ``1e-1``. The default is ``1e-1``. + +Precomputed Wasserstein distances +--------------------------------- + +.. option:: -u , --use + + Control whether precomputed Wasserstein-distance values may be used when + available. + + Accepted values are: + + ``1`` + Use precomputed values when available. This is the default. + + ``0`` + Recalculate the Wasserstein distances. diff --git a/docs/_sources/options/modes.rst.txt b/docs/_sources/options/modes.rst.txt new file mode 100644 index 0000000..5d041d3 --- /dev/null +++ b/docs/_sources/options/modes.rst.txt @@ -0,0 +1,4 @@ +Execution modes +=============== + +``single`` generates files, runs OPM Flow, creates benchmark data, and optionally creates figures. ``files`` creates inputs only and cannot use ``-f all``. ``data`` processes an existing simulation. ``ert`` and ``everest`` run their corresponding history-matching workflows. ``fair`` uses fixed benchmark comparison settings and rejects conflicting input, figure, time, and experiment options. ``none`` skips simulation work and is useful for plotting existing results. diff --git a/docs/_sources/output_folder.rst.txt b/docs/_sources/output_folder.rst.txt index 1a78e9e..98605d6 100644 --- a/docs/_sources/output_folder.rst.txt +++ b/docs/_sources/output_folder.rst.txt @@ -1,6 +1,5 @@ -************* Output folder -************* +============= ========== Single run @@ -42,3 +41,6 @@ in the `ert.toml `_. +This section describes how to reproduce the figures, tables, sensitivity studies, and computational results in the `pofff paper `_. -To this end, we use our friend `plopm `_. All commands are executed from the pofff main directory, -and the figures are saved in new folders corresponding to the figure number. +The workflows use `plopm `_ for visualization. Unless stated otherwise, run all commands from the pofff repository root. Each workflow writes to a directory named after the corresponding figure or group of figures. + +Prerequisites +------------- .. tip:: You can install `plopm `_ by executing in the terminal: @@ -14,16 +15,21 @@ and the figures are saved in new folders corresponding to the figure number. pip install git+https://github.com/cssr-tools/plopm.git -Figure 3: +Figure 3: geology and thickness +------------------------------- + .. code-block:: bash pofff -i examples/input.toml -o figure3 -f none - plopm -i figure3/FIGURe3 -c '101;64;147 81;124;66 181;73;57 193;127;97 127;148;191 193;147;56' -cticks '[G, F, E, D, C, ESF]' -v 'pvtnum - 1 - satnum' -grid 'black,1e-2' -remove 1,1,0,1 -d 20,15 -o figure3 -save figure3a -f 20 -clabel 'Sand' - plopm -i figure3/FIGURe3 -v 'multx * 1.75' -grid 'black,1e-2' -remove 1,1,0,1 -d 20,15 -clabel 'Thickness map [cm]' -cformat .2f -cnum 5 -f 20 -o figure3 -save figure3b + plopm -i figure3/FIGURe3 -c '101;64;147 81;124;66 181;73;57 193;127;97 127;148;191 193;147;56' -cbt '[G, F, E, D, C, ESF]' -v 'pvtnum - 1 - satnum' -ge 'black,1e-2' -hide 1,1,0,1 -fs 20,15 -o figure3 -fn figure3a -fz 20 -cbl 'Sand' + plopm -i figure3/FIGURe3 -v 'multx * 1.75' -ge 'black,1e-2' -hide 1,1,0,1 -fs 20,15 -cbl 'Thickness map [cm]' -cbf .2f -cbn 5 -fz 20 -o figure3 -fn figure3b + +Figure 4: SPE11A grid comparison +-------------------------------- Figure 4 requires running the SPE11A cases using `pyopmspe11 `_, which uses the simulation grid. You might need to edit the configuration files to -adjust the computational resources to your machine (e.g., `r3_cp_1cmish_capmax2500Pa.toml `_ is run with 32 cpus). +adjust the computational resources to your machine (e.g., `r3_cp_1cmish_capmax2500Pa.toml `_ is run with 32 CPUs). .. tip:: You can install `pyopmspe11 `_ by executing in the terminal: @@ -35,7 +41,7 @@ adjust the computational resources to your machine (e.g., `r3_cp_1cmish_capmax25 .. code-block:: bash mkdir figure4 && cd figure4 - curl -O https://raw.githubusercontent.com/OPM/pyopmspe11/refs/heads/main/benchmark/spe11a/r3_cp_1cmish_capmax2500Pa.toml + curl -O https://raw.githubusercontent.com/OPM/pyopmspe11/refs/heads/main/benchmark/spe11a/r2_Cart_1cm_capmax2500Pa.toml curl -O https://raw.githubusercontent.com/OPM/pyopmspe11/refs/heads/main/benchmark/spe11a/r3_cp_1cmish_capmax2500Pa.toml curl -O https://raw.githubusercontent.com/OPM/pyopmspe11/refs/heads/main/benchmark/spe11a/r4_Cart_1mm_capmax2500Pa.toml curl -O https://raw.githubusercontent.com/OPM/pyopmspe11/refs/heads/main/benchmark/spe11a/r5_Cart_1mm_capmax2500Pa_strictol.toml @@ -43,7 +49,10 @@ adjust the computational resources to your machine (e.g., `r3_cp_1cmish_capmax25 pyopmspe11 -i r3_cp_1cmish_capmax2500Pa.toml -o r3_cp_1cmish_capmax2500Pa -t 1 -r 280,1,120 -w 0.16666666666666666 pyopmspe11 -i r4_Cart_1mm_capmax2500Pa.toml -o r4_Cart_1mm_capmax2500Pa -t 1 -r 280,1,120 -w 0.16666666666666666 pyopmspe11 -i r5_Cart_1mm_capmax2500Pa_strictol.toml -o r5_Cart_1mm_capmax2500Pa_strictol -t 1 -r 280,1,120 -w 0.16666666666666666 - plopm -v xco2l -r 53 -mask satnum -maskthr 7e-5 -i 'r2_Cart_1cm_capmax2500Pa/flow/R2_CART_1CM_CAPMAX2500PA r3_cp_1cmish_capmax2500Pa/flow/R3_CP_1CMISH_CAPMAX2500PA r4_Cart_1mm_capmax2500Pa/flow/R4_CART_1MM_CAPMAX2500PA r5_Cart_1mm_capmax2500Pa_strictol/flow/R5_CART_1MM_CAPMAX2500PA_STRICTOL' -cnum 3 -xlnum 8 -clabel 'OPM results for SPE11A: CO$_2$ mass fraction (liquid phase) after 2 days' -d 16,6.5 -t "(a) Cartesian grid 1cm (b) Corner-point grid 1cmish (c) Cartesian grid 1mm (d) Cartesian grid 1mm stricter tolerances" -yunits cm -xunits cm -yformat .0f -xformat .0f -f 16 -save figure4 -cformat .2e -suptitle 0 -subfigs 2,2 -cbsfax 0.35,0.97,0.3,0.02 -delax 1 -c '#9ca245 #9da347 #9fa44a #a0a64d #a2a750 #a3a953 #a5aa56 #a6ac59 #a8ad5c #a9af5f #abb062 #adb164 #aeb367 #b0b46a #b1b66d #b3b770 #b4b973 #b6ba76 #b7bc79 #b9bd7c #babf7f #bcc082 #bec184 #bfc387 #c1c48a #c2c68d #c4c790 #c5c993 #c7ca96 #c8cc99 #cacd9c #cbcf9f #cdd0a2 #cfd1a4 #d0d3a7 #d2d4aa #d3d6ad #d5d7b0 #d6d9b3 #d8dab6 #d9dcb9 #dbddbc #dcdfbf #dee0c1 #e0e1c4 #e1e3c7 #e3e4ca #e4e6cd #e6e7d0 #e7e9d3 #e9ead6 #eaecd9 #eceddc #edefdf #eff0e1 #f1f1e4 #f2f3e7 #f4f4ea #f5f6ed #f7f7f0 #f8f9f3 #fafaf6 #fbfcf9 #fdfdfc #ffffff #fefbfb #fdf7f7 #fcf3f3 #fbefef #faebeb #f9e7e7 #f8e3e3 #f7e0e0 #f6dcdc #f5d8d8 #f4d4d4 #f3d0d0 #f2cccc #f1c8c8 #f0c5c5 #efc1c1 #eebdbd #edb9b9 #ecb5b5 #ebb1b1 #eaadad #e9aaaa #e8a6a6 #e7a2a2 #e69e9e #e59a9a #e49696 #e39292 #e28f8f #e18b8b #e08787 #df8383 #de7f7f #dd7b7b #dc7777 #db7474 #da7070 #d96c6c #d86868 #d76464 #d66060 #d55c5c #d45959 #d35555 #d25151 #d14d4d #d04949 #cf4545 #ce4141 #cd3e3e #cc3a3a #cb3636 #ca3232 #c92e2e #c82a2a #c72626 #c62323 #c51f1f #c41b1b #c31717 #c21313 #c10f0f #c00b0b' + plopm -v xco2l -r 53 -mv satnum -mt 7e-5 -i 'r2_Cart_1cm_capmax2500Pa/flow/R2_CART_1CM_CAPMAX2500PA r3_cp_1cmish_capmax2500Pa/flow/R3_CP_1CMISH_CAPMAX2500PA r4_Cart_1mm_capmax2500Pa/flow/R4_CART_1MM_CAPMAX2500PA r5_Cart_1mm_capmax2500Pa_strictol/flow/R5_CART_1MM_CAPMAX2500PA_STRICTOL' -cbn 3 -xnt 8 -cbl 'OPM results for SPE11A: CO$_2$ mass fraction (liquid phase) after 2 days' -fs 16,6.5 -t "(a) Cartesian grid 1cm (b) Corner-point grid 1cmish (c) Cartesian grid 1mm (d) Cartesian grid 1mm stricter tolerances" -yu cm -xu cm -yf .0f -xf .0f -fz 16 -fn figure4 -cbf .2e -st 0 -sg 2,2 -cbp 0.35,0.97,0.3,0.02 -rdl 1 -c '#9ca245 #9da347 #9fa44a #a0a64d #a2a750 #a3a953 #a5aa56 #a6ac59 #a8ad5c #a9af5f #abb062 #adb164 #aeb367 #b0b46a #b1b66d #b3b770 #b4b973 #b6ba76 #b7bc79 #b9bd7c #babf7f #bcc082 #bec184 #bfc387 #c1c48a #c2c68d #c4c790 #c5c993 #c7ca96 #c8cc99 #cacd9c #cbcf9f #cdd0a2 #cfd1a4 #d0d3a7 #d2d4aa #d3d6ad #d5d7b0 #d6d9b3 #d8dab6 #d9dcb9 #dbddbc #dcdfbf #dee0c1 #e0e1c4 #e1e3c7 #e3e4ca #e4e6cd #e6e7d0 #e7e9d3 #e9ead6 #eaecd9 #eceddc #edefdf #eff0e1 #f1f1e4 #f2f3e7 #f4f4ea #f5f6ed #f7f7f0 #f8f9f3 #fafaf6 #fbfcf9 #fdfdfc #ffffff #fefbfb #fdf7f7 #fcf3f3 #fbefef #faebeb #f9e7e7 #f8e3e3 #f7e0e0 #f6dcdc #f5d8d8 #f4d4d4 #f3d0d0 #f2cccc #f1c8c8 #f0c5c5 #efc1c1 #eebdbd #edb9b9 #ecb5b5 #ebb1b1 #eaadad #e9aaaa #e8a6a6 #e7a2a2 #e69e9e #e59a9a #e49696 #e39292 #e28f8f #e18b8b #e08787 #df8383 #de7f7f #dd7b7b #dc7777 #db7474 #da7070 #d96c6c #d86868 #d76464 #d66060 #d55c5c #d45959 #d35555 #d25151 #d14d4d #d04949 #cf4545 #ce4141 #cd3e3e #cc3a3a #cb3636 #ca3232 #c92e2e #c82a2a #c72626 #c62323 #c51f1f #c41b1b #c31717 #c21313 #c10f0f #c00b0b' + +Using submitted SPE11A CSV data ++++++++++++++++++++++++++++++++ A similar figure without the need of running the simulations (not showing the sands in the background and using the reporting grid) can be obtained by downloading the SPE11A benchmark data in csv format available at `this website `_ (the submitted benchmark data does not include the `r5_Cart_1mm_capmax2500Pa_strictol.toml `_ results): @@ -57,9 +66,12 @@ A similar figure without the need of running the simulations (not showing the sa unzip 375716 curl -L -O https://darus.uni-stuttgart.de/api/access/datafile/375707 unzip 375707 - plopm -v xco2l -i 'spe11a/opm2/spe11a_spatial_map_48h spe11a/opm3/spe11a_spatial_map_48h spe11a/opm4/spe11a_spatial_map_48h' -csv "1,2,5;1,2,5;1,2,5" -c cet_diverging_gwr_55_95_c38 -cnum 3 -xlnum 8 -clabel 'OPM results for SPE11A: CO$_2$ mass fraction (liquid phase) after 2 days' -d 16,6.5 -t "(a) Cartesian grid 1cm (b) Corner-point grid 1cmish (c) Cartesian grid 1mm" -yunits cm -xunits cm -yformat .0f -xformat .0f -f 16 -save figure4ish -cformat .2e -suptitle 0 -subfigs 2,2 -cbsfax 0.35,0.97,0.3,0.02 -delax 1 + plopm -v xco2l -i 'spe11a/opm2/spe11a_spatial_map_48h spe11a/opm3/spe11a_spatial_map_48h spe11a/opm4/spe11a_spatial_map_48h' -cc "1,2,5;1,2,5;1,2,5" -c cet_diverging_gwr_55_95_c38 -cbn 3 -xnt 8 -cbl 'OPM results for SPE11A: CO$_2$ mass fraction (liquid phase) after 2 days' -fs 16,6.5 -t "(a) Cartesian grid 1cm (b) Corner-point grid 1cmish (c) Cartesian grid 1mm" -yu cm -xu cm -yf .0f -xf .0f -fz 16 -fn figure4ish -cbf .2e -st 0 -sg 2,2 -cbp 0.35,0.97,0.3,0.02 -rdl 1 + +Figures 5 to 8 and Table 4 +-------------------------- -Figures 5 (g, h, i), 6, 7, and 8 (in addition, the values on Table 4 are obtained from the generated error_table_satmin-0.01_conmin-0.1.csv): +Figures 5 (g, h, i), 6, 7, and 8 (in addition, the values on Table 4 are obtained from the generated ``error_table_satmin-0.01_conmin-0.1.csv``): .. code-block:: bash @@ -68,36 +80,48 @@ Figures 5 (g, h, i), 6, 7, and 8 (in addition, the values on Table 4 are obtaine where fair attempts to aling with `these principles `_. -Similarly, Figures 10 (g, h, i) and 11 (in addition, the values on Table 5 are obtained from the generated error_table_satmin-0.01_conmin-0.05.csv) +Figures 10 and 11 and Table 5 +----------------------------- + +Similarly, Figures 10 (g, h, i) and 11 (in addition, the values on Table 5 are obtained from the generated ``error_table_satmin-0.01_conmin-0.05.csv``) .. code-block:: bash mkdir figures10-11 && cd figures10-11 pofff -m fair -c 0.05 -See the **Adding your results** subsection in the :doc:`examples <./examples>` to compare your customized simulation results to the paper results. +See :doc:`tutorial` to add a customized simulation to the paper comparisons. + +Figures 5d to 5f: dissolved CO2 maps +------------------------------------ For the spatial maps of CO2 concentration in Figures 5d to 5f: .. code-block:: bash pofff -i publication/results.toml -o figures5d-f -m single -t 24,48,72,96,120 - plopm -i figures5d-f/FIGURES5D-F -v xco2l -o figures5d-f -remove 1,1,1,1 -r 3 -save figure5d -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' - plopm -i figures5d-f/FIGURES5D-F -v xco2l -o figures5d-f -remove 1,1,1,1 -r 5 -save figure5e -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' - plopm -i figures5d-f/FIGURES5D-F -v xco2l -o figures5d-f -remove 1,1,1,1 -r 7 -save figure5f -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' + plopm -i figures5d-f/FIGURES5D-F -v xco2l -o figures5d-f -hide 1,1,1,1 -r 3 -fn figure5d -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' + plopm -i figures5d-f/FIGURES5D-F -v xco2l -o figures5d-f -hide 1,1,1,1 -r 5 -fn figure5e -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' + plopm -i figures5d-f/FIGURES5D-F -v xco2l -o figures5d-f -hide 1,1,1,1 -r 7 -fn figure5f -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' + +Figures 10d to 10f: dissolved CO2 maps +-------------------------------------- Similarly, for the spatial maps of CO2 concentration in Figures 10d to 10f: .. code-block:: bash pofff -i publication/appendixb.toml -o figures10d-f -c '5e-2' -m single -t 24,48,72,96,120 - plopm -i figures10d-f/FIGURES10D-F -v xco2l -o figures10d-f -remove 1,1,1,1 -r 3 -save figure10d -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' - plopm -i figures10d-f/FIGURES10D-F -v xco2l -o figures10d-f -remove 1,1,1,1 -r 5 -save figure10e -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' - plopm -i figures10d-f/FIGURES10D-F -v xco2l -o figures10d-f -remove 1,1,1,1 -r 7 -save figure10f -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' + plopm -i figures10d-f/FIGURES10D-F -v xco2l -o figures10d-f -hide 1,1,1,1 -r 3 -fn figure10d -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' + plopm -i figures10d-f/FIGURES10D-F -v xco2l -o figures10d-f -hide 1,1,1,1 -r 5 -fn figure10e -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' + plopm -i figures10d-f/FIGURES10D-F -v xco2l -o figures10d-f -hide 1,1,1,1 -r 7 -fn figure10f -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' + +MPI acceleration +---------------- .. tip:: - If your OPM flow supports MPI, then to speed up the simulations you could change Line 3 in `results.toml `_ + If your OPM Flow supports MPI, then to speed up the simulations you could change Line 3 in `results.toml `_ and `appendixb.toml `_ to: .. code-block:: bash @@ -106,13 +130,22 @@ Similarly, for the spatial maps of CO2 concentration in Figures 10d to 10f: The values in the flags were manually set trying to speed up the simulations. Then, you could also try to run the cases with different flags. -For the Computational details results in the Appendix C, we use the templated configuration file `appendixc.mako `_, -in combination to Python commands, which gives flexibility to generate the different cases. +Appendix C: computational details +--------------------------------- + +For the computational-details results in Appendix C, we use the templated configuration file `appendixc.mako `_, +together with Python commands, which gives flexibility to generate the different cases. + +Sensitivity to setup choices +++++++++++++++++++++++++++++ -The Wasserstein distance (obtained from the generated error_table_satmin-0.01_conmin-0.1.csv) for the +The Wasserstein distance (obtained from the generated ``error_table_satmin-0.01_conmin-0.1.csv``) for the base case and random seed for the sensitivity study regarding setup choices in Table 8 can be generated by executing the `sensitivity.py `_ script (if you have a Mac as well, then execute the script in -there to see the sensitivity to the operative system). +there to see the sensitivity to the operating system). + +Table 7 and Figure 12: profiling +++++++++++++++++++++++++++++++++ The values in Table 7 and Figure 12 can be generated by (increase or decrease the number of cores to adapt to your interest and computer limitations): @@ -120,7 +153,10 @@ The values in Table 7 and Figure 12 can be generated by (increase or decrease th cd publication && python3 profiling.py -Finally, for accuracy-time trade-off Table 9 you can run: +Table 9: accuracy-time trade-off +++++++++++++++++++++++++++++++++ + +For the accuracy-time trade-off in Table 9, run: .. code-block:: bash @@ -128,19 +164,43 @@ Finally, for accuracy-time trade-off Table 9 you can run: .. warning:: - These scripts are set to run with 64 cores, then you should edit them if your machine has less available cpus. + These scripts are set to run with 64 cores, then you should edit them if your machine has fewer available CPUs. -.. tip:: +Reproduce all publication results +--------------------------------- - All previous commands can be executed as (`publication.sh `_): +The maintained ``publication.sh`` script reproduces the publication workflows +described on this page. - .. code-block:: bash +.. code-block:: console - . ./tests/scripts/publication.sh + . ./tests/scripts/publication.sh - This will run the cases with 64 cores. To run the case with 16 cores instead (in a coarser grid as well), run it as: +By default, the script runs the cases with 64 cores. To run the cases with 16 +cores and a coarser grid, use: - .. code-block:: bash +.. code-block:: console + + . ./tests/scripts/publication.sh 16 + +.. grid:: 1 1 2 2 + :gutter: 2 + + .. grid-item:: + + .. button-link:: https://github.com/cssr-tools/pofff/blob/main/tests/scripts/publication.sh + :color: primary + :outline: + :expand: + + View script + + .. grid-item:: + + .. button-link:: https://raw.githubusercontent.com/cssr-tools/pofff/main/tests/scripts/publication.sh + :color: primary + :outline: + :expand: - . ./tests/scripts/publication.sh 16 + View raw script diff --git a/docs/_sources/related.rst.txt b/docs/_sources/related.rst.txt index 528b619..ebfefd2 100644 --- a/docs/_sources/related.rst.txt +++ b/docs/_sources/related.rst.txt @@ -1,67 +1,55 @@ -======= -Related -======= +Related projects +================ -Below are some tools that might be of interest; check 'em out 🙂. +.. grid:: 1 2 2 2 + :gutter: 2 -********** -pyopmspe11 -********** + .. grid-item-card:: pyopmspe11 + :class-card: related-card + :img-top: figs/pyopmspe11.gif + :link: https://github.com/OPM/pyopmspe11 -.. image:: ./figs/pyopmspe11.gif - :scale: 50% + SPE11 benchmark workflows with OPM Flow. -`A Python framework using OPM Flow for the CSP SPE11 benchmark project `_. + .. grid-item-card:: plopm + :class-card: related-card + :img-top: figs/plopm.png + :link: https://github.com/cssr-tools/plopm -***** -plopm -***** + Generate PNGs, GIFs, and VTK data from OPM Flow models. -.. image:: ./figs/plopm.png + .. grid-item-card:: pycopm + :class-card: related-card + :img-top: figs/pycopm.png + :link: https://github.com/cssr-tools/pycopm -`Quick generation of PNGs, GIFs, and VTKs from a OPM Flow type model `_. + Tailor OPM Flow geological models. -****** -pycopm -****** + .. grid-item-card:: pyopmnearwell + :class-card: related-card + :img-top: figs/pyopmnearwell.png + :link: https://github.com/cssr-tools/pyopmnearwell -.. image:: ./figs/pycopm.png - :scale: 32% + Simulate near-well dynamics with OPM Flow. -`An open-source tool to tailor OPM Flow geological models `_. + .. grid-item-card:: expreccs + :class-card: related-card + :img-top: figs/expreccs.gif + :link: https://github.com/cssr-tools/expreccs -************* -pyopmnearwell -************* + Regional and site-reservoir CO2 storage simulations. -.. image:: ./figs/pyopmnearwell.png - :scale: 60% + .. grid-item-card:: ad-micp + :class-card: related-card + :img-top: figs/ad-micp.gif + :link: https://github.com/daavid00/ad-micp -`A Python framework to simulate near well dynamics using OPM Flow `_. + CO2 leakage remediation by MICP. -******** -expreccs -******** + .. grid-item-card:: pymm + :class-card: related-card + :img-top: figs/pymm.gif + :link: https://github.com/cssr-tools/pymm -.. image:: ./figs/expreccs.gif - :scale: 50% + Image-based CFD for microsystems. -`A Python framework using OPM Flow to simulate regional and site reservoirs for CO2 storage `_. - -******* -ad-micp -******* - -.. image:: ./figs/ad-micp.gif - :scale: 40% - -`A module to study CO2 leakage remediation by microbially induced calcite precipitation (MICP) `_. - -**** -pymm -**** - -.. image:: ./figs/pymm.gif - :scale: 15% - -`An open-source image-based framework for CFD in microsystems `_. diff --git a/docs/_sources/tutorial.rst.txt b/docs/_sources/tutorial.rst.txt new file mode 100644 index 0000000..1436f83 --- /dev/null +++ b/docs/_sources/tutorial.rst.txt @@ -0,0 +1,55 @@ +Tutorial +======== + +.. image:: ./figs/readme.png + +Add your results to the FluidFlower comparison +---------------------------------------------- + +This tutorial runs one complete pofff workflow and adds the resulting simulation +to the published FluidFlower comparisons. It is the recommended starting point +for users who already have OPM Flow installed and want to understand how model +generation, simulation, benchmark processing, and plotting connect. + +The tutorial uses ``results.toml`` and writes everything below an output folder +named ``YOURS``. The output folder name is also used as the simulation label in +the comparison figures. + +Prerequisites +------------- + +Before starting, verify that: + +* pofff is installed in the active Python environment; +* OPM Flow is available through the command configured by ``flow`` in + ``results.toml``; +* the ``results.toml`` configuration is available in the current directory, or + its path is supplied with ``--input``; +* LaTeX is installed if you want the recommended figure typography. + +Check the command-line tools with: + +.. code-block:: console + + pofff --help + flow --help + +.. note:: + + The complete workflow can require substantial time and computational + resources. Runtime depends on the grid, OPM Flow settings, requested output + times, and available processors. + +Tutorial sequence +----------------- + +.. toctree:: + :maxdepth: 1 + :numbered: + + tutorial/prepare-configuration + tutorial/run-simulation + tutorial/inspect-data + tutorial/generate-comparisons + tutorial/interpret-results + tutorial/reproduce diff --git a/docs/_sources/tutorial/generate-comparisons.rst.txt b/docs/_sources/tutorial/generate-comparisons.rst.txt new file mode 100644 index 0000000..6abd30d --- /dev/null +++ b/docs/_sources/tutorial/generate-comparisons.rst.txt @@ -0,0 +1,53 @@ +Generate the comparison figures +=============================== + +Goal +---- + +Locate the figures that compare the ``YOURS`` result with the experimental and +published FluidFlower benchmark data. + +Configuration +------------- + +The original command used ``-f all``. This produces the basic benchmark figures +and the Wasserstein-distance comparisons. The complete set requires evaluation +times of 24, 48, 72, 96, and 120 hours. + +Command +------- + +The figures were already generated by the combined tutorial command. To +regenerate figures from existing data without rerunning OPM Flow, use: + +.. code-block:: console + + pofff -o YOURS -m none -t 24,48,72,96,120 -f all + +How it works +------------ + +In ``none`` mode, pofff skips model generation and simulation. It reads the +existing benchmark-format CSV files and recreates spatial overlays, time-series +comparisons, sparse-data comparisons, Wasserstein-distance products, and the +error table. + +Result +------ + +One of the principal outputs is: + +.. figure:: ../figs/compare_all_time_series.png + :alt: FluidFlower time-series comparison including the tutorial simulation + + Published FluidFlower results with the new simulation labeled by its output + directory name. + +The figure should include a series labeled ``YOURS``. If it does not, verify that +the output directory and benchmark CSV files are in the locations expected by +the postprocessing workflow. + +Next +---- + +Continue with :doc:`interpret-results`. diff --git a/docs/_sources/tutorial/inspect-data.rst.txt b/docs/_sources/tutorial/inspect-data.rst.txt new file mode 100644 index 0000000..0bfc948 --- /dev/null +++ b/docs/_sources/tutorial/inspect-data.rst.txt @@ -0,0 +1,74 @@ +Inspect the benchmark data +========================== + +Goal +---- + +Understand the benchmark-format files created from the OPM Flow result before +interpreting the comparison figures. + +Configuration +------------- + +This lesson uses the output generated in :doc:`run-simulation`. No configuration +changes are required. + +Command +------- + +List the generated CSV files: + +.. code-block:: console + + find YOURS -name "*.csv" -maxdepth 3 -print + +How it works +------------ + +The main benchmark products are: + +``time_series.csv`` + Uniformly sampled pressure, mobile, immobile, dissolved, seal, and migration + quantities used by the time-series comparisons. + +``spatial_map_Xh.csv`` + Saturation and dissolved-CO2 concentration mapped to the FluidFlower + reporting grid at time ``X`` hours. + +``sparse_data.csv`` + Sparse benchmark quantities derived from the time series, including maxima, + event times, and values evaluated at 72 hours. + +``sim_metrics_0.txt`` and ``func`` + Per-observation Wasserstein metrics and the combined objective used by ERT or + Everest job workflows when those files are generated. + +Result +------ + +Confirm that the requested spatial maps exist for 24, 48, 72, 96, and 120 hours. +The CSV files may also be generated by another simulator, provided that they +follow the same benchmark layouts and coordinate ranges. + +.. note:: + + You can generate ``sparse_data.csv``, ``time_series.csv``, and + ``spatial_map_Xh.csv`` files with another simulator and use pofff to compare + those results with the experimental data, forecast, CSIRO, MIT_M1, and CSSR + data. + + Place the files in a dedicated output folder, such as ``MY_SIMULATOR``, and + run: + + .. code-block:: console + + pofff -t 24,48,72,96,120 -m none -o MY_SIMULATOR -f all + + The spatial-map CSV files must cover the FluidFlower reporting domain from + 0 to 2.8 m in the x direction and from 0 to 1.2 m in the z direction. The + files must otherwise follow the benchmark layouts expected by pofff. + +Next +---- + +Continue with :doc:`generate-comparisons`. diff --git a/docs/_sources/tutorial/interpret-results.rst.txt b/docs/_sources/tutorial/interpret-results.rst.txt new file mode 100644 index 0000000..8e16b7c --- /dev/null +++ b/docs/_sources/tutorial/interpret-results.rst.txt @@ -0,0 +1,55 @@ +Interpret and improve the results +================================= + +Goal +---- + +Use the generated comparisons and error table to identify model settings that +could improve agreement with the FluidFlower observations. + +Configuration +------------- + +Keep the original ``results.toml`` unchanged as a baseline. Create a copy before +changing grid resolution, facies properties, capillary pressure, relative +permeability, dispersion, diffusion, thickness, or injection controls. + +Command +------- + +Run a modified case in a different output directory so that the baseline is +preserved: + +.. code-block:: console + + pofff -i modified_results.toml -o MODIFIED -m single -t 24,48,72,96,120 -f all + +How it works +------------ + +Use the outputs together: + +* time-series plots show the evolution of pressures and regional CO2 quantities; +* spatial maps show where gaseous and dissolved CO2 differ from experimental + segmentations; +* sparse-data comparisons summarize selected benchmark observables; +* ``error_table_satmin-0.01_conmin-0.1.csv`` reports relative errors, + Wasserstein distance, and their combined metric for the default thresholds. + +.. tip:: + + Change one physical or numerical choice at a time when diagnosing its effect. + Use a separate output directory for every case so results remain reproducible. + +Result +------ + +You should be able to identify whether disagreement is primarily temporal, +spatial, or associated with a specific benchmark region or observable. +Configurations producing better benchmark metrics are welcome in the repository +examples through the fork and pull request workflow. + +Next +---- + +Continue with :doc:`reproduce` to run the maintained tutorial script. diff --git a/docs/_sources/tutorial/prepare-configuration.rst.txt b/docs/_sources/tutorial/prepare-configuration.rst.txt new file mode 100644 index 0000000..9a15f45 --- /dev/null +++ b/docs/_sources/tutorial/prepare-configuration.rst.txt @@ -0,0 +1,73 @@ +Prepare the configuration +========================= + +Goal +---- + +Review the configuration used for the tutorial and identify the settings that +control the physical model, simulation, benchmark evaluation, and figures. + +Configuration +------------- + +The tutorial uses ``results.toml``. The maintained configuration is available in +the repository publication folder: + +.. grid:: 1 1 2 2 + :gutter: 2 + + .. grid-item:: + + .. button-link:: https://github.com/cssr-tools/pofff/blob/main/publication/results.toml + :color: primary + :outline: + :expand: + + View results.toml + + .. grid-item:: + + .. button-link:: https://raw.githubusercontent.com/cssr-tools/pofff/main/publication/results.toml + :color: primary + :outline: + :expand: + + View raw results.toml + +The most important groups of settings are: + +* ``flow``: OPM Flow executable and simulator flags; +* ``grid``, ``x``, and ``z``: grid representation and refinement; +* ``thickness`` and ``mult_thickness``: FluidFlower thickness model; +* ``temperature``, ``pressure``, and ``diffusion``: initial and fluid settings; +* ``sources`` and ``inj``: injection locations and schedule; +* ``facie1`` through ``facie6``: facies-dependent rock and saturation data; +* ``krw``, ``krn``, and ``cap``: saturation-function expressions. + +See :doc:`../configuration_file` for accepted values, units, array layouts, and +validation rules. + +Command +------- + +No pofff command is required at this stage. If ``results.toml`` is not already +available locally, download it from the repository or copy it from the +``publication`` directory. + +How it works +------------ + +When the tutorial command starts, pofff validates the TOML input, creates the +shared runtime configuration, generates the selected grid, assigns facies and +benchmark regions, and renders the OPM Flow input files. + +Result +------ + +You should have a reviewed ``results.toml`` file and know which OPM Flow command +and computational resources it requests. + +Next +---- + +Continue with :doc:`run-simulation`. diff --git a/docs/_sources/tutorial/reproduce.rst.txt b/docs/_sources/tutorial/reproduce.rst.txt new file mode 100644 index 0000000..651cdc3 --- /dev/null +++ b/docs/_sources/tutorial/reproduce.rst.txt @@ -0,0 +1,76 @@ +Reproduce the tutorial +====================== + +Goal +---- + +Run the maintained repository script that reproduces the Add your results +workflow. + +Configuration +------------- + +Run the script from the pofff repository root. Review its commands and +configuration paths before execution, especially on systems with limited CPU or +memory resources. + +Command +------- + +.. code-block:: console + + . ./tests/scripts/docs_adding_your_results.sh + +How it works +------------ + +The script runs the documented simulation and comparison workflow using the +maintained repository inputs. It provides a reproducible check that the tutorial +commands remain aligned with the current source code. + +Result +------ + +The generated output should contain the benchmark CSV products and comparison +figures described in the previous lessons. + +Reproducibility links +--------------------- + +.. grid:: 1 1 3 3 + :gutter: 2 + + .. grid-item:: + + .. button-link:: https://github.com/cssr-tools/pofff/blob/main/tests/scripts/docs_adding_your_results.sh + :color: primary + :outline: + :expand: + + View script + + .. grid-item:: + + .. button-link:: https://raw.githubusercontent.com/cssr-tools/pofff/main/tests/scripts/docs_adding_your_results.sh + :color: primary + :outline: + :expand: + + View raw script + + .. grid-item:: + + .. button-link:: https://github.com/cssr-tools/pofff/blob/main/publication/results.toml + :color: secondary + :outline: + :expand: + + View results.toml + +Where to continue +----------------- + +* Use :doc:`../examples` for external-simulator, history-matching, and plopm workflows. +* Use :doc:`../publication` to reproduce the paper figures and tables. +* Use :doc:`../command-line` for exact CLI choices and defaults. +* Use :doc:`../configuration_file` for the complete TOML reference. diff --git a/docs/_sources/tutorial/run-simulation.rst.txt b/docs/_sources/tutorial/run-simulation.rst.txt new file mode 100644 index 0000000..a734951 --- /dev/null +++ b/docs/_sources/tutorial/run-simulation.rst.txt @@ -0,0 +1,48 @@ +Run the simulation +================== + +Goal +---- + +Generate the FluidFlower model, run OPM Flow, process benchmark data, and create +all supported comparison figures with one command. + +Configuration +------------- + +Use the reviewed ``results.toml`` configuration. The tutorial requests benchmark +output at 24, 48, 72, 96, and 120 hours and enables all figure products. + +Command +------- + +.. code-block:: console + + pofff -i results.toml -o YOURS -m single -t 24,48,72,96,120 -f all + +How it works +------------ + +The command performs the connected workflow in sequence: + +#. Generate the OPM deck, include files, grid properties, and saturation tables. +#. Run OPM Flow with the executable and flags specified by ``flow``. +#. Convert the simulation results to benchmark time-series and spatial CSV data. +#. Calculate benchmark metrics for the requested experimental realization. +#. Generate the basic comparison figures and the Wasserstein-distance products + enabled by ``-f all``. + +The output directory is named ``YOURS``. pofff also uses this name to label the +new simulation in comparison figures. + +Result +------ + +The ``YOURS`` directory contains the generated simulation input, OPM Flow +results, benchmark CSV files, metrics, and figures. See +:doc:`../output_folder` for the complete output organization. + +Next +---- + +Continue with :doc:`inspect-data`. diff --git a/docs/_sphinx_design_static/design-tabs.js b/docs/_sphinx_design_static/design-tabs.js new file mode 100644 index 0000000..b25bd6a --- /dev/null +++ b/docs/_sphinx_design_static/design-tabs.js @@ -0,0 +1,101 @@ +// @ts-check + +// Extra JS capability for selected tabs to be synced +// The selection is stored in local storage so that it persists across page loads. + +/** + * @type {Record} + */ +let sd_id_to_elements = {}; +const storageKeyPrefix = "sphinx-design-tab-id-"; + +/** + * Create a key for a tab element. + * @param {HTMLElement} el - The tab element. + * @returns {[string, string, string] | null} - The key. + * + */ +function create_key(el) { + let syncId = el.getAttribute("data-sync-id"); + let syncGroup = el.getAttribute("data-sync-group"); + if (!syncId || !syncGroup) return null; + return [syncGroup, syncId, syncGroup + "--" + syncId]; +} + +/** + * Initialize the tab selection. + * + */ +function ready() { + // Find all tabs with sync data + + /** @type {string[]} */ + let groups = []; + + document.querySelectorAll(".sd-tab-label").forEach((label) => { + if (label instanceof HTMLElement) { + let data = create_key(label); + if (data) { + let [group, id, key] = data; + + // add click event listener + // @ts-ignore + label.onclick = onSDLabelClick; + + // store map of key to elements + if (!sd_id_to_elements[key]) { + sd_id_to_elements[key] = []; + } + sd_id_to_elements[key].push(label); + + if (groups.indexOf(group) === -1) { + groups.push(group); + // Check if a specific tab has been selected via URL parameter + const tabParam = new URLSearchParams(window.location.search).get( + group + ); + if (tabParam) { + console.log( + "sphinx-design: Selecting tab id for group '" + + group + + "' from URL parameter: " + + tabParam + ); + window.sessionStorage.setItem(storageKeyPrefix + group, tabParam); + } + } + + // Check is a specific tab has been selected previously + let previousId = window.sessionStorage.getItem( + storageKeyPrefix + group + ); + if (previousId === id) { + // console.log( + // "sphinx-design: Selecting tab from session storage: " + id + // ); + // @ts-ignore + label.previousElementSibling.checked = true; + } + } + } + }); +} + +/** + * Activate other tabs with the same sync id. + * + * @this {HTMLElement} - The element that was clicked. + */ +function onSDLabelClick() { + let data = create_key(this); + if (!data) return; + let [group, id, key] = data; + for (const label of sd_id_to_elements[key]) { + if (label === this) continue; + // @ts-ignore + label.previousElementSibling.checked = true; + } + window.sessionStorage.setItem(storageKeyPrefix + group, id); +} + +document.addEventListener("DOMContentLoaded", ready, false); diff --git a/docs/_sphinx_design_static/sphinx-design.min.css b/docs/_sphinx_design_static/sphinx-design.min.css new file mode 100644 index 0000000..860c36d --- /dev/null +++ b/docs/_sphinx_design_static/sphinx-design.min.css @@ -0,0 +1 @@ +.sd-bg-primary{background-color:var(--sd-color-primary) !important}.sd-bg-text-primary{color:var(--sd-color-primary-text) !important}button.sd-bg-primary:focus,button.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}a.sd-bg-primary:focus,a.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}.sd-bg-secondary{background-color:var(--sd-color-secondary) !important}.sd-bg-text-secondary{color:var(--sd-color-secondary-text) !important}button.sd-bg-secondary:focus,button.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}a.sd-bg-secondary:focus,a.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}.sd-bg-success{background-color:var(--sd-color-success) !important}.sd-bg-text-success{color:var(--sd-color-success-text) !important}button.sd-bg-success:focus,button.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}a.sd-bg-success:focus,a.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}.sd-bg-info{background-color:var(--sd-color-info) !important}.sd-bg-text-info{color:var(--sd-color-info-text) !important}button.sd-bg-info:focus,button.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}a.sd-bg-info:focus,a.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}.sd-bg-warning{background-color:var(--sd-color-warning) !important}.sd-bg-text-warning{color:var(--sd-color-warning-text) !important}button.sd-bg-warning:focus,button.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}a.sd-bg-warning:focus,a.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}.sd-bg-danger{background-color:var(--sd-color-danger) !important}.sd-bg-text-danger{color:var(--sd-color-danger-text) !important}button.sd-bg-danger:focus,button.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}a.sd-bg-danger:focus,a.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}.sd-bg-light{background-color:var(--sd-color-light) !important}.sd-bg-text-light{color:var(--sd-color-light-text) !important}button.sd-bg-light:focus,button.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}a.sd-bg-light:focus,a.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}.sd-bg-muted{background-color:var(--sd-color-muted) !important}.sd-bg-text-muted{color:var(--sd-color-muted-text) !important}button.sd-bg-muted:focus,button.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}a.sd-bg-muted:focus,a.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}.sd-bg-dark{background-color:var(--sd-color-dark) !important}.sd-bg-text-dark{color:var(--sd-color-dark-text) !important}button.sd-bg-dark:focus,button.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}a.sd-bg-dark:focus,a.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}.sd-bg-black{background-color:var(--sd-color-black) !important}.sd-bg-text-black{color:var(--sd-color-black-text) !important}button.sd-bg-black:focus,button.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}a.sd-bg-black:focus,a.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}.sd-bg-white{background-color:var(--sd-color-white) !important}.sd-bg-text-white{color:var(--sd-color-white-text) !important}button.sd-bg-white:focus,button.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}a.sd-bg-white:focus,a.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}.sd-text-primary,.sd-text-primary>p{color:var(--sd-color-primary) !important}a.sd-text-primary:focus,a.sd-text-primary:hover{color:var(--sd-color-primary-highlight) !important}.sd-text-secondary,.sd-text-secondary>p{color:var(--sd-color-secondary) !important}a.sd-text-secondary:focus,a.sd-text-secondary:hover{color:var(--sd-color-secondary-highlight) !important}.sd-text-success,.sd-text-success>p{color:var(--sd-color-success) !important}a.sd-text-success:focus,a.sd-text-success:hover{color:var(--sd-color-success-highlight) !important}.sd-text-info,.sd-text-info>p{color:var(--sd-color-info) !important}a.sd-text-info:focus,a.sd-text-info:hover{color:var(--sd-color-info-highlight) !important}.sd-text-warning,.sd-text-warning>p{color:var(--sd-color-warning) !important}a.sd-text-warning:focus,a.sd-text-warning:hover{color:var(--sd-color-warning-highlight) !important}.sd-text-danger,.sd-text-danger>p{color:var(--sd-color-danger) !important}a.sd-text-danger:focus,a.sd-text-danger:hover{color:var(--sd-color-danger-highlight) !important}.sd-text-light,.sd-text-light>p{color:var(--sd-color-light) !important}a.sd-text-light:focus,a.sd-text-light:hover{color:var(--sd-color-light-highlight) !important}.sd-text-muted,.sd-text-muted>p{color:var(--sd-color-muted) !important}a.sd-text-muted:focus,a.sd-text-muted:hover{color:var(--sd-color-muted-highlight) !important}.sd-text-dark,.sd-text-dark>p{color:var(--sd-color-dark) !important}a.sd-text-dark:focus,a.sd-text-dark:hover{color:var(--sd-color-dark-highlight) !important}.sd-text-black,.sd-text-black>p{color:var(--sd-color-black) !important}a.sd-text-black:focus,a.sd-text-black:hover{color:var(--sd-color-black-highlight) !important}.sd-text-white,.sd-text-white>p{color:var(--sd-color-white) !important}a.sd-text-white:focus,a.sd-text-white:hover{color:var(--sd-color-white-highlight) !important}.sd-outline-primary{border-color:var(--sd-color-primary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-primary:focus,a.sd-outline-primary:hover{border-color:var(--sd-color-primary-highlight) !important}.sd-outline-secondary{border-color:var(--sd-color-secondary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-secondary:focus,a.sd-outline-secondary:hover{border-color:var(--sd-color-secondary-highlight) !important}.sd-outline-success{border-color:var(--sd-color-success) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-success:focus,a.sd-outline-success:hover{border-color:var(--sd-color-success-highlight) !important}.sd-outline-info{border-color:var(--sd-color-info) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-info:focus,a.sd-outline-info:hover{border-color:var(--sd-color-info-highlight) !important}.sd-outline-warning{border-color:var(--sd-color-warning) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-warning:focus,a.sd-outline-warning:hover{border-color:var(--sd-color-warning-highlight) !important}.sd-outline-danger{border-color:var(--sd-color-danger) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-danger:focus,a.sd-outline-danger:hover{border-color:var(--sd-color-danger-highlight) !important}.sd-outline-light{border-color:var(--sd-color-light) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-light:focus,a.sd-outline-light:hover{border-color:var(--sd-color-light-highlight) !important}.sd-outline-muted{border-color:var(--sd-color-muted) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-muted:focus,a.sd-outline-muted:hover{border-color:var(--sd-color-muted-highlight) !important}.sd-outline-dark{border-color:var(--sd-color-dark) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-dark:focus,a.sd-outline-dark:hover{border-color:var(--sd-color-dark-highlight) !important}.sd-outline-black{border-color:var(--sd-color-black) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-black:focus,a.sd-outline-black:hover{border-color:var(--sd-color-black-highlight) !important}.sd-outline-white{border-color:var(--sd-color-white) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-white:focus,a.sd-outline-white:hover{border-color:var(--sd-color-white-highlight) !important}.sd-bg-transparent{background-color:transparent !important}.sd-outline-transparent{border-color:transparent !important}.sd-text-transparent{color:transparent !important}.sd-p-0{padding:0 !important}.sd-pt-0,.sd-py-0{padding-top:0 !important}.sd-pr-0,.sd-px-0{padding-right:0 !important}.sd-pb-0,.sd-py-0{padding-bottom:0 !important}.sd-pl-0,.sd-px-0{padding-left:0 !important}.sd-p-1{padding:.25rem !important}.sd-pt-1,.sd-py-1{padding-top:.25rem !important}.sd-pr-1,.sd-px-1{padding-right:.25rem !important}.sd-pb-1,.sd-py-1{padding-bottom:.25rem !important}.sd-pl-1,.sd-px-1{padding-left:.25rem !important}.sd-p-2{padding:.5rem !important}.sd-pt-2,.sd-py-2{padding-top:.5rem !important}.sd-pr-2,.sd-px-2{padding-right:.5rem !important}.sd-pb-2,.sd-py-2{padding-bottom:.5rem !important}.sd-pl-2,.sd-px-2{padding-left:.5rem !important}.sd-p-3{padding:1rem !important}.sd-pt-3,.sd-py-3{padding-top:1rem !important}.sd-pr-3,.sd-px-3{padding-right:1rem !important}.sd-pb-3,.sd-py-3{padding-bottom:1rem !important}.sd-pl-3,.sd-px-3{padding-left:1rem !important}.sd-p-4{padding:1.5rem !important}.sd-pt-4,.sd-py-4{padding-top:1.5rem !important}.sd-pr-4,.sd-px-4{padding-right:1.5rem !important}.sd-pb-4,.sd-py-4{padding-bottom:1.5rem !important}.sd-pl-4,.sd-px-4{padding-left:1.5rem !important}.sd-p-5{padding:3rem !important}.sd-pt-5,.sd-py-5{padding-top:3rem !important}.sd-pr-5,.sd-px-5{padding-right:3rem !important}.sd-pb-5,.sd-py-5{padding-bottom:3rem !important}.sd-pl-5,.sd-px-5{padding-left:3rem !important}.sd-m-auto{margin:auto !important}.sd-mt-auto,.sd-my-auto{margin-top:auto !important}.sd-mr-auto,.sd-mx-auto{margin-right:auto !important}.sd-mb-auto,.sd-my-auto{margin-bottom:auto !important}.sd-ml-auto,.sd-mx-auto{margin-left:auto !important}.sd-m-0{margin:0 !important}.sd-mt-0,.sd-my-0{margin-top:0 !important}.sd-mr-0,.sd-mx-0{margin-right:0 !important}.sd-mb-0,.sd-my-0{margin-bottom:0 !important}.sd-ml-0,.sd-mx-0{margin-left:0 !important}.sd-m-1{margin:.25rem !important}.sd-mt-1,.sd-my-1{margin-top:.25rem !important}.sd-mr-1,.sd-mx-1{margin-right:.25rem !important}.sd-mb-1,.sd-my-1{margin-bottom:.25rem !important}.sd-ml-1,.sd-mx-1{margin-left:.25rem !important}.sd-m-2{margin:.5rem !important}.sd-mt-2,.sd-my-2{margin-top:.5rem !important}.sd-mr-2,.sd-mx-2{margin-right:.5rem !important}.sd-mb-2,.sd-my-2{margin-bottom:.5rem !important}.sd-ml-2,.sd-mx-2{margin-left:.5rem !important}.sd-m-3{margin:1rem !important}.sd-mt-3,.sd-my-3{margin-top:1rem !important}.sd-mr-3,.sd-mx-3{margin-right:1rem !important}.sd-mb-3,.sd-my-3{margin-bottom:1rem !important}.sd-ml-3,.sd-mx-3{margin-left:1rem !important}.sd-m-4{margin:1.5rem !important}.sd-mt-4,.sd-my-4{margin-top:1.5rem !important}.sd-mr-4,.sd-mx-4{margin-right:1.5rem !important}.sd-mb-4,.sd-my-4{margin-bottom:1.5rem !important}.sd-ml-4,.sd-mx-4{margin-left:1.5rem !important}.sd-m-5{margin:3rem !important}.sd-mt-5,.sd-my-5{margin-top:3rem !important}.sd-mr-5,.sd-mx-5{margin-right:3rem !important}.sd-mb-5,.sd-my-5{margin-bottom:3rem !important}.sd-ml-5,.sd-mx-5{margin-left:3rem !important}.sd-w-25{width:25% !important}.sd-w-50{width:50% !important}.sd-w-75{width:75% !important}.sd-w-100{width:100% !important}.sd-w-auto{width:auto !important}.sd-h-25{height:25% !important}.sd-h-50{height:50% !important}.sd-h-75{height:75% !important}.sd-h-100{height:100% !important}.sd-h-auto{height:auto !important}.sd-d-none{display:none !important}.sd-d-inline{display:inline !important}.sd-d-inline-block{display:inline-block !important}.sd-d-block{display:block !important}.sd-d-grid{display:grid !important}.sd-d-flex-row{display:-ms-flexbox !important;display:flex !important;flex-direction:row !important}.sd-d-flex-column{display:-ms-flexbox !important;display:flex !important;flex-direction:column !important}.sd-d-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}@media(min-width: 576px){.sd-d-sm-none{display:none !important}.sd-d-sm-inline{display:inline !important}.sd-d-sm-inline-block{display:inline-block !important}.sd-d-sm-block{display:block !important}.sd-d-sm-grid{display:grid !important}.sd-d-sm-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-sm-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 768px){.sd-d-md-none{display:none !important}.sd-d-md-inline{display:inline !important}.sd-d-md-inline-block{display:inline-block !important}.sd-d-md-block{display:block !important}.sd-d-md-grid{display:grid !important}.sd-d-md-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-md-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 992px){.sd-d-lg-none{display:none !important}.sd-d-lg-inline{display:inline !important}.sd-d-lg-inline-block{display:inline-block !important}.sd-d-lg-block{display:block !important}.sd-d-lg-grid{display:grid !important}.sd-d-lg-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-lg-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 1200px){.sd-d-xl-none{display:none !important}.sd-d-xl-inline{display:inline !important}.sd-d-xl-inline-block{display:inline-block !important}.sd-d-xl-block{display:block !important}.sd-d-xl-grid{display:grid !important}.sd-d-xl-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-xl-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}.sd-align-major-start{justify-content:flex-start !important}.sd-align-major-end{justify-content:flex-end !important}.sd-align-major-center{justify-content:center !important}.sd-align-major-justify{justify-content:space-between !important}.sd-align-major-spaced{justify-content:space-evenly !important}.sd-align-minor-start{align-items:flex-start !important}.sd-align-minor-end{align-items:flex-end !important}.sd-align-minor-center{align-items:center !important}.sd-align-minor-stretch{align-items:stretch !important}.sd-text-justify{text-align:justify !important}.sd-text-left{text-align:left !important}.sd-text-right{text-align:right !important}.sd-text-center{text-align:center !important}.sd-font-weight-light{font-weight:300 !important}.sd-font-weight-lighter{font-weight:lighter !important}.sd-font-weight-normal{font-weight:400 !important}.sd-font-weight-bold{font-weight:700 !important}.sd-font-weight-bolder{font-weight:bolder !important}.sd-font-italic{font-style:italic !important}.sd-text-decoration-none{text-decoration:none !important}.sd-text-lowercase{text-transform:lowercase !important}.sd-text-uppercase{text-transform:uppercase !important}.sd-text-capitalize{text-transform:capitalize !important}.sd-text-wrap{white-space:normal !important}.sd-text-nowrap{white-space:nowrap !important}.sd-text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sd-fs-1,.sd-fs-1>p{font-size:calc(1.375rem + 1.5vw) !important;line-height:unset !important}.sd-fs-2,.sd-fs-2>p{font-size:calc(1.325rem + 0.9vw) !important;line-height:unset !important}.sd-fs-3,.sd-fs-3>p{font-size:calc(1.3rem + 0.6vw) !important;line-height:unset !important}.sd-fs-4,.sd-fs-4>p{font-size:calc(1.275rem + 0.3vw) !important;line-height:unset !important}.sd-fs-5,.sd-fs-5>p{font-size:1.25rem !important;line-height:unset !important}.sd-fs-6,.sd-fs-6>p{font-size:1rem !important;line-height:unset !important}.sd-border-0{border:0 solid !important}.sd-border-top-0{border-top:0 solid !important}.sd-border-bottom-0{border-bottom:0 solid !important}.sd-border-right-0{border-right:0 solid !important}.sd-border-left-0{border-left:0 solid !important}.sd-border-1{border:1px solid !important}.sd-border-top-1{border-top:1px solid !important}.sd-border-bottom-1{border-bottom:1px solid !important}.sd-border-right-1{border-right:1px solid !important}.sd-border-left-1{border-left:1px solid !important}.sd-border-2{border:2px solid !important}.sd-border-top-2{border-top:2px solid !important}.sd-border-bottom-2{border-bottom:2px solid !important}.sd-border-right-2{border-right:2px solid !important}.sd-border-left-2{border-left:2px solid !important}.sd-border-3{border:3px solid !important}.sd-border-top-3{border-top:3px solid !important}.sd-border-bottom-3{border-bottom:3px solid !important}.sd-border-right-3{border-right:3px solid !important}.sd-border-left-3{border-left:3px solid !important}.sd-border-4{border:4px solid !important}.sd-border-top-4{border-top:4px solid !important}.sd-border-bottom-4{border-bottom:4px solid !important}.sd-border-right-4{border-right:4px solid !important}.sd-border-left-4{border-left:4px solid !important}.sd-border-5{border:5px solid !important}.sd-border-top-5{border-top:5px solid !important}.sd-border-bottom-5{border-bottom:5px solid !important}.sd-border-right-5{border-right:5px solid !important}.sd-border-left-5{border-left:5px solid !important}.sd-rounded-0{border-radius:0 !important}.sd-rounded-1{border-radius:.2rem !important}.sd-rounded-2{border-radius:.3rem !important}.sd-rounded-3{border-radius:.5rem !important}.sd-rounded-pill{border-radius:50rem !important}.sd-rounded-circle{border-radius:50% !important}.shadow-none{box-shadow:none !important}.sd-shadow-sm{box-shadow:0 .125rem .25rem var(--sd-color-shadow) !important}.sd-shadow-md{box-shadow:0 .5rem 1rem var(--sd-color-shadow) !important}.sd-shadow-lg{box-shadow:0 1rem 3rem var(--sd-color-shadow) !important}@keyframes sd-slide-from-left{0%{transform:translateX(-100%)}100%{transform:translateX(0)}}@keyframes sd-slide-from-right{0%{transform:translateX(200%)}100%{transform:translateX(0)}}@keyframes sd-grow100{0%{transform:scale(0);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50{0%{transform:scale(0.5);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50-rot20{0%{transform:scale(0.5) rotateZ(-20deg);opacity:.5}75%{transform:scale(1) rotateZ(5deg);opacity:1}95%{transform:scale(1) rotateZ(-1deg);opacity:1}100%{transform:scale(1) rotateZ(0);opacity:1}}.sd-animate-slide-from-left{animation:1s ease-out 0s 1 normal none running sd-slide-from-left}.sd-animate-slide-from-right{animation:1s ease-out 0s 1 normal none running sd-slide-from-right}.sd-animate-grow100{animation:1s ease-out 0s 1 normal none running sd-grow100}.sd-animate-grow50{animation:1s ease-out 0s 1 normal none running sd-grow50}.sd-animate-grow50-rot20{animation:1s ease-out 0s 1 normal none running sd-grow50-rot20}.sd-badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.sd-badge:empty{display:none}a.sd-badge{text-decoration:none}.sd-btn .sd-badge{position:relative;top:-1px}.sd-btn{background-color:transparent;border:1px solid transparent;border-radius:.25rem;cursor:pointer;display:inline-block;font-weight:400;font-size:1rem;line-height:1.5;padding:.375rem .75rem;text-align:center;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;vertical-align:middle;user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none}.sd-btn:hover{text-decoration:none}@media(prefers-reduced-motion: reduce){.sd-btn{transition:none}}.sd-btn-primary,.sd-btn-outline-primary:hover,.sd-btn-outline-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-primary:hover,.sd-btn-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary-highlight) !important;border-color:var(--sd-color-primary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-primary{color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary,.sd-btn-outline-secondary:hover,.sd-btn-outline-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary:hover,.sd-btn-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary-highlight) !important;border-color:var(--sd-color-secondary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-secondary{color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success,.sd-btn-outline-success:hover,.sd-btn-outline-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success:hover,.sd-btn-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success-highlight) !important;border-color:var(--sd-color-success-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-success{color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info,.sd-btn-outline-info:hover,.sd-btn-outline-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info:hover,.sd-btn-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info-highlight) !important;border-color:var(--sd-color-info-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-info{color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning,.sd-btn-outline-warning:hover,.sd-btn-outline-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning:hover,.sd-btn-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning-highlight) !important;border-color:var(--sd-color-warning-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-warning{color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger,.sd-btn-outline-danger:hover,.sd-btn-outline-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger:hover,.sd-btn-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger-highlight) !important;border-color:var(--sd-color-danger-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-danger{color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light,.sd-btn-outline-light:hover,.sd-btn-outline-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light:hover,.sd-btn-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light-highlight) !important;border-color:var(--sd-color-light-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-light{color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted,.sd-btn-outline-muted:hover,.sd-btn-outline-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted:hover,.sd-btn-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted-highlight) !important;border-color:var(--sd-color-muted-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-muted{color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark,.sd-btn-outline-dark:hover,.sd-btn-outline-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark:hover,.sd-btn-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark-highlight) !important;border-color:var(--sd-color-dark-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-dark{color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black,.sd-btn-outline-black:hover,.sd-btn-outline-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black:hover,.sd-btn-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black-highlight) !important;border-color:var(--sd-color-black-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-black{color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white,.sd-btn-outline-white:hover,.sd-btn-outline-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white:hover,.sd-btn-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white-highlight) !important;border-color:var(--sd-color-white-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-white{color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.sd-hide-link-text{font-size:0}.sd-octicon,.sd-material-icon{display:inline-block;fill:currentColor;vertical-align:middle}.sd-avatar-xs{border-radius:50%;object-fit:cover;object-position:center;width:1rem;height:1rem}.sd-avatar-sm{border-radius:50%;object-fit:cover;object-position:center;width:3rem;height:3rem}.sd-avatar-md{border-radius:50%;object-fit:cover;object-position:center;width:5rem;height:5rem}.sd-avatar-lg{border-radius:50%;object-fit:cover;object-position:center;width:7rem;height:7rem}.sd-avatar-xl{border-radius:50%;object-fit:cover;object-position:center;width:10rem;height:10rem}.sd-avatar-inherit{border-radius:50%;object-fit:cover;object-position:center;width:inherit;height:inherit}.sd-avatar-initial{border-radius:50%;object-fit:cover;object-position:center;width:initial;height:initial}.sd-card{background-clip:border-box;background-color:var(--sd-color-card-background);border:1px solid var(--sd-color-card-border);border-radius:.25rem;color:var(--sd-color-card-text);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;position:relative;word-wrap:break-word}.sd-card>hr{margin-left:0;margin-right:0}.sd-card-hover:hover{border-color:var(--sd-color-card-border-hover);transform:scale(1.01)}.sd-card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem 1rem}.sd-card-title{margin-bottom:.5rem}.sd-card-subtitle{margin-top:-0.25rem;margin-bottom:0}.sd-card-text:last-child{margin-bottom:0}.sd-card-link:hover{text-decoration:none}.sd-card-link+.card-link{margin-left:1rem}.sd-card-header{padding:.5rem 1rem;margin-bottom:0;background-color:var(--sd-color-card-header);border-bottom:1px solid var(--sd-color-card-border)}.sd-card-header:first-child{border-radius:calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0}.sd-card-footer{padding:.5rem 1rem;background-color:var(--sd-color-card-footer);border-top:1px solid var(--sd-color-card-border)}.sd-card-footer:last-child{border-radius:0 0 calc(0.25rem - 1px) calc(0.25rem - 1px)}.sd-card-header-tabs{margin-right:-0.5rem;margin-bottom:-0.5rem;margin-left:-0.5rem;border-bottom:0}.sd-card-header-pills{margin-right:-0.5rem;margin-left:-0.5rem}.sd-card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom,.sd-card-img-top{width:100%}.sd-card-img,.sd-card-img-top{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom{border-bottom-left-radius:calc(0.25rem - 1px);border-bottom-right-radius:calc(0.25rem - 1px)}.sd-cards-carousel{width:100%;display:flex;flex-wrap:nowrap;-ms-flex-direction:row;flex-direction:row;overflow-x:hidden;scroll-snap-type:x mandatory}.sd-cards-carousel.sd-show-scrollbar{overflow-x:auto}.sd-cards-carousel:hover,.sd-cards-carousel:focus{overflow-x:auto}.sd-cards-carousel>.sd-card{flex-shrink:0;scroll-snap-align:start}.sd-cards-carousel>.sd-card:not(:last-child){margin-right:3px}.sd-card-cols-1>.sd-card{width:90%}.sd-card-cols-2>.sd-card{width:45%}.sd-card-cols-3>.sd-card{width:30%}.sd-card-cols-4>.sd-card{width:22.5%}.sd-card-cols-5>.sd-card{width:18%}.sd-card-cols-6>.sd-card{width:15%}.sd-card-cols-7>.sd-card{width:12.8571428571%}.sd-card-cols-8>.sd-card{width:11.25%}.sd-card-cols-9>.sd-card{width:10%}.sd-card-cols-10>.sd-card{width:9%}.sd-card-cols-11>.sd-card{width:8.1818181818%}.sd-card-cols-12>.sd-card{width:7.5%}.sd-container,.sd-container-fluid,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container-xl{margin-left:auto;margin-right:auto;padding-left:var(--sd-gutter-x, 0.75rem);padding-right:var(--sd-gutter-x, 0.75rem);width:100%}@media(min-width: 576px){.sd-container-sm,.sd-container{max-width:540px}}@media(min-width: 768px){.sd-container-md,.sd-container-sm,.sd-container{max-width:720px}}@media(min-width: 992px){.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:960px}}@media(min-width: 1200px){.sd-container-xl,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:1140px}}.sd-row{--sd-gutter-x: 1.5rem;--sd-gutter-y: 0;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-top:calc(var(--sd-gutter-y) * -1);margin-right:calc(var(--sd-gutter-x) * -0.5);margin-left:calc(var(--sd-gutter-x) * -0.5)}.sd-row>*{box-sizing:border-box;flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--sd-gutter-x) * 0.5);padding-left:calc(var(--sd-gutter-x) * 0.5);margin-top:var(--sd-gutter-y)}.sd-col{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-auto>*{flex:0 0 auto;width:auto}.sd-row-cols-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}@media(min-width: 576px){.sd-col-sm{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-sm-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-sm-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-sm-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-sm-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-sm-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-sm-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-sm-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-sm-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-sm-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-sm-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-sm-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-sm-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-sm-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 768px){.sd-col-md{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-md-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-md-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-md-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-md-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-md-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-md-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-md-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-md-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-md-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-md-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-md-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-md-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-md-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 992px){.sd-col-lg{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-lg-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-lg-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-lg-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-lg-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-lg-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-lg-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-lg-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-lg-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-lg-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-lg-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-lg-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-lg-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-lg-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 1200px){.sd-col-xl{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-xl-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-xl-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-xl-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-xl-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-xl-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-xl-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-xl-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-xl-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-xl-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-xl-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-xl-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-xl-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-xl-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}.sd-col-auto{flex:0 0 auto;-ms-flex:0 0 auto;width:auto}.sd-col-1{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}.sd-col-2{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-col-3{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-col-4{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-col-5{flex:0 0 auto;-ms-flex:0 0 auto;width:41.6666666667%}.sd-col-6{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-col-7{flex:0 0 auto;-ms-flex:0 0 auto;width:58.3333333333%}.sd-col-8{flex:0 0 auto;-ms-flex:0 0 auto;width:66.6666666667%}.sd-col-9{flex:0 0 auto;-ms-flex:0 0 auto;width:75%}.sd-col-10{flex:0 0 auto;-ms-flex:0 0 auto;width:83.3333333333%}.sd-col-11{flex:0 0 auto;-ms-flex:0 0 auto;width:91.6666666667%}.sd-col-12{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-g-0,.sd-gy-0{--sd-gutter-y: 0}.sd-g-0,.sd-gx-0{--sd-gutter-x: 0}.sd-g-1,.sd-gy-1{--sd-gutter-y: 0.25rem}.sd-g-1,.sd-gx-1{--sd-gutter-x: 0.25rem}.sd-g-2,.sd-gy-2{--sd-gutter-y: 0.5rem}.sd-g-2,.sd-gx-2{--sd-gutter-x: 0.5rem}.sd-g-3,.sd-gy-3{--sd-gutter-y: 1rem}.sd-g-3,.sd-gx-3{--sd-gutter-x: 1rem}.sd-g-4,.sd-gy-4{--sd-gutter-y: 1.5rem}.sd-g-4,.sd-gx-4{--sd-gutter-x: 1.5rem}.sd-g-5,.sd-gy-5{--sd-gutter-y: 3rem}.sd-g-5,.sd-gx-5{--sd-gutter-x: 3rem}@media(min-width: 576px){.sd-col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-sm-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-sm-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-sm-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-sm-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-sm-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-sm-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-sm-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-sm-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-sm-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-sm-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-sm-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-sm-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-sm-0,.sd-gy-sm-0{--sd-gutter-y: 0}.sd-g-sm-0,.sd-gx-sm-0{--sd-gutter-x: 0}.sd-g-sm-1,.sd-gy-sm-1{--sd-gutter-y: 0.25rem}.sd-g-sm-1,.sd-gx-sm-1{--sd-gutter-x: 0.25rem}.sd-g-sm-2,.sd-gy-sm-2{--sd-gutter-y: 0.5rem}.sd-g-sm-2,.sd-gx-sm-2{--sd-gutter-x: 0.5rem}.sd-g-sm-3,.sd-gy-sm-3{--sd-gutter-y: 1rem}.sd-g-sm-3,.sd-gx-sm-3{--sd-gutter-x: 1rem}.sd-g-sm-4,.sd-gy-sm-4{--sd-gutter-y: 1.5rem}.sd-g-sm-4,.sd-gx-sm-4{--sd-gutter-x: 1.5rem}.sd-g-sm-5,.sd-gy-sm-5{--sd-gutter-y: 3rem}.sd-g-sm-5,.sd-gx-sm-5{--sd-gutter-x: 3rem}}@media(min-width: 768px){.sd-col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-md-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-md-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-md-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-md-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-md-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-md-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-md-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-md-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-md-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-md-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-md-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-md-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-md-0,.sd-gy-md-0{--sd-gutter-y: 0}.sd-g-md-0,.sd-gx-md-0{--sd-gutter-x: 0}.sd-g-md-1,.sd-gy-md-1{--sd-gutter-y: 0.25rem}.sd-g-md-1,.sd-gx-md-1{--sd-gutter-x: 0.25rem}.sd-g-md-2,.sd-gy-md-2{--sd-gutter-y: 0.5rem}.sd-g-md-2,.sd-gx-md-2{--sd-gutter-x: 0.5rem}.sd-g-md-3,.sd-gy-md-3{--sd-gutter-y: 1rem}.sd-g-md-3,.sd-gx-md-3{--sd-gutter-x: 1rem}.sd-g-md-4,.sd-gy-md-4{--sd-gutter-y: 1.5rem}.sd-g-md-4,.sd-gx-md-4{--sd-gutter-x: 1.5rem}.sd-g-md-5,.sd-gy-md-5{--sd-gutter-y: 3rem}.sd-g-md-5,.sd-gx-md-5{--sd-gutter-x: 3rem}}@media(min-width: 992px){.sd-col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-lg-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-lg-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-lg-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-lg-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-lg-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-lg-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-lg-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-lg-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-lg-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-lg-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-lg-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-lg-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-lg-0,.sd-gy-lg-0{--sd-gutter-y: 0}.sd-g-lg-0,.sd-gx-lg-0{--sd-gutter-x: 0}.sd-g-lg-1,.sd-gy-lg-1{--sd-gutter-y: 0.25rem}.sd-g-lg-1,.sd-gx-lg-1{--sd-gutter-x: 0.25rem}.sd-g-lg-2,.sd-gy-lg-2{--sd-gutter-y: 0.5rem}.sd-g-lg-2,.sd-gx-lg-2{--sd-gutter-x: 0.5rem}.sd-g-lg-3,.sd-gy-lg-3{--sd-gutter-y: 1rem}.sd-g-lg-3,.sd-gx-lg-3{--sd-gutter-x: 1rem}.sd-g-lg-4,.sd-gy-lg-4{--sd-gutter-y: 1.5rem}.sd-g-lg-4,.sd-gx-lg-4{--sd-gutter-x: 1.5rem}.sd-g-lg-5,.sd-gy-lg-5{--sd-gutter-y: 3rem}.sd-g-lg-5,.sd-gx-lg-5{--sd-gutter-x: 3rem}}@media(min-width: 1200px){.sd-col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-xl-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-xl-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-xl-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-xl-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-xl-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-xl-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-xl-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-xl-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-xl-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-xl-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-xl-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-xl-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-xl-0,.sd-gy-xl-0{--sd-gutter-y: 0}.sd-g-xl-0,.sd-gx-xl-0{--sd-gutter-x: 0}.sd-g-xl-1,.sd-gy-xl-1{--sd-gutter-y: 0.25rem}.sd-g-xl-1,.sd-gx-xl-1{--sd-gutter-x: 0.25rem}.sd-g-xl-2,.sd-gy-xl-2{--sd-gutter-y: 0.5rem}.sd-g-xl-2,.sd-gx-xl-2{--sd-gutter-x: 0.5rem}.sd-g-xl-3,.sd-gy-xl-3{--sd-gutter-y: 1rem}.sd-g-xl-3,.sd-gx-xl-3{--sd-gutter-x: 1rem}.sd-g-xl-4,.sd-gy-xl-4{--sd-gutter-y: 1.5rem}.sd-g-xl-4,.sd-gx-xl-4{--sd-gutter-x: 1.5rem}.sd-g-xl-5,.sd-gy-xl-5{--sd-gutter-y: 3rem}.sd-g-xl-5,.sd-gx-xl-5{--sd-gutter-x: 3rem}}.sd-flex-row-reverse{flex-direction:row-reverse !important}details.sd-dropdown{position:relative;font-size:var(--sd-fontsize-dropdown)}details.sd-dropdown:hover{cursor:pointer}details.sd-dropdown .sd-summary-content{cursor:default}details.sd-dropdown summary.sd-summary-title{padding:.5em .6em .5em 1em;font-size:var(--sd-fontsize-dropdown-title);font-weight:var(--sd-fontweight-dropdown-title);user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;list-style:none;display:inline-flex;justify-content:space-between}details.sd-dropdown summary.sd-summary-title::-webkit-details-marker{display:none}details.sd-dropdown summary.sd-summary-title:focus{outline:none}details.sd-dropdown summary.sd-summary-title .sd-summary-icon{margin-right:.6em;display:inline-flex;align-items:center}details.sd-dropdown summary.sd-summary-title .sd-summary-icon svg{opacity:.8}details.sd-dropdown summary.sd-summary-title .sd-summary-text{flex-grow:1;line-height:1.5;padding-right:.5rem}details.sd-dropdown summary.sd-summary-title .sd-summary-state-marker{pointer-events:none;display:inline-flex;align-items:center}details.sd-dropdown summary.sd-summary-title .sd-summary-state-marker svg{opacity:.6}details.sd-dropdown summary.sd-summary-title:hover .sd-summary-state-marker svg{opacity:1;transform:scale(1.1)}details.sd-dropdown[open] summary .sd-octicon.no-title{visibility:hidden}details.sd-dropdown .sd-summary-chevron-right{transition:.25s}details.sd-dropdown[open]>.sd-summary-title .sd-summary-chevron-right{transform:rotate(90deg)}details.sd-dropdown[open]>.sd-summary-title .sd-summary-chevron-down{transform:rotate(180deg)}details.sd-dropdown:not([open]).sd-card{border:none}details.sd-dropdown:not([open])>.sd-card-header{border:1px solid var(--sd-color-card-border);border-radius:.25rem}details.sd-dropdown.sd-fade-in[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out;animation:sd-fade-in .5s ease-in-out}details.sd-dropdown.sd-fade-in-slide-down[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out}.sd-col>.sd-dropdown{width:100%}.sd-summary-content>.sd-tab-set:first-child{margin-top:0}@keyframes sd-fade-in{0%{opacity:0}100%{opacity:1}}@keyframes sd-slide-down{0%{transform:translate(0, -10px)}100%{transform:translate(0, 0)}}.sd-tab-set{border-radius:.125rem;display:flex;flex-wrap:wrap;margin:1em 0;position:relative}.sd-tab-set>input{opacity:0;position:absolute}.sd-tab-set>input:checked+label{border-color:var(--sd-color-tabs-underline-active);color:var(--sd-color-tabs-label-active)}.sd-tab-set>input:checked+label+.sd-tab-content{display:block}.sd-tab-set>input:not(:checked)+label:hover{color:var(--sd-color-tabs-label-hover);border-color:var(--sd-color-tabs-underline-hover)}.sd-tab-set>input:focus+label{outline-style:auto}.sd-tab-set>input:not(.focus-visible)+label{outline:none;-webkit-tap-highlight-color:transparent}.sd-tab-set>label{border-bottom:.125rem solid transparent;margin-bottom:0;color:var(--sd-color-tabs-label-inactive);border-color:var(--sd-color-tabs-underline-inactive);cursor:pointer;font-size:var(--sd-fontsize-tabs-label);font-weight:700;padding:1em 1.25em .5em;transition:color 250ms;width:auto;z-index:1}html .sd-tab-set>label:hover{color:var(--sd-color-tabs-label-active)}.sd-col>.sd-tab-set{width:100%}.sd-tab-content{box-shadow:0 -0.0625rem var(--sd-color-tabs-overline),0 .0625rem var(--sd-color-tabs-underline);display:none;order:99;padding-bottom:.75rem;padding-top:.75rem;width:100%}.sd-tab-content>:first-child{margin-top:0 !important}.sd-tab-content>:last-child{margin-bottom:0 !important}.sd-tab-content>.sd-tab-set{margin:0}.sd-sphinx-override,.sd-sphinx-override *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.sd-sphinx-override p{margin-top:0}:root{--sd-color-primary: #0071bc;--sd-color-secondary: #6c757d;--sd-color-success: #28a745;--sd-color-info: #17a2b8;--sd-color-warning: #f0b37e;--sd-color-danger: #dc3545;--sd-color-light: #f8f9fa;--sd-color-muted: #6c757d;--sd-color-dark: #212529;--sd-color-black: black;--sd-color-white: white;--sd-color-primary-highlight: #0060a0;--sd-color-secondary-highlight: #5c636a;--sd-color-success-highlight: #228e3b;--sd-color-info-highlight: #148a9c;--sd-color-warning-highlight: #cc986b;--sd-color-danger-highlight: #bb2d3b;--sd-color-light-highlight: #d3d4d5;--sd-color-muted-highlight: #5c636a;--sd-color-dark-highlight: #1c1f23;--sd-color-black-highlight: black;--sd-color-white-highlight: #d9d9d9;--sd-color-primary-bg: rgba(0, 113, 188, 0.2);--sd-color-secondary-bg: rgba(108, 117, 125, 0.2);--sd-color-success-bg: rgba(40, 167, 69, 0.2);--sd-color-info-bg: rgba(23, 162, 184, 0.2);--sd-color-warning-bg: rgba(240, 179, 126, 0.2);--sd-color-danger-bg: rgba(220, 53, 69, 0.2);--sd-color-light-bg: rgba(248, 249, 250, 0.2);--sd-color-muted-bg: rgba(108, 117, 125, 0.2);--sd-color-dark-bg: rgba(33, 37, 41, 0.2);--sd-color-black-bg: rgba(0, 0, 0, 0.2);--sd-color-white-bg: rgba(255, 255, 255, 0.2);--sd-color-primary-text: #fff;--sd-color-secondary-text: #fff;--sd-color-success-text: #fff;--sd-color-info-text: #fff;--sd-color-warning-text: #212529;--sd-color-danger-text: #fff;--sd-color-light-text: #212529;--sd-color-muted-text: #fff;--sd-color-dark-text: #fff;--sd-color-black-text: #fff;--sd-color-white-text: #212529;--sd-color-shadow: rgba(0, 0, 0, 0.15);--sd-color-card-border: rgba(0, 0, 0, 0.125);--sd-color-card-border-hover: hsla(231, 99%, 66%, 1);--sd-color-card-background: transparent;--sd-color-card-text: inherit;--sd-color-card-header: transparent;--sd-color-card-footer: transparent;--sd-color-tabs-label-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-hover: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-inactive: hsl(0, 0%, 66%);--sd-color-tabs-underline-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-underline-hover: rgba(178, 206, 245, 0.62);--sd-color-tabs-underline-inactive: transparent;--sd-color-tabs-overline: rgb(222, 222, 222);--sd-color-tabs-underline: rgb(222, 222, 222);--sd-fontsize-tabs-label: 1rem;--sd-fontsize-dropdown: inherit;--sd-fontsize-dropdown-title: 1rem;--sd-fontweight-dropdown-title: 700} diff --git a/docs/_static/_sphinx_javascript_frameworks_compat.js b/docs/_static/_sphinx_javascript_frameworks_compat.js deleted file mode 100644 index 8141580..0000000 --- a/docs/_static/_sphinx_javascript_frameworks_compat.js +++ /dev/null @@ -1,123 +0,0 @@ -/* Compatability shim for jQuery and underscores.js. - * - * Copyright Sphinx contributors - * Released under the two clause BSD licence - */ - -/** - * small helper function to urldecode strings - * - * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL - */ -jQuery.urldecode = function(x) { - if (!x) { - return x - } - return decodeURIComponent(x.replace(/\+/g, ' ')); -}; - -/** - * small helper function to urlencode strings - */ -jQuery.urlencode = encodeURIComponent; - -/** - * This function returns the parsed url parameters of the - * current request. Multiple values per key are supported, - * it will always return arrays of strings for the value parts. - */ -jQuery.getQueryParameters = function(s) { - if (typeof s === 'undefined') - s = document.location.search; - var parts = s.substr(s.indexOf('?') + 1).split('&'); - var result = {}; - for (var i = 0; i < parts.length; i++) { - var tmp = parts[i].split('=', 2); - var key = jQuery.urldecode(tmp[0]); - var value = jQuery.urldecode(tmp[1]); - if (key in result) - result[key].push(value); - else - result[key] = [value]; - } - return result; -}; - -/** - * highlight a given string on a jquery object by wrapping it in - * span elements with the given class name. - */ -jQuery.fn.highlightText = function(text, className) { - function highlight(node, addItems) { - if (node.nodeType === 3) { - var val = node.nodeValue; - var pos = val.toLowerCase().indexOf(text); - if (pos >= 0 && - !jQuery(node.parentNode).hasClass(className) && - !jQuery(node.parentNode).hasClass("nohighlight")) { - var span; - var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); - if (isInSVG) { - span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); - } else { - span = document.createElement("span"); - span.className = className; - } - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - node.parentNode.insertBefore(span, node.parentNode.insertBefore( - document.createTextNode(val.substr(pos + text.length)), - node.nextSibling)); - node.nodeValue = val.substr(0, pos); - if (isInSVG) { - var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); - var bbox = node.parentElement.getBBox(); - rect.x.baseVal.value = bbox.x; - rect.y.baseVal.value = bbox.y; - rect.width.baseVal.value = bbox.width; - rect.height.baseVal.value = bbox.height; - rect.setAttribute('class', className); - addItems.push({ - "parent": node.parentNode, - "target": rect}); - } - } - } - else if (!jQuery(node).is("button, select, textarea")) { - jQuery.each(node.childNodes, function() { - highlight(this, addItems); - }); - } - } - var addItems = []; - var result = this.each(function() { - highlight(this, addItems); - }); - for (var i = 0; i < addItems.length; ++i) { - jQuery(addItems[i].parent).before(addItems[i].target); - } - return result; -}; - -/* - * backward compatibility for jQuery.browser - * This will be supported until firefox bug is fixed. - */ -if (!jQuery.browser) { - jQuery.uaMatch = function(ua) { - ua = ua.toLowerCase(); - - var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || - /(webkit)[ \/]([\w.]+)/.exec(ua) || - /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || - /(msie) ([\w.]+)/.exec(ua) || - ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || - []; - - return { - browser: match[ 1 ] || "", - version: match[ 2 ] || "0" - }; - }; - jQuery.browser = {}; - jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; -} diff --git a/docs/_static/check-solid.svg b/docs/_static/check-solid.svg new file mode 100644 index 0000000..92fad4b --- /dev/null +++ b/docs/_static/check-solid.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/_static/clipboard.min.js b/docs/_static/clipboard.min.js new file mode 100644 index 0000000..54b3c46 --- /dev/null +++ b/docs/_static/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.8 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return o}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),c=n.n(e);function a(t){try{return document.execCommand(t)}catch(t){return}}var f=function(t){t=c()(t);return a("cut"),t};var l=function(t){var e,n,o,r=1 + + + + diff --git a/docs/_static/copybutton.css b/docs/_static/copybutton.css new file mode 100644 index 0000000..f1916ec --- /dev/null +++ b/docs/_static/copybutton.css @@ -0,0 +1,94 @@ +/* Copy buttons */ +button.copybtn { + position: absolute; + display: flex; + top: .3em; + right: .3em; + width: 1.7em; + height: 1.7em; + opacity: 0; + transition: opacity 0.3s, border .3s, background-color .3s; + user-select: none; + padding: 0; + border: none; + outline: none; + border-radius: 0.4em; + /* The colors that GitHub uses */ + border: #1b1f2426 1px solid; + background-color: #f6f8fa; + color: #57606a; +} + +button.copybtn.success { + border-color: #22863a; + color: #22863a; +} + +button.copybtn svg { + stroke: currentColor; + width: 1.5em; + height: 1.5em; + padding: 0.1em; +} + +div.highlight { + position: relative; +} + +/* Show the copybutton */ +.highlight:hover button.copybtn, button.copybtn.success { + opacity: 1; +} + +.highlight button.copybtn:hover { + background-color: rgb(235, 235, 235); +} + +.highlight button.copybtn:active { + background-color: rgb(187, 187, 187); +} + +/** + * A minimal CSS-only tooltip copied from: + * https://codepen.io/mildrenben/pen/rVBrpK + * + * To use, write HTML like the following: + * + *

Short

+ */ + .o-tooltip--left { + position: relative; + } + + .o-tooltip--left:after { + opacity: 0; + visibility: hidden; + position: absolute; + content: attr(data-tooltip); + padding: .2em; + font-size: .8em; + left: -.2em; + background: grey; + color: white; + white-space: nowrap; + z-index: 2; + border-radius: 2px; + transform: translateX(-102%) translateY(0); + transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); +} + +.o-tooltip--left:hover:after { + display: block; + opacity: 1; + visibility: visible; + transform: translateX(-100%) translateY(0); + transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); + transition-delay: .5s; +} + +/* By default the copy button shouldn't show up when printing a page */ +@media print { + button.copybtn { + display: none; + } +} diff --git a/docs/_static/copybutton.js b/docs/_static/copybutton.js new file mode 100644 index 0000000..2ea7ff3 --- /dev/null +++ b/docs/_static/copybutton.js @@ -0,0 +1,248 @@ +// Localization support +const messages = { + 'en': { + 'copy': 'Copy', + 'copy_to_clipboard': 'Copy to clipboard', + 'copy_success': 'Copied!', + 'copy_failure': 'Failed to copy', + }, + 'es' : { + 'copy': 'Copiar', + 'copy_to_clipboard': 'Copiar al portapapeles', + 'copy_success': '¡Copiado!', + 'copy_failure': 'Error al copiar', + }, + 'de' : { + 'copy': 'Kopieren', + 'copy_to_clipboard': 'In die Zwischenablage kopieren', + 'copy_success': 'Kopiert!', + 'copy_failure': 'Fehler beim Kopieren', + }, + 'fr' : { + 'copy': 'Copier', + 'copy_to_clipboard': 'Copier dans le presse-papier', + 'copy_success': 'Copié !', + 'copy_failure': 'Échec de la copie', + }, + 'ru': { + 'copy': 'Скопировать', + 'copy_to_clipboard': 'Скопировать в буфер', + 'copy_success': 'Скопировано!', + 'copy_failure': 'Не удалось скопировать', + }, + 'zh-CN': { + 'copy': '复制', + 'copy_to_clipboard': '复制到剪贴板', + 'copy_success': '复制成功!', + 'copy_failure': '复制失败', + }, + 'it' : { + 'copy': 'Copiare', + 'copy_to_clipboard': 'Copiato negli appunti', + 'copy_success': 'Copiato!', + 'copy_failure': 'Errore durante la copia', + } +} + +let locale = 'en' +if( document.documentElement.lang !== undefined + && messages[document.documentElement.lang] !== undefined ) { + locale = document.documentElement.lang +} + +let doc_url_root = DOCUMENTATION_OPTIONS.URL_ROOT; +if (doc_url_root == '#') { + doc_url_root = ''; +} + +/** + * SVG files for our copy buttons + */ +let iconCheck = ` + ${messages[locale]['copy_success']} + + +` + +// If the user specified their own SVG use that, otherwise use the default +let iconCopy = ``; +if (!iconCopy) { + iconCopy = ` + ${messages[locale]['copy_to_clipboard']} + + + +` +} + +/** + * Set up copy/paste for code blocks + */ + +const runWhenDOMLoaded = cb => { + if (document.readyState != 'loading') { + cb() + } else if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', cb) + } else { + document.attachEvent('onreadystatechange', function() { + if (document.readyState == 'complete') cb() + }) + } +} + +const codeCellId = index => `codecell${index}` + +// Clears selected text since ClipboardJS will select the text when copying +const clearSelection = () => { + if (window.getSelection) { + window.getSelection().removeAllRanges() + } else if (document.selection) { + document.selection.empty() + } +} + +// Changes tooltip text for a moment, then changes it back +// We want the timeout of our `success` class to be a bit shorter than the +// tooltip and icon change, so that we can hide the icon before changing back. +var timeoutIcon = 2000; +var timeoutSuccessClass = 1500; + +const temporarilyChangeTooltip = (el, oldText, newText) => { + el.setAttribute('data-tooltip', newText) + el.classList.add('success') + // Remove success a little bit sooner than we change the tooltip + // So that we can use CSS to hide the copybutton first + setTimeout(() => el.classList.remove('success'), timeoutSuccessClass) + setTimeout(() => el.setAttribute('data-tooltip', oldText), timeoutIcon) +} + +// Changes the copy button icon for two seconds, then changes it back +const temporarilyChangeIcon = (el) => { + el.innerHTML = iconCheck; + setTimeout(() => {el.innerHTML = iconCopy}, timeoutIcon) +} + +const addCopyButtonToCodeCells = () => { + // If ClipboardJS hasn't loaded, wait a bit and try again. This + // happens because we load ClipboardJS asynchronously. + if (window.ClipboardJS === undefined) { + setTimeout(addCopyButtonToCodeCells, 250) + return + } + + // Add copybuttons to all of our code cells + const COPYBUTTON_SELECTOR = 'div.highlight pre'; + const codeCells = document.querySelectorAll(COPYBUTTON_SELECTOR) + codeCells.forEach((codeCell, index) => { + const id = codeCellId(index) + codeCell.setAttribute('id', id) + + const clipboardButton = id => + `` + codeCell.insertAdjacentHTML('afterend', clipboardButton(id)) + }) + +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +/** + * Removes excluded text from a Node. + * + * @param {Node} target Node to filter. + * @param {string} exclude CSS selector of nodes to exclude. + * @returns {DOMString} Text from `target` with text removed. + */ +function filterText(target, exclude) { + const clone = target.cloneNode(true); // clone as to not modify the live DOM + if (exclude) { + // remove excluded nodes + clone.querySelectorAll(exclude).forEach(node => node.remove()); + } + return clone.innerText; +} + +// Callback when a copy button is clicked. Will be passed the node that was clicked +// should then grab the text and replace pieces of text that shouldn't be used in output +function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { + var regexp; + var match; + + // Do we check for line continuation characters and "HERE-documents"? + var useLineCont = !!lineContinuationChar + var useHereDoc = !!hereDocDelim + + // create regexp to capture prompt and remaining line + if (isRegexp) { + regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') + } else { + regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') + } + + const outputLines = []; + var promptFound = false; + var gotLineCont = false; + var gotHereDoc = false; + const lineGotPrompt = []; + for (const line of textContent.split('\n')) { + match = line.match(regexp) + if (match || gotLineCont || gotHereDoc) { + promptFound = regexp.test(line) + lineGotPrompt.push(promptFound) + if (removePrompts && promptFound) { + outputLines.push(match[2]) + } else { + outputLines.push(line) + } + gotLineCont = line.endsWith(lineContinuationChar) & useLineCont + if (line.includes(hereDocDelim) & useHereDoc) + gotHereDoc = !gotHereDoc + } else if (!onlyCopyPromptLines) { + outputLines.push(line) + } else if (copyEmptyLines && line.trim() === '') { + outputLines.push(line) + } + } + + // If no lines with the prompt were found then just use original lines + if (lineGotPrompt.some(v => v === true)) { + textContent = outputLines.join('\n'); + } + + // Remove a trailing newline to avoid auto-running when pasting + if (textContent.endsWith("\n")) { + textContent = textContent.slice(0, -1) + } + return textContent +} + + +var copyTargetText = (trigger) => { + var target = document.querySelector(trigger.attributes['data-clipboard-target'].value); + + // get filtered text + let exclude = '.linenos'; + + let text = filterText(target, exclude); + return formatCopyText(text, '', false, true, true, true, '', '') +} + + // Initialize with a callback so we can modify the text before copy + const clipboard = new ClipboardJS('.copybtn', {text: copyTargetText}) + + // Update UI with error/success messages + clipboard.on('success', event => { + clearSelection() + temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_success']) + temporarilyChangeIcon(event.trigger) + }) + + clipboard.on('error', event => { + temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_failure']) + }) +} + +runWhenDOMLoaded(addCopyButtonToCodeCells) \ No newline at end of file diff --git a/docs/_static/copybutton_funcs.js b/docs/_static/copybutton_funcs.js new file mode 100644 index 0000000..dbe1aaa --- /dev/null +++ b/docs/_static/copybutton_funcs.js @@ -0,0 +1,73 @@ +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +/** + * Removes excluded text from a Node. + * + * @param {Node} target Node to filter. + * @param {string} exclude CSS selector of nodes to exclude. + * @returns {DOMString} Text from `target` with text removed. + */ +export function filterText(target, exclude) { + const clone = target.cloneNode(true); // clone as to not modify the live DOM + if (exclude) { + // remove excluded nodes + clone.querySelectorAll(exclude).forEach(node => node.remove()); + } + return clone.innerText; +} + +// Callback when a copy button is clicked. Will be passed the node that was clicked +// should then grab the text and replace pieces of text that shouldn't be used in output +export function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { + var regexp; + var match; + + // Do we check for line continuation characters and "HERE-documents"? + var useLineCont = !!lineContinuationChar + var useHereDoc = !!hereDocDelim + + // create regexp to capture prompt and remaining line + if (isRegexp) { + regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') + } else { + regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') + } + + const outputLines = []; + var promptFound = false; + var gotLineCont = false; + var gotHereDoc = false; + const lineGotPrompt = []; + for (const line of textContent.split('\n')) { + match = line.match(regexp) + if (match || gotLineCont || gotHereDoc) { + promptFound = regexp.test(line) + lineGotPrompt.push(promptFound) + if (removePrompts && promptFound) { + outputLines.push(match[2]) + } else { + outputLines.push(line) + } + gotLineCont = line.endsWith(lineContinuationChar) & useLineCont + if (line.includes(hereDocDelim) & useHereDoc) + gotHereDoc = !gotHereDoc + } else if (!onlyCopyPromptLines) { + outputLines.push(line) + } else if (copyEmptyLines && line.trim() === '') { + outputLines.push(line) + } + } + + // If no lines with the prompt were found then just use original lines + if (lineGotPrompt.some(v => v === true)) { + textContent = outputLines.join('\n'); + } + + // Remove a trailing newline to avoid auto-running when pasting + if (textContent.endsWith("\n")) { + textContent = textContent.slice(0, -1) + } + return textContent +} diff --git a/docs/_static/css/badge_only.css b/docs/_static/css/badge_only.css deleted file mode 100644 index 88ba55b..0000000 --- a/docs/_static/css/badge_only.css +++ /dev/null @@ -1 +0,0 @@ -.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px} \ No newline at end of file diff --git a/docs/_static/css/fonts/Roboto-Slab-Bold.woff b/docs/_static/css/fonts/Roboto-Slab-Bold.woff deleted file mode 100644 index 6cb6000..0000000 Binary files a/docs/_static/css/fonts/Roboto-Slab-Bold.woff and /dev/null differ diff --git a/docs/_static/css/fonts/Roboto-Slab-Bold.woff2 b/docs/_static/css/fonts/Roboto-Slab-Bold.woff2 deleted file mode 100644 index 7059e23..0000000 Binary files a/docs/_static/css/fonts/Roboto-Slab-Bold.woff2 and /dev/null differ diff --git a/docs/_static/css/fonts/Roboto-Slab-Regular.woff b/docs/_static/css/fonts/Roboto-Slab-Regular.woff deleted file mode 100644 index f815f63..0000000 Binary files a/docs/_static/css/fonts/Roboto-Slab-Regular.woff and /dev/null differ diff --git a/docs/_static/css/fonts/Roboto-Slab-Regular.woff2 b/docs/_static/css/fonts/Roboto-Slab-Regular.woff2 deleted file mode 100644 index f2c76e5..0000000 Binary files a/docs/_static/css/fonts/Roboto-Slab-Regular.woff2 and /dev/null differ diff --git a/docs/_static/css/fonts/fontawesome-webfont.eot b/docs/_static/css/fonts/fontawesome-webfont.eot deleted file mode 100644 index e9f60ca..0000000 Binary files a/docs/_static/css/fonts/fontawesome-webfont.eot and /dev/null differ diff --git a/docs/_static/css/fonts/fontawesome-webfont.svg b/docs/_static/css/fonts/fontawesome-webfont.svg deleted file mode 100644 index 855c845..0000000 --- a/docs/_static/css/fonts/fontawesome-webfont.svg +++ /dev/null @@ -1,2671 +0,0 @@ - - - - -Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 - By ,,, -Copyright Dave Gandy 2016. All rights reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/_static/css/fonts/fontawesome-webfont.ttf b/docs/_static/css/fonts/fontawesome-webfont.ttf deleted file mode 100644 index 35acda2..0000000 Binary files a/docs/_static/css/fonts/fontawesome-webfont.ttf and /dev/null differ diff --git a/docs/_static/css/fonts/fontawesome-webfont.woff b/docs/_static/css/fonts/fontawesome-webfont.woff deleted file mode 100644 index 400014a..0000000 Binary files a/docs/_static/css/fonts/fontawesome-webfont.woff and /dev/null differ diff --git a/docs/_static/css/fonts/fontawesome-webfont.woff2 b/docs/_static/css/fonts/fontawesome-webfont.woff2 deleted file mode 100644 index 4d13fc6..0000000 Binary files a/docs/_static/css/fonts/fontawesome-webfont.woff2 and /dev/null differ diff --git a/docs/_static/css/fonts/lato-bold-italic.woff b/docs/_static/css/fonts/lato-bold-italic.woff deleted file mode 100644 index 88ad05b..0000000 Binary files a/docs/_static/css/fonts/lato-bold-italic.woff and /dev/null differ diff --git a/docs/_static/css/fonts/lato-bold-italic.woff2 b/docs/_static/css/fonts/lato-bold-italic.woff2 deleted file mode 100644 index c4e3d80..0000000 Binary files a/docs/_static/css/fonts/lato-bold-italic.woff2 and /dev/null differ diff --git a/docs/_static/css/fonts/lato-bold.woff b/docs/_static/css/fonts/lato-bold.woff deleted file mode 100644 index c6dff51..0000000 Binary files a/docs/_static/css/fonts/lato-bold.woff and /dev/null differ diff --git a/docs/_static/css/fonts/lato-bold.woff2 b/docs/_static/css/fonts/lato-bold.woff2 deleted file mode 100644 index bb19504..0000000 Binary files a/docs/_static/css/fonts/lato-bold.woff2 and /dev/null differ diff --git a/docs/_static/css/fonts/lato-normal-italic.woff b/docs/_static/css/fonts/lato-normal-italic.woff deleted file mode 100644 index 76114bc..0000000 Binary files a/docs/_static/css/fonts/lato-normal-italic.woff and /dev/null differ diff --git a/docs/_static/css/fonts/lato-normal-italic.woff2 b/docs/_static/css/fonts/lato-normal-italic.woff2 deleted file mode 100644 index 3404f37..0000000 Binary files a/docs/_static/css/fonts/lato-normal-italic.woff2 and /dev/null differ diff --git a/docs/_static/css/fonts/lato-normal.woff b/docs/_static/css/fonts/lato-normal.woff deleted file mode 100644 index ae1307f..0000000 Binary files a/docs/_static/css/fonts/lato-normal.woff and /dev/null differ diff --git a/docs/_static/css/fonts/lato-normal.woff2 b/docs/_static/css/fonts/lato-normal.woff2 deleted file mode 100644 index 3bf9843..0000000 Binary files a/docs/_static/css/fonts/lato-normal.woff2 and /dev/null differ diff --git a/docs/_static/css/theme.css b/docs/_static/css/theme.css deleted file mode 100644 index a88467c..0000000 --- a/docs/_static/css/theme.css +++ /dev/null @@ -1,4 +0,0 @@ -html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! - * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search .wy-dropdown>aactive,.wy-side-nav-search .wy-dropdown>afocus,.wy-side-nav-search>a:hover,.wy-side-nav-search>aactive,.wy-side-nav-search>afocus{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon,.wy-side-nav-search>a.icon{display:block}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.switch-menus{position:relative;display:block;margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-side-nav-search>div.switch-menus>div.language-switch,.wy-side-nav-search>div.switch-menus>div.version-switch{display:inline-block;padding:.2em}.wy-side-nav-search>div.switch-menus>div.language-switch select,.wy-side-nav-search>div.switch-menus>div.version-switch select{display:inline-block;margin-right:-2rem;padding-right:2rem;max-width:240px;text-align-last:center;background:none;border:none;border-radius:0;box-shadow:none;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-size:1em;font-weight:400;color:hsla(0,0%,100%,.3);cursor:pointer;appearance:none;-webkit-appearance:none;-moz-appearance:none}.wy-side-nav-search>div.switch-menus>div.language-switch select:active,.wy-side-nav-search>div.switch-menus>div.language-switch select:focus,.wy-side-nav-search>div.switch-menus>div.language-switch select:hover,.wy-side-nav-search>div.switch-menus>div.version-switch select:active,.wy-side-nav-search>div.switch-menus>div.version-switch select:focus,.wy-side-nav-search>div.switch-menus>div.version-switch select:hover{background:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.5)}.wy-side-nav-search>div.switch-menus>div.language-switch select option,.wy-side-nav-search>div.switch-menus>div.version-switch select option{color:#000}.wy-side-nav-search>div.switch-menus>div.language-switch:has(>select):after,.wy-side-nav-search>div.switch-menus>div.version-switch:has(>select):after{display:inline-block;width:1.5em;height:100%;padding:.1em;content:"\f0d7";font-size:1em;line-height:1.2em;font-family:FontAwesome;text-align:center;pointer-events:none;box-sizing:border-box}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%;float:none;margin-left:0}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/docs/_static/custom.css b/docs/_static/custom.css new file mode 100644 index 0000000..faf5bbe --- /dev/null +++ b/docs/_static/custom.css @@ -0,0 +1 @@ +html[data-theme="light"]{--pst-color-primary:#1683b6;--pst-color-link:#0877aa}html[data-theme="dark"]{--pst-color-primary:#64c8f2;--pst-color-link:#7dd3f5}.bd-main .bd-content .bd-article-container{max-width:1100px}.sd-card{height:100%;border:1px solid var(--pst-color-border);transition:transform .18s ease,box-shadow .18s ease}.sd-card:hover{transform:translateY(-3px);box-shadow:0 .5rem 1rem rgba(0,0,0,.12)}.sd-card-title{color:var(--pst-color-primary);font-weight:700}.example-card img,.related-card img{width:100%;height:210px;object-fit:contain}.related-card img{height:150px}@media(max-width:768px){.example-card img,.related-card img{height:auto;max-height:220px}} diff --git a/docs/_static/design-tabs.js b/docs/_static/design-tabs.js new file mode 100644 index 0000000..b25bd6a --- /dev/null +++ b/docs/_static/design-tabs.js @@ -0,0 +1,101 @@ +// @ts-check + +// Extra JS capability for selected tabs to be synced +// The selection is stored in local storage so that it persists across page loads. + +/** + * @type {Record} + */ +let sd_id_to_elements = {}; +const storageKeyPrefix = "sphinx-design-tab-id-"; + +/** + * Create a key for a tab element. + * @param {HTMLElement} el - The tab element. + * @returns {[string, string, string] | null} - The key. + * + */ +function create_key(el) { + let syncId = el.getAttribute("data-sync-id"); + let syncGroup = el.getAttribute("data-sync-group"); + if (!syncId || !syncGroup) return null; + return [syncGroup, syncId, syncGroup + "--" + syncId]; +} + +/** + * Initialize the tab selection. + * + */ +function ready() { + // Find all tabs with sync data + + /** @type {string[]} */ + let groups = []; + + document.querySelectorAll(".sd-tab-label").forEach((label) => { + if (label instanceof HTMLElement) { + let data = create_key(label); + if (data) { + let [group, id, key] = data; + + // add click event listener + // @ts-ignore + label.onclick = onSDLabelClick; + + // store map of key to elements + if (!sd_id_to_elements[key]) { + sd_id_to_elements[key] = []; + } + sd_id_to_elements[key].push(label); + + if (groups.indexOf(group) === -1) { + groups.push(group); + // Check if a specific tab has been selected via URL parameter + const tabParam = new URLSearchParams(window.location.search).get( + group + ); + if (tabParam) { + console.log( + "sphinx-design: Selecting tab id for group '" + + group + + "' from URL parameter: " + + tabParam + ); + window.sessionStorage.setItem(storageKeyPrefix + group, tabParam); + } + } + + // Check is a specific tab has been selected previously + let previousId = window.sessionStorage.getItem( + storageKeyPrefix + group + ); + if (previousId === id) { + // console.log( + // "sphinx-design: Selecting tab from session storage: " + id + // ); + // @ts-ignore + label.previousElementSibling.checked = true; + } + } + } + }); +} + +/** + * Activate other tabs with the same sync id. + * + * @this {HTMLElement} - The element that was clicked. + */ +function onSDLabelClick() { + let data = create_key(this); + if (!data) return; + let [group, id, key] = data; + for (const label of sd_id_to_elements[key]) { + if (label === this) continue; + // @ts-ignore + label.previousElementSibling.checked = true; + } + window.sessionStorage.setItem(storageKeyPrefix + group, id); +} + +document.addEventListener("DOMContentLoaded", ready, false); diff --git a/docs/_static/fonts/Lato/lato-bold.eot b/docs/_static/fonts/Lato/lato-bold.eot deleted file mode 100644 index 3361183..0000000 Binary files a/docs/_static/fonts/Lato/lato-bold.eot and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-bold.ttf b/docs/_static/fonts/Lato/lato-bold.ttf deleted file mode 100644 index 29f691d..0000000 Binary files a/docs/_static/fonts/Lato/lato-bold.ttf and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-bold.woff b/docs/_static/fonts/Lato/lato-bold.woff deleted file mode 100644 index c6dff51..0000000 Binary files a/docs/_static/fonts/Lato/lato-bold.woff and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-bold.woff2 b/docs/_static/fonts/Lato/lato-bold.woff2 deleted file mode 100644 index bb19504..0000000 Binary files a/docs/_static/fonts/Lato/lato-bold.woff2 and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-bolditalic.eot b/docs/_static/fonts/Lato/lato-bolditalic.eot deleted file mode 100644 index 3d41549..0000000 Binary files a/docs/_static/fonts/Lato/lato-bolditalic.eot and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-bolditalic.ttf b/docs/_static/fonts/Lato/lato-bolditalic.ttf deleted file mode 100644 index f402040..0000000 Binary files a/docs/_static/fonts/Lato/lato-bolditalic.ttf and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-bolditalic.woff b/docs/_static/fonts/Lato/lato-bolditalic.woff deleted file mode 100644 index 88ad05b..0000000 Binary files a/docs/_static/fonts/Lato/lato-bolditalic.woff and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-bolditalic.woff2 b/docs/_static/fonts/Lato/lato-bolditalic.woff2 deleted file mode 100644 index c4e3d80..0000000 Binary files a/docs/_static/fonts/Lato/lato-bolditalic.woff2 and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-italic.eot b/docs/_static/fonts/Lato/lato-italic.eot deleted file mode 100644 index 3f82642..0000000 Binary files a/docs/_static/fonts/Lato/lato-italic.eot and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-italic.ttf b/docs/_static/fonts/Lato/lato-italic.ttf deleted file mode 100644 index b4bfc9b..0000000 Binary files a/docs/_static/fonts/Lato/lato-italic.ttf and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-italic.woff b/docs/_static/fonts/Lato/lato-italic.woff deleted file mode 100644 index 76114bc..0000000 Binary files a/docs/_static/fonts/Lato/lato-italic.woff and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-italic.woff2 b/docs/_static/fonts/Lato/lato-italic.woff2 deleted file mode 100644 index 3404f37..0000000 Binary files a/docs/_static/fonts/Lato/lato-italic.woff2 and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-regular.eot b/docs/_static/fonts/Lato/lato-regular.eot deleted file mode 100644 index 11e3f2a..0000000 Binary files a/docs/_static/fonts/Lato/lato-regular.eot and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-regular.ttf b/docs/_static/fonts/Lato/lato-regular.ttf deleted file mode 100644 index 74decd9..0000000 Binary files a/docs/_static/fonts/Lato/lato-regular.ttf and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-regular.woff b/docs/_static/fonts/Lato/lato-regular.woff deleted file mode 100644 index ae1307f..0000000 Binary files a/docs/_static/fonts/Lato/lato-regular.woff and /dev/null differ diff --git a/docs/_static/fonts/Lato/lato-regular.woff2 b/docs/_static/fonts/Lato/lato-regular.woff2 deleted file mode 100644 index 3bf9843..0000000 Binary files a/docs/_static/fonts/Lato/lato-regular.woff2 and /dev/null differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot deleted file mode 100644 index 79dc8ef..0000000 Binary files a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot and /dev/null differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf deleted file mode 100644 index df5d1df..0000000 Binary files a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf and /dev/null differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff deleted file mode 100644 index 6cb6000..0000000 Binary files a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff and /dev/null differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 deleted file mode 100644 index 7059e23..0000000 Binary files a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 and /dev/null differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot deleted file mode 100644 index 2f7ca78..0000000 Binary files a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot and /dev/null differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf deleted file mode 100644 index eb52a79..0000000 Binary files a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf and /dev/null differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff deleted file mode 100644 index f815f63..0000000 Binary files a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff and /dev/null differ diff --git a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 b/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 deleted file mode 100644 index f2c76e5..0000000 Binary files a/docs/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 and /dev/null differ diff --git a/docs/_static/jquery.js b/docs/_static/jquery.js deleted file mode 100644 index c4c6022..0000000 --- a/docs/_static/jquery.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t a.language.name.localeCompare(b.language.name)); - - const languagesHTML = ` -
-
Languages
- ${languages - .map( - (translation) => ` -
- ${translation.language.code} -
- `, - ) - .join("\n")} -
- `; - return languagesHTML; - } - - function renderVersions(config) { - if (!config.versions.active.length) { - return ""; - } - const versionsHTML = ` -
-
Versions
- ${config.versions.active - .map( - (version) => ` -
- ${version.slug} -
- `, - ) - .join("\n")} -
- `; - return versionsHTML; - } - - function renderDownloads(config) { - if (!Object.keys(config.versions.current.downloads).length) { - return ""; - } - const downloadsNameDisplay = { - pdf: "PDF", - epub: "Epub", - htmlzip: "HTML", - }; - - const downloadsHTML = ` -
-
Downloads
- ${Object.entries(config.versions.current.downloads) - .map( - ([name, url]) => ` -
- ${downloadsNameDisplay[name]} -
- `, - ) - .join("\n")} -
- `; - return downloadsHTML; - } - - document.addEventListener("readthedocs-addons-data-ready", function (event) { - const config = event.detail.data(); - - const flyout = ` -
- - Read the Docs - v: ${config.versions.current.slug} - - -
-
- ${renderLanguages(config)} - ${renderVersions(config)} - ${renderDownloads(config)} -
-
On Read the Docs
-
- Project Home -
-
- Builds -
-
- Downloads -
-
-
-
Search
-
-
- -
-
-
-
- - Hosted by Read the Docs - -
-
- `; - - // Inject the generated flyout into the body HTML element. - document.body.insertAdjacentHTML("beforeend", flyout); - - // Trigger the Read the Docs Addons Search modal when clicking on the "Search docs" input from inside the flyout. - document - .querySelector("#flyout-search-form") - .addEventListener("focusin", () => { - const event = new CustomEvent("readthedocs-search-show"); - document.dispatchEvent(event); - }); - }) -} - -if (themeLanguageSelector || themeVersionSelector) { - function onSelectorSwitch(event) { - const option = event.target.selectedIndex; - const item = event.target.options[option]; - window.location.href = item.dataset.url; - } - - document.addEventListener("readthedocs-addons-data-ready", function (event) { - const config = event.detail.data(); - - const versionSwitch = document.querySelector( - "div.switch-menus > div.version-switch", - ); - if (themeVersionSelector) { - let versions = config.versions.active; - if (config.versions.current.hidden || config.versions.current.type === "external") { - versions.unshift(config.versions.current); - } - const versionSelect = ` - - `; - - versionSwitch.innerHTML = versionSelect; - versionSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); - } - - const languageSwitch = document.querySelector( - "div.switch-menus > div.language-switch", - ); - - if (themeLanguageSelector) { - if (config.projects.translations.length) { - // Add the current language to the options on the selector - let languages = config.projects.translations.concat( - config.projects.current, - ); - languages = languages.sort((a, b) => - a.language.name.localeCompare(b.language.name), - ); - - const languageSelect = ` - - `; - - languageSwitch.innerHTML = languageSelect; - languageSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); - } - else { - languageSwitch.remove(); - } - } - }); -} - -document.addEventListener("readthedocs-addons-data-ready", function (event) { - // Trigger the Read the Docs Addons Search modal when clicking on "Search docs" input from the topnav. - document - .querySelector("[role='search'] input") - .addEventListener("focusin", () => { - const event = new CustomEvent("readthedocs-search-show"); - document.dispatchEvent(event); - }); -}); \ No newline at end of file diff --git a/docs/_static/pygments.css b/docs/_static/pygments.css index 6f8b210..d7dd577 100644 --- a/docs/_static/pygments.css +++ b/docs/_static/pygments.css @@ -1,75 +1,152 @@ -pre { line-height: 125%; } -td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -.highlight .hll { background-color: #ffffcc } -.highlight { background: #f8f8f8; } -.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ -.highlight .err { border: 1px solid #F00 } /* Error */ -.highlight .k { color: #008000; font-weight: bold } /* Keyword */ -.highlight .o { color: #666 } /* Operator */ -.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ -.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #9C6500 } /* Comment.Preproc */ -.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ -.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ -.highlight .gd { color: #A00000 } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ -.highlight .gr { color: #E40000 } /* Generic.Error */ -.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #008400 } /* Generic.Inserted */ -.highlight .go { color: #717171 } /* Generic.Output */ -.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #04D } /* Generic.Traceback */ -.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ -.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { color: #008000 } /* Keyword.Pseudo */ -.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #B00040 } /* Keyword.Type */ -.highlight .m { color: #666 } /* Literal.Number */ -.highlight .s { color: #BA2121 } /* Literal.String */ -.highlight .na { color: #687822 } /* Name.Attribute */ -.highlight .nb { color: #008000 } /* Name.Builtin */ -.highlight .nc { color: #00F; font-weight: bold } /* Name.Class */ -.highlight .no { color: #800 } /* Name.Constant */ -.highlight .nd { color: #A2F } /* Name.Decorator */ -.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ -.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #00F } /* Name.Function */ -.highlight .nl { color: #767600 } /* Name.Label */ -.highlight .nn { color: #00F; font-weight: bold } /* Name.Namespace */ -.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #19177C } /* Name.Variable */ -.highlight .ow { color: #A2F; font-weight: bold } /* Operator.Word */ -.highlight .w { color: #BBB } /* Text.Whitespace */ -.highlight .mb { color: #666 } /* Literal.Number.Bin */ -.highlight .mf { color: #666 } /* Literal.Number.Float */ -.highlight .mh { color: #666 } /* Literal.Number.Hex */ -.highlight .mi { color: #666 } /* Literal.Number.Integer */ -.highlight .mo { color: #666 } /* Literal.Number.Oct */ -.highlight .sa { color: #BA2121 } /* Literal.String.Affix */ -.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ -.highlight .sc { color: #BA2121 } /* Literal.String.Char */ -.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ -.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ -.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ -.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ -.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ -.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ -.highlight .sx { color: #008000 } /* Literal.String.Other */ -.highlight .sr { color: #A45A77 } /* Literal.String.Regex */ -.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ -.highlight .ss { color: #19177C } /* Literal.String.Symbol */ -.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ -.highlight .fm { color: #00F } /* Name.Function.Magic */ -.highlight .vc { color: #19177C } /* Name.Variable.Class */ -.highlight .vg { color: #19177C } /* Name.Variable.Global */ -.highlight .vi { color: #19177C } /* Name.Variable.Instance */ -.highlight .vm { color: #19177C } /* Name.Variable.Magic */ -.highlight .il { color: #666 } /* Literal.Number.Integer.Long */ \ No newline at end of file +html[data-theme="light"] .highlight pre { line-height: 125%; } +html[data-theme="light"] .highlight td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +html[data-theme="light"] .highlight span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +html[data-theme="light"] .highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +html[data-theme="light"] .highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +html[data-theme="light"] .highlight .hll { background-color: #fae4c2 } +html[data-theme="light"] .highlight { background: #fefefe; color: #080808 } +html[data-theme="light"] .highlight .c { color: #515151 } /* Comment */ +html[data-theme="light"] .highlight .err { color: #A12236 } /* Error */ +html[data-theme="light"] .highlight .k { color: #6730C5 } /* Keyword */ +html[data-theme="light"] .highlight .l { color: #7F4707 } /* Literal */ +html[data-theme="light"] .highlight .n { color: #080808 } /* Name */ +html[data-theme="light"] .highlight .o { color: #00622F } /* Operator */ +html[data-theme="light"] .highlight .p { color: #080808 } /* Punctuation */ +html[data-theme="light"] .highlight .ch { color: #515151 } /* Comment.Hashbang */ +html[data-theme="light"] .highlight .cm { color: #515151 } /* Comment.Multiline */ +html[data-theme="light"] .highlight .cp { color: #515151 } /* Comment.Preproc */ +html[data-theme="light"] .highlight .cpf { color: #515151 } /* Comment.PreprocFile */ +html[data-theme="light"] .highlight .c1 { color: #515151 } /* Comment.Single */ +html[data-theme="light"] .highlight .cs { color: #515151 } /* Comment.Special */ +html[data-theme="light"] .highlight .gd { color: #005B82 } /* Generic.Deleted */ +html[data-theme="light"] .highlight .ge { font-style: italic } /* Generic.Emph */ +html[data-theme="light"] .highlight .gh { color: #005B82 } /* Generic.Heading */ +html[data-theme="light"] .highlight .gs { font-weight: bold } /* Generic.Strong */ +html[data-theme="light"] .highlight .gu { color: #005B82 } /* Generic.Subheading */ +html[data-theme="light"] .highlight .kc { color: #6730C5 } /* Keyword.Constant */ +html[data-theme="light"] .highlight .kd { color: #6730C5 } /* Keyword.Declaration */ +html[data-theme="light"] .highlight .kn { color: #6730C5 } /* Keyword.Namespace */ +html[data-theme="light"] .highlight .kp { color: #6730C5 } /* Keyword.Pseudo */ +html[data-theme="light"] .highlight .kr { color: #6730C5 } /* Keyword.Reserved */ +html[data-theme="light"] .highlight .kt { color: #7F4707 } /* Keyword.Type */ +html[data-theme="light"] .highlight .ld { color: #7F4707 } /* Literal.Date */ +html[data-theme="light"] .highlight .m { color: #7F4707 } /* Literal.Number */ +html[data-theme="light"] .highlight .s { color: #00622F } /* Literal.String */ +html[data-theme="light"] .highlight .na { color: #912583 } /* Name.Attribute */ +html[data-theme="light"] .highlight .nb { color: #7F4707 } /* Name.Builtin */ +html[data-theme="light"] .highlight .nc { color: #005B82 } /* Name.Class */ +html[data-theme="light"] .highlight .no { color: #005B82 } /* Name.Constant */ +html[data-theme="light"] .highlight .nd { color: #7F4707 } /* Name.Decorator */ +html[data-theme="light"] .highlight .ni { color: #00622F } /* Name.Entity */ +html[data-theme="light"] .highlight .ne { color: #6730C5 } /* Name.Exception */ +html[data-theme="light"] .highlight .nf { color: #005B82 } /* Name.Function */ +html[data-theme="light"] .highlight .nl { color: #7F4707 } /* Name.Label */ +html[data-theme="light"] .highlight .nn { color: #080808 } /* Name.Namespace */ +html[data-theme="light"] .highlight .nx { color: #080808 } /* Name.Other */ +html[data-theme="light"] .highlight .py { color: #005B82 } /* Name.Property */ +html[data-theme="light"] .highlight .nt { color: #005B82 } /* Name.Tag */ +html[data-theme="light"] .highlight .nv { color: #A12236 } /* Name.Variable */ +html[data-theme="light"] .highlight .ow { color: #6730C5 } /* Operator.Word */ +html[data-theme="light"] .highlight .pm { color: #080808 } /* Punctuation.Marker */ +html[data-theme="light"] .highlight .w { color: #080808 } /* Text.Whitespace */ +html[data-theme="light"] .highlight .mb { color: #7F4707 } /* Literal.Number.Bin */ +html[data-theme="light"] .highlight .mf { color: #7F4707 } /* Literal.Number.Float */ +html[data-theme="light"] .highlight .mh { color: #7F4707 } /* Literal.Number.Hex */ +html[data-theme="light"] .highlight .mi { color: #7F4707 } /* Literal.Number.Integer */ +html[data-theme="light"] .highlight .mo { color: #7F4707 } /* Literal.Number.Oct */ +html[data-theme="light"] .highlight .sa { color: #00622F } /* Literal.String.Affix */ +html[data-theme="light"] .highlight .sb { color: #00622F } /* Literal.String.Backtick */ +html[data-theme="light"] .highlight .sc { color: #00622F } /* Literal.String.Char */ +html[data-theme="light"] .highlight .dl { color: #00622F } /* Literal.String.Delimiter */ +html[data-theme="light"] .highlight .sd { color: #00622F } /* Literal.String.Doc */ +html[data-theme="light"] .highlight .s2 { color: #00622F } /* Literal.String.Double */ +html[data-theme="light"] .highlight .se { color: #00622F } /* Literal.String.Escape */ +html[data-theme="light"] .highlight .sh { color: #00622F } /* Literal.String.Heredoc */ +html[data-theme="light"] .highlight .si { color: #00622F } /* Literal.String.Interpol */ +html[data-theme="light"] .highlight .sx { color: #00622F } /* Literal.String.Other */ +html[data-theme="light"] .highlight .sr { color: #A12236 } /* Literal.String.Regex */ +html[data-theme="light"] .highlight .s1 { color: #00622F } /* Literal.String.Single */ +html[data-theme="light"] .highlight .ss { color: #005B82 } /* Literal.String.Symbol */ +html[data-theme="light"] .highlight .bp { color: #7F4707 } /* Name.Builtin.Pseudo */ +html[data-theme="light"] .highlight .fm { color: #005B82 } /* Name.Function.Magic */ +html[data-theme="light"] .highlight .vc { color: #A12236 } /* Name.Variable.Class */ +html[data-theme="light"] .highlight .vg { color: #A12236 } /* Name.Variable.Global */ +html[data-theme="light"] .highlight .vi { color: #A12236 } /* Name.Variable.Instance */ +html[data-theme="light"] .highlight .vm { color: #7F4707 } /* Name.Variable.Magic */ +html[data-theme="light"] .highlight .il { color: #7F4707 } /* Literal.Number.Integer.Long */ +html[data-theme="dark"] .highlight pre { line-height: 125%; } +html[data-theme="dark"] .highlight td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +html[data-theme="dark"] .highlight span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +html[data-theme="dark"] .highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +html[data-theme="dark"] .highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +html[data-theme="dark"] .highlight .hll { background-color: #ffd9002e } +html[data-theme="dark"] .highlight { background: #2b2b2b; color: #F8F8F2 } +html[data-theme="dark"] .highlight .c { color: #FFD900 } /* Comment */ +html[data-theme="dark"] .highlight .err { color: #FFA07A } /* Error */ +html[data-theme="dark"] .highlight .k { color: #DCC6E0 } /* Keyword */ +html[data-theme="dark"] .highlight .l { color: #FFD900 } /* Literal */ +html[data-theme="dark"] .highlight .n { color: #F8F8F2 } /* Name */ +html[data-theme="dark"] .highlight .o { color: #ABE338 } /* Operator */ +html[data-theme="dark"] .highlight .p { color: #F8F8F2 } /* Punctuation */ +html[data-theme="dark"] .highlight .ch { color: #FFD900 } /* Comment.Hashbang */ +html[data-theme="dark"] .highlight .cm { color: #FFD900 } /* Comment.Multiline */ +html[data-theme="dark"] .highlight .cp { color: #FFD900 } /* Comment.Preproc */ +html[data-theme="dark"] .highlight .cpf { color: #FFD900 } /* Comment.PreprocFile */ +html[data-theme="dark"] .highlight .c1 { color: #FFD900 } /* Comment.Single */ +html[data-theme="dark"] .highlight .cs { color: #FFD900 } /* Comment.Special */ +html[data-theme="dark"] .highlight .gd { color: #00E0E0 } /* Generic.Deleted */ +html[data-theme="dark"] .highlight .ge { font-style: italic } /* Generic.Emph */ +html[data-theme="dark"] .highlight .gh { color: #00E0E0 } /* Generic.Heading */ +html[data-theme="dark"] .highlight .gs { font-weight: bold } /* Generic.Strong */ +html[data-theme="dark"] .highlight .gu { color: #00E0E0 } /* Generic.Subheading */ +html[data-theme="dark"] .highlight .kc { color: #DCC6E0 } /* Keyword.Constant */ +html[data-theme="dark"] .highlight .kd { color: #DCC6E0 } /* Keyword.Declaration */ +html[data-theme="dark"] .highlight .kn { color: #DCC6E0 } /* Keyword.Namespace */ +html[data-theme="dark"] .highlight .kp { color: #DCC6E0 } /* Keyword.Pseudo */ +html[data-theme="dark"] .highlight .kr { color: #DCC6E0 } /* Keyword.Reserved */ +html[data-theme="dark"] .highlight .kt { color: #FFD900 } /* Keyword.Type */ +html[data-theme="dark"] .highlight .ld { color: #FFD900 } /* Literal.Date */ +html[data-theme="dark"] .highlight .m { color: #FFD900 } /* Literal.Number */ +html[data-theme="dark"] .highlight .s { color: #ABE338 } /* Literal.String */ +html[data-theme="dark"] .highlight .na { color: #FFD900 } /* Name.Attribute */ +html[data-theme="dark"] .highlight .nb { color: #FFD900 } /* Name.Builtin */ +html[data-theme="dark"] .highlight .nc { color: #00E0E0 } /* Name.Class */ +html[data-theme="dark"] .highlight .no { color: #00E0E0 } /* Name.Constant */ +html[data-theme="dark"] .highlight .nd { color: #FFD900 } /* Name.Decorator */ +html[data-theme="dark"] .highlight .ni { color: #ABE338 } /* Name.Entity */ +html[data-theme="dark"] .highlight .ne { color: #DCC6E0 } /* Name.Exception */ +html[data-theme="dark"] .highlight .nf { color: #00E0E0 } /* Name.Function */ +html[data-theme="dark"] .highlight .nl { color: #FFD900 } /* Name.Label */ +html[data-theme="dark"] .highlight .nn { color: #F8F8F2 } /* Name.Namespace */ +html[data-theme="dark"] .highlight .nx { color: #F8F8F2 } /* Name.Other */ +html[data-theme="dark"] .highlight .py { color: #00E0E0 } /* Name.Property */ +html[data-theme="dark"] .highlight .nt { color: #00E0E0 } /* Name.Tag */ +html[data-theme="dark"] .highlight .nv { color: #FFA07A } /* Name.Variable */ +html[data-theme="dark"] .highlight .ow { color: #DCC6E0 } /* Operator.Word */ +html[data-theme="dark"] .highlight .pm { color: #F8F8F2 } /* Punctuation.Marker */ +html[data-theme="dark"] .highlight .w { color: #F8F8F2 } /* Text.Whitespace */ +html[data-theme="dark"] .highlight .mb { color: #FFD900 } /* Literal.Number.Bin */ +html[data-theme="dark"] .highlight .mf { color: #FFD900 } /* Literal.Number.Float */ +html[data-theme="dark"] .highlight .mh { color: #FFD900 } /* Literal.Number.Hex */ +html[data-theme="dark"] .highlight .mi { color: #FFD900 } /* Literal.Number.Integer */ +html[data-theme="dark"] .highlight .mo { color: #FFD900 } /* Literal.Number.Oct */ +html[data-theme="dark"] .highlight .sa { color: #ABE338 } /* Literal.String.Affix */ +html[data-theme="dark"] .highlight .sb { color: #ABE338 } /* Literal.String.Backtick */ +html[data-theme="dark"] .highlight .sc { color: #ABE338 } /* Literal.String.Char */ +html[data-theme="dark"] .highlight .dl { color: #ABE338 } /* Literal.String.Delimiter */ +html[data-theme="dark"] .highlight .sd { color: #ABE338 } /* Literal.String.Doc */ +html[data-theme="dark"] .highlight .s2 { color: #ABE338 } /* Literal.String.Double */ +html[data-theme="dark"] .highlight .se { color: #ABE338 } /* Literal.String.Escape */ +html[data-theme="dark"] .highlight .sh { color: #ABE338 } /* Literal.String.Heredoc */ +html[data-theme="dark"] .highlight .si { color: #ABE338 } /* Literal.String.Interpol */ +html[data-theme="dark"] .highlight .sx { color: #ABE338 } /* Literal.String.Other */ +html[data-theme="dark"] .highlight .sr { color: #FFA07A } /* Literal.String.Regex */ +html[data-theme="dark"] .highlight .s1 { color: #ABE338 } /* Literal.String.Single */ +html[data-theme="dark"] .highlight .ss { color: #00E0E0 } /* Literal.String.Symbol */ +html[data-theme="dark"] .highlight .bp { color: #FFD900 } /* Name.Builtin.Pseudo */ +html[data-theme="dark"] .highlight .fm { color: #00E0E0 } /* Name.Function.Magic */ +html[data-theme="dark"] .highlight .vc { color: #FFA07A } /* Name.Variable.Class */ +html[data-theme="dark"] .highlight .vg { color: #FFA07A } /* Name.Variable.Global */ +html[data-theme="dark"] .highlight .vi { color: #FFA07A } /* Name.Variable.Instance */ +html[data-theme="dark"] .highlight .vm { color: #FFD900 } /* Name.Variable.Magic */ +html[data-theme="dark"] .highlight .il { color: #FFD900 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/_static/scripts/bootstrap.js b/docs/_static/scripts/bootstrap.js new file mode 100644 index 0000000..92d9df9 --- /dev/null +++ b/docs/_static/scripts/bootstrap.js @@ -0,0 +1,3 @@ +/*! For license information please see bootstrap.js.LICENSE.txt */ +(()=>{"use strict";var t={d:(e,i)=>{for(var n in i)t.o(i,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:i[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};t.r(e),t.d(e,{afterMain:()=>E,afterRead:()=>v,afterWrite:()=>T,applyStyles:()=>$,arrow:()=>J,auto:()=>a,basePlacements:()=>l,beforeMain:()=>y,beforeRead:()=>_,beforeWrite:()=>A,bottom:()=>s,clippingParents:()=>d,computeStyles:()=>it,createPopper:()=>Dt,createPopperBase:()=>St,createPopperLite:()=>$t,detectOverflow:()=>_t,end:()=>h,eventListeners:()=>st,flip:()=>bt,hide:()=>wt,left:()=>r,main:()=>w,modifierPhases:()=>O,offset:()=>Et,placements:()=>g,popper:()=>f,popperGenerator:()=>Lt,popperOffsets:()=>At,preventOverflow:()=>Ct,read:()=>b,reference:()=>p,right:()=>o,start:()=>c,top:()=>n,variationPlacements:()=>m,viewport:()=>u,write:()=>C});var i={};t.r(i),t.d(i,{Alert:()=>xe,Button:()=>Le,Carousel:()=>ci,Collapse:()=>Ai,Dropdown:()=>Qi,Modal:()=>Sn,Offcanvas:()=>Qn,Popover:()=>vs,ScrollSpy:()=>Ss,Tab:()=>Zs,Toast:()=>mo,Tooltip:()=>ps});var n="top",s="bottom",o="right",r="left",a="auto",l=[n,s,o,r],c="start",h="end",d="clippingParents",u="viewport",f="popper",p="reference",m=l.reduce(function(t,e){return t.concat([e+"-"+c,e+"-"+h])},[]),g=[].concat(l,[a]).reduce(function(t,e){return t.concat([e,e+"-"+c,e+"-"+h])},[]),_="beforeRead",b="read",v="afterRead",y="beforeMain",w="main",E="afterMain",A="beforeWrite",C="write",T="afterWrite",O=[_,b,v,y,w,E,A,C,T];function x(t){return t?(t.nodeName||"").toLowerCase():null}function k(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function L(t){return t instanceof k(t).Element||t instanceof Element}function S(t){return t instanceof k(t).HTMLElement||t instanceof HTMLElement}function D(t){return"undefined"!=typeof ShadowRoot&&(t instanceof k(t).ShadowRoot||t instanceof ShadowRoot)}const $={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach(function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];S(s)&&x(s)&&(Object.assign(s.style,i),Object.keys(n).forEach(function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)}))})},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach(function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce(function(t,e){return t[e]="",t},{});S(n)&&x(n)&&(Object.assign(n.style,o),Object.keys(s).forEach(function(t){n.removeAttribute(t)}))})}},requires:["computeStyles"]};function I(t){return t.split("-")[0]}var N=Math.max,P=Math.min,M=Math.round;function j(){var t=navigator.userAgentData;return null!=t&&t.brands&&Array.isArray(t.brands)?t.brands.map(function(t){return t.brand+"/"+t.version}).join(" "):navigator.userAgent}function F(){return!/^((?!chrome|android).)*safari/i.test(j())}function H(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),s=1,o=1;e&&S(t)&&(s=t.offsetWidth>0&&M(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&M(n.height)/t.offsetHeight||1);var r=(L(t)?k(t):window).visualViewport,a=!F()&&i,l=(n.left+(a&&r?r.offsetLeft:0))/s,c=(n.top+(a&&r?r.offsetTop:0))/o,h=n.width/s,d=n.height/o;return{width:h,height:d,top:c,right:l+h,bottom:c+d,left:l,x:l,y:c}}function B(t){var e=H(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function W(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&D(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function z(t){return k(t).getComputedStyle(t)}function R(t){return["table","td","th"].indexOf(x(t))>=0}function q(t){return((L(t)?t.ownerDocument:t.document)||window.document).documentElement}function V(t){return"html"===x(t)?t:t.assignedSlot||t.parentNode||(D(t)?t.host:null)||q(t)}function Y(t){return S(t)&&"fixed"!==z(t).position?t.offsetParent:null}function K(t){for(var e=k(t),i=Y(t);i&&R(i)&&"static"===z(i).position;)i=Y(i);return i&&("html"===x(i)||"body"===x(i)&&"static"===z(i).position)?e:i||function(t){var e=/firefox/i.test(j());if(/Trident/i.test(j())&&S(t)&&"fixed"===z(t).position)return null;var i=V(t);for(D(i)&&(i=i.host);S(i)&&["html","body"].indexOf(x(i))<0;){var n=z(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function Q(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function X(t,e,i){return N(t,P(e,i))}function U(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function G(t,e){return e.reduce(function(e,i){return e[i]=t,e},{})}const J={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,a=t.name,c=t.options,h=i.elements.arrow,d=i.modifiersData.popperOffsets,u=I(i.placement),f=Q(u),p=[r,o].indexOf(u)>=0?"height":"width";if(h&&d){var m=function(t,e){return U("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:G(t,l))}(c.padding,i),g=B(h),_="y"===f?n:r,b="y"===f?s:o,v=i.rects.reference[p]+i.rects.reference[f]-d[f]-i.rects.popper[p],y=d[f]-i.rects.reference[f],w=K(h),E=w?"y"===f?w.clientHeight||0:w.clientWidth||0:0,A=v/2-y/2,C=m[_],T=E-g[p]-m[b],O=E/2-g[p]/2+A,x=X(C,O,T),k=f;i.modifiersData[a]=((e={})[k]=x,e.centerOffset=x-O,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&W(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Z(t){return t.split("-")[1]}var tt={top:"auto",right:"auto",bottom:"auto",left:"auto"};function et(t){var e,i=t.popper,a=t.popperRect,l=t.placement,c=t.variation,d=t.offsets,u=t.position,f=t.gpuAcceleration,p=t.adaptive,m=t.roundOffsets,g=t.isFixed,_=d.x,b=void 0===_?0:_,v=d.y,y=void 0===v?0:v,w="function"==typeof m?m({x:b,y}):{x:b,y};b=w.x,y=w.y;var E=d.hasOwnProperty("x"),A=d.hasOwnProperty("y"),C=r,T=n,O=window;if(p){var x=K(i),L="clientHeight",S="clientWidth";x===k(i)&&"static"!==z(x=q(i)).position&&"absolute"===u&&(L="scrollHeight",S="scrollWidth"),(l===n||(l===r||l===o)&&c===h)&&(T=s,y-=(g&&x===O&&O.visualViewport?O.visualViewport.height:x[L])-a.height,y*=f?1:-1),l!==r&&(l!==n&&l!==s||c!==h)||(C=o,b-=(g&&x===O&&O.visualViewport?O.visualViewport.width:x[S])-a.width,b*=f?1:-1)}var D,$=Object.assign({position:u},p&&tt),I=!0===m?function(t,e){var i=t.x,n=t.y,s=e.devicePixelRatio||1;return{x:M(i*s)/s||0,y:M(n*s)/s||0}}({x:b,y},k(i)):{x:b,y};return b=I.x,y=I.y,f?Object.assign({},$,((D={})[T]=A?"0":"",D[C]=E?"0":"",D.transform=(O.devicePixelRatio||1)<=1?"translate("+b+"px, "+y+"px)":"translate3d("+b+"px, "+y+"px, 0)",D)):Object.assign({},$,((e={})[T]=A?y+"px":"",e[C]=E?b+"px":"",e.transform="",e))}const it={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:I(e.placement),variation:Z(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,et(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,et(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var nt={passive:!0};const st={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=k(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach(function(t){t.addEventListener("scroll",i.update,nt)}),a&&l.addEventListener("resize",i.update,nt),function(){o&&c.forEach(function(t){t.removeEventListener("scroll",i.update,nt)}),a&&l.removeEventListener("resize",i.update,nt)}},data:{}};var ot={left:"right",right:"left",bottom:"top",top:"bottom"};function rt(t){return t.replace(/left|right|bottom|top/g,function(t){return ot[t]})}var at={start:"end",end:"start"};function lt(t){return t.replace(/start|end/g,function(t){return at[t]})}function ct(t){var e=k(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function ht(t){return H(q(t)).left+ct(t).scrollLeft}function dt(t){var e=z(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function ut(t){return["html","body","#document"].indexOf(x(t))>=0?t.ownerDocument.body:S(t)&&dt(t)?t:ut(V(t))}function ft(t,e){var i;void 0===e&&(e=[]);var n=ut(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=k(n),r=s?[o].concat(o.visualViewport||[],dt(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(ft(V(r)))}function pt(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function mt(t,e,i){return e===u?pt(function(t,e){var i=k(t),n=q(t),s=i.visualViewport,o=n.clientWidth,r=n.clientHeight,a=0,l=0;if(s){o=s.width,r=s.height;var c=F();(c||!c&&"fixed"===e)&&(a=s.offsetLeft,l=s.offsetTop)}return{width:o,height:r,x:a+ht(t),y:l}}(t,i)):L(e)?function(t,e){var i=H(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):pt(function(t){var e,i=q(t),n=ct(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=N(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=N(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+ht(t),l=-n.scrollTop;return"rtl"===z(s||i).direction&&(a+=N(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(q(t)))}function gt(t){var e,i=t.reference,a=t.element,l=t.placement,d=l?I(l):null,u=l?Z(l):null,f=i.x+i.width/2-a.width/2,p=i.y+i.height/2-a.height/2;switch(d){case n:e={x:f,y:i.y-a.height};break;case s:e={x:f,y:i.y+i.height};break;case o:e={x:i.x+i.width,y:p};break;case r:e={x:i.x-a.width,y:p};break;default:e={x:i.x,y:i.y}}var m=d?Q(d):null;if(null!=m){var g="y"===m?"height":"width";switch(u){case c:e[m]=e[m]-(i[g]/2-a[g]/2);break;case h:e[m]=e[m]+(i[g]/2-a[g]/2)}}return e}function _t(t,e){void 0===e&&(e={});var i=e,r=i.placement,a=void 0===r?t.placement:r,c=i.strategy,h=void 0===c?t.strategy:c,m=i.boundary,g=void 0===m?d:m,_=i.rootBoundary,b=void 0===_?u:_,v=i.elementContext,y=void 0===v?f:v,w=i.altBoundary,E=void 0!==w&&w,A=i.padding,C=void 0===A?0:A,T=U("number"!=typeof C?C:G(C,l)),O=y===f?p:f,k=t.rects.popper,D=t.elements[E?O:y],$=function(t,e,i,n){var s="clippingParents"===e?function(t){var e=ft(V(t)),i=["absolute","fixed"].indexOf(z(t).position)>=0&&S(t)?K(t):t;return L(i)?e.filter(function(t){return L(t)&&W(t,i)&&"body"!==x(t)}):[]}(t):[].concat(e),o=[].concat(s,[i]),r=o[0],a=o.reduce(function(e,i){var s=mt(t,i,n);return e.top=N(s.top,e.top),e.right=P(s.right,e.right),e.bottom=P(s.bottom,e.bottom),e.left=N(s.left,e.left),e},mt(t,r,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}(L(D)?D:D.contextElement||q(t.elements.popper),g,b,h),I=H(t.elements.reference),M=gt({reference:I,element:k,strategy:"absolute",placement:a}),j=pt(Object.assign({},k,M)),F=y===f?j:I,B={top:$.top-F.top+T.top,bottom:F.bottom-$.bottom+T.bottom,left:$.left-F.left+T.left,right:F.right-$.right+T.right},R=t.modifiersData.offset;if(y===f&&R){var Y=R[a];Object.keys(B).forEach(function(t){var e=[o,s].indexOf(t)>=0?1:-1,i=[n,s].indexOf(t)>=0?"y":"x";B[t]+=Y[i]*e})}return B}const bt={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,h=t.name;if(!e.modifiersData[h]._skip){for(var d=i.mainAxis,u=void 0===d||d,f=i.altAxis,p=void 0===f||f,_=i.fallbackPlacements,b=i.padding,v=i.boundary,y=i.rootBoundary,w=i.altBoundary,E=i.flipVariations,A=void 0===E||E,C=i.allowedAutoPlacements,T=e.options.placement,O=I(T),x=_||(O!==T&&A?function(t){if(I(t)===a)return[];var e=rt(t);return[lt(t),e,lt(e)]}(T):[rt(T)]),k=[T].concat(x).reduce(function(t,i){return t.concat(I(i)===a?function(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,c=i.allowedAutoPlacements,h=void 0===c?g:c,d=Z(n),u=d?a?m:m.filter(function(t){return Z(t)===d}):l,f=u.filter(function(t){return h.indexOf(t)>=0});0===f.length&&(f=u);var p=f.reduce(function(e,i){return e[i]=_t(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[I(i)],e},{});return Object.keys(p).sort(function(t,e){return p[t]-p[e]})}(e,{placement:i,boundary:v,rootBoundary:y,padding:b,flipVariations:A,allowedAutoPlacements:C}):i)},[]),L=e.rects.reference,S=e.rects.popper,D=new Map,$=!0,N=k[0],P=0;P=0,B=H?"width":"height",W=_t(e,{placement:M,boundary:v,rootBoundary:y,altBoundary:w,padding:b}),z=H?F?o:r:F?s:n;L[B]>S[B]&&(z=rt(z));var R=rt(z),q=[];if(u&&q.push(W[j]<=0),p&&q.push(W[z]<=0,W[R]<=0),q.every(function(t){return t})){N=M,$=!1;break}D.set(M,q)}if($)for(var V=function(t){var e=k.find(function(e){var i=D.get(e);if(i)return i.slice(0,t).every(function(t){return t})});if(e)return N=e,"break"},Y=A?3:1;Y>0&&"break"!==V(Y);Y--);e.placement!==N&&(e.modifiersData[h]._skip=!0,e.placement=N,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function vt(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function yt(t){return[n,o,s,r].some(function(e){return t[e]>=0})}const wt={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=_t(e,{elementContext:"reference"}),a=_t(e,{altBoundary:!0}),l=vt(r,n),c=vt(a,s,o),h=yt(l),d=yt(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},Et={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,s=t.name,a=i.offset,l=void 0===a?[0,0]:a,c=g.reduce(function(t,i){return t[i]=function(t,e,i){var s=I(t),a=[r,n].indexOf(s)>=0?-1:1,l="function"==typeof i?i(Object.assign({},e,{placement:t})):i,c=l[0],h=l[1];return c=c||0,h=(h||0)*a,[r,o].indexOf(s)>=0?{x:h,y:c}:{x:c,y:h}}(i,e.rects,l),t},{}),h=c[e.placement],d=h.x,u=h.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=d,e.modifiersData.popperOffsets.y+=u),e.modifiersData[s]=c}},At={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=gt({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},Ct={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,a=t.name,l=i.mainAxis,h=void 0===l||l,d=i.altAxis,u=void 0!==d&&d,f=i.boundary,p=i.rootBoundary,m=i.altBoundary,g=i.padding,_=i.tether,b=void 0===_||_,v=i.tetherOffset,y=void 0===v?0:v,w=_t(e,{boundary:f,rootBoundary:p,padding:g,altBoundary:m}),E=I(e.placement),A=Z(e.placement),C=!A,T=Q(E),O="x"===T?"y":"x",x=e.modifiersData.popperOffsets,k=e.rects.reference,L=e.rects.popper,S="function"==typeof y?y(Object.assign({},e.rects,{placement:e.placement})):y,D="number"==typeof S?{mainAxis:S,altAxis:S}:Object.assign({mainAxis:0,altAxis:0},S),$=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,M={x:0,y:0};if(x){if(h){var j,F="y"===T?n:r,H="y"===T?s:o,W="y"===T?"height":"width",z=x[T],R=z+w[F],q=z-w[H],V=b?-L[W]/2:0,Y=A===c?k[W]:L[W],U=A===c?-L[W]:-k[W],G=e.elements.arrow,J=b&&G?B(G):{width:0,height:0},tt=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},et=tt[F],it=tt[H],nt=X(0,k[W],J[W]),st=C?k[W]/2-V-nt-et-D.mainAxis:Y-nt-et-D.mainAxis,ot=C?-k[W]/2+V+nt+it+D.mainAxis:U+nt+it+D.mainAxis,rt=e.elements.arrow&&K(e.elements.arrow),at=rt?"y"===T?rt.clientTop||0:rt.clientLeft||0:0,lt=null!=(j=null==$?void 0:$[T])?j:0,ct=z+ot-lt,ht=X(b?P(R,z+st-lt-at):R,z,b?N(q,ct):q);x[T]=ht,M[T]=ht-z}if(u){var dt,ut="x"===T?n:r,ft="x"===T?s:o,pt=x[O],mt="y"===O?"height":"width",gt=pt+w[ut],bt=pt-w[ft],vt=-1!==[n,r].indexOf(E),yt=null!=(dt=null==$?void 0:$[O])?dt:0,wt=vt?gt:pt-k[mt]-L[mt]-yt+D.altAxis,Et=vt?pt+k[mt]+L[mt]-yt-D.altAxis:bt,At=b&&vt?function(t,e,i){var n=X(t,e,i);return n>i?i:n}(wt,pt,Et):X(b?wt:gt,pt,b?Et:bt);x[O]=At,M[O]=At-pt}e.modifiersData[a]=M}},requiresIfExists:["offset"]};function Tt(t,e,i){void 0===i&&(i=!1);var n,s,o=S(e),r=S(e)&&function(t){var e=t.getBoundingClientRect(),i=M(e.width)/t.offsetWidth||1,n=M(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),a=q(e),l=H(t,r,i),c={scrollLeft:0,scrollTop:0},h={x:0,y:0};return(o||!o&&!i)&&(("body"!==x(e)||dt(a))&&(c=(n=e)!==k(n)&&S(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:ct(n)),S(e)?((h=H(e,!0)).x+=e.clientLeft,h.y+=e.clientTop):a&&(h.x=ht(a))),{x:l.left+c.scrollLeft-h.x,y:l.top+c.scrollTop-h.y,width:l.width,height:l.height}}function Ot(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach(function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}}),n.push(t)}return t.forEach(function(t){e.set(t.name,t)}),t.forEach(function(t){i.has(t.name)||s(t)}),n}var xt={placement:"bottom",modifiers:[],strategy:"absolute"};function kt(){for(var t=arguments.length,e=new Array(t),i=0;iIt.has(t)&&It.get(t).get(e)||null,remove(t,e){if(!It.has(t))return;const i=It.get(t);i.delete(e),0===i.size&&It.delete(t)}},Pt="transitionend",Mt=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,(t,e)=>`#${CSS.escape(e)}`)),t),jt=t=>null==t?`${t}`:Object.prototype.toString.call(t).match(/\s([a-z]+)/i)[1].toLowerCase(),Ft=t=>{t.dispatchEvent(new Event(Pt))},Ht=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),Bt=t=>Ht(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(Mt(t)):null,Wt=t=>{if(!Ht(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},zt=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),Rt=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?Rt(t.parentNode):null},qt=()=>{},Vt=t=>{t.offsetHeight},Yt=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,Kt=[],Qt=()=>"rtl"===document.documentElement.dir,Xt=t=>{var e;e=()=>{const e=Yt();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(Kt.length||document.addEventListener("DOMContentLoaded",()=>{for(const t of Kt)t()}),Kt.push(e)):e()},Ut=(t,e=[],i=t)=>"function"==typeof t?t(...e):i,Gt=(t,e,i=!0)=>{if(!i)return void Ut(t);const n=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let s=!1;const o=({target:i})=>{i===e&&(s=!0,e.removeEventListener(Pt,o),Ut(t))};e.addEventListener(Pt,o),setTimeout(()=>{s||Ft(e)},n)},Jt=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},Zt=/[^.]*(?=\..*)\.|.*/,te=/\..*/,ee=/::\d+$/,ie={};let ne=1;const se={mouseenter:"mouseover",mouseleave:"mouseout"},oe=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function re(t,e){return e&&`${e}::${ne++}`||t.uidEvent||ne++}function ae(t){const e=re(t);return t.uidEvent=e,ie[e]=ie[e]||{},ie[e]}function le(t,e,i=null){return Object.values(t).find(t=>t.callable===e&&t.delegationSelector===i)}function ce(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=fe(t);return oe.has(o)||(o=t),[n,s,o]}function he(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=ce(e,i,n);if(e in se){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=ae(t),c=l[a]||(l[a]={}),h=le(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=re(r,e.replace(Zt,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return me(s,{delegateTarget:r}),n.oneOff&&pe.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return me(n,{delegateTarget:t}),i.oneOff&&pe.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function de(t,e,i,n,s){const o=le(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function ue(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&de(t,e,i,r.callable,r.delegationSelector)}function fe(t){return t=t.replace(te,""),se[t]||t}const pe={on(t,e,i,n){he(t,e,i,n,!1)},one(t,e,i,n){he(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=ce(e,i,n),a=r!==e,l=ae(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))ue(t,l,i,e.slice(1));for(const[i,n]of Object.entries(c)){const s=i.replace(ee,"");a&&!e.includes(s)||de(t,l,r,n.callable,n.delegationSelector)}}else{if(!Object.keys(c).length)return;de(t,l,r,o,s?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=Yt();let s=null,o=!0,r=!0,a=!1;e!==fe(e)&&n&&(s=n.Event(e,i),n(t).trigger(s),o=!s.isPropagationStopped(),r=!s.isImmediatePropagationStopped(),a=s.isDefaultPrevented());const l=me(new Event(e,{bubbles:o,cancelable:!0}),i);return a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&s&&s.preventDefault(),l}};function me(t,e={}){for(const[i,n]of Object.entries(e))try{t[i]=n}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>n})}return t}function ge(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function _e(t){return t.replace(/[A-Z]/g,t=>`-${t.toLowerCase()}`)}const be={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${_e(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${_e(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter(t=>t.startsWith("bs")&&!t.startsWith("bsConfig"));for(const n of i){let i=n.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=ge(t.dataset[n])}return e},getDataAttribute:(t,e)=>ge(t.getAttribute(`data-bs-${_e(e)}`))};class ve{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=Ht(e)?be.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...Ht(e)?be.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const[i,n]of Object.entries(e)){const e=t[i],s=Ht(e)?"element":jt(e);if(!new RegExp(n).test(s))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${i}" provided type "${s}" but expected type "${n}".`)}}}class ye extends ve{constructor(t,e){super(),(t=Bt(t))&&(this._element=t,this._config=this._getConfig(e),Nt.set(this._element,this.constructor.DATA_KEY,this))}dispose(){Nt.remove(this._element,this.constructor.DATA_KEY),pe.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){Gt(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return Nt.get(Bt(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.3.3"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const we=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e?e.split(",").map(t=>Mt(t)).join(","):null},Ee={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter(t=>t.matches(e)),parents(t,e){const i=[];let n=t.parentNode.closest(e);for(;n;)i.push(n),n=n.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map(t=>`${t}:not([tabindex^="-"])`).join(",");return this.find(e,t).filter(t=>!zt(t)&&Wt(t))},getSelectorFromElement(t){const e=we(t);return e&&Ee.findOne(e)?e:null},getElementFromSelector(t){const e=we(t);return e?Ee.findOne(e):null},getMultipleElementsFromSelector(t){const e=we(t);return e?Ee.find(e):[]}},Ae=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,n=t.NAME;pe.on(document,i,`[data-bs-dismiss="${n}"]`,function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),zt(this))return;const s=Ee.getElementFromSelector(this)||this.closest(`.${n}`);t.getOrCreateInstance(s)[e]()})},Ce=".bs.alert",Te=`close${Ce}`,Oe=`closed${Ce}`;class xe extends ye{static get NAME(){return"alert"}close(){if(pe.trigger(this._element,Te).defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback(()=>this._destroyElement(),this._element,t)}_destroyElement(){this._element.remove(),pe.trigger(this._element,Oe),this.dispose()}static jQueryInterface(t){return this.each(function(){const e=xe.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}})}}Ae(xe,"close"),Xt(xe);const ke='[data-bs-toggle="button"]';class Le extends ye{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each(function(){const e=Le.getOrCreateInstance(this);"toggle"===t&&e[t]()})}}pe.on(document,"click.bs.button.data-api",ke,t=>{t.preventDefault();const e=t.target.closest(ke);Le.getOrCreateInstance(e).toggle()}),Xt(Le);const Se=".bs.swipe",De=`touchstart${Se}`,$e=`touchmove${Se}`,Ie=`touchend${Se}`,Ne=`pointerdown${Se}`,Pe=`pointerup${Se}`,Me={endCallback:null,leftCallback:null,rightCallback:null},je={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class Fe extends ve{constructor(t,e){super(),this._element=t,t&&Fe.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return Me}static get DefaultType(){return je}static get NAME(){return"swipe"}dispose(){pe.off(this._element,Se)}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),Ut(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&Ut(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(pe.on(this._element,Ne,t=>this._start(t)),pe.on(this._element,Pe,t=>this._end(t)),this._element.classList.add("pointer-event")):(pe.on(this._element,De,t=>this._start(t)),pe.on(this._element,$e,t=>this._move(t)),pe.on(this._element,Ie,t=>this._end(t)))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const He=".bs.carousel",Be=".data-api",We="ArrowLeft",ze="ArrowRight",Re="next",qe="prev",Ve="left",Ye="right",Ke=`slide${He}`,Qe=`slid${He}`,Xe=`keydown${He}`,Ue=`mouseenter${He}`,Ge=`mouseleave${He}`,Je=`dragstart${He}`,Ze=`load${He}${Be}`,ti=`click${He}${Be}`,ei="carousel",ii="active",ni=".active",si=".carousel-item",oi=ni+si,ri={[We]:Ye,[ze]:Ve},ai={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},li={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class ci extends ye{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=Ee.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===ei&&this.cycle()}static get Default(){return ai}static get DefaultType(){return li}static get NAME(){return"carousel"}next(){this._slide(Re)}nextWhenVisible(){!document.hidden&&Wt(this._element)&&this.next()}prev(){this._slide(qe)}pause(){this._isSliding&&Ft(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval(()=>this.nextWhenVisible(),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?pe.one(this._element,Qe,()=>this.cycle()):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void pe.one(this._element,Qe,()=>this.to(t));const i=this._getItemIndex(this._getActive());if(i===t)return;const n=t>i?Re:qe;this._slide(n,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&pe.on(this._element,Xe,t=>this._keydown(t)),"hover"===this._config.pause&&(pe.on(this._element,Ue,()=>this.pause()),pe.on(this._element,Ge,()=>this._maybeEnableCycle())),this._config.touch&&Fe.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of Ee.find(".carousel-item img",this._element))pe.on(t,Je,t=>t.preventDefault());const t={leftCallback:()=>this._slide(this._directionToOrder(Ve)),rightCallback:()=>this._slide(this._directionToOrder(Ye)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout(()=>this._maybeEnableCycle(),500+this._config.interval))}};this._swipeHelper=new Fe(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=ri[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=Ee.findOne(ni,this._indicatorsElement);e.classList.remove(ii),e.removeAttribute("aria-current");const i=Ee.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(ii),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),n=t===Re,s=e||Jt(this._getItems(),i,n,this._config.wrap);if(s===i)return;const o=this._getItemIndex(s),r=e=>pe.trigger(this._element,e,{relatedTarget:s,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r(Ke).defaultPrevented)return;if(!i||!s)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=s;const l=n?"carousel-item-start":"carousel-item-end",c=n?"carousel-item-next":"carousel-item-prev";s.classList.add(c),Vt(s),i.classList.add(l),s.classList.add(l),this._queueCallback(()=>{s.classList.remove(l,c),s.classList.add(ii),i.classList.remove(ii,c,l),this._isSliding=!1,r(Qe)},i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return Ee.findOne(oi,this._element)}_getItems(){return Ee.find(si,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return Qt()?t===Ve?qe:Re:t===Ve?Re:qe}_orderToDirection(t){return Qt()?t===qe?Ve:Ye:t===qe?Ye:Ve}static jQueryInterface(t){return this.each(function(){const e=ci.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)})}}pe.on(document,ti,"[data-bs-slide], [data-bs-slide-to]",function(t){const e=Ee.getElementFromSelector(this);if(!e||!e.classList.contains(ei))return;t.preventDefault();const i=ci.getOrCreateInstance(e),n=this.getAttribute("data-bs-slide-to");return n?(i.to(n),void i._maybeEnableCycle()):"next"===be.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())}),pe.on(window,Ze,()=>{const t=Ee.find('[data-bs-ride="carousel"]');for(const e of t)ci.getOrCreateInstance(e)}),Xt(ci);const hi=".bs.collapse",di=`show${hi}`,ui=`shown${hi}`,fi=`hide${hi}`,pi=`hidden${hi}`,mi=`click${hi}.data-api`,gi="show",_i="collapse",bi="collapsing",vi=`:scope .${_i} .${_i}`,yi='[data-bs-toggle="collapse"]',wi={parent:null,toggle:!0},Ei={parent:"(null|element)",toggle:"boolean"};class Ai extends ye{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const i=Ee.find(yi);for(const t of i){const e=Ee.getSelectorFromElement(t),i=Ee.find(e).filter(t=>t===this._element);null!==e&&i.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return wi}static get DefaultType(){return Ei}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter(t=>t!==this._element).map(t=>Ai.getOrCreateInstance(t,{toggle:!1}))),t.length&&t[0]._isTransitioning)return;if(pe.trigger(this._element,di).defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(_i),this._element.classList.add(bi),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback(()=>{this._isTransitioning=!1,this._element.classList.remove(bi),this._element.classList.add(_i,gi),this._element.style[e]="",pe.trigger(this._element,ui)},this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(pe.trigger(this._element,fi).defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,Vt(this._element),this._element.classList.add(bi),this._element.classList.remove(_i,gi);for(const t of this._triggerArray){const e=Ee.getElementFromSelector(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0,this._element.style[t]="",this._queueCallback(()=>{this._isTransitioning=!1,this._element.classList.remove(bi),this._element.classList.add(_i),pe.trigger(this._element,pi)},this._element,!0)}_isShown(t=this._element){return t.classList.contains(gi)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=Bt(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(yi);for(const e of t){const t=Ee.getElementFromSelector(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=Ee.find(vi,this._config.parent);return Ee.find(t,this._config.parent).filter(t=>!e.includes(t))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each(function(){const i=Ai.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}})}}pe.on(document,mi,yi,function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();for(const t of Ee.getMultipleElementsFromSelector(this))Ai.getOrCreateInstance(t,{toggle:!1}).toggle()}),Xt(Ai);const Ci="dropdown",Ti=".bs.dropdown",Oi=".data-api",xi="ArrowUp",ki="ArrowDown",Li=`hide${Ti}`,Si=`hidden${Ti}`,Di=`show${Ti}`,$i=`shown${Ti}`,Ii=`click${Ti}${Oi}`,Ni=`keydown${Ti}${Oi}`,Pi=`keyup${Ti}${Oi}`,Mi="show",ji='[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled)',Fi=`${ji}.${Mi}`,Hi=".dropdown-menu",Bi=Qt()?"top-end":"top-start",Wi=Qt()?"top-start":"top-end",zi=Qt()?"bottom-end":"bottom-start",Ri=Qt()?"bottom-start":"bottom-end",qi=Qt()?"left-start":"right-start",Vi=Qt()?"right-start":"left-start",Yi={autoClose:!0,boundary:"clippingParents",display:"dynamic",offset:[0,2],popperConfig:null,reference:"toggle"},Ki={autoClose:"(boolean|string)",boundary:"(string|element)",display:"string",offset:"(array|string|function)",popperConfig:"(null|object|function)",reference:"(string|element|object)"};class Qi extends ye{constructor(t,e){super(t,e),this._popper=null,this._parent=this._element.parentNode,this._menu=Ee.next(this._element,Hi)[0]||Ee.prev(this._element,Hi)[0]||Ee.findOne(Hi,this._parent),this._inNavbar=this._detectNavbar()}static get Default(){return Yi}static get DefaultType(){return Ki}static get NAME(){return Ci}toggle(){return this._isShown()?this.hide():this.show()}show(){if(zt(this._element)||this._isShown())return;const t={relatedTarget:this._element};if(!pe.trigger(this._element,Di,t).defaultPrevented){if(this._createPopper(),"ontouchstart"in document.documentElement&&!this._parent.closest(".navbar-nav"))for(const t of[].concat(...document.body.children))pe.on(t,"mouseover",qt);this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(Mi),this._element.classList.add(Mi),pe.trigger(this._element,$i,t)}}hide(){if(zt(this._element)||!this._isShown())return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){if(!pe.trigger(this._element,Li,t).defaultPrevented){if("ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))pe.off(t,"mouseover",qt);this._popper&&this._popper.destroy(),this._menu.classList.remove(Mi),this._element.classList.remove(Mi),this._element.setAttribute("aria-expanded","false"),be.removeDataAttribute(this._menu,"popper"),pe.trigger(this._element,Si,t)}}_getConfig(t){if("object"==typeof(t=super._getConfig(t)).reference&&!Ht(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Ci.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(){let t=this._element;"parent"===this._config.reference?t=this._parent:Ht(this._config.reference)?t=Bt(this._config.reference):"object"==typeof this._config.reference&&(t=this._config.reference);const e=this._getPopperConfig();this._popper=Dt(t,this._menu,e)}_isShown(){return this._menu.classList.contains(Mi)}_getPlacement(){const t=this._parent;if(t.classList.contains("dropend"))return qi;if(t.classList.contains("dropstart"))return Vi;if(t.classList.contains("dropup-center"))return"top";if(t.classList.contains("dropdown-center"))return"bottom";const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?Wi:Bi:e?Ri:zi}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(be.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,...Ut(this._config.popperConfig,[t])}}_selectMenuItem({key:t,target:e}){const i=Ee.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(t=>Wt(t));i.length&&Jt(i,e,t===ki,!i.includes(e)).focus()}static jQueryInterface(t){return this.each(function(){const e=Qi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}})}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=Ee.find(Fi);for(const i of e){const e=Qi.getInstance(i);if(!e||!1===e._config.autoClose)continue;const n=t.composedPath(),s=n.includes(e._menu);if(n.includes(e._element)||"inside"===e._config.autoClose&&!s||"outside"===e._config.autoClose&&s)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,n=[xi,ki].includes(t.key);if(!n&&!i)return;if(e&&!i)return;t.preventDefault();const s=this.matches(ji)?this:Ee.prev(this,ji)[0]||Ee.next(this,ji)[0]||Ee.findOne(ji,t.delegateTarget.parentNode),o=Qi.getOrCreateInstance(s);if(n)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),s.focus())}}pe.on(document,Ni,ji,Qi.dataApiKeydownHandler),pe.on(document,Ni,Hi,Qi.dataApiKeydownHandler),pe.on(document,Ii,Qi.clearMenus),pe.on(document,Pi,Qi.clearMenus),pe.on(document,Ii,ji,function(t){t.preventDefault(),Qi.getOrCreateInstance(this).toggle()}),Xt(Qi);const Xi="backdrop",Ui="show",Gi=`mousedown.bs.${Xi}`,Ji={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},Zi={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class tn extends ve{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return Ji}static get DefaultType(){return Zi}static get NAME(){return Xi}show(t){if(!this._config.isVisible)return void Ut(t);this._append();const e=this._getElement();this._config.isAnimated&&Vt(e),e.classList.add(Ui),this._emulateAnimation(()=>{Ut(t)})}hide(t){this._config.isVisible?(this._getElement().classList.remove(Ui),this._emulateAnimation(()=>{this.dispose(),Ut(t)})):Ut(t)}dispose(){this._isAppended&&(pe.off(this._element,Gi),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=Bt(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),pe.on(t,Gi,()=>{Ut(this._config.clickCallback)}),this._isAppended=!0}_emulateAnimation(t){Gt(t,this._getElement(),this._config.isAnimated)}}const en=".bs.focustrap",nn=`focusin${en}`,sn=`keydown.tab${en}`,on="backward",rn={autofocus:!0,trapElement:null},an={autofocus:"boolean",trapElement:"element"};class ln extends ve{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return rn}static get DefaultType(){return an}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),pe.off(document,en),pe.on(document,nn,t=>this._handleFocusin(t)),pe.on(document,sn,t=>this._handleKeydown(t)),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,pe.off(document,en))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=Ee.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===on?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?on:"forward")}}const cn=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",hn=".sticky-top",dn="padding-right",un="margin-right";class fn{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,dn,e=>e+t),this._setElementAttributes(cn,dn,e=>e+t),this._setElementAttributes(hn,un,e=>e-t)}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,dn),this._resetElementAttributes(cn,dn),this._resetElementAttributes(hn,un)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(s))}px`)})}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&be.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,t=>{const i=be.getDataAttribute(t,e);null!==i?(be.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)})}_applyManipulationCallback(t,e){if(Ht(t))e(t);else for(const i of Ee.find(t,this._element))e(i)}}const pn=".bs.modal",mn=`hide${pn}`,gn=`hidePrevented${pn}`,_n=`hidden${pn}`,bn=`show${pn}`,vn=`shown${pn}`,yn=`resize${pn}`,wn=`click.dismiss${pn}`,En=`mousedown.dismiss${pn}`,An=`keydown.dismiss${pn}`,Cn=`click${pn}.data-api`,Tn="modal-open",On="show",xn="modal-static",kn={backdrop:!0,focus:!0,keyboard:!0},Ln={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class Sn extends ye{constructor(t,e){super(t,e),this._dialog=Ee.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new fn,this._addEventListeners()}static get Default(){return kn}static get DefaultType(){return Ln}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||pe.trigger(this._element,bn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(Tn),this._adjustDialog(),this._backdrop.show(()=>this._showElement(t)))}hide(){this._isShown&&!this._isTransitioning&&(pe.trigger(this._element,mn).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(On),this._queueCallback(()=>this._hideModal(),this._element,this._isAnimated())))}dispose(){pe.off(window,pn),pe.off(this._dialog,pn),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new tn({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new ln({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=Ee.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),Vt(this._element),this._element.classList.add(On),this._queueCallback(()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,pe.trigger(this._element,vn,{relatedTarget:t})},this._dialog,this._isAnimated())}_addEventListeners(){pe.on(this._element,An,t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():this._triggerBackdropTransition())}),pe.on(window,yn,()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()}),pe.on(this._element,En,t=>{pe.one(this._element,wn,e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())})})}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide(()=>{document.body.classList.remove(Tn),this._resetAdjustments(),this._scrollBar.reset(),pe.trigger(this._element,_n)})}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(pe.trigger(this._element,gn).defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(xn)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(xn),this._queueCallback(()=>{this._element.classList.remove(xn),this._queueCallback(()=>{this._element.style.overflowY=e},this._dialog)},this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=Qt()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=Qt()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each(function(){const i=Sn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}})}}pe.on(document,Cn,'[data-bs-toggle="modal"]',function(t){const e=Ee.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),pe.one(e,bn,t=>{t.defaultPrevented||pe.one(e,_n,()=>{Wt(this)&&this.focus()})});const i=Ee.findOne(".modal.show");i&&Sn.getInstance(i).hide(),Sn.getOrCreateInstance(e).toggle(this)}),Ae(Sn),Xt(Sn);const Dn=".bs.offcanvas",$n=".data-api",In=`load${Dn}${$n}`,Nn="show",Pn="showing",Mn="hiding",jn=".offcanvas.show",Fn=`show${Dn}`,Hn=`shown${Dn}`,Bn=`hide${Dn}`,Wn=`hidePrevented${Dn}`,zn=`hidden${Dn}`,Rn=`resize${Dn}`,qn=`click${Dn}${$n}`,Vn=`keydown.dismiss${Dn}`,Yn={backdrop:!0,keyboard:!0,scroll:!1},Kn={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class Qn extends ye{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return Yn}static get DefaultType(){return Kn}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||pe.trigger(this._element,Fn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new fn).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(Pn),this._queueCallback(()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(Nn),this._element.classList.remove(Pn),pe.trigger(this._element,Hn,{relatedTarget:t})},this._element,!0))}hide(){this._isShown&&(pe.trigger(this._element,Bn).defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add(Mn),this._backdrop.hide(),this._queueCallback(()=>{this._element.classList.remove(Nn,Mn),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new fn).reset(),pe.trigger(this._element,zn)},this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new tn({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():pe.trigger(this._element,Wn)}:null})}_initializeFocusTrap(){return new ln({trapElement:this._element})}_addEventListeners(){pe.on(this._element,Vn,t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():pe.trigger(this._element,Wn))})}static jQueryInterface(t){return this.each(function(){const e=Qn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}})}}pe.on(document,qn,'[data-bs-toggle="offcanvas"]',function(t){const e=Ee.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),zt(this))return;pe.one(e,zn,()=>{Wt(this)&&this.focus()});const i=Ee.findOne(jn);i&&i!==e&&Qn.getInstance(i).hide(),Qn.getOrCreateInstance(e).toggle(this)}),pe.on(window,In,()=>{for(const t of Ee.find(jn))Qn.getOrCreateInstance(t).show()}),pe.on(window,Rn,()=>{for(const t of Ee.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&Qn.getOrCreateInstance(t).hide()}),Ae(Qn),Xt(Qn);const Xn={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],dd:[],div:[],dl:[],dt:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},Un=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Gn=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Jn=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!Un.has(i)||Boolean(Gn.test(t.nodeValue)):e.filter(t=>t instanceof RegExp).some(t=>t.test(i))},Zn={allowList:Xn,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"
"},ts={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},es={entry:"(string|element|function|null)",selector:"(string|element)"};class is extends ve{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Zn}static get DefaultType(){return ts}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map(t=>this._resolvePossibleFunction(t)).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},es)}_setContent(t,e,i){const n=Ee.findOne(i,t);n&&((e=this._resolvePossibleFunction(e))?Ht(e)?this._putElementInTemplate(Bt(e),n):this._config.html?n.innerHTML=this._maybeSanitize(e):n.textContent=e:n.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const n=(new window.DOMParser).parseFromString(t,"text/html"),s=[].concat(...n.body.querySelectorAll("*"));for(const t of s){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const n=[].concat(...t.attributes),s=[].concat(e["*"]||[],e[i]||[]);for(const e of n)Jn(e,s)||t.removeAttribute(e.nodeName)}return n.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return Ut(t,[this])}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const ns=new Set(["sanitize","allowList","sanitizeFn"]),ss="fade",os="show",rs=".tooltip-inner",as=".modal",ls="hide.bs.modal",cs="hover",hs="focus",ds={AUTO:"auto",TOP:"top",RIGHT:Qt()?"left":"right",BOTTOM:"bottom",LEFT:Qt()?"right":"left"},us={allowList:Xn,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'',title:"",trigger:"hover focus"},fs={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class ps extends ye{constructor(t,e){super(t,e),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return us}static get DefaultType(){return fs}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._activeTrigger.click=!this._activeTrigger.click,this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),pe.off(this._element.closest(as),ls,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=pe.trigger(this._element,this.constructor.eventName("show")),e=(Rt(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this._disposePopper();const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:n}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(n.append(i),pe.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(i),i.classList.add(os),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))pe.on(t,"mouseover",qt);this._queueCallback(()=>{pe.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1},this.tip,this._isAnimated())}hide(){if(this._isShown()&&!pe.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented){if(this._getTipElement().classList.remove(os),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))pe.off(t,"mouseover",qt);this._activeTrigger.click=!1,this._activeTrigger[hs]=!1,this._activeTrigger[cs]=!1,this._isHovered=null,this._queueCallback(()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),pe.trigger(this._element,this.constructor.eventName("hidden")))},this.tip,this._isAnimated())}}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove(ss,os),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add(ss),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new is({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{[rs]:this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(ss)}_isShown(){return this.tip&&this.tip.classList.contains(os)}_createPopper(t){const e=Ut(this._config.placement,[this,t,this._element]),i=ds[e.toUpperCase()];return Dt(this._element,t,this._getPopperConfig(i))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return Ut(t,[this._element])}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,...Ut(this._config.popperConfig,[e])}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)pe.on(this._element,this.constructor.eventName("click"),this._config.selector,t=>{this._initializeOnDelegatedTarget(t).toggle()});else if("manual"!==e){const t=e===cs?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===cs?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");pe.on(this._element,t,this._config.selector,t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?hs:cs]=!0,e._enter()}),pe.on(this._element,i,this._config.selector,t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?hs:cs]=e._element.contains(t.relatedTarget),e._leave()})}this._hideModalHandler=()=>{this._element&&this.hide()},pe.on(this._element.closest(as),ls,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout(()=>{this._isHovered&&this.show()},this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout(()=>{this._isHovered||this.hide()},this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=be.getDataAttributes(this._element);for(const t of Object.keys(e))ns.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:Bt(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const[e,i]of Object.entries(this._config))this.constructor.Default[e]!==i&&(t[e]=i);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(t){return this.each(function(){const e=ps.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}})}}Xt(ps);const ms=".popover-header",gs=".popover-body",_s={...ps.Default,content:"",offset:[0,8],placement:"right",template:'',trigger:"click"},bs={...ps.DefaultType,content:"(null|string|element|function)"};class vs extends ps{static get Default(){return _s}static get DefaultType(){return bs}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{[ms]:this._getTitle(),[gs]:this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each(function(){const e=vs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}})}}Xt(vs);const ys=".bs.scrollspy",ws=`activate${ys}`,Es=`click${ys}`,As=`load${ys}.data-api`,Cs="active",Ts="[href]",Os=".nav-link",xs=`${Os}, .nav-item > ${Os}, .list-group-item`,ks={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},Ls={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class Ss extends ye{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return ks}static get DefaultType(){return Ls}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=Bt(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map(t=>Number.parseFloat(t))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(pe.off(this._config.target,Es),pe.on(this._config.target,Es,Ts,t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,n=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:n,behavior:"smooth"});i.scrollTop=n}}))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver(t=>this._observerCallback(t),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},n=(this._rootElement||document.documentElement).scrollTop,s=n>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=n;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(s&&t){if(i(o),!n)return}else s||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=Ee.find(Ts,this._config.target);for(const e of t){if(!e.hash||zt(e))continue;const t=Ee.findOne(decodeURI(e.hash),this._element);Wt(t)&&(this._targetLinks.set(decodeURI(e.hash),e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(Cs),this._activateParents(t),pe.trigger(this._element,ws,{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))Ee.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(Cs);else for(const e of Ee.parents(t,".nav, .list-group"))for(const t of Ee.prev(e,xs))t.classList.add(Cs)}_clearActiveClass(t){t.classList.remove(Cs);const e=Ee.find(`${Ts}.${Cs}`,t);for(const t of e)t.classList.remove(Cs)}static jQueryInterface(t){return this.each(function(){const e=Ss.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}})}}pe.on(window,As,()=>{for(const t of Ee.find('[data-bs-spy="scroll"]'))Ss.getOrCreateInstance(t)}),Xt(Ss);const Ds=".bs.tab",$s=`hide${Ds}`,Is=`hidden${Ds}`,Ns=`show${Ds}`,Ps=`shown${Ds}`,Ms=`click${Ds}`,js=`keydown${Ds}`,Fs=`load${Ds}`,Hs="ArrowLeft",Bs="ArrowRight",Ws="ArrowUp",zs="ArrowDown",Rs="Home",qs="End",Vs="active",Ys="fade",Ks="show",Qs=".dropdown-toggle",Xs=`:not(${Qs})`,Us='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',Gs=`.nav-link${Xs}, .list-group-item${Xs}, [role="tab"]${Xs}, ${Us}`,Js=`.${Vs}[data-bs-toggle="tab"], .${Vs}[data-bs-toggle="pill"], .${Vs}[data-bs-toggle="list"]`;class Zs extends ye{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),pe.on(this._element,js,t=>this._keydown(t)))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?pe.trigger(e,$s,{relatedTarget:t}):null;pe.trigger(t,Ns,{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(Vs),this._activate(Ee.getElementFromSelector(t)),this._queueCallback(()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),pe.trigger(t,Ps,{relatedTarget:e})):t.classList.add(Ks)},t,t.classList.contains(Ys)))}_deactivate(t,e){t&&(t.classList.remove(Vs),t.blur(),this._deactivate(Ee.getElementFromSelector(t)),this._queueCallback(()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),pe.trigger(t,Is,{relatedTarget:e})):t.classList.remove(Ks)},t,t.classList.contains(Ys)))}_keydown(t){if(![Hs,Bs,Ws,zs,Rs,qs].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=this._getChildren().filter(t=>!zt(t));let i;if([Rs,qs].includes(t.key))i=e[t.key===Rs?0:e.length-1];else{const n=[Bs,zs].includes(t.key);i=Jt(e,t.target,n,!0)}i&&(i.focus({preventScroll:!0}),Zs.getOrCreateInstance(i).show())}_getChildren(){return Ee.find(Gs,this._parent)}_getActiveElem(){return this._getChildren().find(t=>this._elemIsActive(t))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=Ee.getElementFromSelector(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const n=(t,n)=>{const s=Ee.findOne(t,i);s&&s.classList.toggle(n,e)};n(Qs,Vs),n(".dropdown-menu",Ks),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(Vs)}_getInnerElement(t){return t.matches(Gs)?t:Ee.findOne(Gs,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each(function(){const e=Zs.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}})}}pe.on(document,Ms,Us,function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),zt(this)||Zs.getOrCreateInstance(this).show()}),pe.on(window,Fs,()=>{for(const t of Ee.find(Js))Zs.getOrCreateInstance(t)}),Xt(Zs);const to=".bs.toast",eo=`mouseover${to}`,io=`mouseout${to}`,no=`focusin${to}`,so=`focusout${to}`,oo=`hide${to}`,ro=`hidden${to}`,ao=`show${to}`,lo=`shown${to}`,co="hide",ho="show",uo="showing",fo={animation:"boolean",autohide:"boolean",delay:"number"},po={animation:!0,autohide:!0,delay:5e3};class mo extends ye{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return po}static get DefaultType(){return fo}static get NAME(){return"toast"}show(){pe.trigger(this._element,ao).defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(co),Vt(this._element),this._element.classList.add(ho,uo),this._queueCallback(()=>{this._element.classList.remove(uo),pe.trigger(this._element,lo),this._maybeScheduleHide()},this._element,this._config.animation))}hide(){this.isShown()&&(pe.trigger(this._element,oo).defaultPrevented||(this._element.classList.add(uo),this._queueCallback(()=>{this._element.classList.add(co),this._element.classList.remove(uo,ho),pe.trigger(this._element,ro)},this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(ho),super.dispose()}isShown(){return this._element.classList.contains(ho)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout(()=>{this.hide()},this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){pe.on(this._element,eo,t=>this._onInteraction(t,!0)),pe.on(this._element,io,t=>this._onInteraction(t,!1)),pe.on(this._element,no,t=>this._onInteraction(t,!0)),pe.on(this._element,so,t=>this._onInteraction(t,!1))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each(function(){const e=mo.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}})}}function go(t){"loading"!=document.readyState?t():document.addEventListener("DOMContentLoaded",t)}Ae(mo),Xt(mo),go(function(){[].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')).map(function(t){return new ps(t,{delay:{show:500,hide:100}})})}),go(function(){const t=document.querySelector(".theme-switch-container");if(!t)return;const e=ps.getInstance(t);e&&(t.addEventListener("show.bs.dropdown",()=>{e.hide(),e.disable()}),t.addEventListener("hide.bs.dropdown",()=>{e.enable()}))}),go(function(){var t=document.getElementById("pst-back-to-top");t&&t.addEventListener("click",function(){document.body.scrollTop=0,document.documentElement.scrollTop=0})}),go(function(){var t=document.getElementById("pst-back-to-top");if(t){var e=document.getElementsByClassName("bd-header")[0].getBoundingClientRect();window.addEventListener("scroll",function(){this.oldScroll>this.scrollY&&this.scrollY>e.bottom?t.style.display="block":t.style.display="none",this.oldScroll=this.scrollY})}}),window.bootstrap=i})(); +//# sourceMappingURL=bootstrap.js.map \ No newline at end of file diff --git a/docs/_static/scripts/bootstrap.js.LICENSE.txt b/docs/_static/scripts/bootstrap.js.LICENSE.txt new file mode 100644 index 0000000..28755c2 --- /dev/null +++ b/docs/_static/scripts/bootstrap.js.LICENSE.txt @@ -0,0 +1,5 @@ +/*! + * Bootstrap v5.3.3 (https://getbootstrap.com/) + * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ diff --git a/docs/_static/scripts/bootstrap.js.map b/docs/_static/scripts/bootstrap.js.map new file mode 100644 index 0000000..a8ea336 --- /dev/null +++ b/docs/_static/scripts/bootstrap.js.map @@ -0,0 +1 @@ +{"version":3,"file":"scripts/bootstrap.js","mappings":"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","sources":["webpack://pydata_sphinx_theme/webpack/bootstrap","webpack://pydata_sphinx_theme/webpack/runtime/define property getters","webpack://pydata_sphinx_theme/webpack/runtime/hasOwnProperty shorthand","webpack://pydata_sphinx_theme/webpack/runtime/make namespace object","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/enums.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getNodeName.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getWindow.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/instanceOf.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/applyStyles.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getBasePlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/math.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/userAgent.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/isLayoutViewport.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getBoundingClientRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getLayoutRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/contains.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getComputedStyle.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/isTableElement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getDocumentElement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getParentNode.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getOffsetParent.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getMainAxisFromPlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/within.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/mergePaddingObject.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getFreshSideObject.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/expandToHashMap.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/arrow.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getVariation.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/computeStyles.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/eventListeners.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getOppositePlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getOppositeVariationPlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getWindowScroll.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getWindowScrollBarX.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/isScrollParent.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getScrollParent.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/listScrollParents.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/rectToClientRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getClippingRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getViewportRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getDocumentRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/computeOffsets.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/detectOverflow.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/flip.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/computeAutoPlacement.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/hide.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/offset.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/popperOffsets.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/modifiers/preventOverflow.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/getAltAxis.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getCompositeRect.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getNodeScroll.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/dom-utils/getHTMLElementScroll.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/orderModifiers.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/createPopper.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/debounce.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/utils/mergeByName.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/popper.js","webpack://pydata_sphinx_theme/./node_modules/@popperjs/core/lib/popper-lite.js","webpack://pydata_sphinx_theme/./node_modules/bootstrap/dist/js/bootstrap.esm.js","webpack://pydata_sphinx_theme/./src/pydata_sphinx_theme/assets/scripts/mixin.js","webpack://pydata_sphinx_theme/./src/pydata_sphinx_theme/assets/scripts/bootstrap.js"],"sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","export var top = 'top';\nexport var bottom = 'bottom';\nexport var right = 'right';\nexport var left = 'left';\nexport var auto = 'auto';\nexport var basePlacements = [top, bottom, right, left];\nexport var start = 'start';\nexport var end = 'end';\nexport var clippingParents = 'clippingParents';\nexport var viewport = 'viewport';\nexport var popper = 'popper';\nexport var reference = 'reference';\nexport var variationPlacements = /*#__PURE__*/basePlacements.reduce(function (acc, placement) {\n return acc.concat([placement + \"-\" + start, placement + \"-\" + end]);\n}, []);\nexport var placements = /*#__PURE__*/[].concat(basePlacements, [auto]).reduce(function (acc, placement) {\n return acc.concat([placement, placement + \"-\" + start, placement + \"-\" + end]);\n}, []); // modifiers that need to read the DOM\n\nexport var beforeRead = 'beforeRead';\nexport var read = 'read';\nexport var afterRead = 'afterRead'; // pure-logic modifiers\n\nexport var beforeMain = 'beforeMain';\nexport var main = 'main';\nexport var afterMain = 'afterMain'; // modifier with the purpose to write to the DOM (or write into a framework state)\n\nexport var beforeWrite = 'beforeWrite';\nexport var write = 'write';\nexport var afterWrite = 'afterWrite';\nexport var modifierPhases = [beforeRead, read, afterRead, beforeMain, main, afterMain, beforeWrite, write, afterWrite];","export default function getNodeName(element) {\n return element ? (element.nodeName || '').toLowerCase() : null;\n}","export default function getWindow(node) {\n if (node == null) {\n return window;\n }\n\n if (node.toString() !== '[object Window]') {\n var ownerDocument = node.ownerDocument;\n return ownerDocument ? ownerDocument.defaultView || window : window;\n }\n\n return node;\n}","import getWindow from \"./getWindow.js\";\n\nfunction isElement(node) {\n var OwnElement = getWindow(node).Element;\n return node instanceof OwnElement || node instanceof Element;\n}\n\nfunction isHTMLElement(node) {\n var OwnElement = getWindow(node).HTMLElement;\n return node instanceof OwnElement || node instanceof HTMLElement;\n}\n\nfunction isShadowRoot(node) {\n // IE 11 has no ShadowRoot\n if (typeof ShadowRoot === 'undefined') {\n return false;\n }\n\n var OwnElement = getWindow(node).ShadowRoot;\n return node instanceof OwnElement || node instanceof ShadowRoot;\n}\n\nexport { isElement, isHTMLElement, isShadowRoot };","import getNodeName from \"../dom-utils/getNodeName.js\";\nimport { isHTMLElement } from \"../dom-utils/instanceOf.js\"; // This modifier takes the styles prepared by the `computeStyles` modifier\n// and applies them to the HTMLElements such as popper and arrow\n\nfunction applyStyles(_ref) {\n var state = _ref.state;\n Object.keys(state.elements).forEach(function (name) {\n var style = state.styles[name] || {};\n var attributes = state.attributes[name] || {};\n var element = state.elements[name]; // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n } // Flow doesn't support to extend this property, but it's the most\n // effective way to apply styles to an HTMLElement\n // $FlowFixMe[cannot-write]\n\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (name) {\n var value = attributes[name];\n\n if (value === false) {\n element.removeAttribute(name);\n } else {\n element.setAttribute(name, value === true ? '' : value);\n }\n });\n });\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state;\n var initialStyles = {\n popper: {\n position: state.options.strategy,\n left: '0',\n top: '0',\n margin: '0'\n },\n arrow: {\n position: 'absolute'\n },\n reference: {}\n };\n Object.assign(state.elements.popper.style, initialStyles.popper);\n state.styles = initialStyles;\n\n if (state.elements.arrow) {\n Object.assign(state.elements.arrow.style, initialStyles.arrow);\n }\n\n return function () {\n Object.keys(state.elements).forEach(function (name) {\n var element = state.elements[name];\n var attributes = state.attributes[name] || {};\n var styleProperties = Object.keys(state.styles.hasOwnProperty(name) ? state.styles[name] : initialStyles[name]); // Set all values to an empty string to unset them\n\n var style = styleProperties.reduce(function (style, property) {\n style[property] = '';\n return style;\n }, {}); // arrow is optional + virtual elements\n\n if (!isHTMLElement(element) || !getNodeName(element)) {\n return;\n }\n\n Object.assign(element.style, style);\n Object.keys(attributes).forEach(function (attribute) {\n element.removeAttribute(attribute);\n });\n });\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'applyStyles',\n enabled: true,\n phase: 'write',\n fn: applyStyles,\n effect: effect,\n requires: ['computeStyles']\n};","import { auto } from \"../enums.js\";\nexport default function getBasePlacement(placement) {\n return placement.split('-')[0];\n}","export var max = Math.max;\nexport var min = Math.min;\nexport var round = Math.round;","export default function getUAString() {\n var uaData = navigator.userAgentData;\n\n if (uaData != null && uaData.brands && Array.isArray(uaData.brands)) {\n return uaData.brands.map(function (item) {\n return item.brand + \"/\" + item.version;\n }).join(' ');\n }\n\n return navigator.userAgent;\n}","import getUAString from \"../utils/userAgent.js\";\nexport default function isLayoutViewport() {\n return !/^((?!chrome|android).)*safari/i.test(getUAString());\n}","import { isElement, isHTMLElement } from \"./instanceOf.js\";\nimport { round } from \"../utils/math.js\";\nimport getWindow from \"./getWindow.js\";\nimport isLayoutViewport from \"./isLayoutViewport.js\";\nexport default function getBoundingClientRect(element, includeScale, isFixedStrategy) {\n if (includeScale === void 0) {\n includeScale = false;\n }\n\n if (isFixedStrategy === void 0) {\n isFixedStrategy = false;\n }\n\n var clientRect = element.getBoundingClientRect();\n var scaleX = 1;\n var scaleY = 1;\n\n if (includeScale && isHTMLElement(element)) {\n scaleX = element.offsetWidth > 0 ? round(clientRect.width) / element.offsetWidth || 1 : 1;\n scaleY = element.offsetHeight > 0 ? round(clientRect.height) / element.offsetHeight || 1 : 1;\n }\n\n var _ref = isElement(element) ? getWindow(element) : window,\n visualViewport = _ref.visualViewport;\n\n var addVisualOffsets = !isLayoutViewport() && isFixedStrategy;\n var x = (clientRect.left + (addVisualOffsets && visualViewport ? visualViewport.offsetLeft : 0)) / scaleX;\n var y = (clientRect.top + (addVisualOffsets && visualViewport ? visualViewport.offsetTop : 0)) / scaleY;\n var width = clientRect.width / scaleX;\n var height = clientRect.height / scaleY;\n return {\n width: width,\n height: height,\n top: y,\n right: x + width,\n bottom: y + height,\n left: x,\n x: x,\n y: y\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\"; // Returns the layout rect of an element relative to its offsetParent. Layout\n// means it doesn't take into account transforms.\n\nexport default function getLayoutRect(element) {\n var clientRect = getBoundingClientRect(element); // Use the clientRect sizes if it's not been transformed.\n // Fixes https://github.com/popperjs/popper-core/issues/1223\n\n var width = element.offsetWidth;\n var height = element.offsetHeight;\n\n if (Math.abs(clientRect.width - width) <= 1) {\n width = clientRect.width;\n }\n\n if (Math.abs(clientRect.height - height) <= 1) {\n height = clientRect.height;\n }\n\n return {\n x: element.offsetLeft,\n y: element.offsetTop,\n width: width,\n height: height\n };\n}","import { isShadowRoot } from \"./instanceOf.js\";\nexport default function contains(parent, child) {\n var rootNode = child.getRootNode && child.getRootNode(); // First, attempt with faster native method\n\n if (parent.contains(child)) {\n return true;\n } // then fallback to custom implementation with Shadow DOM support\n else if (rootNode && isShadowRoot(rootNode)) {\n var next = child;\n\n do {\n if (next && parent.isSameNode(next)) {\n return true;\n } // $FlowFixMe[prop-missing]: need a better way to handle this...\n\n\n next = next.parentNode || next.host;\n } while (next);\n } // Give up, the result is false\n\n\n return false;\n}","import getWindow from \"./getWindow.js\";\nexport default function getComputedStyle(element) {\n return getWindow(element).getComputedStyle(element);\n}","import getNodeName from \"./getNodeName.js\";\nexport default function isTableElement(element) {\n return ['table', 'td', 'th'].indexOf(getNodeName(element)) >= 0;\n}","import { isElement } from \"./instanceOf.js\";\nexport default function getDocumentElement(element) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return ((isElement(element) ? element.ownerDocument : // $FlowFixMe[prop-missing]\n element.document) || window.document).documentElement;\n}","import getNodeName from \"./getNodeName.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport { isShadowRoot } from \"./instanceOf.js\";\nexport default function getParentNode(element) {\n if (getNodeName(element) === 'html') {\n return element;\n }\n\n return (// this is a quicker (but less type safe) way to save quite some bytes from the bundle\n // $FlowFixMe[incompatible-return]\n // $FlowFixMe[prop-missing]\n element.assignedSlot || // step into the shadow DOM of the parent of a slotted node\n element.parentNode || ( // DOM Element detected\n isShadowRoot(element) ? element.host : null) || // ShadowRoot detected\n // $FlowFixMe[incompatible-call]: HTMLElement is a Node\n getDocumentElement(element) // fallback\n\n );\n}","import getWindow from \"./getWindow.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isHTMLElement, isShadowRoot } from \"./instanceOf.js\";\nimport isTableElement from \"./isTableElement.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport getUAString from \"../utils/userAgent.js\";\n\nfunction getTrueOffsetParent(element) {\n if (!isHTMLElement(element) || // https://github.com/popperjs/popper-core/issues/837\n getComputedStyle(element).position === 'fixed') {\n return null;\n }\n\n return element.offsetParent;\n} // `.offsetParent` reports `null` for fixed elements, while absolute elements\n// return the containing block\n\n\nfunction getContainingBlock(element) {\n var isFirefox = /firefox/i.test(getUAString());\n var isIE = /Trident/i.test(getUAString());\n\n if (isIE && isHTMLElement(element)) {\n // In IE 9, 10 and 11 fixed elements containing block is always established by the viewport\n var elementCss = getComputedStyle(element);\n\n if (elementCss.position === 'fixed') {\n return null;\n }\n }\n\n var currentNode = getParentNode(element);\n\n if (isShadowRoot(currentNode)) {\n currentNode = currentNode.host;\n }\n\n while (isHTMLElement(currentNode) && ['html', 'body'].indexOf(getNodeName(currentNode)) < 0) {\n var css = getComputedStyle(currentNode); // This is non-exhaustive but covers the most common CSS properties that\n // create a containing block.\n // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block\n\n if (css.transform !== 'none' || css.perspective !== 'none' || css.contain === 'paint' || ['transform', 'perspective'].indexOf(css.willChange) !== -1 || isFirefox && css.willChange === 'filter' || isFirefox && css.filter && css.filter !== 'none') {\n return currentNode;\n } else {\n currentNode = currentNode.parentNode;\n }\n }\n\n return null;\n} // Gets the closest ancestor positioned element. Handles some edge cases,\n// such as table ancestors and cross browser bugs.\n\n\nexport default function getOffsetParent(element) {\n var window = getWindow(element);\n var offsetParent = getTrueOffsetParent(element);\n\n while (offsetParent && isTableElement(offsetParent) && getComputedStyle(offsetParent).position === 'static') {\n offsetParent = getTrueOffsetParent(offsetParent);\n }\n\n if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle(offsetParent).position === 'static')) {\n return window;\n }\n\n return offsetParent || getContainingBlock(element) || window;\n}","export default function getMainAxisFromPlacement(placement) {\n return ['top', 'bottom'].indexOf(placement) >= 0 ? 'x' : 'y';\n}","import { max as mathMax, min as mathMin } from \"./math.js\";\nexport function within(min, value, max) {\n return mathMax(min, mathMin(value, max));\n}\nexport function withinMaxClamp(min, value, max) {\n var v = within(min, value, max);\n return v > max ? max : v;\n}","import getFreshSideObject from \"./getFreshSideObject.js\";\nexport default function mergePaddingObject(paddingObject) {\n return Object.assign({}, getFreshSideObject(), paddingObject);\n}","export default function getFreshSideObject() {\n return {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0\n };\n}","export default function expandToHashMap(value, keys) {\n return keys.reduce(function (hashMap, key) {\n hashMap[key] = value;\n return hashMap;\n }, {});\n}","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport contains from \"../dom-utils/contains.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport { within } from \"../utils/within.js\";\nimport mergePaddingObject from \"../utils/mergePaddingObject.js\";\nimport expandToHashMap from \"../utils/expandToHashMap.js\";\nimport { left, right, basePlacements, top, bottom } from \"../enums.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar toPaddingObject = function toPaddingObject(padding, state) {\n padding = typeof padding === 'function' ? padding(Object.assign({}, state.rects, {\n placement: state.placement\n })) : padding;\n return mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n};\n\nfunction arrow(_ref) {\n var _state$modifiersData$;\n\n var state = _ref.state,\n name = _ref.name,\n options = _ref.options;\n var arrowElement = state.elements.arrow;\n var popperOffsets = state.modifiersData.popperOffsets;\n var basePlacement = getBasePlacement(state.placement);\n var axis = getMainAxisFromPlacement(basePlacement);\n var isVertical = [left, right].indexOf(basePlacement) >= 0;\n var len = isVertical ? 'height' : 'width';\n\n if (!arrowElement || !popperOffsets) {\n return;\n }\n\n var paddingObject = toPaddingObject(options.padding, state);\n var arrowRect = getLayoutRect(arrowElement);\n var minProp = axis === 'y' ? top : left;\n var maxProp = axis === 'y' ? bottom : right;\n var endDiff = state.rects.reference[len] + state.rects.reference[axis] - popperOffsets[axis] - state.rects.popper[len];\n var startDiff = popperOffsets[axis] - state.rects.reference[axis];\n var arrowOffsetParent = getOffsetParent(arrowElement);\n var clientSize = arrowOffsetParent ? axis === 'y' ? arrowOffsetParent.clientHeight || 0 : arrowOffsetParent.clientWidth || 0 : 0;\n var centerToReference = endDiff / 2 - startDiff / 2; // Make sure the arrow doesn't overflow the popper if the center point is\n // outside of the popper bounds\n\n var min = paddingObject[minProp];\n var max = clientSize - arrowRect[len] - paddingObject[maxProp];\n var center = clientSize / 2 - arrowRect[len] / 2 + centerToReference;\n var offset = within(min, center, max); // Prevents breaking syntax highlighting...\n\n var axisProp = axis;\n state.modifiersData[name] = (_state$modifiersData$ = {}, _state$modifiersData$[axisProp] = offset, _state$modifiersData$.centerOffset = offset - center, _state$modifiersData$);\n}\n\nfunction effect(_ref2) {\n var state = _ref2.state,\n options = _ref2.options;\n var _options$element = options.element,\n arrowElement = _options$element === void 0 ? '[data-popper-arrow]' : _options$element;\n\n if (arrowElement == null) {\n return;\n } // CSS selector\n\n\n if (typeof arrowElement === 'string') {\n arrowElement = state.elements.popper.querySelector(arrowElement);\n\n if (!arrowElement) {\n return;\n }\n }\n\n if (!contains(state.elements.popper, arrowElement)) {\n return;\n }\n\n state.elements.arrow = arrowElement;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'arrow',\n enabled: true,\n phase: 'main',\n fn: arrow,\n effect: effect,\n requires: ['popperOffsets'],\n requiresIfExists: ['preventOverflow']\n};","export default function getVariation(placement) {\n return placement.split('-')[1];\n}","import { top, left, right, bottom, end } from \"../enums.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport getWindow from \"../dom-utils/getWindow.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport getComputedStyle from \"../dom-utils/getComputedStyle.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getVariation from \"../utils/getVariation.js\";\nimport { round } from \"../utils/math.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar unsetSides = {\n top: 'auto',\n right: 'auto',\n bottom: 'auto',\n left: 'auto'\n}; // Round the offsets to the nearest suitable subpixel based on the DPR.\n// Zooming can change the DPR, but it seems to report a value that will\n// cleanly divide the values into the appropriate subpixels.\n\nfunction roundOffsetsByDPR(_ref, win) {\n var x = _ref.x,\n y = _ref.y;\n var dpr = win.devicePixelRatio || 1;\n return {\n x: round(x * dpr) / dpr || 0,\n y: round(y * dpr) / dpr || 0\n };\n}\n\nexport function mapToStyles(_ref2) {\n var _Object$assign2;\n\n var popper = _ref2.popper,\n popperRect = _ref2.popperRect,\n placement = _ref2.placement,\n variation = _ref2.variation,\n offsets = _ref2.offsets,\n position = _ref2.position,\n gpuAcceleration = _ref2.gpuAcceleration,\n adaptive = _ref2.adaptive,\n roundOffsets = _ref2.roundOffsets,\n isFixed = _ref2.isFixed;\n var _offsets$x = offsets.x,\n x = _offsets$x === void 0 ? 0 : _offsets$x,\n _offsets$y = offsets.y,\n y = _offsets$y === void 0 ? 0 : _offsets$y;\n\n var _ref3 = typeof roundOffsets === 'function' ? roundOffsets({\n x: x,\n y: y\n }) : {\n x: x,\n y: y\n };\n\n x = _ref3.x;\n y = _ref3.y;\n var hasX = offsets.hasOwnProperty('x');\n var hasY = offsets.hasOwnProperty('y');\n var sideX = left;\n var sideY = top;\n var win = window;\n\n if (adaptive) {\n var offsetParent = getOffsetParent(popper);\n var heightProp = 'clientHeight';\n var widthProp = 'clientWidth';\n\n if (offsetParent === getWindow(popper)) {\n offsetParent = getDocumentElement(popper);\n\n if (getComputedStyle(offsetParent).position !== 'static' && position === 'absolute') {\n heightProp = 'scrollHeight';\n widthProp = 'scrollWidth';\n }\n } // $FlowFixMe[incompatible-cast]: force type refinement, we compare offsetParent with window above, but Flow doesn't detect it\n\n\n offsetParent = offsetParent;\n\n if (placement === top || (placement === left || placement === right) && variation === end) {\n sideY = bottom;\n var offsetY = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.height : // $FlowFixMe[prop-missing]\n offsetParent[heightProp];\n y -= offsetY - popperRect.height;\n y *= gpuAcceleration ? 1 : -1;\n }\n\n if (placement === left || (placement === top || placement === bottom) && variation === end) {\n sideX = right;\n var offsetX = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.width : // $FlowFixMe[prop-missing]\n offsetParent[widthProp];\n x -= offsetX - popperRect.width;\n x *= gpuAcceleration ? 1 : -1;\n }\n }\n\n var commonStyles = Object.assign({\n position: position\n }, adaptive && unsetSides);\n\n var _ref4 = roundOffsets === true ? roundOffsetsByDPR({\n x: x,\n y: y\n }, getWindow(popper)) : {\n x: x,\n y: y\n };\n\n x = _ref4.x;\n y = _ref4.y;\n\n if (gpuAcceleration) {\n var _Object$assign;\n\n return Object.assign({}, commonStyles, (_Object$assign = {}, _Object$assign[sideY] = hasY ? '0' : '', _Object$assign[sideX] = hasX ? '0' : '', _Object$assign.transform = (win.devicePixelRatio || 1) <= 1 ? \"translate(\" + x + \"px, \" + y + \"px)\" : \"translate3d(\" + x + \"px, \" + y + \"px, 0)\", _Object$assign));\n }\n\n return Object.assign({}, commonStyles, (_Object$assign2 = {}, _Object$assign2[sideY] = hasY ? y + \"px\" : '', _Object$assign2[sideX] = hasX ? x + \"px\" : '', _Object$assign2.transform = '', _Object$assign2));\n}\n\nfunction computeStyles(_ref5) {\n var state = _ref5.state,\n options = _ref5.options;\n var _options$gpuAccelerat = options.gpuAcceleration,\n gpuAcceleration = _options$gpuAccelerat === void 0 ? true : _options$gpuAccelerat,\n _options$adaptive = options.adaptive,\n adaptive = _options$adaptive === void 0 ? true : _options$adaptive,\n _options$roundOffsets = options.roundOffsets,\n roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets;\n var commonStyles = {\n placement: getBasePlacement(state.placement),\n variation: getVariation(state.placement),\n popper: state.elements.popper,\n popperRect: state.rects.popper,\n gpuAcceleration: gpuAcceleration,\n isFixed: state.options.strategy === 'fixed'\n };\n\n if (state.modifiersData.popperOffsets != null) {\n state.styles.popper = Object.assign({}, state.styles.popper, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.popperOffsets,\n position: state.options.strategy,\n adaptive: adaptive,\n roundOffsets: roundOffsets\n })));\n }\n\n if (state.modifiersData.arrow != null) {\n state.styles.arrow = Object.assign({}, state.styles.arrow, mapToStyles(Object.assign({}, commonStyles, {\n offsets: state.modifiersData.arrow,\n position: 'absolute',\n adaptive: false,\n roundOffsets: roundOffsets\n })));\n }\n\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-placement': state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'computeStyles',\n enabled: true,\n phase: 'beforeWrite',\n fn: computeStyles,\n data: {}\n};","import getWindow from \"../dom-utils/getWindow.js\"; // eslint-disable-next-line import/no-unused-modules\n\nvar passive = {\n passive: true\n};\n\nfunction effect(_ref) {\n var state = _ref.state,\n instance = _ref.instance,\n options = _ref.options;\n var _options$scroll = options.scroll,\n scroll = _options$scroll === void 0 ? true : _options$scroll,\n _options$resize = options.resize,\n resize = _options$resize === void 0 ? true : _options$resize;\n var window = getWindow(state.elements.popper);\n var scrollParents = [].concat(state.scrollParents.reference, state.scrollParents.popper);\n\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.addEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.addEventListener('resize', instance.update, passive);\n }\n\n return function () {\n if (scroll) {\n scrollParents.forEach(function (scrollParent) {\n scrollParent.removeEventListener('scroll', instance.update, passive);\n });\n }\n\n if (resize) {\n window.removeEventListener('resize', instance.update, passive);\n }\n };\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'eventListeners',\n enabled: true,\n phase: 'write',\n fn: function fn() {},\n effect: effect,\n data: {}\n};","var hash = {\n left: 'right',\n right: 'left',\n bottom: 'top',\n top: 'bottom'\n};\nexport default function getOppositePlacement(placement) {\n return placement.replace(/left|right|bottom|top/g, function (matched) {\n return hash[matched];\n });\n}","var hash = {\n start: 'end',\n end: 'start'\n};\nexport default function getOppositeVariationPlacement(placement) {\n return placement.replace(/start|end/g, function (matched) {\n return hash[matched];\n });\n}","import getWindow from \"./getWindow.js\";\nexport default function getWindowScroll(node) {\n var win = getWindow(node);\n var scrollLeft = win.pageXOffset;\n var scrollTop = win.pageYOffset;\n return {\n scrollLeft: scrollLeft,\n scrollTop: scrollTop\n };\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nexport default function getWindowScrollBarX(element) {\n // If has a CSS width greater than the viewport, then this will be\n // incorrect for RTL.\n // Popper 1 is broken in this case and never had a bug report so let's assume\n // it's not an issue. I don't think anyone ever specifies width on \n // anyway.\n // Browsers where the left scrollbar doesn't cause an issue report `0` for\n // this (e.g. Edge 2019, IE11, Safari)\n return getBoundingClientRect(getDocumentElement(element)).left + getWindowScroll(element).scrollLeft;\n}","import getComputedStyle from \"./getComputedStyle.js\";\nexport default function isScrollParent(element) {\n // Firefox wants us to check `-x` and `-y` variations as well\n var _getComputedStyle = getComputedStyle(element),\n overflow = _getComputedStyle.overflow,\n overflowX = _getComputedStyle.overflowX,\n overflowY = _getComputedStyle.overflowY;\n\n return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX);\n}","import getParentNode from \"./getParentNode.js\";\nimport isScrollParent from \"./isScrollParent.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nexport default function getScrollParent(node) {\n if (['html', 'body', '#document'].indexOf(getNodeName(node)) >= 0) {\n // $FlowFixMe[incompatible-return]: assume body is always available\n return node.ownerDocument.body;\n }\n\n if (isHTMLElement(node) && isScrollParent(node)) {\n return node;\n }\n\n return getScrollParent(getParentNode(node));\n}","import getScrollParent from \"./getScrollParent.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport getWindow from \"./getWindow.js\";\nimport isScrollParent from \"./isScrollParent.js\";\n/*\ngiven a DOM element, return the list of all scroll parents, up the list of ancesors\nuntil we get to the top window object. This list is what we attach scroll listeners\nto, because if any of these parent elements scroll, we'll need to re-calculate the\nreference element's position.\n*/\n\nexport default function listScrollParents(element, list) {\n var _element$ownerDocumen;\n\n if (list === void 0) {\n list = [];\n }\n\n var scrollParent = getScrollParent(element);\n var isBody = scrollParent === ((_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body);\n var win = getWindow(scrollParent);\n var target = isBody ? [win].concat(win.visualViewport || [], isScrollParent(scrollParent) ? scrollParent : []) : scrollParent;\n var updatedList = list.concat(target);\n return isBody ? updatedList : // $FlowFixMe[incompatible-call]: isBody tells us target will be an HTMLElement here\n updatedList.concat(listScrollParents(getParentNode(target)));\n}","export default function rectToClientRect(rect) {\n return Object.assign({}, rect, {\n left: rect.x,\n top: rect.y,\n right: rect.x + rect.width,\n bottom: rect.y + rect.height\n });\n}","import { viewport } from \"../enums.js\";\nimport getViewportRect from \"./getViewportRect.js\";\nimport getDocumentRect from \"./getDocumentRect.js\";\nimport listScrollParents from \"./listScrollParents.js\";\nimport getOffsetParent from \"./getOffsetParent.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport { isElement, isHTMLElement } from \"./instanceOf.js\";\nimport getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getParentNode from \"./getParentNode.js\";\nimport contains from \"./contains.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport rectToClientRect from \"../utils/rectToClientRect.js\";\nimport { max, min } from \"../utils/math.js\";\n\nfunction getInnerBoundingClientRect(element, strategy) {\n var rect = getBoundingClientRect(element, false, strategy === 'fixed');\n rect.top = rect.top + element.clientTop;\n rect.left = rect.left + element.clientLeft;\n rect.bottom = rect.top + element.clientHeight;\n rect.right = rect.left + element.clientWidth;\n rect.width = element.clientWidth;\n rect.height = element.clientHeight;\n rect.x = rect.left;\n rect.y = rect.top;\n return rect;\n}\n\nfunction getClientRectFromMixedType(element, clippingParent, strategy) {\n return clippingParent === viewport ? rectToClientRect(getViewportRect(element, strategy)) : isElement(clippingParent) ? getInnerBoundingClientRect(clippingParent, strategy) : rectToClientRect(getDocumentRect(getDocumentElement(element)));\n} // A \"clipping parent\" is an overflowable container with the characteristic of\n// clipping (or hiding) overflowing elements with a position different from\n// `initial`\n\n\nfunction getClippingParents(element) {\n var clippingParents = listScrollParents(getParentNode(element));\n var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle(element).position) >= 0;\n var clipperElement = canEscapeClipping && isHTMLElement(element) ? getOffsetParent(element) : element;\n\n if (!isElement(clipperElement)) {\n return [];\n } // $FlowFixMe[incompatible-return]: https://github.com/facebook/flow/issues/1414\n\n\n return clippingParents.filter(function (clippingParent) {\n return isElement(clippingParent) && contains(clippingParent, clipperElement) && getNodeName(clippingParent) !== 'body';\n });\n} // Gets the maximum area that the element is visible in due to any number of\n// clipping parents\n\n\nexport default function getClippingRect(element, boundary, rootBoundary, strategy) {\n var mainClippingParents = boundary === 'clippingParents' ? getClippingParents(element) : [].concat(boundary);\n var clippingParents = [].concat(mainClippingParents, [rootBoundary]);\n var firstClippingParent = clippingParents[0];\n var clippingRect = clippingParents.reduce(function (accRect, clippingParent) {\n var rect = getClientRectFromMixedType(element, clippingParent, strategy);\n accRect.top = max(rect.top, accRect.top);\n accRect.right = min(rect.right, accRect.right);\n accRect.bottom = min(rect.bottom, accRect.bottom);\n accRect.left = max(rect.left, accRect.left);\n return accRect;\n }, getClientRectFromMixedType(element, firstClippingParent, strategy));\n clippingRect.width = clippingRect.right - clippingRect.left;\n clippingRect.height = clippingRect.bottom - clippingRect.top;\n clippingRect.x = clippingRect.left;\n clippingRect.y = clippingRect.top;\n return clippingRect;\n}","import getWindow from \"./getWindow.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport isLayoutViewport from \"./isLayoutViewport.js\";\nexport default function getViewportRect(element, strategy) {\n var win = getWindow(element);\n var html = getDocumentElement(element);\n var visualViewport = win.visualViewport;\n var width = html.clientWidth;\n var height = html.clientHeight;\n var x = 0;\n var y = 0;\n\n if (visualViewport) {\n width = visualViewport.width;\n height = visualViewport.height;\n var layoutViewport = isLayoutViewport();\n\n if (layoutViewport || !layoutViewport && strategy === 'fixed') {\n x = visualViewport.offsetLeft;\n y = visualViewport.offsetTop;\n }\n }\n\n return {\n width: width,\n height: height,\n x: x + getWindowScrollBarX(element),\n y: y\n };\n}","import getDocumentElement from \"./getDocumentElement.js\";\nimport getComputedStyle from \"./getComputedStyle.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getWindowScroll from \"./getWindowScroll.js\";\nimport { max } from \"../utils/math.js\"; // Gets the entire size of the scrollable document area, even extending outside\n// of the `` and `` rect bounds if horizontally scrollable\n\nexport default function getDocumentRect(element) {\n var _element$ownerDocumen;\n\n var html = getDocumentElement(element);\n var winScroll = getWindowScroll(element);\n var body = (_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body;\n var width = max(html.scrollWidth, html.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0);\n var height = max(html.scrollHeight, html.clientHeight, body ? body.scrollHeight : 0, body ? body.clientHeight : 0);\n var x = -winScroll.scrollLeft + getWindowScrollBarX(element);\n var y = -winScroll.scrollTop;\n\n if (getComputedStyle(body || html).direction === 'rtl') {\n x += max(html.clientWidth, body ? body.clientWidth : 0) - width;\n }\n\n return {\n width: width,\n height: height,\n x: x,\n y: y\n };\n}","import getBasePlacement from \"./getBasePlacement.js\";\nimport getVariation from \"./getVariation.js\";\nimport getMainAxisFromPlacement from \"./getMainAxisFromPlacement.js\";\nimport { top, right, bottom, left, start, end } from \"../enums.js\";\nexport default function computeOffsets(_ref) {\n var reference = _ref.reference,\n element = _ref.element,\n placement = _ref.placement;\n var basePlacement = placement ? getBasePlacement(placement) : null;\n var variation = placement ? getVariation(placement) : null;\n var commonX = reference.x + reference.width / 2 - element.width / 2;\n var commonY = reference.y + reference.height / 2 - element.height / 2;\n var offsets;\n\n switch (basePlacement) {\n case top:\n offsets = {\n x: commonX,\n y: reference.y - element.height\n };\n break;\n\n case bottom:\n offsets = {\n x: commonX,\n y: reference.y + reference.height\n };\n break;\n\n case right:\n offsets = {\n x: reference.x + reference.width,\n y: commonY\n };\n break;\n\n case left:\n offsets = {\n x: reference.x - element.width,\n y: commonY\n };\n break;\n\n default:\n offsets = {\n x: reference.x,\n y: reference.y\n };\n }\n\n var mainAxis = basePlacement ? getMainAxisFromPlacement(basePlacement) : null;\n\n if (mainAxis != null) {\n var len = mainAxis === 'y' ? 'height' : 'width';\n\n switch (variation) {\n case start:\n offsets[mainAxis] = offsets[mainAxis] - (reference[len] / 2 - element[len] / 2);\n break;\n\n case end:\n offsets[mainAxis] = offsets[mainAxis] + (reference[len] / 2 - element[len] / 2);\n break;\n\n default:\n }\n }\n\n return offsets;\n}","import getClippingRect from \"../dom-utils/getClippingRect.js\";\nimport getDocumentElement from \"../dom-utils/getDocumentElement.js\";\nimport getBoundingClientRect from \"../dom-utils/getBoundingClientRect.js\";\nimport computeOffsets from \"./computeOffsets.js\";\nimport rectToClientRect from \"./rectToClientRect.js\";\nimport { clippingParents, reference, popper, bottom, top, right, basePlacements, viewport } from \"../enums.js\";\nimport { isElement } from \"../dom-utils/instanceOf.js\";\nimport mergePaddingObject from \"./mergePaddingObject.js\";\nimport expandToHashMap from \"./expandToHashMap.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport default function detectOverflow(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n _options$placement = _options.placement,\n placement = _options$placement === void 0 ? state.placement : _options$placement,\n _options$strategy = _options.strategy,\n strategy = _options$strategy === void 0 ? state.strategy : _options$strategy,\n _options$boundary = _options.boundary,\n boundary = _options$boundary === void 0 ? clippingParents : _options$boundary,\n _options$rootBoundary = _options.rootBoundary,\n rootBoundary = _options$rootBoundary === void 0 ? viewport : _options$rootBoundary,\n _options$elementConte = _options.elementContext,\n elementContext = _options$elementConte === void 0 ? popper : _options$elementConte,\n _options$altBoundary = _options.altBoundary,\n altBoundary = _options$altBoundary === void 0 ? false : _options$altBoundary,\n _options$padding = _options.padding,\n padding = _options$padding === void 0 ? 0 : _options$padding;\n var paddingObject = mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements));\n var altContext = elementContext === popper ? reference : popper;\n var popperRect = state.rects.popper;\n var element = state.elements[altBoundary ? altContext : elementContext];\n var clippingClientRect = getClippingRect(isElement(element) ? element : element.contextElement || getDocumentElement(state.elements.popper), boundary, rootBoundary, strategy);\n var referenceClientRect = getBoundingClientRect(state.elements.reference);\n var popperOffsets = computeOffsets({\n reference: referenceClientRect,\n element: popperRect,\n strategy: 'absolute',\n placement: placement\n });\n var popperClientRect = rectToClientRect(Object.assign({}, popperRect, popperOffsets));\n var elementClientRect = elementContext === popper ? popperClientRect : referenceClientRect; // positive = overflowing the clipping rect\n // 0 or negative = within the clipping rect\n\n var overflowOffsets = {\n top: clippingClientRect.top - elementClientRect.top + paddingObject.top,\n bottom: elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom,\n left: clippingClientRect.left - elementClientRect.left + paddingObject.left,\n right: elementClientRect.right - clippingClientRect.right + paddingObject.right\n };\n var offsetData = state.modifiersData.offset; // Offsets can be applied only to the popper element\n\n if (elementContext === popper && offsetData) {\n var offset = offsetData[placement];\n Object.keys(overflowOffsets).forEach(function (key) {\n var multiply = [right, bottom].indexOf(key) >= 0 ? 1 : -1;\n var axis = [top, bottom].indexOf(key) >= 0 ? 'y' : 'x';\n overflowOffsets[key] += offset[axis] * multiply;\n });\n }\n\n return overflowOffsets;\n}","import getOppositePlacement from \"../utils/getOppositePlacement.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getOppositeVariationPlacement from \"../utils/getOppositeVariationPlacement.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport computeAutoPlacement from \"../utils/computeAutoPlacement.js\";\nimport { bottom, top, start, right, left, auto } from \"../enums.js\";\nimport getVariation from \"../utils/getVariation.js\"; // eslint-disable-next-line import/no-unused-modules\n\nfunction getExpandedFallbackPlacements(placement) {\n if (getBasePlacement(placement) === auto) {\n return [];\n }\n\n var oppositePlacement = getOppositePlacement(placement);\n return [getOppositeVariationPlacement(placement), oppositePlacement, getOppositeVariationPlacement(oppositePlacement)];\n}\n\nfunction flip(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n\n if (state.modifiersData[name]._skip) {\n return;\n }\n\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? true : _options$altAxis,\n specifiedFallbackPlacements = options.fallbackPlacements,\n padding = options.padding,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n _options$flipVariatio = options.flipVariations,\n flipVariations = _options$flipVariatio === void 0 ? true : _options$flipVariatio,\n allowedAutoPlacements = options.allowedAutoPlacements;\n var preferredPlacement = state.options.placement;\n var basePlacement = getBasePlacement(preferredPlacement);\n var isBasePlacement = basePlacement === preferredPlacement;\n var fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipVariations ? [getOppositePlacement(preferredPlacement)] : getExpandedFallbackPlacements(preferredPlacement));\n var placements = [preferredPlacement].concat(fallbackPlacements).reduce(function (acc, placement) {\n return acc.concat(getBasePlacement(placement) === auto ? computeAutoPlacement(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n flipVariations: flipVariations,\n allowedAutoPlacements: allowedAutoPlacements\n }) : placement);\n }, []);\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var checksMap = new Map();\n var makeFallbackChecks = true;\n var firstFittingPlacement = placements[0];\n\n for (var i = 0; i < placements.length; i++) {\n var placement = placements[i];\n\n var _basePlacement = getBasePlacement(placement);\n\n var isStartVariation = getVariation(placement) === start;\n var isVertical = [top, bottom].indexOf(_basePlacement) >= 0;\n var len = isVertical ? 'width' : 'height';\n var overflow = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n altBoundary: altBoundary,\n padding: padding\n });\n var mainVariationSide = isVertical ? isStartVariation ? right : left : isStartVariation ? bottom : top;\n\n if (referenceRect[len] > popperRect[len]) {\n mainVariationSide = getOppositePlacement(mainVariationSide);\n }\n\n var altVariationSide = getOppositePlacement(mainVariationSide);\n var checks = [];\n\n if (checkMainAxis) {\n checks.push(overflow[_basePlacement] <= 0);\n }\n\n if (checkAltAxis) {\n checks.push(overflow[mainVariationSide] <= 0, overflow[altVariationSide] <= 0);\n }\n\n if (checks.every(function (check) {\n return check;\n })) {\n firstFittingPlacement = placement;\n makeFallbackChecks = false;\n break;\n }\n\n checksMap.set(placement, checks);\n }\n\n if (makeFallbackChecks) {\n // `2` may be desired in some cases – research later\n var numberOfChecks = flipVariations ? 3 : 1;\n\n var _loop = function _loop(_i) {\n var fittingPlacement = placements.find(function (placement) {\n var checks = checksMap.get(placement);\n\n if (checks) {\n return checks.slice(0, _i).every(function (check) {\n return check;\n });\n }\n });\n\n if (fittingPlacement) {\n firstFittingPlacement = fittingPlacement;\n return \"break\";\n }\n };\n\n for (var _i = numberOfChecks; _i > 0; _i--) {\n var _ret = _loop(_i);\n\n if (_ret === \"break\") break;\n }\n }\n\n if (state.placement !== firstFittingPlacement) {\n state.modifiersData[name]._skip = true;\n state.placement = firstFittingPlacement;\n state.reset = true;\n }\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'flip',\n enabled: true,\n phase: 'main',\n fn: flip,\n requiresIfExists: ['offset'],\n data: {\n _skip: false\n }\n};","import getVariation from \"./getVariation.js\";\nimport { variationPlacements, basePlacements, placements as allPlacements } from \"../enums.js\";\nimport detectOverflow from \"./detectOverflow.js\";\nimport getBasePlacement from \"./getBasePlacement.js\";\nexport default function computeAutoPlacement(state, options) {\n if (options === void 0) {\n options = {};\n }\n\n var _options = options,\n placement = _options.placement,\n boundary = _options.boundary,\n rootBoundary = _options.rootBoundary,\n padding = _options.padding,\n flipVariations = _options.flipVariations,\n _options$allowedAutoP = _options.allowedAutoPlacements,\n allowedAutoPlacements = _options$allowedAutoP === void 0 ? allPlacements : _options$allowedAutoP;\n var variation = getVariation(placement);\n var placements = variation ? flipVariations ? variationPlacements : variationPlacements.filter(function (placement) {\n return getVariation(placement) === variation;\n }) : basePlacements;\n var allowedPlacements = placements.filter(function (placement) {\n return allowedAutoPlacements.indexOf(placement) >= 0;\n });\n\n if (allowedPlacements.length === 0) {\n allowedPlacements = placements;\n } // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions...\n\n\n var overflows = allowedPlacements.reduce(function (acc, placement) {\n acc[placement] = detectOverflow(state, {\n placement: placement,\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding\n })[getBasePlacement(placement)];\n return acc;\n }, {});\n return Object.keys(overflows).sort(function (a, b) {\n return overflows[a] - overflows[b];\n });\n}","import { top, bottom, left, right } from \"../enums.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\n\nfunction getSideOffsets(overflow, rect, preventedOffsets) {\n if (preventedOffsets === void 0) {\n preventedOffsets = {\n x: 0,\n y: 0\n };\n }\n\n return {\n top: overflow.top - rect.height - preventedOffsets.y,\n right: overflow.right - rect.width + preventedOffsets.x,\n bottom: overflow.bottom - rect.height + preventedOffsets.y,\n left: overflow.left - rect.width - preventedOffsets.x\n };\n}\n\nfunction isAnySideFullyClipped(overflow) {\n return [top, right, bottom, left].some(function (side) {\n return overflow[side] >= 0;\n });\n}\n\nfunction hide(_ref) {\n var state = _ref.state,\n name = _ref.name;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var preventedOffsets = state.modifiersData.preventOverflow;\n var referenceOverflow = detectOverflow(state, {\n elementContext: 'reference'\n });\n var popperAltOverflow = detectOverflow(state, {\n altBoundary: true\n });\n var referenceClippingOffsets = getSideOffsets(referenceOverflow, referenceRect);\n var popperEscapeOffsets = getSideOffsets(popperAltOverflow, popperRect, preventedOffsets);\n var isReferenceHidden = isAnySideFullyClipped(referenceClippingOffsets);\n var hasPopperEscaped = isAnySideFullyClipped(popperEscapeOffsets);\n state.modifiersData[name] = {\n referenceClippingOffsets: referenceClippingOffsets,\n popperEscapeOffsets: popperEscapeOffsets,\n isReferenceHidden: isReferenceHidden,\n hasPopperEscaped: hasPopperEscaped\n };\n state.attributes.popper = Object.assign({}, state.attributes.popper, {\n 'data-popper-reference-hidden': isReferenceHidden,\n 'data-popper-escaped': hasPopperEscaped\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'hide',\n enabled: true,\n phase: 'main',\n requiresIfExists: ['preventOverflow'],\n fn: hide\n};","import getBasePlacement from \"../utils/getBasePlacement.js\";\nimport { top, left, right, placements } from \"../enums.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport function distanceAndSkiddingToXY(placement, rects, offset) {\n var basePlacement = getBasePlacement(placement);\n var invertDistance = [left, top].indexOf(basePlacement) >= 0 ? -1 : 1;\n\n var _ref = typeof offset === 'function' ? offset(Object.assign({}, rects, {\n placement: placement\n })) : offset,\n skidding = _ref[0],\n distance = _ref[1];\n\n skidding = skidding || 0;\n distance = (distance || 0) * invertDistance;\n return [left, right].indexOf(basePlacement) >= 0 ? {\n x: distance,\n y: skidding\n } : {\n x: skidding,\n y: distance\n };\n}\n\nfunction offset(_ref2) {\n var state = _ref2.state,\n options = _ref2.options,\n name = _ref2.name;\n var _options$offset = options.offset,\n offset = _options$offset === void 0 ? [0, 0] : _options$offset;\n var data = placements.reduce(function (acc, placement) {\n acc[placement] = distanceAndSkiddingToXY(placement, state.rects, offset);\n return acc;\n }, {});\n var _data$state$placement = data[state.placement],\n x = _data$state$placement.x,\n y = _data$state$placement.y;\n\n if (state.modifiersData.popperOffsets != null) {\n state.modifiersData.popperOffsets.x += x;\n state.modifiersData.popperOffsets.y += y;\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'offset',\n enabled: true,\n phase: 'main',\n requires: ['popperOffsets'],\n fn: offset\n};","import computeOffsets from \"../utils/computeOffsets.js\";\n\nfunction popperOffsets(_ref) {\n var state = _ref.state,\n name = _ref.name;\n // Offsets are the actual position the popper needs to have to be\n // properly positioned near its reference element\n // This is the most basic placement, and will be adjusted by\n // the modifiers in the next step\n state.modifiersData[name] = computeOffsets({\n reference: state.rects.reference,\n element: state.rects.popper,\n strategy: 'absolute',\n placement: state.placement\n });\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'popperOffsets',\n enabled: true,\n phase: 'read',\n fn: popperOffsets,\n data: {}\n};","import { top, left, right, bottom, start } from \"../enums.js\";\nimport getBasePlacement from \"../utils/getBasePlacement.js\";\nimport getMainAxisFromPlacement from \"../utils/getMainAxisFromPlacement.js\";\nimport getAltAxis from \"../utils/getAltAxis.js\";\nimport { within, withinMaxClamp } from \"../utils/within.js\";\nimport getLayoutRect from \"../dom-utils/getLayoutRect.js\";\nimport getOffsetParent from \"../dom-utils/getOffsetParent.js\";\nimport detectOverflow from \"../utils/detectOverflow.js\";\nimport getVariation from \"../utils/getVariation.js\";\nimport getFreshSideObject from \"../utils/getFreshSideObject.js\";\nimport { min as mathMin, max as mathMax } from \"../utils/math.js\";\n\nfunction preventOverflow(_ref) {\n var state = _ref.state,\n options = _ref.options,\n name = _ref.name;\n var _options$mainAxis = options.mainAxis,\n checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis,\n _options$altAxis = options.altAxis,\n checkAltAxis = _options$altAxis === void 0 ? false : _options$altAxis,\n boundary = options.boundary,\n rootBoundary = options.rootBoundary,\n altBoundary = options.altBoundary,\n padding = options.padding,\n _options$tether = options.tether,\n tether = _options$tether === void 0 ? true : _options$tether,\n _options$tetherOffset = options.tetherOffset,\n tetherOffset = _options$tetherOffset === void 0 ? 0 : _options$tetherOffset;\n var overflow = detectOverflow(state, {\n boundary: boundary,\n rootBoundary: rootBoundary,\n padding: padding,\n altBoundary: altBoundary\n });\n var basePlacement = getBasePlacement(state.placement);\n var variation = getVariation(state.placement);\n var isBasePlacement = !variation;\n var mainAxis = getMainAxisFromPlacement(basePlacement);\n var altAxis = getAltAxis(mainAxis);\n var popperOffsets = state.modifiersData.popperOffsets;\n var referenceRect = state.rects.reference;\n var popperRect = state.rects.popper;\n var tetherOffsetValue = typeof tetherOffset === 'function' ? tetherOffset(Object.assign({}, state.rects, {\n placement: state.placement\n })) : tetherOffset;\n var normalizedTetherOffsetValue = typeof tetherOffsetValue === 'number' ? {\n mainAxis: tetherOffsetValue,\n altAxis: tetherOffsetValue\n } : Object.assign({\n mainAxis: 0,\n altAxis: 0\n }, tetherOffsetValue);\n var offsetModifierState = state.modifiersData.offset ? state.modifiersData.offset[state.placement] : null;\n var data = {\n x: 0,\n y: 0\n };\n\n if (!popperOffsets) {\n return;\n }\n\n if (checkMainAxis) {\n var _offsetModifierState$;\n\n var mainSide = mainAxis === 'y' ? top : left;\n var altSide = mainAxis === 'y' ? bottom : right;\n var len = mainAxis === 'y' ? 'height' : 'width';\n var offset = popperOffsets[mainAxis];\n var min = offset + overflow[mainSide];\n var max = offset - overflow[altSide];\n var additive = tether ? -popperRect[len] / 2 : 0;\n var minLen = variation === start ? referenceRect[len] : popperRect[len];\n var maxLen = variation === start ? -popperRect[len] : -referenceRect[len]; // We need to include the arrow in the calculation so the arrow doesn't go\n // outside the reference bounds\n\n var arrowElement = state.elements.arrow;\n var arrowRect = tether && arrowElement ? getLayoutRect(arrowElement) : {\n width: 0,\n height: 0\n };\n var arrowPaddingObject = state.modifiersData['arrow#persistent'] ? state.modifiersData['arrow#persistent'].padding : getFreshSideObject();\n var arrowPaddingMin = arrowPaddingObject[mainSide];\n var arrowPaddingMax = arrowPaddingObject[altSide]; // If the reference length is smaller than the arrow length, we don't want\n // to include its full size in the calculation. If the reference is small\n // and near the edge of a boundary, the popper can overflow even if the\n // reference is not overflowing as well (e.g. virtual elements with no\n // width or height)\n\n var arrowLen = within(0, referenceRect[len], arrowRect[len]);\n var minOffset = isBasePlacement ? referenceRect[len] / 2 - additive - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis : minLen - arrowLen - arrowPaddingMin - normalizedTetherOffsetValue.mainAxis;\n var maxOffset = isBasePlacement ? -referenceRect[len] / 2 + additive + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis : maxLen + arrowLen + arrowPaddingMax + normalizedTetherOffsetValue.mainAxis;\n var arrowOffsetParent = state.elements.arrow && getOffsetParent(state.elements.arrow);\n var clientOffset = arrowOffsetParent ? mainAxis === 'y' ? arrowOffsetParent.clientTop || 0 : arrowOffsetParent.clientLeft || 0 : 0;\n var offsetModifierValue = (_offsetModifierState$ = offsetModifierState == null ? void 0 : offsetModifierState[mainAxis]) != null ? _offsetModifierState$ : 0;\n var tetherMin = offset + minOffset - offsetModifierValue - clientOffset;\n var tetherMax = offset + maxOffset - offsetModifierValue;\n var preventedOffset = within(tether ? mathMin(min, tetherMin) : min, offset, tether ? mathMax(max, tetherMax) : max);\n popperOffsets[mainAxis] = preventedOffset;\n data[mainAxis] = preventedOffset - offset;\n }\n\n if (checkAltAxis) {\n var _offsetModifierState$2;\n\n var _mainSide = mainAxis === 'x' ? top : left;\n\n var _altSide = mainAxis === 'x' ? bottom : right;\n\n var _offset = popperOffsets[altAxis];\n\n var _len = altAxis === 'y' ? 'height' : 'width';\n\n var _min = _offset + overflow[_mainSide];\n\n var _max = _offset - overflow[_altSide];\n\n var isOriginSide = [top, left].indexOf(basePlacement) !== -1;\n\n var _offsetModifierValue = (_offsetModifierState$2 = offsetModifierState == null ? void 0 : offsetModifierState[altAxis]) != null ? _offsetModifierState$2 : 0;\n\n var _tetherMin = isOriginSide ? _min : _offset - referenceRect[_len] - popperRect[_len] - _offsetModifierValue + normalizedTetherOffsetValue.altAxis;\n\n var _tetherMax = isOriginSide ? _offset + referenceRect[_len] + popperRect[_len] - _offsetModifierValue - normalizedTetherOffsetValue.altAxis : _max;\n\n var _preventedOffset = tether && isOriginSide ? withinMaxClamp(_tetherMin, _offset, _tetherMax) : within(tether ? _tetherMin : _min, _offset, tether ? _tetherMax : _max);\n\n popperOffsets[altAxis] = _preventedOffset;\n data[altAxis] = _preventedOffset - _offset;\n }\n\n state.modifiersData[name] = data;\n} // eslint-disable-next-line import/no-unused-modules\n\n\nexport default {\n name: 'preventOverflow',\n enabled: true,\n phase: 'main',\n fn: preventOverflow,\n requiresIfExists: ['offset']\n};","export default function getAltAxis(axis) {\n return axis === 'x' ? 'y' : 'x';\n}","import getBoundingClientRect from \"./getBoundingClientRect.js\";\nimport getNodeScroll from \"./getNodeScroll.js\";\nimport getNodeName from \"./getNodeName.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getWindowScrollBarX from \"./getWindowScrollBarX.js\";\nimport getDocumentElement from \"./getDocumentElement.js\";\nimport isScrollParent from \"./isScrollParent.js\";\nimport { round } from \"../utils/math.js\";\n\nfunction isElementScaled(element) {\n var rect = element.getBoundingClientRect();\n var scaleX = round(rect.width) / element.offsetWidth || 1;\n var scaleY = round(rect.height) / element.offsetHeight || 1;\n return scaleX !== 1 || scaleY !== 1;\n} // Returns the composite rect of an element relative to its offsetParent.\n// Composite means it takes into account transforms as well as layout.\n\n\nexport default function getCompositeRect(elementOrVirtualElement, offsetParent, isFixed) {\n if (isFixed === void 0) {\n isFixed = false;\n }\n\n var isOffsetParentAnElement = isHTMLElement(offsetParent);\n var offsetParentIsScaled = isHTMLElement(offsetParent) && isElementScaled(offsetParent);\n var documentElement = getDocumentElement(offsetParent);\n var rect = getBoundingClientRect(elementOrVirtualElement, offsetParentIsScaled, isFixed);\n var scroll = {\n scrollLeft: 0,\n scrollTop: 0\n };\n var offsets = {\n x: 0,\n y: 0\n };\n\n if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) {\n if (getNodeName(offsetParent) !== 'body' || // https://github.com/popperjs/popper-core/issues/1078\n isScrollParent(documentElement)) {\n scroll = getNodeScroll(offsetParent);\n }\n\n if (isHTMLElement(offsetParent)) {\n offsets = getBoundingClientRect(offsetParent, true);\n offsets.x += offsetParent.clientLeft;\n offsets.y += offsetParent.clientTop;\n } else if (documentElement) {\n offsets.x = getWindowScrollBarX(documentElement);\n }\n }\n\n return {\n x: rect.left + scroll.scrollLeft - offsets.x,\n y: rect.top + scroll.scrollTop - offsets.y,\n width: rect.width,\n height: rect.height\n };\n}","import getWindowScroll from \"./getWindowScroll.js\";\nimport getWindow from \"./getWindow.js\";\nimport { isHTMLElement } from \"./instanceOf.js\";\nimport getHTMLElementScroll from \"./getHTMLElementScroll.js\";\nexport default function getNodeScroll(node) {\n if (node === getWindow(node) || !isHTMLElement(node)) {\n return getWindowScroll(node);\n } else {\n return getHTMLElementScroll(node);\n }\n}","export default function getHTMLElementScroll(element) {\n return {\n scrollLeft: element.scrollLeft,\n scrollTop: element.scrollTop\n };\n}","import { modifierPhases } from \"../enums.js\"; // source: https://stackoverflow.com/questions/49875255\n\nfunction order(modifiers) {\n var map = new Map();\n var visited = new Set();\n var result = [];\n modifiers.forEach(function (modifier) {\n map.set(modifier.name, modifier);\n }); // On visiting object, check for its dependencies and visit them recursively\n\n function sort(modifier) {\n visited.add(modifier.name);\n var requires = [].concat(modifier.requires || [], modifier.requiresIfExists || []);\n requires.forEach(function (dep) {\n if (!visited.has(dep)) {\n var depModifier = map.get(dep);\n\n if (depModifier) {\n sort(depModifier);\n }\n }\n });\n result.push(modifier);\n }\n\n modifiers.forEach(function (modifier) {\n if (!visited.has(modifier.name)) {\n // check for visited object\n sort(modifier);\n }\n });\n return result;\n}\n\nexport default function orderModifiers(modifiers) {\n // order based on dependencies\n var orderedModifiers = order(modifiers); // order based on phase\n\n return modifierPhases.reduce(function (acc, phase) {\n return acc.concat(orderedModifiers.filter(function (modifier) {\n return modifier.phase === phase;\n }));\n }, []);\n}","import getCompositeRect from \"./dom-utils/getCompositeRect.js\";\nimport getLayoutRect from \"./dom-utils/getLayoutRect.js\";\nimport listScrollParents from \"./dom-utils/listScrollParents.js\";\nimport getOffsetParent from \"./dom-utils/getOffsetParent.js\";\nimport orderModifiers from \"./utils/orderModifiers.js\";\nimport debounce from \"./utils/debounce.js\";\nimport mergeByName from \"./utils/mergeByName.js\";\nimport detectOverflow from \"./utils/detectOverflow.js\";\nimport { isElement } from \"./dom-utils/instanceOf.js\";\nvar DEFAULT_OPTIONS = {\n placement: 'bottom',\n modifiers: [],\n strategy: 'absolute'\n};\n\nfunction areValidElements() {\n for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n args[_key] = arguments[_key];\n }\n\n return !args.some(function (element) {\n return !(element && typeof element.getBoundingClientRect === 'function');\n });\n}\n\nexport function popperGenerator(generatorOptions) {\n if (generatorOptions === void 0) {\n generatorOptions = {};\n }\n\n var _generatorOptions = generatorOptions,\n _generatorOptions$def = _generatorOptions.defaultModifiers,\n defaultModifiers = _generatorOptions$def === void 0 ? [] : _generatorOptions$def,\n _generatorOptions$def2 = _generatorOptions.defaultOptions,\n defaultOptions = _generatorOptions$def2 === void 0 ? DEFAULT_OPTIONS : _generatorOptions$def2;\n return function createPopper(reference, popper, options) {\n if (options === void 0) {\n options = defaultOptions;\n }\n\n var state = {\n placement: 'bottom',\n orderedModifiers: [],\n options: Object.assign({}, DEFAULT_OPTIONS, defaultOptions),\n modifiersData: {},\n elements: {\n reference: reference,\n popper: popper\n },\n attributes: {},\n styles: {}\n };\n var effectCleanupFns = [];\n var isDestroyed = false;\n var instance = {\n state: state,\n setOptions: function setOptions(setOptionsAction) {\n var options = typeof setOptionsAction === 'function' ? setOptionsAction(state.options) : setOptionsAction;\n cleanupModifierEffects();\n state.options = Object.assign({}, defaultOptions, state.options, options);\n state.scrollParents = {\n reference: isElement(reference) ? listScrollParents(reference) : reference.contextElement ? listScrollParents(reference.contextElement) : [],\n popper: listScrollParents(popper)\n }; // Orders the modifiers based on their dependencies and `phase`\n // properties\n\n var orderedModifiers = orderModifiers(mergeByName([].concat(defaultModifiers, state.options.modifiers))); // Strip out disabled modifiers\n\n state.orderedModifiers = orderedModifiers.filter(function (m) {\n return m.enabled;\n });\n runModifierEffects();\n return instance.update();\n },\n // Sync update – it will always be executed, even if not necessary. This\n // is useful for low frequency updates where sync behavior simplifies the\n // logic.\n // For high frequency updates (e.g. `resize` and `scroll` events), always\n // prefer the async Popper#update method\n forceUpdate: function forceUpdate() {\n if (isDestroyed) {\n return;\n }\n\n var _state$elements = state.elements,\n reference = _state$elements.reference,\n popper = _state$elements.popper; // Don't proceed if `reference` or `popper` are not valid elements\n // anymore\n\n if (!areValidElements(reference, popper)) {\n return;\n } // Store the reference and popper rects to be read by modifiers\n\n\n state.rects = {\n reference: getCompositeRect(reference, getOffsetParent(popper), state.options.strategy === 'fixed'),\n popper: getLayoutRect(popper)\n }; // Modifiers have the ability to reset the current update cycle. The\n // most common use case for this is the `flip` modifier changing the\n // placement, which then needs to re-run all the modifiers, because the\n // logic was previously ran for the previous placement and is therefore\n // stale/incorrect\n\n state.reset = false;\n state.placement = state.options.placement; // On each update cycle, the `modifiersData` property for each modifier\n // is filled with the initial data specified by the modifier. This means\n // it doesn't persist and is fresh on each update.\n // To ensure persistent data, use `${name}#persistent`\n\n state.orderedModifiers.forEach(function (modifier) {\n return state.modifiersData[modifier.name] = Object.assign({}, modifier.data);\n });\n\n for (var index = 0; index < state.orderedModifiers.length; index++) {\n if (state.reset === true) {\n state.reset = false;\n index = -1;\n continue;\n }\n\n var _state$orderedModifie = state.orderedModifiers[index],\n fn = _state$orderedModifie.fn,\n _state$orderedModifie2 = _state$orderedModifie.options,\n _options = _state$orderedModifie2 === void 0 ? {} : _state$orderedModifie2,\n name = _state$orderedModifie.name;\n\n if (typeof fn === 'function') {\n state = fn({\n state: state,\n options: _options,\n name: name,\n instance: instance\n }) || state;\n }\n }\n },\n // Async and optimistically optimized update – it will not be executed if\n // not necessary (debounced to run at most once-per-tick)\n update: debounce(function () {\n return new Promise(function (resolve) {\n instance.forceUpdate();\n resolve(state);\n });\n }),\n destroy: function destroy() {\n cleanupModifierEffects();\n isDestroyed = true;\n }\n };\n\n if (!areValidElements(reference, popper)) {\n return instance;\n }\n\n instance.setOptions(options).then(function (state) {\n if (!isDestroyed && options.onFirstUpdate) {\n options.onFirstUpdate(state);\n }\n }); // Modifiers have the ability to execute arbitrary code before the first\n // update cycle runs. They will be executed in the same order as the update\n // cycle. This is useful when a modifier adds some persistent data that\n // other modifiers need to use, but the modifier is run after the dependent\n // one.\n\n function runModifierEffects() {\n state.orderedModifiers.forEach(function (_ref) {\n var name = _ref.name,\n _ref$options = _ref.options,\n options = _ref$options === void 0 ? {} : _ref$options,\n effect = _ref.effect;\n\n if (typeof effect === 'function') {\n var cleanupFn = effect({\n state: state,\n name: name,\n instance: instance,\n options: options\n });\n\n var noopFn = function noopFn() {};\n\n effectCleanupFns.push(cleanupFn || noopFn);\n }\n });\n }\n\n function cleanupModifierEffects() {\n effectCleanupFns.forEach(function (fn) {\n return fn();\n });\n effectCleanupFns = [];\n }\n\n return instance;\n };\n}\nexport var createPopper = /*#__PURE__*/popperGenerator(); // eslint-disable-next-line import/no-unused-modules\n\nexport { detectOverflow };","export default function debounce(fn) {\n var pending;\n return function () {\n if (!pending) {\n pending = new Promise(function (resolve) {\n Promise.resolve().then(function () {\n pending = undefined;\n resolve(fn());\n });\n });\n }\n\n return pending;\n };\n}","export default function mergeByName(modifiers) {\n var merged = modifiers.reduce(function (merged, current) {\n var existing = merged[current.name];\n merged[current.name] = existing ? Object.assign({}, existing, current, {\n options: Object.assign({}, existing.options, current.options),\n data: Object.assign({}, existing.data, current.data)\n }) : current;\n return merged;\n }, {}); // IE11 does not support Object.values\n\n return Object.keys(merged).map(function (key) {\n return merged[key];\n });\n}","import { popperGenerator, detectOverflow } from \"./createPopper.js\";\nimport eventListeners from \"./modifiers/eventListeners.js\";\nimport popperOffsets from \"./modifiers/popperOffsets.js\";\nimport computeStyles from \"./modifiers/computeStyles.js\";\nimport applyStyles from \"./modifiers/applyStyles.js\";\nimport offset from \"./modifiers/offset.js\";\nimport flip from \"./modifiers/flip.js\";\nimport preventOverflow from \"./modifiers/preventOverflow.js\";\nimport arrow from \"./modifiers/arrow.js\";\nimport hide from \"./modifiers/hide.js\";\nvar defaultModifiers = [eventListeners, popperOffsets, computeStyles, applyStyles, offset, flip, preventOverflow, arrow, hide];\nvar createPopper = /*#__PURE__*/popperGenerator({\n defaultModifiers: defaultModifiers\n}); // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow }; // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper as createPopperLite } from \"./popper-lite.js\"; // eslint-disable-next-line import/no-unused-modules\n\nexport * from \"./modifiers/index.js\";","import { popperGenerator, detectOverflow } from \"./createPopper.js\";\nimport eventListeners from \"./modifiers/eventListeners.js\";\nimport popperOffsets from \"./modifiers/popperOffsets.js\";\nimport computeStyles from \"./modifiers/computeStyles.js\";\nimport applyStyles from \"./modifiers/applyStyles.js\";\nvar defaultModifiers = [eventListeners, popperOffsets, computeStyles, applyStyles];\nvar createPopper = /*#__PURE__*/popperGenerator({\n defaultModifiers: defaultModifiers\n}); // eslint-disable-next-line import/no-unused-modules\n\nexport { createPopper, popperGenerator, defaultModifiers, detectOverflow };","/*!\n * Bootstrap v5.3.3 (https://getbootstrap.com/)\n * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\nimport * as Popper from '@popperjs/core';\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/data.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n/**\n * Constants\n */\n\nconst elementMap = new Map();\nconst Data = {\n set(element, key, instance) {\n if (!elementMap.has(element)) {\n elementMap.set(element, new Map());\n }\n const instanceMap = elementMap.get(element);\n\n // make it clear we only want one instance per element\n // can be removed later when multiple key/instances are fine to be used\n if (!instanceMap.has(key) && instanceMap.size !== 0) {\n // eslint-disable-next-line no-console\n console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`);\n return;\n }\n instanceMap.set(key, instance);\n },\n get(element, key) {\n if (elementMap.has(element)) {\n return elementMap.get(element).get(key) || null;\n }\n return null;\n },\n remove(element, key) {\n if (!elementMap.has(element)) {\n return;\n }\n const instanceMap = elementMap.get(element);\n instanceMap.delete(key);\n\n // free up element references if there are no instances left for an element\n if (instanceMap.size === 0) {\n elementMap.delete(element);\n }\n }\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/index.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst MAX_UID = 1000000;\nconst MILLISECONDS_MULTIPLIER = 1000;\nconst TRANSITION_END = 'transitionend';\n\n/**\n * Properly escape IDs selectors to handle weird IDs\n * @param {string} selector\n * @returns {string}\n */\nconst parseSelector = selector => {\n if (selector && window.CSS && window.CSS.escape) {\n // document.querySelector needs escaping to handle IDs (html5+) containing for instance /\n selector = selector.replace(/#([^\\s\"#']+)/g, (match, id) => `#${CSS.escape(id)}`);\n }\n return selector;\n};\n\n// Shout-out Angus Croll (https://goo.gl/pxwQGp)\nconst toType = object => {\n if (object === null || object === undefined) {\n return `${object}`;\n }\n return Object.prototype.toString.call(object).match(/\\s([a-z]+)/i)[1].toLowerCase();\n};\n\n/**\n * Public Util API\n */\n\nconst getUID = prefix => {\n do {\n prefix += Math.floor(Math.random() * MAX_UID);\n } while (document.getElementById(prefix));\n return prefix;\n};\nconst getTransitionDurationFromElement = element => {\n if (!element) {\n return 0;\n }\n\n // Get transition-duration of the element\n let {\n transitionDuration,\n transitionDelay\n } = window.getComputedStyle(element);\n const floatTransitionDuration = Number.parseFloat(transitionDuration);\n const floatTransitionDelay = Number.parseFloat(transitionDelay);\n\n // Return 0 if element or transition duration is not found\n if (!floatTransitionDuration && !floatTransitionDelay) {\n return 0;\n }\n\n // If multiple durations are defined, take the first\n transitionDuration = transitionDuration.split(',')[0];\n transitionDelay = transitionDelay.split(',')[0];\n return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER;\n};\nconst triggerTransitionEnd = element => {\n element.dispatchEvent(new Event(TRANSITION_END));\n};\nconst isElement = object => {\n if (!object || typeof object !== 'object') {\n return false;\n }\n if (typeof object.jquery !== 'undefined') {\n object = object[0];\n }\n return typeof object.nodeType !== 'undefined';\n};\nconst getElement = object => {\n // it's a jQuery object or a node element\n if (isElement(object)) {\n return object.jquery ? object[0] : object;\n }\n if (typeof object === 'string' && object.length > 0) {\n return document.querySelector(parseSelector(object));\n }\n return null;\n};\nconst isVisible = element => {\n if (!isElement(element) || element.getClientRects().length === 0) {\n return false;\n }\n const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible';\n // Handle `details` element as its content may falsie appear visible when it is closed\n const closedDetails = element.closest('details:not([open])');\n if (!closedDetails) {\n return elementIsVisible;\n }\n if (closedDetails !== element) {\n const summary = element.closest('summary');\n if (summary && summary.parentNode !== closedDetails) {\n return false;\n }\n if (summary === null) {\n return false;\n }\n }\n return elementIsVisible;\n};\nconst isDisabled = element => {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n return true;\n }\n if (element.classList.contains('disabled')) {\n return true;\n }\n if (typeof element.disabled !== 'undefined') {\n return element.disabled;\n }\n return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false';\n};\nconst findShadowRoot = element => {\n if (!document.documentElement.attachShadow) {\n return null;\n }\n\n // Can find the shadow root otherwise it'll return the document\n if (typeof element.getRootNode === 'function') {\n const root = element.getRootNode();\n return root instanceof ShadowRoot ? root : null;\n }\n if (element instanceof ShadowRoot) {\n return element;\n }\n\n // when we don't find a shadow root\n if (!element.parentNode) {\n return null;\n }\n return findShadowRoot(element.parentNode);\n};\nconst noop = () => {};\n\n/**\n * Trick to restart an element's animation\n *\n * @param {HTMLElement} element\n * @return void\n *\n * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation\n */\nconst reflow = element => {\n element.offsetHeight; // eslint-disable-line no-unused-expressions\n};\nconst getjQuery = () => {\n if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {\n return window.jQuery;\n }\n return null;\n};\nconst DOMContentLoadedCallbacks = [];\nconst onDOMContentLoaded = callback => {\n if (document.readyState === 'loading') {\n // add listener on the first call when the document is in loading state\n if (!DOMContentLoadedCallbacks.length) {\n document.addEventListener('DOMContentLoaded', () => {\n for (const callback of DOMContentLoadedCallbacks) {\n callback();\n }\n });\n }\n DOMContentLoadedCallbacks.push(callback);\n } else {\n callback();\n }\n};\nconst isRTL = () => document.documentElement.dir === 'rtl';\nconst defineJQueryPlugin = plugin => {\n onDOMContentLoaded(() => {\n const $ = getjQuery();\n /* istanbul ignore if */\n if ($) {\n const name = plugin.NAME;\n const JQUERY_NO_CONFLICT = $.fn[name];\n $.fn[name] = plugin.jQueryInterface;\n $.fn[name].Constructor = plugin;\n $.fn[name].noConflict = () => {\n $.fn[name] = JQUERY_NO_CONFLICT;\n return plugin.jQueryInterface;\n };\n }\n });\n};\nconst execute = (possibleCallback, args = [], defaultValue = possibleCallback) => {\n return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue;\n};\nconst executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {\n if (!waitForTransition) {\n execute(callback);\n return;\n }\n const durationPadding = 5;\n const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding;\n let called = false;\n const handler = ({\n target\n }) => {\n if (target !== transitionElement) {\n return;\n }\n called = true;\n transitionElement.removeEventListener(TRANSITION_END, handler);\n execute(callback);\n };\n transitionElement.addEventListener(TRANSITION_END, handler);\n setTimeout(() => {\n if (!called) {\n triggerTransitionEnd(transitionElement);\n }\n }, emulatedDuration);\n};\n\n/**\n * Return the previous/next element of a list.\n *\n * @param {array} list The list of elements\n * @param activeElement The active element\n * @param shouldGetNext Choose to get next or previous element\n * @param isCycleAllowed\n * @return {Element|elem} The proper element\n */\nconst getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => {\n const listLength = list.length;\n let index = list.indexOf(activeElement);\n\n // if the element does not exist in the list return an element\n // depending on the direction and if cycle is allowed\n if (index === -1) {\n return !shouldGetNext && isCycleAllowed ? list[listLength - 1] : list[0];\n }\n index += shouldGetNext ? 1 : -1;\n if (isCycleAllowed) {\n index = (index + listLength) % listLength;\n }\n return list[Math.max(0, Math.min(index, listLength - 1))];\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/event-handler.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst namespaceRegex = /[^.]*(?=\\..*)\\.|.*/;\nconst stripNameRegex = /\\..*/;\nconst stripUidRegex = /::\\d+$/;\nconst eventRegistry = {}; // Events storage\nlet uidEvent = 1;\nconst customEvents = {\n mouseenter: 'mouseover',\n mouseleave: 'mouseout'\n};\nconst nativeEvents = new Set(['click', 'dblclick', 'mouseup', 'mousedown', 'contextmenu', 'mousewheel', 'DOMMouseScroll', 'mouseover', 'mouseout', 'mousemove', 'selectstart', 'selectend', 'keydown', 'keypress', 'keyup', 'orientationchange', 'touchstart', 'touchmove', 'touchend', 'touchcancel', 'pointerdown', 'pointermove', 'pointerup', 'pointerleave', 'pointercancel', 'gesturestart', 'gesturechange', 'gestureend', 'focus', 'blur', 'change', 'reset', 'select', 'submit', 'focusin', 'focusout', 'load', 'unload', 'beforeunload', 'resize', 'move', 'DOMContentLoaded', 'readystatechange', 'error', 'abort', 'scroll']);\n\n/**\n * Private methods\n */\n\nfunction makeEventUid(element, uid) {\n return uid && `${uid}::${uidEvent++}` || element.uidEvent || uidEvent++;\n}\nfunction getElementEvents(element) {\n const uid = makeEventUid(element);\n element.uidEvent = uid;\n eventRegistry[uid] = eventRegistry[uid] || {};\n return eventRegistry[uid];\n}\nfunction bootstrapHandler(element, fn) {\n return function handler(event) {\n hydrateObj(event, {\n delegateTarget: element\n });\n if (handler.oneOff) {\n EventHandler.off(element, event.type, fn);\n }\n return fn.apply(element, [event]);\n };\n}\nfunction bootstrapDelegationHandler(element, selector, fn) {\n return function handler(event) {\n const domElements = element.querySelectorAll(selector);\n for (let {\n target\n } = event; target && target !== this; target = target.parentNode) {\n for (const domElement of domElements) {\n if (domElement !== target) {\n continue;\n }\n hydrateObj(event, {\n delegateTarget: target\n });\n if (handler.oneOff) {\n EventHandler.off(element, event.type, selector, fn);\n }\n return fn.apply(target, [event]);\n }\n }\n };\n}\nfunction findHandler(events, callable, delegationSelector = null) {\n return Object.values(events).find(event => event.callable === callable && event.delegationSelector === delegationSelector);\n}\nfunction normalizeParameters(originalTypeEvent, handler, delegationFunction) {\n const isDelegated = typeof handler === 'string';\n // TODO: tooltip passes `false` instead of selector, so we need to check\n const callable = isDelegated ? delegationFunction : handler || delegationFunction;\n let typeEvent = getTypeEvent(originalTypeEvent);\n if (!nativeEvents.has(typeEvent)) {\n typeEvent = originalTypeEvent;\n }\n return [isDelegated, callable, typeEvent];\n}\nfunction addHandler(element, originalTypeEvent, handler, delegationFunction, oneOff) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return;\n }\n let [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction);\n\n // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position\n // this prevents the handler from being dispatched the same way as mouseover or mouseout does\n if (originalTypeEvent in customEvents) {\n const wrapFunction = fn => {\n return function (event) {\n if (!event.relatedTarget || event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget)) {\n return fn.call(this, event);\n }\n };\n };\n callable = wrapFunction(callable);\n }\n const events = getElementEvents(element);\n const handlers = events[typeEvent] || (events[typeEvent] = {});\n const previousFunction = findHandler(handlers, callable, isDelegated ? handler : null);\n if (previousFunction) {\n previousFunction.oneOff = previousFunction.oneOff && oneOff;\n return;\n }\n const uid = makeEventUid(callable, originalTypeEvent.replace(namespaceRegex, ''));\n const fn = isDelegated ? bootstrapDelegationHandler(element, handler, callable) : bootstrapHandler(element, callable);\n fn.delegationSelector = isDelegated ? handler : null;\n fn.callable = callable;\n fn.oneOff = oneOff;\n fn.uidEvent = uid;\n handlers[uid] = fn;\n element.addEventListener(typeEvent, fn, isDelegated);\n}\nfunction removeHandler(element, events, typeEvent, handler, delegationSelector) {\n const fn = findHandler(events[typeEvent], handler, delegationSelector);\n if (!fn) {\n return;\n }\n element.removeEventListener(typeEvent, fn, Boolean(delegationSelector));\n delete events[typeEvent][fn.uidEvent];\n}\nfunction removeNamespacedHandlers(element, events, typeEvent, namespace) {\n const storeElementEvent = events[typeEvent] || {};\n for (const [handlerKey, event] of Object.entries(storeElementEvent)) {\n if (handlerKey.includes(namespace)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector);\n }\n }\n}\nfunction getTypeEvent(event) {\n // allow to get the native events from namespaced events ('click.bs.button' --> 'click')\n event = event.replace(stripNameRegex, '');\n return customEvents[event] || event;\n}\nconst EventHandler = {\n on(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, false);\n },\n one(element, event, handler, delegationFunction) {\n addHandler(element, event, handler, delegationFunction, true);\n },\n off(element, originalTypeEvent, handler, delegationFunction) {\n if (typeof originalTypeEvent !== 'string' || !element) {\n return;\n }\n const [isDelegated, callable, typeEvent] = normalizeParameters(originalTypeEvent, handler, delegationFunction);\n const inNamespace = typeEvent !== originalTypeEvent;\n const events = getElementEvents(element);\n const storeElementEvent = events[typeEvent] || {};\n const isNamespace = originalTypeEvent.startsWith('.');\n if (typeof callable !== 'undefined') {\n // Simplest case: handler is passed, remove that listener ONLY.\n if (!Object.keys(storeElementEvent).length) {\n return;\n }\n removeHandler(element, events, typeEvent, callable, isDelegated ? handler : null);\n return;\n }\n if (isNamespace) {\n for (const elementEvent of Object.keys(events)) {\n removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1));\n }\n }\n for (const [keyHandlers, event] of Object.entries(storeElementEvent)) {\n const handlerKey = keyHandlers.replace(stripUidRegex, '');\n if (!inNamespace || originalTypeEvent.includes(handlerKey)) {\n removeHandler(element, events, typeEvent, event.callable, event.delegationSelector);\n }\n }\n },\n trigger(element, event, args) {\n if (typeof event !== 'string' || !element) {\n return null;\n }\n const $ = getjQuery();\n const typeEvent = getTypeEvent(event);\n const inNamespace = event !== typeEvent;\n let jQueryEvent = null;\n let bubbles = true;\n let nativeDispatch = true;\n let defaultPrevented = false;\n if (inNamespace && $) {\n jQueryEvent = $.Event(event, args);\n $(element).trigger(jQueryEvent);\n bubbles = !jQueryEvent.isPropagationStopped();\n nativeDispatch = !jQueryEvent.isImmediatePropagationStopped();\n defaultPrevented = jQueryEvent.isDefaultPrevented();\n }\n const evt = hydrateObj(new Event(event, {\n bubbles,\n cancelable: true\n }), args);\n if (defaultPrevented) {\n evt.preventDefault();\n }\n if (nativeDispatch) {\n element.dispatchEvent(evt);\n }\n if (evt.defaultPrevented && jQueryEvent) {\n jQueryEvent.preventDefault();\n }\n return evt;\n }\n};\nfunction hydrateObj(obj, meta = {}) {\n for (const [key, value] of Object.entries(meta)) {\n try {\n obj[key] = value;\n } catch (_unused) {\n Object.defineProperty(obj, key, {\n configurable: true,\n get() {\n return value;\n }\n });\n }\n }\n return obj;\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/manipulator.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nfunction normalizeData(value) {\n if (value === 'true') {\n return true;\n }\n if (value === 'false') {\n return false;\n }\n if (value === Number(value).toString()) {\n return Number(value);\n }\n if (value === '' || value === 'null') {\n return null;\n }\n if (typeof value !== 'string') {\n return value;\n }\n try {\n return JSON.parse(decodeURIComponent(value));\n } catch (_unused) {\n return value;\n }\n}\nfunction normalizeDataKey(key) {\n return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`);\n}\nconst Manipulator = {\n setDataAttribute(element, key, value) {\n element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value);\n },\n removeDataAttribute(element, key) {\n element.removeAttribute(`data-bs-${normalizeDataKey(key)}`);\n },\n getDataAttributes(element) {\n if (!element) {\n return {};\n }\n const attributes = {};\n const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'));\n for (const key of bsKeys) {\n let pureKey = key.replace(/^bs/, '');\n pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length);\n attributes[pureKey] = normalizeData(element.dataset[key]);\n }\n return attributes;\n },\n getDataAttribute(element, key) {\n return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`));\n }\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/config.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Class definition\n */\n\nclass Config {\n // Getters\n static get Default() {\n return {};\n }\n static get DefaultType() {\n return {};\n }\n static get NAME() {\n throw new Error('You have to implement the static method \"NAME\", for each component!');\n }\n _getConfig(config) {\n config = this._mergeConfigObj(config);\n config = this._configAfterMerge(config);\n this._typeCheckConfig(config);\n return config;\n }\n _configAfterMerge(config) {\n return config;\n }\n _mergeConfigObj(config, element) {\n const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {}; // try to parse\n\n return {\n ...this.constructor.Default,\n ...(typeof jsonConfig === 'object' ? jsonConfig : {}),\n ...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),\n ...(typeof config === 'object' ? config : {})\n };\n }\n _typeCheckConfig(config, configTypes = this.constructor.DefaultType) {\n for (const [property, expectedTypes] of Object.entries(configTypes)) {\n const value = config[property];\n const valueType = isElement(value) ? 'element' : toType(value);\n if (!new RegExp(expectedTypes).test(valueType)) {\n throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option \"${property}\" provided type \"${valueType}\" but expected type \"${expectedTypes}\".`);\n }\n }\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap base-component.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst VERSION = '5.3.3';\n\n/**\n * Class definition\n */\n\nclass BaseComponent extends Config {\n constructor(element, config) {\n super();\n element = getElement(element);\n if (!element) {\n return;\n }\n this._element = element;\n this._config = this._getConfig(config);\n Data.set(this._element, this.constructor.DATA_KEY, this);\n }\n\n // Public\n dispose() {\n Data.remove(this._element, this.constructor.DATA_KEY);\n EventHandler.off(this._element, this.constructor.EVENT_KEY);\n for (const propertyName of Object.getOwnPropertyNames(this)) {\n this[propertyName] = null;\n }\n }\n _queueCallback(callback, element, isAnimated = true) {\n executeAfterTransition(callback, element, isAnimated);\n }\n _getConfig(config) {\n config = this._mergeConfigObj(config, this._element);\n config = this._configAfterMerge(config);\n this._typeCheckConfig(config);\n return config;\n }\n\n // Static\n static getInstance(element) {\n return Data.get(getElement(element), this.DATA_KEY);\n }\n static getOrCreateInstance(element, config = {}) {\n return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null);\n }\n static get VERSION() {\n return VERSION;\n }\n static get DATA_KEY() {\n return `bs.${this.NAME}`;\n }\n static get EVENT_KEY() {\n return `.${this.DATA_KEY}`;\n }\n static eventName(name) {\n return `${name}${this.EVENT_KEY}`;\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dom/selector-engine.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst getSelector = element => {\n let selector = element.getAttribute('data-bs-target');\n if (!selector || selector === '#') {\n let hrefAttribute = element.getAttribute('href');\n\n // The only valid content that could double as a selector are IDs or classes,\n // so everything starting with `#` or `.`. If a \"real\" URL is used as the selector,\n // `document.querySelector` will rightfully complain it is invalid.\n // See https://github.com/twbs/bootstrap/issues/32273\n if (!hrefAttribute || !hrefAttribute.includes('#') && !hrefAttribute.startsWith('.')) {\n return null;\n }\n\n // Just in case some CMS puts out a full URL with the anchor appended\n if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {\n hrefAttribute = `#${hrefAttribute.split('#')[1]}`;\n }\n selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null;\n }\n return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null;\n};\nconst SelectorEngine = {\n find(selector, element = document.documentElement) {\n return [].concat(...Element.prototype.querySelectorAll.call(element, selector));\n },\n findOne(selector, element = document.documentElement) {\n return Element.prototype.querySelector.call(element, selector);\n },\n children(element, selector) {\n return [].concat(...element.children).filter(child => child.matches(selector));\n },\n parents(element, selector) {\n const parents = [];\n let ancestor = element.parentNode.closest(selector);\n while (ancestor) {\n parents.push(ancestor);\n ancestor = ancestor.parentNode.closest(selector);\n }\n return parents;\n },\n prev(element, selector) {\n let previous = element.previousElementSibling;\n while (previous) {\n if (previous.matches(selector)) {\n return [previous];\n }\n previous = previous.previousElementSibling;\n }\n return [];\n },\n // TODO: this is now unused; remove later along with prev()\n next(element, selector) {\n let next = element.nextElementSibling;\n while (next) {\n if (next.matches(selector)) {\n return [next];\n }\n next = next.nextElementSibling;\n }\n return [];\n },\n focusableChildren(element) {\n const focusables = ['a', 'button', 'input', 'textarea', 'select', 'details', '[tabindex]', '[contenteditable=\"true\"]'].map(selector => `${selector}:not([tabindex^=\"-\"])`).join(',');\n return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el));\n },\n getSelectorFromElement(element) {\n const selector = getSelector(element);\n if (selector) {\n return SelectorEngine.findOne(selector) ? selector : null;\n }\n return null;\n },\n getElementFromSelector(element) {\n const selector = getSelector(element);\n return selector ? SelectorEngine.findOne(selector) : null;\n },\n getMultipleElementsFromSelector(element) {\n const selector = getSelector(element);\n return selector ? SelectorEngine.find(selector) : [];\n }\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/component-functions.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nconst enableDismissTrigger = (component, method = 'hide') => {\n const clickEvent = `click.dismiss${component.EVENT_KEY}`;\n const name = component.NAME;\n EventHandler.on(document, clickEvent, `[data-bs-dismiss=\"${name}\"]`, function (event) {\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault();\n }\n if (isDisabled(this)) {\n return;\n }\n const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`);\n const instance = component.getOrCreateInstance(target);\n\n // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method\n instance[method]();\n });\n};\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$f = 'alert';\nconst DATA_KEY$a = 'bs.alert';\nconst EVENT_KEY$b = `.${DATA_KEY$a}`;\nconst EVENT_CLOSE = `close${EVENT_KEY$b}`;\nconst EVENT_CLOSED = `closed${EVENT_KEY$b}`;\nconst CLASS_NAME_FADE$5 = 'fade';\nconst CLASS_NAME_SHOW$8 = 'show';\n\n/**\n * Class definition\n */\n\nclass Alert extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME$f;\n }\n\n // Public\n close() {\n const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE);\n if (closeEvent.defaultPrevented) {\n return;\n }\n this._element.classList.remove(CLASS_NAME_SHOW$8);\n const isAnimated = this._element.classList.contains(CLASS_NAME_FADE$5);\n this._queueCallback(() => this._destroyElement(), this._element, isAnimated);\n }\n\n // Private\n _destroyElement() {\n this._element.remove();\n EventHandler.trigger(this._element, EVENT_CLOSED);\n this.dispose();\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Alert.getOrCreateInstance(this);\n if (typeof config !== 'string') {\n return;\n }\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config](this);\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nenableDismissTrigger(Alert, 'close');\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Alert);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$e = 'button';\nconst DATA_KEY$9 = 'bs.button';\nconst EVENT_KEY$a = `.${DATA_KEY$9}`;\nconst DATA_API_KEY$6 = '.data-api';\nconst CLASS_NAME_ACTIVE$3 = 'active';\nconst SELECTOR_DATA_TOGGLE$5 = '[data-bs-toggle=\"button\"]';\nconst EVENT_CLICK_DATA_API$6 = `click${EVENT_KEY$a}${DATA_API_KEY$6}`;\n\n/**\n * Class definition\n */\n\nclass Button extends BaseComponent {\n // Getters\n static get NAME() {\n return NAME$e;\n }\n\n // Public\n toggle() {\n // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method\n this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE$3));\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Button.getOrCreateInstance(this);\n if (config === 'toggle') {\n data[config]();\n }\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$6, SELECTOR_DATA_TOGGLE$5, event => {\n event.preventDefault();\n const button = event.target.closest(SELECTOR_DATA_TOGGLE$5);\n const data = Button.getOrCreateInstance(button);\n data.toggle();\n});\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Button);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/swipe.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$d = 'swipe';\nconst EVENT_KEY$9 = '.bs.swipe';\nconst EVENT_TOUCHSTART = `touchstart${EVENT_KEY$9}`;\nconst EVENT_TOUCHMOVE = `touchmove${EVENT_KEY$9}`;\nconst EVENT_TOUCHEND = `touchend${EVENT_KEY$9}`;\nconst EVENT_POINTERDOWN = `pointerdown${EVENT_KEY$9}`;\nconst EVENT_POINTERUP = `pointerup${EVENT_KEY$9}`;\nconst POINTER_TYPE_TOUCH = 'touch';\nconst POINTER_TYPE_PEN = 'pen';\nconst CLASS_NAME_POINTER_EVENT = 'pointer-event';\nconst SWIPE_THRESHOLD = 40;\nconst Default$c = {\n endCallback: null,\n leftCallback: null,\n rightCallback: null\n};\nconst DefaultType$c = {\n endCallback: '(function|null)',\n leftCallback: '(function|null)',\n rightCallback: '(function|null)'\n};\n\n/**\n * Class definition\n */\n\nclass Swipe extends Config {\n constructor(element, config) {\n super();\n this._element = element;\n if (!element || !Swipe.isSupported()) {\n return;\n }\n this._config = this._getConfig(config);\n this._deltaX = 0;\n this._supportPointerEvents = Boolean(window.PointerEvent);\n this._initEvents();\n }\n\n // Getters\n static get Default() {\n return Default$c;\n }\n static get DefaultType() {\n return DefaultType$c;\n }\n static get NAME() {\n return NAME$d;\n }\n\n // Public\n dispose() {\n EventHandler.off(this._element, EVENT_KEY$9);\n }\n\n // Private\n _start(event) {\n if (!this._supportPointerEvents) {\n this._deltaX = event.touches[0].clientX;\n return;\n }\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX;\n }\n }\n _end(event) {\n if (this._eventIsPointerPenTouch(event)) {\n this._deltaX = event.clientX - this._deltaX;\n }\n this._handleSwipe();\n execute(this._config.endCallback);\n }\n _move(event) {\n this._deltaX = event.touches && event.touches.length > 1 ? 0 : event.touches[0].clientX - this._deltaX;\n }\n _handleSwipe() {\n const absDeltaX = Math.abs(this._deltaX);\n if (absDeltaX <= SWIPE_THRESHOLD) {\n return;\n }\n const direction = absDeltaX / this._deltaX;\n this._deltaX = 0;\n if (!direction) {\n return;\n }\n execute(direction > 0 ? this._config.rightCallback : this._config.leftCallback);\n }\n _initEvents() {\n if (this._supportPointerEvents) {\n EventHandler.on(this._element, EVENT_POINTERDOWN, event => this._start(event));\n EventHandler.on(this._element, EVENT_POINTERUP, event => this._end(event));\n this._element.classList.add(CLASS_NAME_POINTER_EVENT);\n } else {\n EventHandler.on(this._element, EVENT_TOUCHSTART, event => this._start(event));\n EventHandler.on(this._element, EVENT_TOUCHMOVE, event => this._move(event));\n EventHandler.on(this._element, EVENT_TOUCHEND, event => this._end(event));\n }\n }\n _eventIsPointerPenTouch(event) {\n return this._supportPointerEvents && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH);\n }\n\n // Static\n static isSupported() {\n return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0;\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap carousel.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$c = 'carousel';\nconst DATA_KEY$8 = 'bs.carousel';\nconst EVENT_KEY$8 = `.${DATA_KEY$8}`;\nconst DATA_API_KEY$5 = '.data-api';\nconst ARROW_LEFT_KEY$1 = 'ArrowLeft';\nconst ARROW_RIGHT_KEY$1 = 'ArrowRight';\nconst TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch\n\nconst ORDER_NEXT = 'next';\nconst ORDER_PREV = 'prev';\nconst DIRECTION_LEFT = 'left';\nconst DIRECTION_RIGHT = 'right';\nconst EVENT_SLIDE = `slide${EVENT_KEY$8}`;\nconst EVENT_SLID = `slid${EVENT_KEY$8}`;\nconst EVENT_KEYDOWN$1 = `keydown${EVENT_KEY$8}`;\nconst EVENT_MOUSEENTER$1 = `mouseenter${EVENT_KEY$8}`;\nconst EVENT_MOUSELEAVE$1 = `mouseleave${EVENT_KEY$8}`;\nconst EVENT_DRAG_START = `dragstart${EVENT_KEY$8}`;\nconst EVENT_LOAD_DATA_API$3 = `load${EVENT_KEY$8}${DATA_API_KEY$5}`;\nconst EVENT_CLICK_DATA_API$5 = `click${EVENT_KEY$8}${DATA_API_KEY$5}`;\nconst CLASS_NAME_CAROUSEL = 'carousel';\nconst CLASS_NAME_ACTIVE$2 = 'active';\nconst CLASS_NAME_SLIDE = 'slide';\nconst CLASS_NAME_END = 'carousel-item-end';\nconst CLASS_NAME_START = 'carousel-item-start';\nconst CLASS_NAME_NEXT = 'carousel-item-next';\nconst CLASS_NAME_PREV = 'carousel-item-prev';\nconst SELECTOR_ACTIVE = '.active';\nconst SELECTOR_ITEM = '.carousel-item';\nconst SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM;\nconst SELECTOR_ITEM_IMG = '.carousel-item img';\nconst SELECTOR_INDICATORS = '.carousel-indicators';\nconst SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]';\nconst SELECTOR_DATA_RIDE = '[data-bs-ride=\"carousel\"]';\nconst KEY_TO_DIRECTION = {\n [ARROW_LEFT_KEY$1]: DIRECTION_RIGHT,\n [ARROW_RIGHT_KEY$1]: DIRECTION_LEFT\n};\nconst Default$b = {\n interval: 5000,\n keyboard: true,\n pause: 'hover',\n ride: false,\n touch: true,\n wrap: true\n};\nconst DefaultType$b = {\n interval: '(number|boolean)',\n // TODO:v6 remove boolean support\n keyboard: 'boolean',\n pause: '(string|boolean)',\n ride: '(boolean|string)',\n touch: 'boolean',\n wrap: 'boolean'\n};\n\n/**\n * Class definition\n */\n\nclass Carousel extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._interval = null;\n this._activeElement = null;\n this._isSliding = false;\n this.touchTimeout = null;\n this._swipeHelper = null;\n this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element);\n this._addEventListeners();\n if (this._config.ride === CLASS_NAME_CAROUSEL) {\n this.cycle();\n }\n }\n\n // Getters\n static get Default() {\n return Default$b;\n }\n static get DefaultType() {\n return DefaultType$b;\n }\n static get NAME() {\n return NAME$c;\n }\n\n // Public\n next() {\n this._slide(ORDER_NEXT);\n }\n nextWhenVisible() {\n // FIXME TODO use `document.visibilityState`\n // Don't call next when the page isn't visible\n // or the carousel or its parent isn't visible\n if (!document.hidden && isVisible(this._element)) {\n this.next();\n }\n }\n prev() {\n this._slide(ORDER_PREV);\n }\n pause() {\n if (this._isSliding) {\n triggerTransitionEnd(this._element);\n }\n this._clearInterval();\n }\n cycle() {\n this._clearInterval();\n this._updateInterval();\n this._interval = setInterval(() => this.nextWhenVisible(), this._config.interval);\n }\n _maybeEnableCycle() {\n if (!this._config.ride) {\n return;\n }\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.cycle());\n return;\n }\n this.cycle();\n }\n to(index) {\n const items = this._getItems();\n if (index > items.length - 1 || index < 0) {\n return;\n }\n if (this._isSliding) {\n EventHandler.one(this._element, EVENT_SLID, () => this.to(index));\n return;\n }\n const activeIndex = this._getItemIndex(this._getActive());\n if (activeIndex === index) {\n return;\n }\n const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV;\n this._slide(order, items[index]);\n }\n dispose() {\n if (this._swipeHelper) {\n this._swipeHelper.dispose();\n }\n super.dispose();\n }\n\n // Private\n _configAfterMerge(config) {\n config.defaultInterval = config.interval;\n return config;\n }\n _addEventListeners() {\n if (this._config.keyboard) {\n EventHandler.on(this._element, EVENT_KEYDOWN$1, event => this._keydown(event));\n }\n if (this._config.pause === 'hover') {\n EventHandler.on(this._element, EVENT_MOUSEENTER$1, () => this.pause());\n EventHandler.on(this._element, EVENT_MOUSELEAVE$1, () => this._maybeEnableCycle());\n }\n if (this._config.touch && Swipe.isSupported()) {\n this._addTouchEventListeners();\n }\n }\n _addTouchEventListeners() {\n for (const img of SelectorEngine.find(SELECTOR_ITEM_IMG, this._element)) {\n EventHandler.on(img, EVENT_DRAG_START, event => event.preventDefault());\n }\n const endCallBack = () => {\n if (this._config.pause !== 'hover') {\n return;\n }\n\n // If it's a touch-enabled device, mouseenter/leave are fired as\n // part of the mouse compatibility events on first tap - the carousel\n // would stop cycling until user tapped out of it;\n // here, we listen for touchend, explicitly pause the carousel\n // (as if it's the second time we tap on it, mouseenter compat event\n // is NOT fired) and after a timeout (to allow for mouse compatibility\n // events to fire) we explicitly restart cycling\n\n this.pause();\n if (this.touchTimeout) {\n clearTimeout(this.touchTimeout);\n }\n this.touchTimeout = setTimeout(() => this._maybeEnableCycle(), TOUCHEVENT_COMPAT_WAIT + this._config.interval);\n };\n const swipeConfig = {\n leftCallback: () => this._slide(this._directionToOrder(DIRECTION_LEFT)),\n rightCallback: () => this._slide(this._directionToOrder(DIRECTION_RIGHT)),\n endCallback: endCallBack\n };\n this._swipeHelper = new Swipe(this._element, swipeConfig);\n }\n _keydown(event) {\n if (/input|textarea/i.test(event.target.tagName)) {\n return;\n }\n const direction = KEY_TO_DIRECTION[event.key];\n if (direction) {\n event.preventDefault();\n this._slide(this._directionToOrder(direction));\n }\n }\n _getItemIndex(element) {\n return this._getItems().indexOf(element);\n }\n _setActiveIndicatorElement(index) {\n if (!this._indicatorsElement) {\n return;\n }\n const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE, this._indicatorsElement);\n activeIndicator.classList.remove(CLASS_NAME_ACTIVE$2);\n activeIndicator.removeAttribute('aria-current');\n const newActiveIndicator = SelectorEngine.findOne(`[data-bs-slide-to=\"${index}\"]`, this._indicatorsElement);\n if (newActiveIndicator) {\n newActiveIndicator.classList.add(CLASS_NAME_ACTIVE$2);\n newActiveIndicator.setAttribute('aria-current', 'true');\n }\n }\n _updateInterval() {\n const element = this._activeElement || this._getActive();\n if (!element) {\n return;\n }\n const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10);\n this._config.interval = elementInterval || this._config.defaultInterval;\n }\n _slide(order, element = null) {\n if (this._isSliding) {\n return;\n }\n const activeElement = this._getActive();\n const isNext = order === ORDER_NEXT;\n const nextElement = element || getNextActiveElement(this._getItems(), activeElement, isNext, this._config.wrap);\n if (nextElement === activeElement) {\n return;\n }\n const nextElementIndex = this._getItemIndex(nextElement);\n const triggerEvent = eventName => {\n return EventHandler.trigger(this._element, eventName, {\n relatedTarget: nextElement,\n direction: this._orderToDirection(order),\n from: this._getItemIndex(activeElement),\n to: nextElementIndex\n });\n };\n const slideEvent = triggerEvent(EVENT_SLIDE);\n if (slideEvent.defaultPrevented) {\n return;\n }\n if (!activeElement || !nextElement) {\n // Some weirdness is happening, so we bail\n // TODO: change tests that use empty divs to avoid this check\n return;\n }\n const isCycling = Boolean(this._interval);\n this.pause();\n this._isSliding = true;\n this._setActiveIndicatorElement(nextElementIndex);\n this._activeElement = nextElement;\n const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END;\n const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV;\n nextElement.classList.add(orderClassName);\n reflow(nextElement);\n activeElement.classList.add(directionalClassName);\n nextElement.classList.add(directionalClassName);\n const completeCallBack = () => {\n nextElement.classList.remove(directionalClassName, orderClassName);\n nextElement.classList.add(CLASS_NAME_ACTIVE$2);\n activeElement.classList.remove(CLASS_NAME_ACTIVE$2, orderClassName, directionalClassName);\n this._isSliding = false;\n triggerEvent(EVENT_SLID);\n };\n this._queueCallback(completeCallBack, activeElement, this._isAnimated());\n if (isCycling) {\n this.cycle();\n }\n }\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_SLIDE);\n }\n _getActive() {\n return SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element);\n }\n _getItems() {\n return SelectorEngine.find(SELECTOR_ITEM, this._element);\n }\n _clearInterval() {\n if (this._interval) {\n clearInterval(this._interval);\n this._interval = null;\n }\n }\n _directionToOrder(direction) {\n if (isRTL()) {\n return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT;\n }\n return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV;\n }\n _orderToDirection(order) {\n if (isRTL()) {\n return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT;\n }\n return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT;\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Carousel.getOrCreateInstance(this, config);\n if (typeof config === 'number') {\n data.to(config);\n return;\n }\n if (typeof config === 'string') {\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n }\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$5, SELECTOR_DATA_SLIDE, function (event) {\n const target = SelectorEngine.getElementFromSelector(this);\n if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) {\n return;\n }\n event.preventDefault();\n const carousel = Carousel.getOrCreateInstance(target);\n const slideIndex = this.getAttribute('data-bs-slide-to');\n if (slideIndex) {\n carousel.to(slideIndex);\n carousel._maybeEnableCycle();\n return;\n }\n if (Manipulator.getDataAttribute(this, 'slide') === 'next') {\n carousel.next();\n carousel._maybeEnableCycle();\n return;\n }\n carousel.prev();\n carousel._maybeEnableCycle();\n});\nEventHandler.on(window, EVENT_LOAD_DATA_API$3, () => {\n const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE);\n for (const carousel of carousels) {\n Carousel.getOrCreateInstance(carousel);\n }\n});\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Carousel);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap collapse.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$b = 'collapse';\nconst DATA_KEY$7 = 'bs.collapse';\nconst EVENT_KEY$7 = `.${DATA_KEY$7}`;\nconst DATA_API_KEY$4 = '.data-api';\nconst EVENT_SHOW$6 = `show${EVENT_KEY$7}`;\nconst EVENT_SHOWN$6 = `shown${EVENT_KEY$7}`;\nconst EVENT_HIDE$6 = `hide${EVENT_KEY$7}`;\nconst EVENT_HIDDEN$6 = `hidden${EVENT_KEY$7}`;\nconst EVENT_CLICK_DATA_API$4 = `click${EVENT_KEY$7}${DATA_API_KEY$4}`;\nconst CLASS_NAME_SHOW$7 = 'show';\nconst CLASS_NAME_COLLAPSE = 'collapse';\nconst CLASS_NAME_COLLAPSING = 'collapsing';\nconst CLASS_NAME_COLLAPSED = 'collapsed';\nconst CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`;\nconst CLASS_NAME_HORIZONTAL = 'collapse-horizontal';\nconst WIDTH = 'width';\nconst HEIGHT = 'height';\nconst SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing';\nconst SELECTOR_DATA_TOGGLE$4 = '[data-bs-toggle=\"collapse\"]';\nconst Default$a = {\n parent: null,\n toggle: true\n};\nconst DefaultType$a = {\n parent: '(null|element)',\n toggle: 'boolean'\n};\n\n/**\n * Class definition\n */\n\nclass Collapse extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._isTransitioning = false;\n this._triggerArray = [];\n const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE$4);\n for (const elem of toggleList) {\n const selector = SelectorEngine.getSelectorFromElement(elem);\n const filterElement = SelectorEngine.find(selector).filter(foundElement => foundElement === this._element);\n if (selector !== null && filterElement.length) {\n this._triggerArray.push(elem);\n }\n }\n this._initializeChildren();\n if (!this._config.parent) {\n this._addAriaAndCollapsedClass(this._triggerArray, this._isShown());\n }\n if (this._config.toggle) {\n this.toggle();\n }\n }\n\n // Getters\n static get Default() {\n return Default$a;\n }\n static get DefaultType() {\n return DefaultType$a;\n }\n static get NAME() {\n return NAME$b;\n }\n\n // Public\n toggle() {\n if (this._isShown()) {\n this.hide();\n } else {\n this.show();\n }\n }\n show() {\n if (this._isTransitioning || this._isShown()) {\n return;\n }\n let activeChildren = [];\n\n // find active children\n if (this._config.parent) {\n activeChildren = this._getFirstLevelChildren(SELECTOR_ACTIVES).filter(element => element !== this._element).map(element => Collapse.getOrCreateInstance(element, {\n toggle: false\n }));\n }\n if (activeChildren.length && activeChildren[0]._isTransitioning) {\n return;\n }\n const startEvent = EventHandler.trigger(this._element, EVENT_SHOW$6);\n if (startEvent.defaultPrevented) {\n return;\n }\n for (const activeInstance of activeChildren) {\n activeInstance.hide();\n }\n const dimension = this._getDimension();\n this._element.classList.remove(CLASS_NAME_COLLAPSE);\n this._element.classList.add(CLASS_NAME_COLLAPSING);\n this._element.style[dimension] = 0;\n this._addAriaAndCollapsedClass(this._triggerArray, true);\n this._isTransitioning = true;\n const complete = () => {\n this._isTransitioning = false;\n this._element.classList.remove(CLASS_NAME_COLLAPSING);\n this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7);\n this._element.style[dimension] = '';\n EventHandler.trigger(this._element, EVENT_SHOWN$6);\n };\n const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1);\n const scrollSize = `scroll${capitalizedDimension}`;\n this._queueCallback(complete, this._element, true);\n this._element.style[dimension] = `${this._element[scrollSize]}px`;\n }\n hide() {\n if (this._isTransitioning || !this._isShown()) {\n return;\n }\n const startEvent = EventHandler.trigger(this._element, EVENT_HIDE$6);\n if (startEvent.defaultPrevented) {\n return;\n }\n const dimension = this._getDimension();\n this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`;\n reflow(this._element);\n this._element.classList.add(CLASS_NAME_COLLAPSING);\n this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7);\n for (const trigger of this._triggerArray) {\n const element = SelectorEngine.getElementFromSelector(trigger);\n if (element && !this._isShown(element)) {\n this._addAriaAndCollapsedClass([trigger], false);\n }\n }\n this._isTransitioning = true;\n const complete = () => {\n this._isTransitioning = false;\n this._element.classList.remove(CLASS_NAME_COLLAPSING);\n this._element.classList.add(CLASS_NAME_COLLAPSE);\n EventHandler.trigger(this._element, EVENT_HIDDEN$6);\n };\n this._element.style[dimension] = '';\n this._queueCallback(complete, this._element, true);\n }\n _isShown(element = this._element) {\n return element.classList.contains(CLASS_NAME_SHOW$7);\n }\n\n // Private\n _configAfterMerge(config) {\n config.toggle = Boolean(config.toggle); // Coerce string values\n config.parent = getElement(config.parent);\n return config;\n }\n _getDimension() {\n return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT;\n }\n _initializeChildren() {\n if (!this._config.parent) {\n return;\n }\n const children = this._getFirstLevelChildren(SELECTOR_DATA_TOGGLE$4);\n for (const element of children) {\n const selected = SelectorEngine.getElementFromSelector(element);\n if (selected) {\n this._addAriaAndCollapsedClass([element], this._isShown(selected));\n }\n }\n }\n _getFirstLevelChildren(selector) {\n const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent);\n // remove children if greater depth\n return SelectorEngine.find(selector, this._config.parent).filter(element => !children.includes(element));\n }\n _addAriaAndCollapsedClass(triggerArray, isOpen) {\n if (!triggerArray.length) {\n return;\n }\n for (const element of triggerArray) {\n element.classList.toggle(CLASS_NAME_COLLAPSED, !isOpen);\n element.setAttribute('aria-expanded', isOpen);\n }\n }\n\n // Static\n static jQueryInterface(config) {\n const _config = {};\n if (typeof config === 'string' && /show|hide/.test(config)) {\n _config.toggle = false;\n }\n return this.each(function () {\n const data = Collapse.getOrCreateInstance(this, _config);\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n }\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$4, SELECTOR_DATA_TOGGLE$4, function (event) {\n // preventDefault only for elements (which change the URL) not inside the collapsible element\n if (event.target.tagName === 'A' || event.delegateTarget && event.delegateTarget.tagName === 'A') {\n event.preventDefault();\n }\n for (const element of SelectorEngine.getMultipleElementsFromSelector(this)) {\n Collapse.getOrCreateInstance(element, {\n toggle: false\n }).toggle();\n }\n});\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Collapse);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap dropdown.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$a = 'dropdown';\nconst DATA_KEY$6 = 'bs.dropdown';\nconst EVENT_KEY$6 = `.${DATA_KEY$6}`;\nconst DATA_API_KEY$3 = '.data-api';\nconst ESCAPE_KEY$2 = 'Escape';\nconst TAB_KEY$1 = 'Tab';\nconst ARROW_UP_KEY$1 = 'ArrowUp';\nconst ARROW_DOWN_KEY$1 = 'ArrowDown';\nconst RIGHT_MOUSE_BUTTON = 2; // MouseEvent.button value for the secondary button, usually the right button\n\nconst EVENT_HIDE$5 = `hide${EVENT_KEY$6}`;\nconst EVENT_HIDDEN$5 = `hidden${EVENT_KEY$6}`;\nconst EVENT_SHOW$5 = `show${EVENT_KEY$6}`;\nconst EVENT_SHOWN$5 = `shown${EVENT_KEY$6}`;\nconst EVENT_CLICK_DATA_API$3 = `click${EVENT_KEY$6}${DATA_API_KEY$3}`;\nconst EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY$6}${DATA_API_KEY$3}`;\nconst EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY$6}${DATA_API_KEY$3}`;\nconst CLASS_NAME_SHOW$6 = 'show';\nconst CLASS_NAME_DROPUP = 'dropup';\nconst CLASS_NAME_DROPEND = 'dropend';\nconst CLASS_NAME_DROPSTART = 'dropstart';\nconst CLASS_NAME_DROPUP_CENTER = 'dropup-center';\nconst CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center';\nconst SELECTOR_DATA_TOGGLE$3 = '[data-bs-toggle=\"dropdown\"]:not(.disabled):not(:disabled)';\nconst SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE$3}.${CLASS_NAME_SHOW$6}`;\nconst SELECTOR_MENU = '.dropdown-menu';\nconst SELECTOR_NAVBAR = '.navbar';\nconst SELECTOR_NAVBAR_NAV = '.navbar-nav';\nconst SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)';\nconst PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start';\nconst PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end';\nconst PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start';\nconst PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end';\nconst PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start';\nconst PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start';\nconst PLACEMENT_TOPCENTER = 'top';\nconst PLACEMENT_BOTTOMCENTER = 'bottom';\nconst Default$9 = {\n autoClose: true,\n boundary: 'clippingParents',\n display: 'dynamic',\n offset: [0, 2],\n popperConfig: null,\n reference: 'toggle'\n};\nconst DefaultType$9 = {\n autoClose: '(boolean|string)',\n boundary: '(string|element)',\n display: 'string',\n offset: '(array|string|function)',\n popperConfig: '(null|object|function)',\n reference: '(string|element|object)'\n};\n\n/**\n * Class definition\n */\n\nclass Dropdown extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._popper = null;\n this._parent = this._element.parentNode; // dropdown wrapper\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.findOne(SELECTOR_MENU, this._parent);\n this._inNavbar = this._detectNavbar();\n }\n\n // Getters\n static get Default() {\n return Default$9;\n }\n static get DefaultType() {\n return DefaultType$9;\n }\n static get NAME() {\n return NAME$a;\n }\n\n // Public\n toggle() {\n return this._isShown() ? this.hide() : this.show();\n }\n show() {\n if (isDisabled(this._element) || this._isShown()) {\n return;\n }\n const relatedTarget = {\n relatedTarget: this._element\n };\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$5, relatedTarget);\n if (showEvent.defaultPrevented) {\n return;\n }\n this._createPopper();\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement && !this._parent.closest(SELECTOR_NAVBAR_NAV)) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop);\n }\n }\n this._element.focus();\n this._element.setAttribute('aria-expanded', true);\n this._menu.classList.add(CLASS_NAME_SHOW$6);\n this._element.classList.add(CLASS_NAME_SHOW$6);\n EventHandler.trigger(this._element, EVENT_SHOWN$5, relatedTarget);\n }\n hide() {\n if (isDisabled(this._element) || !this._isShown()) {\n return;\n }\n const relatedTarget = {\n relatedTarget: this._element\n };\n this._completeHide(relatedTarget);\n }\n dispose() {\n if (this._popper) {\n this._popper.destroy();\n }\n super.dispose();\n }\n update() {\n this._inNavbar = this._detectNavbar();\n if (this._popper) {\n this._popper.update();\n }\n }\n\n // Private\n _completeHide(relatedTarget) {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$5, relatedTarget);\n if (hideEvent.defaultPrevented) {\n return;\n }\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop);\n }\n }\n if (this._popper) {\n this._popper.destroy();\n }\n this._menu.classList.remove(CLASS_NAME_SHOW$6);\n this._element.classList.remove(CLASS_NAME_SHOW$6);\n this._element.setAttribute('aria-expanded', 'false');\n Manipulator.removeDataAttribute(this._menu, 'popper');\n EventHandler.trigger(this._element, EVENT_HIDDEN$5, relatedTarget);\n }\n _getConfig(config) {\n config = super._getConfig(config);\n if (typeof config.reference === 'object' && !isElement(config.reference) && typeof config.reference.getBoundingClientRect !== 'function') {\n // Popper virtual elements require a getBoundingClientRect method\n throw new TypeError(`${NAME$a.toUpperCase()}: Option \"reference\" provided type \"object\" without a required \"getBoundingClientRect\" method.`);\n }\n return config;\n }\n _createPopper() {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s dropdowns require Popper (https://popper.js.org)');\n }\n let referenceElement = this._element;\n if (this._config.reference === 'parent') {\n referenceElement = this._parent;\n } else if (isElement(this._config.reference)) {\n referenceElement = getElement(this._config.reference);\n } else if (typeof this._config.reference === 'object') {\n referenceElement = this._config.reference;\n }\n const popperConfig = this._getPopperConfig();\n this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig);\n }\n _isShown() {\n return this._menu.classList.contains(CLASS_NAME_SHOW$6);\n }\n _getPlacement() {\n const parentDropdown = this._parent;\n if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {\n return PLACEMENT_RIGHT;\n }\n if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {\n return PLACEMENT_LEFT;\n }\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) {\n return PLACEMENT_TOPCENTER;\n }\n if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) {\n return PLACEMENT_BOTTOMCENTER;\n }\n\n // We need to trim the value because custom properties can also include spaces\n const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end';\n if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {\n return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP;\n }\n return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM;\n }\n _detectNavbar() {\n return this._element.closest(SELECTOR_NAVBAR) !== null;\n }\n _getOffset() {\n const {\n offset\n } = this._config;\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10));\n }\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element);\n }\n return offset;\n }\n _getPopperConfig() {\n const defaultBsPopperConfig = {\n placement: this._getPlacement(),\n modifiers: [{\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n }, {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n }]\n };\n\n // Disable Popper if we have a static display or Dropdown is in Navbar\n if (this._inNavbar || this._config.display === 'static') {\n Manipulator.setDataAttribute(this._menu, 'popper', 'static'); // TODO: v6 remove\n defaultBsPopperConfig.modifiers = [{\n name: 'applyStyles',\n enabled: false\n }];\n }\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [defaultBsPopperConfig])\n };\n }\n _selectMenuItem({\n key,\n target\n }) {\n const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element));\n if (!items.length) {\n return;\n }\n\n // if target isn't included in items (e.g. when expanding the dropdown)\n // allow cycling to get the last item in case key equals ARROW_UP_KEY\n getNextActiveElement(items, target, key === ARROW_DOWN_KEY$1, !items.includes(target)).focus();\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Dropdown.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n });\n }\n static clearMenus(event) {\n if (event.button === RIGHT_MOUSE_BUTTON || event.type === 'keyup' && event.key !== TAB_KEY$1) {\n return;\n }\n const openToggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE_SHOWN);\n for (const toggle of openToggles) {\n const context = Dropdown.getInstance(toggle);\n if (!context || context._config.autoClose === false) {\n continue;\n }\n const composedPath = event.composedPath();\n const isMenuTarget = composedPath.includes(context._menu);\n if (composedPath.includes(context._element) || context._config.autoClose === 'inside' && !isMenuTarget || context._config.autoClose === 'outside' && isMenuTarget) {\n continue;\n }\n\n // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu\n if (context._menu.contains(event.target) && (event.type === 'keyup' && event.key === TAB_KEY$1 || /input|select|option|textarea|form/i.test(event.target.tagName))) {\n continue;\n }\n const relatedTarget = {\n relatedTarget: context._element\n };\n if (event.type === 'click') {\n relatedTarget.clickEvent = event;\n }\n context._completeHide(relatedTarget);\n }\n }\n static dataApiKeydownHandler(event) {\n // If not an UP | DOWN | ESCAPE key => not a dropdown command\n // If input/textarea && if key is other than ESCAPE => not a dropdown command\n\n const isInput = /input|textarea/i.test(event.target.tagName);\n const isEscapeEvent = event.key === ESCAPE_KEY$2;\n const isUpOrDownEvent = [ARROW_UP_KEY$1, ARROW_DOWN_KEY$1].includes(event.key);\n if (!isUpOrDownEvent && !isEscapeEvent) {\n return;\n }\n if (isInput && !isEscapeEvent) {\n return;\n }\n event.preventDefault();\n\n // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/\n const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE$3) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.next(this, SELECTOR_DATA_TOGGLE$3)[0] || SelectorEngine.findOne(SELECTOR_DATA_TOGGLE$3, event.delegateTarget.parentNode);\n const instance = Dropdown.getOrCreateInstance(getToggleButton);\n if (isUpOrDownEvent) {\n event.stopPropagation();\n instance.show();\n instance._selectMenuItem(event);\n return;\n }\n if (instance._isShown()) {\n // else is escape and we check if it is shown\n event.stopPropagation();\n instance.hide();\n getToggleButton.focus();\n }\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE$3, Dropdown.dataApiKeydownHandler);\nEventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler);\nEventHandler.on(document, EVENT_CLICK_DATA_API$3, Dropdown.clearMenus);\nEventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus);\nEventHandler.on(document, EVENT_CLICK_DATA_API$3, SELECTOR_DATA_TOGGLE$3, function (event) {\n event.preventDefault();\n Dropdown.getOrCreateInstance(this).toggle();\n});\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Dropdown);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/backdrop.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$9 = 'backdrop';\nconst CLASS_NAME_FADE$4 = 'fade';\nconst CLASS_NAME_SHOW$5 = 'show';\nconst EVENT_MOUSEDOWN = `mousedown.bs.${NAME$9}`;\nconst Default$8 = {\n className: 'modal-backdrop',\n clickCallback: null,\n isAnimated: false,\n isVisible: true,\n // if false, we use the backdrop helper without adding any element to the dom\n rootElement: 'body' // give the choice to place backdrop under different elements\n};\nconst DefaultType$8 = {\n className: 'string',\n clickCallback: '(function|null)',\n isAnimated: 'boolean',\n isVisible: 'boolean',\n rootElement: '(element|string)'\n};\n\n/**\n * Class definition\n */\n\nclass Backdrop extends Config {\n constructor(config) {\n super();\n this._config = this._getConfig(config);\n this._isAppended = false;\n this._element = null;\n }\n\n // Getters\n static get Default() {\n return Default$8;\n }\n static get DefaultType() {\n return DefaultType$8;\n }\n static get NAME() {\n return NAME$9;\n }\n\n // Public\n show(callback) {\n if (!this._config.isVisible) {\n execute(callback);\n return;\n }\n this._append();\n const element = this._getElement();\n if (this._config.isAnimated) {\n reflow(element);\n }\n element.classList.add(CLASS_NAME_SHOW$5);\n this._emulateAnimation(() => {\n execute(callback);\n });\n }\n hide(callback) {\n if (!this._config.isVisible) {\n execute(callback);\n return;\n }\n this._getElement().classList.remove(CLASS_NAME_SHOW$5);\n this._emulateAnimation(() => {\n this.dispose();\n execute(callback);\n });\n }\n dispose() {\n if (!this._isAppended) {\n return;\n }\n EventHandler.off(this._element, EVENT_MOUSEDOWN);\n this._element.remove();\n this._isAppended = false;\n }\n\n // Private\n _getElement() {\n if (!this._element) {\n const backdrop = document.createElement('div');\n backdrop.className = this._config.className;\n if (this._config.isAnimated) {\n backdrop.classList.add(CLASS_NAME_FADE$4);\n }\n this._element = backdrop;\n }\n return this._element;\n }\n _configAfterMerge(config) {\n // use getElement() with the default \"body\" to get a fresh Element on each instantiation\n config.rootElement = getElement(config.rootElement);\n return config;\n }\n _append() {\n if (this._isAppended) {\n return;\n }\n const element = this._getElement();\n this._config.rootElement.append(element);\n EventHandler.on(element, EVENT_MOUSEDOWN, () => {\n execute(this._config.clickCallback);\n });\n this._isAppended = true;\n }\n _emulateAnimation(callback) {\n executeAfterTransition(callback, this._getElement(), this._config.isAnimated);\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/focustrap.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$8 = 'focustrap';\nconst DATA_KEY$5 = 'bs.focustrap';\nconst EVENT_KEY$5 = `.${DATA_KEY$5}`;\nconst EVENT_FOCUSIN$2 = `focusin${EVENT_KEY$5}`;\nconst EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY$5}`;\nconst TAB_KEY = 'Tab';\nconst TAB_NAV_FORWARD = 'forward';\nconst TAB_NAV_BACKWARD = 'backward';\nconst Default$7 = {\n autofocus: true,\n trapElement: null // The element to trap focus inside of\n};\nconst DefaultType$7 = {\n autofocus: 'boolean',\n trapElement: 'element'\n};\n\n/**\n * Class definition\n */\n\nclass FocusTrap extends Config {\n constructor(config) {\n super();\n this._config = this._getConfig(config);\n this._isActive = false;\n this._lastTabNavDirection = null;\n }\n\n // Getters\n static get Default() {\n return Default$7;\n }\n static get DefaultType() {\n return DefaultType$7;\n }\n static get NAME() {\n return NAME$8;\n }\n\n // Public\n activate() {\n if (this._isActive) {\n return;\n }\n if (this._config.autofocus) {\n this._config.trapElement.focus();\n }\n EventHandler.off(document, EVENT_KEY$5); // guard against infinite focus loop\n EventHandler.on(document, EVENT_FOCUSIN$2, event => this._handleFocusin(event));\n EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event));\n this._isActive = true;\n }\n deactivate() {\n if (!this._isActive) {\n return;\n }\n this._isActive = false;\n EventHandler.off(document, EVENT_KEY$5);\n }\n\n // Private\n _handleFocusin(event) {\n const {\n trapElement\n } = this._config;\n if (event.target === document || event.target === trapElement || trapElement.contains(event.target)) {\n return;\n }\n const elements = SelectorEngine.focusableChildren(trapElement);\n if (elements.length === 0) {\n trapElement.focus();\n } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {\n elements[elements.length - 1].focus();\n } else {\n elements[0].focus();\n }\n }\n _handleKeydown(event) {\n if (event.key !== TAB_KEY) {\n return;\n }\n this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD;\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/scrollBar.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top';\nconst SELECTOR_STICKY_CONTENT = '.sticky-top';\nconst PROPERTY_PADDING = 'padding-right';\nconst PROPERTY_MARGIN = 'margin-right';\n\n/**\n * Class definition\n */\n\nclass ScrollBarHelper {\n constructor() {\n this._element = document.body;\n }\n\n // Public\n getWidth() {\n // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes\n const documentWidth = document.documentElement.clientWidth;\n return Math.abs(window.innerWidth - documentWidth);\n }\n hide() {\n const width = this.getWidth();\n this._disableOverFlow();\n // give padding to element to balance the hidden scrollbar width\n this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width);\n // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth\n this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width);\n this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width);\n }\n reset() {\n this._resetElementAttributes(this._element, 'overflow');\n this._resetElementAttributes(this._element, PROPERTY_PADDING);\n this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING);\n this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN);\n }\n isOverflowing() {\n return this.getWidth() > 0;\n }\n\n // Private\n _disableOverFlow() {\n this._saveInitialAttribute(this._element, 'overflow');\n this._element.style.overflow = 'hidden';\n }\n _setElementAttributes(selector, styleProperty, callback) {\n const scrollbarWidth = this.getWidth();\n const manipulationCallBack = element => {\n if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {\n return;\n }\n this._saveInitialAttribute(element, styleProperty);\n const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty);\n element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`);\n };\n this._applyManipulationCallback(selector, manipulationCallBack);\n }\n _saveInitialAttribute(element, styleProperty) {\n const actualValue = element.style.getPropertyValue(styleProperty);\n if (actualValue) {\n Manipulator.setDataAttribute(element, styleProperty, actualValue);\n }\n }\n _resetElementAttributes(selector, styleProperty) {\n const manipulationCallBack = element => {\n const value = Manipulator.getDataAttribute(element, styleProperty);\n // We only want to remove the property if the value is `null`; the value can also be zero\n if (value === null) {\n element.style.removeProperty(styleProperty);\n return;\n }\n Manipulator.removeDataAttribute(element, styleProperty);\n element.style.setProperty(styleProperty, value);\n };\n this._applyManipulationCallback(selector, manipulationCallBack);\n }\n _applyManipulationCallback(selector, callBack) {\n if (isElement(selector)) {\n callBack(selector);\n return;\n }\n for (const sel of SelectorEngine.find(selector, this._element)) {\n callBack(sel);\n }\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap modal.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$7 = 'modal';\nconst DATA_KEY$4 = 'bs.modal';\nconst EVENT_KEY$4 = `.${DATA_KEY$4}`;\nconst DATA_API_KEY$2 = '.data-api';\nconst ESCAPE_KEY$1 = 'Escape';\nconst EVENT_HIDE$4 = `hide${EVENT_KEY$4}`;\nconst EVENT_HIDE_PREVENTED$1 = `hidePrevented${EVENT_KEY$4}`;\nconst EVENT_HIDDEN$4 = `hidden${EVENT_KEY$4}`;\nconst EVENT_SHOW$4 = `show${EVENT_KEY$4}`;\nconst EVENT_SHOWN$4 = `shown${EVENT_KEY$4}`;\nconst EVENT_RESIZE$1 = `resize${EVENT_KEY$4}`;\nconst EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY$4}`;\nconst EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY$4}`;\nconst EVENT_KEYDOWN_DISMISS$1 = `keydown.dismiss${EVENT_KEY$4}`;\nconst EVENT_CLICK_DATA_API$2 = `click${EVENT_KEY$4}${DATA_API_KEY$2}`;\nconst CLASS_NAME_OPEN = 'modal-open';\nconst CLASS_NAME_FADE$3 = 'fade';\nconst CLASS_NAME_SHOW$4 = 'show';\nconst CLASS_NAME_STATIC = 'modal-static';\nconst OPEN_SELECTOR$1 = '.modal.show';\nconst SELECTOR_DIALOG = '.modal-dialog';\nconst SELECTOR_MODAL_BODY = '.modal-body';\nconst SELECTOR_DATA_TOGGLE$2 = '[data-bs-toggle=\"modal\"]';\nconst Default$6 = {\n backdrop: true,\n focus: true,\n keyboard: true\n};\nconst DefaultType$6 = {\n backdrop: '(boolean|string)',\n focus: 'boolean',\n keyboard: 'boolean'\n};\n\n/**\n * Class definition\n */\n\nclass Modal extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element);\n this._backdrop = this._initializeBackDrop();\n this._focustrap = this._initializeFocusTrap();\n this._isShown = false;\n this._isTransitioning = false;\n this._scrollBar = new ScrollBarHelper();\n this._addEventListeners();\n }\n\n // Getters\n static get Default() {\n return Default$6;\n }\n static get DefaultType() {\n return DefaultType$6;\n }\n static get NAME() {\n return NAME$7;\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget);\n }\n show(relatedTarget) {\n if (this._isShown || this._isTransitioning) {\n return;\n }\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$4, {\n relatedTarget\n });\n if (showEvent.defaultPrevented) {\n return;\n }\n this._isShown = true;\n this._isTransitioning = true;\n this._scrollBar.hide();\n document.body.classList.add(CLASS_NAME_OPEN);\n this._adjustDialog();\n this._backdrop.show(() => this._showElement(relatedTarget));\n }\n hide() {\n if (!this._isShown || this._isTransitioning) {\n return;\n }\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$4);\n if (hideEvent.defaultPrevented) {\n return;\n }\n this._isShown = false;\n this._isTransitioning = true;\n this._focustrap.deactivate();\n this._element.classList.remove(CLASS_NAME_SHOW$4);\n this._queueCallback(() => this._hideModal(), this._element, this._isAnimated());\n }\n dispose() {\n EventHandler.off(window, EVENT_KEY$4);\n EventHandler.off(this._dialog, EVENT_KEY$4);\n this._backdrop.dispose();\n this._focustrap.deactivate();\n super.dispose();\n }\n handleUpdate() {\n this._adjustDialog();\n }\n\n // Private\n _initializeBackDrop() {\n return new Backdrop({\n isVisible: Boolean(this._config.backdrop),\n // 'static' option will be translated to true, and booleans will keep their value,\n isAnimated: this._isAnimated()\n });\n }\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n });\n }\n _showElement(relatedTarget) {\n // try to append dynamic modal\n if (!document.body.contains(this._element)) {\n document.body.append(this._element);\n }\n this._element.style.display = 'block';\n this._element.removeAttribute('aria-hidden');\n this._element.setAttribute('aria-modal', true);\n this._element.setAttribute('role', 'dialog');\n this._element.scrollTop = 0;\n const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog);\n if (modalBody) {\n modalBody.scrollTop = 0;\n }\n reflow(this._element);\n this._element.classList.add(CLASS_NAME_SHOW$4);\n const transitionComplete = () => {\n if (this._config.focus) {\n this._focustrap.activate();\n }\n this._isTransitioning = false;\n EventHandler.trigger(this._element, EVENT_SHOWN$4, {\n relatedTarget\n });\n };\n this._queueCallback(transitionComplete, this._dialog, this._isAnimated());\n }\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS$1, event => {\n if (event.key !== ESCAPE_KEY$1) {\n return;\n }\n if (this._config.keyboard) {\n this.hide();\n return;\n }\n this._triggerBackdropTransition();\n });\n EventHandler.on(window, EVENT_RESIZE$1, () => {\n if (this._isShown && !this._isTransitioning) {\n this._adjustDialog();\n }\n });\n EventHandler.on(this._element, EVENT_MOUSEDOWN_DISMISS, event => {\n // a bad trick to segregate clicks that may start inside dialog but end outside, and avoid listen to scrollbar clicks\n EventHandler.one(this._element, EVENT_CLICK_DISMISS, event2 => {\n if (this._element !== event.target || this._element !== event2.target) {\n return;\n }\n if (this._config.backdrop === 'static') {\n this._triggerBackdropTransition();\n return;\n }\n if (this._config.backdrop) {\n this.hide();\n }\n });\n });\n }\n _hideModal() {\n this._element.style.display = 'none';\n this._element.setAttribute('aria-hidden', true);\n this._element.removeAttribute('aria-modal');\n this._element.removeAttribute('role');\n this._isTransitioning = false;\n this._backdrop.hide(() => {\n document.body.classList.remove(CLASS_NAME_OPEN);\n this._resetAdjustments();\n this._scrollBar.reset();\n EventHandler.trigger(this._element, EVENT_HIDDEN$4);\n });\n }\n _isAnimated() {\n return this._element.classList.contains(CLASS_NAME_FADE$3);\n }\n _triggerBackdropTransition() {\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED$1);\n if (hideEvent.defaultPrevented) {\n return;\n }\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;\n const initialOverflowY = this._element.style.overflowY;\n // return if the following background transition hasn't yet completed\n if (initialOverflowY === 'hidden' || this._element.classList.contains(CLASS_NAME_STATIC)) {\n return;\n }\n if (!isModalOverflowing) {\n this._element.style.overflowY = 'hidden';\n }\n this._element.classList.add(CLASS_NAME_STATIC);\n this._queueCallback(() => {\n this._element.classList.remove(CLASS_NAME_STATIC);\n this._queueCallback(() => {\n this._element.style.overflowY = initialOverflowY;\n }, this._dialog);\n }, this._dialog);\n this._element.focus();\n }\n\n /**\n * The following methods are used to handle overflowing modals\n */\n\n _adjustDialog() {\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;\n const scrollbarWidth = this._scrollBar.getWidth();\n const isBodyOverflowing = scrollbarWidth > 0;\n if (isBodyOverflowing && !isModalOverflowing) {\n const property = isRTL() ? 'paddingLeft' : 'paddingRight';\n this._element.style[property] = `${scrollbarWidth}px`;\n }\n if (!isBodyOverflowing && isModalOverflowing) {\n const property = isRTL() ? 'paddingRight' : 'paddingLeft';\n this._element.style[property] = `${scrollbarWidth}px`;\n }\n }\n _resetAdjustments() {\n this._element.style.paddingLeft = '';\n this._element.style.paddingRight = '';\n }\n\n // Static\n static jQueryInterface(config, relatedTarget) {\n return this.each(function () {\n const data = Modal.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config](relatedTarget);\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$2, SELECTOR_DATA_TOGGLE$2, function (event) {\n const target = SelectorEngine.getElementFromSelector(this);\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault();\n }\n EventHandler.one(target, EVENT_SHOW$4, showEvent => {\n if (showEvent.defaultPrevented) {\n // only register focus restorer if modal will actually get shown\n return;\n }\n EventHandler.one(target, EVENT_HIDDEN$4, () => {\n if (isVisible(this)) {\n this.focus();\n }\n });\n });\n\n // avoid conflict when clicking modal toggler while another one is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR$1);\n if (alreadyOpen) {\n Modal.getInstance(alreadyOpen).hide();\n }\n const data = Modal.getOrCreateInstance(target);\n data.toggle(this);\n});\nenableDismissTrigger(Modal);\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Modal);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap offcanvas.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$6 = 'offcanvas';\nconst DATA_KEY$3 = 'bs.offcanvas';\nconst EVENT_KEY$3 = `.${DATA_KEY$3}`;\nconst DATA_API_KEY$1 = '.data-api';\nconst EVENT_LOAD_DATA_API$2 = `load${EVENT_KEY$3}${DATA_API_KEY$1}`;\nconst ESCAPE_KEY = 'Escape';\nconst CLASS_NAME_SHOW$3 = 'show';\nconst CLASS_NAME_SHOWING$1 = 'showing';\nconst CLASS_NAME_HIDING = 'hiding';\nconst CLASS_NAME_BACKDROP = 'offcanvas-backdrop';\nconst OPEN_SELECTOR = '.offcanvas.show';\nconst EVENT_SHOW$3 = `show${EVENT_KEY$3}`;\nconst EVENT_SHOWN$3 = `shown${EVENT_KEY$3}`;\nconst EVENT_HIDE$3 = `hide${EVENT_KEY$3}`;\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY$3}`;\nconst EVENT_HIDDEN$3 = `hidden${EVENT_KEY$3}`;\nconst EVENT_RESIZE = `resize${EVENT_KEY$3}`;\nconst EVENT_CLICK_DATA_API$1 = `click${EVENT_KEY$3}${DATA_API_KEY$1}`;\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY$3}`;\nconst SELECTOR_DATA_TOGGLE$1 = '[data-bs-toggle=\"offcanvas\"]';\nconst Default$5 = {\n backdrop: true,\n keyboard: true,\n scroll: false\n};\nconst DefaultType$5 = {\n backdrop: '(boolean|string)',\n keyboard: 'boolean',\n scroll: 'boolean'\n};\n\n/**\n * Class definition\n */\n\nclass Offcanvas extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n this._isShown = false;\n this._backdrop = this._initializeBackDrop();\n this._focustrap = this._initializeFocusTrap();\n this._addEventListeners();\n }\n\n // Getters\n static get Default() {\n return Default$5;\n }\n static get DefaultType() {\n return DefaultType$5;\n }\n static get NAME() {\n return NAME$6;\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget);\n }\n show(relatedTarget) {\n if (this._isShown) {\n return;\n }\n const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$3, {\n relatedTarget\n });\n if (showEvent.defaultPrevented) {\n return;\n }\n this._isShown = true;\n this._backdrop.show();\n if (!this._config.scroll) {\n new ScrollBarHelper().hide();\n }\n this._element.setAttribute('aria-modal', true);\n this._element.setAttribute('role', 'dialog');\n this._element.classList.add(CLASS_NAME_SHOWING$1);\n const completeCallBack = () => {\n if (!this._config.scroll || this._config.backdrop) {\n this._focustrap.activate();\n }\n this._element.classList.add(CLASS_NAME_SHOW$3);\n this._element.classList.remove(CLASS_NAME_SHOWING$1);\n EventHandler.trigger(this._element, EVENT_SHOWN$3, {\n relatedTarget\n });\n };\n this._queueCallback(completeCallBack, this._element, true);\n }\n hide() {\n if (!this._isShown) {\n return;\n }\n const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$3);\n if (hideEvent.defaultPrevented) {\n return;\n }\n this._focustrap.deactivate();\n this._element.blur();\n this._isShown = false;\n this._element.classList.add(CLASS_NAME_HIDING);\n this._backdrop.hide();\n const completeCallback = () => {\n this._element.classList.remove(CLASS_NAME_SHOW$3, CLASS_NAME_HIDING);\n this._element.removeAttribute('aria-modal');\n this._element.removeAttribute('role');\n if (!this._config.scroll) {\n new ScrollBarHelper().reset();\n }\n EventHandler.trigger(this._element, EVENT_HIDDEN$3);\n };\n this._queueCallback(completeCallback, this._element, true);\n }\n dispose() {\n this._backdrop.dispose();\n this._focustrap.deactivate();\n super.dispose();\n }\n\n // Private\n _initializeBackDrop() {\n const clickCallback = () => {\n if (this._config.backdrop === 'static') {\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);\n return;\n }\n this.hide();\n };\n\n // 'static' option will be translated to true, and booleans will keep their value\n const isVisible = Boolean(this._config.backdrop);\n return new Backdrop({\n className: CLASS_NAME_BACKDROP,\n isVisible,\n isAnimated: true,\n rootElement: this._element.parentNode,\n clickCallback: isVisible ? clickCallback : null\n });\n }\n _initializeFocusTrap() {\n return new FocusTrap({\n trapElement: this._element\n });\n }\n _addEventListeners() {\n EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {\n if (event.key !== ESCAPE_KEY) {\n return;\n }\n if (this._config.keyboard) {\n this.hide();\n return;\n }\n EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED);\n });\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Offcanvas.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config](this);\n });\n }\n}\n\n/**\n * Data API implementation\n */\n\nEventHandler.on(document, EVENT_CLICK_DATA_API$1, SELECTOR_DATA_TOGGLE$1, function (event) {\n const target = SelectorEngine.getElementFromSelector(this);\n if (['A', 'AREA'].includes(this.tagName)) {\n event.preventDefault();\n }\n if (isDisabled(this)) {\n return;\n }\n EventHandler.one(target, EVENT_HIDDEN$3, () => {\n // focus on trigger when it is closed\n if (isVisible(this)) {\n this.focus();\n }\n });\n\n // avoid conflict when clicking a toggler of an offcanvas, while another is open\n const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR);\n if (alreadyOpen && alreadyOpen !== target) {\n Offcanvas.getInstance(alreadyOpen).hide();\n }\n const data = Offcanvas.getOrCreateInstance(target);\n data.toggle(this);\n});\nEventHandler.on(window, EVENT_LOAD_DATA_API$2, () => {\n for (const selector of SelectorEngine.find(OPEN_SELECTOR)) {\n Offcanvas.getOrCreateInstance(selector).show();\n }\n});\nEventHandler.on(window, EVENT_RESIZE, () => {\n for (const element of SelectorEngine.find('[aria-modal][class*=show][class*=offcanvas-]')) {\n if (getComputedStyle(element).position !== 'fixed') {\n Offcanvas.getOrCreateInstance(element).hide();\n }\n }\n});\nenableDismissTrigger(Offcanvas);\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Offcanvas);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/sanitizer.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n// js-docs-start allow-list\nconst ARIA_ATTRIBUTE_PATTERN = /^aria-[\\w-]*$/i;\nconst DefaultAllowlist = {\n // Global attributes allowed on any supplied element below.\n '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],\n a: ['target', 'href', 'title', 'rel'],\n area: [],\n b: [],\n br: [],\n col: [],\n code: [],\n dd: [],\n div: [],\n dl: [],\n dt: [],\n em: [],\n hr: [],\n h1: [],\n h2: [],\n h3: [],\n h4: [],\n h5: [],\n h6: [],\n i: [],\n img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],\n li: [],\n ol: [],\n p: [],\n pre: [],\n s: [],\n small: [],\n span: [],\n sub: [],\n sup: [],\n strong: [],\n u: [],\n ul: []\n};\n// js-docs-end allow-list\n\nconst uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']);\n\n/**\n * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation\n * contexts.\n *\n * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38\n */\n// eslint-disable-next-line unicorn/better-regex\nconst SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i;\nconst allowedAttribute = (attribute, allowedAttributeList) => {\n const attributeName = attribute.nodeName.toLowerCase();\n if (allowedAttributeList.includes(attributeName)) {\n if (uriAttributes.has(attributeName)) {\n return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue));\n }\n return true;\n }\n\n // Check if a regular expression validates the attribute.\n return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp).some(regex => regex.test(attributeName));\n};\nfunction sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {\n if (!unsafeHtml.length) {\n return unsafeHtml;\n }\n if (sanitizeFunction && typeof sanitizeFunction === 'function') {\n return sanitizeFunction(unsafeHtml);\n }\n const domParser = new window.DOMParser();\n const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html');\n const elements = [].concat(...createdDocument.body.querySelectorAll('*'));\n for (const element of elements) {\n const elementName = element.nodeName.toLowerCase();\n if (!Object.keys(allowList).includes(elementName)) {\n element.remove();\n continue;\n }\n const attributeList = [].concat(...element.attributes);\n const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []);\n for (const attribute of attributeList) {\n if (!allowedAttribute(attribute, allowedAttributes)) {\n element.removeAttribute(attribute.nodeName);\n }\n }\n }\n return createdDocument.body.innerHTML;\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap util/template-factory.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$5 = 'TemplateFactory';\nconst Default$4 = {\n allowList: DefaultAllowlist,\n content: {},\n // { selector : text , selector2 : text2 , }\n extraClass: '',\n html: false,\n sanitize: true,\n sanitizeFn: null,\n template: '
'\n};\nconst DefaultType$4 = {\n allowList: 'object',\n content: 'object',\n extraClass: '(string|function)',\n html: 'boolean',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n template: 'string'\n};\nconst DefaultContentType = {\n entry: '(string|element|function|null)',\n selector: '(string|element)'\n};\n\n/**\n * Class definition\n */\n\nclass TemplateFactory extends Config {\n constructor(config) {\n super();\n this._config = this._getConfig(config);\n }\n\n // Getters\n static get Default() {\n return Default$4;\n }\n static get DefaultType() {\n return DefaultType$4;\n }\n static get NAME() {\n return NAME$5;\n }\n\n // Public\n getContent() {\n return Object.values(this._config.content).map(config => this._resolvePossibleFunction(config)).filter(Boolean);\n }\n hasContent() {\n return this.getContent().length > 0;\n }\n changeContent(content) {\n this._checkContent(content);\n this._config.content = {\n ...this._config.content,\n ...content\n };\n return this;\n }\n toHtml() {\n const templateWrapper = document.createElement('div');\n templateWrapper.innerHTML = this._maybeSanitize(this._config.template);\n for (const [selector, text] of Object.entries(this._config.content)) {\n this._setContent(templateWrapper, text, selector);\n }\n const template = templateWrapper.children[0];\n const extraClass = this._resolvePossibleFunction(this._config.extraClass);\n if (extraClass) {\n template.classList.add(...extraClass.split(' '));\n }\n return template;\n }\n\n // Private\n _typeCheckConfig(config) {\n super._typeCheckConfig(config);\n this._checkContent(config.content);\n }\n _checkContent(arg) {\n for (const [selector, content] of Object.entries(arg)) {\n super._typeCheckConfig({\n selector,\n entry: content\n }, DefaultContentType);\n }\n }\n _setContent(template, content, selector) {\n const templateElement = SelectorEngine.findOne(selector, template);\n if (!templateElement) {\n return;\n }\n content = this._resolvePossibleFunction(content);\n if (!content) {\n templateElement.remove();\n return;\n }\n if (isElement(content)) {\n this._putElementInTemplate(getElement(content), templateElement);\n return;\n }\n if (this._config.html) {\n templateElement.innerHTML = this._maybeSanitize(content);\n return;\n }\n templateElement.textContent = content;\n }\n _maybeSanitize(arg) {\n return this._config.sanitize ? sanitizeHtml(arg, this._config.allowList, this._config.sanitizeFn) : arg;\n }\n _resolvePossibleFunction(arg) {\n return execute(arg, [this]);\n }\n _putElementInTemplate(element, templateElement) {\n if (this._config.html) {\n templateElement.innerHTML = '';\n templateElement.append(element);\n return;\n }\n templateElement.textContent = element.textContent;\n }\n}\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap tooltip.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$4 = 'tooltip';\nconst DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn']);\nconst CLASS_NAME_FADE$2 = 'fade';\nconst CLASS_NAME_MODAL = 'modal';\nconst CLASS_NAME_SHOW$2 = 'show';\nconst SELECTOR_TOOLTIP_INNER = '.tooltip-inner';\nconst SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`;\nconst EVENT_MODAL_HIDE = 'hide.bs.modal';\nconst TRIGGER_HOVER = 'hover';\nconst TRIGGER_FOCUS = 'focus';\nconst TRIGGER_CLICK = 'click';\nconst TRIGGER_MANUAL = 'manual';\nconst EVENT_HIDE$2 = 'hide';\nconst EVENT_HIDDEN$2 = 'hidden';\nconst EVENT_SHOW$2 = 'show';\nconst EVENT_SHOWN$2 = 'shown';\nconst EVENT_INSERTED = 'inserted';\nconst EVENT_CLICK$1 = 'click';\nconst EVENT_FOCUSIN$1 = 'focusin';\nconst EVENT_FOCUSOUT$1 = 'focusout';\nconst EVENT_MOUSEENTER = 'mouseenter';\nconst EVENT_MOUSELEAVE = 'mouseleave';\nconst AttachmentMap = {\n AUTO: 'auto',\n TOP: 'top',\n RIGHT: isRTL() ? 'left' : 'right',\n BOTTOM: 'bottom',\n LEFT: isRTL() ? 'right' : 'left'\n};\nconst Default$3 = {\n allowList: DefaultAllowlist,\n animation: true,\n boundary: 'clippingParents',\n container: false,\n customClass: '',\n delay: 0,\n fallbackPlacements: ['top', 'right', 'bottom', 'left'],\n html: false,\n offset: [0, 6],\n placement: 'top',\n popperConfig: null,\n sanitize: true,\n sanitizeFn: null,\n selector: false,\n template: '
' + '
' + '
' + '
',\n title: '',\n trigger: 'hover focus'\n};\nconst DefaultType$3 = {\n allowList: 'object',\n animation: 'boolean',\n boundary: '(string|element)',\n container: '(string|element|boolean)',\n customClass: '(string|function)',\n delay: '(number|object)',\n fallbackPlacements: 'array',\n html: 'boolean',\n offset: '(array|string|function)',\n placement: '(string|function)',\n popperConfig: '(null|object|function)',\n sanitize: 'boolean',\n sanitizeFn: '(null|function)',\n selector: '(string|boolean)',\n template: 'string',\n title: '(string|element|function)',\n trigger: 'string'\n};\n\n/**\n * Class definition\n */\n\nclass Tooltip extends BaseComponent {\n constructor(element, config) {\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s tooltips require Popper (https://popper.js.org)');\n }\n super(element, config);\n\n // Private\n this._isEnabled = true;\n this._timeout = 0;\n this._isHovered = null;\n this._activeTrigger = {};\n this._popper = null;\n this._templateFactory = null;\n this._newContent = null;\n\n // Protected\n this.tip = null;\n this._setListeners();\n if (!this._config.selector) {\n this._fixTitle();\n }\n }\n\n // Getters\n static get Default() {\n return Default$3;\n }\n static get DefaultType() {\n return DefaultType$3;\n }\n static get NAME() {\n return NAME$4;\n }\n\n // Public\n enable() {\n this._isEnabled = true;\n }\n disable() {\n this._isEnabled = false;\n }\n toggleEnabled() {\n this._isEnabled = !this._isEnabled;\n }\n toggle() {\n if (!this._isEnabled) {\n return;\n }\n this._activeTrigger.click = !this._activeTrigger.click;\n if (this._isShown()) {\n this._leave();\n return;\n }\n this._enter();\n }\n dispose() {\n clearTimeout(this._timeout);\n EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler);\n if (this._element.getAttribute('data-bs-original-title')) {\n this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'));\n }\n this._disposePopper();\n super.dispose();\n }\n show() {\n if (this._element.style.display === 'none') {\n throw new Error('Please use show on visible elements');\n }\n if (!(this._isWithContent() && this._isEnabled)) {\n return;\n }\n const showEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOW$2));\n const shadowRoot = findShadowRoot(this._element);\n const isInTheDom = (shadowRoot || this._element.ownerDocument.documentElement).contains(this._element);\n if (showEvent.defaultPrevented || !isInTheDom) {\n return;\n }\n\n // TODO: v6 remove this or make it optional\n this._disposePopper();\n const tip = this._getTipElement();\n this._element.setAttribute('aria-describedby', tip.getAttribute('id'));\n const {\n container\n } = this._config;\n if (!this._element.ownerDocument.documentElement.contains(this.tip)) {\n container.append(tip);\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED));\n }\n this._popper = this._createPopper(tip);\n tip.classList.add(CLASS_NAME_SHOW$2);\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.on(element, 'mouseover', noop);\n }\n }\n const complete = () => {\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_SHOWN$2));\n if (this._isHovered === false) {\n this._leave();\n }\n this._isHovered = false;\n };\n this._queueCallback(complete, this.tip, this._isAnimated());\n }\n hide() {\n if (!this._isShown()) {\n return;\n }\n const hideEvent = EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDE$2));\n if (hideEvent.defaultPrevented) {\n return;\n }\n const tip = this._getTipElement();\n tip.classList.remove(CLASS_NAME_SHOW$2);\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n for (const element of [].concat(...document.body.children)) {\n EventHandler.off(element, 'mouseover', noop);\n }\n }\n this._activeTrigger[TRIGGER_CLICK] = false;\n this._activeTrigger[TRIGGER_FOCUS] = false;\n this._activeTrigger[TRIGGER_HOVER] = false;\n this._isHovered = null; // it is a trick to support manual triggering\n\n const complete = () => {\n if (this._isWithActiveTrigger()) {\n return;\n }\n if (!this._isHovered) {\n this._disposePopper();\n }\n this._element.removeAttribute('aria-describedby');\n EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN$2));\n };\n this._queueCallback(complete, this.tip, this._isAnimated());\n }\n update() {\n if (this._popper) {\n this._popper.update();\n }\n }\n\n // Protected\n _isWithContent() {\n return Boolean(this._getTitle());\n }\n _getTipElement() {\n if (!this.tip) {\n this.tip = this._createTipElement(this._newContent || this._getContentForTemplate());\n }\n return this.tip;\n }\n _createTipElement(content) {\n const tip = this._getTemplateFactory(content).toHtml();\n\n // TODO: remove this check in v6\n if (!tip) {\n return null;\n }\n tip.classList.remove(CLASS_NAME_FADE$2, CLASS_NAME_SHOW$2);\n // TODO: v6 the following can be achieved with CSS only\n tip.classList.add(`bs-${this.constructor.NAME}-auto`);\n const tipId = getUID(this.constructor.NAME).toString();\n tip.setAttribute('id', tipId);\n if (this._isAnimated()) {\n tip.classList.add(CLASS_NAME_FADE$2);\n }\n return tip;\n }\n setContent(content) {\n this._newContent = content;\n if (this._isShown()) {\n this._disposePopper();\n this.show();\n }\n }\n _getTemplateFactory(content) {\n if (this._templateFactory) {\n this._templateFactory.changeContent(content);\n } else {\n this._templateFactory = new TemplateFactory({\n ...this._config,\n // the `content` var has to be after `this._config`\n // to override config.content in case of popover\n content,\n extraClass: this._resolvePossibleFunction(this._config.customClass)\n });\n }\n return this._templateFactory;\n }\n _getContentForTemplate() {\n return {\n [SELECTOR_TOOLTIP_INNER]: this._getTitle()\n };\n }\n _getTitle() {\n return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('data-bs-original-title');\n }\n\n // Private\n _initializeOnDelegatedTarget(event) {\n return this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig());\n }\n _isAnimated() {\n return this._config.animation || this.tip && this.tip.classList.contains(CLASS_NAME_FADE$2);\n }\n _isShown() {\n return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW$2);\n }\n _createPopper(tip) {\n const placement = execute(this._config.placement, [this, tip, this._element]);\n const attachment = AttachmentMap[placement.toUpperCase()];\n return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment));\n }\n _getOffset() {\n const {\n offset\n } = this._config;\n if (typeof offset === 'string') {\n return offset.split(',').map(value => Number.parseInt(value, 10));\n }\n if (typeof offset === 'function') {\n return popperData => offset(popperData, this._element);\n }\n return offset;\n }\n _resolvePossibleFunction(arg) {\n return execute(arg, [this._element]);\n }\n _getPopperConfig(attachment) {\n const defaultBsPopperConfig = {\n placement: attachment,\n modifiers: [{\n name: 'flip',\n options: {\n fallbackPlacements: this._config.fallbackPlacements\n }\n }, {\n name: 'offset',\n options: {\n offset: this._getOffset()\n }\n }, {\n name: 'preventOverflow',\n options: {\n boundary: this._config.boundary\n }\n }, {\n name: 'arrow',\n options: {\n element: `.${this.constructor.NAME}-arrow`\n }\n }, {\n name: 'preSetPlacement',\n enabled: true,\n phase: 'beforeMain',\n fn: data => {\n // Pre-set Popper's placement attribute in order to read the arrow sizes properly.\n // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement\n this._getTipElement().setAttribute('data-popper-placement', data.state.placement);\n }\n }]\n };\n return {\n ...defaultBsPopperConfig,\n ...execute(this._config.popperConfig, [defaultBsPopperConfig])\n };\n }\n _setListeners() {\n const triggers = this._config.trigger.split(' ');\n for (const trigger of triggers) {\n if (trigger === 'click') {\n EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK$1), this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event);\n context.toggle();\n });\n } else if (trigger !== TRIGGER_MANUAL) {\n const eventIn = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSEENTER) : this.constructor.eventName(EVENT_FOCUSIN$1);\n const eventOut = trigger === TRIGGER_HOVER ? this.constructor.eventName(EVENT_MOUSELEAVE) : this.constructor.eventName(EVENT_FOCUSOUT$1);\n EventHandler.on(this._element, eventIn, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event);\n context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true;\n context._enter();\n });\n EventHandler.on(this._element, eventOut, this._config.selector, event => {\n const context = this._initializeOnDelegatedTarget(event);\n context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] = context._element.contains(event.relatedTarget);\n context._leave();\n });\n }\n }\n this._hideModalHandler = () => {\n if (this._element) {\n this.hide();\n }\n };\n EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler);\n }\n _fixTitle() {\n const title = this._element.getAttribute('title');\n if (!title) {\n return;\n }\n if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {\n this._element.setAttribute('aria-label', title);\n }\n this._element.setAttribute('data-bs-original-title', title); // DO NOT USE IT. Is only for backwards compatibility\n this._element.removeAttribute('title');\n }\n _enter() {\n if (this._isShown() || this._isHovered) {\n this._isHovered = true;\n return;\n }\n this._isHovered = true;\n this._setTimeout(() => {\n if (this._isHovered) {\n this.show();\n }\n }, this._config.delay.show);\n }\n _leave() {\n if (this._isWithActiveTrigger()) {\n return;\n }\n this._isHovered = false;\n this._setTimeout(() => {\n if (!this._isHovered) {\n this.hide();\n }\n }, this._config.delay.hide);\n }\n _setTimeout(handler, timeout) {\n clearTimeout(this._timeout);\n this._timeout = setTimeout(handler, timeout);\n }\n _isWithActiveTrigger() {\n return Object.values(this._activeTrigger).includes(true);\n }\n _getConfig(config) {\n const dataAttributes = Manipulator.getDataAttributes(this._element);\n for (const dataAttribute of Object.keys(dataAttributes)) {\n if (DISALLOWED_ATTRIBUTES.has(dataAttribute)) {\n delete dataAttributes[dataAttribute];\n }\n }\n config = {\n ...dataAttributes,\n ...(typeof config === 'object' && config ? config : {})\n };\n config = this._mergeConfigObj(config);\n config = this._configAfterMerge(config);\n this._typeCheckConfig(config);\n return config;\n }\n _configAfterMerge(config) {\n config.container = config.container === false ? document.body : getElement(config.container);\n if (typeof config.delay === 'number') {\n config.delay = {\n show: config.delay,\n hide: config.delay\n };\n }\n if (typeof config.title === 'number') {\n config.title = config.title.toString();\n }\n if (typeof config.content === 'number') {\n config.content = config.content.toString();\n }\n return config;\n }\n _getDelegateConfig() {\n const config = {};\n for (const [key, value] of Object.entries(this._config)) {\n if (this.constructor.Default[key] !== value) {\n config[key] = value;\n }\n }\n config.selector = false;\n config.trigger = 'manual';\n\n // In the future can be replaced with:\n // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]])\n // `Object.fromEntries(keysWithDifferentValues)`\n return config;\n }\n _disposePopper() {\n if (this._popper) {\n this._popper.destroy();\n this._popper = null;\n }\n if (this.tip) {\n this.tip.remove();\n this.tip = null;\n }\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Tooltip.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n });\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Tooltip);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap popover.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$3 = 'popover';\nconst SELECTOR_TITLE = '.popover-header';\nconst SELECTOR_CONTENT = '.popover-body';\nconst Default$2 = {\n ...Tooltip.Default,\n content: '',\n offset: [0, 8],\n placement: 'right',\n template: '
' + '
' + '

' + '
' + '
',\n trigger: 'click'\n};\nconst DefaultType$2 = {\n ...Tooltip.DefaultType,\n content: '(null|string|element|function)'\n};\n\n/**\n * Class definition\n */\n\nclass Popover extends Tooltip {\n // Getters\n static get Default() {\n return Default$2;\n }\n static get DefaultType() {\n return DefaultType$2;\n }\n static get NAME() {\n return NAME$3;\n }\n\n // Overrides\n _isWithContent() {\n return this._getTitle() || this._getContent();\n }\n\n // Private\n _getContentForTemplate() {\n return {\n [SELECTOR_TITLE]: this._getTitle(),\n [SELECTOR_CONTENT]: this._getContent()\n };\n }\n _getContent() {\n return this._resolvePossibleFunction(this._config.content);\n }\n\n // Static\n static jQueryInterface(config) {\n return this.each(function () {\n const data = Popover.getOrCreateInstance(this, config);\n if (typeof config !== 'string') {\n return;\n }\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`);\n }\n data[config]();\n });\n }\n}\n\n/**\n * jQuery\n */\n\ndefineJQueryPlugin(Popover);\n\n/**\n * --------------------------------------------------------------------------\n * Bootstrap scrollspy.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\n\n/**\n * Constants\n */\n\nconst NAME$2 = 'scrollspy';\nconst DATA_KEY$2 = 'bs.scrollspy';\nconst EVENT_KEY$2 = `.${DATA_KEY$2}`;\nconst DATA_API_KEY = '.data-api';\nconst EVENT_ACTIVATE = `activate${EVENT_KEY$2}`;\nconst EVENT_CLICK = `click${EVENT_KEY$2}`;\nconst EVENT_LOAD_DATA_API$1 = `load${EVENT_KEY$2}${DATA_API_KEY}`;\nconst CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item';\nconst CLASS_NAME_ACTIVE$1 = 'active';\nconst SELECTOR_DATA_SPY = '[data-bs-spy=\"scroll\"]';\nconst SELECTOR_TARGET_LINKS = '[href]';\nconst SELECTOR_NAV_LIST_GROUP = '.nav, .list-group';\nconst SELECTOR_NAV_LINKS = '.nav-link';\nconst SELECTOR_NAV_ITEMS = '.nav-item';\nconst SELECTOR_LIST_ITEMS = '.list-group-item';\nconst SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`;\nconst SELECTOR_DROPDOWN = '.dropdown';\nconst SELECTOR_DROPDOWN_TOGGLE$1 = '.dropdown-toggle';\nconst Default$1 = {\n offset: null,\n // TODO: v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: '0px 0px -25%',\n smoothScroll: false,\n target: null,\n threshold: [0.1, 0.5, 1]\n};\nconst DefaultType$1 = {\n offset: '(number|null)',\n // TODO v6 @deprecated, keep it for backwards compatibility reasons\n rootMargin: 'string',\n smoothScroll: 'boolean',\n target: 'element',\n threshold: 'array'\n};\n\n/**\n * Class definition\n */\n\nclass ScrollSpy extends BaseComponent {\n constructor(element, config) {\n super(element, config);\n\n // this._element is the observablesContainer and config.target the menu links wrapper\n this._targetLinks = new Map();\n this._observableSections = new Map();\n this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element;\n this._activeTarget = null;\n this._observer = null;\n this._previousScrollData = {\n visibleEntryTop: 0,\n parentScrollTop: 0\n };\n this.refresh(); // initialize\n }\n\n // Getters\n static get Default() {\n return Default$1;\n }\n static get DefaultType() {\n return DefaultType$1;\n }\n static get NAME() {\n return NAME$2;\n }\n\n // Public\n refresh() {\n this._initializeTargetsAndObservables();\n this._maybeEnableSmoothScroll();\n if (this._observer) {\n this._observer.disconnect();\n } else {\n this._observer = this._getNewObserver();\n }\n for (const section of this._observableSections.values()) {\n this._observer.observe(section);\n }\n }\n dispose() {\n this._observer.disconnect();\n super.dispose();\n }\n\n // Private\n _configAfterMerge(config) {\n // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case\n config.target = getElement(config.target) || document.body;\n\n // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only\n config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin;\n if (typeof config.threshold === 'string') {\n config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value));\n }\n return config;\n }\n _maybeEnableSmoothScroll() {\n if (!this._config.smoothScroll) {\n return;\n }\n\n // unregister any previous listeners\n EventHandler.off(this._config.target, EVENT_CLICK);\n EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => {\n const observableSection = this._observableSections.get(event.target.hash);\n if (observableSection) {\n event.preventDefault();\n const root = this._rootElement || window;\n const height = observableSection.offsetTop - this._element.offsetTop;\n if (root.scrollTo) {\n root.scrollTo({\n top: height,\n behavior: 'smooth'\n });\n return;\n }\n\n // Chrome 60 doesn't support `scrollTo`\n root.scrollTop = height;\n }\n });\n }\n _getNewObserver() {\n const options = {\n root: this._rootElement,\n threshold: this._config.threshold,\n rootMargin: this._config.rootMargin\n };\n return new IntersectionObserver(entries => this._observerCallback(entries), options);\n }\n\n // The logic of selection\n _observerCallback(entries) {\n const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`);\n const activate = entry => {\n this._previousScrollData.visibleEntryTop = entry.target.offsetTop;\n this._process(targetElement(entry));\n };\n const parentScrollTop = (this._rootElement || document.documentElement).scrollTop;\n const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop;\n this._previousScrollData.parentScrollTop = parentScrollTop;\n for (const entry of entries) {\n if (!entry.isIntersecting) {\n this._activeTarget = null;\n this._clearActiveClass(targetElement(entry));\n continue;\n }\n const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop;\n // if we are scrolling down, pick the bigger offsetTop\n if (userScrollsDown && entryIsLowerThanPrevious) {\n activate(entry);\n // if parent isn't scrolled, let's keep the first visible item, breaking the iteration\n if (!parentScrollTop) {\n return;\n }\n continue;\n }\n\n // if we are scrolling up, pick the smallest offsetTop\n if (!userScrollsDown && !entryIsLowerThanPrevious) {\n activate(entry);\n }\n }\n }\n _initializeTargetsAndObservables() {\n this._targetLinks = new Map();\n this._observableSections = new Map();\n const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target);\n for (const anchor of targetLinks) {\n // ensure that the anchor has an id and is not disabled\n if (!anchor.hash || isDisabled(anchor)) {\n continue;\n }\n const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element);\n\n // ensure that the observableSection exists & is visible\n if (isVisible(observableSection)) {\n this._targetLinks.set(decodeURI(anchor.hash), anchor);\n this._observableSections.set(anchor.hash, observableSection);\n }\n }\n }\n _process(target) {\n if (this._activeTarget === target) {\n return;\n }\n this._clearActiveClass(this._config.target);\n this._activeTarget = target;\n target.classList.add(CLASS_NAME_ACTIVE$1);\n this._activateParents(target);\n EventHandler.trigger(this._element, EVENT_ACTIVATE, {\n relatedTarget: target\n });\n }\n _activateParents(target) {\n // Activate dropdown parents\n if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) {\n SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE$1, target.closest(SELECTOR_DROPDOWN)).classList.add(CLASS_NAME_ACTIVE$1);\n return;\n }\n for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) {\n // Set triggered links parents as active\n // With both
+ + + + + + + + + + + + + + + - - - -
- -
-
-
-
+ - + + + + + + +
+ +
+ + + + + + + + + +
+ - +
+ \ No newline at end of file diff --git a/docs/api/modules.html b/docs/api/modules.html new file mode 100644 index 0000000..1f48759 --- /dev/null +++ b/docs/api/modules.html @@ -0,0 +1,729 @@ + + + + + + + + + + + pofff — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + +
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/api/pofff.config.config.html b/docs/api/pofff.config.config.html new file mode 100644 index 0000000..9ecbceb --- /dev/null +++ b/docs/api/pofff.config.config.html @@ -0,0 +1,1370 @@ + + + + + + + + + + + pofff.config.config module — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

pofff.config.config module#

+

Configuration and runtime settings shared across pofff workflows.

+

The data classes combine raw command-line selections, validated TOML input, +FluidFlower geometry, OPM deck arrays, history-matching options, and values +derived while grids, simulations, and benchmark products are created. +PofffConfig is mutable because paths, cell indices, properties, and runtime +flags are populated progressively.

+
+
+class pofff.config.config.CliConfig(fol: Path, deck: Path, jobs: Path, experiment: str, times: str, msat: str, mcon: str, mode: str, figures: str, location: str, use: str)[source]#
+

Bases: object

+

Store command-line values before TOML normalization.

+
+
Attributes:
+
+
fol

Base output directory.

+
+
deck

Directory used for generated OPM deck files.

+
+
jobs

Directory used for generated ERT and Everest job scripts.

+
+
experiment

FluidFlower experimental realization, normalized as run1 through run5.

+
+
times

Comma-separated benchmark evaluation times in hours.

+
+
msat

Minimum gas-saturation threshold used for segmentation.

+
+
mcon

Minimum dissolved-CO2 concentration threshold used for segmentation.

+
+
mode

Selected simulation, file-generation, history-matching, or FAIR workflow.

+
+
figures

Figure mode: all, basic, or none.

+
+
location

Directory containing comparison or precomputed benchmark data.

+
+
use

Whether precomputed Wasserstein distances may be used.

+
+
+
+
+
+
+deck: Path#
+
+ +
+
+experiment: str#
+
+ +
+
+figures: str#
+
+ +
+
+fol: Path#
+
+ +
+
+jobs: Path#
+
+ +
+
+location: str#
+
+ +
+
+mcon: str#
+
+ +
+
+mode: str#
+
+ +
+
+msat: str#
+
+ +
+
+times: str#
+
+ +
+
+use: str#
+
+ +
+ +
+
+class pofff.config.config.PofffConfig(path: Path, fol: Path = PosixPath('output'), deck: Path = PosixPath('output'), jobs: Path = PosixPath('output'), experiment: str = 'run2', times: str = '0.25', msat: str = '1e-2', mcon: str = '1e-1', mode: str = 'single', figures: str = 'basic', location: str = '', use: str = '1', flow: str = 'flow', grid: str = 'corner-point', thickness: str = 'final', mult_thickness: float = 1.0, x: list[int] = <factory>, z: list[int] = <factory>, temperature: list[float] = <factory>, pressure: float = 0.0, diffusion: ndarray = <factory>, sources: list[list[float]] = <factory>, inj: list[list[Any]] = <factory>, krw: str = '(max(0, (sw - swi) / (1 - swi))) ** nkrw', krn: str = '(max(0, (1 - sw - sni) / (1 - sni))) ** nkrn', cap: str = 'pen * ((sw-swi) / (1-swi)) ** (-(1.0 / npen))', cores: int | None = None, maxtime: float | None = None, delete: bool | None = None, ertargs: str | None = None, ensembles: int | None = None, enkf_alpha: float | None = None, errors: ndarray | None = None, random_seed: int | None = None, min_realizations_success: int | None = None, max_function_evaluations: int | None = None, max_batch_num: int | None = None, args: tuple[~typing.Any, ...] | None=None, strategy: str | None = None, maxiter: int | None = None, popsize: int | None = None, tol: float | None = None, mutation: float | tuple[float, float] | None=None, recombination: float | None = None, rng: Any | None = None, callback: Callable | None = None, disp: bool | None = None, polish: bool | None = None, init: str | None = None, atol: float | None = None, updating: str | None = None, workers: int | None = None, constraints: Iterable[Any] | None = None, x0: Sequence[float] | None = None, integrality: Sequence[bool] | None = None, vectorized: bool | None = None, facies: list[int] = <factory>, fluxnum: list[str] = <factory>, fipnum: list[str] = <factory>, porv: list[str] = <factory>, multpv: list[str] = <factory>, dx: list | None = <factory>, dz: list | None = <factory>, dims: list[float] = <factory>, sensors: list[list[float]] = <factory>, sensor_ik: list[list[int]] = <factory>, source_ik: list[list[int]] = <factory>, boxa: list[list[float]] = <factory>, boxb: list[list[float]] = <factory>, boxc: list[list[float]] = <factory>, hm: dict[str, ~typing.Any]=<factory>, monotonic: bool = False, hascellmaps: bool = False, everert: bool = False, tuning: bool = False, para: dict[str, ~typing.Any]=<factory>, nxz: list[int] = <factory>, data: str | None = None)[source]#
+

Bases: object

+

Store TOML input, CLI options, and derived pofff runtime state.

+

TOML-backed attributes retain the spelling used by existing configuration files. +Derived arrays, grid dimensions, feature indices, paths, and history-matching +flags are populated while input is normalized and model files are generated.

+
+
Attributes:
+
+
path

Package root containing geology, templates, jobs, and benchmark resources.

+
+
fol, deck, jobs

Base output, generated deck, and generated job-script directories.

+
+
experiment, times, msat, mcon

Experimental realization, evaluation times [h], and segmentation thresholds.

+
+
mode, figures, location, use

Workflow, figure selection, comparison-data location, and reuse selection.

+
+
flow

OPM Flow command and command-line options.

+
+
grid

Grid representation: cartesian, tensor, or corner-point.

+
+
thickness

initial or final thickness map, or a positive physical thickness [m].

+
+
mult_thickness

Positive multiplier applied to the selected thickness map.

+
+
x, z

Horizontal and vertical refinement counts.

+
+
temperature

Initial and boundary temperatures used by the deck.

+
+
pressure

Positive reference pressure used for initialization and boundary properties.

+
+
diffusion

Two molecular diffusion coefficients, converted from m²/s to m²/day.

+
+
sources

Two injection-source coordinates as [x, z] rows [m].

+
+
inj

Injection rows containing times, rates, and optional TUNING text.

+
+
krw, krn, cap

Python expressions used to generate saturation-function tables.

+
+
cores, maxtime, delete

Shared ERT/Everest resources, run timeout, and cleanup selection.

+
+
ertargs, ensembles, enkf_alpha, errors, random_seed

ERT command options, ensemble controls, observation errors, and random seed.

+
+
min_realizations_success, max_function_evaluations, max_batch_num

Shared success requirement and Everest evaluation limits.

+
+
strategy, maxiter, popsize, tol, mutation, recombination

Differential-evolution strategy and convergence settings.

+
+
rng, callback, disp, polish, init, atol, updating, workers

Additional differential-evolution options passed to Everest.

+
+
constraints, x0, integrality, vectorized

Optional differential-evolution constraints and evaluation controls.

+
+
facies, fluxnum, fipnum, porv, multpv, dx, dz

Generated facies, region, pore-volume, and grid-size arrays for OPM input.

+
+
dims

FluidFlower dimensions in x, y, and z order [m].

+
+
sensors, sensor_ik

Sensor coordinates [m] and zero-based grid indices.

+
+
source_ik

One-based grid indices of the two injection sources.

+
+
boxa, boxb, boxc

Opposite [x, z] corners of the benchmark reporting boxes [m].

+
+
hm, para

History-matching definitions and fixed facies properties.

+
+
monotonic, hascellmaps, everert, tuning

Derived workflow and file-generation flags.

+
+
nxz

Numbers of simulation cells in x and z order.

+
+
data

Uppercase OPM deck base name derived from the output directory.

+
+
+
+
+
+
+args: tuple[Any, ...] | None#
+
+ +
+
+atol: float | None#
+
+ +
+
+boxa: list[list[float]]#
+
+ +
+
+boxb: list[list[float]]#
+
+ +
+
+boxc: list[list[float]]#
+
+ +
+
+callback: Callable | None#
+
+ +
+
+cap: str#
+
+ +
+
+constraints: Iterable[Any] | None#
+
+ +
+
+cores: int | None#
+
+ +
+
+data: str | None#
+
+ +
+
+deck: Path#
+
+ +
+
+delete: bool | None#
+
+ +
+
+diffusion: ndarray#
+
+ +
+
+dims: list[float]#
+
+ +
+
+disp: bool | None#
+
+ +
+
+dx: list | None#
+
+ +
+
+dz: list | None#
+
+ +
+
+enkf_alpha: float | None#
+
+ +
+
+ensembles: int | None#
+
+ +
+
+errors: ndarray | None#
+
+ +
+
+ertargs: str | None#
+
+ +
+
+everert: bool#
+
+ +
+
+experiment: str#
+
+ +
+
+facies: list[int]#
+
+ +
+
+figures: str#
+
+ +
+
+fipnum: list[str]#
+
+ +
+
+flow: str#
+
+ +
+
+fluxnum: list[str]#
+
+ +
+
+fol: Path#
+
+ +
+
+grid: str#
+
+ +
+
+hascellmaps: bool#
+
+ +
+
+hm: dict[str, Any]#
+
+ +
+
+init: str | None#
+
+ +
+
+inj: list[list[Any]]#
+
+ +
+
+integrality: Sequence[bool] | None#
+
+ +
+
+jobs: Path#
+
+ +
+
+krn: str#
+
+ +
+
+krw: str#
+
+ +
+
+location: str#
+
+ +
+
+max_batch_num: int | None#
+
+ +
+
+max_function_evaluations: int | None#
+
+ +
+
+maxiter: int | None#
+
+ +
+
+maxtime: float | None#
+
+ +
+
+mcon: str#
+
+ +
+
+min_realizations_success: int | None#
+
+ +
+
+mode: str#
+
+ +
+
+monotonic: bool#
+
+ +
+
+msat: str#
+
+ +
+
+mult_thickness: float#
+
+ +
+
+multpv: list[str]#
+
+ +
+
+mutation: float | tuple[float, float] | None#
+
+ +
+
+nxz: list[int]#
+
+ +
+
+para: dict[str, Any]#
+
+ +
+
+path: Path#
+
+ +
+
+polish: bool | None#
+
+ +
+
+popsize: int | None#
+
+ +
+
+porv: list[str]#
+
+ +
+
+pressure: float#
+
+ +
+
+random_seed: int | None#
+
+ +
+
+recombination: float | None#
+
+ +
+
+rng: Any | None#
+
+ +
+
+sensor_ik: list[list[int]]#
+
+ +
+
+sensors: list[list[float]]#
+
+ +
+
+source_ik: list[list[int]]#
+
+ +
+
+sources: list[list[float]]#
+
+ +
+
+strategy: str | None#
+
+ +
+
+temperature: list[float]#
+
+ +
+
+thickness: str#
+
+ +
+
+times: str#
+
+ +
+
+tol: float | None#
+
+ +
+
+tuning: bool#
+
+ +
+
+updating: str | None#
+
+ +
+
+use: str#
+
+ +
+
+vectorized: bool | None#
+
+ +
+
+workers: int | None#
+
+ +
+
+x: list[int]#
+
+ +
+
+x0: Sequence[float] | None#
+
+ +
+
+z: list[int]#
+
+ +
+ +
+ + +
+ + + + + + + +
+ + + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/api/pofff.config.html b/docs/api/pofff.config.html new file mode 100644 index 0000000..0a8d49c --- /dev/null +++ b/docs/api/pofff.config.html @@ -0,0 +1,820 @@ + + + + + + + + + + + pofff.config package — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

pofff.config package#

+
+

Submodules#

+
+ +
+
+
+

Module contents#

+
+
+ + +
+ + + + + + + +
+ + + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/api/pofff.core.html b/docs/api/pofff.core.html new file mode 100644 index 0000000..5baecee --- /dev/null +++ b/docs/api/pofff.core.html @@ -0,0 +1,733 @@ + + + + + + + + + + + pofff.core package — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + + + + + + + + + +
+ + + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/api/pofff.core.pofff.html b/docs/api/pofff.core.pofff.html new file mode 100644 index 0000000..e964f7b --- /dev/null +++ b/docs/api/pofff.core.pofff.html @@ -0,0 +1,867 @@ + + + + + + + + + + + pofff.core.pofff module — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

pofff.core.pofff module#

+

Command-line entry point and top-level workflow coordination for pofff.

+

pofff supports connected workflows for FluidFlower history matching:

+
    +
  • File generation creates OPM Flow, ERT, and Everest input.

  • +
  • Single execution runs OPM Flow and generates benchmark data.

  • +
  • History matching runs ERT ensemble studies or Everest optimization.

  • +
  • Postprocessing creates benchmark maps, metrics, and comparison figures.

  • +
+

This module parses and validates CLI arguments, builds the shared configuration, +prepares output folders, dispatches the selected workflows, and reports results. +Grid construction, file writing, execution, and visualization are implemented in +the utility and visualization modules.

+
+
+pofff.core.pofff._build_cli_config(args: Namespace, *, pofff_path: Path) → CliConfig[source]#
+

Build normalized CLI configuration object.

+
+
Parameters:
+
+
argsargparse.Namespace

Parsed command-line arguments.

+
+
pofff_pathPath

Package root containing templates, geology, jobs, and benchmark data.

+
+
+
+
Returns:
+
+
CliConfig

Normalized command-line configuration.

+
+
+
+
+
+ +
+
+pofff.core.pofff._generate_figures(cfg: PofffConfig) → None[source]#
+

Generate benchmark plots and comparison figures.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
+
+ +
+
+pofff.core.pofff._parse_arguments(argv: list[str] | None = None) → Namespace[source]#
+

Define and parse command-line arguments.

+
+
Parameters:
+
+
argvlist[str] | None, optional

Arguments to parse instead of the process command line.

+
+
+
+
Returns:
+
+
argparse.Namespace

Parsed command-line arguments or the corresponding runtime configuration.

+
+
+
+
+
+ +
+
+pofff.core.pofff._prepare_jobs_folder(cfg: PofffConfig) → None[source]#
+

Copy job scripts into output/jobs and make them executable.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
+
+ +
+
+pofff.core.pofff._prepare_simulation(cfg: PofffConfig) → None[source]#
+

Prepare directories and generate all input files.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
+
+ +
+
+pofff.core.pofff._run_simulation_steps(cfg: PofffConfig) → None[source]#
+

Run the selected execution mode.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
+
+ +
+
+pofff.core.pofff._validate_arguments(cmdargs: Namespace) → None[source]#
+

Validate command-line arguments and incompatible operations.

+
+
Parameters:
+
+
cmdargsargparse.Namespace

Parsed command-line arguments.

+
+
+
+
Raises:
+
+
SystemExit

If an argument is invalid or incompatible with the selected workflow.

+
+
+
+
+
+ +
+
+pofff.core.pofff.main(argv: list[str] | None = None) → None[source]#
+

Main pofff execution routine.

+

The selected file-generation, simulation, data, history-matching, and +plotting stages are executed in dependency order.

+
+
Parameters:
+
+
argvlist[str] | None, optional

Arguments to parse instead of the process command line.

+
+
+
+
+
+ +
+ + +
+ + + + + + + +
+ + + + + + + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/api/pofff.html b/docs/api/pofff.html new file mode 100644 index 0000000..355a0a9 --- /dev/null +++ b/docs/api/pofff.html @@ -0,0 +1,932 @@ + + + + + + + + + + + pofff package — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

pofff package#

+
+

Subpackages#

+
+ +
+
+
+

Module contents#

+

Code version

+
+
+ + +
+ + + + + + + +
+ + + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/api/pofff.jobs.data.html b/docs/api/pofff.jobs.data.html new file mode 100644 index 0000000..6757ad0 --- /dev/null +++ b/docs/api/pofff.jobs.data.html @@ -0,0 +1,1304 @@ + + + + + + + + + + + pofff.jobs.data module — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

pofff.jobs.data module#

+

Generate FluidFlower benchmark data from OPM Flow results.

+

The job locates the current simulation, reads restart, grid, initialization, and +summary files through the OPM Python bindings, calculates sparse benchmark time +series, maps saturation and dissolved CO2 to the reporting grid, and writes CSV +files at the requested evaluation times.

+
+
+class pofff.jobs.data.SimulationContext(resolution: NDArray, dense_t: NDArray, maps: Path, where: Path = PosixPath('.'), sparse_t: float = 600.0, dims: NDArray = <factory>, sim: Path | None = None, unrst: ERst | None = None, ini: EclFile | None = None, egrid: EGrid | None = None, smspec: ESmry | None = None, times: list[float] = <factory>, times_summary: NDArray | None = None, time_initial: float = 0.0, porv: NDArray | None = None, actind: NDArray | None = None, gxyz: tuple[int, int, int] | None=None, norst: int | None = None)[source]#
+

Bases: object

+

Store simulation files, time grids, and OPM grid metadata.

+

The object is initialized from command-line selections. OPM handles, report +times, active-cell indices, pore volumes, and grid dimensions are populated +after the current simulation base name is located.

+
+
Attributes:
+
+
resolution

Numbers of benchmark reporting cells in x and z order.

+
+
dense_t

Requested dense-output times in seconds.

+
+
maps

Path to the NumPy mapping from reporting cells to simulation cells.

+
+
where

Directory containing simulation files and receiving generated CSV data.

+
+
sparse_t

Uniform sparse-output interval in seconds.

+
+
dims

FluidFlower dimensions in x, y, and z order, in metres.

+
+
sim

Simulation base path without an OPM file extension.

+
+
unrst

Open OPM restart result, or None before results are loaded.

+
+
ini

Open OPM initialization result, or None before results are loaded.

+
+
egrid

Open OPM grid result, or None before results are loaded.

+
+
smspec

Open OPM summary result, or None before results are loaded.

+
+
times

Restart times relative to the start of CO2 injection, in seconds.

+
+
times_summary

Summary times relative to the start of CO2 injection, in seconds.

+
+
time_initial

Absolute simulator time at the start of CO2 injection, in seconds.

+
+
porv

Pore volumes in global cell order.

+
+
actind

Global indices of cells with positive pore volume.

+
+
gxyz

Simulation-grid dimensions in x, y, and z order.

+
+
norst

Number of restart report steps.

+
+
+
+
+
+
+actind: NDArray | None#
+
+ +
+
+dense_t: NDArray#
+
+ +
+
+dims: NDArray#
+
+ +
+
+egrid: EGrid | None#
+
+ +
+
+gxyz: tuple[int, int, int] | None#
+
+ +
+
+ini: EclFile | None#
+
+ +
+
+maps: Path#
+
+ +
+
+norst: int | None#
+
+ +
+
+porv: NDArray | None#
+
+ +
+
+resolution: NDArray#
+
+ +
+
+sim: Path | None#
+
+ +
+
+smspec: ESmry | None#
+
+ +
+
+sparse_t: float#
+
+ +
+
+time_initial: float#
+
+ +
+
+times: list[float]#
+
+ +
+
+times_summary: NDArray | None#
+
+ +
+
+unrst: ERst | None#
+
+ +
+
+where: Path#
+
+ +
+ +
+
+class pofff.jobs.data.SparseResults(ctx: SimulationContext, times_data: NDArray = <factory>, fipnum: NDArray = <factory>, dx: NDArray = <factory>, dz: NDArray = <factory>, pop1: NDArray = <factory>, pop2: NDArray = <factory>, moba: NDArray = <factory>, imma: NDArray = <factory>, dissa: NDArray = <factory>, seala: NDArray = <factory>, mobb: NDArray = <factory>, immb: NDArray = <factory>, dissb: NDArray = <factory>, sealb: NDArray = <factory>, sealt: NDArray = <factory>, m_c_series: list[float] = <factory>, m_c: NDArray | None = None)[source]#
+

Bases: object

+

Store sparse FluidFlower quantities before writing the time series.

+

The state is initialized by _initialize_sparse_results() with the uniform +time grid, region identifiers, and cell dimensions. Pressure, mobile, immobile, +dissolved, seal, and migration quantities are then populated from OPM summary +and restart results and interpolated to times_data.

+
+
Attributes:
+
+
ctx

Shared simulation context and OPM file handles.

+
+
times_data

Uniform sparse-output times in seconds.

+
+
fipnum

FIPNUM values in global cell order.

+
+
dx, dz

Cell dimensions in x and z order, in metres.

+
+
pop1, pop2

Pressure time series at the two observation points, in pascals.

+
+
moba, imma, dissa, seala

Mobile, immobile, dissolved, and seal CO2 masses in box A, in kilograms.

+
+
mobb, immb, dissb, sealb

Mobile, immobile, dissolved, and seal CO2 masses in box B, in kilograms.

+
+
sealt

Total CO2 mass in the seal regions, in kilograms.

+
+
m_c_series

Mass-center migration values calculated at restart times.

+
+
m_c

Mass-center migration values interpolated to times_data, in metres.

+
+
+
+
+
+
+ctx: SimulationContext#
+
+ +
+
+dissa: NDArray#
+
+ +
+
+dissb: NDArray#
+
+ +
+
+dx: NDArray#
+
+ +
+
+dz: NDArray#
+
+ +
+
+fipnum: NDArray#
+
+ +
+
+imma: NDArray#
+
+ +
+
+immb: NDArray#
+
+ +
+
+m_c: NDArray | None#
+
+ +
+
+m_c_series: list[float]#
+
+ +
+
+moba: NDArray#
+
+ +
+
+mobb: NDArray#
+
+ +
+
+pop1: NDArray#
+
+ +
+
+pop2: NDArray#
+
+ +
+
+seala: NDArray#
+
+ +
+
+sealb: NDArray#
+
+ +
+
+sealt: NDArray#
+
+ +
+
+times_data: NDArray#
+
+ +
+ +
+
+pofff.jobs.data._compute_mass_center_metric(ctx: SimulationContext, res: SparseResults) → None[source]#
+

Calculate the box-C mass-center migration series.

+
+
Parameters:
+
+
ctxSimulationContext

Loaded simulation context and restart metadata.

+
+
resSparseResults

Sparse result state receiving migration values.

+
+
+
+
Raises:
+
+
SystemExit

If restart results or grid metadata are unavailable.

+
+
+
+
+
+ +
+
+pofff.jobs.data._extract_summary_quantities(ctx: SimulationContext, res: SparseResults) → None[source]#
+

Extract pressures and regional CO2 masses from OPM summary vectors.

+
+
Parameters:
+
+
ctxSimulationContext

Loaded simulation context and OPM summary data.

+
+
resSparseResults

Sparse result arrays populated in place.

+
+
+
+
Raises:
+
+
SystemExit

If summary or restart results are unavailable.

+
+
+
+
+
+ +
+
+pofff.jobs.data._find_simulation_base(path: Path) → Path[source]#
+

Locate the simulation base name from an OPM restart file.

+
+
Parameters:
+
+
pathPath

Directory to search for a .UNRST file.

+
+
+
+
Returns:
+
+
Path

Restart-file path without its extension.

+
+
+
+
Raises:
+
+
SystemExit

If the directory contains no .UNRST result.

+
+
+
+
+
+ +
+
+pofff.jobs.data._get_initial_water_pressure(unrst: ERst, fipnum: NDArray, region: int) → float[source]#
+

Return the initial water pressure for one FIP region.

+
+
Parameters:
+
+
unrstOpmRestart

Open OPM restart result containing pressure and capillary pressure.

+
+
fipnumNDArray

FIPNUM values in global cell order.

+
+
regionint

FIPNUM identifying the observation-point region.

+
+
+
+
Returns:
+
+
float

Water pressure corrected for capillary pressure, in pascals.

+
+
+
+
+
+ +
+
+pofff.jobs.data._initialize_sparse_results(ctx: SimulationContext) → SparseResults[source]#
+

Initialize sparse time coordinates and OPM cell-property arrays.

+
+
Parameters:
+
+
ctxSimulationContext

Loaded simulation context with initialization data and restart times.

+
+
+
+
Returns:
+
+
SparseResults

Sparse state initialized with times, FIPNUM, DX, and DZ arrays.

+
+
+
+
Raises:
+
+
SystemExit

If initialization data or restart times are unavailable.

+
+
+
+
+
+ +
+
+pofff.jobs.data._interpolate_sparse_quantities(ctx: SimulationContext, res: SparseResults) → None[source]#
+

Interpolate sparse quantities to a uniform time grid.

+
+
Parameters:
+
+
ctxSimulationContext

Simulation and summary time coordinates.

+
+
resSparseResults

Sparse quantities replaced by their interpolated arrays.

+
+
+
+
+
+ +
+
+pofff.jobs.data._read_opm_results(ctx: SimulationContext) → None[source]#
+

Load OPM restart, initialization, grid, and summary results.

+

Populate OPM handles, injection-relative times, pore volumes, active-cell +indices, and grid dimensions on ctx.

+
+
Parameters:
+
+
ctxSimulationContext

Mutable simulation context with an initialized simulation base path.

+
+
+
+
+
+ +
+
+pofff.jobs.data._sum_summary_vectors(smry: ESmry, expressions: Iterable[str]) → NDArray[source]#
+

Sum selected OPM summary vectors and convert CO2 to kilograms.

+
+
Parameters:
+
+
smryOpmSummary

Open OPM summary result containing the requested vectors.

+
+
expressionsIterable[str]

OPM summary-vector names to add.

+
+
+
+
Returns:
+
+
NDArray

Total mass time series in kilograms.

+
+
+
+
+
+ +
+
+pofff.jobs.data._write_dense_outputs(ctx: SimulationContext) → None[source]#
+

Write spatial maps at the requested dense-output times.

+

Map active-cell saturation and dissolved-CO2 concentration to the reporting +grid and write one CSV file for every requested restart time that is available.

+
+
Parameters:
+
+
ctxSimulationContext

Loaded restart data, reporting-grid resolution, and cell mapping.

+
+
+
+
Raises:
+
+
SystemExit

If restart or initialization data are unavailable.

+
+
+
+
+
+ +
+
+pofff.jobs.data._write_sparse_outputs(ctx: SimulationContext) → None[source]#
+

Calculate and write the sparse benchmark time series.

+
+
Parameters:
+
+
ctxSimulationContext

Loaded simulation context and OPM results.

+
+
+
+
+
+ +
+
+pofff.jobs.data._write_spatial_map_csv(ctx: SimulationContext, refxz: list[NDArray], sgas: NDArray, cco2: NDArray, hours: float) → None[source]#
+

Write one saturation and concentration reporting-grid map.

+
+
Parameters:
+
+
ctxSimulationContext

Output location and reporting-grid resolution.

+
+
refxzlist[NDArray]

Reporting-cell centre coordinates in x and z order, in metres.

+
+
sgasNDArray

Gas saturation in reporting-cell order.

+
+
cco2NDArray

Dissolved-CO2 concentration in reporting-cell order.

+
+
hoursint | float

Evaluation time in hours used in the output filename.

+
+
+
+
+
+ +
+
+pofff.jobs.data._write_time_series_csv(res: SparseResults) → None[source]#
+

Write sparse benchmark quantities to time_series.csv.

+
+
Parameters:
+
+
resSparseResults

Interpolated pressure, mass, and migration series.

+
+
+
+
+
+ +
+
+pofff.jobs.data.main(argv: list[str] | None = None) → None[source]#
+

Generate sparse time-series and dense spatial benchmark data.

+

Parse reporting-grid settings, locate the OPM simulation, load its results, and +write the benchmark time-series and spatial-map CSV files.

+
+
Parameters:
+
+
argvlist[str] | None, optional

Arguments to parse instead of sys.argv[1:].

+
+
+
+
+
+ +
+ + +
+ + + + + + + +
+ + + + + + + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/api/pofff.jobs.delete.html b/docs/api/pofff.jobs.delete.html new file mode 100644 index 0000000..57c9570 --- /dev/null +++ b/docs/api/pofff.jobs.delete.html @@ -0,0 +1,706 @@ + + + + + + + + + + + pofff.jobs.delete module — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

pofff.jobs.delete module#

+

Remove large generated files after a history-matching evaluation.

+

The job preserves nonmonotonic evaluations and otherwise deletes simulator, +include, restart, summary, initialization, CSV, and deck files from the current +working directory.

+
+ + +
+ + + + + + + +
+ + + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/api/pofff.jobs.html b/docs/api/pofff.jobs.html new file mode 100644 index 0000000..5566df7 --- /dev/null +++ b/docs/api/pofff.jobs.html @@ -0,0 +1,788 @@ + + + + + + + + + + + pofff.jobs package — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

pofff.jobs package#

+
+

Submodules#

+ +
+
+

Module contents#

+
+
+ + +
+ + + + + + + +
+ + + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/api/pofff.jobs.metric.html b/docs/api/pofff.jobs.metric.html new file mode 100644 index 0000000..406eca5 --- /dev/null +++ b/docs/api/pofff.jobs.metric.html @@ -0,0 +1,812 @@ + + + + + + + + + + + pofff.jobs.metric module — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

pofff.jobs.metric module#

+

Calculate segmented FluidFlower Wasserstein-distance metrics.

+

The job converts simulated saturation and dissolved-CO2 fields into benchmark +segmentation classes, compares them with experimental segmentations using Earth +Mover’s Distance, and writes per-observation metrics and the Everest objective.

+
+
+pofff.jobs.metric._calculate_wasserstein_distance(file_1: str, file_2: str) → float[source]#
+

Calculate the Earth Mover’s Distance between two segmented images.

+

Both images are converted to grayscale, resized to 140 by 60 pixels, normalized, +and compared on the physical FluidFlower domain.

+
+
Parameters:
+
+
file_1str

Path to the first segmented PNG image.

+
+
file_2str

Path to the second segmented PNG image, resized in place.

+
+
+
+
Returns:
+
+
float

Wasserstein distance in physical x-z coordinates.

+
+
+
+
+
+ +
+
+pofff.jobs.metric._prepare_distance_images(model_result: NDArray[int64], experimental_data: NDArray[int64], indx: int) → float[source]#
+

Write segmentation images and calculate their Wasserstein distance.

+

The PNG round trip is retained to preserve the established metric calculation.

+
+
Parameters:
+
+
model_resultNDArray[np.int64]

Simulated benchmark classes on the 120 by 280 grid.

+
+
experimental_dataNDArray[np.int64]

Experimental benchmark classes on the same grid.

+
+
indxint | str

Evaluation-time identifier used in temporary filenames.

+
+
+
+
Returns:
+
+
float

Wasserstein distance between the generated images.

+
+
+
+
+
+ +
+
+pofff.jobs.metric._segment_spatial_map(file_name: str, xlim: tuple[float, float], zlim: tuple[float, float], satmin: float, conmin: float) → NDArray[int64][source]#
+

Convert saturation and concentration fields to benchmark classes.

+
+
Parameters:
+
+
file_namestr

Spatial-map CSV containing x, z, saturation, and concentration columns.

+
+
xlimtuple[float, float]

Minimum and maximum x coordinates, in metres.

+
+
zlimtuple[float, float]

Minimum and maximum z coordinates, in metres.

+
+
satminfloat

Minimum gas saturation for class 2.

+
+
conminfloat

Minimum dissolved-CO2 concentration for class 1.

+
+
+
+
Returns:
+
+
NDArray[np.int64]

Segmentation on the 120 by 280 benchmark reporting grid.

+
+
+
+
+
+ +
+
+pofff.jobs.metric.main(argv=None) → None[source]#
+

Calculate segmented metrics and the Everest objective.

+
+
Parameters:
+
+
argvlist[str] | None, optional

Arguments to parse instead of the process command line.

+
+
+
+
+
+ +
+ + +
+ + + + + + + +
+ + + + + + + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/api/pofff.utils.html b/docs/api/pofff.utils.html new file mode 100644 index 0000000..2134cfe --- /dev/null +++ b/docs/api/pofff.utils.html @@ -0,0 +1,807 @@ + + + + + + + + + + + pofff.utils package — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

pofff.utils package#

+
+

Submodules#

+
+ +
+
+
+

Module contents#

+
+
+ + +
+ + + + + + + +
+ + + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/api/pofff.utils.inputvalues.html b/docs/api/pofff.utils.inputvalues.html new file mode 100644 index 0000000..131c12e --- /dev/null +++ b/docs/api/pofff.utils.inputvalues.html @@ -0,0 +1,1202 @@ + + + + + + + + + + + pofff.utils.inputvalues module — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

pofff.utils.inputvalues module#

+

Load, validate, and normalize pofff configuration values.

+

This module combines command-line settings with TOML input, validates common +simulation variables and workflow-specific ERT or Everest options, extracts +facies and history-matching definitions, and populates derived runtime settings. +Validation reports accepted values through the shared terminal helpers.

+
+
+pofff.utils.inputvalues._add_validation_error(errors: list[str], message: str) → None[source]#
+

Add a TOML validation error.

+
+
Parameters:
+
+
errorslist[str]

Validation messages collected during the current validation pass.

+
+
messagestr

Human-readable message to display or append.

+
+
+
+
+
+ +
+
+pofff.utils.inputvalues._effective_workflow(cfg_file: dict[str, Any], mode: str) → str[source]#
+

Return the effective simulation, ERT, or Everest workflow.

+
+
Parameters:
+
+
cfg_filedict[str, Any]

Cfg file used by this operation.

+
+
modestr

Selected pofff execution mode.

+
+
+
+
Returns:
+
+
str

Effective workflow name used for validation.

+
+
+
+
+
+ +
+
+pofff.utils.inputvalues._is_finite_number(value: Any) → bool[source]#
+

Return whether a value is a finite non-Boolean number.

+
+
Parameters:
+
+
valueAny

Value to inspect or format.

+
+
+
+
Returns:
+
+
bool

Whether the value is a finite, non-Boolean number.

+
+
+
+
+
+ +
+
+pofff.utils.inputvalues._is_integer(value: Any) → bool[source]#
+

Return whether a value is a non-Boolean integer.

+
+
Parameters:
+
+
valueAny

Value to inspect or format.

+
+
+
+
Returns:
+
+
bool

Whether the value is a non-Boolean integer.

+
+
+
+
+
+ +
+
+pofff.utils.inputvalues._is_tuning_enabled(flow: Any) → bool[source]#
+

Return whether TUNING is enabled in the Flow command.

+
+
Parameters:
+
+
flowAny

Flow used by this operation.

+
+
+
+
Returns:
+
+
bool

Whether the Flow command explicitly enables TUNING.

+
+
+
+
+
+ +
+
+pofff.utils.inputvalues._validate_array(cfg_file: dict[str, Any], key: str, errors: list[str], length: int | None = None, integer: bool = False, minimum: float | None = None, strict: bool = False, nonempty: bool = False) → bool[source]#
+

Check the type, length, and numeric values of a TOML array.

+
+
Parameters:
+
+
cfg_filedict[str, Any]

Cfg file used by this operation.

+
+
keystr

Configuration variable or history-matching parameter name.

+
+
errorslist[str]

Validation messages collected during the current validation pass.

+
+
lengthint | None, optional

Required array length, when specified.

+
+
integerbool, optional

Whether accepted entries must be non-Boolean integers.

+
+
minimumfloat | None, optional

Optional lower bound for accepted values.

+
+
strictbool, optional

Whether the lower bound is exclusive.

+
+
nonemptybool, optional

Whether the array must contain at least one entry.

+
+
+
+
Returns:
+
+
bool

Whether the variable is present and satisfies the array constraints.

+
+
+
+
+
+ +
+
+pofff.utils.inputvalues._validate_expression(cfg_file: dict[str, Any], key: str, errors: list[str]) → None[source]#
+

Check that a TOML variable contains a valid Python expression.

+
+
Parameters:
+
+
cfg_filedict[str, Any]

Cfg file used by this operation.

+
+
keystr

Configuration variable or history-matching parameter name.

+
+
errorslist[str]

Validation messages collected during the current validation pass.

+
+
+
+
+
+ +
+
+pofff.utils.inputvalues._validate_facies(cfg_file: dict[str, Any], key: str, index: int, errors: list[str]) → None[source]#
+

Check one fixed facies-property definition.

+
+
Parameters:
+
+
cfg_filedict[str, Any]

Cfg file used by this operation.

+
+
keystr

Configuration variable or history-matching parameter name.

+
+
indexint

One-based facies index.

+
+
errorslist[str]

Validation messages collected during the current validation pass.

+
+
+
+
+
+ +
+
+pofff.utils.inputvalues._validate_history_parameter(cfg_file: dict[str, Any], key: str, workflow: str, errors: list[str]) → None[source]#
+

Check an ERT distribution or Everest interval definition.

+
+
Parameters:
+
+
cfg_filedict[str, Any]

Cfg file used by this operation.

+
+
keystr

Configuration variable or history-matching parameter name.

+
+
workflowstr

Effective simulation, ERT, or Everest workflow.

+
+
errorslist[str]

Validation messages collected during the current validation pass.

+
+
+
+
+
+ +
+
+pofff.utils.inputvalues._validate_injection_schedule(cfg_file: dict[str, Any], errors: list[str]) → bool[source]#
+

Check injection values and return whether TUNING values are defined.

+
+
Parameters:
+
+
cfg_filedict[str, Any]

Cfg file used by this operation.

+
+
errorslist[str]

Validation messages collected during the current validation pass.

+
+
+
+
Returns:
+
+
bool

Whether at least one injection row defines TUNING values.

+
+
+
+
+
+ +
+
+pofff.utils.inputvalues._validate_matrix(cfg_file: dict[str, Any], key: str, errors: list[str], rows: int, columns: int) → bool[source]#
+

Check the dimensions and numeric values of a TOML matrix.

+
+
Parameters:
+
+
cfg_filedict[str, Any]

Cfg file used by this operation.

+
+
keystr

Configuration variable or history-matching parameter name.

+
+
errorslist[str]

Validation messages collected during the current validation pass.

+
+
rowsint

Required number of matrix rows.

+
+
columnsint

Required number of entries in each matrix row.

+
+
+
+
Returns:
+
+
bool

Whether the variable is present and has the required numeric shape.

+
+
+
+
+
+ +
+
+pofff.utils.inputvalues._validate_number(cfg_file: dict[str, Any], key: str, errors: list[str], minimum: float | None = None, maximum: float | None = None, strict: bool = False, integer: bool = False) → bool[source]#
+

Check that a TOML variable is numeric and in the expected range.

+
+
Parameters:
+
+
cfg_filedict[str, Any]

Cfg file used by this operation.

+
+
keystr

Configuration variable or history-matching parameter name.

+
+
errorslist[str]

Validation messages collected during the current validation pass.

+
+
minimumfloat | None, optional

Optional lower bound for accepted values.

+
+
maximumfloat | None, optional

Optional upper bound for accepted values.

+
+
strictbool, optional

Whether the lower bound is exclusive.

+
+
integerbool, optional

Whether accepted entries must be non-Boolean integers.

+
+
+
+
Returns:
+
+
bool

Whether the variable is present and satisfies the numeric constraints.

+
+
+
+
+
+ +
+
+pofff.utils.inputvalues._validate_string(cfg_file: dict[str, Any], key: str, errors: list[str]) → bool[source]#
+

Check that a TOML variable is a non-empty string.

+
+
Parameters:
+
+
cfg_filedict[str, Any]

Cfg file used by this operation.

+
+
keystr

Configuration variable or history-matching parameter name.

+
+
errorslist[str]

Validation messages collected during the current validation pass.

+
+
+
+
Returns:
+
+
bool

Whether the variable is present and is a non-empty string.

+
+
+
+
+
+ +
+
+pofff.utils.inputvalues._validate_thickness(cfg_file: dict[str, Any], errors: list[str]) → None[source]#
+

Check the thickness-map selection or numeric thickness.

+
+
Parameters:
+
+
cfg_filedict[str, Any]

Cfg file used by this operation.

+
+
errorslist[str]

Validation messages collected during the current validation pass.

+
+
+
+
+
+ +
+
+pofff.utils.inputvalues._validate_toml(cfg_file: dict[str, Any], mode: str) → dict[str, Any][source]#
+

Validate and normalize TOML configuration values.

+
+
Parameters:
+
+
cfg_filedict[str, Any]

Cfg file used by this operation.

+
+
modestr

Selected pofff execution mode.

+
+
+
+
Returns:
+
+
dict[str, Any]

Validated configuration values suitable for PofffConfig.

+
+
+
+
Raises:
+
+
SystemExit

If one or more configuration values are invalid.

+
+
+
+
+
+ +
+
+pofff.utils.inputvalues._warn_and_remove(cfg_file: dict[str, Any], keys: set[str], message: str) → None[source]#
+

Warn about ineffective variables and remove them.

+
+
Parameters:
+
+
cfg_filedict[str, Any]

Cfg file used by this operation.

+
+
keysset[str]

Configuration variables to remove when they are ineffective.

+
+
messagestr

Human-readable message to display or append.

+
+
+
+
+
+ +
+
+pofff.utils.inputvalues.build_config(*, pofff_path: Path, cli: CliConfig, toml: dict) → PofffConfig[source]#
+

Build and return a fully initialized PofffConfig object.

+
+
Parameters:
+
+
pofff_pathPath

Package root containing templates, geology, jobs, and benchmark data.

+
+
cliCliConfig

Cli used by this operation.

+
+
tomldict

Validated TOML values used to populate the runtime configuration.

+
+
+
+
Returns:
+
+
PofffConfig

Runtime configuration populated from CLI and TOML values.

+
+
+
+
+
+ +
+
+pofff.utils.inputvalues.extract_facies(data: dict) → dict[source]#
+

Extract definitions and remove them from the main config dict.

+
+
Parameters:
+
+
datadict

Configuration dictionary modified while extracting definitions.

+
+
+
+
Returns:
+
+
dict

Facies tables removed from the input dictionary.

+
+
+
+
+
+ +
+
+pofff.utils.inputvalues.load_toml(filename: str, mode: str) → dict[source]#
+

Load and validate a TOML configuration file.

+
+
Parameters:
+
+
filenamestr

Path to the input file.

+
+
modestr

Selected pofff execution mode.

+
+
+
+
Returns:
+
+
dict

Validated and normalized TOML configuration values.

+
+
+
+
Raises:
+
+
SystemExit

If the TOML configuration is invalid.

+
+
+
+
+
+ +
+
+pofff.utils.inputvalues.postprocess_config(cfg: PofffConfig, toml: dict) → None[source]#
+

Enrich configuration with derived values and runtime flags.

+

The function converts diffusion to m²/day and populates derived grid, +history-matching, and workflow flags on cfg.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
tomldict

Validated TOML values used to populate the runtime configuration.

+
+
+
+
+
+ +
+
+pofff.utils.inputvalues.process_tuning(cfg: PofffConfig) → None[source]#
+

Enable tuning and normalize injection specifications if requested.

+

Injection rows with TUNING text are expanded in place and cfg.tuning is enabled.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
+
+ +
+ + +
+ + + + + + + +
+ + + + + + + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/api/pofff.utils.mapproperties.html b/docs/api/pofff.utils.mapproperties.html new file mode 100644 index 0000000..d1faf42 --- /dev/null +++ b/docs/api/pofff.utils.mapproperties.html @@ -0,0 +1,1113 @@ + + + + + + + + + + + pofff.utils.mapproperties module — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

pofff.utils.mapproperties module#

+

Generate FluidFlower grids and assign spatial model properties.

+

The module builds Cartesian, tensor, and corner-point grids; reads geological +points, horizons, and facies polygons; applies thickness maps; locates sensors +and injection sources; classifies reporting boxes; and writes mappings from the +simulation grid to the benchmark reporting grid.

+
+
+pofff.utils.mapproperties._assign_box_region(bounds: list[list[float]], sand_region: str, other_region: str, x: NDArray[float64], z: NDArray[float64], fluxnum: NDArray[Any], fipnum: NDArray[Any]) → None[source]#
+

Assign FIPNUM values inside one benchmark reporting box.

+

Cells belonging to facies 1 receive sand_region. All other cells +inside the box receive other_region. The fipnum array is modified +in place.

+
+
Parameters:
+
+
boundslist[list[float]]

Lower-left and upper-right box corners as +[[xmin, zmin], [xmax, zmax]] in metres.

+
+
sand_regionstr

FIPNUM assigned to cells in facies 1.

+
+
other_regionstr

FIPNUM assigned to all other facies inside the box.

+
+
xNDArray[np.float64]

Cell-centre x coordinates in global cell order, in metres.

+
+
zNDArray[np.float64]

Cell-centre z coordinates in global cell order, in metres.

+
+
fluxnumlist[str]

Facies identifiers in global cell order.

+
+
fipnumlist[str]

FIPNUM values in global cell order, modified in place.

+
+
+
+
+
+ +
+
+pofff.utils.mapproperties._assign_corner_point_properties(cfg: PofffConfig, xyz: NDArray[float64], polygons: list[Polygon], xcoord: NDArray[float64], zcoord: NDArray[float64]) → None[source]#
+

Assign properties and feature indices on a corner-point grid.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
xyzNDArray[np.float64]

Cell-centroid coordinates with columns x, y, and z.

+
+
polygonslist[Polygon]

FluidFlower facies polygons in model coordinates.

+
+
xcoordNDArray[np.float64]

Corner-point x coordinates arranged by pillar and vertical interface.

+
+
zcoordNDArray[np.float64]

Corner-point z coordinates arranged by pillar and vertical interface.

+
+
+
+
+
+ +
+
+pofff.utils.mapproperties._assign_spatial_properties(cfg: PofffConfig, polygons: list[Polygon], xcent: NDArray[float64] | None = None, zcent: NDArray[float64] | None = None, xyz: NDArray[float64] | None = None, xcoord: NDArray[float64] | None = None, zcoord: NDArray[float64] | None = None) → None[source]#
+

Dispatch property assignment for the selected grid representation.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
polygonslist[Polygon]

FluidFlower facies polygons in model coordinates.

+
+
xcentNDArray[np.float64] | None, optional

Cell-centre x coordinates in metres.

+
+
zcentNDArray[np.float64] | None, optional

Cell-centre z coordinates in metres.

+
+
xyzNDArray[np.float64] | None, optional

Cell-centroid coordinates with columns x, y, and z.

+
+
xcoordNDArray[np.float64] | None, optional

Corner-point x coordinates arranged by pillar and vertical interface.

+
+
zcoordNDArray[np.float64] | None, optional

Corner-point z coordinates arranged by pillar and vertical interface.

+
+
+
+
+
+ +
+
+pofff.utils.mapproperties._assign_structured_grid_properties(cfg: PofffConfig, xcent: NDArray[float64], zcent: NDArray[float64], polygons: list[Polygon]) → None[source]#
+

Assign properties and feature indices on a structured grid.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
xcentNDArray[np.float64]

Cell-centre x coordinates in metres.

+
+
zcentNDArray[np.float64]

Cell-centre z coordinates in metres.

+
+
polygonslist[Polygon]

FluidFlower facies polygons in model coordinates.

+
+
+
+
+
+ +
+
+pofff.utils.mapproperties._build_corner_point_coordinates(cfg: PofffConfig, points: list[list[float]]) → tuple[NDArray[float64], NDArray[float64], NDArray[float64]][source]#
+

Build refined corner-point coordinates and cell centroids.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
pointslist[list[float]]

Geological points represented by x and z coordinates in metres.

+
+
+
+
Returns:
+
+
tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]

Cell centroids followed by refined corner x and z coordinates.

+
+
+
+
+
+ +
+
+pofff.utils.mapproperties._classify_reporting_boxes(cfg: PofffConfig, x: NDArray[float64], z: NDArray[float64], flux: list[str]) → list[str][source]#
+

Assign benchmark reporting-region FIPNUM values.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
xNDArray[np.float64]

Cell or plotting x coordinates in metres.

+
+
zNDArray[np.float64]

Cell or plotting z coordinates in metres.

+
+
fluxlist[str]

Facies identifiers in global cell order.

+
+
+
+
Returns:
+
+
list[str]

FIPNUM values in global cell order.

+
+
+
+
+
+ +
+
+pofff.utils.mapproperties._load_thickness_map(cfg: PofffConfig) → tuple[float, NDArray, NDArray, NDArray][source]#
+

Load the selected FluidFlower thickness map and multipliers.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
Returns:
+
+
tuple[float, NDArray, NDArray, NDArray]

Minimum thickness, map x and z coordinates, and normalized multipliers.

+
+
+
+
+
+ +
+
+pofff.utils.mapproperties._locate_corner_point_sensors(cfg: PofffConfig, x: NDArray[float64], ztop: NDArray[float64]) → tuple[int, int][source]#
+

Locate both observation sensors on a corner-point grid.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
xNDArray[np.float64]

Cell or plotting x coordinates in metres.

+
+
ztopNDArray[np.float64]

Cell-centre depth coordinates measured from the model top.

+
+
+
+
Returns:
+
+
tuple[int, int]

Global zero-based indices of the two sensor cells.

+
+
+
+
+
+ +
+
+pofff.utils.mapproperties._locate_corner_point_sources(cfg: PofffConfig, x: NDArray[float64], ztop: NDArray[float64]) → None[source]#
+

Locate both injection sources on a corner-point grid.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
xNDArray[np.float64]

Cell or plotting x coordinates in metres.

+
+
ztopNDArray[np.float64]

Cell-centre depth coordinates measured from the model top.

+
+
+
+
+
+ +
+
+pofff.utils.mapproperties._locate_structured_sensors(cfg: PofffConfig, xcent: NDArray[float64], zcent: NDArray[float64]) → tuple[int, int][source]#
+

Locate both observation sensors on a structured grid.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
xcentNDArray[np.float64]

Cell-centre x coordinates in metres.

+
+
zcentNDArray[np.float64]

Cell-centre z coordinates in metres.

+
+
+
+
Returns:
+
+
tuple[int, int]

Global zero-based indices of the two sensor cells.

+
+
+
+
+
+ +
+
+pofff.utils.mapproperties._locate_structured_sources(cfg: PofffConfig, xcent: NDArray[float64], zcent: NDArray[float64]) → None[source]#
+

Locate both injection sources on a structured grid.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
xcentNDArray[np.float64]

Cell-centre x coordinates in metres.

+
+
zcentNDArray[np.float64]

Cell-centre z coordinates in metres.

+
+
+
+
+
+ +
+
+pofff.utils.mapproperties._read_geological_polygons(cfg: PofffConfig) → tuple[list[Polygon], list[list[float]]][source]#
+

Read geological points, line connectivity, and facies polygons.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
Returns:
+
+
tuple[list[Polygon], list[list[float]]]

Facies polygons and the geological point coordinates.

+
+
+
+
+
+ +
+
+pofff.utils.mapproperties._read_horizon_lines(cfg: PofffConfig, points: list[list[float]]) → list[list[list[float]]][source]#
+

Read and order geological horizon polylines.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
pointslist[list[float]]

Geological points represented by x and z coordinates in metres.

+
+
+
+
Returns:
+
+
list[list[list[float]]]

Geological horizons ordered from bottom to top.

+
+
+
+
+
+ +
+
+pofff.utils.mapproperties._refine_vertical_coordinates(xci: NDArray[float64], zci: NDArray[float64], ncz: int, znr: NDArray[float64]) → tuple[NDArray[float64], NDArray[float64], int, int][source]#
+

Subdivide geological layers by their vertical refinement factors.

+
+
Parameters:
+
+
xciNDArray[np.float64]

Unrefined corner-point x coordinates.

+
+
zciNDArray[np.float64]

Unrefined corner-point z coordinates.

+
+
nczint

Number of unrefined vertical cells.

+
+
znrNDArray[np.float64]

Vertical refinement factor for each geological layer.

+
+
+
+
Returns:
+
+
tuple[NDArray[np.float64], NDArray[np.float64], int, int]

Refined x and z coordinates and the new x and z cell counts.

+
+
+
+
+
+ +
+
+pofff.utils.mapproperties._tensor(parts: list[int], dimension: float) → NDArray[float64][source]#
+

Construct tensor-grid edges from per-region refinement counts.

+

Divide the physical dimension into equally sized regions, then subdivide +each region according to its corresponding refinement count.

+
+
Parameters:
+
+
partslist[int]

Positive numbers of cells assigned to consecutive equal-sized regions.

+
+
dimensionfloat

Total physical length of the grid axis, in metres.

+
+
+
+
Returns:
+
+
NDArray[np.float64]

Ordered grid-edge coordinates from zero through dimension, in +metres.

+
+
+
+
+
+ +
+
+pofff.utils.mapproperties._write_cell_map(cfg: PofffConfig, simxcent: NDArray[float64], simzcent: NDArray[float64]) → None[source]#
+

Map benchmark reporting cells to their nearest simulation cells.

+

The mapping is written as cellmap.npy below cfg.deck.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
simxcentNDArray[np.float64]

Simulation-cell x coordinates in global cell order.

+
+
simzcentNDArray[np.float64]

Simulation-cell z coordinates in global cell order.

+
+
+
+
+
+ +
+
+pofff.utils.mapproperties.grid_and_properties(cfg: PofffConfig) → None[source]#
+

Generate the selected grid and assign spatial model properties.

+

The function updates grid dimensions, generated OPM arrays, sensor and source +indices, and the benchmark cell map on cfg.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
+
+ +
+ + +
+ + + + + + + +
+ + + + + + + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/api/pofff.utils.runs.html b/docs/api/pofff.utils.runs.html new file mode 100644 index 0000000..6856ff8 --- /dev/null +++ b/docs/api/pofff.utils.runs.html @@ -0,0 +1,846 @@ + + + + + + + + + + + pofff.utils.runs module — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

pofff.utils.runs module#

+

Execute simulation, data, history-matching, and plotting workflows.

+

The functions run OPM Flow and the maintained data scripts, launch ERT or +Everest, postprocess ensemble results, and coordinate benchmark maps, sparse +values, Wasserstein comparisons, and error tables.

+
+
+pofff.utils.runs._run(cmd: Sequence[str]) → None[source]#
+

Execute a command and abort if it fails.

+
+
Parameters:
+
+
cmdSequence[str]

Command and arguments to execute without a shell.

+
+
+
+
Raises:
+
+
subprocess.CalledProcessError

If the command exits with a nonzero status.

+
+
+
+
+
+ +
+
+pofff.utils.runs.benchmark(cfg: PofffConfig) → None[source]#
+

Generate benchmark figures and comparisons.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
+
+ +
+
+pofff.utils.runs.data(cfg: PofffConfig) → None[source]#
+

Generate benchmark time-series and spatial data.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
Raises:
+
+
subprocess.CalledProcessError

If a data or metric script exits with a nonzero status.

+
+
+
+
+
+ +
+
+pofff.utils.runs.ert(cfg: PofffConfig) → None[source]#
+

Run ERT with configured arguments.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
Raises:
+
+
subprocess.CalledProcessError

If ERT exits with a nonzero status.

+
+
+
+
+
+ +
+
+pofff.utils.runs.everest() → None[source]#
+

Run Everest optimization and skip interactive prompts.

+
+
Raises:
+
+
subprocess.CalledProcessError

If Everest exits with a nonzero status.

+
+
+
+
+
+ +
+
+pofff.utils.runs.flow(cfg: PofffConfig) → None[source]#
+

Run the OPM Flow simulator with configured options.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
Raises:
+
+
subprocess.CalledProcessError

If OPM Flow exits with a nonzero status.

+
+
+
+
+
+ +
+
+pofff.utils.runs.postprocess(cfg: PofffConfig) → None[source]#
+

Postprocess ERT and Everest simulation results.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
+
+ +
+ + +
+ + + + + + + +
+ + + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/api/pofff.utils.terminal.html b/docs/api/pofff.utils.terminal.html new file mode 100644 index 0000000..1d068dd --- /dev/null +++ b/docs/api/pofff.utils.terminal.html @@ -0,0 +1,954 @@ + + + + + + + + + + + pofff.utils.terminal module — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

pofff.utils.terminal module#

+

Format command-line messages for pofff.

+

ANSI colors are applied when supported by the selected stream. The module +provides consistent formatting for invalid, accepted, deprecated, and +informational values, plus fatal errors, warnings, tips, progress messages, and +generated-file reports.

+
+
+pofff.utils.terminal._colorize(text: str, code: str, stream: object = <_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>) → str[source]#
+

Wrap text in an ANSI color sequence when supported.

+
+
Parameters:
+
+
textstr

Text to format.

+
+
codestr

ANSI Select Graphic Rendition code.

+
+
streamobject, optional

Output stream used to determine ANSI-color support.

+
+
+
+
Returns:
+
+
str

Colored text, or unchanged text when colors are disabled.

+
+
+
+
+
+ +
+
+pofff.utils.terminal._supports_color(stream: object = <_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>) → bool[source]#
+

Check whether an output stream supports ANSI colors.

+
+
Parameters:
+
+
streamobject, optional

Output stream used to determine ANSI-color support.

+
+
+
+
Returns:
+
+
bool

Whether ANSI color output is enabled for the stream.

+
+
+
+
+
+ +
+
+pofff.utils.terminal.cli_correct_value(value: str) → str[source]#
+

Format a correct CLI option or value.

+
+
Parameters:
+
+
valuestr

Value to inspect or format.

+
+
+
+
Returns:
+
+
str

Quoted value colored as a valid alternative when supported.

+
+
+
+
+
+ +
+
+pofff.utils.terminal.cli_error_value(value: str) → str[source]#
+

Format an invalid CLI option or value.

+
+
Parameters:
+
+
valuestr

Value to inspect or format.

+
+
+
+
Returns:
+
+
str

Quoted value colored as invalid when supported.

+
+
+
+
+
+ +
+
+pofff.utils.terminal.cli_info_value(value: str) → str[source]#
+

Format an informational CLI option or value.

+
+
Parameters:
+
+
valuestr

Value to inspect or format.

+
+
+
+
Returns:
+
+
str

Quoted value colored as information when supported.

+
+
+
+
+
+ +
+
+pofff.utils.terminal.cli_warning_value(value: str) → str[source]#
+

Format a deprecated CLI option or value.

+
+
Parameters:
+
+
valuestr

Value to inspect or format.

+
+
+
+
Returns:
+
+
str

Quoted value colored as a warning when supported.

+
+
+
+
+
+ +
+
+pofff.utils.terminal.pofff_error(message: str) → NoReturn[source]#
+

Raise a fatal command-line error.

+
+
Parameters:
+
+
messagestr

Human-readable message to display or append.

+
+
+
+
Raises:
+
+
SystemExit

Always raised with the formatted error message.

+
+
+
+
+
+ +
+
+pofff.utils.terminal.pofff_info(message: str) → None[source]#
+

Display an informational command-line message.

+
+
Parameters:
+
+
messagestr

Human-readable message to display or append.

+
+
+
+
+
+ +
+
+pofff.utils.terminal.pofff_name(stream: object = <_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>) → str[source]#
+

Format the pofff program name.

+
+
Parameters:
+
+
streamobject, optional

Output stream used to determine ANSI-color support.

+
+
+
+
Returns:
+
+
str

Formatted program name.

+
+
+
+
+
+ +
+
+pofff.utils.terminal.pofff_success(msg: str, output_dir: str, filenames: list[str]) → None[source]#
+

Display the generated output location and filenames.

+
+
Parameters:
+
+
msgstr

Optional success text printed before the output location.

+
+
output_dirstr

Directory containing the generated files.

+
+
filenameslist[str]

Generated filenames to report.

+
+
+
+
+
+ +
+
+pofff.utils.terminal.pofff_tip(message: str) → None[source]#
+

Display a command-line suggestion.

+
+
Parameters:
+
+
messagestr

Human-readable message to display or append.

+
+
+
+
+
+ +
+
+pofff.utils.terminal.pofff_warning(message: str) → None[source]#
+

Display a non-fatal command-line warning.

+
+
Parameters:
+
+
messagestr

Human-readable message to display or append.

+
+
+
+
+
+ +
+ + +
+ + + + + + + +
+ + + + + + + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/api/pofff.utils.writefile.html b/docs/api/pofff.utils.writefile.html new file mode 100644 index 0000000..56346e7 --- /dev/null +++ b/docs/api/pofff.utils.writefile.html @@ -0,0 +1,925 @@ + + + + + + + + + + + pofff.utils.writefile module — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

pofff.utils.writefile module#

+

Write OPM Flow, ERT, Everest, and generated job files.

+

The module serializes grid and region keywords, renders the main OPM deck, and +creates helper scripts for boundary properties, saturation functions, regional +properties, Flow execution, copying, scaling, and monotonicity checks. It also +renders the ERT and Everest workflow configurations.

+
+
+pofff.utils.writefile._compact_eclipse_values(values: list) → list[source]#
+

Encode repeated numeric values with Eclipse run-length syntax.

+
+
Parameters:
+
+
valueslist

Values to encode in their existing global order.

+
+
+
+
Returns:
+
+
list

Eclipse-formatted strings using n*value compaction.

+
+
+
+
+
+ +
+
+pofff.utils.writefile._render_boundary_properties_script(cfg: PofffConfig) → None[source]#
+

Render the generated boundary-property job script.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
+
+ +
+
+pofff.utils.writefile._render_copy_script(cfg: PofffConfig) → None[source]#
+

Render the generated deck-copy job script.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
+
+ +
+
+pofff.utils.writefile._render_ert_configuration(cfg: PofffConfig) → None[source]#
+

Render the ERT configuration and forward-model sequence.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
+
+ +
+
+pofff.utils.writefile._render_everest_configuration(cfg: PofffConfig) → None[source]#
+

Render the Everest optimization configuration.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
+
+ +
+
+pofff.utils.writefile._render_flow_script(cfg: PofffConfig) → None[source]#
+

Render the generated OPM Flow execution script.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
+
+ +
+
+pofff.utils.writefile._render_monotonic_script(cfg: PofffConfig) → None[source]#
+

Render the generated monotonicity-check job script.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
+
+ +
+
+pofff.utils.writefile._render_region_properties_script(cfg: PofffConfig) → None[source]#
+

Render the generated regional rock-property script.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
+
+ +
+
+pofff.utils.writefile._render_saturation_functions_script(cfg: PofffConfig) → None[source]#
+

Render the generated saturation-function script.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
+
+ +
+
+pofff.utils.writefile._render_scale_script(cfg: PofffConfig) → None[source]#
+

Render the generated Everest parameter-scaling script.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
+
+ +
+
+pofff.utils.writefile._write_keyword_files(cfg: PofffConfig) → None[source]#
+

Write generated OPM property and geometry include files.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
+
+ +
+
+pofff.utils.writefile.create_corner_point_grid(cfg: PofffConfig, xcoord: NDArray, zcoord: NDArray) → None[source]#
+

Write OPM COORD and ZCORN data for the corner-point grid.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
xcoordNDArray

Corner-point x coordinates arranged by pillar and vertical interface.

+
+
zcoordNDArray

Corner-point z coordinates arranged by pillar and vertical interface.

+
+
+
+
+
+ +
+
+pofff.utils.writefile.write_files(cfg: PofffConfig) → None[source]#
+

Generate the OPM deck and optional ERT or Everest files.

+

Generated files are written below cfg.deck, cfg.jobs, and cfg.fol +according to the selected workflow.

+
+
Parameters:
+
+
cfgPofffConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
+
+ +
+ + +
+ + + + + + + +
+ + + + + + + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/api/pofff.visualization.benchmark.html b/docs/api/pofff.visualization.benchmark.html new file mode 100644 index 0000000..d328b52 --- /dev/null +++ b/docs/api/pofff.visualization.benchmark.html @@ -0,0 +1,777 @@ + + + + + + + + + + + pofff.visualization.benchmark module — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

pofff.visualization.benchmark module#

+

Generate FluidFlower benchmark figures and comparisons.

+

The module configures plotting and dispatches the maintained comparison scripts +for time series, sparse values, segmented Wasserstein distances, and mean +distance summaries.

+
+
+pofff.visualization.benchmark._generate_benchmark_comparisons(cfg)[source]#
+

Run the maintained benchmark comparison scripts.

+
+
Parameters:
+
+
cfgobject

Shared pofff configuration and derived runtime state.

+
+
+
+
+
+ +
+
+pofff.visualization.benchmark._parse_arguments(argv)[source]#
+

Define and parse command-line arguments.

+
+
Parameters:
+
+
argvobject

Arguments to parse instead of the process command line.

+
+
+
+
+
+ +
+
+pofff.visualization.benchmark.run(cmd)[source]#
+

Execute external command with logging.

+
+
Parameters:
+
+
cmdobject

Command and arguments to execute without a shell.

+
+
+
+
+
+ +
+
+pofff.visualization.benchmark.run_benchmark(argv=None)[source]#
+

Configure and generate benchmark comparison figures.

+
+
Parameters:
+
+
argvobject, optional

Arguments to parse instead of the process command line.

+
+
+
+
+
+ +
+ + +
+ + + + + + + +
+ + + + + + + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/api/pofff.visualization.error_table.html b/docs/api/pofff.visualization.error_table.html new file mode 100644 index 0000000..b9980db --- /dev/null +++ b/docs/api/pofff.visualization.error_table.html @@ -0,0 +1,762 @@ + + + + + + + + + + + pofff.visualization.error_table module — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

pofff.visualization.error_table module#

+

Compare simulation and experimental FluidFlower benchmark metrics.

+

The module loads sparse quantities and segmented Wasserstein distances, computes +relative errors for participating groups, ranks the reported metrics, and writes +a formatted comparison table for the selected segmentation thresholds.

+
+
+pofff.visualization.error_table._load_sparse_csv(filename)[source]#
+

Load a sparse CSV file, automatically handling optional headers.

+
+
Parameters:
+
+
filenameobject

Path to the input file.

+
+
+
+
+
+ +
+
+pofff.visualization.error_table._parse_arguments(argv)[source]#
+

Parse command-line arguments controlling paths and thresholds.

+
+
Parameters:
+
+
argvobject

Arguments to parse instead of the process command line.

+
+
+
+
+
+ +
+
+pofff.visualization.error_table.run_error_table(argv=None)[source]#
+

Compute and write the benchmark error comparison table.

+
+
Parameters:
+
+
argvobject, optional

Arguments to parse instead of the process command line.

+
+
+
+
+
+ +
+ + +
+ + + + + + + +
+ + + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/api/pofff.visualization.everert.html b/docs/api/pofff.visualization.everert.html new file mode 100644 index 0000000..0a54269 --- /dev/null +++ b/docs/api/pofff.visualization.everert.html @@ -0,0 +1,1374 @@ + + + + + + + + + + + pofff.visualization.everert module — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

pofff.visualization.everert module#

+

Postprocess ERT ensembles and Everest optimization studies.

+

The module reads realizations and optimization batches, computes diagnostics, +plots simulation and parameter distributions, reports failures and +monotonicity rejections, and reconstructs the best simulation for benchmark +postprocessing.

+
+
+class pofff.visualization.everert.Config(path: Path, times: str, jobs: list[str], external: Path, run_command: str, maps: Path, min_saturation: float, min_concentration: float)[source]#
+

Bases: object

+

Store options and paths for ERT and Everest postprocessing.

+

The object is populated from command-line arguments and shared by ensemble +diagnostics, optimization processing, best-simulation extraction, and +benchmark-data generation.

+
+
Attributes:
+
+
path

Root directory containing the ERT or Everest results and generated +figures.

+
+
times

Comma-separated benchmark evaluation times in hours.

+
+
jobs

Generated job scripts to execute when reconstructing the best +simulation.

+
+
external

Root directory containing the external FluidFlower benchmark data.

+
+
run_command

Experimental realization identifier, such as run2.

+
+
maps

Path to the NumPy cell map used to generate benchmark spatial data.

+
+
min_saturation

Minimum gas-saturation threshold used to segment simulated CO2.

+
+
min_concentration

Minimum dissolved-CO2 concentration threshold used to segment simulated +CO2.

+
+
+
+
+
+
+external: Path#
+
+ +
+
+jobs: list[str]#
+
+ +
+
+maps: Path#
+
+ +
+
+min_concentration: float#
+
+ +
+
+min_saturation: float#
+
+ +
+
+path: Path#
+
+ +
+
+run_command: str#
+
+ +
+
+times: str#
+
+ +
+ +
+
+class pofff.visualization.everert.EnsembleState(observations: ndarray, n_e: int, n_i: int, no_obs: int, no_para: int, simulations: list[list[list]] = <factory>, sim_ens: list[list[float]] = <factory>, miss_ens: list[list[float]] = <factory>, par_dis: list[list[float]] = <factory>, idrealisation: list[list[int]] = <factory>, num_ens: list[int] = <factory>, cumulative: list[list[list[float]]] = <factory>, para_file: Path | None = None, para_names: list[str] = <factory>)[source]#
+

Bases: object

+

Store ERT ensemble results and derived diagnostics.

+

The object is initialized from the ERT output structure. Successful +realizations then populate its simulation values, misfits, parameter +distributions, realization identifiers, and per-observation diagnostics +for each iteration.

+
+
Attributes:
+
+
observations

Observation values and uncertainties loaded from deck/obs.txt. +A one-dimensional array represents one observation; otherwise, rows +contain the observed value and its uncertainty.

+
+
n_e

Number of ensemble realizations found in the simulation output.

+
+
n_i

Number of ERT iterations represented in the simulation output.

+
+
no_obs

Number of observations used to calculate the ensemble misfit.

+
+
no_para

Number of history-matching parameters found in parameters.txt.

+
+
simulations

Simulated observable values grouped by iteration and realization.

+
+
sim_ens

Sum of the simulated observable values for each successful realization, +grouped by iteration.

+
+
miss_ens

Normalized least-squares misfit for each successful realization, +grouped by iteration.

+
+
par_dis

History-matching parameter values grouped by iteration. Values for all +parameters and realizations are stored in file order.

+
+
idrealisation

Original realization identifiers for successful simulations, grouped +by iteration.

+
+
num_ens

Number of successful realizations in each iteration.

+
+
cumulative

Simulated values grouped by iteration, observation, and successful +realization for cumulative-misfit plots.

+
+
para_file

Path to the most recently discovered parameters.txt file, or +None when parameter values are unavailable.

+
+
para_names

History-matching parameter names read from para_file.

+
+
+
+
+
+
+cumulative: list[list[list[float]]]#
+
+ +
+
+idrealisation: list[list[int]]#
+
+ +
+
+miss_ens: list[list[float]]#
+
+ +
+
+n_e: int#
+
+ +
+
+n_i: int#
+
+ +
+
+no_obs: int#
+
+ +
+
+no_para: int#
+
+ +
+
+num_ens: list[int]#
+
+ +
+
+observations: ndarray#
+
+ +
+
+par_dis: list[list[float]]#
+
+ +
+
+para_file: Path | None#
+
+ +
+
+para_names: list[str]#
+
+ +
+
+sim_ens: list[list[float]]#
+
+ +
+
+simulations: list[list[list]]#
+
+ +
+ +
+
+class pofff.visualization.everert.OptimizationState(optimization: list[float] = <factory>, optimal_value: float = -inf, ind_batch: int = 0, ind_sim: int = 0, tot_eval: int = 0, s: list[list[int]] = <factory>, x: list[int] = <factory>)[source]#
+

Bases: object

+

Store Everest optimization progress and the best evaluation.

+

The object is populated while processing optimization batches. It tracks +the best objective value reached after each batch, counts successful and +unsuccessful evaluations, and identifies the evaluation from which the +optimal simulation should be reconstructed.

+
+
Attributes:
+
+
optimization

Best objective value reached after each optimization batch.

+
+
optimal_value

Best objective value found across all processed batches.

+
+
ind_batch

Zero-based index of the batch containing the best evaluation.

+
+
ind_sim

Zero-based evaluation index of the best solution within ind_batch.

+
+
tot_eval

Total number of processed optimization evaluations.

+
+
s

Evaluation counts grouped by status and batch. Rows contain successful, +failed, and nonmonotonic evaluation counts, respectively.

+
+
x

Temporary counters for successful, failed, and nonmonotonic evaluations +in the batch currently being processed.

+
+
+
+
+
+
+ind_batch: int#
+
+ +
+
+ind_sim: int#
+
+ +
+
+optimal_value: float#
+
+ +
+
+optimization: list[float]#
+
+ +
+
+s: list[list[int]]#
+
+ +
+
+tot_eval: int#
+
+ +
+
+x: list[int]#
+
+ +
+ +
+
+pofff.visualization.everert._configure_matplotlib()[source]#
+

Apply consistent matplotlib styling.

+
+ +
+
+pofff.visualization.everert._copy_tree_contents(src: Path, dst: Path)[source]#
+

Copy contents of src into dst (cp -r src/. dst/).

+
+
Parameters:
+
+
srcPath

Source file or directory.

+
+
dstPath

Destination file or directory.

+
+
+
+
+
+ +
+
+pofff.visualization.everert._ensure_directory(path: Path)[source]#
+

Create directory if missing.

+
+
Parameters:
+
+
pathPath

Input, output, or project path.

+
+
+
+
+
+ +
+
+pofff.visualization.everert._extract_best_simulation(cfg: Config, state: EnsembleState)[source]#
+

Extract best-fitting ensemble realization.

+
+
Parameters:
+
+
cfgConfig

Shared pofff configuration and derived runtime state.

+
+
stateEnsembleState

Mutable ensemble state populated during postprocessing.

+
+
+
+
+
+ +
+
+pofff.visualization.everert._extract_optimal_solution(cfg: Config, opt: OptimizationState)[source]#
+

Extract and postprocess optimal optimization result.

+
+
Parameters:
+
+
cfgConfig

Shared pofff configuration and derived runtime state.

+
+
optOptimizationState

Processed Everest optimization state.

+
+
+
+
+
+ +
+
+pofff.visualization.everert._initialize_ensemble(cfg: Config) → EnsembleState[source]#
+

Initialize ensemble from simulation folders.

+
+
Parameters:
+
+
cfgConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
Returns:
+
+
EnsembleState

Initialized ensemble state sized from the result directories.

+
+
+
+
+
+ +
+
+pofff.visualization.everert._parse_arguments(argv) → Config[source]#
+

Parse command-line arguments.

+
+
Parameters:
+
+
argvobject

Arguments to parse instead of the process command line.

+
+
+
+
Returns:
+
+
Config

Parsed command-line arguments or the corresponding runtime configuration.

+
+
+
+
+
+ +
+
+pofff.visualization.everert._plot_cumulative_misfit(cfg: Config, state: EnsembleState, tab20)[source]#
+

Plot cumulative misfit contributions per observation.

+
+
Parameters:
+
+
cfgConfig

Shared pofff configuration and derived runtime state.

+
+
stateEnsembleState

Mutable ensemble state populated during postprocessing.

+
+
tab20object

Matplotlib categorical colormap used for consistent series colors.

+
+
+
+
+
+ +
+
+pofff.visualization.everert._plot_history_matching_mismatch(cfg: Config, state: EnsembleState)[source]#
+

Plot ensemble-mean misfit per iteration.

+
+
Parameters:
+
+
cfgConfig

Shared pofff configuration and derived runtime state.

+
+
stateEnsembleState

Mutable ensemble state populated during postprocessing.

+
+
+
+
+
+ +
+
+pofff.visualization.everert._plot_misfit(cfg: Config, state: EnsembleState)[source]#
+

Plot ensemble misfit per iteration.

+
+
Parameters:
+
+
cfgConfig

Shared pofff configuration and derived runtime state.

+
+
stateEnsembleState

Mutable ensemble state populated during postprocessing.

+
+
+
+
+
+ +
+
+pofff.visualization.everert._plot_observable_distribution(cfg: Config, state: EnsembleState)[source]#
+

Plot observable sum distributions.

+
+
Parameters:
+
+
cfgConfig

Shared pofff configuration and derived runtime state.

+
+
stateEnsembleState

Mutable ensemble state populated during postprocessing.

+
+
+
+
+
+ +
+
+pofff.visualization.everert._plot_optimization(cfg: Config, opt: OptimizationState)[source]#
+

Plot optimization progress.

+
+
Parameters:
+
+
cfgConfig

Shared pofff configuration and derived runtime state.

+
+
optOptimizationState

Processed Everest optimization state.

+
+
+
+
+
+ +
+
+pofff.visualization.everert._plot_optimization_details(cfg: Config, opt: OptimizationState)[source]#
+

Plot optimization success statistics.

+
+
Parameters:
+
+
cfgConfig

Shared pofff configuration and derived runtime state.

+
+
optOptimizationState

Processed Everest optimization state.

+
+
+
+
+
+ +
+
+pofff.visualization.everert._plot_parameter_distributions(cfg: Config, state: EnsembleState)[source]#
+

Boxplots of parameter distributions.

+
+
Parameters:
+
+
cfgConfig

Shared pofff configuration and derived runtime state.

+
+
stateEnsembleState

Mutable ensemble state populated during postprocessing.

+
+
+
+
+
+ +
+
+pofff.visualization.everert._plot_simulation_ensemble(cfg: Config, state: EnsembleState, tab20)[source]#
+

Plot initial and final ensemble simulations.

+
+
Parameters:
+
+
cfgConfig

Shared pofff configuration and derived runtime state.

+
+
stateEnsembleState

Mutable ensemble state populated during postprocessing.

+
+
tab20object

Matplotlib categorical colormap used for consistent series colors.

+
+
+
+
+
+ +
+
+pofff.visualization.everert._process_optimization(cfg: Config) → OptimizationState[source]#
+

Process Everest optimization results.

+
+
Parameters:
+
+
cfgConfig

Shared pofff configuration and derived runtime state.

+
+
+
+
Returns:
+
+
OptimizationState

Optimization history and location of the best evaluation.

+
+
+
+
+
+ +
+
+pofff.visualization.everert._read_realisation(cfg: Config, state: EnsembleState, i: int, j: int)[source]#
+

Read one realization and update ensemble statistics.

+
+
Parameters:
+
+
cfgConfig

Shared pofff configuration and derived runtime state.

+
+
stateEnsembleState

Mutable ensemble state populated during postprocessing.

+
+
iint

Iteration or row index.

+
+
jint

Realization or column index.

+
+
+
+
+
+ +
+
+pofff.visualization.everert._run_command(cmd: list[str]) → None[source]#
+

Execute external command and abort on failure.

+
+
Parameters:
+
+
cmdlist[str]

Command and arguments to execute without a shell.

+
+
+
+
Raises:
+
+
subprocess.CalledProcessError

If the command exits with a nonzero status.

+
+
+
+
+
+ +
+
+pofff.visualization.everert._save_figure(fig, path: Path, dpi=300)[source]#
+

Save figure and release memory.

+
+
Parameters:
+
+
figobject

Matplotlib figure to save and close.

+
+
pathPath

Input, output, or project path.

+
+
dpiobject, optional

Output resolution in dots per inch.

+
+
+
+
+
+ +
+
+pofff.visualization.everert.run_everert(argv=None)[source]#
+

Generate diagnostics for an ERT ensemble or Everest optimization.

+
+
Parameters:
+
+
argvobject, optional

Arguments to parse instead of the process command line.

+
+
+
+
+
+ +
+ + +
+ + + + + + + +
+ + + + + + + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/api/pofff.visualization.html b/docs/api/pofff.visualization.html new file mode 100644 index 0000000..83f1998 --- /dev/null +++ b/docs/api/pofff.visualization.html @@ -0,0 +1,807 @@ + + + + + + + + + + + pofff.visualization package — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

pofff.visualization package#

+
+

Submodules#

+
+ +
+
+
+

Module contents#

+
+
+ + +
+ + + + + + + +
+ + + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/api/pofff.visualization.maps.html b/docs/api/pofff.visualization.maps.html new file mode 100644 index 0000000..a73e57d --- /dev/null +++ b/docs/api/pofff.visualization.maps.html @@ -0,0 +1,794 @@ + + + + + + + + + + + pofff.visualization.maps module — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

pofff.visualization.maps module#

+

Create spatial benchmark overlays from simulation and experiment data.

+

Simulation saturation and dissolved-CO2 fields are segmented on the FluidFlower +reporting grid, combined with experimental contour boundaries and geological +lines, and written as maps for the requested evaluation times.

+
+
+pofff.visualization.maps._load_geological_lines(path)[source]#
+

Load geological points and line connectivity for plotting.

+
+
Parameters:
+
+
pathobject

Input, output, or project path.

+
+
+
+
+
+ +
+
+pofff.visualization.maps._segment_simulation_fields(filename, satmin, conmin)[source]#
+

Segment saturation and concentration on the reporting grid.

+
+
Parameters:
+
+
filenameobject

Path to the input file.

+
+
satminobject

Minimum gas-saturation threshold.

+
+
conminobject

Minimum dissolved-CO2 concentration threshold.

+
+
+
+
+
+ +
+
+pofff.visualization.maps._write_overlay_map(simulations, experiment, x, z, points, lines, t)[source]#
+

Write one simulation and experiment overlay map.

+

The figure is written as map_<time>h.png in the current directory.

+
+
Parameters:
+
+
simulationsobject

Segmented simulation classes on the benchmark reporting grid.

+
+
experimentobject

Segmented experimental classes on the benchmark reporting grid.

+
+
xobject

Cell or plotting x coordinates in metres.

+
+
zobject

Cell or plotting z coordinates in metres.

+
+
pointsobject

Geological points represented by x and z coordinates in metres.

+
+
linesobject

Pairs of point indices defining geological line segments.

+
+
tobject

Evaluation time label in hours.

+
+
+
+
+
+ +
+
+pofff.visualization.maps.run_maps(argv=None)[source]#
+

Generate spatial overlay maps for the requested times.

+
+
Parameters:
+
+
argvobject, optional

Arguments to parse instead of the process command line.

+
+
+
+
+
+ +
+ + +
+ + + + + + + +
+ + + + + + + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/api/pofff.visualization.sparse_values.html b/docs/api/pofff.visualization.sparse_values.html new file mode 100644 index 0000000..5e1fc64 --- /dev/null +++ b/docs/api/pofff.visualization.sparse_values.html @@ -0,0 +1,736 @@ + + + + + + + + + + + pofff.visualization.sparse_values module — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

pofff.visualization.sparse_values module#

+

Generate FluidFlower sparse benchmark values from time-series data.

+

The module validates the input duration, extracts maxima, event times, and +72-hour quantities from the benchmark columns, and writes the standard sparse +CSV layout.

+
+
+pofff.visualization.sparse_values.main(timeseries_file: str = 'time_series.csv', output_file: str = 'sparse_data.csv') → None[source]#
+

Read a time series CSV, extract benchmark values.

+

The selected file-generation, simulation, data, history-matching, and +plotting stages are executed in dependency order.

+
+
Parameters:
+
+
timeseries_filestr, optional

Time-series CSV containing the benchmark quantities.

+
+
output_filestr, optional

Sparse benchmark CSV to create.

+
+
+
+
+
+ +
+ + +
+ + + + + + + +
+ + + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/command-line.html b/docs/command-line.html new file mode 100644 index 0000000..1eaa005 --- /dev/null +++ b/docs/command-line.html @@ -0,0 +1,663 @@ + + + + + + + + + + + Command-line reference — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + + + + + + + + + +
+ + + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/configuration/complete.html b/docs/configuration/complete.html new file mode 100644 index 0000000..51fb2f8 --- /dev/null +++ b/docs/configuration/complete.html @@ -0,0 +1,703 @@ + + + + + + + + + + + Complete TOML example — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

Complete TOML example#

+

Here we use as an example one of the configuration files used in the tests +(see input.toml). +The first input parameter is:

+
1# Set mpirun, the full path to the flow executable, and simulator flags (except --output-dir)
+2flow = "flow --newton-min-iterations=1 --enable-tuning=true --enable-opm-rst-file=true"
+
+
+

If flow is not in your path, then write the full path to the executable, as well as adding mpirun +if this is supported in your machine (e.g., flow = “mpirun -np 8 /Users/dmar/Github/opm/build/opm-simulators/bin/flow --newton-min-iterations=1”).

+

The next entries define the following parameters:

+
 4# Set the model parameters
+ 5grid="corner-point" # Type of grid (cartesian, tensor, or corner-point)
+ 6thickness="final" # Thickness maps (measured 'initial', 'final', or a real positive value)
+ 7mult_thickness=1 # Thickness multiplier (a real positive value)
+ 8x=[140] # If cartesian, number of x cells [-]; otherwise, variable array of x-refinement
+ 9z=[7,5,5,5,5,5,5,8,10,9,5] # If cartesian, number of z cells [-]; if tensor, variable array of z-refinement; if corner-point, fix array of z-refinement (11 entries)
+10temperature=[20, 20] # Temperature bottom and top rig [C]
+11pressure=104900 # Pressure at the datum [Pa]
+12diffusion=[1e-9, 2e-8] # Diffusion (in liquid and gas) [m^2/s]
+13sources=[[0.9, 0.3], [1.7, 0.7]] # Source positions: x and z coordinates [m], source 1 to 2
+14
+15# Schedule: 1) injection time [s], 2) time step size to write results [s], 3) injection rate [kg/s] (source1), and 4) injection rate [kg/s] (source2)
+16inj=[[900, 900, 3E-7, 0, '1e-2 3e-4 1e-20 1e-20 1.6 0.2 0.65 1.1']]
+17
+18# Facie Properties
+19facie1={"permx1"=50e3,"permz1"=50e3,"poro1"=0.37,"disperc1"=1e-1,"swi1"=0.32,"sni1"=0.3,"pen1"=1500,"nkrw1"=2,"nkrn1"=2,"npe1"=2,"thre1"=5e-2,"npnt1"=100}
+20facie2={"permx2"=100e3,"permz2"=100e3,"poro2"=0.38,"disperc2"=1e-1,"swi2"=0.14,"sni2"=0.3,"pen2"=800,"nkrw2"=2,"nkrn2"=2,"npe2"=2,"thre2"=5e-2,"npnt2"=100}
+21facie3={"permx3"=300e3,"permz3"=300e3,"poro3"=0.40,"disperc3"=1e-1,"swi3"=0.12,"sni3"=0.1,"pen3"=200,"nkrw3"=2,"nkrn3"=2,"npe3"=2,"thre3"=5e-2,"npnt3"=100}
+22facie4={"permx4"=800e3,"permz4"=800e3,"poro4"=0.39,"disperc4"=1e-1,"swi4"=0.12,"sni4"=0.1,"pen4"=150,"nkrw4"=2,"nkrn4"=2,"npe4"=2,"thre4"=5e-2,"npnt4"=100}
+23facie5={"permx5"=1500e3,"permz5"=1500e3,"poro5"=0.39,"disperc5"=1e-1,"swi5"=0.12,"sni5"=0.1,"pen5"=100,"nkrw5"=2,"nkrn5"=2,"npe5"=2,"thre5"=5e-2,"npnt5"=100}
+24facie6={"permx6"=3000e3,"permz6"=3000e3,"poro6"=0.42,"disperc6"=1e-1,"swi6"=0,"sni6"=0,"pen6"=1,"nkrw6"=2,"nkrn6"=2,"npe6"=2,"thre6"=5e-2,"npnt6"=100}
+25
+26# Set the saturation functions
+27krw="(max(0, (sw - swi) / (1 - swi))) ** nkrw"             #Wetting rel perm saturation function [-]
+28krn="(max(0, (1 - sw - sni) / (1 - sni))) ** nkrn"         #Non-wetting rel perm saturation function [-]
+29cap="pen * ((sw-swi) / (1-swi)) ** (-(1.0 / npen))"        #Capillary pressure saturation function [Pa]
+
+
+

Each line adds a description of the variables. For the facie properties, “THREN” is the threshold to evaluate the capillary pressure function to avoid dividing by 0, +and “NPNTN” is the number of points to generate the saturation tables.

+

See the input files in the examples and publication +to set the history matchings.

+
+ + +
+ + + + + + + +
+ + + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/configuration/facies.html b/docs/configuration/facies.html new file mode 100644 index 0000000..58653c4 --- /dev/null +++ b/docs/configuration/facies.html @@ -0,0 +1,683 @@ + + + + + + + + + + + Facies and saturation functions — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

Facies and saturation functions#

+

Six required facieN tables define permeability, porosity, dispersion, residual saturations, capillary entry pressure, Corey exponents, capillary exponent, threshold, and table resolution.

+
+

Required keys#

+

For each facies N: permxN, permzN, poroN, dispercN, swiN, sniN, penN, nkrwN, nkrnN, npeN, threN, and npntN. npntN is an integer of at least 2. Porosity and residual saturations lie in [0, 1], and swiN + sniN <= 1.

+
+
+

krw, krn, and cap#

+

These required strings are valid Python expressions used to generate wetting relative permeability, non-wetting relative permeability, and capillary-pressure tables. threN prevents evaluation too close to a singular endpoint, and npntN controls the number of table points.

+
+
+ + +
+ + + + + + + +
+ + + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/configuration/history-matching.html b/docs/configuration/history-matching.html new file mode 100644 index 0000000..725838e --- /dev/null +++ b/docs/configuration/history-matching.html @@ -0,0 +1,689 @@ + + + + + + + + + + + ERT and Everest settings — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

ERT and Everest settings#

+

History matching requires cores, min_realizations_success, and at least one facies or thickness parameter.

+
+

ERT#

+

ERT additionally requires ertargs, ensembles, enkf_alpha, and positive errors. Optional random_seed controls reproducibility.

+
+
+

Everest#

+

Everest requires max_function_evaluations and popsize. Supported differential-evolution options include strategy, maxiter, tol, mutation, recombination, rng, disp, polish, init, atol, updating, workers, x0, integrality, and vectorized.

+
+
+

History-matching parameters#

+

ERT parameters use a distribution name and two parameters. Everest parameters use [initial, minimum, maximum, scale]. Facies properties may be selected by their numbered key, isotropic permeability may use permN, and thicknessmult controls the thickness multiplier.

+

monotonic = true only has an effect when at least two optimized variables form a comparable same-property facies sequence.

+
+
+ + +
+ + + + + + + +
+ + + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/configuration/simulation.html b/docs/configuration/simulation.html new file mode 100644 index 0000000..bb825ba --- /dev/null +++ b/docs/configuration/simulation.html @@ -0,0 +1,702 @@ + + + + + + + + + + + Simulation setup — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

Simulation setup#

+
+

flow#

+

Non-empty OPM Flow command and flags. pofff adds the output directory. Include --enable-tuning=true when injection rows contain TUNING values.

+
+
+

grid#

+

Accepted values are cartesian, tensor, and corner-point. Cartesian grids require one entry in x and z. Corner-point grids require 11 entries in z.

+
+
+

thickness and mult_thickness#

+

Select initial or final measured thickness, or provide a positive physical thickness. mult_thickness is a positive multiplier.

+
+
+

x and z#

+

Positive integer refinement arrays. Their interpretation depends on grid.

+
+
+

temperature, pressure, and diffusion#

+

temperature contains bottom and top values [°C]. pressure is positive [Pa]. diffusion contains liquid and gas coefficients [m²/s].

+
+
+

sources and inj#

+

sources has two [x, z] rows [m]. Each injection row contains injection time [s], write time step [s], rates for sources 1 and 2 [kg/s], and optionally one TUNING string.

+
+
+ + +
+ + + + + + + +
+ + + + + + + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/configuration_file.html b/docs/configuration_file.html index 8c37b21..626065a 100644 --- a/docs/configuration_file.html +++ b/docs/configuration_file.html @@ -1,161 +1,667 @@ - - - - - - - Configuration file — pofff documentation - - - - - - - - - - - + + + + + + + + + Configuration reference — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + +
+ + + + + + + + + + -
- - -
- -
-
-
-
    -
  • - -
  • - View page source -
  • + +
    + + +
    +
    + +
    +
    + +
    + +
    + +
    -
    -
    - -
    -

    Configuration file

    -

    Here we use as an example one of the configuration files used in the tests -(see input.toml). -The first input parameter is:

    -
    1# Set mpirun, the full path to the flow executable, and simulator flags (except --output-dir)
    -2flow = "flow --newton-min-iterations=1 --enable-tuning=true --enable-opm-rst-file=true"
    -
    + +
    + +
    -

    If flow is not in your path, then write the full path to the executable, as well as adding mpirun -if this is supported in your machine (e.g., flow = “mpirun -np 8 /Users/dmar/Github/opm/build/opm-simulators/bin/flow --newton-min-iterations=1”).

    -

    The next entries define the following parameters:

    -
     4# Set the model parameters
    - 5grid="corner-point" # Type of grid (cartesian, tensor, or corner-point)
    - 6thickness="final" # Thickness maps (measured 'initial', 'final', or a real positive value)
    - 7mult_thickness=1 # Thickness multiplier (a real positive value)
    - 8x=[140] # If cartesian, number of x cells [-]; otherwise, variable array of x-refinement
    - 9z=[7,5,5,5,5,5,5,8,10,9,5] # If cartesian, number of z cells [-]; if tensor, variable array of z-refinement; if corner-point, fix array of z-refinement (18 entries)
    -10temperature=[20, 20] # Temperature bottom and top rig [C]
    -11pressure=104900 # Pressure at the datum [Pa]
    -12diffusion=[1e-9, 2e-8] # Diffusion (in liquid and gas) [m^2/s]
    -13sources=[[0.9, 0.3], [1.7, 0.7]] # Source positions: x and z coordinates [m], source 1 to 2
    -14
    -15# Schedule: 1) injection time [s], 2) time step size to write results [s], 3) injection rate [kg/s] (source1), and 4) injection rate [kg/s] (source2)
    -16inj=[[900, 900, 3E-7, 0, '1e-2 3e-4 1e-20 1e-20 1.6 0.2 0.65 1.1']]
    -17
    -18# Facie Properties
    -19facie1={"permx1"=50e3,"permz1"=50e3,"poro1"=0.37,"disperc1"=1e-1,"swi1"=0.32,"sni1"=0.3,"pen1"=1500,"nkrw1"=2,"nkrn1"=2,"npe1"=2,"thre1"=5e-2,"npnt1"=100}
    -20facie2={"permx2"=100e3,"permz2"=100e3,"poro2"=0.38,"disperc2"=1e-1,"swi2"=0.14,"sni2"=0.3,"pen2"=800,"nkrw2"=2,"nkrn2"=2,"npe2"=2,"thre2"=5e-2,"npnt2"=100}
    -21facie3={"permx3"=300e3,"permz3"=300e3,"poro3"=0.40,"disperc3"=1e-1,"swi3"=0.12,"sni3"=0.1,"pen3"=200,"nkrw3"=2,"nkrn3"=2,"npe3"=2,"thre3"=5e-2,"npnt3"=100}
    -22facie4={"permx4"=800e3,"permz4"=800e3,"poro4"=0.39,"disperc4"=1e-1,"swi4"=0.12,"sni4"=0.1,"pen4"=150,"nkrw4"=2,"nkrn4"=2,"npe4"=2,"thre4"=5e-2,"npnt4"=100}
    -23facie5={"permx5"=1500e3,"permz5"=1500e3,"poro5"=0.39,"disperc5"=1e-1,"swi5"=0.12,"sni5"=0.1,"pen5"=100,"nkrw5"=2,"nkrn5"=2,"npe5"=2,"thre5"=5e-2,"npnt5"=100}
    -24facie6={"permx6"=3000e3,"permz6"=3000e3,"poro6"=0.42,"disperc6"=1e-1,"swi6"=0,"sni6"=0,"pen6"=1,"nkrw6"=2,"nkrn6"=2,"npe6"=2,"thre6"=5e-2,"npnt6"=100}
    -25
    -26# Set the saturation functions
    -27krw="(max(0, (sw - swi) / (1 - swi))) ** nkrw"             #Wetting rel perm saturation function [-]
    -28krn="(max(0, (1 - sw - sni) / (1 - sni))) ** nkrn"         #Non-wetting rel perm saturation function [-]
    -29cap="pen * ((sw-swi) / (1-swi)) ** (-(1.0 / npen))"        #Capillary pressure saturation function [Pa]
    -
    -

    Each line adds a description of the variables. For the facie properties, “THREN” is the threshold to evaluate the capillary pressure function to avoid dividing by 0, -and “NPNTN” is the number of points to generate the saturation tables.

    -

    See the input files in the examples and publication -to set the history matchings.

    + + + + + +
    -
    -
    + + + + +
+ +
+ + + + + +
+ +
+ + + - + + + + + +
+ - +
+ \ No newline at end of file diff --git a/docs/contributing.html b/docs/contributing.html index 144f60e..8b51bdb 100644 --- a/docs/contributing.html +++ b/docs/contributing.html @@ -1,99 +1,544 @@ - - - - - - - Contributing — pofff documentation - - - - - - - - - - - + + + + + + + + + Contributing — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + - -
-
+ + + + + + + + + + + + + + + + - - -
- -
-
-
-
+ + + + + + + + + + +
+ +
+ + + + + + + + + +
+ - +
+ \ No newline at end of file diff --git a/docs/examples.html b/docs/examples.html index 1ba0441..af577cc 100644 --- a/docs/examples.html +++ b/docs/examples.html @@ -1,243 +1,697 @@ - - - - - - - Examples — pofff documentation - - - - - - - - - - - + + + + + + + + + Examples — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + +
+ + + + + + + + + + -
- - -
- -
-
-
-
    -
  • - -
  • - View page source -
  • + +
    + + +
    +
    + +
    +
    + +
    + +
    + + +
    + +
    + +
    -
    -
    - +
    + + + + + +
    +
    -

    Examples

    -
    -

    Hello world

    -

    The examples folder contains a few configuration files -with low grid resolution and shorter simulation times (for initial testing of the framework). For example, by executing:

    -
    pofff -i single.toml -t 24,48,72
    -
    -
    -

    The following is the figure map_24h.png. You can compare your example results to this figure to evaluate if your example ran correctly:

    -
    -_images/map_24h.png -
    -
    -

    Tip

    -

    All previous commands can be executed as (docs_hello_world.sh):

    -
    . ./tests/scripts/docs_hello_world.sh
    -
    +

    Examples#

    +

    These examples complement the Tutorial. The tutorial now contains the +complete Add your results workflow. This gallery focuses on the remaining +maintained workflows: a small first run, third-party simulator results, ERT and +Everest history matching, and visualization with plopm.

    +
    +
    +
    +
    + +
    +
    +Hello world
    +

    Run a low-resolution case and compare the generated 24-hour map.

    +Hello world
    -
    -
    -

    Adding your results

    -

    The publication folder contains the configuration files used for the results in the -pofff paper (see publication for details in the steps to reproduce the figures in the paper).

    -
    pofff -i results.toml -o YOURS -m single -t 24,48,72,96,120 -f all
    -
    -
    -

    This generates the figures in the paper in addition to the new simulation labeled as the name of the output folder (“YOURS” in this case), e.g., for the compare_all_time_series.png:

    -
    -_images/compare_all_time_series.png -
    -
    -

    Tip

    -

    One can always modify the plotting scripts to change the colors, line styles, font sizes, etc.

    -
    -

    We welcome pull request with your configuration files to the examples folder for cases with better results (less error in the error_table_satmin-0.01_conmin-0.1.csv).

    -
    -

    Tip

    -

    All previous commands can be executed as (docs_adding_your_results.sh):

    -
    . ./tests/scripts/docs_adding_your_results.sh
    -
    -
    -
    -
    -

    Note

    -

    One can always also generate the sparse_data.csv, time_series.csv, and spatial_map_Xh.csv files using other simulators and use the pofff tool to compare -to the experimental data, forecast, CSIRO, MIT_M1, and CSSR data. To this end, you can add those files to a folder call “MY_SIMULATOR” and execute:

    -
    pofff -t 24,48,72,96,120 -m none -o MY_SIMULATOR -f all
    -
    -
    -

    Note that the spatial maps csvs need to be given in the x range of 0 to 2.8 m and z range of 0 to 1.2 m.

    +
    +
    + +
    +
    +History matching
    +

    Configure and run ERT ensemble studies or Everest optimization.

    -
    -
    -

    History matching

    -

    The flag -m can be set to everest or ert to run the history matchings, which also requires to set additional -variables in the toml configuration file (e.g., parameters to history match, number of parallel runs, random seed). For example, to run the -last iteration of the history matching in the pofff paper corresponding to everest_iter_3.toml:

    -
    pofff -i everest_iter_3.toml -o everest_iter_3 -m everest -t 24,48,72,96,120 -f all
    -
    -
    -
    -

    Warning

    -

    If you are running everest_iter_3.toml locally in your machine, then you might need to decrease the number of parallel runs (cores = 50 in line 39) and maximum function evaluations (max_function_evaluations = 200000) to your system capabilities.

    -
    -

    The possible parameters to hm are the 13 parameters for facie 1 to 6 (e.g., permx2, pen5, np6), in addition to consider isotropic permeability (e.g., perm3), and a constant to multiply the tickness (i.e., thicknessmult). -For additional examples on how to set the history matching studies using ert or everest, -see/run the scripts in the tests and publication folder.

    -
    -

    Note

    -

    We refer to the documentation of everest and ert -for the description of the different keywords. While via the toml configuration files in pofff we have added the most common keywords, then one could always -add additional keywords to the generated (after execution of pofff) everest (everest.yml) and ert (ert.ert) configuration files, and after running the history matching directly using the everest/ert command line executables. -Details on all supported (and format types) variables that can be assigned via toml configuration files can be found in config.py. -After the runs, one can always use pofff to postprocess the data and generate the figures, running with the flag -m none (see the -profiling.py for an example of splitting the generation of the files, running of everest, and postprocessing). -Please raise an issue for missing keywords in the toml configuration files that you would like to be added.

    +History matching with ERT and Everest
    -
-
-

Visualization

-

To postprocess the data, plopm can be used.

-
-

Tip

-

You can install plopm by executing in the terminal:

-
pip install git+https://github.com/cssr-tools/plopm.git
-
-
-
-

For example, if you run the appendixb.toml configuration file and change the ‘inj’ variable in line 17 to:

-
inj=[[8100, 300, 3E-7, 0],
-[10200, 300, 3E-7, 3E-7],
-[3300, 300, 0, 0],
-[64800, 3600, 0, 0],
-[345600, 21600, 0, 0]]
-
-
-

Then the following GIF is generated by:

-
-
pofff -i appendixb.toml -o pofff+plopm -m single -c '5e-2' -f none
-plopm -v xco2l -i 'pofff+plopm/POFFF+PLOPM' -d 16,5 -mask satnum -m gif -dpi 1000 -f 20 -loop 1 -cformat .1e -cbsfax 0.30,0.01,0.4,0.02 -interval 437.5 -maskthr 1e-5 -tunits h -cnum 5 -clabel 'CO$_2$ mass fraction in liquid [-]' -t 'FluidFlower simulation (GitHub/cssr-tools/pofff),'
-
-
-
-
-_images/pofff%2Bplopm_xco2l.gif -
-

The total simulation time for this GIF (five days of experiment) using eight CPUs for OPM Flow in a Mac is LESS THAN TWO MINUTES!!!

-
-
-

See the plopm online docmunetation for additional information of supported flag parameters to generate customized PNGs and GIFs.

-
-

Tip

-

All previous commands can be executed as (docs_visualization.sh):

-
. ./tests/scripts/docs_visualization.sh
-
+
+
+ +
+
+Visualization with plopm
+

Generate customized FluidFlower figures and animations from OPM results.

+Visualization with plopm
+
+
+
+
-
-
- - - - - + - + + + + + + +
+ +
+ + + + + + + + + +
+ - +
+ \ No newline at end of file diff --git a/docs/examples/hello-world.html b/docs/examples/hello-world.html new file mode 100644 index 0000000..3e5c6ac --- /dev/null +++ b/docs/examples/hello-world.html @@ -0,0 +1,717 @@ + + + + + + + + + + + Hello world — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

Hello world#

+
+

Overview#

+

The repository examples folder contains configuration +files with low grid resolution and short simulation times for initial testing. +This example runs one of those small cases and generates benchmark products at +24, 48, and 72 hours.

+
+
+

Command#

+
pofff -i single.toml -t 24,48,72
+
+
+
+
+

Result#

+

The following figure is map_24h.png. Compare your generated result with this +figure to check that the example ran correctly.

+
+FluidFlower simulation and experimental contours after 24 hours +
+

Spatial benchmark map after 24 hours.#

+
+
+
+
+

Reproduce the example#

+

Run the maintained documentation script from the repository root:

+
. ./tests/scripts/docs_hello_world.sh
+
+
+
+ +
+

Back to examples gallery

+
+
+ + +
+ + + + + + + +
+ + + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/examples/history-matching.html b/docs/examples/history-matching.html new file mode 100644 index 0000000..1b17d85 --- /dev/null +++ b/docs/examples/history-matching.html @@ -0,0 +1,779 @@ + + + + + + + + + + + History matching with ERT and Everest — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

History matching with ERT and Everest#

+
+

Overview#

+

Set --mode to ert or everest to run history matching. These modes +require additional TOML variables, including the parameters to history match, +the number of parallel runs, resource limits, and a random seed when +reproducibility is required.

+

Refer to the external references for the underlying configuration keywords:

+ +
+
+

Everest example#

+

The following command runs the final history-matching iteration used in the +pofff paper with everest_iter_3.toml:

+
pofff -i everest_iter_3.toml -o everest_iter_3 -m everest -t 24,48,72,96,120 -f all
+
+
+ +
+

Warning

+

The publication configuration uses cores = 50 and +max_function_evaluations = 200000. When running it locally, decrease the +number of parallel runs and maximum function evaluations to match the CPU, +memory, and time available on your system.

+
+
+
+

History-matching parameters#

+

The possible history-matching variables include the supported facies properties +for facies 1 through 6, for example permx2 and pen5. The current +configuration model supports the numbered properties poroN, permN, +permxN, permyN, permzN, dispercN, swiN, sniN, +penN, nkrwN, nkrnN, npeN, threN, and npntN where +applicable. Isotropic permeability can be selected with a variable such as +perm3. The thicknessmult variable scales the thickness map.

+
+

Note

+

History-matching values use different layouts for the two workflows. ERT +parameters contain a distribution name and two parameters. Everest +parameters contain [initial, minimum, maximum, scale]. See +Configuration reference for the maintained TOML reference.

+
+
+
+

Additional examples#

+

Additional ERT and Everest examples are available in the pofff repository:

+ +
+
+

Generated configurations and direct execution#

+

pofff exposes the most commonly used ERT and Everest options through TOML. After +pofff generates everest.yml or ert.txt, you may add supported keywords +that are not yet exposed by pofff and then run the corresponding Everest or ERT +command-line executable directly.

+

Please raise a pofff issue when a +useful TOML keyword is missing, so it can be considered for the maintained +configuration model.

+
+
+

Separate generation, execution, and postprocessing#

+

After ERT or Everest has run, pofff can postprocess the existing results and +generate figures without starting another history-matching study:

+
pofff -o HISTORY_MATCHING_OUTPUT -m none -t 24,48,72,96,120 -f all
+
+
+

The publication profiling.py workflow demonstrates how to separate file +generation, Everest execution, and postprocessing:

+ +
+
+

Outputs#

+

ERT and Everest produce different runtime directories, but both postprocessing +workflows create a figures/best_simulation folder containing the closest or +optimal simulation and its benchmark tables and figures. See +Output folder for the output layouts.

+

Back to examples gallery

+
+
+ + +
+ + + + + + + +
+ + + + + + + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/examples/visualization.html b/docs/examples/visualization.html new file mode 100644 index 0000000..3d5dbe2 --- /dev/null +++ b/docs/examples/visualization.html @@ -0,0 +1,742 @@ + + + + + + + + + + + Visualization with plopm — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

Visualization with plopm#

+
+

Overview#

+

plopm can postprocess the OPM Flow +results generated by pofff and create customized PNG figures and GIF animations.

+
+
+

Installation#

+
pip install git+https://github.com/cssr-tools/plopm.git
+
+
+
+
+

Configuration#

+

Run publication/appendixb.toml after replacing its inj schedule with:

+
inj = [
+    [8100, 300, 3E-7, 0],
+    [10200, 300, 3E-7, 3E-7],
+    [3300, 300, 0, 0],
+    [64800, 3600, 0, 0],
+    [345600, 21600, 0, 0],
+]
+
+
+
+
+

Commands#

+

Generate the OPM Flow result without the pofff benchmark figures:

+
pofff -i appendixb.toml -o pofff+plopm -m single -c 5e-2 -f none
+
+
+

Generate the dissolved-CO2 animation:

+
plopm -v xco2l -i 'pofff+plopm/POFFF+PLOPM' -fs 16,5 -mv satnum -m gif -dpi 1000 -fz 20 -gl 1 -cbf .1e -cbp 0.30,0.01,0.4,0.02 -gi 437.5 -mt 1e-5 -tu h -cbn 5 -cbl 'CO$_2$ mass fraction in liquid [-]' -t 'FluidFlower simulation (GitHub/cssr-tools/pofff),'
+
+
+
+
+

Result#

+
+Dissolved CO2 animation generated from a pofff simulation with plopm +
+

The documented five-day simulation completed in less than two minutes using +eight OPM Flow CPUs on a Mac.#

+
+
+

See the plopm documentation for the +complete plotting and animation reference.

+
+
+

Reproduce the example#

+

Run the maintained documentation script from the repository root:

+
. ./tests/scripts/docs_visualization.sh
+
+
+
+ +
+

Back to examples gallery

+
+
+ + +
+ + + + + + + +
+ + + + + + + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/genindex.html b/docs/genindex.html index 397b388..23791b9 100644 --- a/docs/genindex.html +++ b/docs/genindex.html @@ -1,87 +1,497 @@ - - - - - - Index — pofff documentation - - + + + + + + + Index — pofff documentation + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + -
- -
- +
+ - +
+ \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index e20c1e0..5efaf70 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,192 +1,740 @@ - - - - - - - Welcome to pofff’s documentation! — pofff documentation - - - - - - - - - - - + + + + + + + + + pofff — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + - - + +
+ + + - - -
- -
-
-
-
    -
  • - -
  • - View page source -
  • + + + + + + +
+ +
+ + + + + + + +
- + + + + + + + +
+ + +
+
+ +
+ + + + + +
+ +
+

pofff#

+

An open-source image-based history-matching framework for the FluidFlower +benchmark using OPM Flow, ERT, and Everest.

+

pofff generates FluidFlower simulation models, runs OPM Flow, writes +benchmark CSV files, compares simulations with experimental and published +results, and supports history matching with ERT and Everest.

+
+
+
+
+
+
+ Get started
+

Learn the main simulation, benchmark, and history-matching workflows.

+
+Introduction
+
+
+
+
+
+ Install
+

Install pofff, OPM Flow, ERT, Everest, and visualization tools.

+
+Installation
+
+
+
+
+
+ Configure a case
+

Define the grid, thickness, facies, sources, injection schedule, and +history-matching settings.

+
+Configuration reference
+
+
+
+
+
+ Follow the tutorial
+

Run a simulation and add its results to the FluidFlower comparisons.

+
+Tutorial
+
+
+
+
+

Quick installation#

+

Install the current development version:

+
pip install git+https://github.com/cssr-tools/pofff.git
+
+
+

See Installation for virtual environments, OPM Flow, ERT, Everest, +ResInsight, plopm, optional LaTeX support, and installation from source.

+
+
+

Quick start#

+

Run a FluidFlower simulation and generate benchmark results at 24, 48, and +72 hours:

+
pofff -i examples/single.toml -o output -t 24,48,72
+
+
+

Generate only the OPM Flow and workflow input files:

+
pofff -i examples/single.toml -o output -m files -f none
+
+
+

Display the available command-line options:

+
pofff --help
+
+
+

See Tutorial for a guided simulation and comparison workflow, +Examples for focused applications, and Command-line reference for exact +syntax, accepted values, defaults, and option compatibility.

+
+
+

What can pofff do?#

+
+
+
+
+
+
+Generate FluidFlower models
+

Create Cartesian, tensor, and corner-point grids with geological facies, +thickness maps, sources, observation sensors, and benchmark regions.

+
+
+
+
+
+
+
+Run configurable workflows
+

Generate input files, run OPM Flow, process benchmark data, and create +figures independently or as a connected workflow.

+
+
+
+
+
+
+
+Write and compare benchmark results
+

Export sparse time-series and dense spatial CSV data, then compare local +or external simulations with experimental and published results.

+
+
+
+
+
+
+
+Perform history matching
+

Run ERT ensemble studies or Everest differential-evolution optimization, +then extract and postprocess the best simulation.

+
+
+
+
+
+
-
-

Indices and tables

-
-
-
- - - -
+ + + + + + + + + + +
+ +
+ + + - + + + + + +
+ - +
+ \ No newline at end of file diff --git a/docs/installation.html b/docs/installation.html index f90d953..892d840 100644 --- a/docs/installation.html +++ b/docs/installation.html @@ -1,98 +1,539 @@ - - - - - - - Installation — pofff documentation - - - - - - - - - - - + + + + + + + + + Installation — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + +
+ + + + + + + + + +
+ + + + + + +
- - -
- -
-
-
-
-
- - - - - + + + + + + + +
+ +
+ + + - + + + + + +
+ - +
+ \ No newline at end of file diff --git a/docs/introduction.html b/docs/introduction.html index 0f0a9e2..8913305 100644 --- a/docs/introduction.html +++ b/docs/introduction.html @@ -1,166 +1,750 @@ - - - - - - - Introduction — pofff documentation - - - - - - - - - - - + + + + + + + + + Introduction — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + +
+ + + + - - -
- -
-
-
-
+ + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
-
-
- + + + + + +
+
-

Introduction

-
_images/pofff.png -

This documentation describes the pofff tool hosted in https://github.com/cssr-tools/pofff.

-
-

Concept

-

User-friendly open-source image-based history-matching framework for the FluidFlower benchmark study using OPM Flow.

+

Introduction#

+
FluidFlower simulation and history matching with pofff +

pofff is an open-source, image-based history-matching framework for the +FluidFlower benchmark. It uses a +configuration file to generate OPM Flow input, +run simulations, write benchmark data, compare simulations with experimental +observations, and create publication-quality figures.

+
+

Core workflows#

+
    +
  • Generate Cartesian, tensor, or corner-point FluidFlower grids.

  • +
  • Assign geological facies, thickness maps, source locations, reporting +regions, and observation sensors.

  • +
  • Set rock, fluid, saturation-function, diffusion, and injection-schedule +properties.

  • +
  • Generate OPM Flow decks without running a simulation.

  • +
  • Run a complete FluidFlower simulation with OPM Flow.

  • +
  • Write sparse time-series and dense spatial data in benchmark format.

  • +
  • Compare simulation results with experimental, forecast, CSIRO, MIT_M1, CSSR, +and other published data.

  • +
  • Run ensemble history matching with ERT.

  • +
  • Run differential-evolution optimization with Everest.

  • +
  • Create benchmark maps, time-series comparisons, Wasserstein-distance +metrics, error tables, and publication figures.

  • +
  • Postprocess benchmark-format results generated by other simulators.

  • +
-
-

Overview

-

The current implementation supports the following executable with the argument options:

-
pofff -i name_of_input_file.toml
+
+

Basic usage#

+

Run a FluidFlower simulation and generate benchmark results:

+
pofff -i examples/single.toml -o output -t 24,48,72
 
-

where

-
-
-i
-

The base name of the toml configuration file, (‘input.toml’ by default).

-
-
-o
-

The base name of the output folder (‘output’ by default).

-
-
-f
-

Use ‘all’ to generate all benchmark figures, ‘basic’ to not generate the Wasserstain distance plot (it is slow), and ‘none’ for no figures (‘basic’ by default).

-
-
-m
-

Run a ‘single’ simulation, generate only the input ‘files’, generate only the csv ‘data’, ‘everest’, ‘ert’, ‘fair’, or ‘none’ (‘none’ is useful to generate only figures in combination to ‘-f’) (‘single’ by default).

-
-
-t
-

Times in hours separated by commas to evaluate the metrics (‘0.25’ by default).

-
-
-e
-

Experimental data to history match, valid options are C1 to C5 (‘C2’ by default).

-
-
-s
-

The minimum saturation above which gaseous CO2 is considered for the segmentation (‘1e-2’ by default).

-
-
-c
-

The minimum concentration above which CO2 is considered to be dissolved for the segmentation (‘1e-1’ by default).

-
-
-u
-

Use the precomputed Wasserstein distance matrix values for minimum concentration of 1e-1 and 5e-2 (minimum saturation of 1e-2) to speed up the computations (‘1’ by default; set to ‘0’ to compute all).

-
-
+

Use Command-line reference for exact option syntax and Examples for +additional reproducible workflows.

+

To add a simulation to the published FluidFlower comparisons, follow the +Tutorial.

+
+
+

About the project#

+Project context for pofff + +

pofff is funded by the Center for Sustainable Subsurface Resources under project number 331841.

+

The framework was developed to support reproducible simulation, benchmark +processing, visualization, and history matching for the FluidFlower benchmark. +It integrates OPM Flow with ERT, Everest, FluidFlower experimental data, and +open-source visualization tools.

+

See the pofff paper for the +scientific description, benchmark results, history-matching studies, and +computational analysis.

+
+
+

Citation#

+

Landa-Marbán, D., Sandve, T. H., Both, J. W., Nordbotten, J. M., and +Gasda, S. E. (2026). Performance of an open-source image-based history +matching framework for CO2 storage. Transport in Porous Media, 153, 21. +https://doi.org/10.1007/s11242-025-02275-0

+
+
+

Where to continue#

+
    +
  • Complete the Installation and verify the Python package, OPM Flow, +ERT, Everest, and optional visualization tools.

  • +
  • Use the Configuration reference reference to understand simulation, +benchmark, ERT, and Everest TOML variables.

  • +
  • Follow the Tutorial to run a FluidFlower simulation and add its +results to the published benchmark comparisons.

  • +
  • Browse Examples for a small first run, external-simulator results, +history matching, and visualization with plopm.

  • +
  • Use Reproducing the publication to reproduce the figures, tables, sensitivity +studies, profiling results, and accuracy-time analysis from the pofff paper.

  • +
  • Review Output folder to understand the files generated by single-run, +ERT, and Everest workflows.

  • +
  • Use Command-line reference for exact CLI syntax, defaults, accepted values, and +option compatibility.

  • +
  • Browse pofff Python API for the Python modules, data classes, functions, and +internal workflow utilities.

  • +
  • See Contributing to report issues, request features, submit improved +configurations, or contribute to pofff.

  • +
  • Explore Related projects for complementary open-source simulation, +visualization, and modeling tools.

  • +
-
-
-
+ + + + +
+ + +
+ + + +
+ + + +
+ +
+ + + + + + + + + +
+ - +
+ \ No newline at end of file diff --git a/docs/modules.html b/docs/modules.html deleted file mode 100644 index f14e8eb..0000000 --- a/docs/modules.html +++ /dev/null @@ -1,150 +0,0 @@ - - - - - - - - - pofff — pofff documentation - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
- -
- -
- -
-

© Copyright 2025-2026, NORCE Research AS.

-
- - Built with Sphinx using a - theme - provided by Read the Docs. - - -
-
-
-
-
- - - - \ No newline at end of file diff --git a/docs/objects.inv b/docs/objects.inv index 88d00c3..dbacb8e 100644 Binary files a/docs/objects.inv and b/docs/objects.inv differ diff --git a/docs/options/general.html b/docs/options/general.html new file mode 100644 index 0000000..94a659f --- /dev/null +++ b/docs/options/general.html @@ -0,0 +1,812 @@ + + + + + + + + + + + General options — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

General options#

+

The following options control the input configuration, output directory, +execution mode, benchmark evaluation, and segmentation thresholds.

+
+

Input configuration#

+
+
+-i <file>, --input <file>#
+

TOML configuration file. The default is input.toml.

+

This option is not used by the fair and none modes.

+
+ +
+
+

Output directory#

+
+
+-o <directory>, --output <directory>#
+

Output directory. The default is output.

+
+ +
+
+

Figure generation#

+
+
+-f <mode>, --figures <mode>#
+

Select which benchmark figures to generate.

+

Accepted values are:

+
+
all

Generate all benchmark figures, including the Wasserstein-distance +comparisons.

+
+
basic

Generate the basic comparison figures without the slower +Wasserstein-distance plots. This is the default.

+
+
none

Do not generate figures.

+
+
+
+ +
+
+

Execution mode#

+
+
+-m <mode>, --mode <mode>#
+

Select the pofff workflow to execute.

+

Accepted values are:

+
+
single

Generate the input files, run OPM Flow, process the benchmark data, and +optionally generate figures. This is the default.

+
+
files

Generate the input files without running OPM Flow.

+
+
data

Generate benchmark data from existing simulation results.

+
+
everest

Run an Everest optimization workflow.

+
+
ert

Run an ERT ensemble history-matching workflow.

+
+
fair

Generate the maintained FAIR benchmark comparisons using the +workflow-specific settings.

+
+
none

Skip model generation and simulation. Use this mode to postprocess +existing benchmark-format results.

+
+
+
+ +
+
+

Evaluation times#

+
+
+-t <hours>, --times <hours>#
+

Comma-separated positive finite evaluation times in hours.

+

The default is 0.25. For example:

+
pofff -t 24,48,72,96,120
+
+
+
+ +
+
+

Experimental realization#

+
+
+-e <experiment>, --experiment <experiment>#
+

Experimental FluidFlower realization used for comparison or history +matching.

+

Accepted values are C1, C2, C3, C4, and C5. The default +is C2.

+
+ +
+
+

Minimum gas saturation#

+
+
+-s <threshold>, --minimumsaturation <threshold>#
+

Minimum gas saturation used when segmenting gaseous CO2.

+

The value must be finite and lie between 0 and 1, inclusive. The default is +1e-2.

+
+ +
+
+

Minimum dissolved concentration#

+
+
+-c <threshold>, --minimumconcentration <threshold>#
+

Minimum concentration used when segmenting dissolved CO2.

+

Accepted values are 5e-2 and 1e-1. The default is 1e-1.

+
+ +
+
+

Precomputed Wasserstein distances#

+
+
+-u <choice>, --use <choice>#
+

Control whether precomputed Wasserstein-distance values may be used when +available.

+

Accepted values are:

+
+
1

Use precomputed values when available. This is the default.

+
+
0

Recalculate the Wasserstein distances.

+
+
+
+ +
+
+ + +
+ + + + + + + +
+ + + + + + + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/options/modes.html b/docs/options/modes.html new file mode 100644 index 0000000..7b530f1 --- /dev/null +++ b/docs/options/modes.html @@ -0,0 +1,660 @@ + + + + + + + + + + + Execution modes — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

Execution modes#

+

single generates files, runs OPM Flow, creates benchmark data, and optionally creates figures. files creates inputs only and cannot use -f all. data processes an existing simulation. ert and everest run their corresponding history-matching workflows. fair uses fixed benchmark comparison settings and rejects conflicting input, figure, time, and experiment options. none skips simulation work and is useful for plotting existing results.

+
+ + +
+ + + + + + + +
+ + + + +
+ + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/output_folder.html b/docs/output_folder.html index 074dfd7..b85db55 100644 --- a/docs/output_folder.html +++ b/docs/output_folder.html @@ -1,95 +1,541 @@ - - - - - - - Output folder — pofff documentation - - - - - - - - - - - + + + + + + + + + Output folder — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + - -
-
+ + + + + + + + + + + + + + + + - - -
- -
-
-
-
    -
  • - -
  • - View page source -
  • + +
    + + +
    +
    + +
    +
    + +
    + +
    + +
    -
    -
    - + +
    + + +
    +
    + + + + + +
    +
    -

    Output folder

    +

    Output folder#

    -

    Single run

    +

    Single run#

    The following screenshot shows the generated files in the output folder after executing pofff in the Adding your results subsection in the examples.

    @@ -97,7 +543,7 @@

    Single run -

    Everest

    +

    Everest#

    The following screenshot shows the generated files in the output folder after executing pofff in the everest.toml:

    pofff -i everest.toml -o everest -m everest
    @@ -110,7 +556,7 @@ 

    Everest and pngs figures as in the single run folder.

    -

    ERT

    +

    ERT#

    The following screenshot shows the generated files in the output folder after executing pofff in the ert.toml:

    pofff -i ert.toml -o ert -m ert
    @@ -121,38 +567,135 @@ 

    ERT

    The best_simulation folder contains the closest simulation to the observations, and generates the tables and pngs figures as in the single run folder.

    +

    The generated OPM input, ERT and Everest configurations, job scripts, CSV data, and best-simulation files can be inspected and, where appropriate, edited and rerun. Use the commands in Examples to reproduce additional output layouts.

    -
    -
    -
    + + + + +
+ + + + + + + + + + +
+ +
+ + + + + + + + + +
+ - +
+ \ No newline at end of file diff --git a/docs/pofff.config.config.html b/docs/pofff.config.config.html deleted file mode 100644 index b02f9f1..0000000 --- a/docs/pofff.config.config.html +++ /dev/null @@ -1,586 +0,0 @@ - - - - - - - - - pofff.config.config module — pofff documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

pofff.config.config module

-

Central configuration models for pofff.

-

Defines structured configuration objects used across the codebase: -- CliConfig: raw CLI inputs -- PofffConfig: unified runtime and TOML-based configuration

-
-
-class pofff.config.config.CliConfig(fol: Path, deck: Path, jobs: Path, experiment: str, times: str, msat: str, mcon: str, mode: str, figures: str, location: str, use: str)
-

Bases: object

-

Container for command-line arguments before normalization.

-
-
-deck: Path
-
- -
-
-experiment: str
-
- -
-
-figures: str
-
- -
-
-fol: Path
-
- -
-
-jobs: Path
-
- -
-
-location: str
-
- -
-
-mcon: str
-
- -
-
-mode: str
-
- -
-
-msat: str
-
- -
-
-times: str
-
- -
-
-use: str
-
- -
- -
-
-class pofff.config.config.PofffConfig(path: Path, fol: Path = PosixPath('output'), deck: Path = PosixPath('output'), jobs: Path = PosixPath('output'), experiment: str = 'run2', times: str = '0.25', msat: str = '1e-2', mcon: str = '1e-1', mode: str = 'single', figures: str = 'basic', location: str = '', use: str = '1', flow: str = 'flow', grid: str = 'corner-point', thickness: str = 'final', mult_thickness: float = 1.0, x: list[int] = <factory>, z: list[int] = <factory>, temperature: list[float] = <factory>, pressure: float = 0.0, diffusion: ndarray = <factory>, sources: list[list[float]] = <factory>, inj: list[list[Any]] = <factory>, krw: str = '(max(0, (sw - swi) / (1 - swi))) ** nkrw', krn: str = '(max(0, (1 - sw - sni) / (1 - sni))) ** nkrn', cap: str = 'pen * ((sw-swi) / (1-swi)) ** (-(1.0 / npen))', cores: int | None = None, maxtime: float | None = None, delete: bool | None = None, ertargs: str | None = None, ensembles: int | None = None, enkf_alpha: float | None = None, errors: ndarray | None = None, random_seed: int | None = None, min_realizations_success: int | None = None, max_function_evaluations: int | None = None, max_batch_num: int | None = None, args: tuple[~typing.Any, ...] | None=None, strategy: str | None = None, maxiter: int | None = None, popsize: int | None = None, tol: float | None = None, mutation: float | tuple[float, float] | None=None, recombination: float | None = None, rng: Any | None = None, callback: Callable | None = None, disp: bool | None = None, polish: bool | None = None, init: str | None = None, atol: float | None = None, updating: str | None = None, workers: int | None = None, constraints: Iterable[Any] | None = None, x0: Sequence[float] | None = None, integrality: Sequence[bool] | None = None, vectorized: bool | None = None, facies: list[int] = <factory>, fluxnum: list[str] = <factory>, fipnum: list[str] = <factory>, porv: list[str] = <factory>, multpv: list[str] = <factory>, dx: list | None = <factory>, dz: list | None = <factory>, dims: list[float] = <factory>, sensors: list[list[float]] = <factory>, sensor_ik: list[list[int]] = <factory>, source_ik: list[list[int]] = <factory>, boxa: list[list[float]] = <factory>, boxb: list[list[float]] = <factory>, boxc: list[list[float]] = <factory>, hm: dict[str, ~typing.Any]=<factory>, monotonic: bool = False, hascellmaps: bool = False, everert: bool = False, tuning: bool = False, para: dict[str, ~typing.Any]=<factory>, nxz: list[int] = <factory>, data: str | None = None)
-

Bases: object

-

Central configuration object for pofff.

-

Combines CLI options, TOML inputs, and derived runtime settings.

-
-
-args: tuple[Any, ...] | None
-
- -
-
-atol: float | None
-
- -
-
-boxa: list[list[float]]
-
- -
-
-boxb: list[list[float]]
-
- -
-
-boxc: list[list[float]]
-
- -
-
-callback: Callable | None
-
- -
-
-cap: str
-
- -
-
-constraints: Iterable[Any] | None
-
- -
-
-cores: int | None
-
- -
-
-data: str | None
-
- -
-
-deck: Path
-
- -
-
-delete: bool | None
-
- -
-
-diffusion: ndarray
-
- -
-
-dims: list[float]
-
- -
-
-disp: bool | None
-
- -
-
-dx: list | None
-
- -
-
-dz: list | None
-
- -
-
-enkf_alpha: float | None
-
- -
-
-ensembles: int | None
-
- -
-
-errors: ndarray | None
-
- -
-
-ertargs: str | None
-
- -
-
-everert: bool
-
- -
-
-experiment: str
-
- -
-
-facies: list[int]
-
- -
-
-figures: str
-
- -
-
-fipnum: list[str]
-
- -
-
-flow: str
-
- -
-
-fluxnum: list[str]
-
- -
-
-fol: Path
-
- -
-
-grid: str
-
- -
-
-hascellmaps: bool
-
- -
-
-hm: dict[str, Any]
-
- -
-
-init: str | None
-
- -
-
-inj: list[list[Any]]
-
- -
-
-integrality: Sequence[bool] | None
-
- -
-
-jobs: Path
-
- -
-
-krn: str
-
- -
-
-krw: str
-
- -
-
-location: str
-
- -
-
-max_batch_num: int | None
-
- -
-
-max_function_evaluations: int | None
-
- -
-
-maxiter: int | None
-
- -
-
-maxtime: float | None
-
- -
-
-mcon: str
-
- -
-
-min_realizations_success: int | None
-
- -
-
-mode: str
-
- -
-
-monotonic: bool
-
- -
-
-msat: str
-
- -
-
-mult_thickness: float
-
- -
-
-multpv: list[str]
-
- -
-
-mutation: float | tuple[float, float] | None
-
- -
-
-nxz: list[int]
-
- -
-
-para: dict[str, Any]
-
- -
-
-path: Path
-
- -
-
-polish: bool | None
-
- -
-
-popsize: int | None
-
- -
-
-porv: list[str]
-
- -
-
-pressure: float
-
- -
-
-random_seed: int | None
-
- -
-
-recombination: float | None
-
- -
-
-rng: Any | None
-
- -
-
-sensor_ik: list[list[int]]
-
- -
-
-sensors: list[list[float]]
-
- -
-
-source_ik: list[list[int]]
-
- -
-
-sources: list[list[float]]
-
- -
-
-strategy: str | None
-
- -
-
-temperature: list[float]
-
- -
-
-thickness: str
-
- -
-
-times: str
-
- -
-
-tol: float | None
-
- -
-
-tuning: bool
-
- -
-
-updating: str | None
-
- -
-
-use: str
-
- -
-
-vectorized: bool | None
-
- -
-
-workers: int | None
-
- -
-
-x: list[int]
-
- -
-
-x0: Sequence[float] | None
-
- -
-
-z: list[int]
-
- -
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/pofff.config.html b/docs/pofff.config.html deleted file mode 100644 index fb87486..0000000 --- a/docs/pofff.config.html +++ /dev/null @@ -1,229 +0,0 @@ - - - - - - - - - pofff.config package — pofff documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

pofff.config package

-
-

Submodules

-
- -
-
-
-

Module contents

-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/pofff.core.html b/docs/pofff.core.html deleted file mode 100644 index 891b8da..0000000 --- a/docs/pofff.core.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - - - - pofff.core package — pofff documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/pofff.core.pofff.html b/docs/pofff.core.pofff.html deleted file mode 100644 index bcb63a7..0000000 --- a/docs/pofff.core.pofff.html +++ /dev/null @@ -1,182 +0,0 @@ - - - - - - - - - pofff.core.pofff module — pofff documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

pofff.core.pofff module

-

Main entry script for pofff.

-
-
-pofff.core.pofff.check_cmdargs(cmdargs: Namespace) → None
-

Validate command-line arguments and incompatible operations.

-

The checks cover input and output paths, evaluation times, saturation and -concentration thresholds, options ignored by specialized workflows, and -combinations that do not result in any operation.

-
-
Parameters:
-

cmdargs – Parsed arguments returned by parse_args().

-
-
Raises:
-

SystemExit – If an argument is invalid or an incompatible combination is requested.

-
-
-
- -
-
-pofff.core.pofff.cli_config(args: Namespace, *, pofff_path: Path) → CliConfig
-

Build normalized CLI configuration object.

-
- -
-
-pofff.core.pofff.generate_figures(cfg: PofffConfig) → None
-

Generate benchmark plots and comparison figures.

-
- -
-
-pofff.core.pofff.main(argv: list[str] | None = None) → None
-

Main pofff execution routine.

-
- -
-
-pofff.core.pofff.parse_args(argv: list[str] | None = None) → Namespace
-

Define and parse command-line arguments.

-
- -
-
-pofff.core.pofff.prepare_jobs_folder(cfg: PofffConfig) → None
-

Copy job scripts into output/jobs and make them executable.

-
- -
-
-pofff.core.pofff.prepare_simulation(cfg: PofffConfig) → None
-

Prepare directories and generate all input files.

-
- -
-
-pofff.core.pofff.run_simulation_steps(cfg: PofffConfig) → None
-

Run the selected execution mode.

-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/pofff.html b/docs/pofff.html deleted file mode 100644 index 5f95ee7..0000000 --- a/docs/pofff.html +++ /dev/null @@ -1,306 +0,0 @@ - - - - - - - - - pofff package — pofff documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

pofff package

-
-

Subpackages

-
- -
-
-
-

Module contents

-

Code version

-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/pofff.jobs.data.html b/docs/pofff.jobs.data.html deleted file mode 100644 index bf27913..0000000 --- a/docs/pofff.jobs.data.html +++ /dev/null @@ -1,383 +0,0 @@ - - - - - - - - - pofff.jobs.data module — pofff documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

pofff.jobs.data module

-

Write benchmark time-series and spatial (dense) data from OPM simulations.

-
-
-class pofff.jobs.data.SimulationContext(resolution: NDArray, dense_t: NDArray, maps: Path, where: Path = PosixPath('.'), sparse_t: float = 600.0, dims: NDArray = <factory>, sim: Path | None = None, unrst: ERst | None = None, ini: EclFile | None = None, egrid: EGrid | None = None, smspec: ESmry | None = None, times: list[float] = <factory>, times_summary: NDArray | None = None, time_initial: float = 0.0, porv: NDArray | None = None, actind: NDArray | None = None, gxyz: tuple[int, int, int] | None=None, norst: int | None = None)
-

Bases: object

-

Simulation-wide state, file handles, and grid information.

-
-
-actind: NDArray | None = None
-
- -
-
-dense_t: NDArray
-
- -
-
-dims: NDArray
-
- -
-
-egrid: EGrid | None = None
-
- -
-
-gxyz: tuple[int, int, int] | None = None
-
- -
-
-ini: EclFile | None = None
-
- -
-
-maps: Path
-
- -
-
-norst: int | None = None
-
- -
-
-property nxz: NDArray
-

Alias for resolution to preserve original naming.

-
- -
-
-porv: NDArray | None = None
-
- -
-
-resolution: NDArray
-
- -
-
-sim: Path | None = None
-
- -
-
-smspec: ESmry | None = None
-
- -
-
-sparse_t: float = 600.0
-
- -
-
-time_initial: float = 0.0
-
- -
-
-times: list[float]
-
- -
-
-times_summary: NDArray | None = None
-
- -
-
-unrst: ERst | None = None
-
- -
-
-where: Path = PosixPath('.')
-
- -
- -
-
-class pofff.jobs.data.SparseResults(ctx: SimulationContext, m_c_series: list[float] = <factory>, m_c: NDArray | None = None)
-

Bases: object

-

Container for sparse (time-series) benchmark quantities.

-
-
-ctx: SimulationContext
-
- -
-
-dissa: NDArray
-
- -
-
-dissb: NDArray
-
- -
-
-dx: NDArray
-
- -
-
-dz: NDArray
-
- -
-
-fipnum: NDArray
-
- -
-
-imma: NDArray
-
- -
-
-immb: NDArray
-
- -
-
-m_c: NDArray | None = None
-
- -
-
-m_c_series: list[float]
-
- -
-
-moba: NDArray
-
- -
-
-mobb: NDArray
-
- -
-
-pop1: NDArray
-
- -
-
-pop2: NDArray
-
- -
-
-seala: NDArray
-
- -
-
-sealb: NDArray
-
- -
-
-sealt: NDArray
-
- -
-
-times_data: NDArray
-
- -
- -
-
-pofff.jobs.data.compute_m_c(ctx: SimulationContext, res: SparseResults) → None
-

Compute mass-center migration metric.

-
- -
-
-pofff.jobs.data.create_from_summary(ctx: SimulationContext, res: SparseResults) → None
-

Extract sparse quantities from OPM summary vectors.

-
- -
-
-pofff.jobs.data.dense_data(ctx: SimulationContext) → None
-

Write spatial maps for selected dense output times.

-
- -
-
-pofff.jobs.data.find_simulation_base(path: Path) → Path
-

Locate simulation base name from .UNRST file.

-
- -
-
-pofff.jobs.data.interpolate_sparse(ctx: SimulationContext, res: SparseResults) → None
-

Interpolate sparse quantities onto uniform time grid.

-
- -
-
-pofff.jobs.data.main(argv=None) → None
-

Entry point for benchmark postprocessing.

-
- -
-
-pofff.jobs.data.read_opm(ctx: SimulationContext) → None
-

Load OPM restart, grid, and summary data.

-
- -
-
-pofff.jobs.data.sparse_data(ctx: SimulationContext) → None
-

Compute and write sparse time-series benchmark data.

-
- -
-
-pofff.jobs.data.write_dense_data(ctx: SimulationContext, refxz, sgas, cco2, hours) → None
-

Write single spatial map CSV.

-
- -
-
-pofff.jobs.data.write_sparse_data(res: SparseResults) → None
-

Write sparse time-series values to CSV.

-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/pofff.jobs.delete.html b/docs/pofff.jobs.delete.html deleted file mode 100644 index 2db4a02..0000000 --- a/docs/pofff.jobs.delete.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - pofff.jobs.delete module — pofff documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

pofff.jobs.delete module

-

Script to delete large files

-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/pofff.jobs.html b/docs/pofff.jobs.html deleted file mode 100644 index b6f0bc0..0000000 --- a/docs/pofff.jobs.html +++ /dev/null @@ -1,195 +0,0 @@ - - - - - - - - - pofff.jobs package — pofff documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/pofff.jobs.metric.html b/docs/pofff.jobs.metric.html deleted file mode 100644 index dd2ee01..0000000 --- a/docs/pofff.jobs.metric.html +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - - - - pofff.jobs.metric module — pofff documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

pofff.jobs.metric module

-

Plot spatial results and compute Wasserstein (EMD) distances -between simulated and experimental segmentations.

-
-
-pofff.jobs.metric.before_emd(model_result: NDArray[int64], experimental_data: NDArray[int64], indx: int) → float
-

Create PNG images from segmentation maps and compute EMD. -PNG round-trip is required to preserve original behavior.

-
- -
-
-pofff.jobs.metric.calculate_emd(file_1: str, file_2: str) → float
-

Compute Wasserstein distance between two images. -Reproduces the original numerical behavior exactly.

-
- -
-
-pofff.jobs.metric.generate_segment_map(file_name: str, xlim: tuple[float, float], zlim: tuple[float, float], satmin: float, conmin: float) → NDArray[int64]
-

Convert continuous saturation and concentration fields -into a discrete segmentation map.

-
- -
-
-pofff.jobs.metric.main(argv=None) → None
-

Segment maps at requested times and compute total EMD score.

-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/pofff.utils.html b/docs/pofff.utils.html deleted file mode 100644 index f01f34c..0000000 --- a/docs/pofff.utils.html +++ /dev/null @@ -1,183 +0,0 @@ - - - - - - - - - pofff.utils package — pofff documentation - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/pofff.utils.inputvalues.html b/docs/pofff.utils.inputvalues.html deleted file mode 100644 index 1429976..0000000 --- a/docs/pofff.utils.inputvalues.html +++ /dev/null @@ -1,153 +0,0 @@ - - - - - - - - - pofff.utils.inputvalues module — pofff documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

pofff.utils.inputvalues module

-

Utility functions to prepare and normalize input values for pofff.

-
-
-pofff.utils.inputvalues.build_config(*, pofff_path: Path, cli: CliConfig, toml: dict) → PofffConfig
-

Build and return a fully initialized PofffConfig object.

-
- -
-
-pofff.utils.inputvalues.extract_facies(data: dict) → dict
-

Extract definitions and remove them from the main config dict.

-
- -
-
-pofff.utils.inputvalues.load_toml(filename: str) → dict
-

Load a TOML configuration file and return its content.

-
- -
-
-pofff.utils.inputvalues.postprocess_config(cfg: PofffConfig, toml: dict) → None
-

Enrich configuration with derived values and runtime flags.

-
- -
-
-pofff.utils.inputvalues.process_tuning(cfg: PofffConfig) → None
-

Enable tuning and normalize injection specifications if requested.

-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/pofff.utils.mapproperties.html b/docs/pofff.utils.mapproperties.html deleted file mode 100644 index 61815b0..0000000 --- a/docs/pofff.utils.mapproperties.html +++ /dev/null @@ -1,215 +0,0 @@ - - - - - - - - - pofff.utils.mapproperties module — pofff documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

pofff.utils.mapproperties module

-

Utility functions for grid generation and spatial indexing -in geological FluidFlower-style models.

-
-
-pofff.utils.mapproperties.classify_boxes(cfg: PofffConfig, x: NDArray[float64], z: NDArray[float64], flux: list[str]) → list[str]
-

Assign box-specific FIP numbers based on spatial regions.

-
- -
-
-pofff.utils.mapproperties.corner(cfg: PofffConfig, points: list[list[float]]) → tuple[NDArray[float64], NDArray[float64], NDArray[float64]]
-

Build corner-point grid coordinates and compute cell centroids.

-
- -
-
-pofff.utils.mapproperties.corner_point_handling_fluidflower(cfg: PofffConfig, xyz: NDArray[float64], polygons: list[Polygon], xcoord: NDArray[float64], zcoord: NDArray[float64]) → None
-

Assign facies, boxes, sensors, and wells for corner-point grids.

-
- -
-
-pofff.utils.mapproperties.get_cellmaps(cfg: PofffConfig, simxcent: NDArray[float64], simzcent: NDArray[float64]) → None
-

Construct mapping from structured grid to reference grid.

-
- -
-
-pofff.utils.mapproperties.get_lines(cfg: PofffConfig, points: list[list[float]]) → list[list[list[float]]]
-

Read geological horizon lines.

-
- -
-
-pofff.utils.mapproperties.getpolygons(cfg: PofffConfig) → tuple[list[Polygon], list[list[float]]]
-

Read geological polygons and facies definitions.

-
- -
-
-pofff.utils.mapproperties.grid_and_properties(cfg: PofffConfig) → None
-

Dispatch grid generation and spatial property assignment -based on the selected grid type.

-
- -
-
-pofff.utils.mapproperties.handle_thickness_map(cfg: PofffConfig) → tuple[float, NDArray, NDArray, NDArray]
-

Load and normalize thickness map and corresponding multipliers.

-
- -
-
-pofff.utils.mapproperties.positions(cfg: PofffConfig, polygons: list[Polygon], xcent: NDArray[float64] | None = None, zcent: NDArray[float64] | None = None, xyz: NDArray[float64] | None = None, xcoord: NDArray[float64] | None = None, zcoord: NDArray[float64] | None = None) → None
-

Dispatch spatial indexing for either grid type.

-
- -
-
-pofff.utils.mapproperties.refinement_z(xci: NDArray[float64], zci: NDArray[float64], ncz: int, znr: NDArray[float64]) → tuple[NDArray[float64], NDArray[float64], int, int]
-

Refine grid vertically according to znr refinement factors.

-
- -
-
-pofff.utils.mapproperties.sensors_corner_point(cfg: PofffConfig, x: NDArray[float64], ztop: NDArray[float64]) → tuple[int, int]
-

Determine corner-point sensor indices.

-
- -
-
-pofff.utils.mapproperties.sensors_structured(cfg: PofffConfig, xcent: NDArray[float64], zcent: NDArray[float64]) → tuple[int, int]
-

Determine structured-grid sensor indices.

-
- -
-
-pofff.utils.mapproperties.structured_handling_fluidflower(cfg: PofffConfig, xcent: NDArray[float64], zcent: NDArray[float64], polygons: list[Polygon]) → None
-

Assign facies, boxes, sensors, and wells on structured grids.

-
- -
-
-pofff.utils.mapproperties.wells_corner_point(cfg: PofffConfig, x: NDArray[float64], ztop: NDArray[float64]) → None
-

Determine corner-point well indices.

-
- -
-
-pofff.utils.mapproperties.wells_structured(cfg: PofffConfig, xcent: NDArray[float64], zcent: NDArray[float64]) → None
-

Determine structured-grid well indices.

-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/pofff.utils.runs.html b/docs/pofff.utils.runs.html deleted file mode 100644 index 37a857e..0000000 --- a/docs/pofff.utils.runs.html +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - - - - pofff.utils.runs module — pofff documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

pofff.utils.runs module

-

Utility functions for running simulations, processing data, -and generating benchmark plots.

-
-
-pofff.utils.runs._run(cmd: Sequence[str]) → None
-

Execute a command and abort if it fails.

-
- -
-
-pofff.utils.runs.benchmark(cfg: PofffConfig) → None
-

Generate benchmark figures and comparisons.

-
- -
-
-pofff.utils.runs.data(cfg: PofffConfig) → None
-

Generate benchmark time-series and spatial data.

-
- -
-
-pofff.utils.runs.ert(cfg: PofffConfig) → None
-

Run ERT with configured arguments.

-
- -
-
-pofff.utils.runs.everest() → None
-

Run Everest optimization and skip interactive prompts.

-
- -
-
-pofff.utils.runs.flow(cfg: PofffConfig) → None
-

Run the OPM Flow simulator with configured options.

-
- -
-
-pofff.utils.runs.postprocess(cfg: PofffConfig) → None
-

Postprocess ERT and Everest simulation results.

-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/pofff.utils.writefile.html b/docs/pofff.utils.writefile.html deleted file mode 100644 index be6b4c7..0000000 --- a/docs/pofff.utils.writefile.html +++ /dev/null @@ -1,203 +0,0 @@ - - - - - - - - - pofff.utils.writefile module — pofff documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

pofff.utils.writefile module

-

Utility functions to write OPM/pofff files and variables.

-
-
-pofff.utils.writefile.compact_format(values: list) → list
-

Encode values using Eclipse-style n*value compaction. -Uses NumPy for efficient run-length encoding.

-
- -
-
-pofff.utils.writefile.create_corner_point_grid(cfg: PofffConfig, xcoord: NDArray, zcoord: NDArray) → None
-

Write COORD and ZCORN sections for a corner-point grid. -Output is bit-identical to the original implementation.

-
- -
-
-pofff.utils.writefile.render_bcprop(cfg: PofffConfig) → None
-

Generate bcprop.py

-
- -
-
-pofff.utils.writefile.render_copyd(cfg: PofffConfig) → None
-

Generate copyd.py

-
- -
-
-pofff.utils.writefile.render_equalreg(cfg: PofffConfig) → None
-

Generate equalreg.py

-
- -
-
-pofff.utils.writefile.render_ert(cfg: PofffConfig) → None
-

Generate ert.txt

-
- -
-
-pofff.utils.writefile.render_everest(cfg: PofffConfig) → None
-

Generate everest.yml

-
- -
-
-pofff.utils.writefile.render_flow(cfg: PofffConfig) → None
-

Generate flow.py

-
- -
-
-pofff.utils.writefile.render_monotonic(cfg: PofffConfig) → None
-

Generate monotonic.py

-
- -
-
-pofff.utils.writefile.render_satufunc(cfg: PofffConfig) → None
-

Generate satufunc.py

-
- -
-
-pofff.utils.writefile.render_scale(cfg: PofffConfig) → None
-

Generate scale.py

-
- -
-
-pofff.utils.writefile.write_files(cfg: PofffConfig) → None
-

Generate all OPM, ERT, and Everest files using mako templates.

-
- -
-
-pofff.utils.writefile.write_keywords(cfg: PofffConfig) → None
-

Write OPM keyword include files (FIPNUM, FLUXNUM, DX, DZ, etc.).

-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/pofff.visualization.benchmark.html b/docs/pofff.visualization.benchmark.html deleted file mode 100644 index b0a4b97..0000000 --- a/docs/pofff.visualization.benchmark.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - - - - pofff.visualization.benchmark module — pofff documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

pofff.visualization.benchmark module

-

Generate benchmark-format figures and comparisons.

-
-
-pofff.visualization.benchmark.benchmark(cfg)
-

Run figure generation and benchmark comparisons.

-
- -
-
-pofff.visualization.benchmark.load_parser(argv)
-

Define and parse command-line arguments.

-
- -
-
-pofff.visualization.benchmark.run_benchmark(argv=None)
-

Main entry point for generating benchmark figures.

-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/pofff.visualization.error_table.html b/docs/pofff.visualization.error_table.html deleted file mode 100644 index 8d7d561..0000000 --- a/docs/pofff.visualization.error_table.html +++ /dev/null @@ -1,143 +0,0 @@ - - - - - - - - - pofff.visualization.error_table module — pofff documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

pofff.visualization.error_table module

-

Quantify errors between simulation results and experimental data -and summarize them in a comparison table.

-
-
-pofff.visualization.error_table.load_sparse_csv(filename)
-

Load a sparse CSV file, automatically handling optional headers.

-
- -
-
-pofff.visualization.error_table.parse_arguments(argv)
-

Parse command-line arguments controlling paths and thresholds.

-
- -
-
-pofff.visualization.error_table.run_error_table(argv=None)
-

Compute error metrics and Wasserstein distances -and write a formatted comparison table to disk.

-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/pofff.visualization.everert.html b/docs/pofff.visualization.everert.html deleted file mode 100644 index 7601e03..0000000 --- a/docs/pofff.visualization.everert.html +++ /dev/null @@ -1,410 +0,0 @@ - - - - - - - - - pofff.visualization.everert module — pofff documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

pofff.visualization.everert module

-

Postprocess Everest (ERT) ensemble and optimization studies. -Generates diagnostics, plots, and extracts best simulations.

-
-
-class pofff.visualization.everert.Config(path: Path, times: str, jobs: list[str], external: Path, run: str, maps: Path, min_saturation: float, min_concentration: float)
-

Bases: object

-

Runtime configuration and paths.

-
-
-external: Path
-
- -
-
-jobs: list[str]
-
- -
-
-maps: Path
-
- -
-
-min_concentration: float
-
- -
-
-min_saturation: float
-
- -
-
-path: Path
-
- -
-
-run: str
-
- -
-
-times: str
-
- -
- -
-
-class pofff.visualization.everert.EnsembleState(observations: ndarray, n_e: int, n_i: int, no_obs: int, no_para: int, simulations: list[list[list]] = <factory>, sim_ens: list[list[float]] = <factory>, miss_ens: list[list[float]] = <factory>, par_dis: list[list[float]] = <factory>, idrealisation: list[list[int]] = <factory>, num_ens: list[int] = <factory>, cumulative: list[list[list[float]]] = <factory>, para_file: Path | None = None, para_names: list[str] = <factory>)
-

Bases: object

-

Holds ensemble simulations and diagnostics.

-
-
-cumulative: list[list[list[float]]]
-
- -
-
-idrealisation: list[list[int]]
-
- -
-
-miss_ens: list[list[float]]
-
- -
-
-n_e: int
-
- -
-
-n_i: int
-
- -
-
-no_obs: int
-
- -
-
-no_para: int
-
- -
-
-num_ens: list[int]
-
- -
-
-observations: ndarray
-
- -
-
-par_dis: list[list[float]]
-
- -
-
-para_file: Path | None = None
-
- -
-
-para_names: list[str]
-
- -
-
-sim_ens: list[list[float]]
-
- -
-
-simulations: list[list[list]]
-
- -
- -
-
-class pofff.visualization.everert.OptimizationState(optimization: list[float] = <factory>, optimal_value: float = -inf, ind_batch: int = 0, ind_sim: int = 0, tot_eval: int = 0, s: list[list[int]] = <factory>, x: list[int] = <factory>)
-

Bases: object

-

Tracks optimization progress and outcomes.

-
-
-ind_batch: int = 0
-
- -
-
-ind_sim: int = 0
-
- -
-
-optimal_value: float = -inf
-
- -
-
-optimization: list[float]
-
- -
-
-s: list[list[int]]
-
- -
-
-tot_eval: int = 0
-
- -
-
-x: list[int]
-
- -
- -
-
-pofff.visualization.everert.copy_tree_contents(src: Path, dst: Path)
-

Copy contents of src into dst (cp -r src/. dst/).

-
- -
-
-pofff.visualization.everert.ensure_dir(path: Path)
-

Create directory if missing.

-
- -
-
-pofff.visualization.everert.extract_best_simulation(cfg: Config, state: EnsembleState)
-

Extract best-fitting ensemble realization.

-
- -
-
-pofff.visualization.everert.extract_optimal_solution(cfg: Config, opt: OptimizationState)
-

Extract and postprocess optimal optimization result.

-
- -
-
-pofff.visualization.everert.initialize_ensemble(cfg: Config) → EnsembleState
-

Initialize ensemble from simulation folders.

-
- -
-
-pofff.visualization.everert.parse_args(argv) → Config
-

Parse command-line arguments.

-
- -
-
-pofff.visualization.everert.plot_cumulative_misfit(cfg: Config, state: EnsembleState, tab20)
-

Plot cumulative misfit contributions per observation.

-
- -
-
-pofff.visualization.everert.plot_hm_mismatch(cfg: Config, state: EnsembleState)
-

Plot ensemble-mean misfit per iteration.

-
- -
-
-pofff.visualization.everert.plot_misfit(cfg: Config, state: EnsembleState)
-

Plot ensemble misfit per iteration.

-
- -
-
-pofff.visualization.everert.plot_observable_distribution(cfg: Config, state: EnsembleState)
-

Plot observable sum distributions.

-
- -
-
-pofff.visualization.everert.plot_optimization(cfg: Config, opt: OptimizationState)
-

Plot optimization progress.

-
- -
-
-pofff.visualization.everert.plot_optimization_details(cfg: Config, opt: OptimizationState)
-

Plot optimization success statistics.

-
- -
-
-pofff.visualization.everert.plot_parameter_distributions(cfg: Config, state: EnsembleState)
-

Boxplots of parameter distributions.

-
- -
-
-pofff.visualization.everert.plot_simulation_ensemble(cfg: Config, state: EnsembleState, tab20)
-

Plot initial and final ensemble simulations.

-
- -
-
-pofff.visualization.everert.process_optimization(cfg: Config) → OptimizationState
-

Process Everest optimization results.

-
- -
-
-pofff.visualization.everert.read_realisation(cfg: Config, state: EnsembleState, i: int, j: int)
-

Read one realization and update ensemble statistics.

-
- -
-
-pofff.visualization.everert.run(cmd: list[str]) → None
-

Execute external command and abort on failure.

-
- -
-
-pofff.visualization.everert.run_everert(argv=None)
-

Entry point.

-
- -
-
-pofff.visualization.everert.save_figure(fig, path: Path, dpi=300)
-

Save figure and release memory.

-
- -
-
-pofff.visualization.everert.setup_matplotlib()
-

Apply consistent matplotlib styling.

-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/pofff.visualization.html b/docs/pofff.visualization.html deleted file mode 100644 index 3b019da..0000000 --- a/docs/pofff.visualization.html +++ /dev/null @@ -1,215 +0,0 @@ - - - - - - - - - pofff.visualization package — pofff documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

pofff.visualization package

-
-

Submodules

-
- -
-
-
-

Module contents

-
-
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/pofff.visualization.maps.html b/docs/pofff.visualization.maps.html deleted file mode 100644 index 24fb267..0000000 --- a/docs/pofff.visualization.maps.html +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - - - pofff.visualization.maps module — pofff documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

pofff.visualization.maps module

-

Overlay simulated spatial maps with experimental contour boundaries.

-
-
-pofff.visualization.maps.load_points(path)
-

Load geological points and line connectivity from .geo files.

-
- -
-
-pofff.visualization.maps.pngs(simulations, experiment, x, z, points, lines, t)
-

Plot segmented simulation map with experimental contours and geology.

-
- -
-
-pofff.visualization.maps.run_maps(argv=None)
-

Generate spatial overlay maps for selected times.

-
- -
-
-pofff.visualization.maps.segment(filename, satmin, conmin)
-

Segment saturation and concentration fields into discrete classes.

-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/pofff.visualization.sparse_values.html b/docs/pofff.visualization.sparse_values.html deleted file mode 100644 index 0e3742c..0000000 --- a/docs/pofff.visualization.sparse_values.html +++ /dev/null @@ -1,130 +0,0 @@ - - - - - - - - - pofff.visualization.sparse_values module — pofff documentation - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -
-

pofff.visualization.sparse_values module

-

Generate sparse benchmark values from a time series CSV.

-
-
-pofff.visualization.sparse_values.main(timeseries_file: str = 'time_series.csv', output_file: str = 'sparse_data.csv') → None
-

Read a time series CSV, extract benchmark values, -and write them in sparse CSV format.

-
- -
- - -
-
- -
-
-
-
- - - - \ No newline at end of file diff --git a/docs/publication.html b/docs/publication.html index b55b1fc..4421a9f 100644 --- a/docs/publication.html +++ b/docs/publication.html @@ -1,91 +1,543 @@ - - - - - - - Publication — pofff documentation - - - - - - - - - - - + + + + + + + + + Reproducing the publication — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + + + +
+ + +
+
+ +
+ + + + + + + + + +
+ - +
+ \ No newline at end of file diff --git a/docs/py-modindex.html b/docs/py-modindex.html index 52335ed..8d73594 100644 --- a/docs/py-modindex.html +++ b/docs/py-modindex.html @@ -1,85 +1,494 @@ - - - - - - Python Module Index — pofff documentation - - - - - - - - - - - + + + + + + + + Python Module Index — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + + + +
+ + + + -
- -
- + +
+ + + + + + + + - + + \ No newline at end of file diff --git a/docs/related.html b/docs/related.html index 82dba1d..1fa1fdc 100644 --- a/docs/related.html +++ b/docs/related.html @@ -1,170 +1,716 @@ - - - - - - - Related — pofff documentation - - - - - - - - - - - + + + + + + + + + Related projects — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + + - + - +
+ +
+ + + +
+ +
+ + + + + + + + + +
+ - +
+ \ No newline at end of file diff --git a/docs/search.html b/docs/search.html index ab34df0..89a626b 100644 --- a/docs/search.html +++ b/docs/search.html @@ -1,130 +1,580 @@ - - - - - - Search — pofff documentation - - - - - - - - - - - - - - + + + + + + + Search - pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + - -
- - -
- -
-
-
-
    -
  • - -
  • -
  • + + + - + + + -
    + -
    +
+ + + + +
+
+ + + + + + + + + + + + + + - Built with Sphinx using a - theme - provided by Read the Docs. - - -
-
+ +
+ + +
+
+ +
+ + +
+

Search

+ + + +
- + + + + + +
+ +
+
+
+ +
+ + + + +
+
+ +
+ +
+
+ + + + + + +
+ - +
+ \ No newline at end of file diff --git a/docs/searchindex.js b/docs/searchindex.js index 8dbdb3d..be40c23 100644 --- a/docs/searchindex.js +++ b/docs/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles":{"About pofff":[[0,null]],"Adding your results":[[4,"adding-your-results"]],"Binary packages":[[6,"binary-packages"]],"Brew formula for macOS":[[6,"brew-formula-for-macos"]],"Citing":[[0,"citing"]],"Concept":[[7,"concept"]],"Configuration file":[[2,null]],"Contribute to the software":[[3,"contribute-to-the-software"]],"Contributing":[[3,null]],"ERT":[[9,"ert"]],"Everest":[[9,"everest"]],"Examples":[[4,null]],"Ground Rules":[[3,"ground-rules"]],"Hello world":[[4,"hello-world"]],"History matching":[[4,"history-matching"]],"Indices and tables":[[5,"indices-and-tables"]],"Installation":[[6,null]],"Introduction":[[7,null]],"Module contents":[[10,"module-pofff"],[11,"module-pofff.config"],[13,"module-pofff.core"],[15,"module-pofff.jobs"],[19,"module-pofff.utils"],[24,"module-pofff.visualization"]],"OPM Flow":[[6,"opm-flow"]],"Output folder":[[9,null]],"Overview":[[7,"overview"]],"Publication":[[30,null]],"Python package":[[6,"python-package"]],"Related":[[31,null]],"Report issues or problems":[[3,"report-issues-or-problems"]],"Seek support":[[3,"seek-support"]],"Single run":[[9,"single-run"]],"Source build in Linux/Windows":[[6,"source-build-in-linux-windows"]],"Source build in macOS":[[6,"source-build-in-macos"]],"Submodules":[[11,"submodules"],[13,"submodules"],[15,"submodules"],[19,"submodules"],[24,"submodules"]],"Subpackages":[[10,"subpackages"]],"Visualization":[[4,"visualization"]],"Welcome to pofff\u2019s documentation!":[[5,null]],"ad-micp":[[31,"ad-micp"]],"expreccs":[[31,"expreccs"]],"plopm":[[31,"plopm"]],"pofff":[[1,"pofff"],[8,null]],"pofff Python API":[[1,null]],"pofff package":[[10,null]],"pofff.config package":[[11,null]],"pofff.config.config module":[[12,null]],"pofff.core package":[[13,null]],"pofff.core.pofff module":[[14,null]],"pofff.jobs package":[[15,null]],"pofff.jobs.data module":[[16,null]],"pofff.jobs.delete module":[[17,null]],"pofff.jobs.metric module":[[18,null]],"pofff.utils package":[[19,null]],"pofff.utils.inputvalues module":[[20,null]],"pofff.utils.mapproperties module":[[21,null]],"pofff.utils.runs module":[[22,null]],"pofff.utils.writefile module":[[23,null]],"pofff.visualization package":[[24,null]],"pofff.visualization.benchmark module":[[25,null]],"pofff.visualization.error_table module":[[26,null]],"pofff.visualization.everert module":[[27,null]],"pofff.visualization.maps module":[[28,null]],"pofff.visualization.sparse_values module":[[29,null]],"pycopm":[[31,"pycopm"]],"pymm":[[31,"pymm"]],"pyopmnearwell":[[31,"pyopmnearwell"]],"pyopmspe11":[[31,"pyopmspe11"]]},"docnames":["about","api","configuration_file","contributing","examples","index","installation","introduction","modules","output_folder","pofff","pofff.config","pofff.config.config","pofff.core","pofff.core.pofff","pofff.jobs","pofff.jobs.data","pofff.jobs.delete","pofff.jobs.metric","pofff.utils","pofff.utils.inputvalues","pofff.utils.mapproperties","pofff.utils.runs","pofff.utils.writefile","pofff.visualization","pofff.visualization.benchmark","pofff.visualization.error_table","pofff.visualization.everert","pofff.visualization.maps","pofff.visualization.sparse_values","publication","related"],"envversion":{"sphinx":66,"sphinx.domains.c":3,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":9,"sphinx.domains.index":1,"sphinx.domains.javascript":3,"sphinx.domains.math":2,"sphinx.domains.python":4,"sphinx.domains.rst":2,"sphinx.domains.std":2},"filenames":["about.rst","api.rst","configuration_file.rst","contributing.rst","examples.rst","index.rst","installation.rst","introduction.rst","modules.rst","output_folder.rst","pofff.rst","pofff.config.rst","pofff.config.config.rst","pofff.core.rst","pofff.core.pofff.rst","pofff.jobs.rst","pofff.jobs.data.rst","pofff.jobs.delete.rst","pofff.jobs.metric.rst","pofff.utils.rst","pofff.utils.inputvalues.rst","pofff.utils.mapproperties.rst","pofff.utils.runs.rst","pofff.utils.writefile.rst","pofff.visualization.rst","pofff.visualization.benchmark.rst","pofff.visualization.error_table.rst","pofff.visualization.everert.rst","pofff.visualization.maps.rst","pofff.visualization.sparse_values.rst","publication.rst","related.rst"],"indexentries":{},"objects":{"":[[10,0,0,"-","pofff"]],"pofff":[[11,0,0,"-","config"],[13,0,0,"-","core"],[15,0,0,"-","jobs"],[19,0,0,"-","utils"],[24,0,0,"-","visualization"]],"pofff.config":[[12,0,0,"-","config"]],"pofff.config.config":[[12,1,1,"","CliConfig"],[12,1,1,"","PofffConfig"]],"pofff.config.config.CliConfig":[[12,2,1,"","deck"],[12,2,1,"","experiment"],[12,2,1,"","figures"],[12,2,1,"","fol"],[12,2,1,"","jobs"],[12,2,1,"","location"],[12,2,1,"","mcon"],[12,2,1,"","mode"],[12,2,1,"","msat"],[12,2,1,"","times"],[12,2,1,"","use"]],"pofff.config.config.PofffConfig":[[12,2,1,"","args"],[12,2,1,"","atol"],[12,2,1,"","boxa"],[12,2,1,"","boxb"],[12,2,1,"","boxc"],[12,2,1,"","callback"],[12,2,1,"","cap"],[12,2,1,"","constraints"],[12,2,1,"","cores"],[12,2,1,"","data"],[12,2,1,"","deck"],[12,2,1,"","delete"],[12,2,1,"","diffusion"],[12,2,1,"","dims"],[12,2,1,"","disp"],[12,2,1,"","dx"],[12,2,1,"","dz"],[12,2,1,"","enkf_alpha"],[12,2,1,"","ensembles"],[12,2,1,"","errors"],[12,2,1,"","ertargs"],[12,2,1,"","everert"],[12,2,1,"","experiment"],[12,2,1,"","facies"],[12,2,1,"","figures"],[12,2,1,"","fipnum"],[12,2,1,"","flow"],[12,2,1,"","fluxnum"],[12,2,1,"","fol"],[12,2,1,"","grid"],[12,2,1,"","hascellmaps"],[12,2,1,"","hm"],[12,2,1,"","init"],[12,2,1,"","inj"],[12,2,1,"","integrality"],[12,2,1,"","jobs"],[12,2,1,"","krn"],[12,2,1,"","krw"],[12,2,1,"","location"],[12,2,1,"","max_batch_num"],[12,2,1,"","max_function_evaluations"],[12,2,1,"","maxiter"],[12,2,1,"","maxtime"],[12,2,1,"","mcon"],[12,2,1,"","min_realizations_success"],[12,2,1,"","mode"],[12,2,1,"","monotonic"],[12,2,1,"","msat"],[12,2,1,"","mult_thickness"],[12,2,1,"","multpv"],[12,2,1,"","mutation"],[12,2,1,"","nxz"],[12,2,1,"","para"],[12,2,1,"","path"],[12,2,1,"","polish"],[12,2,1,"","popsize"],[12,2,1,"","porv"],[12,2,1,"","pressure"],[12,2,1,"","random_seed"],[12,2,1,"","recombination"],[12,2,1,"","rng"],[12,2,1,"","sensor_ik"],[12,2,1,"","sensors"],[12,2,1,"","source_ik"],[12,2,1,"","sources"],[12,2,1,"","strategy"],[12,2,1,"","temperature"],[12,2,1,"","thickness"],[12,2,1,"","times"],[12,2,1,"","tol"],[12,2,1,"","tuning"],[12,2,1,"","updating"],[12,2,1,"","use"],[12,2,1,"","vectorized"],[12,2,1,"","workers"],[12,2,1,"","x"],[12,2,1,"","x0"],[12,2,1,"","z"]],"pofff.core":[[14,0,0,"-","pofff"]],"pofff.core.pofff":[[14,3,1,"","check_cmdargs"],[14,3,1,"","cli_config"],[14,3,1,"","generate_figures"],[14,3,1,"","main"],[14,3,1,"","parse_args"],[14,3,1,"","prepare_jobs_folder"],[14,3,1,"","prepare_simulation"],[14,3,1,"","run_simulation_steps"]],"pofff.jobs":[[16,0,0,"-","data"],[17,0,0,"-","delete"],[18,0,0,"-","metric"]],"pofff.jobs.data":[[16,1,1,"","SimulationContext"],[16,1,1,"","SparseResults"],[16,3,1,"","compute_m_c"],[16,3,1,"","create_from_summary"],[16,3,1,"","dense_data"],[16,3,1,"","find_simulation_base"],[16,3,1,"","interpolate_sparse"],[16,3,1,"","main"],[16,3,1,"","read_opm"],[16,3,1,"","sparse_data"],[16,3,1,"","write_dense_data"],[16,3,1,"","write_sparse_data"]],"pofff.jobs.data.SimulationContext":[[16,2,1,"","actind"],[16,2,1,"","dense_t"],[16,2,1,"","dims"],[16,2,1,"","egrid"],[16,2,1,"","gxyz"],[16,2,1,"","ini"],[16,2,1,"","maps"],[16,2,1,"","norst"],[16,4,1,"","nxz"],[16,2,1,"","porv"],[16,2,1,"","resolution"],[16,2,1,"","sim"],[16,2,1,"","smspec"],[16,2,1,"","sparse_t"],[16,2,1,"","time_initial"],[16,2,1,"","times"],[16,2,1,"","times_summary"],[16,2,1,"","unrst"],[16,2,1,"","where"]],"pofff.jobs.data.SparseResults":[[16,2,1,"","ctx"],[16,2,1,"","dissa"],[16,2,1,"","dissb"],[16,2,1,"","dx"],[16,2,1,"","dz"],[16,2,1,"","fipnum"],[16,2,1,"","imma"],[16,2,1,"","immb"],[16,2,1,"","m_c"],[16,2,1,"","m_c_series"],[16,2,1,"","moba"],[16,2,1,"","mobb"],[16,2,1,"","pop1"],[16,2,1,"","pop2"],[16,2,1,"","seala"],[16,2,1,"","sealb"],[16,2,1,"","sealt"],[16,2,1,"","times_data"]],"pofff.jobs.metric":[[18,3,1,"","before_emd"],[18,3,1,"","calculate_emd"],[18,3,1,"","generate_segment_map"],[18,3,1,"","main"]],"pofff.utils":[[20,0,0,"-","inputvalues"],[21,0,0,"-","mapproperties"],[22,0,0,"-","runs"],[23,0,0,"-","writefile"]],"pofff.utils.inputvalues":[[20,3,1,"","build_config"],[20,3,1,"","extract_facies"],[20,3,1,"","load_toml"],[20,3,1,"","postprocess_config"],[20,3,1,"","process_tuning"]],"pofff.utils.mapproperties":[[21,3,1,"","classify_boxes"],[21,3,1,"","corner"],[21,3,1,"","corner_point_handling_fluidflower"],[21,3,1,"","get_cellmaps"],[21,3,1,"","get_lines"],[21,3,1,"","getpolygons"],[21,3,1,"","grid_and_properties"],[21,3,1,"","handle_thickness_map"],[21,3,1,"","positions"],[21,3,1,"","refinement_z"],[21,3,1,"","sensors_corner_point"],[21,3,1,"","sensors_structured"],[21,3,1,"","structured_handling_fluidflower"],[21,3,1,"","wells_corner_point"],[21,3,1,"","wells_structured"]],"pofff.utils.runs":[[22,3,1,"","_run"],[22,3,1,"","benchmark"],[22,3,1,"","data"],[22,3,1,"","ert"],[22,3,1,"","everest"],[22,3,1,"","flow"],[22,3,1,"","postprocess"]],"pofff.utils.writefile":[[23,3,1,"","compact_format"],[23,3,1,"","create_corner_point_grid"],[23,3,1,"","render_bcprop"],[23,3,1,"","render_copyd"],[23,3,1,"","render_equalreg"],[23,3,1,"","render_ert"],[23,3,1,"","render_everest"],[23,3,1,"","render_flow"],[23,3,1,"","render_monotonic"],[23,3,1,"","render_satufunc"],[23,3,1,"","render_scale"],[23,3,1,"","write_files"],[23,3,1,"","write_keywords"]],"pofff.visualization":[[25,0,0,"-","benchmark"],[26,0,0,"-","error_table"],[27,0,0,"-","everert"],[28,0,0,"-","maps"],[29,0,0,"-","sparse_values"]],"pofff.visualization.benchmark":[[25,3,1,"","benchmark"],[25,3,1,"","load_parser"],[25,3,1,"","run_benchmark"]],"pofff.visualization.error_table":[[26,3,1,"","load_sparse_csv"],[26,3,1,"","parse_arguments"],[26,3,1,"","run_error_table"]],"pofff.visualization.everert":[[27,1,1,"","Config"],[27,1,1,"","EnsembleState"],[27,1,1,"","OptimizationState"],[27,3,1,"","copy_tree_contents"],[27,3,1,"","ensure_dir"],[27,3,1,"","extract_best_simulation"],[27,3,1,"","extract_optimal_solution"],[27,3,1,"","initialize_ensemble"],[27,3,1,"","parse_args"],[27,3,1,"","plot_cumulative_misfit"],[27,3,1,"","plot_hm_mismatch"],[27,3,1,"","plot_misfit"],[27,3,1,"","plot_observable_distribution"],[27,3,1,"","plot_optimization"],[27,3,1,"","plot_optimization_details"],[27,3,1,"","plot_parameter_distributions"],[27,3,1,"","plot_simulation_ensemble"],[27,3,1,"","process_optimization"],[27,3,1,"","read_realisation"],[27,3,1,"","run"],[27,3,1,"","run_everert"],[27,3,1,"","save_figure"],[27,3,1,"","setup_matplotlib"]],"pofff.visualization.everert.Config":[[27,2,1,"","external"],[27,2,1,"","jobs"],[27,2,1,"","maps"],[27,2,1,"","min_concentration"],[27,2,1,"","min_saturation"],[27,2,1,"","path"],[27,2,1,"","run"],[27,2,1,"","times"]],"pofff.visualization.everert.EnsembleState":[[27,2,1,"","cumulative"],[27,2,1,"","idrealisation"],[27,2,1,"","miss_ens"],[27,2,1,"","n_e"],[27,2,1,"","n_i"],[27,2,1,"","no_obs"],[27,2,1,"","no_para"],[27,2,1,"","num_ens"],[27,2,1,"","observations"],[27,2,1,"","par_dis"],[27,2,1,"","para_file"],[27,2,1,"","para_names"],[27,2,1,"","sim_ens"],[27,2,1,"","simulations"]],"pofff.visualization.everert.OptimizationState":[[27,2,1,"","ind_batch"],[27,2,1,"","ind_sim"],[27,2,1,"","optimal_value"],[27,2,1,"","optimization"],[27,2,1,"","s"],[27,2,1,"","tot_eval"],[27,2,1,"","x"]],"pofff.visualization.maps":[[28,3,1,"","load_points"],[28,3,1,"","pngs"],[28,3,1,"","run_maps"],[28,3,1,"","segment"]],"pofff.visualization.sparse_values":[[29,3,1,"","main"]]},"objnames":{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","attribute","Python attribute"],"3":["py","function","Python function"],"4":["py","property","Python property"]},"objtypes":{"0":"py:module","1":"py:class","2":"py:attribute","3":"py:function","4":"py:property"},"terms":{"01_conmin":[4,30],"0f":30,"100e3":2,"10d":30,"10f":30,"1500e3":2,"1cm":30,"1cmish":30,"1e":[2,4,7,12,30],"1mm":30,"2e":[2,30],"2f":30,"3000e3":2,"300e3":2,"3e":[2,4],"50e3":2,"5d":30,"5e":[2,4,7,30],"5f":30,"636b45":30,"7e":30,"800e3":2,"9ca245":30,"9da347":30,"9fa44a":30,"A":[30,31],"About":5,"After":4,"All":[4,30],"An":[3,31],"Below":31,"Both":0,"Each":2,"For":[0,2,3,4,6,30],"Here":2,"If":[2,4,6,14,30],"In":3,"THAN":4,"The":[0,1,2,3,4,6,7,9,14,30],"Then":[4,6,30],"There":6,"These":30,"This":[4,7,30],"To":[4,6,30],"We":[3,4],"While":[4,6],"YOURS":4,"You":[4,6,30],"_2":[4,30],"_build":3,"_run":[10,19,22],"a0a64d":30,"a2a750":30,"a3a953":30,"a5aa56":30,"a6ac59":30,"a8ad5c":30,"a9af5f":30,"abb062":30,"abl":6,"abort":[22,27],"abov":7,"accept":3,"access":30,"accord":21,"accuraci":30,"across":12,"actind":[15,16],"action":[3,6],"activ":6,"ad":5,"adapt":30,"adb164":30,"add":[2,3,5,6,9,30],"addit":[3,4,30],"adjust":30,"aeb367":30,"ale":30,"alia":16,"also":[3,4,6,30],"altern":3,"alway":4,"anaconda":6,"analys":3,"ani":[3,12,14],"anoth":6,"answer":3,"api":[5,30],"appendix":30,"appendixb":[4,30],"appendixc":30,"appli":27,"approach":[0,3],"apt":6,"arg":[11,12,14],"argument":[7,12,14,22,25,26,27],"argv":[14,16,18,25,26,27,28],"array":2,"assign":[4,21],"atol":[11,12],"attempt":30,"auto":3,"automat":26,"avail":[6,30],"avoid":2,"b":30,"b0b46a":30,"b1b66d":30,"b3b770":30,"b4b973":30,"b6ba76":30,"b7bc79":30,"b9bd7c":30,"babf7f":30,"background":30,"base":[0,7,12,16,21,27,30,31],"basetemp":3,"bashrc":6,"basic":[7,12],"bcc082":30,"bcprop":23,"bec184":30,"befor":[3,12],"before_emd":[10,15,18],"behavior":18,"benchmark":[1,7,10,14,16,19,22,24,29,30,31],"best":27,"best_simul":9,"better":4,"bfc387":30,"bin":[2,3,6],"binari":[3,5],"bit":23,"black":[3,30],"bool":12,"bottom":2,"boundari":28,"box":21,"boxa":[11,12],"boxb":[11,12],"boxc":[11,12],"boxplot":27,"branch":6,"brew":5,"build":[2,3,5,14,20,21],"build_config":[10,19,20],"build_opm_mpi":6,"built":6,"c":[2,4,7,30],"c00b0b":30,"c1":7,"c10f0f":30,"c1c48a":30,"c2":7,"c21313":30,"c2c68d":30,"c31717":30,"c41b1b":30,"c4c790":30,"c5":7,"c51f1f":30,"c5c993":30,"c62323":30,"c72626":30,"c7ca96":30,"c82a2a":30,"c8cc99":30,"c92e2e":30,"ca3232":30,"cacd9c":30,"calcit":31,"calculate_emd":[10,15,18],"call":[1,4],"callabl":12,"callback":[11,12],"can":[3,4,6,30],"cap":[2,11,12],"capabl":4,"capillari":2,"cartesian":[2,30],"case":[4,30],"cb3636":30,"cbcf9f":30,"cbsfax":[4,30],"cc3a3a":30,"cco2":16,"cd":[6,30],"cd3e3e":30,"cdd0a2":30,"ce4141":30,"cell":[2,21],"center":[0,16],"central":12,"centroid":21,"cet_diverging_gwr_55_95_c38":30,"cf4545":30,"cfd":31,"cfd1a4":30,"cfg":[14,20,21,22,23,25,27],"cformat":[4,30],"chang":[3,4,30],"check":[3,6,14,31],"check_cmdarg":[10,13,14],"checker":3,"checkout":6,"choic":30,"ci":[3,6],"ci_pycopm_maco":3,"cite":5,"clabel":[4,30],"class":[12,16,27,28],"classify_box":[10,19,21],"cli":[12,14,20],"cli_config":[10,13,14],"cliconfig":[10,11,12,14,20],"clone":6,"closest":9,"cm":[6,30],"cmake":6,"cmd":[22,27],"cmdarg":14,"cnum":[4,30],"co":[4,30],"co2":[0,7,30,31],"coarser":30,"code":[3,6,10],"codebas":12,"collabor":3,"color":4,"com":[1,4,6,7,30],"combin":[7,12,14,30],"comma":7,"command":[4,6,12,14,22,25,26,27,30],"commit":3,"common":[4,6],"compact":23,"compact_format":[10,19,23],"compar":[4,30],"compare_all_time_seri":4,"comparison":[14,22,25,26],"comput":[7,16,18,21,26,30],"compute_m_c":[10,15,16],"comun":1,"concentr":[7,14,18,28,30],"concept":5,"config":[1,4,8,10,20,24,27],"configur":[4,5,6,7,12,14,20,22,27,30],"conmin":[18,28],"connect":28,"consid":[4,7],"consist":27,"constant":4,"constraint":[11,12],"construct":21,"contain":[1,3,4,9,12,16],"content":[1,3,5,8,20,27],"continu":18,"contour":28,"contribut":[0,5,6,27],"control":[26,30],"convert":18,"coord":23,"coordin":[2,21],"copi":[3,6,14,27],"copy_tree_cont":[10,24,27],"copyd":23,"core":[1,4,8,10,11,12,30],"corner":[1,2,10,12,19,21,23,30],"corner_point_handling_fluidflow":[10,19,21],"correct":4,"correspond":[4,21,30],"cov":3,"cover":14,"cp":27,"cpr_trueimp":30,"cpus":[4,30],"creat":[3,6,18,27],"create_corner_point_grid":[10,19,23],"create_from_summari":[10,15,16],"csiro":4,"csp":31,"cssr":[0,4,6,7,30],"csv":[4,7,16,26,29,30],"csvs":4,"ctick":30,"ctx":[15,16],"cumul":[24,27],"curl":30,"current":[6,7],"current_directori":6,"custom":[4,30],"d":[0,4,30],"d04949":30,"d0d3a7":30,"d14d4d":30,"d25151":30,"d2d4aa":30,"d35555":30,"d3d6ad":30,"d45959":30,"d55c5c":30,"d5d7b0":30,"d66060":30,"d6d9b3":30,"d76464":30,"d86868":30,"d8dab6":30,"d96c6c":30,"d9dcb9":30,"da7070":30,"darus":30,"data":[1,4,7,10,11,12,15,19,20,22,26,30],"datafil":30,"datum":2,"day":[4,30],"db7474":30,"dbddbc":30,"dc7777":30,"dcdfbf":30,"dcmake_build_typ":6,"dcmake_prefix_path":6,"dd7b7b":30,"de":30,"de7f7f":30,"decay":30,"deck":[1,11,12],"decreas":[4,30],"dedic":6,"dee0c1":30,"default":7,"defin":[2,12,14,25],"definit":[20,21],"delax":30,"delet":[10,11,12,15],"dens":16,"dense_data":[10,15,16],"dense_t":[15,16],"depend":[3,6],"deriv":[12,20],"describ":[3,7],"descript":[2,4],"detail":[4,30],"determin":21,"dev":[3,6],"develop":6,"df8383":30,"diagnost":27,"dict":[12,20],"differ":[1,4,30],"diffus":[2,11,12],"dim":[11,12,15,16],"dir":2,"direct":4,"directori":[3,14,27,30],"discret":[18,28],"disk":26,"disp":[11,12],"dispatch":21,"disperc1":2,"disperc2":2,"disperc3":2,"disperc4":2,"disperc5":2,"disperc6":2,"dissa":[15,16],"dissb":[15,16],"dissolv":7,"distanc":[7,18,26,30],"distribut":27,"divid":2,"dmar":2,"doc":3,"docmunet":4,"docs_adding_your_result":4,"docs_hello_world":4,"docs_visu":4,"document":[3,4,6,7],"doe":30,"doi":0,"done":6,"download":[1,6,30],"dpi":[4,27],"dst":27,"duse_mpi":6,"dvipng":6,"dwith_ndebug":6,"dx":[11,12,15,16,23],"dynam":31,"dz":[11,12,15,16,23],"e":[0,2,3,4,6,7,30],"e08787":30,"e0e1c4":30,"e18b8b":30,"e1e3c7":30,"e28f8f":30,"e39292":30,"e3e4ca":30,"e49696":30,"e4e6cd":30,"e59a9a":30,"e69e9":30,"e6e7d0":30,"e7a2a2":30,"e7e9d3":30,"e8a6a6":30,"e9aaaa":30,"e9ead6":30,"eaadad":30,"eaecd9":30,"ebb1b1":30,"ecb5b5":30,"eceddc":30,"eclfil":16,"eclips":23,"edb9b9":30,"edefdf":30,"edg":6,"edit":30,"eebdbd":30,"efc1c1":30,"eff0e1":30,"effici":23,"egrid":[15,16],"eight":4,"either":[6,21],"els":6,"em":31,"email":3,"emd":18,"enabl":[2,20,30],"encod":23,"end":[4,6,30],"enkf_alpha":[11,12],"enrich":20,"ensembl":[11,12,27],"ensemblest":[10,24,27],"ensure_dir":[10,24,27],"enterpris":6,"entri":[2,14,16,25,27],"environ":[3,6],"equalreg":23,"error":[4,11,12,26],"error_t":[10,24],"error_table_satmin":[4,30],"erst":16,"ert":[1,4,5,7,10,19,22,23,27],"ertarg":[11,12],"esf":30,"esmri":16,"etc":[4,23],"evalu":[2,4,7,14],"everert":[10,11,12,24],"everest":[1,4,5,7,10,19,22,23,27],"everest_iter_3":4,"exact":18,"exampl":[2,5,9,30],"except":2,"execut":[1,2,3,4,6,7,9,14,22,27,30],"experi":[4,11,12,28],"experiment":[4,7,18,26,28],"experimental_data":18,"export":[3,6],"exprecc":5,"extend":3,"extern":[24,27],"extra":6,"extract":[16,20,27,29],"extract_best_simul":[10,24,27],"extract_faci":[10,19,20],"extract_optimal_solut":[10,24,27],"f":[4,7,30],"f0c5c5":30,"f1c8c8":30,"f1f1e4":30,"f2cccc":30,"f2f3e7":30,"f3d0d0":30,"f4d4d4":30,"f4f4ea":30,"f5d8d8":30,"f5f6ed":30,"f6dcdc":30,"f7e0e0":30,"f7f7f0":30,"f8e3e3":30,"f8f9f3":30,"f9e7e7":30,"faci":[2,4,11,12,21],"facie1":2,"facie2":2,"facie3":2,"facie4":2,"facie5":2,"facie6":2,"factor":[21,30],"factori":[12,16,27],"faebeb":30,"fafaf6":30,"fail":22,"failur":27,"fair":[7,30],"fals":12,"familiar":3,"fbefef":30,"fbfcf9":30,"fcf3f3":30,"fdf7f7":30,"fdfdfc":30,"featur":0,"fefbfb":30,"ff0000":30,"ff0100":30,"ff0200":30,"ff0300":30,"ff0400":30,"ff0500":30,"ff0600":30,"ff0700":30,"ff0800":30,"ff0900":30,"ff0a00":30,"ff0b00":30,"ff0c00":30,"ff0d00":30,"ff0e00":30,"ff0f00":30,"ff1000":30,"ff1100":30,"ff1200":30,"ff1300":30,"ff1400":30,"ff1500":30,"ff1600":30,"ff1700":30,"ff1800":30,"ff1900":30,"ff1a00":30,"ff1b00":30,"ff1c00":30,"ff1d00":30,"ff1e00":30,"ff1f00":30,"ff2000":30,"ff2100":30,"ff2200":30,"ff2300":30,"ff2400":30,"ff2500":30,"ff2600":30,"ff2700":30,"ff2800":30,"ff2900":30,"ff2a00":30,"ff2b00":30,"ff2c00":30,"ff2d00":30,"ff2e00":30,"ff2f00":30,"ff3000":30,"ff3100":30,"ff3200":30,"ff3300":30,"ff3400":30,"ff3500":30,"ff3600":30,"ff3700":30,"ff3800":30,"ff3900":30,"ff3a00":30,"ff3b00":30,"ff3c00":30,"ff3d00":30,"ff3e00":30,"ff3f00":30,"ff4000":30,"ff4100":30,"ff4200":30,"ff4300":30,"ff4400":30,"ff4500":30,"ff4600":30,"ff4700":30,"ff4800":30,"ff4900":30,"ff4a00":30,"ff4b00":30,"ff4c00":30,"ff4d00":30,"ff4e00":30,"ff4f00":30,"ff5000":30,"ff5100":30,"ff5200":30,"ff5300":30,"ff5400":30,"ff5500":30,"ff5600":30,"ff5700":30,"ff5800":30,"ff5900":30,"ff5a00":30,"ff5b00":30,"ff5c00":30,"ff5d00":30,"ff5e00":30,"ff5f00":30,"ff6000":30,"ff6100":30,"ff6200":30,"ff6300":30,"ff6400":30,"ff6500":30,"ff6600":30,"ff6700":30,"ff6800":30,"ff6900":30,"ff6a00":30,"ff6b00":30,"ff6c00":30,"ff6d00":30,"ff6e00":30,"ff6f00":30,"ff7000":30,"ff7100":30,"ff7200":30,"ff7300":30,"ff7400":30,"ff7500":30,"ff7600":30,"ff7700":30,"ff7800":30,"ff7900":30,"ff7a00":30,"ff7b00":30,"ff7c00":30,"ff7d00":30,"ff7e00":30,"ff7f00":30,"ff8000":30,"ff8100":30,"ff8200":30,"ff8300":30,"ff8400":30,"ff8500":30,"ff8600":30,"ff8700":30,"ff8800":30,"ff8900":30,"ff8a00":30,"ff8b00":30,"ff8c00":30,"ff8d00":30,"ff8e00":30,"ff8f00":30,"ff9000":30,"ff9100":30,"ff9200":30,"ff9300":30,"ff9400":30,"ff9500":30,"ff9600":30,"ff9700":30,"ff9800":30,"ff9900":30,"ff9a00":30,"ff9b00":30,"ff9c00":30,"ff9d00":30,"ff9e00":30,"ff9f00":30,"ffa000":30,"ffa100":30,"ffa200":30,"ffa300":30,"ffa400":30,"ffa500":30,"ffa600":30,"ffa700":30,"ffa800":30,"ffa900":30,"ffaa00":30,"ffab00":30,"ffac00":30,"ffad00":30,"ffae00":30,"ffaf00":30,"ffb000":30,"ffb100":30,"ffb200":30,"ffb300":30,"ffb400":30,"ffb500":30,"ffb600":30,"ffb700":30,"ffb800":30,"ffb900":30,"ffba00":30,"ffbb00":30,"ffbc00":30,"ffbd00":30,"ffbe00":30,"ffbf00":30,"ffc000":30,"ffc100":30,"ffc200":30,"ffc300":30,"ffc400":30,"ffc500":30,"ffc600":30,"ffc700":30,"ffc800":30,"ffc900":30,"ffca00":30,"ffcb00":30,"ffcc00":30,"ffcd00":30,"ffce00":30,"ffcf00":30,"ffd000":30,"ffd100":30,"ffd200":30,"ffd300":30,"ffd400":30,"ffd500":30,"ffd600":30,"ffd700":30,"ffd800":30,"ffd900":30,"ffda00":30,"ffdb00":30,"ffdc00":30,"ffdd00":30,"ffde00":30,"ffdf00":30,"ffe000":30,"ffe100":30,"ffe200":30,"ffe300":30,"ffe400":30,"ffe500":30,"ffe600":30,"ffe700":30,"ffe800":30,"ffe900":30,"ffea00":30,"ffeb00":30,"ffec00":30,"ffed00":30,"ffee00":30,"ffef00":30,"fff000":30,"fff100":30,"fff200":30,"fff300":30,"fff400":30,"fff500":30,"fff600":30,"fff700":30,"fff800":30,"fff900":30,"fffa00":30,"fffb00":30,"fffc00":30,"fffd00":30,"ffffff":30,"fi":6,"field":[18,28],"fig":27,"figur":[4,6,7,9,11,12,14,22,25,27,30],"figure10":30,"figure10d":30,"figure10f":30,"figure3":30,"figure3a":30,"figure3b":30,"figure4":30,"figure4ish":30,"figure5":30,"figure5d":30,"figure5f":30,"figures10":30,"figures10d":30,"figures5":30,"figures5d":30,"file":[1,3,4,5,6,7,9,14,16,17,20,23,26,28,30],"file_1":18,"file_2":18,"file_nam":18,"filenam":[20,26,28],"final":[1,2,12,27,30],"find_simulation_bas":[10,15,16],"fip":21,"fipnum":[11,12,15,16,23],"first":2,"fit":27,"five":4,"fix":[2,3],"flag":[2,4,20,30],"flexibl":30,"float":[12,16,18,21,27],"float64":21,"flow":[2,3,4,5,7,10,11,12,19,22,23,30,31],"flow_maco":3,"fluidflow":[1,4,7,21],"flux":21,"fluxnum":[11,12,23],"fol":[11,12],"folder":[1,3,4,5,6,7,27,30],"follow":[2,4,6,7,9],"font":[4,6],"forecast":4,"fork":[0,3],"forlder":1,"format":[3,4,6,25,26,29,30],"formula":5,"found":4,"fraction":[4,30],"framework":[0,1,4,7,31],"friend":[7,30],"full":[2,6],"fulli":20,"function":[2,4,20,21,22,23],"fund":0,"g":[2,3,4,6,30],"gas":2,"gasda":0,"gaseous":7,"generat":[1,2,3,4,7,9,14,21,22,23,25,27,28,29,30,31],"generate_figur":[10,13,14],"generate_segment_map":[10,15,18],"geo":28,"geolog":[1,21,28,31],"geometri":1,"get":[3,6],"get_cellmap":[10,19,21],"get_lin":[10,19,21],"getpolygon":[10,19,21],"gif":[4,31],"git":[4,6,30],"github":[1,2,3,4,6,7,30],"githubusercont":30,"give":30,"given":4,"grid":[1,2,4,6,11,12,16,21,23,30],"grid_and_properti":[10,19,21],"ground":5,"growth":30,"gxyz":[15,16],"h":[0,4,30],"handl":[16,26],"handle_thickness_map":[10,19,21],"hascellmap":[11,12],"hat":6,"head":30,"header":26,"hello":5,"help":[3,6],"histori":[0,2,5,7],"hm":[4,11,12],"hold":27,"homebrew":6,"horizon":21,"host":[1,7],"hour":[7,16],"html":3,"https":[0,1,4,6,7,30],"ident":23,"idrealis":[24,27],"ignor":[3,14],"imag":[0,7,18,31],"imma":[15,16],"immb":[15,16],"implement":[7,23],"import":3,"includ":[6,23,30],"incompat":14,"increas":30,"ind_batch":[24,27],"ind_sim":[24,27],"index":[5,21],"indic":21,"induc":31,"indx":18,"inf":27,"inform":[4,16],"ini":[15,16],"init":[11,12],"initi":[2,4,20,27],"initialize_ensembl":[10,24,27],"inj":[2,4,11,12],"inject":[2,20],"input":[1,2,7,12,14,20,30],"inputvalu":[10,19],"insid":6,"instal":[3,4,5,30],"instead":30,"instruct":6,"int":[12,16,18,21,27],"int64":18,"integr":[11,12],"interact":22,"interest":[6,30,31],"interim":6,"interpol":16,"interpolate_spars":[10,15,16],"interval":4,"introduct":5,"invalid":14,"isotrop":4,"issu":[0,4,5],"iter":[2,4,12,27,30],"j":[0,27],"j5":6,"job":[1,8,10,11,12,14,24,27],"keyword":[4,23],"kg":2,"krn":[2,11,12],"krw":[2,11,12],"l":[6,30],"label":4,"landa":0,"larg":17,"last":4,"latest":6,"latex":6,"leakag":31,"length":23,"less":[4,30],"librari":6,"like":4,"limit":30,"line":[2,3,4,6,12,14,21,25,26,27,28,30],"linear":30,"link":6,"lint":6,"linux":5,"liquid":[2,4,30],"list":[12,14,16,21,23,27],"load":[16,20,21,26,28],"load_pars":[10,24,25],"load_point":[10,24,28],"load_sparse_csv":[10,24,26],"load_toml":[10,19,20],"local":[3,4],"locat":[1,11,12,16],"look":3,"loop":4,"low":4,"m":[0,2,4,6,7,9,30],"m_c":[15,16],"m_c_seri":[15,16],"mac":[4,30],"machin":[2,4,6,30],"maco":[3,5],"macport":6,"mactex":6,"main":[1,3,10,13,14,15,16,18,20,24,25,29,30],"maintain":3,"make":[3,6,14],"mako":[1,23,30],"mamba":6,"manag":6,"manual":30,"map":[2,4,10,15,16,18,21,24,27,30],"map_24h":4,"mapproperti":[10,19],"marb\u00e1n":0,"mask":[4,30],"maskthr":[4,30],"mass":[4,16,30],"master":6,"match":[0,2,5,7],"matplotlib":27,"matrix":7,"max":[2,12,30],"max_batch_num":[11,12],"max_function_evalu":[4,11,12],"maximum":4,"maxit":[11,12],"maxtim":[11,12],"mcon":[11,12],"mean":27,"measur":2,"med":0,"memori":27,"merg":3,"metric":[7,10,15,16,26],"micp":5,"microbi":31,"microsystem":31,"might":[3,4,6,30,31],"migrat":16,"min":[2,30],"min_concentr":[24,27],"min_realizations_success":[11,12],"min_satur":[24,27],"miniforg":6,"minimum":7,"minut":4,"misfit":27,"miss":[3,4,27],"miss_en":[24,27],"mit_m1":4,"mkdir":[6,30],"moba":[15,16],"mobb":[15,16],"mode":[11,12,14],"model":[2,12,21,31],"model_result":18,"modifi":[1,4,6],"modul":[1,5,8,31],"monoton":[11,12,23],"mpi":[6,30],"mpirun":[2,30],"msat":[11,12],"mult_thick":[2,11,12],"multipli":[2,4,21],"multpv":[11,12],"multx":30,"mutat":[11,12],"my_simul":4,"mypi":3,"n":[3,23],"n_e":[24,27],"n_i":[24,27],"name":[4,7,16],"name_of_input_fil":7,"namespac":14,"ncz":21,"ndarray":[12,16,18,21,23,27],"near":31,"need":[3,4,6,30],"new":[0,3,4,30],"newton":[2,30],"newtoniterationcount":30,"next":2,"nice":[3,6],"nkrn":[2,12],"nkrn1":2,"nkrn2":2,"nkrn3":2,"nkrn4":2,"nkrn5":2,"nkrn6":2,"nkrw":[2,12],"nkrw1":2,"nkrw2":2,"nkrw3":2,"nkrw4":2,"nkrw5":2,"nkrw6":2,"no_ob":[24,27],"no_para":[24,27],"non":2,"none":[4,7,12,14,16,18,20,21,22,23,25,26,27,28,29,30],"nordbotten":0,"normal":[12,14,20,21],"norst":[15,16],"note":4,"np":[2,30],"np6":4,"npe1":2,"npe2":2,"npe3":2,"npe4":2,"npe5":2,"npe6":2,"npen":[2,12],"npnt1":2,"npnt2":2,"npnt3":2,"npnt4":2,"npnt5":2,"npnt6":2,"npntn":2,"num_en":[24,27],"number":[2,4,21,30],"numer":18,"numpi":23,"nxz":[11,12,15,16],"o":[4,7,9,30],"object":[12,14,16,20,27],"observ":[9,24,27],"obtain":30,"one":[2,4,27],"onli":7,"onlin":[4,6],"onto":16,"open":[0,6,7,31],"oper":[14,30],"opm":[2,3,4,5,7,16,22,23,30,31],"opm2":30,"opm3":30,"opm4":30,"opt":27,"optim":[22,24,27],"optimal_valu":[24,27],"optimizationst":[10,24,27],"option":[6,7,12,14,22,26],"org":[0,6],"origin":[16,18,23],"otherwis":2,"outcom":27,"output":[2,4,5,7,12,14,16,23],"output_fil":29,"overlay":28,"overview":5,"pa":2,"packag":[1,3,5,8],"page":[3,5],"paper":[4,30],"par_di":[24,27],"para":[11,12],"para_fil":[24,27],"para_nam":[24,27],"parallel":4,"paramet":[2,4,14,27],"pars":[14,25,26,27],"parse_arg":[10,13,14,24,27],"parse_argu":[10,24,26],"part":1,"path":[2,3,6,11,12,14,16,20,24,26,27,28],"pen":[2,12],"pen1":2,"pen2":2,"pen3":2,"pen4":2,"pen5":[2,4],"pen6":2,"per":27,"perform":0,"perm":2,"perm3":4,"permeabl":4,"permx1":2,"permx2":[2,4],"permx3":2,"permx4":2,"permx5":2,"permx6":2,"permz1":2,"permz2":2,"permz3":2,"permz4":2,"permz5":2,"permz6":2,"phase":30,"pip":[3,4,6,30],"platform":6,"pleas":[0,3,4],"plopm":[4,5,6,30],"plot":[4,7,14,18,22,27,28],"plot_cumulative_misfit":[10,24,27],"plot_hm_mismatch":[10,24,27],"plot_misfit":[10,24,27],"plot_observable_distribut":[10,24,27],"plot_optim":[10,24,27],"plot_optimization_detail":[10,24,27],"plot_parameter_distribut":[10,24,27],"plot_simulation_ensembl":[10,24,27],"png":[4,18],"pngs":[4,9,10,24,28,31],"pofff":[3,4,6,7,9,30],"pofff_path":[14,20],"pofffconfig":[10,11,12,14,20,21,22,23],"point":[1,2,12,16,21,23,25,27,28,30],"polish":[11,12],"polygon":21,"pop1":[15,16],"pop2":[15,16],"popsiz":[11,12],"poro1":2,"poro2":2,"poro3":2,"poro4":2,"poro5":2,"poro6":2,"porous":0,"porv":[11,12,15,16],"posit":[2,10,19,21],"posixpath":[12,16],"possibl":[3,4],"postprocess":[1,4,10,16,19,22,27],"postprocess_config":[10,19,20],"precipit":31,"precomput":7,"prefer":3,"prepar":[14,20],"prepare_jobs_fold":[10,13,14],"prepare_simul":[10,13,14],"prerequisit":6,"preserv":[16,18],"pressur":[2,11,12],"previous":[3,4,6,30],"principl":30,"problem":5,"process":[22,27],"process_optim":[10,24,27],"process_tun":[10,19,20],"profil":[4,30],"progress":27,"project":[0,6,31],"prompt":22,"properti":[2,16,21],"prs":3,"public":[2,3,4,5],"pull":[0,3,4],"push":3,"pushd":3,"pvtnum":30,"pwd":6,"py":[4,23,30],"py314":3,"pycopm":[3,5,6],"pylint":3,"pymm":5,"pyopmnearwel":5,"pyopmspe11":[5,30],"pytest":3,"python":[3,5,30,31],"python3":[6,30],"quantifi":26,"quantiti":16,"quick":31,"r":[3,6,27,30],"r2_cart_1cm_capmax2500pa":30,"r3_cp_1cmish_capmax2500pa":30,"r4_cart_1mm_capmax2500pa":30,"r5_cart_1mm_capmax2500pa_strictol":30,"rais":[0,3,4,14],"ran":4,"random":[4,30],"random_se":[11,12],"rang":4,"rate":[2,30],"raw":[12,30],"read":[21,27,29],"read_opm":[10,15,16],"read_realis":[10,24,27],"real":2,"realiz":27,"recent":6,"recombin":[11,12],"recommend":6,"red":6,"ref":30,"refer":[4,21],"refin":[2,21],"refinement_z":[10,19,21],"refxz":16,"regard":30,"region":[21,31],"rel":2,"relat":[3,5],"releas":[6,27],"reload":6,"remedi":31,"remov":[20,30],"render_bcprop":[10,19,23],"render_copyd":[10,19,23],"render_equalreg":[10,19,23],"render_ert":[10,19,23],"render_everest":[10,19,23],"render_flow":[10,19,23],"render_monoton":[10,19,23],"render_satufunc":[10,19,23],"render_scal":[10,19,23],"replac":3,"repo":[3,6],"report":[5,30],"repositori":[3,6],"reproduc":[4,18,30],"request":[0,3,4,14,18,20],"requir":[3,4,6,18,30],"res":16,"reservoir":31,"resinsight":6,"resolut":[4,15,16],"resourc":[0,30],"restart":[16,30],"result":[2,5,6,9,14,18,22,26,27,30],"return":[14,20],"review":3,"rig":2,"rise":3,"rng":[11,12],"round":18,"routin":14,"rst":[2,30],"ruff":3,"rule":5,"run":[3,4,5,6,7,10,14,19,23,24,25,27,30],"run2":12,"run_benchmark":[10,24,25],"run_error_t":[10,24,26],"run_everert":[10,24,27],"run_map":[10,24,28],"run_simulation_step":[10,13,14],"runtim":[12,20,27],"s":[0,2,3,6,7,24,27],"s11242":0,"sand":30,"sandv":0,"satmin":[18,28],"satnum":[4,30],"satufunc":23,"satur":[2,7,14,18,28],"save":[27,30],"save_figur":[10,24,27],"scale":23,"schedul":2,"score":18,"screenshot":9,"script":[1,3,4,6,14,17,30],"seala":[15,16],"sealb":[15,16],"sealt":[15,16],"search":[5,6],"section":[23,30],"see":[2,3,4,6,30],"seed":[4,30],"seek":5,"segment":[7,10,18,24,28],"select":[14,16,21,28],"send":3,"sensit":30,"sensor":[11,12,21],"sensor_ik":[11,12],"sensors_corner_point":[10,19,21],"sensors_structur":[10,19,21],"separ":7,"sequenc":[12,22],"seri":[16,22,29],"set":[2,4,6,7,12,30],"setup":30,"setup_matplotlib":[10,24,27],"setuptool":6,"sgas":16,"sh":[4,6,30],"shorter":4,"show":[9,30],"sim":[15,16],"sim_en":[24,27],"similar":30,"simpli":6,"simul":[2,3,4,6,7,9,16,18,22,24,26,27,28,30,31],"simulationcontext":[10,15,16],"simxcent":21,"simzcent":21,"singl":[3,4,5,7,12,16,30],"site":31,"six":3,"size":[2,4],"skip":[6,22],"slow":7,"smspec":[15,16],"sni":[2,12],"sni1":2,"sni2":2,"sni3":2,"sni4":2,"sni5":2,"sni6":2,"softwar":5,"solver":30,"soon":3,"sourc":[0,2,3,5,7,11,12,31],"source1":2,"source2":2,"source_ik":[11,12],"spars":[16,26,29],"sparse_data":[4,10,15,16,29],"sparse_t":[15,16],"sparse_valu":[10,24],"sparseresult":[10,15,16],"spatial":[4,16,18,21,22,28,30],"spatial_map_xh":4,"spe11":31,"spe11a":30,"spe11a_spatial_map_48h":30,"special":14,"specif":[6,20,21],"speed":[7,30],"split":4,"squash":3,"src":[3,27],"state":[16,27],"static":3,"statist":27,"step":[2,3,4,6,30],"storag":[0,31],"str":[12,14,18,20,21,22,27,29],"strategi":[11,12],"stricter":30,"structur":[12,21],"structured_handling_fluidflow":[10,19,21],"studi":[4,7,27,30,31],"stuttgart":30,"style":[4,21,23,27],"subfig":30,"submit":30,"submodul":[1,8,10],"subpackag":[1,5,8],"subsect":[9,30],"subsurfac":0,"subsystem":6,"succed":6,"succeed":[3,6],"success":27,"sudo":6,"sum":27,"summar":26,"summari":16,"super":6,"support":[2,4,5,6,7,30],"suptitl":30,"sustain":0,"sw":[2,12],"swi":[2,12],"swi1":2,"swi2":2,"swi3":2,"swi4":2,"swi5":2,"swi6":2,"system":[3,4,30],"systemexit":14,"t":[0,4,7,28,30],"tab20":27,"tabl":[2,9,26,30],"tag":6,"tailor":31,"tap":6,"target":3,"temperatur":[2,11,12],"templat":[23,30],"templeat":1,"tensor":2,"term":3,"termin":[4,6,30],"test":[2,3,4,6,30],"test_output":3,"texliv":6,"thick":[2,11,12,21,30],"thicknessmult":4,"thre1":2,"thre2":2,"thre3":2,"thre4":2,"thre5":2,"thre6":2,"thren":2,"threshold":[2,14,26],"tick":4,"time":[2,4,7,11,12,14,15,16,18,22,24,27,28,29,30],"time_initi":[15,16],"time_seri":[4,29],"times_data":[15,16],"times_summari":[15,16],"timeseries_fil":29,"tol":[11,12],"toler":30,"toml":[2,4,7,9,12,20,30],"tool":[0,3,4,6,7,30,31],"top":2,"tot_ev":[24,27],"total":[4,18],"track":27,"trade":30,"transp":0,"tri":[3,6,30],"trip":18,"true":[2,30],"trust":6,"tug":6,"tune":[2,11,12,20],"tunit":4,"tupl":[12,16,18,21],"turn":6,"tutori":[3,6],"two":[4,18],"txt":[3,6,23],"type":[2,4,6,12,21,31],"u":7,"ubuntu":[3,6],"uni":30,"unifi":12,"uniform":16,"unrst":[15,16],"unzip":30,"updat":[11,12,27],"upgrad":6,"use":[0,2,3,4,6,7,11,12,23,30,31],"user":[2,3,6,7],"util":[1,8,10],"v":[4,30],"v2026":6,"valid":[7,14],"valu":[2,7,16,20,23,29,30],"variabl":[2,3,4,23],"vector":[11,12,16],"venv":6,"version":[3,6,10],"vertic":21,"via":[4,6],"virtual":6,"visit":3,"visual":[1,5,6,8,10],"vpofff":6,"vtks":31,"w":[0,30],"wasserstain":7,"wasserstein":[7,18,26,30],"websit":30,"welcom":[0,3,4],"well":[2,3,21,30,31],"wells_corner_point":[10,19,21],"wells_structur":[10,19,21],"wet":2,"wheel":6,"wide":16,"will":[3,30],"window":5,"without":30,"work":[3,6],"worker":[11,12],"workflow":14,"world":5,"write":[2,6,16,23,26,29],"write_dense_data":[10,15,16],"write_fil":[10,19,23],"write_keyword":[10,19,23],"write_sparse_data":[10,15,16],"writefil":[10,19],"wsl":6,"www":6,"x":[2,4,11,12,21,24,27,28],"x0":[11,12],"xcent":21,"xci":21,"xco2l":[4,30],"xcoord":[21,23],"xformat":30,"xlim":18,"xlnum":30,"xunit":30,"xyz":21,"y":6,"yformat":30,"yml":[3,4,6,23],"yournam":3,"yunit":30,"z":[2,4,11,12,21,28],"zcent":21,"zci":21,"zcoord":[21,23],"zcorn":23,"zlim":18,"znr":21,"zshrc":6,"ztop":21},"titles":["About pofff","pofff Python API","Configuration file","Contributing","Examples","Welcome to pofff\u2019s documentation!","Installation","Introduction","pofff","Output folder","pofff package","pofff.config package","pofff.config.config module","pofff.core package","pofff.core.pofff module","pofff.jobs package","pofff.jobs.data module","pofff.jobs.delete module","pofff.jobs.metric module","pofff.utils package","pofff.utils.inputvalues module","pofff.utils.mapproperties module","pofff.utils.runs module","pofff.utils.writefile module","pofff.visualization package","pofff.visualization.benchmark module","pofff.visualization.error_table module","pofff.visualization.everert module","pofff.visualization.maps module","pofff.visualization.sparse_values module","Publication","Related"],"titleterms":{"About":0,"ad":31,"add":4,"api":1,"benchmark":25,"binari":6,"brew":6,"build":6,"cite":0,"concept":7,"config":[11,12],"configur":2,"content":[10,11,13,15,19,24],"contribut":3,"core":[13,14],"data":16,"delet":17,"document":5,"error_t":26,"ert":9,"everert":27,"everest":9,"exampl":4,"exprecc":31,"file":2,"flow":6,"folder":9,"formula":6,"ground":3,"hello":4,"histori":4,"indic":5,"inputvalu":20,"instal":6,"introduct":7,"issu":3,"job":[15,16,17,18],"linux":6,"maco":6,"map":28,"mapproperti":21,"match":4,"metric":18,"micp":31,"modul":[10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29],"opm":6,"output":9,"overview":7,"packag":[6,10,11,13,15,19,24],"plopm":31,"pofff":[0,1,5,8,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29],"problem":3,"public":30,"pycopm":31,"pymm":31,"pyopmnearwel":31,"pyopmspe11":31,"python":[1,6],"relat":31,"report":3,"result":4,"rule":3,"run":[9,22],"s":5,"seek":3,"singl":9,"softwar":3,"sourc":6,"sparse_valu":29,"submodul":[11,13,15,19,24],"subpackag":10,"support":3,"tabl":5,"util":[19,20,21,22,23],"visual":[4,24,25,26,27,28,29],"welcom":5,"window":6,"world":4,"writefil":23}}) \ No newline at end of file +Search.setIndex({"alltitles":{"About the project":[[36,"about-the-project"]],"Add your results to the FluidFlower comparison":[[42,"add-your-results-to-the-fluidflower-comparison"]],"Additional examples":[[32,"additional-examples"]],"Appendix C: computational details":[[40,"appendix-c-computational-details"]],"Basic usage":[[36,"basic-usage"]],"Binary packages":[[35,"binary-packages"]],"Brew formula for macOS":[[35,"brew-formula-for-macos"]],"Citation":[[36,"citation"]],"Command":[[31,"command"],[43,"command"],[44,"command"],[45,"command"],[46,"command"],[47,"command"],[48,"command"]],"Command-line reference":[[23,null]],"Commands":[[33,"commands"]],"Complete TOML example":[[24,null]],"Configuration":[[33,"configuration"],[43,"configuration"],[44,"configuration"],[45,"configuration"],[46,"configuration"],[47,"configuration"],[48,"configuration"]],"Configuration reference":[[28,null]],"Contribute to the software":[[29,"contribute-to-the-software"]],"Contributing":[[29,null]],"Core workflows":[[36,"core-workflows"]],"ERT":[[26,"ert"],[39,"ert"]],"ERT and Everest settings":[[26,null]],"Evaluation times":[[37,"evaluation-times"]],"Everest":[[26,"everest"],[39,"everest"]],"Everest example":[[32,"everest-example"]],"Examples":[[30,null]],"Execution mode":[[37,"execution-mode"]],"Execution modes":[[38,null]],"Experimental realization":[[37,"experimental-realization"]],"Facies and saturation functions":[[25,null]],"Figure 3: geology and thickness":[[40,"figure-3-geology-and-thickness"]],"Figure 4: SPE11A grid comparison":[[40,"figure-4-spe11a-grid-comparison"]],"Figure generation":[[37,"figure-generation"]],"Figures 10 and 11 and Table 5":[[40,"figures-10-and-11-and-table-5"]],"Figures 10d to 10f: dissolved CO2 maps":[[40,"figures-10d-to-10f-dissolved-co2-maps"]],"Figures 5 to 8 and Table 4":[[40,"figures-5-to-8-and-table-4"]],"Figures 5d to 5f: dissolved CO2 maps":[[40,"figures-5d-to-5f-dissolved-co2-maps"]],"General options":[[37,null]],"Generate the comparison figures":[[43,null]],"Generated configurations and direct execution":[[32,"generated-configurations-and-direct-execution"]],"Goal":[[43,"goal"],[44,"goal"],[45,"goal"],[46,"goal"],[47,"goal"],[48,"goal"]],"Ground Rules":[[29,"ground-rules"]],"Hello world":[[31,null]],"History matching with ERT and Everest":[[32,null]],"History-matching parameters":[[26,"history-matching-parameters"],[32,"history-matching-parameters"]],"How it works":[[43,"how-it-works"],[44,"how-it-works"],[45,"how-it-works"],[46,"how-it-works"],[47,"how-it-works"],[48,"how-it-works"]],"Input configuration":[[37,"input-configuration"]],"Inspect the benchmark data":[[44,null]],"Installation":[[33,"installation"],[35,null]],"Interpret and improve the results":[[45,null]],"Introduction":[[36,null]],"MPI acceleration":[[40,"mpi-acceleration"]],"Minimum dissolved concentration":[[37,"minimum-dissolved-concentration"]],"Minimum gas saturation":[[37,"minimum-gas-saturation"]],"Module contents":[[2,"module-pofff"],[3,"module-pofff.config"],[5,"module-pofff.core"],[7,"module-pofff.jobs"],[11,"module-pofff.utils"],[17,"module-pofff.visualization"]],"Next":[[43,"next"],[44,"next"],[45,"next"],[46,"next"],[48,"next"]],"Next steps":[[35,"next-steps"]],"OPM Flow":[[35,"opm-flow"]],"Output directory":[[37,"output-directory"]],"Output folder":[[39,null]],"Outputs":[[32,"outputs"]],"Overview":[[31,"overview"],[32,"overview"],[33,"overview"]],"Precomputed Wasserstein distances":[[37,"precomputed-wasserstein-distances"]],"Prepare the configuration":[[46,null]],"Prerequisites":[[40,"prerequisites"],[42,"prerequisites"]],"Python package":[[35,"python-package"]],"Quick installation":[[34,"quick-installation"]],"Quick start":[[34,"quick-start"]],"Related projects":[[41,null]],"Report issues or problems":[[29,"report-issues-or-problems"]],"Reproduce all publication results":[[40,"reproduce-all-publication-results"]],"Reproduce the example":[[31,"reproduce-the-example"],[33,"reproduce-the-example"]],"Reproduce the tutorial":[[47,null]],"Reproducibility links":[[47,"reproducibility-links"]],"Reproducing the publication":[[40,null]],"Required keys":[[25,"required-keys"]],"Result":[[31,"result"],[33,"result"],[43,"result"],[44,"result"],[45,"result"],[46,"result"],[47,"result"],[48,"result"]],"Run the simulation":[[48,null]],"Seek support":[[29,"seek-support"]],"Sensitivity to setup choices":[[40,"sensitivity-to-setup-choices"]],"Separate generation, execution, and postprocessing":[[32,"separate-generation-execution-and-postprocessing"]],"Simulation setup":[[27,null]],"Single run":[[39,"single-run"]],"Source build in Linux/Windows":[[35,"source-build-in-linux-windows"]],"Source build in macOS":[[35,"source-build-in-macos"]],"Submodules":[[3,"submodules"],[5,"submodules"],[7,"submodules"],[11,"submodules"],[17,"submodules"]],"Subpackages":[[2,"subpackages"]],"Table 7 and Figure 12: profiling":[[40,"table-7-and-figure-12-profiling"]],"Table 9: accuracy-time trade-off":[[40,"table-9-accuracy-time-trade-off"]],"Tutorial":[[42,null]],"Tutorial sequence":[[42,"tutorial-sequence"]],"Using submitted SPE11A CSV data":[[40,"using-submitted-spe11a-csv-data"]],"Verification":[[35,"verification"]],"Visualization with plopm":[[33,null]],"What can pofff do?":[[34,"what-can-pofff-do"]],"Where to continue":[[36,"where-to-continue"],[47,"where-to-continue"]],"flow":[[27,"flow"]],"grid":[[27,"grid"]],"krw, krn, and cap":[[25,"krw-krn-and-cap"]],"pofff":[[1,null],[34,null]],"pofff Python API":[[0,null]],"pofff package":[[2,null]],"pofff.config package":[[3,null]],"pofff.config.config module":[[4,null]],"pofff.core package":[[5,null]],"pofff.core.pofff module":[[6,null]],"pofff.jobs package":[[7,null]],"pofff.jobs.data module":[[8,null]],"pofff.jobs.delete module":[[9,null]],"pofff.jobs.metric module":[[10,null]],"pofff.utils package":[[11,null]],"pofff.utils.inputvalues module":[[12,null]],"pofff.utils.mapproperties module":[[13,null]],"pofff.utils.runs module":[[14,null]],"pofff.utils.terminal module":[[15,null]],"pofff.utils.writefile module":[[16,null]],"pofff.visualization package":[[17,null]],"pofff.visualization.benchmark module":[[18,null]],"pofff.visualization.error_table module":[[19,null]],"pofff.visualization.everert module":[[20,null]],"pofff.visualization.maps module":[[21,null]],"pofff.visualization.sparse_values module":[[22,null]],"sources and inj":[[27,"sources-and-inj"]],"temperature, pressure, and diffusion":[[27,"temperature-pressure-and-diffusion"]],"thickness and mult_thickness":[[27,"thickness-and-mult-thickness"]],"x and z":[[27,"x-and-z"]]},"docnames":["api","api/modules","api/pofff","api/pofff.config","api/pofff.config.config","api/pofff.core","api/pofff.core.pofff","api/pofff.jobs","api/pofff.jobs.data","api/pofff.jobs.delete","api/pofff.jobs.metric","api/pofff.utils","api/pofff.utils.inputvalues","api/pofff.utils.mapproperties","api/pofff.utils.runs","api/pofff.utils.terminal","api/pofff.utils.writefile","api/pofff.visualization","api/pofff.visualization.benchmark","api/pofff.visualization.error_table","api/pofff.visualization.everert","api/pofff.visualization.maps","api/pofff.visualization.sparse_values","command-line","configuration/complete","configuration/facies","configuration/history-matching","configuration/simulation","configuration_file","contributing","examples","examples/hello-world","examples/history-matching","examples/visualization","index","installation","introduction","options/general","options/modes","output_folder","publication","related","tutorial","tutorial/generate-comparisons","tutorial/inspect-data","tutorial/interpret-results","tutorial/prepare-configuration","tutorial/reproduce","tutorial/run-simulation"],"envversion":{"sphinx":66,"sphinx.domains.c":3,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":9,"sphinx.domains.index":1,"sphinx.domains.javascript":3,"sphinx.domains.math":2,"sphinx.domains.python":4,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.intersphinx":1,"sphinx.ext.viewcode":1},"filenames":["api.rst","api/modules.rst","api/pofff.rst","api/pofff.config.rst","api/pofff.config.config.rst","api/pofff.core.rst","api/pofff.core.pofff.rst","api/pofff.jobs.rst","api/pofff.jobs.data.rst","api/pofff.jobs.delete.rst","api/pofff.jobs.metric.rst","api/pofff.utils.rst","api/pofff.utils.inputvalues.rst","api/pofff.utils.mapproperties.rst","api/pofff.utils.runs.rst","api/pofff.utils.terminal.rst","api/pofff.utils.writefile.rst","api/pofff.visualization.rst","api/pofff.visualization.benchmark.rst","api/pofff.visualization.error_table.rst","api/pofff.visualization.everert.rst","api/pofff.visualization.maps.rst","api/pofff.visualization.sparse_values.rst","command-line.rst","configuration/complete.rst","configuration/facies.rst","configuration/history-matching.rst","configuration/simulation.rst","configuration_file.rst","contributing.rst","examples.rst","examples/hello-world.rst","examples/history-matching.rst","examples/visualization.rst","index.rst","installation.rst","introduction.rst","options/general.rst","options/modes.rst","output_folder.rst","publication.rst","related.rst","tutorial.rst","tutorial/generate-comparisons.rst","tutorial/inspect-data.rst","tutorial/interpret-results.rst","tutorial/prepare-configuration.rst","tutorial/reproduce.rst","tutorial/run-simulation.rst"],"indexentries":{"--experiment":[[37,"cmdoption-pofff-e",false]],"--figures":[[37,"cmdoption-pofff-f",false]],"--input":[[37,"cmdoption-pofff-i",false]],"--minimumconcentration":[[37,"cmdoption-pofff-c",false]],"--minimumsaturation":[[37,"cmdoption-pofff-s",false]],"--mode":[[37,"cmdoption-pofff-m",false]],"--output":[[37,"cmdoption-pofff-o",false]],"--times":[[37,"cmdoption-pofff-t",false]],"--use":[[37,"cmdoption-pofff-u",false]],"-c":[[37,"cmdoption-pofff-c",false]],"-e":[[37,"cmdoption-pofff-e",false]],"-f":[[37,"cmdoption-pofff-f",false]],"-i":[[37,"cmdoption-pofff-i",false]],"-m":[[37,"cmdoption-pofff-m",false]],"-o":[[37,"cmdoption-pofff-o",false]],"-s":[[37,"cmdoption-pofff-s",false]],"-t":[[37,"cmdoption-pofff-t",false]],"-u":[[37,"cmdoption-pofff-u",false]],"_add_validation_error() (in module pofff.utils.inputvalues)":[[12,"pofff.utils.inputvalues._add_validation_error",false]],"_assign_box_region() (in module pofff.utils.mapproperties)":[[13,"pofff.utils.mapproperties._assign_box_region",false]],"_assign_corner_point_properties() (in module pofff.utils.mapproperties)":[[13,"pofff.utils.mapproperties._assign_corner_point_properties",false]],"_assign_spatial_properties() (in module pofff.utils.mapproperties)":[[13,"pofff.utils.mapproperties._assign_spatial_properties",false]],"_assign_structured_grid_properties() (in module pofff.utils.mapproperties)":[[13,"pofff.utils.mapproperties._assign_structured_grid_properties",false]],"_build_cli_config() (in module pofff.core.pofff)":[[6,"pofff.core.pofff._build_cli_config",false]],"_build_corner_point_coordinates() (in module pofff.utils.mapproperties)":[[13,"pofff.utils.mapproperties._build_corner_point_coordinates",false]],"_calculate_wasserstein_distance() (in module pofff.jobs.metric)":[[10,"pofff.jobs.metric._calculate_wasserstein_distance",false]],"_classify_reporting_boxes() (in module pofff.utils.mapproperties)":[[13,"pofff.utils.mapproperties._classify_reporting_boxes",false]],"_colorize() (in module pofff.utils.terminal)":[[15,"pofff.utils.terminal._colorize",false]],"_compact_eclipse_values() (in module pofff.utils.writefile)":[[16,"pofff.utils.writefile._compact_eclipse_values",false]],"_compute_mass_center_metric() (in module pofff.jobs.data)":[[8,"pofff.jobs.data._compute_mass_center_metric",false]],"_configure_matplotlib() (in module pofff.visualization.everert)":[[20,"pofff.visualization.everert._configure_matplotlib",false]],"_copy_tree_contents() (in module pofff.visualization.everert)":[[20,"pofff.visualization.everert._copy_tree_contents",false]],"_effective_workflow() (in module pofff.utils.inputvalues)":[[12,"pofff.utils.inputvalues._effective_workflow",false]],"_ensure_directory() (in module pofff.visualization.everert)":[[20,"pofff.visualization.everert._ensure_directory",false]],"_extract_best_simulation() (in module pofff.visualization.everert)":[[20,"pofff.visualization.everert._extract_best_simulation",false]],"_extract_optimal_solution() (in module pofff.visualization.everert)":[[20,"pofff.visualization.everert._extract_optimal_solution",false]],"_extract_summary_quantities() (in module pofff.jobs.data)":[[8,"pofff.jobs.data._extract_summary_quantities",false]],"_find_simulation_base() (in module pofff.jobs.data)":[[8,"pofff.jobs.data._find_simulation_base",false]],"_generate_benchmark_comparisons() (in module pofff.visualization.benchmark)":[[18,"pofff.visualization.benchmark._generate_benchmark_comparisons",false]],"_generate_figures() (in module pofff.core.pofff)":[[6,"pofff.core.pofff._generate_figures",false]],"_get_initial_water_pressure() (in module pofff.jobs.data)":[[8,"pofff.jobs.data._get_initial_water_pressure",false]],"_initialize_ensemble() (in module pofff.visualization.everert)":[[20,"pofff.visualization.everert._initialize_ensemble",false]],"_initialize_sparse_results() (in module pofff.jobs.data)":[[8,"pofff.jobs.data._initialize_sparse_results",false]],"_interpolate_sparse_quantities() (in module pofff.jobs.data)":[[8,"pofff.jobs.data._interpolate_sparse_quantities",false]],"_is_finite_number() (in module pofff.utils.inputvalues)":[[12,"pofff.utils.inputvalues._is_finite_number",false]],"_is_integer() (in module pofff.utils.inputvalues)":[[12,"pofff.utils.inputvalues._is_integer",false]],"_is_tuning_enabled() (in module pofff.utils.inputvalues)":[[12,"pofff.utils.inputvalues._is_tuning_enabled",false]],"_load_geological_lines() (in module pofff.visualization.maps)":[[21,"pofff.visualization.maps._load_geological_lines",false]],"_load_sparse_csv() (in module pofff.visualization.error_table)":[[19,"pofff.visualization.error_table._load_sparse_csv",false]],"_load_thickness_map() (in module pofff.utils.mapproperties)":[[13,"pofff.utils.mapproperties._load_thickness_map",false]],"_locate_corner_point_sensors() (in module pofff.utils.mapproperties)":[[13,"pofff.utils.mapproperties._locate_corner_point_sensors",false]],"_locate_corner_point_sources() (in module pofff.utils.mapproperties)":[[13,"pofff.utils.mapproperties._locate_corner_point_sources",false]],"_locate_structured_sensors() (in module pofff.utils.mapproperties)":[[13,"pofff.utils.mapproperties._locate_structured_sensors",false]],"_locate_structured_sources() (in module pofff.utils.mapproperties)":[[13,"pofff.utils.mapproperties._locate_structured_sources",false]],"_parse_arguments() (in module pofff.core.pofff)":[[6,"pofff.core.pofff._parse_arguments",false]],"_parse_arguments() (in module pofff.visualization.benchmark)":[[18,"pofff.visualization.benchmark._parse_arguments",false]],"_parse_arguments() (in module pofff.visualization.error_table)":[[19,"pofff.visualization.error_table._parse_arguments",false]],"_parse_arguments() (in module pofff.visualization.everert)":[[20,"pofff.visualization.everert._parse_arguments",false]],"_plot_cumulative_misfit() (in module pofff.visualization.everert)":[[20,"pofff.visualization.everert._plot_cumulative_misfit",false]],"_plot_history_matching_mismatch() (in module pofff.visualization.everert)":[[20,"pofff.visualization.everert._plot_history_matching_mismatch",false]],"_plot_misfit() (in module pofff.visualization.everert)":[[20,"pofff.visualization.everert._plot_misfit",false]],"_plot_observable_distribution() (in module pofff.visualization.everert)":[[20,"pofff.visualization.everert._plot_observable_distribution",false]],"_plot_optimization() (in module pofff.visualization.everert)":[[20,"pofff.visualization.everert._plot_optimization",false]],"_plot_optimization_details() (in module pofff.visualization.everert)":[[20,"pofff.visualization.everert._plot_optimization_details",false]],"_plot_parameter_distributions() (in module pofff.visualization.everert)":[[20,"pofff.visualization.everert._plot_parameter_distributions",false]],"_plot_simulation_ensemble() (in module pofff.visualization.everert)":[[20,"pofff.visualization.everert._plot_simulation_ensemble",false]],"_prepare_distance_images() (in module pofff.jobs.metric)":[[10,"pofff.jobs.metric._prepare_distance_images",false]],"_prepare_jobs_folder() (in module pofff.core.pofff)":[[6,"pofff.core.pofff._prepare_jobs_folder",false]],"_prepare_simulation() (in module pofff.core.pofff)":[[6,"pofff.core.pofff._prepare_simulation",false]],"_process_optimization() (in module pofff.visualization.everert)":[[20,"pofff.visualization.everert._process_optimization",false]],"_read_geological_polygons() (in module pofff.utils.mapproperties)":[[13,"pofff.utils.mapproperties._read_geological_polygons",false]],"_read_horizon_lines() (in module pofff.utils.mapproperties)":[[13,"pofff.utils.mapproperties._read_horizon_lines",false]],"_read_opm_results() (in module pofff.jobs.data)":[[8,"pofff.jobs.data._read_opm_results",false]],"_read_realisation() (in module pofff.visualization.everert)":[[20,"pofff.visualization.everert._read_realisation",false]],"_refine_vertical_coordinates() (in module pofff.utils.mapproperties)":[[13,"pofff.utils.mapproperties._refine_vertical_coordinates",false]],"_render_boundary_properties_script() (in module pofff.utils.writefile)":[[16,"pofff.utils.writefile._render_boundary_properties_script",false]],"_render_copy_script() (in module pofff.utils.writefile)":[[16,"pofff.utils.writefile._render_copy_script",false]],"_render_ert_configuration() (in module pofff.utils.writefile)":[[16,"pofff.utils.writefile._render_ert_configuration",false]],"_render_everest_configuration() (in module pofff.utils.writefile)":[[16,"pofff.utils.writefile._render_everest_configuration",false]],"_render_flow_script() (in module pofff.utils.writefile)":[[16,"pofff.utils.writefile._render_flow_script",false]],"_render_monotonic_script() (in module pofff.utils.writefile)":[[16,"pofff.utils.writefile._render_monotonic_script",false]],"_render_region_properties_script() (in module pofff.utils.writefile)":[[16,"pofff.utils.writefile._render_region_properties_script",false]],"_render_saturation_functions_script() (in module pofff.utils.writefile)":[[16,"pofff.utils.writefile._render_saturation_functions_script",false]],"_render_scale_script() (in module pofff.utils.writefile)":[[16,"pofff.utils.writefile._render_scale_script",false]],"_run() (in module pofff.utils.runs)":[[14,"pofff.utils.runs._run",false]],"_run_command() (in module pofff.visualization.everert)":[[20,"pofff.visualization.everert._run_command",false]],"_run_simulation_steps() (in module pofff.core.pofff)":[[6,"pofff.core.pofff._run_simulation_steps",false]],"_save_figure() (in module pofff.visualization.everert)":[[20,"pofff.visualization.everert._save_figure",false]],"_segment_simulation_fields() (in module pofff.visualization.maps)":[[21,"pofff.visualization.maps._segment_simulation_fields",false]],"_segment_spatial_map() (in module pofff.jobs.metric)":[[10,"pofff.jobs.metric._segment_spatial_map",false]],"_sum_summary_vectors() (in module pofff.jobs.data)":[[8,"pofff.jobs.data._sum_summary_vectors",false]],"_supports_color() (in module pofff.utils.terminal)":[[15,"pofff.utils.terminal._supports_color",false]],"_tensor() (in module pofff.utils.mapproperties)":[[13,"pofff.utils.mapproperties._tensor",false]],"_validate_arguments() (in module pofff.core.pofff)":[[6,"pofff.core.pofff._validate_arguments",false]],"_validate_array() (in module pofff.utils.inputvalues)":[[12,"pofff.utils.inputvalues._validate_array",false]],"_validate_expression() (in module pofff.utils.inputvalues)":[[12,"pofff.utils.inputvalues._validate_expression",false]],"_validate_facies() (in module pofff.utils.inputvalues)":[[12,"pofff.utils.inputvalues._validate_facies",false]],"_validate_history_parameter() (in module pofff.utils.inputvalues)":[[12,"pofff.utils.inputvalues._validate_history_parameter",false]],"_validate_injection_schedule() (in module pofff.utils.inputvalues)":[[12,"pofff.utils.inputvalues._validate_injection_schedule",false]],"_validate_matrix() (in module pofff.utils.inputvalues)":[[12,"pofff.utils.inputvalues._validate_matrix",false]],"_validate_number() (in module pofff.utils.inputvalues)":[[12,"pofff.utils.inputvalues._validate_number",false]],"_validate_string() (in module pofff.utils.inputvalues)":[[12,"pofff.utils.inputvalues._validate_string",false]],"_validate_thickness() (in module pofff.utils.inputvalues)":[[12,"pofff.utils.inputvalues._validate_thickness",false]],"_validate_toml() (in module pofff.utils.inputvalues)":[[12,"pofff.utils.inputvalues._validate_toml",false]],"_warn_and_remove() (in module pofff.utils.inputvalues)":[[12,"pofff.utils.inputvalues._warn_and_remove",false]],"_write_cell_map() (in module pofff.utils.mapproperties)":[[13,"pofff.utils.mapproperties._write_cell_map",false]],"_write_dense_outputs() (in module pofff.jobs.data)":[[8,"pofff.jobs.data._write_dense_outputs",false]],"_write_keyword_files() (in module pofff.utils.writefile)":[[16,"pofff.utils.writefile._write_keyword_files",false]],"_write_overlay_map() (in module pofff.visualization.maps)":[[21,"pofff.visualization.maps._write_overlay_map",false]],"_write_sparse_outputs() (in module pofff.jobs.data)":[[8,"pofff.jobs.data._write_sparse_outputs",false]],"_write_spatial_map_csv() (in module pofff.jobs.data)":[[8,"pofff.jobs.data._write_spatial_map_csv",false]],"_write_time_series_csv() (in module pofff.jobs.data)":[[8,"pofff.jobs.data._write_time_series_csv",false]],"actind (pofff.jobs.data.simulationcontext attribute)":[[8,"pofff.jobs.data.SimulationContext.actind",false]],"args (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.args",false]],"atol (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.atol",false]],"benchmark() (in module pofff.utils.runs)":[[14,"pofff.utils.runs.benchmark",false]],"boxa (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.boxa",false]],"boxb (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.boxb",false]],"boxc (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.boxc",false]],"build_config() (in module pofff.utils.inputvalues)":[[12,"pofff.utils.inputvalues.build_config",false]],"callback (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.callback",false]],"cap (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.cap",false]],"cli_correct_value() (in module pofff.utils.terminal)":[[15,"pofff.utils.terminal.cli_correct_value",false]],"cli_error_value() (in module pofff.utils.terminal)":[[15,"pofff.utils.terminal.cli_error_value",false]],"cli_info_value() (in module pofff.utils.terminal)":[[15,"pofff.utils.terminal.cli_info_value",false]],"cli_warning_value() (in module pofff.utils.terminal)":[[15,"pofff.utils.terminal.cli_warning_value",false]],"cliconfig (class in pofff.config.config)":[[4,"pofff.config.config.CliConfig",false]],"config (class in pofff.visualization.everert)":[[20,"pofff.visualization.everert.Config",false]],"constraints (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.constraints",false]],"cores (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.cores",false]],"create_corner_point_grid() (in module pofff.utils.writefile)":[[16,"pofff.utils.writefile.create_corner_point_grid",false]],"ctx (pofff.jobs.data.sparseresults attribute)":[[8,"pofff.jobs.data.SparseResults.ctx",false]],"cumulative (pofff.visualization.everert.ensemblestate attribute)":[[20,"pofff.visualization.everert.EnsembleState.cumulative",false]],"data (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.data",false]],"data() (in module pofff.utils.runs)":[[14,"pofff.utils.runs.data",false]],"deck (pofff.config.config.cliconfig attribute)":[[4,"pofff.config.config.CliConfig.deck",false]],"deck (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.deck",false]],"delete (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.delete",false]],"dense_t (pofff.jobs.data.simulationcontext attribute)":[[8,"pofff.jobs.data.SimulationContext.dense_t",false]],"diffusion (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.diffusion",false]],"dims (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.dims",false]],"dims (pofff.jobs.data.simulationcontext attribute)":[[8,"pofff.jobs.data.SimulationContext.dims",false]],"disp (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.disp",false]],"dissa (pofff.jobs.data.sparseresults attribute)":[[8,"pofff.jobs.data.SparseResults.dissa",false]],"dissb (pofff.jobs.data.sparseresults attribute)":[[8,"pofff.jobs.data.SparseResults.dissb",false]],"dx (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.dx",false]],"dx (pofff.jobs.data.sparseresults attribute)":[[8,"pofff.jobs.data.SparseResults.dx",false]],"dz (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.dz",false]],"dz (pofff.jobs.data.sparseresults attribute)":[[8,"pofff.jobs.data.SparseResults.dz",false]],"egrid (pofff.jobs.data.simulationcontext attribute)":[[8,"pofff.jobs.data.SimulationContext.egrid",false]],"enkf_alpha (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.enkf_alpha",false]],"ensembles (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.ensembles",false]],"ensemblestate (class in pofff.visualization.everert)":[[20,"pofff.visualization.everert.EnsembleState",false]],"errors (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.errors",false]],"ert() (in module pofff.utils.runs)":[[14,"pofff.utils.runs.ert",false]],"ertargs (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.ertargs",false]],"everert (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.everert",false]],"everest() (in module pofff.utils.runs)":[[14,"pofff.utils.runs.everest",false]],"experiment (pofff.config.config.cliconfig attribute)":[[4,"pofff.config.config.CliConfig.experiment",false]],"experiment (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.experiment",false]],"external (pofff.visualization.everert.config attribute)":[[20,"pofff.visualization.everert.Config.external",false]],"extract_facies() (in module pofff.utils.inputvalues)":[[12,"pofff.utils.inputvalues.extract_facies",false]],"facies (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.facies",false]],"figures (pofff.config.config.cliconfig attribute)":[[4,"pofff.config.config.CliConfig.figures",false]],"figures (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.figures",false]],"fipnum (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.fipnum",false]],"fipnum (pofff.jobs.data.sparseresults attribute)":[[8,"pofff.jobs.data.SparseResults.fipnum",false]],"flow (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.flow",false]],"flow() (in module pofff.utils.runs)":[[14,"pofff.utils.runs.flow",false]],"fluxnum (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.fluxnum",false]],"fol (pofff.config.config.cliconfig attribute)":[[4,"pofff.config.config.CliConfig.fol",false]],"fol (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.fol",false]],"grid (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.grid",false]],"grid_and_properties() (in module pofff.utils.mapproperties)":[[13,"pofff.utils.mapproperties.grid_and_properties",false]],"gxyz (pofff.jobs.data.simulationcontext attribute)":[[8,"pofff.jobs.data.SimulationContext.gxyz",false]],"hascellmaps (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.hascellmaps",false]],"hm (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.hm",false]],"idrealisation (pofff.visualization.everert.ensemblestate attribute)":[[20,"pofff.visualization.everert.EnsembleState.idrealisation",false]],"imma (pofff.jobs.data.sparseresults attribute)":[[8,"pofff.jobs.data.SparseResults.imma",false]],"immb (pofff.jobs.data.sparseresults attribute)":[[8,"pofff.jobs.data.SparseResults.immb",false]],"ind_batch (pofff.visualization.everert.optimizationstate attribute)":[[20,"pofff.visualization.everert.OptimizationState.ind_batch",false]],"ind_sim (pofff.visualization.everert.optimizationstate attribute)":[[20,"pofff.visualization.everert.OptimizationState.ind_sim",false]],"ini (pofff.jobs.data.simulationcontext attribute)":[[8,"pofff.jobs.data.SimulationContext.ini",false]],"init (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.init",false]],"inj (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.inj",false]],"integrality (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.integrality",false]],"jobs (pofff.config.config.cliconfig attribute)":[[4,"pofff.config.config.CliConfig.jobs",false]],"jobs (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.jobs",false]],"jobs (pofff.visualization.everert.config attribute)":[[20,"pofff.visualization.everert.Config.jobs",false]],"krn (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.krn",false]],"krw (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.krw",false]],"load_toml() (in module pofff.utils.inputvalues)":[[12,"pofff.utils.inputvalues.load_toml",false]],"location (pofff.config.config.cliconfig attribute)":[[4,"pofff.config.config.CliConfig.location",false]],"location (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.location",false]],"m_c (pofff.jobs.data.sparseresults attribute)":[[8,"pofff.jobs.data.SparseResults.m_c",false]],"m_c_series (pofff.jobs.data.sparseresults attribute)":[[8,"pofff.jobs.data.SparseResults.m_c_series",false]],"main() (in module pofff.core.pofff)":[[6,"pofff.core.pofff.main",false]],"main() (in module pofff.jobs.data)":[[8,"pofff.jobs.data.main",false]],"main() (in module pofff.jobs.metric)":[[10,"pofff.jobs.metric.main",false]],"main() (in module pofff.visualization.sparse_values)":[[22,"pofff.visualization.sparse_values.main",false]],"maps (pofff.jobs.data.simulationcontext attribute)":[[8,"pofff.jobs.data.SimulationContext.maps",false]],"maps (pofff.visualization.everert.config attribute)":[[20,"pofff.visualization.everert.Config.maps",false]],"max_batch_num (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.max_batch_num",false]],"max_function_evaluations (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.max_function_evaluations",false]],"maxiter (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.maxiter",false]],"maxtime (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.maxtime",false]],"mcon (pofff.config.config.cliconfig attribute)":[[4,"pofff.config.config.CliConfig.mcon",false]],"mcon (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.mcon",false]],"min_concentration (pofff.visualization.everert.config attribute)":[[20,"pofff.visualization.everert.Config.min_concentration",false]],"min_realizations_success (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.min_realizations_success",false]],"min_saturation (pofff.visualization.everert.config attribute)":[[20,"pofff.visualization.everert.Config.min_saturation",false]],"miss_ens (pofff.visualization.everert.ensemblestate attribute)":[[20,"pofff.visualization.everert.EnsembleState.miss_ens",false]],"moba (pofff.jobs.data.sparseresults attribute)":[[8,"pofff.jobs.data.SparseResults.moba",false]],"mobb (pofff.jobs.data.sparseresults attribute)":[[8,"pofff.jobs.data.SparseResults.mobb",false]],"mode (pofff.config.config.cliconfig attribute)":[[4,"pofff.config.config.CliConfig.mode",false]],"mode (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.mode",false]],"module":[[2,"module-pofff",false],[3,"module-pofff.config",false],[4,"module-pofff.config.config",false],[5,"module-pofff.core",false],[6,"module-pofff.core.pofff",false],[7,"module-pofff.jobs",false],[8,"module-pofff.jobs.data",false],[9,"module-pofff.jobs.delete",false],[10,"module-pofff.jobs.metric",false],[11,"module-pofff.utils",false],[12,"module-pofff.utils.inputvalues",false],[13,"module-pofff.utils.mapproperties",false],[14,"module-pofff.utils.runs",false],[15,"module-pofff.utils.terminal",false],[16,"module-pofff.utils.writefile",false],[17,"module-pofff.visualization",false],[18,"module-pofff.visualization.benchmark",false],[19,"module-pofff.visualization.error_table",false],[20,"module-pofff.visualization.everert",false],[21,"module-pofff.visualization.maps",false],[22,"module-pofff.visualization.sparse_values",false]],"monotonic (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.monotonic",false]],"msat (pofff.config.config.cliconfig attribute)":[[4,"pofff.config.config.CliConfig.msat",false]],"msat (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.msat",false]],"mult_thickness (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.mult_thickness",false]],"multpv (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.multpv",false]],"mutation (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.mutation",false]],"n_e (pofff.visualization.everert.ensemblestate attribute)":[[20,"pofff.visualization.everert.EnsembleState.n_e",false]],"n_i (pofff.visualization.everert.ensemblestate attribute)":[[20,"pofff.visualization.everert.EnsembleState.n_i",false]],"no_obs (pofff.visualization.everert.ensemblestate attribute)":[[20,"pofff.visualization.everert.EnsembleState.no_obs",false]],"no_para (pofff.visualization.everert.ensemblestate attribute)":[[20,"pofff.visualization.everert.EnsembleState.no_para",false]],"norst (pofff.jobs.data.simulationcontext attribute)":[[8,"pofff.jobs.data.SimulationContext.norst",false]],"num_ens (pofff.visualization.everert.ensemblestate attribute)":[[20,"pofff.visualization.everert.EnsembleState.num_ens",false]],"nxz (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.nxz",false]],"observations (pofff.visualization.everert.ensemblestate attribute)":[[20,"pofff.visualization.everert.EnsembleState.observations",false]],"optimal_value (pofff.visualization.everert.optimizationstate attribute)":[[20,"pofff.visualization.everert.OptimizationState.optimal_value",false]],"optimization (pofff.visualization.everert.optimizationstate attribute)":[[20,"pofff.visualization.everert.OptimizationState.optimization",false]],"optimizationstate (class in pofff.visualization.everert)":[[20,"pofff.visualization.everert.OptimizationState",false]],"par_dis (pofff.visualization.everert.ensemblestate attribute)":[[20,"pofff.visualization.everert.EnsembleState.par_dis",false]],"para (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.para",false]],"para_file (pofff.visualization.everert.ensemblestate attribute)":[[20,"pofff.visualization.everert.EnsembleState.para_file",false]],"para_names (pofff.visualization.everert.ensemblestate attribute)":[[20,"pofff.visualization.everert.EnsembleState.para_names",false]],"path (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.path",false]],"path (pofff.visualization.everert.config attribute)":[[20,"pofff.visualization.everert.Config.path",false]],"pofff":[[2,"module-pofff",false]],"pofff command line option":[[37,"cmdoption-pofff-c",false],[37,"cmdoption-pofff-e",false],[37,"cmdoption-pofff-f",false],[37,"cmdoption-pofff-i",false],[37,"cmdoption-pofff-m",false],[37,"cmdoption-pofff-o",false],[37,"cmdoption-pofff-s",false],[37,"cmdoption-pofff-t",false],[37,"cmdoption-pofff-u",false]],"pofff.config":[[3,"module-pofff.config",false]],"pofff.config.config":[[4,"module-pofff.config.config",false]],"pofff.core":[[5,"module-pofff.core",false]],"pofff.core.pofff":[[6,"module-pofff.core.pofff",false]],"pofff.jobs":[[7,"module-pofff.jobs",false]],"pofff.jobs.data":[[8,"module-pofff.jobs.data",false]],"pofff.jobs.delete":[[9,"module-pofff.jobs.delete",false]],"pofff.jobs.metric":[[10,"module-pofff.jobs.metric",false]],"pofff.utils":[[11,"module-pofff.utils",false]],"pofff.utils.inputvalues":[[12,"module-pofff.utils.inputvalues",false]],"pofff.utils.mapproperties":[[13,"module-pofff.utils.mapproperties",false]],"pofff.utils.runs":[[14,"module-pofff.utils.runs",false]],"pofff.utils.terminal":[[15,"module-pofff.utils.terminal",false]],"pofff.utils.writefile":[[16,"module-pofff.utils.writefile",false]],"pofff.visualization":[[17,"module-pofff.visualization",false]],"pofff.visualization.benchmark":[[18,"module-pofff.visualization.benchmark",false]],"pofff.visualization.error_table":[[19,"module-pofff.visualization.error_table",false]],"pofff.visualization.everert":[[20,"module-pofff.visualization.everert",false]],"pofff.visualization.maps":[[21,"module-pofff.visualization.maps",false]],"pofff.visualization.sparse_values":[[22,"module-pofff.visualization.sparse_values",false]],"pofff_error() (in module pofff.utils.terminal)":[[15,"pofff.utils.terminal.pofff_error",false]],"pofff_info() (in module pofff.utils.terminal)":[[15,"pofff.utils.terminal.pofff_info",false]],"pofff_name() (in module pofff.utils.terminal)":[[15,"pofff.utils.terminal.pofff_name",false]],"pofff_success() (in module pofff.utils.terminal)":[[15,"pofff.utils.terminal.pofff_success",false]],"pofff_tip() (in module pofff.utils.terminal)":[[15,"pofff.utils.terminal.pofff_tip",false]],"pofff_warning() (in module pofff.utils.terminal)":[[15,"pofff.utils.terminal.pofff_warning",false]],"pofffconfig (class in pofff.config.config)":[[4,"pofff.config.config.PofffConfig",false]],"polish (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.polish",false]],"pop1 (pofff.jobs.data.sparseresults attribute)":[[8,"pofff.jobs.data.SparseResults.pop1",false]],"pop2 (pofff.jobs.data.sparseresults attribute)":[[8,"pofff.jobs.data.SparseResults.pop2",false]],"popsize (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.popsize",false]],"porv (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.porv",false]],"porv (pofff.jobs.data.simulationcontext attribute)":[[8,"pofff.jobs.data.SimulationContext.porv",false]],"postprocess() (in module pofff.utils.runs)":[[14,"pofff.utils.runs.postprocess",false]],"postprocess_config() (in module pofff.utils.inputvalues)":[[12,"pofff.utils.inputvalues.postprocess_config",false]],"pressure (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.pressure",false]],"process_tuning() (in module pofff.utils.inputvalues)":[[12,"pofff.utils.inputvalues.process_tuning",false]],"random_seed (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.random_seed",false]],"recombination (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.recombination",false]],"resolution (pofff.jobs.data.simulationcontext attribute)":[[8,"pofff.jobs.data.SimulationContext.resolution",false]],"rng (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.rng",false]],"run() (in module pofff.visualization.benchmark)":[[18,"pofff.visualization.benchmark.run",false]],"run_benchmark() (in module pofff.visualization.benchmark)":[[18,"pofff.visualization.benchmark.run_benchmark",false]],"run_command (pofff.visualization.everert.config attribute)":[[20,"pofff.visualization.everert.Config.run_command",false]],"run_error_table() (in module pofff.visualization.error_table)":[[19,"pofff.visualization.error_table.run_error_table",false]],"run_everert() (in module pofff.visualization.everert)":[[20,"pofff.visualization.everert.run_everert",false]],"run_maps() (in module pofff.visualization.maps)":[[21,"pofff.visualization.maps.run_maps",false]],"s (pofff.visualization.everert.optimizationstate attribute)":[[20,"pofff.visualization.everert.OptimizationState.s",false]],"seala (pofff.jobs.data.sparseresults attribute)":[[8,"pofff.jobs.data.SparseResults.seala",false]],"sealb (pofff.jobs.data.sparseresults attribute)":[[8,"pofff.jobs.data.SparseResults.sealb",false]],"sealt (pofff.jobs.data.sparseresults attribute)":[[8,"pofff.jobs.data.SparseResults.sealt",false]],"sensor_ik (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.sensor_ik",false]],"sensors (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.sensors",false]],"sim (pofff.jobs.data.simulationcontext attribute)":[[8,"pofff.jobs.data.SimulationContext.sim",false]],"sim_ens (pofff.visualization.everert.ensemblestate attribute)":[[20,"pofff.visualization.everert.EnsembleState.sim_ens",false]],"simulationcontext (class in pofff.jobs.data)":[[8,"pofff.jobs.data.SimulationContext",false]],"simulations (pofff.visualization.everert.ensemblestate attribute)":[[20,"pofff.visualization.everert.EnsembleState.simulations",false]],"smspec (pofff.jobs.data.simulationcontext attribute)":[[8,"pofff.jobs.data.SimulationContext.smspec",false]],"source_ik (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.source_ik",false]],"sources (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.sources",false]],"sparse_t (pofff.jobs.data.simulationcontext attribute)":[[8,"pofff.jobs.data.SimulationContext.sparse_t",false]],"sparseresults (class in pofff.jobs.data)":[[8,"pofff.jobs.data.SparseResults",false]],"strategy (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.strategy",false]],"temperature (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.temperature",false]],"thickness (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.thickness",false]],"time_initial (pofff.jobs.data.simulationcontext attribute)":[[8,"pofff.jobs.data.SimulationContext.time_initial",false]],"times (pofff.config.config.cliconfig attribute)":[[4,"pofff.config.config.CliConfig.times",false]],"times (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.times",false]],"times (pofff.jobs.data.simulationcontext attribute)":[[8,"pofff.jobs.data.SimulationContext.times",false]],"times (pofff.visualization.everert.config attribute)":[[20,"pofff.visualization.everert.Config.times",false]],"times_data (pofff.jobs.data.sparseresults attribute)":[[8,"pofff.jobs.data.SparseResults.times_data",false]],"times_summary (pofff.jobs.data.simulationcontext attribute)":[[8,"pofff.jobs.data.SimulationContext.times_summary",false]],"tol (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.tol",false]],"tot_eval (pofff.visualization.everert.optimizationstate attribute)":[[20,"pofff.visualization.everert.OptimizationState.tot_eval",false]],"tuning (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.tuning",false]],"unrst (pofff.jobs.data.simulationcontext attribute)":[[8,"pofff.jobs.data.SimulationContext.unrst",false]],"updating (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.updating",false]],"use (pofff.config.config.cliconfig attribute)":[[4,"pofff.config.config.CliConfig.use",false]],"use (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.use",false]],"vectorized (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.vectorized",false]],"where (pofff.jobs.data.simulationcontext attribute)":[[8,"pofff.jobs.data.SimulationContext.where",false]],"workers (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.workers",false]],"write_files() (in module pofff.utils.writefile)":[[16,"pofff.utils.writefile.write_files",false]],"x (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.x",false]],"x (pofff.visualization.everert.optimizationstate attribute)":[[20,"pofff.visualization.everert.OptimizationState.x",false]],"x0 (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.x0",false]],"z (pofff.config.config.pofffconfig attribute)":[[4,"pofff.config.config.PofffConfig.z",false]]},"objects":{"":[[2,0,0,"-","pofff"]],"pofff":[[3,0,0,"-","config"],[5,0,0,"-","core"],[7,0,0,"-","jobs"],[11,0,0,"-","utils"],[17,0,0,"-","visualization"],[37,4,1,"cmdoption-pofff-e","--experiment"],[37,4,1,"cmdoption-pofff-f","--figures"],[37,4,1,"cmdoption-pofff-i","--input"],[37,4,1,"cmdoption-pofff-c","--minimumconcentration"],[37,4,1,"cmdoption-pofff-s","--minimumsaturation"],[37,4,1,"cmdoption-pofff-m","--mode"],[37,4,1,"cmdoption-pofff-o","--output"],[37,4,1,"cmdoption-pofff-t","--times"],[37,4,1,"cmdoption-pofff-u","--use"],[37,4,1,"cmdoption-pofff-c","-c"],[37,4,1,"cmdoption-pofff-e","-e"],[37,4,1,"cmdoption-pofff-f","-f"],[37,4,1,"cmdoption-pofff-i","-i"],[37,4,1,"cmdoption-pofff-m","-m"],[37,4,1,"cmdoption-pofff-o","-o"],[37,4,1,"cmdoption-pofff-s","-s"],[37,4,1,"cmdoption-pofff-t","-t"],[37,4,1,"cmdoption-pofff-u","-u"]],"pofff.config":[[4,0,0,"-","config"]],"pofff.config.config":[[4,1,1,"","CliConfig"],[4,1,1,"","PofffConfig"]],"pofff.config.config.CliConfig":[[4,2,1,"","deck"],[4,2,1,"","experiment"],[4,2,1,"","figures"],[4,2,1,"","fol"],[4,2,1,"","jobs"],[4,2,1,"","location"],[4,2,1,"","mcon"],[4,2,1,"","mode"],[4,2,1,"","msat"],[4,2,1,"","times"],[4,2,1,"","use"]],"pofff.config.config.PofffConfig":[[4,2,1,"","args"],[4,2,1,"","atol"],[4,2,1,"","boxa"],[4,2,1,"","boxb"],[4,2,1,"","boxc"],[4,2,1,"","callback"],[4,2,1,"","cap"],[4,2,1,"","constraints"],[4,2,1,"","cores"],[4,2,1,"","data"],[4,2,1,"","deck"],[4,2,1,"","delete"],[4,2,1,"","diffusion"],[4,2,1,"","dims"],[4,2,1,"","disp"],[4,2,1,"","dx"],[4,2,1,"","dz"],[4,2,1,"","enkf_alpha"],[4,2,1,"","ensembles"],[4,2,1,"","errors"],[4,2,1,"","ertargs"],[4,2,1,"","everert"],[4,2,1,"","experiment"],[4,2,1,"","facies"],[4,2,1,"","figures"],[4,2,1,"","fipnum"],[4,2,1,"","flow"],[4,2,1,"","fluxnum"],[4,2,1,"","fol"],[4,2,1,"","grid"],[4,2,1,"","hascellmaps"],[4,2,1,"","hm"],[4,2,1,"","init"],[4,2,1,"","inj"],[4,2,1,"","integrality"],[4,2,1,"","jobs"],[4,2,1,"","krn"],[4,2,1,"","krw"],[4,2,1,"","location"],[4,2,1,"","max_batch_num"],[4,2,1,"","max_function_evaluations"],[4,2,1,"","maxiter"],[4,2,1,"","maxtime"],[4,2,1,"","mcon"],[4,2,1,"","min_realizations_success"],[4,2,1,"","mode"],[4,2,1,"","monotonic"],[4,2,1,"","msat"],[4,2,1,"","mult_thickness"],[4,2,1,"","multpv"],[4,2,1,"","mutation"],[4,2,1,"","nxz"],[4,2,1,"","para"],[4,2,1,"","path"],[4,2,1,"","polish"],[4,2,1,"","popsize"],[4,2,1,"","porv"],[4,2,1,"","pressure"],[4,2,1,"","random_seed"],[4,2,1,"","recombination"],[4,2,1,"","rng"],[4,2,1,"","sensor_ik"],[4,2,1,"","sensors"],[4,2,1,"","source_ik"],[4,2,1,"","sources"],[4,2,1,"","strategy"],[4,2,1,"","temperature"],[4,2,1,"","thickness"],[4,2,1,"","times"],[4,2,1,"","tol"],[4,2,1,"","tuning"],[4,2,1,"","updating"],[4,2,1,"","use"],[4,2,1,"","vectorized"],[4,2,1,"","workers"],[4,2,1,"","x"],[4,2,1,"","x0"],[4,2,1,"","z"]],"pofff.core":[[6,0,0,"-","pofff"]],"pofff.core.pofff":[[6,3,1,"","_build_cli_config"],[6,3,1,"","_generate_figures"],[6,3,1,"","_parse_arguments"],[6,3,1,"","_prepare_jobs_folder"],[6,3,1,"","_prepare_simulation"],[6,3,1,"","_run_simulation_steps"],[6,3,1,"","_validate_arguments"],[6,3,1,"","main"]],"pofff.jobs":[[8,0,0,"-","data"],[9,0,0,"-","delete"],[10,0,0,"-","metric"]],"pofff.jobs.data":[[8,1,1,"","SimulationContext"],[8,1,1,"","SparseResults"],[8,3,1,"","_compute_mass_center_metric"],[8,3,1,"","_extract_summary_quantities"],[8,3,1,"","_find_simulation_base"],[8,3,1,"","_get_initial_water_pressure"],[8,3,1,"","_initialize_sparse_results"],[8,3,1,"","_interpolate_sparse_quantities"],[8,3,1,"","_read_opm_results"],[8,3,1,"","_sum_summary_vectors"],[8,3,1,"","_write_dense_outputs"],[8,3,1,"","_write_sparse_outputs"],[8,3,1,"","_write_spatial_map_csv"],[8,3,1,"","_write_time_series_csv"],[8,3,1,"","main"]],"pofff.jobs.data.SimulationContext":[[8,2,1,"","actind"],[8,2,1,"","dense_t"],[8,2,1,"","dims"],[8,2,1,"","egrid"],[8,2,1,"","gxyz"],[8,2,1,"","ini"],[8,2,1,"","maps"],[8,2,1,"","norst"],[8,2,1,"","porv"],[8,2,1,"","resolution"],[8,2,1,"","sim"],[8,2,1,"","smspec"],[8,2,1,"","sparse_t"],[8,2,1,"","time_initial"],[8,2,1,"","times"],[8,2,1,"","times_summary"],[8,2,1,"","unrst"],[8,2,1,"","where"]],"pofff.jobs.data.SparseResults":[[8,2,1,"","ctx"],[8,2,1,"","dissa"],[8,2,1,"","dissb"],[8,2,1,"","dx"],[8,2,1,"","dz"],[8,2,1,"","fipnum"],[8,2,1,"","imma"],[8,2,1,"","immb"],[8,2,1,"","m_c"],[8,2,1,"","m_c_series"],[8,2,1,"","moba"],[8,2,1,"","mobb"],[8,2,1,"","pop1"],[8,2,1,"","pop2"],[8,2,1,"","seala"],[8,2,1,"","sealb"],[8,2,1,"","sealt"],[8,2,1,"","times_data"]],"pofff.jobs.metric":[[10,3,1,"","_calculate_wasserstein_distance"],[10,3,1,"","_prepare_distance_images"],[10,3,1,"","_segment_spatial_map"],[10,3,1,"","main"]],"pofff.utils":[[12,0,0,"-","inputvalues"],[13,0,0,"-","mapproperties"],[14,0,0,"-","runs"],[15,0,0,"-","terminal"],[16,0,0,"-","writefile"]],"pofff.utils.inputvalues":[[12,3,1,"","_add_validation_error"],[12,3,1,"","_effective_workflow"],[12,3,1,"","_is_finite_number"],[12,3,1,"","_is_integer"],[12,3,1,"","_is_tuning_enabled"],[12,3,1,"","_validate_array"],[12,3,1,"","_validate_expression"],[12,3,1,"","_validate_facies"],[12,3,1,"","_validate_history_parameter"],[12,3,1,"","_validate_injection_schedule"],[12,3,1,"","_validate_matrix"],[12,3,1,"","_validate_number"],[12,3,1,"","_validate_string"],[12,3,1,"","_validate_thickness"],[12,3,1,"","_validate_toml"],[12,3,1,"","_warn_and_remove"],[12,3,1,"","build_config"],[12,3,1,"","extract_facies"],[12,3,1,"","load_toml"],[12,3,1,"","postprocess_config"],[12,3,1,"","process_tuning"]],"pofff.utils.mapproperties":[[13,3,1,"","_assign_box_region"],[13,3,1,"","_assign_corner_point_properties"],[13,3,1,"","_assign_spatial_properties"],[13,3,1,"","_assign_structured_grid_properties"],[13,3,1,"","_build_corner_point_coordinates"],[13,3,1,"","_classify_reporting_boxes"],[13,3,1,"","_load_thickness_map"],[13,3,1,"","_locate_corner_point_sensors"],[13,3,1,"","_locate_corner_point_sources"],[13,3,1,"","_locate_structured_sensors"],[13,3,1,"","_locate_structured_sources"],[13,3,1,"","_read_geological_polygons"],[13,3,1,"","_read_horizon_lines"],[13,3,1,"","_refine_vertical_coordinates"],[13,3,1,"","_tensor"],[13,3,1,"","_write_cell_map"],[13,3,1,"","grid_and_properties"]],"pofff.utils.runs":[[14,3,1,"","_run"],[14,3,1,"","benchmark"],[14,3,1,"","data"],[14,3,1,"","ert"],[14,3,1,"","everest"],[14,3,1,"","flow"],[14,3,1,"","postprocess"]],"pofff.utils.terminal":[[15,3,1,"","_colorize"],[15,3,1,"","_supports_color"],[15,3,1,"","cli_correct_value"],[15,3,1,"","cli_error_value"],[15,3,1,"","cli_info_value"],[15,3,1,"","cli_warning_value"],[15,3,1,"","pofff_error"],[15,3,1,"","pofff_info"],[15,3,1,"","pofff_name"],[15,3,1,"","pofff_success"],[15,3,1,"","pofff_tip"],[15,3,1,"","pofff_warning"]],"pofff.utils.writefile":[[16,3,1,"","_compact_eclipse_values"],[16,3,1,"","_render_boundary_properties_script"],[16,3,1,"","_render_copy_script"],[16,3,1,"","_render_ert_configuration"],[16,3,1,"","_render_everest_configuration"],[16,3,1,"","_render_flow_script"],[16,3,1,"","_render_monotonic_script"],[16,3,1,"","_render_region_properties_script"],[16,3,1,"","_render_saturation_functions_script"],[16,3,1,"","_render_scale_script"],[16,3,1,"","_write_keyword_files"],[16,3,1,"","create_corner_point_grid"],[16,3,1,"","write_files"]],"pofff.visualization":[[18,0,0,"-","benchmark"],[19,0,0,"-","error_table"],[20,0,0,"-","everert"],[21,0,0,"-","maps"],[22,0,0,"-","sparse_values"]],"pofff.visualization.benchmark":[[18,3,1,"","_generate_benchmark_comparisons"],[18,3,1,"","_parse_arguments"],[18,3,1,"","run"],[18,3,1,"","run_benchmark"]],"pofff.visualization.error_table":[[19,3,1,"","_load_sparse_csv"],[19,3,1,"","_parse_arguments"],[19,3,1,"","run_error_table"]],"pofff.visualization.everert":[[20,1,1,"","Config"],[20,1,1,"","EnsembleState"],[20,1,1,"","OptimizationState"],[20,3,1,"","_configure_matplotlib"],[20,3,1,"","_copy_tree_contents"],[20,3,1,"","_ensure_directory"],[20,3,1,"","_extract_best_simulation"],[20,3,1,"","_extract_optimal_solution"],[20,3,1,"","_initialize_ensemble"],[20,3,1,"","_parse_arguments"],[20,3,1,"","_plot_cumulative_misfit"],[20,3,1,"","_plot_history_matching_mismatch"],[20,3,1,"","_plot_misfit"],[20,3,1,"","_plot_observable_distribution"],[20,3,1,"","_plot_optimization"],[20,3,1,"","_plot_optimization_details"],[20,3,1,"","_plot_parameter_distributions"],[20,3,1,"","_plot_simulation_ensemble"],[20,3,1,"","_process_optimization"],[20,3,1,"","_read_realisation"],[20,3,1,"","_run_command"],[20,3,1,"","_save_figure"],[20,3,1,"","run_everert"]],"pofff.visualization.everert.Config":[[20,2,1,"","external"],[20,2,1,"","jobs"],[20,2,1,"","maps"],[20,2,1,"","min_concentration"],[20,2,1,"","min_saturation"],[20,2,1,"","path"],[20,2,1,"","run_command"],[20,2,1,"","times"]],"pofff.visualization.everert.EnsembleState":[[20,2,1,"","cumulative"],[20,2,1,"","idrealisation"],[20,2,1,"","miss_ens"],[20,2,1,"","n_e"],[20,2,1,"","n_i"],[20,2,1,"","no_obs"],[20,2,1,"","no_para"],[20,2,1,"","num_ens"],[20,2,1,"","observations"],[20,2,1,"","par_dis"],[20,2,1,"","para_file"],[20,2,1,"","para_names"],[20,2,1,"","sim_ens"],[20,2,1,"","simulations"]],"pofff.visualization.everert.OptimizationState":[[20,2,1,"","ind_batch"],[20,2,1,"","ind_sim"],[20,2,1,"","optimal_value"],[20,2,1,"","optimization"],[20,2,1,"","s"],[20,2,1,"","tot_eval"],[20,2,1,"","x"]],"pofff.visualization.maps":[[21,3,1,"","_load_geological_lines"],[21,3,1,"","_segment_simulation_fields"],[21,3,1,"","_write_overlay_map"],[21,3,1,"","run_maps"]],"pofff.visualization.sparse_values":[[22,3,1,"","main"]]},"objnames":{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","attribute","Python attribute"],"3":["py","function","Python function"],"4":["std","cmdoption","program option"]},"objtypes":{"0":"py:module","1":"py:class","2":"py:attribute","3":"py:function","4":"std:cmdoption"},"terms":{"01_conmin":[40,45],"0f":40,"100e3":24,"1500e3":24,"1cm":40,"1cmish":40,"1e":[4,24,33,37,40],"1mm":40,"2e":[24,40],"2f":40,"3000e3":24,"300e3":24,"3e":[24,33],"50e3":24,"5e":[24,33,37,40],"636b45":40,"7e":40,"800e3":24,"9ca245":40,"9da347":40,"9fa44a":40,"A":[8,20,40],"After":32,"All":13,"An":[29,34],"Both":[10,36],"By":40,"Do":37,"Each":[24,27,40],"For":[24,25,29,35,37,40],"Here":24,"If":[6,8,12,14,20,24,35,40,43,46],"In":[29,43],"It":[16,20,36,42,43,47],"No":[44,46],"The":[0,4,6,8,9,10,12,13,14,15,16,18,19,20,21,22,23,24,28,29,30,31,32,33,35,36,37,39,40,42,43,44,46,47,48],"Their":27,"Then":[35,40],"There":35,"These":[25,30,32,40],"This":[6,12,30,31,37,40,42,43,44],"To":[35,36,40,43],"We":29,"When":[32,46],"While":35,"YOURS":[42,43,44,48],"You":[35,40,44,45,46],"_2":[33,40],"_add_validation_error":[2,11,12],"_assign_box_region":[2,11,13],"_assign_corner_point_properti":[2,11,13],"_assign_spatial_properti":[2,11,13],"_assign_structured_grid_properti":[2,11,13],"_build":29,"_build_cli_config":[2,5,6],"_build_corner_point_coordin":[2,11,13],"_calculate_wasserstein_dist":[2,7,10],"_classify_reporting_box":[2,11,13],"_color":[2,11,15],"_compact_eclipse_valu":[2,11,16],"_compute_mass_center_metr":[2,7,8],"_configure_matplotlib":[2,17,20],"_copy_tree_cont":[2,17,20],"_effective_workflow":[2,11,12],"_ensure_directori":[2,17,20],"_extract_best_simul":[2,17,20],"_extract_optimal_solut":[2,17,20],"_extract_summary_quant":[2,7,8],"_find_simulation_bas":[2,7,8],"_generate_benchmark_comparison":[2,17,18],"_generate_figur":[2,5,6],"_get_initial_water_pressur":[2,7,8],"_initialize_ensembl":[2,17,20],"_initialize_sparse_result":[2,7,8],"_interpolate_sparse_quant":[2,7,8],"_io":15,"_is_finite_numb":[2,11,12],"_is_integ":[2,11,12],"_is_tuning_en":[2,11,12],"_load_geological_lin":[2,17,21],"_load_sparse_csv":[2,17,19],"_load_thickness_map":[2,11,13],"_locate_corner_point_sensor":[2,11,13],"_locate_corner_point_sourc":[2,11,13],"_locate_structured_sensor":[2,11,13],"_locate_structured_sourc":[2,11,13],"_parse_argu":[2,5,6,17,18,19,20],"_plot_cumulative_misfit":[2,17,20],"_plot_history_matching_mismatch":[2,17,20],"_plot_misfit":[2,17,20],"_plot_observable_distribut":[2,17,20],"_plot_optim":[2,17,20],"_plot_optimization_detail":[2,17,20],"_plot_parameter_distribut":[2,17,20],"_plot_simulation_ensembl":[2,17,20],"_prepare_distance_imag":[2,7,10],"_prepare_jobs_fold":[2,5,6],"_prepare_simul":[2,5,6],"_process_optim":[2,17,20],"_read_geological_polygon":[2,11,13],"_read_horizon_lin":[2,11,13],"_read_opm_result":[2,7,8],"_read_realis":[2,17,20],"_refine_vertical_coordin":[2,11,13],"_render_boundary_properties_script":[2,11,16],"_render_copy_script":[2,11,16],"_render_ert_configur":[2,11,16],"_render_everest_configur":[2,11,16],"_render_flow_script":[2,11,16],"_render_monotonic_script":[2,11,16],"_render_region_properties_script":[2,11,16],"_render_saturation_functions_script":[2,11,16],"_render_scale_script":[2,11,16],"_run":[2,11,14],"_run_command":[2,17,20],"_run_simulation_step":[2,5,6],"_save_figur":[2,17,20],"_segment_simulation_field":[2,17,21],"_segment_spatial_map":[2,7,10],"_sum_summary_vector":[2,7,8],"_supports_color":[2,11,15],"_tensor":[2,11,13],"_validate_argu":[2,5,6],"_validate_array":[2,11,12],"_validate_express":[2,11,12],"_validate_faci":[2,11,12],"_validate_history_paramet":[2,11,12],"_validate_injection_schedul":[2,11,12],"_validate_matrix":[2,11,12],"_validate_numb":[2,11,12],"_validate_str":[2,11,12],"_validate_thick":[2,11,12],"_validate_toml":[2,11,12],"_warn_and_remov":[2,11,12],"_write_cell_map":[2,11,13],"_write_dense_output":[2,7,8],"_write_keyword_fil":[2,11,16],"_write_overlay_map":[2,17,21],"_write_sparse_output":[2,7,8],"_write_spatial_map_csv":[2,7,8],"_write_time_series_csv":[2,7,8],"a0a64d":40,"a2a750":40,"a3a953":40,"a5aa56":40,"a6ac59":40,"a8ad5c":40,"a9af5f":40,"abb062":40,"abl":[35,45],"abort":[14,20],"absolut":8,"accept":[12,15,27,28,29,34,36,37,46],"access":40,"accord":[13,16],"accuraci":36,"across":[4,20],"actind":[7,8],"action":[29,35],"activ":[8,35,42],"ad":41,"adapt":40,"adb164":40,"add":[8,12,24,27,29,30,32,34,35,36,39,40,47],"addit":[4,26,29,36,39,40],"adjust":40,"aeb367":40,"agreement":45,"ale":40,"align":47,"alreadi":[42,43,46],"also":[16,29,35,40,42,44,48],"altern":[15,29],"alway":15,"anaconda":35,"analys":29,"analysi":36,"ani":[4,12,13,29],"anim":[30,33],"anoth":[32,35,44],"ansi":15,"answer":29,"api":[36,40],"append":[12,15],"appendixb":[33,40],"appendixc":40,"appli":[4,13,15,20],"applic":[32,34],"approach":29,"appropri":39,"apt":35,"arg":[3,4,6],"argpars":[6,23],"argument":[6,8,10,14,18,19,20,21],"argv":[6,8,10,18,19,20,21],"arrang":[13,16],"array":[4,8,12,13,20,24,27,46],"assign":[13,36,46],"associ":45,"atol":[3,4,26],"attempt":40,"attribut":[4,8,20],"auto":29,"automat":19,"avail":[8,32,34,35,37,40,42,46],"avoid":24,"axi":13,"b":[8,40],"b0b46a":40,"b1b66d":40,"b3b770":40,"b4b973":40,"b6ba76":40,"b7bc79":40,"b9bd7c":40,"babf7f":40,"back":[4,31,32,33],"background":40,"base":[4,8,12,13,20,34,36,40,41],"baselin":45,"basetemp":29,"bashrc":35,"basic":[4,37,43,48],"batch":20,"bcc082":40,"bec184":40,"becaus":4,"befor":[0,4,8,15,29,42,44,45,47],"belong":13,"benchmark":[0,2,4,6,8,10,11,12,13,14,17,19,20,21,22,28,31,32,33,34,36,37,38,40,41,42,43,45,46,47,48],"best":[20,34,39],"best_simul":[32,39],"better":45,"bfc387":40,"bin":[24,29,35],"binari":29,"bind":8,"black":[29,40],"bool":[4,12,15],"boolean":12,"bottom":[13,24,27],"bound":[12,13],"boundari":[4,16,21],"box":[4,8,13],"boxa":[3,4],"boxb":[3,4],"boxc":[3,4],"boxplot":20,"branch":35,"brows":36,"build":[0,6,12,13,24,29],"build_config":[2,11,12],"build_opm_mpi":35,"built":35,"c":[8,24,27,33,37],"c00b0b":40,"c1":37,"c10f0f":40,"c1c48a":40,"c2":37,"c21313":40,"c2c68d":40,"c3":37,"c31717":40,"c4":37,"c41b1b":40,"c4c790":40,"c5":37,"c51f1f":40,"c5c993":40,"c62323":40,"c72626":40,"c7ca96":40,"c82a2a":40,"c8cc99":40,"c92e2e":40,"ca3232":40,"cacd9c":40,"calcul":[8,10,20,48],"callabl":4,"callback":[3,4],"calledprocesserror":[14,20],"can":[29,32,33,35,39,40,42,44],"cap":[3,4,24,46],"capillari":[8,24,25,45],"cartesian":[4,13,24,27,34,36,40],"case":[30,31,34,40,45],"categor":20,"cb3636":40,"cbcf9f":40,"cbf":[33,40],"cbl":[33,40],"cbn":[33,40],"cbp":[33,40],"cbt":40,"cc":40,"cc3a3a":40,"cco2":8,"cd":[35,40],"cd3e3e":40,"cdd0a2":40,"ce4141":40,"cell":[4,8,13,20,21,24],"cellmap":13,"center":[8,36],"centr":[8,13],"centroid":13,"cet_diverging_gwr_55_95_c38":40,"cf4545":40,"cfd":41,"cfd1a4":40,"cfg":[6,12,13,14,16,18,20],"cfg_file":12,"chang":[29,40,44,45],"check":[12,15,16,29,31,35,42,47],"checker":29,"checkout":35,"choic":[37,45,47],"ci":[29,35],"ci_pycopm_maco":29,"class":[4,8,10,20,21,32,36],"classifi":13,"cleanup":4,"cli":[4,6,12,15,36,47],"cli_correct_valu":[2,11,15],"cli_error_valu":[2,11,15],"cli_info_valu":[2,11,15],"cli_warning_valu":[2,11,15],"cliconfig":[2,3,4,6,12],"clone":35,"close":[20,25],"closest":[32,39],"cm":[35,40],"cmake":35,"cmd":[14,18,20],"cmdarg":6,"co":[33,40],"co2":[4,8,10,20,21,33,36,37,41,44,45],"coarser":40,"code":[2,15,28,29,35,47],"coeffici":[4,27],"collabor":29,"collect":12,"color":[15,20],"colormap":20,"column":[10,12,13,20,22],"com":[33,34,35,40,41],"combin":[4,12,21,43,44,45],"comma":[4,20,37],"command":[4,6,8,10,12,14,15,18,19,20,21,27,32,34,35,36,39,40,42],"commit":29,"common":[12,32,35],"compact":16,"compar":[10,19,26,30,31,34,36,43,44],"comparison":[4,6,14,18,19,34,36,37,38,44,45,47,48],"compat":[34,36],"complement":30,"complementari":36,"complet":[28,30,33,36,42,43,47,48],"comput":[19,20,36,42,46],"concentr":[4,8,10,20,21,40,44],"config":[1,2,12,17,20],"configur":[0,4,6,12,13,14,16,18,20,24,30,31,34,35,36,39,40,42],"confirm":44,"conflict":38,"conmin":[10,21],"connect":[0,6,13,21,34,42,48],"consecut":13,"consid":32,"consist":[15,20],"constraint":[3,4,12],"construct":[6,13],"contain":[0,4,6,8,10,12,15,20,22,27,29,30,31,32,39,47,48],"content":[1,20,29],"context":8,"continu":[35,43,44,45,46,48],"contour":21,"contribut":[20,35,36],"control":[4,19,25,26,37,40,45,46],"converg":4,"convert":[4,8,10,12,48],"coord":16,"coordin":[0,4,6,8,10,13,14,16,21,24,44],"copi":[6,16,20,29,35,45,46],"core":[1,2,3,4,26,32,40],"corey":25,"corner":[0,4,13,16,24,27,34,36,40],"correct":[8,15,31],"correspond":[6,13,20,32,38,40],"count":[4,13,20],"counter":20,"cov":29,"cover":44,"cp":20,"cpr_trueimp":40,"cpu":[32,47],"cpus":[33,40],"creat":[4,6,16,20,21,22,29,32,33,34,35,36,38,44,45,46,48],"create_corner_point_grid":[2,11,16],"csiro":[36,44],"cssr":[33,34,35,36,40,41,44],"csv":[8,9,10,19,22,34,39,43,44,45,47,48],"ctx":[7,8],"cumul":[17,20],"curl":40,"current":[8,9,12,20,21,23,32,34,35,42,47],"current_directori":35,"custom":[30,33,40],"d":[36,40],"d04949":40,"d0d3a7":40,"d14d4d":40,"d25151":40,"d2d4aa":40,"d35555":40,"d3d6ad":40,"d45959":40,"d55c5c":40,"d5d7b0":40,"d66060":40,"d6d9b3":40,"d76464":40,"d86868":40,"d8dab6":40,"d96c6c":40,"d9dcb9":40,"da7070":40,"daavid00":41,"darus":40,"data":[0,2,3,4,6,7,11,12,14,16,20,21,22,32,34,36,37,38,39,41,42,43,45,46,48],"datafil":40,"datum":24,"day":[4,12,33,40],"db7474":40,"dbddbc":40,"dc7777":40,"dcdfbf":40,"dcmake_build_typ":35,"dcmake_prefix_path":35,"dd7b7b":40,"de":40,"de7f7f":40,"decay":40,"deck":[3,4,9,13,16,20,36,48],"decreas":[32,40],"dedic":[35,44],"dee0c1":40,"default":[34,36,37,40,45,47],"defin":[6,12,18,21,24,25,28,34],"definit":[4,12],"delet":[2,3,4,7],"demonstr":32,"dens":[8,34,36],"dense_t":[7,8],"depend":[6,22,27,29,35,42,46],"deprec":15,"depth":13,"deriv":[4,6,12,13,14,16,18,20,23,44],"describ":[29,40,47],"descript":[24,36],"destin":20,"determin":15,"dev":[29,35],"develop":[34,35,36],"df8383":40,"diagnos":45,"diagnost":20,"dict":[4,12],"dictionari":12,"differ":[32,40,45],"differenti":[4,26,34,36],"diffus":[3,4,12,24,36,45,46],"dim":[3,4,7,8],"dimens":[4,8,12,13],"dimension":20,"dir":24,"direct":44,"directori":[4,6,8,9,15,20,21,27,29,32,40,42,43,45,46,48],"disabl":15,"disagr":45,"discov":20,"disp":[3,4,26],"dispatch":[6,13,18],"disperc1":24,"disperc2":24,"disperc3":24,"disperc4":24,"disperc5":24,"disperc6":24,"dispercn":[25,32],"dispers":[25,45],"display":[12,15,34],"dissa":[7,8],"dissb":[7,8],"dissolv":[4,8,10,20,21,33,44,45],"distanc":[4,10,18,19,36,40,43,45,48],"distribut":[12,20,26,32],"divid":[13,24],"dmar":24,"doc":[0,29],"docs_adding_your_result":47,"docs_hello_world":31,"docs_visu":33,"document":[29,31,33,35,47],"doe":[40,43],"doi":36,"domain":[10,44],"done":35,"dot":20,"download":[35,40,46],"dpi":[20,33],"dst":20,"durat":22,"dure":[12,20],"duse_mpi":35,"dvipng":35,"dwith_ndebug":35,"dx":[3,4,7,8],"dynam":41,"dz":[3,4,7,8],"e":[24,29,35,36,37,40],"e08787":40,"e0e1c4":40,"e18b8b":40,"e1e3c7":40,"e28f8f":40,"e39292":40,"e3e4ca":40,"e49696":40,"e4e6cd":40,"e59a9a":40,"e69e9":40,"e6e7d0":40,"e7a2a2":40,"e7e9d3":40,"e8a6a6":40,"e9aaaa":40,"e9ead6":40,"eaadad":40,"eaecd9":40,"earth":10,"ebb1b1":40,"ecb5b5":40,"eceddc":40,"eclfil":8,"eclips":16,"edb9b9":40,"edefdf":40,"edg":[13,35],"edit":[39,40],"eebdbd":40,"efc1c1":40,"eff0e1":40,"effect":[12,26,45],"egrid":[7,8],"eight":33,"either":35,"els":35,"email":29,"empti":[12,27],"enabl":[12,15,24,27,40,48],"encod":[15,16],"end":35,"endpoint":25,"enkf_alpha":[3,4,26],"enrich":12,"ensembl":[3,4,6,14,20,26,30,34,36,37],"ensemblest":[2,17,20],"enterpris":35,"entri":[6,12,24,25,27],"environ":[29,34,35,42],"equal":13,"error":[3,4,12,14,15,19,26,36,43,45],"error_t":[2,17],"error_table_satmin":[40,45],"erst":8,"ert":[0,2,4,6,11,12,14,16,20,28,30,34,35,36,37,38,44],"ertarg":[3,4,26],"esf":40,"esmri":8,"especi":47,"establish":10,"evalu":[4,8,9,10,20,21,24,25,32,43,44,46],"event":[22,44],"everert":[2,3,4,17],"everest":[0,2,4,6,10,11,12,14,16,20,28,30,34,35,36,37,38,44],"everest_iter_3":32,"everi":[8,45],"everyth":42,"evolut":[4,26,34,36,45],"exact":[34,36,47],"exampl":[28,34,36,37,39,40,45,47],"except":24,"exclus":12,"execut":[0,6,12,14,16,18,20,22,23,24,29,35,39,40,46,47,48],"exist":[4,16,32,37,38,43,44],"exit":[14,20],"expand":12,"expect":[12,43,44],"experi":[3,4,21,37,38],"experiment":[4,10,19,20,21,34,36,43,44,45,48],"experimental_data":10,"explicit":12,"explor":36,"expon":25,"export":[29,34,35],"expos":32,"exprecc":41,"express":[4,8,12,25,46],"extend":29,"extens":8,"extern":[17,18,20,32,34,36,47],"extra":35,"extract":[8,12,20,22,34],"extract_faci":[2,11,12],"f":[32,33,34,37,38,40,43,44,45,48],"f0c5c5":40,"f1c8c8":40,"f1f1e4":40,"f2cccc":40,"f2f3e7":40,"f3d0d0":40,"f4d4d4":40,"f4f4ea":40,"f5d8d8":40,"f5f6ed":40,"f6dcdc":40,"f7e0e0":40,"f7f7f0":40,"f8e3e3":40,"f8f9f3":40,"f9e7e7":40,"faci":[3,4,12,13,24,26,28,32,34,36,45,46],"facie1":[24,46],"facie2":24,"facie3":24,"facie4":24,"facie5":24,"facie6":[24,46],"facien":25,"factor":[13,40],"factori":[4,8,20],"faebeb":40,"fafaf6":40,"fail":[14,20],"failur":20,"fair":[4,37,38,40],"fals":[4,12],"familiar":29,"fatal":15,"fbefef":40,"fbfcf9":40,"fcf3f3":40,"fdf7f7":40,"fdfdfc":40,"featur":[4,13,36],"fefbfb":40,"fewer":40,"ff0000":40,"ff0100":40,"ff0200":40,"ff0300":40,"ff0400":40,"ff0500":40,"ff0600":40,"ff0700":40,"ff0800":40,"ff0900":40,"ff0a00":40,"ff0b00":40,"ff0c00":40,"ff0d00":40,"ff0e00":40,"ff0f00":40,"ff1000":40,"ff1100":40,"ff1200":40,"ff1300":40,"ff1400":40,"ff1500":40,"ff1600":40,"ff1700":40,"ff1800":40,"ff1900":40,"ff1a00":40,"ff1b00":40,"ff1c00":40,"ff1d00":40,"ff1e00":40,"ff1f00":40,"ff2000":40,"ff2100":40,"ff2200":40,"ff2300":40,"ff2400":40,"ff2500":40,"ff2600":40,"ff2700":40,"ff2800":40,"ff2900":40,"ff2a00":40,"ff2b00":40,"ff2c00":40,"ff2d00":40,"ff2e00":40,"ff2f00":40,"ff3000":40,"ff3100":40,"ff3200":40,"ff3300":40,"ff3400":40,"ff3500":40,"ff3600":40,"ff3700":40,"ff3800":40,"ff3900":40,"ff3a00":40,"ff3b00":40,"ff3c00":40,"ff3d00":40,"ff3e00":40,"ff3f00":40,"ff4000":40,"ff4100":40,"ff4200":40,"ff4300":40,"ff4400":40,"ff4500":40,"ff4600":40,"ff4700":40,"ff4800":40,"ff4900":40,"ff4a00":40,"ff4b00":40,"ff4c00":40,"ff4d00":40,"ff4e00":40,"ff4f00":40,"ff5000":40,"ff5100":40,"ff5200":40,"ff5300":40,"ff5400":40,"ff5500":40,"ff5600":40,"ff5700":40,"ff5800":40,"ff5900":40,"ff5a00":40,"ff5b00":40,"ff5c00":40,"ff5d00":40,"ff5e00":40,"ff5f00":40,"ff6000":40,"ff6100":40,"ff6200":40,"ff6300":40,"ff6400":40,"ff6500":40,"ff6600":40,"ff6700":40,"ff6800":40,"ff6900":40,"ff6a00":40,"ff6b00":40,"ff6c00":40,"ff6d00":40,"ff6e00":40,"ff6f00":40,"ff7000":40,"ff7100":40,"ff7200":40,"ff7300":40,"ff7400":40,"ff7500":40,"ff7600":40,"ff7700":40,"ff7800":40,"ff7900":40,"ff7a00":40,"ff7b00":40,"ff7c00":40,"ff7d00":40,"ff7e00":40,"ff7f00":40,"ff8000":40,"ff8100":40,"ff8200":40,"ff8300":40,"ff8400":40,"ff8500":40,"ff8600":40,"ff8700":40,"ff8800":40,"ff8900":40,"ff8a00":40,"ff8b00":40,"ff8c00":40,"ff8d00":40,"ff8e00":40,"ff8f00":40,"ff9000":40,"ff9100":40,"ff9200":40,"ff9300":40,"ff9400":40,"ff9500":40,"ff9600":40,"ff9700":40,"ff9800":40,"ff9900":40,"ff9a00":40,"ff9b00":40,"ff9c00":40,"ff9d00":40,"ff9e00":40,"ff9f00":40,"ffa000":40,"ffa100":40,"ffa200":40,"ffa300":40,"ffa400":40,"ffa500":40,"ffa600":40,"ffa700":40,"ffa800":40,"ffa900":40,"ffaa00":40,"ffab00":40,"ffac00":40,"ffad00":40,"ffae00":40,"ffaf00":40,"ffb000":40,"ffb100":40,"ffb200":40,"ffb300":40,"ffb400":40,"ffb500":40,"ffb600":40,"ffb700":40,"ffb800":40,"ffb900":40,"ffba00":40,"ffbb00":40,"ffbc00":40,"ffbd00":40,"ffbe00":40,"ffbf00":40,"ffc000":40,"ffc100":40,"ffc200":40,"ffc300":40,"ffc400":40,"ffc500":40,"ffc600":40,"ffc700":40,"ffc800":40,"ffc900":40,"ffca00":40,"ffcb00":40,"ffcc00":40,"ffcd00":40,"ffce00":40,"ffcf00":40,"ffd000":40,"ffd100":40,"ffd200":40,"ffd300":40,"ffd400":40,"ffd500":40,"ffd600":40,"ffd700":40,"ffd800":40,"ffd900":40,"ffda00":40,"ffdb00":40,"ffdc00":40,"ffdd00":40,"ffde00":40,"ffdf00":40,"ffe000":40,"ffe100":40,"ffe200":40,"ffe300":40,"ffe400":40,"ffe500":40,"ffe600":40,"ffe700":40,"ffe800":40,"ffe900":40,"ffea00":40,"ffeb00":40,"ffec00":40,"ffed00":40,"ffee00":40,"ffef00":40,"fff000":40,"fff100":40,"fff200":40,"fff300":40,"fff400":40,"fff500":40,"fff600":40,"fff700":40,"fff800":40,"fff900":40,"fffa00":40,"fffb00":40,"fffc00":40,"fffd00":40,"ffffff":40,"fi":35,"field":[10,21],"fig":20,"figur":[3,4,6,14,18,20,21,30,31,32,33,34,35,36,38,39,42,44,46,47,48],"figure10":40,"figure10d":40,"figure10f":40,"figure3":40,"figure3a":40,"figure3b":40,"figure4":40,"figure4ish":40,"figure5":40,"figure5d":40,"figure5f":40,"figures10":40,"figures10d":40,"figures5":40,"figures5d":40,"file":[0,4,6,8,9,12,15,16,19,20,21,22,24,29,31,32,34,35,36,37,38,39,40,43,44,46,48],"file_1":10,"file_2":10,"file_nam":10,"filenam":[8,10,12,15,19,21],"final":[4,20,24,27,32],"find":44,"finit":[12,37],"fip":8,"fipnum":[3,4,7,8,13],"first":[10,24,30,36],"fit":20,"five":33,"fix":[4,12,24,29,38],"flag":[4,12,24,27,40,46,48],"flexibl":40,"float":[4,8,10,12,13,20],"float64":13,"flow":[2,3,4,6,8,11,12,14,16,24,29,33,34,36,37,38,40,41,42,43,44,46,48],"flow_maco":29,"fluid":[36,46],"fluidflow":[0,4,6,8,10,13,18,19,20,21,22,30,33,34,36,37,43,44,45,46,48],"flux":13,"fluxnum":[3,4,13],"fn":40,"focus":[30,34],"fol":[3,4,16],"folder":[6,20,29,31,32,35,36,42,44,46,48],"follow":[13,24,31,32,34,35,36,37,39,44],"font":35,"forecast":[36,44],"fork":[29,45],"form":26,"format":[12,15,16,19,29,35,36,37,40,43,44],"forward":16,"found":20,"fraction":[33,40],"framework":[34,36],"fs":[33,40],"full":[24,35],"fulli":12,"func":44,"function":[4,12,13,14,16,24,28,32,36,46],"fund":36,"fz":[33,40],"g":[24,29,35,40],"galleri":[30,31,32,33],"gas":[4,8,10,20,21,24,27],"gasda":36,"gaseous":[37,45],"ge":40,"general":23,"generat":[0,4,6,8,9,10,13,14,15,16,18,20,21,22,24,25,29,30,31,33,34,36,38,39,40,41,42,44,45,46,47,48],"geolog":[0,4,6,12,13,21,34,36,41],"geometri":[0,4,16],"get":[29,34,35],"gi":33,"gif":[33,41],"git":[33,34,35,40],"github":[24,29,33,34,35,40,41],"githubusercont":40,"give":40,"gl":33,"global":[8,13,16],"graphic":15,"grayscal":10,"grid":[0,3,4,6,8,10,12,13,16,21,24,31,34,35,36,42,44,45,46,48],"grid_and_properti":[2,11,13],"group":[19,20,40,46],"growth":40,"guid":34,"gxyz":[7,8],"h":[4,21,33,36,40],"handl":[8,19],"hascellmap":[3,4],"hat":35,"head":40,"header":19,"hello":30,"help":[29,34,35,42],"helper":[12,16],"hide":40,"histori":[0,4,6,9,12,14,20,22,24,30,34,36,37,38,47],"history_matching_output":32,"hm":[3,4],"homebrew":35,"horizon":13,"horizont":4,"hour":[4,8,20,21,22,30,31,34,37,43,44,48],"html":29,"https":[33,34,35,36,40,41],"human":[12,15],"identifi":[8,10,13,20,45,46],"idrealis":[17,20],"ignor":29,"imag":[10,34,36,41],"imma":[7,8],"immb":[7,8],"immobil":[8,44],"implement":6,"import":[29,46],"improv":[36,42,43],"inch":20,"includ":[9,16,26,27,32,35,37,40,43,44,48],"inclus":37,"incompat":6,"increas":40,"ind_batch":[17,20],"ind_sim":[17,20],"independ":34,"index":[12,20],"indic":[4,8,13,21],"indx":10,"ineffect":12,"inf":20,"inform":15,"ini":[7,8],"init":[3,4,26],"initi":[4,8,9,12,20,24,26,27,31,32,46],"inj":[3,4,24,33,46],"inject":[4,8,12,13,24,27,34,36,45,46],"input":[0,4,6,12,19,20,21,22,24,34,36,38,39,40,42,46,47,48],"inputvalu":[2,11],"insid":[13,35],"inspect":[12,15,39,42,48],"instal":[29,36,40,42],"instead":[6,8,10,18,19,20,21],"instruct":35,"int":[4,8,10,12,13,20],"int64":10,"integ":[12,25,27],"integr":[3,4,26,36],"interact":14,"interest":[35,40],"interfac":[13,16],"interim":35,"internal":36,"interpol":8,"interpret":[27,42,43,44],"interval":[8,12],"introduct":34,"invalid":[6,12,15],"isotrop":[26,32],"issu":[32,36],"iter":[4,8,20,24,32,40],"j":[20,36],"j5":35,"job":[0,1,2,3,4,6,12,16,17,20,39,44],"keep":45,"key":[12,26],"keyword":[16,32],"kg":[24,27],"kilogram":8,"know":46,"krn":[3,4,24,46],"krw":[3,4,24,46],"l":[35,40],"label":[21,42,43,48],"landa":36,"larg":9,"latest":35,"latex":[34,35,42],"launch":14,"layer":13,"layout":[22,32,39,44,46],"leakag":41,"learn":34,"least":[12,20,25,26],"left":13,"length":[12,13,16],"less":33,"lesson":[44,47],"level":[0,6],"librari":35,"lie":[25,37],"limit":[4,32,40,47],"line":[4,6,8,10,12,13,15,18,19,20,21,24,29,32,34,35,36,40,42,47],"linear":40,"link":35,"lint":35,"liquid":[24,27,33,40],"list":[4,6,8,10,12,13,15,16,20,44],"load":[8,12,13,19,20,21],"load_toml":[2,11,12],"local":[29,32,34,46],"locat":[3,4,8,13,15,20,36,43,46],"log":18,"look":29,"low":[30,31],"lower":[12,13],"m":[4,24,27,32,33,34,35,36,37,39,40,43,44,45,48],"m_c":[7,8],"m_c_seri":[7,8],"mac":[33,40],"machin":[24,35,40],"maco":29,"macport":35,"mactex":35,"main":[2,5,6,7,8,10,12,16,17,22,29,34,40,44],"maintain":[14,18,29,30,31,32,33,37,40,45,46,47],"make":[6,29,35],"mako":[0,40],"mamba":35,"manag":35,"manual":40,"map":[2,4,6,7,8,10,12,13,14,17,20,24,30,31,32,34,36,44,45],"map_":21,"map_24h":31,"mapproperti":[2,11],"marb\u00e1n":36,"mass":[8,33,40],"master":35,"match":[0,4,6,9,12,14,20,22,24,30,34,36,37,38,47],"matplotlib":20,"matrix":12,"max":[4,24,40],"max_batch_num":[3,4],"max_function_evalu":[3,4,26,32],"maxdepth":44,"maxima":[22,44],"maximum":[10,12,26,32],"maxit":[3,4,26],"maxtim":[3,4],"may":[4,26,32,37,44],"mcon":[3,4],"mean":[18,20],"measur":[13,24,27],"media":36,"memori":[20,32,47],"merg":29,"messag":[12,15],"metadata":8,"metr":[8,10,13,21],"metric":[2,6,7,14,19,36,44,45,48],"micp":41,"microsystem":41,"might":[29,35,40],"migrat":[8,44],"min":[24,40],"min_concentr":[17,20],"min_realizations_success":[3,4,26],"min_satur":[17,20],"miniforg":35,"minimum":[4,10,12,13,20,21,26,32],"minimumconcentr":37,"minimumsatur":37,"minut":33,"misfit":20,"miss":[20,29,32],"miss_en":[17,20],"mit_m1":[36,44],"mkdir":[35,40],"moba":[7,8],"mobb":[7,8],"mobil":[8,44],"mode":[3,4,6,12,15,23,32,43],"model":[4,13,16,24,32,34,36,37,41,42,43,45,46,48],"model_result":10,"modifi":[12,13,35,45],"modified_result":45,"modul":[1,36],"molecular":4,"monoton":[3,4,16,20,26],"mover":10,"mpi":35,"mpirun":[24,40],"msat":[3,4],"msg":15,"mt":[33,40],"mult_thick":[3,4,24,46],"multipli":[4,13,24,26,27],"multpv":[3,4],"multx":40,"must":[12,37,44],"mutabl":[4,8,20],"mutat":[3,4,26],"mv":[33,40],"my_simul":44,"mypi":29,"m\u00b2":[4,12,27],"n":[16,25,29],"n_e":[17,20],"n_i":[17,20],"name":[4,8,12,15,20,26,32,40,42,43,44,48],"namespac":6,"ncz":13,"ndarray":[4,8,10,13,16,20],"near":41,"nearest":13,"need":[29,35,40],"new":[13,29,43,48],"newton":[24,40],"newtoniterationcount":40,"next":24,"nice":[29,35],"nkrn":[4,24],"nkrn1":24,"nkrn2":24,"nkrn3":24,"nkrn4":24,"nkrn5":24,"nkrn6":24,"nkrnn":[25,32],"nkrw":[4,24],"nkrw1":24,"nkrw2":24,"nkrw3":24,"nkrw4":24,"nkrw5":24,"nkrw6":24,"nkrwn":[25,32],"no_ob":[17,20],"no_para":[17,20],"non":[12,15,24,25,27],"none":[4,6,8,10,12,13,14,15,16,18,19,20,21,22,32,33,34,37,38,40,43,44],"nonempti":12,"nonmonoton":[9,20],"nonzero":[14,20],"nordbotten":36,"noreturn":15,"normal":[4,6,10,12,13,20],"norst":[7,8],"now":30,"np":[10,13,24,40],"npe1":24,"npe2":24,"npe3":24,"npe4":24,"npe5":24,"npe6":24,"npen":[4,24,25,32],"npi":13,"npnt1":24,"npnt2":24,"npnt3":24,"npnt4":24,"npnt5":24,"npnt6":24,"npntn":[24,25,32],"num_en":[17,20],"number":[4,8,12,13,20,24,25,26,32,36,40],"numer":[12,16,45],"numpi":[8,20],"nxz":[3,4],"o":[32,33,34,36,37,39,40,43,44,45,48],"ob":20,"object":[4,6,8,10,12,15,18,19,20,21,44],"observ":[4,8,10,13,17,20,34,36,39,44,45],"obtain":40,"one":[4,8,12,13,20,21,24,26,27,31,42,43,45,48],"onli":[26,34,38],"onlin":35,"open":[8,34,35,36],"oper":[6,12,40],"opm":[4,6,8,13,14,16,24,27,29,30,33,34,36,37,38,39,40,41,42,43,44,46,48],"opm2":40,"opm3":40,"opm4":40,"opmrestart":8,"opmsummari":8,"opposit":4,"opt":20,"optim":[6,14,16,17,20,26,30,32,34,36,37],"optimal_valu":[17,20],"optimizationst":[2,17,20],"option":[4,6,8,10,12,13,14,15,16,18,19,20,21,22,23,26,27,32,34,35,36,38],"order":[4,6,8,13,16,20,22],"org":[35,36],"organiz":48,"origin":[20,43,45],"other_region":13,"otherwis":[9,20,24,40,44],"output":[4,6,8,15,20,21,24,27,34,36,42,43,44,45,47,48],"output_dir":15,"output_fil":22,"overlay":[21,43],"pa":[24,27],"packag":[0,1,4,6,12,29,36],"page":[29,40],"pair":21,"paper":[32,36,40,47],"par_di":[17,20],"para":[3,4],"para_fil":[17,20],"para_nam":[17,20],"parallel":32,"paramet":[6,8,10,12,13,14,15,16,18,19,20,21,22,24],"pars":[6,8,10,18,19,20,21],"parser":[23,28],"part":13,"parti":30,"particip":19,"pascal":8,"pass":[4,12],"path":[3,4,6,8,10,12,17,19,20,21,24,29,35,42,47],"pen":[4,24],"pen1":24,"pen2":24,"pen3":24,"pen4":24,"pen5":[24,32],"pen6":24,"penn":[25,32],"per":[10,13,20,44],"perform":[34,36,48],"perm":24,"perm3":32,"permeabl":[25,26,32,45],"permn":[26,32],"permx1":24,"permx2":[24,32],"permx3":24,"permx4":24,"permx5":24,"permx6":24,"permxn":[25,32],"permyn":32,"permz1":24,"permz2":24,"permz3":24,"permz4":24,"permz5":24,"permz6":24,"permzn":[25,32],"phase":40,"physic":[4,10,13,27,45,46],"pillar":[13,16],"pip":[29,33,34,35,40],"pixel":10,"place":[8,10,12,13,44],"platform":35,"pleas":[29,32],"plopm":[30,34,35,36,40,41,47],"plot":[6,13,14,18,20,21,22,33,37,38,42,45],"plus":15,"png":[10,21,31,33],"pngs":[39,41],"pofff":[27,28,29,31,32,33,35,36,37,39,40,42,43,44,45,46,47,48],"pofff_error":[2,11,15],"pofff_info":[2,11,15],"pofff_nam":[2,11,15],"pofff_path":[6,12],"pofff_success":[2,11,15],"pofff_tip":[2,11,15],"pofff_warn":[2,11,15],"pofffconfig":[2,3,4,6,12,13,14,16],"point":[0,4,6,8,13,16,21,24,25,27,34,36,40,42],"polish":[3,4,26],"polygon":13,"polylin":13,"pop1":[7,8],"pop2":[7,8],"popsiz":[3,4,26],"popul":[4,8,12,20],"pore":[4,8],"poro1":24,"poro2":24,"poro3":24,"poro4":24,"poro5":24,"poro6":24,"poron":[25,32],"poros":25,"porous":36,"porv":[3,4,7,8],"posit":[4,8,13,24,26,27,37],"posixpath":[4,8],"possibl":[29,32],"postprocess":[0,2,6,11,14,20,33,34,36,37,43],"postprocess_config":[2,11,12],"precomput":4,"prefer":29,"prepar":[6,42],"prerequisit":35,"present":12,"preserv":[9,10,45],"pressur":[3,4,8,24,25,44,45,46],"prevent":25,"previous":[29,35,47],"primarili":45,"princip":43,"principl":40,"print":[15,44],"process":[0,6,10,18,19,20,21,34,36,37,38,42,48],"process_tun":[2,11,12],"processor":42,"produc":[32,43,45],"product":[4,31,43,44,47,48],"profil":[32,36],"program":15,"progress":[4,15,20],"project":[20,21,35],"prompt":14,"properti":[0,4,8,12,13,16,24,26,32,36,45,48],"provid":[15,27,44,47],"prs":29,"public":[24,29,32,33,36,46,47],"publish":[34,36,42,43],"pull":[29,45],"push":29,"pushd":29,"pvtnum":40,"pwd":35,"py":[32,40],"py314":29,"pycopm":[29,35,41],"pylint":29,"pymm":41,"pyopmnearwel":41,"pyopmspe11":[40,41],"pytest":29,"python":[4,8,12,25,29,36,40,42],"python3":[35,40],"qualiti":36,"quantiti":[8,19,22,44,45],"quot":15,"r":[20,29,35,40],"r2_cart_1cm_capmax2500pa":40,"r3_cp_1cmish_capmax2500pa":40,"r4_cart_1mm_capmax2500pa":40,"r5_cart_1mm_capmax2500pa_strictol":40,"rais":[6,8,12,14,15,20,29,32],"ran":31,"random":[4,32,40],"random_se":[3,4,26],"rang":[12,44],"rank":19,"rate":[4,24,27,40],"raw":[4,31,32,33,40,46,47],"rdl":40,"reach":20,"read":[8,13,20,22,43],"readabl":[12,15],"real":24,"realiz":[4,20,48],"recalcul":37,"receiv":[8,13],"recent":[20,35],"recombin":[3,4,26],"recommend":[35,42],"reconstruct":20,"recreat":43,"red":35,"ref":40,"refer":[4,32,33,34,35,36,46,47],"refin":[4,13,24,27,46],"refxz":8,"regard":40,"regener":[0,43],"region":[4,8,13,16,34,36,41,45,46],"reject":[20,38],"rel":24,"relat":[8,19,25,29,36,45],"releas":[20,35],"reload":35,"remain":[30,45,47],"remedi":41,"remov":[9,12],"render":[16,46],"rendit":15,"repeat":16,"replac":[8,29,33],"repo":[29,35],"report":[4,6,8,10,12,13,15,19,20,21,36,40,44,45],"repositori":[29,31,32,33,35,40,45,46,47],"repres":[13,20,21],"represent":[4,13,46],"reproduc":[26,32,36,39,42,45],"request":[8,12,21,29,36,42,44,45,46,48],"requir":[4,12,26,27,29,32,35,40,42,43,44,46],"rerun":[39,43],"res":8,"reservoir":41,"residu":25,"resinsight":[34,35],"resiz":10,"resolut":[7,8,20,25,30,31,45],"resourc":[4,32,36,40,42,46,47],"respect":20,"restart":[8,9,40],"restrict":28,"result":[6,8,14,20,24,30,32,34,35,36,37,38,39],"retain":[4,10],"return":[6,8,10,12,13,15,16,20],"reus":4,"review":[29,35,36,46,47,48],"rig":24,"right":13,"rise":29,"rng":[3,4,26],"rock":[16,36,46],"root":[4,6,12,20,31,33,40,47],"round":10,"routin":6,"row":[4,12,20,27],"rst":[24,40],"ruff":29,"rule":46,"run":[2,4,6,11,16,17,18,29,30,31,32,33,34,35,36,37,38,40,42,44,45,46,47],"run1":4,"run2":[4,20],"run5":4,"run_benchmark":[2,17,18],"run_command":[17,20],"run_error_t":[2,17,19],"run_everert":[2,17,20],"run_map":[2,17,21],"runtim":[4,6,12,13,14,16,18,20,32,42,46],"s":[4,10,17,20,24,27,29,35,36,37],"s11242":36,"sampl":44,"sand":40,"sand_region":13,"sandv":36,"satisfi":12,"satmin":[10,21],"satnum":[33,40],"satur":[4,8,10,16,20,21,24,28,36,44,46,48],"save":20,"scale":[16,26,32],"schedul":[24,33,34,36,46],"scientif":36,"screenshot":39,"script":[4,6,14,16,18,20,29,31,33,35,39,40,45,47],"seal":[8,44],"seala":[7,8],"sealb":[7,8],"sealt":[7,8],"search":[8,35],"second":[8,10],"section":40,"see":[24,29,32,33,34,35,36,40,46,48],"seed":[4,32,40],"segment":[4,10,18,19,20,21,37,45],"select":[4,6,8,12,13,15,16,19,22,26,27,32,37,45,46],"send":29,"sensit":36,"sensor":[3,4,13,34,36],"sensor_ik":[3,4],"separ":[4,20,37,45],"sequenc":[4,14,15,16,26,48],"seri":[8,14,18,20,22,34,36,43,44,45,48],"serial":16,"set":[4,8,12,24,28,32,34,35,36,37,38,40,42,43,45,46],"setup":28,"setuptool":35,"sg":40,"sgas":8,"sh":[31,33,35,40,47],"shape":12,"share":[4,6,8,12,13,14,16,18,20,46],"shell":[14,18,20],"short":31,"show":[39,40,45],"sim":[7,8],"sim_en":[17,20],"sim_metrics_0":44,"similar":40,"simpli":35,"simul":[0,4,6,8,9,10,12,13,14,17,19,20,21,22,24,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,46,47],"simulationcontext":[2,7,8],"simxcent":13,"simzcent":13,"singl":[4,6,29,31,33,34,36,37,38,40,45,48],"singular":25,"site":41,"six":[25,29],"size":[4,13,20,24],"skip":[14,35,37,38,43],"slower":37,"small":[30,31,36],"smri":8,"smspec":[7,8],"sni":[4,24],"sni1":24,"sni2":24,"sni3":24,"sni4":24,"sni5":24,"sni6":24,"snin":[25,32],"solut":20,"solver":40,"soon":29,"sourc":[3,4,6,8,10,12,13,14,15,16,18,19,20,21,22,24,29,34,36,46,47],"source1":24,"source2":24,"source_ik":[3,4],"spars":[8,14,18,19,22,34,36,43,44,45],"sparse_data":[22,44],"sparse_t":[7,8],"sparse_valu":[2,17],"sparseresult":[2,7,8],"spatial":[8,10,13,14,20,21,31,34,36,40,43,44,45,48],"spatial_map_xh":44,"spe11":41,"spe11a_spatial_map_48h":40,"specif":[12,35,37,45],"specifi":[12,48],"speed":40,"spell":4,"squar":20,"squash":29,"src":[20,29],"st":40,"stage":[6,22,46],"standard":22,"start":[8,32,42,46],"state":[4,6,8,12,13,14,16,18,20,40],"static":29,"statist":20,"status":[14,20],"stderr":15,"step":[8,24,27,29,40],"storag":[36,41],"store":[4,8,20],"str":[4,6,8,10,12,13,14,15,20,22],"strategi":[3,4,26],"stream":15,"strict":12,"stricter":40,"string":[12,16,25,27],"structur":[13,20],"studi":[6,20,30,32,34,36,40],"stuttgart":40,"style":20,"subdivid":13,"submit":36,"submodul":[1,2],"subpackag":1,"subprocess":[14,20],"subsect":39,"substanti":42,"subsurfac":36,"subsystem":35,"succed":35,"succeed":[29,35],"success":[4,15,20],"sudo":35,"suggest":15,"suitabl":12,"sum":[8,20],"summar":45,"summari":[8,9,18],"super":35,"suppli":42,"support":[6,15,24,26,32,34,35,36,40,48],"sustain":36,"sw":[4,24],"swi":[4,24],"swi1":24,"swi2":24,"swi3":24,"swi4":24,"swi5":24,"swi6":24,"swin":[25,32],"syntax":[16,34,36],"sys":8,"system":[29,32,40,47],"systemexit":[6,8,12,15],"t":[21,31,32,33,34,36,37,40,43,44,45,48],"tab20":20,"tabl":[4,12,14,19,24,25,32,36,39,43,45,47,48],"tag":35,"tailor":41,"tap":35,"target":29,"temperatur":[3,4,24,46],"templat":[0,4,6,12,40],"tempor":45,"temporari":[10,20],"tensor":[4,13,24,27,34,36],"term":29,"termin":[2,11,12,35,40],"test":[24,29,31,32,33,35,40,47],"test_output":29,"texliv":35,"text":[0,4,12,15],"textiowrapp":15,"thick":[3,4,12,13,24,26,32,34,36,45,46],"thicknessmult":[26,32],"third":30,"thre1":24,"thre2":24,"thre3":24,"thre4":24,"thre5":24,"thre6":24,"thren":[24,25,32],"threshold":[4,19,20,21,24,25,37,45],"time":[3,4,7,8,10,14,17,18,20,21,22,24,27,31,32,34,36,38,42,43,44,45,48],"time_initi":[7,8],"time_seri":[8,22,44],"timeout":4,"times_data":[7,8],"times_summari":[7,8],"timeseries_fil":22,"tip":15,"togeth":[40,45],"tol":[3,4,26],"toler":40,"toml":[4,12,28,31,32,33,34,36,37,39,40,42,45,46,47,48],"tool":[29,33,34,35,36,40,41,42],"top":[0,6,13,24,27],"tot_ev":[17,20],"total":[8,13,20],"track":20,"transport":36,"tri":[29,35,40],"trip":10,"true":[24,26,27,40],"trust":35,"tu":33,"tug":35,"tune":[3,4,12,24,27],"tupl":[4,8,10,13],"turn":35,"tutori":[29,30,34,35,36,40,43,45,46,48],"two":[4,8,10,13,26,27,32,33],"txt":[20,29,32,35,44],"type":[4,12,24,35],"typographi":42,"u":37,"ubuntu":[29,35],"unavail":[8,20],"uncertainti":20,"unchang":[15,45],"underlying":32,"understand":[36,42,44],"uni":40,"uniform":[8,44],"unit":46,"unless":40,"unrefin":13,"unrst":[7,8],"unsuccess":20,"unzip":40,"updat":[3,4,13,20,26],"upgrad":35,"upper":[12,13],"uppercas":4,"use":[3,4,8,10,12,15,16,20,24,25,26,28,29,32,33,34,35,36,37,38,39,42,43,44,45,46,47,48],"user":[24,29,35,42],"utf":15,"util":[0,1,2,6,36],"v":[33,40],"v2026":35,"valid":[4,6,12,15,22,25,28,46],"valu":[4,8,12,13,14,15,16,18,20,22,24,27,28,32,34,36,37,40,44,46],"variabl":[12,24,26,29,32,36],"vector":[3,4,8,26],"venv":35,"verifi":[36,42,43],"version":[2,29,34,35],"vertic":[4,13,16],"via":35,"view":[31,32,33,40,46,47],"virtual":[34,35],"visit":29,"visual":[0,1,2,6,30,34,35,36,40],"volum":[4,8],"vpofff":35,"vtk":41,"w":[15,36,40],"want":42,"warn":[12,15],"wasserstein":[4,10,14,18,19,36,40,43,44,45,48],"water":8,"websit":40,"welcom":[29,45],"well":[24,29,40,41],"wet":[24,25],"wheel":35,"whether":[4,12,15,37,45],"will":29,"within":20,"without":[8,14,18,20,32,33,36,37,40,43],"work":[9,29,35,38],"worker":[3,4,26],"workflow":[0,4,6,12,14,16,28,30,32,34,37,38,40,41,42,43,44,45,47,48],"world":30,"wrap":15,"write":[0,6,8,10,13,16,19,21,22,24,27,34,35,36,40,42],"write_fil":[2,11,16],"writefil":[2,11],"written":[13,16,21],"wsl":35,"www":35,"x":[3,4,8,10,13,16,17,20,21,24,44,46],"x0":[3,4,26],"xcent":13,"xci":13,"xco2l":[33,40],"xcoord":[13,16],"xf":40,"xlim":10,"xmax":13,"xmin":13,"xnt":40,"xu":40,"xyz":13,"y":[4,8,13,35],"yet":32,"yf":40,"yml":[29,32,35],"yournam":29,"yu":40,"z":[3,4,8,10,13,16,21,24,44,46],"zcent":13,"zci":13,"zcoord":[13,16],"zcorn":16,"zero":[4,13,20],"zlim":10,"zmax":13,"zmin":13,"znr":13,"zshrc":35,"ztop":13},"titles":["pofff Python API","pofff","pofff package","pofff.config package","pofff.config.config module","pofff.core package","pofff.core.pofff module","pofff.jobs package","pofff.jobs.data module","pofff.jobs.delete module","pofff.jobs.metric module","pofff.utils package","pofff.utils.inputvalues module","pofff.utils.mapproperties module","pofff.utils.runs module","pofff.utils.terminal module","pofff.utils.writefile module","pofff.visualization package","pofff.visualization.benchmark module","pofff.visualization.error_table module","pofff.visualization.everert module","pofff.visualization.maps module","pofff.visualization.sparse_values module","Command-line reference","Complete TOML example","Facies and saturation functions","ERT and Everest settings","Simulation setup","Configuration reference","Contributing","Examples","Hello world","History matching with ERT and Everest","Visualization with plopm","pofff","Installation","Introduction","General options","Execution modes","Output folder","Reproducing the publication","Related projects","Tutorial","Generate the comparison figures","Inspect the benchmark data","Interpret and improve the results","Prepare the configuration","Reproduce the tutorial","Run the simulation"],"titleterms":{"10d":40,"10f":40,"5d":40,"5f":40,"About":36,"How":[43,44,45,46,47,48],"What":34,"Where":[36,47],"acceler":40,"accuraci":40,"add":42,"addit":32,"api":0,"appendix":40,"basic":36,"benchmark":[18,44],"binari":35,"brew":35,"build":35,"c":40,"can":34,"cap":25,"choic":40,"citat":36,"co2":40,"command":[23,31,33,43,44,45,46,47,48],"comparison":[40,42,43],"complet":24,"comput":40,"concentr":37,"config":[3,4],"configur":[28,32,33,37,43,44,45,46,47,48],"content":[2,3,5,7,11,17],"continu":[36,47],"contribut":29,"core":[5,6,36],"csv":40,"data":[8,40,44],"delet":9,"detail":40,"diffus":27,"direct":32,"directori":37,"dissolv":[37,40],"distanc":37,"error_t":19,"ert":[26,32,39],"evalu":37,"everert":20,"everest":[26,32,39],"exampl":[24,30,31,32,33],"execut":[32,37,38],"experiment":37,"faci":25,"figur":[37,40,43],"flow":[27,35],"fluidflow":42,"folder":39,"formula":35,"function":25,"gas":37,"general":37,"generat":[32,37,43],"geolog":40,"goal":[43,44,45,46,47,48],"grid":[27,40],"ground":29,"hello":31,"histori":[26,32],"improv":45,"inj":27,"input":37,"inputvalu":12,"inspect":44,"instal":[33,34,35],"interpret":45,"introduct":36,"issu":29,"job":[7,8,9,10],"key":25,"krn":25,"krw":25,"line":23,"link":47,"linux":35,"maco":35,"map":[21,40],"mapproperti":13,"match":[26,32],"metric":10,"minimum":37,"mode":[37,38],"modul":[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22],"mpi":40,"mult_thick":27,"next":[35,43,44,45,46,48],"opm":35,"option":37,"output":[32,37,39],"overview":[31,32,33],"packag":[2,3,5,7,11,17,35],"paramet":[26,32],"plopm":33,"pofff":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,34],"postprocess":32,"precomput":37,"prepar":46,"prerequisit":[40,42],"pressur":27,"problem":29,"profil":40,"project":[36,41],"public":40,"python":[0,35],"quick":34,"realiz":37,"refer":[23,28],"relat":41,"report":29,"reproduc":[31,33,40,47],"requir":25,"result":[31,33,40,42,43,44,45,46,47,48],"rule":29,"run":[14,39,48],"satur":[25,37],"seek":29,"sensit":40,"separ":32,"sequenc":42,"set":26,"setup":[27,40],"simul":[27,48],"singl":39,"softwar":29,"sourc":[27,35],"sparse_valu":22,"spe11a":40,"start":34,"step":35,"submit":40,"submodul":[3,5,7,11,17],"subpackag":2,"support":29,"tabl":40,"temperatur":27,"termin":15,"thick":[27,40],"time":[37,40],"toml":24,"trade":40,"tutori":[42,47],"usag":36,"use":40,"util":[11,12,13,14,15,16],"verif":35,"visual":[17,18,19,20,21,22,33],"wasserstein":37,"window":35,"work":[43,44,45,46,47,48],"workflow":36,"world":31,"writefil":16,"x":27,"z":27}}) \ No newline at end of file diff --git a/docs/text/_static/custom.css b/docs/text/_static/custom.css new file mode 100644 index 0000000..faf5bbe --- /dev/null +++ b/docs/text/_static/custom.css @@ -0,0 +1 @@ +html[data-theme="light"]{--pst-color-primary:#1683b6;--pst-color-link:#0877aa}html[data-theme="dark"]{--pst-color-primary:#64c8f2;--pst-color-link:#7dd3f5}.bd-main .bd-content .bd-article-container{max-width:1100px}.sd-card{height:100%;border:1px solid var(--pst-color-border);transition:transform .18s ease,box-shadow .18s ease}.sd-card:hover{transform:translateY(-3px);box-shadow:0 .5rem 1rem rgba(0,0,0,.12)}.sd-card-title{color:var(--pst-color-primary);font-weight:700}.example-card img,.related-card img{width:100%;height:210px;object-fit:contain}.related-card img{height:150px}@media(max-width:768px){.example-card img,.related-card img{height:auto;max-height:220px}} diff --git a/docs/text/about.rst b/docs/text/about.rst deleted file mode 100644 index 93fdb65..0000000 --- a/docs/text/about.rst +++ /dev/null @@ -1,15 +0,0 @@ -============ -About pofff -============ - -.. image:: ./figs/readme.png - -The **pofff** tool is funded by the `Center for Sustainable Subsurface Resources (CSSR) `_ [project no. 331841]. - -Contributions are more than welcome using the fork and pull request approach. - -For a new feature, please request this raising an issue. - -Citing ------- -Landa-Marbán, D., Sandve, T.H., Both, J.W., Nordbotten, J.M., and Gasda, S.E., 2026. Performance of an open-source image-based history matching framework for CO2 storage. Transp Porous Med 153, 21, https://doi.org/10.1007/s11242-025-02275-0. diff --git a/docs/text/api.rst b/docs/text/api.rst index 21a55f7..c61965d 100644 --- a/docs/text/api.rst +++ b/docs/text/api.rst @@ -1,17 +1,16 @@ -================= pofff Python API -================= +================ -The main script for the **pofff** executable is located in the core folder. The data from the -FluidFlower benchmark is located in the fluidflower forlder, which is downloaded and modified -from https://github.com/fluidflower. The geology folder contains the geometry to generate the -corner-point grid. The different jobs called by ERT and everest are located in the jobs folder. -The templeates folder contains mako files to generate the input decks. The utils folder has -scripts to comunicate all parts in the framework. Finally, the visualization folder hosts -scripts to postprocess the data. +The top-level executable coordinates configuration, grid and property generation, file writing, simulation, history matching, data processing, and visualization. ``fluidflower`` contains benchmark data, ``geology`` contains corner-point geometry, ``jobs`` contains ERT and Everest jobs, ``templates`` contains Mako inputs, ``utils`` connects the workflows, and ``visualization`` contains benchmark postprocessing. .. figure:: figs/content.png + :alt: Files and directories in the pofff package Files in the pofff package. -.. include:: modules.rst +API files are regenerated under ``docs/text/api`` before each build. + +.. toctree:: + :maxdepth: 2 + + api/modules diff --git a/docs/text/modules.rst b/docs/text/api/modules.rst similarity index 100% rename from docs/text/modules.rst rename to docs/text/api/modules.rst diff --git a/docs/text/pofff.config.config.rst b/docs/text/api/pofff.config.config.rst similarity index 100% rename from docs/text/pofff.config.config.rst rename to docs/text/api/pofff.config.config.rst diff --git a/docs/text/pofff.config.rst b/docs/text/api/pofff.config.rst similarity index 100% rename from docs/text/pofff.config.rst rename to docs/text/api/pofff.config.rst diff --git a/docs/text/pofff.core.pofff.rst b/docs/text/api/pofff.core.pofff.rst similarity index 100% rename from docs/text/pofff.core.pofff.rst rename to docs/text/api/pofff.core.pofff.rst diff --git a/docs/text/pofff.core.rst b/docs/text/api/pofff.core.rst similarity index 100% rename from docs/text/pofff.core.rst rename to docs/text/api/pofff.core.rst diff --git a/docs/text/pofff.jobs.data.rst b/docs/text/api/pofff.jobs.data.rst similarity index 100% rename from docs/text/pofff.jobs.data.rst rename to docs/text/api/pofff.jobs.data.rst diff --git a/docs/text/pofff.jobs.delete.rst b/docs/text/api/pofff.jobs.delete.rst similarity index 100% rename from docs/text/pofff.jobs.delete.rst rename to docs/text/api/pofff.jobs.delete.rst diff --git a/docs/text/pofff.jobs.metric.rst b/docs/text/api/pofff.jobs.metric.rst similarity index 100% rename from docs/text/pofff.jobs.metric.rst rename to docs/text/api/pofff.jobs.metric.rst diff --git a/docs/text/pofff.jobs.rst b/docs/text/api/pofff.jobs.rst similarity index 100% rename from docs/text/pofff.jobs.rst rename to docs/text/api/pofff.jobs.rst diff --git a/docs/text/pofff.rst b/docs/text/api/pofff.rst similarity index 100% rename from docs/text/pofff.rst rename to docs/text/api/pofff.rst diff --git a/docs/text/pofff.utils.inputvalues.rst b/docs/text/api/pofff.utils.inputvalues.rst similarity index 100% rename from docs/text/pofff.utils.inputvalues.rst rename to docs/text/api/pofff.utils.inputvalues.rst diff --git a/docs/text/pofff.utils.mapproperties.rst b/docs/text/api/pofff.utils.mapproperties.rst similarity index 100% rename from docs/text/pofff.utils.mapproperties.rst rename to docs/text/api/pofff.utils.mapproperties.rst diff --git a/docs/text/pofff.utils.rst b/docs/text/api/pofff.utils.rst similarity index 93% rename from docs/text/pofff.utils.rst rename to docs/text/api/pofff.utils.rst index b3c3fd6..9b29470 100644 --- a/docs/text/pofff.utils.rst +++ b/docs/text/api/pofff.utils.rst @@ -10,6 +10,7 @@ Submodules pofff.utils.inputvalues pofff.utils.mapproperties pofff.utils.runs + pofff.utils.terminal pofff.utils.writefile Module contents diff --git a/docs/text/pofff.utils.runs.rst b/docs/text/api/pofff.utils.runs.rst similarity index 100% rename from docs/text/pofff.utils.runs.rst rename to docs/text/api/pofff.utils.runs.rst diff --git a/docs/text/api/pofff.utils.terminal.rst b/docs/text/api/pofff.utils.terminal.rst new file mode 100644 index 0000000..5b2bdbc --- /dev/null +++ b/docs/text/api/pofff.utils.terminal.rst @@ -0,0 +1,8 @@ +pofff.utils.terminal module +=========================== + +.. automodule:: pofff.utils.terminal + :members: + :private-members: + :show-inheritance: + :undoc-members: diff --git a/docs/text/pofff.utils.writefile.rst b/docs/text/api/pofff.utils.writefile.rst similarity index 100% rename from docs/text/pofff.utils.writefile.rst rename to docs/text/api/pofff.utils.writefile.rst diff --git a/docs/text/pofff.visualization.benchmark.rst b/docs/text/api/pofff.visualization.benchmark.rst similarity index 100% rename from docs/text/pofff.visualization.benchmark.rst rename to docs/text/api/pofff.visualization.benchmark.rst diff --git a/docs/text/pofff.visualization.error_table.rst b/docs/text/api/pofff.visualization.error_table.rst similarity index 100% rename from docs/text/pofff.visualization.error_table.rst rename to docs/text/api/pofff.visualization.error_table.rst diff --git a/docs/text/pofff.visualization.everert.rst b/docs/text/api/pofff.visualization.everert.rst similarity index 100% rename from docs/text/pofff.visualization.everert.rst rename to docs/text/api/pofff.visualization.everert.rst diff --git a/docs/text/pofff.visualization.maps.rst b/docs/text/api/pofff.visualization.maps.rst similarity index 100% rename from docs/text/pofff.visualization.maps.rst rename to docs/text/api/pofff.visualization.maps.rst diff --git a/docs/text/pofff.visualization.rst b/docs/text/api/pofff.visualization.rst similarity index 100% rename from docs/text/pofff.visualization.rst rename to docs/text/api/pofff.visualization.rst diff --git a/docs/text/pofff.visualization.sparse_values.rst b/docs/text/api/pofff.visualization.sparse_values.rst similarity index 100% rename from docs/text/pofff.visualization.sparse_values.rst rename to docs/text/api/pofff.visualization.sparse_values.rst diff --git a/docs/text/command-line.rst b/docs/text/command-line.rst new file mode 100644 index 0000000..6581ccd --- /dev/null +++ b/docs/text/command-line.rst @@ -0,0 +1,10 @@ +Command-line reference +====================== + +The reference is derived from the current ``argparse`` parser. + +.. toctree:: + :maxdepth: 1 + + options/general + options/modes diff --git a/docs/text/conf.py b/docs/text/conf.py index 09c6fc4..3c7e09d 100644 --- a/docs/text/conf.py +++ b/docs/text/conf.py @@ -1,30 +1,33 @@ -# Configuration file for the Sphinx documentation builder. -# -# For the full list of built-in configuration values, see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Project information ----------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information - -project = 'pofff' -copyright = '2025-2026, NORCE Research AS' - -# -- General configuration --------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration - -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.napoleon', 'sphinxcontrib.video'] - -templates_path = ['_templates'] -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - - - -# -- Options for HTML output ------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output - -html_theme = 'sphinx_rtd_theme' -html_logo = "./figs/logo.png" +project = "pofff" +copyright = "2025-2026, NORCE Research AS" +author = "NORCE Research AS" +extensions = [ + "sphinx.ext.autodoc", "sphinx.ext.autosummary", "sphinx.ext.intersphinx", + "sphinx.ext.viewcode", "numpydoc", "sphinx_copybutton", "sphinx_design", + "sphinxcontrib.video", + "sphinx_design", +] +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] +autosummary_generate = True +numpydoc_show_class_members = False +html_theme = "pydata_sphinx_theme" +html_logo = "figs/logo.png" +html_static_path = ["_static"] +html_css_files = ["custom.css"] +html_show_sourcelink = True +html_show_sphinx = False +html_last_updated_fmt = "%Y-%m-%d" html_theme_options = { - "logo_only": True, - "style_nav_header_background": "#aaaaaa", + "navbar_start": ["navbar-logo"], "navbar_center": ["navbar-nav"], + "navbar_persistent": ["search-button"], + "navbar_end": ["theme-switcher", "navbar-icon-links"], + "header_links_before_dropdown": 6, "show_nav_level": 2, + "navigation_depth": 4, "show_toc_level": 2, "use_edit_page_button": True, + "icon_links": [ + {"name": "GitHub", "url": "https://github.com/cssr-tools/pofff", "icon": "fa-brands fa-github"}, + {"name": "Report an issue", "url": "https://github.com/cssr-tools/pofff/issues", "icon": "fa-solid fa-bug"}, + ], } +html_context = {"github_user": "cssr-tools", "github_repo": "pofff", "github_version": "main", "doc_path": "docs/text"} +intersphinx_mapping = {"python": ("https://docs.python.org/3", None), "numpy": ("https://numpy.org/doc/stable", None)} diff --git a/docs/text/configuration/complete.rst b/docs/text/configuration/complete.rst new file mode 100644 index 0000000..c1de8be --- /dev/null +++ b/docs/text/configuration/complete.rst @@ -0,0 +1,54 @@ +Complete TOML example +===================== + +Here we use as an example one of the configuration files used in the tests +(see `input.toml `_). +The first input parameter is: + +.. code-block:: python + :linenos: + + # Set mpirun, the full path to the flow executable, and simulator flags (except --output-dir) + flow = "flow --newton-min-iterations=1 --enable-tuning=true --enable-opm-rst-file=true" + +If **flow** is not in your path, then write the full path to the executable, as well as adding mpirun +if this is supported in your machine (e.g., flow = "mpirun -np 8 /Users/dmar/Github/opm/build/opm-simulators/bin/flow \-\-newton-min-iterations=1"). + +The next entries define the following parameters: + +.. code-block:: python + :linenos: + :lineno-start: 4 + + # Set the model parameters + grid="corner-point" # Type of grid (cartesian, tensor, or corner-point) + thickness="final" # Thickness maps (measured 'initial', 'final', or a real positive value) + mult_thickness=1 # Thickness multiplier (a real positive value) + x=[140] # If cartesian, number of x cells [-]; otherwise, variable array of x-refinement + z=[7,5,5,5,5,5,5,8,10,9,5] # If cartesian, number of z cells [-]; if tensor, variable array of z-refinement; if corner-point, fix array of z-refinement (11 entries) + temperature=[20, 20] # Temperature bottom and top rig [C] + pressure=104900 # Pressure at the datum [Pa] + diffusion=[1e-9, 2e-8] # Diffusion (in liquid and gas) [m^2/s] + sources=[[0.9, 0.3], [1.7, 0.7]] # Source positions: x and z coordinates [m], source 1 to 2 + + # Schedule: 1) injection time [s], 2) time step size to write results [s], 3) injection rate [kg/s] (source1), and 4) injection rate [kg/s] (source2) + inj=[[900, 900, 3E-7, 0, '1e-2 3e-4 1e-20 1e-20 1.6 0.2 0.65 1.1']] + + # Facie Properties + facie1={"permx1"=50e3,"permz1"=50e3,"poro1"=0.37,"disperc1"=1e-1,"swi1"=0.32,"sni1"=0.3,"pen1"=1500,"nkrw1"=2,"nkrn1"=2,"npe1"=2,"thre1"=5e-2,"npnt1"=100} + facie2={"permx2"=100e3,"permz2"=100e3,"poro2"=0.38,"disperc2"=1e-1,"swi2"=0.14,"sni2"=0.3,"pen2"=800,"nkrw2"=2,"nkrn2"=2,"npe2"=2,"thre2"=5e-2,"npnt2"=100} + facie3={"permx3"=300e3,"permz3"=300e3,"poro3"=0.40,"disperc3"=1e-1,"swi3"=0.12,"sni3"=0.1,"pen3"=200,"nkrw3"=2,"nkrn3"=2,"npe3"=2,"thre3"=5e-2,"npnt3"=100} + facie4={"permx4"=800e3,"permz4"=800e3,"poro4"=0.39,"disperc4"=1e-1,"swi4"=0.12,"sni4"=0.1,"pen4"=150,"nkrw4"=2,"nkrn4"=2,"npe4"=2,"thre4"=5e-2,"npnt4"=100} + facie5={"permx5"=1500e3,"permz5"=1500e3,"poro5"=0.39,"disperc5"=1e-1,"swi5"=0.12,"sni5"=0.1,"pen5"=100,"nkrw5"=2,"nkrn5"=2,"npe5"=2,"thre5"=5e-2,"npnt5"=100} + facie6={"permx6"=3000e3,"permz6"=3000e3,"poro6"=0.42,"disperc6"=1e-1,"swi6"=0,"sni6"=0,"pen6"=1,"nkrw6"=2,"nkrn6"=2,"npe6"=2,"thre6"=5e-2,"npnt6"=100} + + # Set the saturation functions + krw="(max(0, (sw - swi) / (1 - swi))) ** nkrw" #Wetting rel perm saturation function [-] + krn="(max(0, (1 - sw - sni) / (1 - sni))) ** nkrn" #Non-wetting rel perm saturation function [-] + cap="pen * ((sw-swi) / (1-swi)) ** (-(1.0 / npen))" #Capillary pressure saturation function [Pa] + +Each line adds a description of the variables. For the facie properties, "THREN" is the threshold to evaluate the capillary pressure function to avoid dividing by 0, +and "NPNTN" is the number of points to generate the saturation tables. + +See the input files in the `examples `_ and `publication `_ +to set the history matchings. diff --git a/docs/text/configuration/facies.rst b/docs/text/configuration/facies.rst new file mode 100644 index 0000000..b785fa0 --- /dev/null +++ b/docs/text/configuration/facies.rst @@ -0,0 +1,14 @@ +Facies and saturation functions +=============================== + +Six required ``facieN`` tables define permeability, porosity, dispersion, residual saturations, capillary entry pressure, Corey exponents, capillary exponent, threshold, and table resolution. + +Required keys +------------- + +For each facies ``N``: ``permxN``, ``permzN``, ``poroN``, ``dispercN``, ``swiN``, ``sniN``, ``penN``, ``nkrwN``, ``nkrnN``, ``npeN``, ``threN``, and ``npntN``. ``npntN`` is an integer of at least 2. Porosity and residual saturations lie in [0, 1], and ``swiN + sniN <= 1``. + +``krw``, ``krn``, and ``cap`` +----------------------------- + +These required strings are valid Python expressions used to generate wetting relative permeability, non-wetting relative permeability, and capillary-pressure tables. ``threN`` prevents evaluation too close to a singular endpoint, and ``npntN`` controls the number of table points. diff --git a/docs/text/configuration/history-matching.rst b/docs/text/configuration/history-matching.rst new file mode 100644 index 0000000..dbeff22 --- /dev/null +++ b/docs/text/configuration/history-matching.rst @@ -0,0 +1,21 @@ +ERT and Everest settings +======================== + +History matching requires ``cores``, ``min_realizations_success``, and at least one facies or thickness parameter. + +ERT +--- + +ERT additionally requires ``ertargs``, ``ensembles``, ``enkf_alpha``, and positive ``errors``. Optional ``random_seed`` controls reproducibility. + +Everest +------- + +Everest requires ``max_function_evaluations`` and ``popsize``. Supported differential-evolution options include ``strategy``, ``maxiter``, ``tol``, ``mutation``, ``recombination``, ``rng``, ``disp``, ``polish``, ``init``, ``atol``, ``updating``, ``workers``, ``x0``, ``integrality``, and ``vectorized``. + +History-matching parameters +--------------------------- + +ERT parameters use a distribution name and two parameters. Everest parameters use ``[initial, minimum, maximum, scale]``. Facies properties may be selected by their numbered key, isotropic permeability may use ``permN``, and ``thicknessmult`` controls the thickness multiplier. + +``monotonic = true`` only has an effect when at least two optimized variables form a comparable same-property facies sequence. diff --git a/docs/text/configuration/simulation.rst b/docs/text/configuration/simulation.rst new file mode 100644 index 0000000..649bafd --- /dev/null +++ b/docs/text/configuration/simulation.rst @@ -0,0 +1,32 @@ +Simulation setup +================ + +``flow`` +-------- + +Non-empty OPM Flow command and flags. pofff adds the output directory. Include ``--enable-tuning=true`` when injection rows contain TUNING values. + +``grid`` +-------- + +Accepted values are ``cartesian``, ``tensor``, and ``corner-point``. Cartesian grids require one entry in ``x`` and ``z``. Corner-point grids require 11 entries in ``z``. + +``thickness`` and ``mult_thickness`` +------------------------------------ + +Select ``initial`` or ``final`` measured thickness, or provide a positive physical thickness. ``mult_thickness`` is a positive multiplier. + +``x`` and ``z`` +--------------- + +Positive integer refinement arrays. Their interpretation depends on ``grid``. + +``temperature``, ``pressure``, and ``diffusion`` +------------------------------------------------ + +``temperature`` contains bottom and top values [°C]. ``pressure`` is positive [Pa]. ``diffusion`` contains liquid and gas coefficients [m²/s]. + +``sources`` and ``inj`` +----------------------- + +``sources`` has two ``[x, z]`` rows [m]. Each injection row contains injection time [s], write time step [s], rates for sources 1 and 2 [kg/s], and optionally one TUNING string. diff --git a/docs/text/configuration_file.rst b/docs/text/configuration_file.rst index a61c0f9..a055f08 100644 --- a/docs/text/configuration_file.rst +++ b/docs/text/configuration_file.rst @@ -1,55 +1,12 @@ -================== -Configuration file -================== +Configuration reference +======================= -Here we use as an example one of the configuration files used in the tests -(see `input.toml `_). -The first input parameter is: +pofff uses TOML for simulation, benchmark, ERT, and Everest settings. The parser and validation code define the accepted values and workflow restrictions. -.. code-block:: python - :linenos: +.. toctree:: + :maxdepth: 1 - # Set mpirun, the full path to the flow executable, and simulator flags (except --output-dir) - flow = "flow --newton-min-iterations=1 --enable-tuning=true --enable-opm-rst-file=true" - -If **flow** is not in your path, then write the full path to the executable, as well as adding mpirun -if this is supported in your machine (e.g., flow = "mpirun -np 8 /Users/dmar/Github/opm/build/opm-simulators/bin/flow \-\-newton-min-iterations=1"). - -The next entries define the following parameters: - -.. code-block:: python - :linenos: - :lineno-start: 4 - - # Set the model parameters - grid="corner-point" # Type of grid (cartesian, tensor, or corner-point) - thickness="final" # Thickness maps (measured 'initial', 'final', or a real positive value) - mult_thickness=1 # Thickness multiplier (a real positive value) - x=[140] # If cartesian, number of x cells [-]; otherwise, variable array of x-refinement - z=[7,5,5,5,5,5,5,8,10,9,5] # If cartesian, number of z cells [-]; if tensor, variable array of z-refinement; if corner-point, fix array of z-refinement (18 entries) - temperature=[20, 20] # Temperature bottom and top rig [C] - pressure=104900 # Pressure at the datum [Pa] - diffusion=[1e-9, 2e-8] # Diffusion (in liquid and gas) [m^2/s] - sources=[[0.9, 0.3], [1.7, 0.7]] # Source positions: x and z coordinates [m], source 1 to 2 - - # Schedule: 1) injection time [s], 2) time step size to write results [s], 3) injection rate [kg/s] (source1), and 4) injection rate [kg/s] (source2) - inj=[[900, 900, 3E-7, 0, '1e-2 3e-4 1e-20 1e-20 1.6 0.2 0.65 1.1']] - - # Facie Properties - facie1={"permx1"=50e3,"permz1"=50e3,"poro1"=0.37,"disperc1"=1e-1,"swi1"=0.32,"sni1"=0.3,"pen1"=1500,"nkrw1"=2,"nkrn1"=2,"npe1"=2,"thre1"=5e-2,"npnt1"=100} - facie2={"permx2"=100e3,"permz2"=100e3,"poro2"=0.38,"disperc2"=1e-1,"swi2"=0.14,"sni2"=0.3,"pen2"=800,"nkrw2"=2,"nkrn2"=2,"npe2"=2,"thre2"=5e-2,"npnt2"=100} - facie3={"permx3"=300e3,"permz3"=300e3,"poro3"=0.40,"disperc3"=1e-1,"swi3"=0.12,"sni3"=0.1,"pen3"=200,"nkrw3"=2,"nkrn3"=2,"npe3"=2,"thre3"=5e-2,"npnt3"=100} - facie4={"permx4"=800e3,"permz4"=800e3,"poro4"=0.39,"disperc4"=1e-1,"swi4"=0.12,"sni4"=0.1,"pen4"=150,"nkrw4"=2,"nkrn4"=2,"npe4"=2,"thre4"=5e-2,"npnt4"=100} - facie5={"permx5"=1500e3,"permz5"=1500e3,"poro5"=0.39,"disperc5"=1e-1,"swi5"=0.12,"sni5"=0.1,"pen5"=100,"nkrw5"=2,"nkrn5"=2,"npe5"=2,"thre5"=5e-2,"npnt5"=100} - facie6={"permx6"=3000e3,"permz6"=3000e3,"poro6"=0.42,"disperc6"=1e-1,"swi6"=0,"sni6"=0,"pen6"=1,"nkrw6"=2,"nkrn6"=2,"npe6"=2,"thre6"=5e-2,"npnt6"=100} - - # Set the saturation functions - krw="(max(0, (sw - swi) / (1 - swi))) ** nkrw" #Wetting rel perm saturation function [-] - krn="(max(0, (1 - sw - sni) / (1 - sni))) ** nkrn" #Non-wetting rel perm saturation function [-] - cap="pen * ((sw-swi) / (1-swi)) ** (-(1.0 / npen))" #Capillary pressure saturation function [Pa] - -Each line adds a description of the variables. For the facie properties, "THREN" is the threshold to evaluate the capillary pressure function to avoid dividing by 0, -and "NPNTN" is the number of points to generate the saturation tables. - -See the input files in the `examples `_ and `publication `_ -to set the history matchings. + configuration/simulation + configuration/facies + configuration/history-matching + configuration/complete diff --git a/docs/text/examples.rst b/docs/text/examples.rst index a4446a2..6d0a684 100644 --- a/docs/text/examples.rst +++ b/docs/text/examples.rst @@ -1,140 +1,42 @@ -******** Examples -******** +======== -=========== -Hello world -=========== +These examples complement the :doc:`tutorial`. The tutorial now contains the +complete *Add your results* workflow. This gallery focuses on the remaining +maintained workflows: a small first run, third-party simulator results, ERT and +Everest history matching, and visualization with plopm. -The `examples `_ folder contains a few configuration files -with low grid resolution and shorter simulation times (for initial testing of the framework). For example, by executing: +.. grid:: 1 2 2 2 + :gutter: 2 -.. code-block:: bash + .. grid-item-card:: Hello world + :class-card: example-card + :img-top: figs/map_24h.png + :link: examples/hello-world + :link-type: doc - pofff -i single.toml -t 24,48,72 + Run a low-resolution case and compare the generated 24-hour map. -The following is the figure `map_24h.png`. You can compare your example results to this figure to evaluate if your example ran correctly: + .. grid-item-card:: History matching + :class-card: example-card + :img-top: figs/everest.png + :link: examples/history-matching + :link-type: doc -.. figure:: figs/map_24h.png + Configure and run ERT ensemble studies or Everest optimization. -.. tip:: + .. grid-item-card:: Visualization with plopm + :class-card: example-card + :img-top: figs/pofff+plopm_xco2l.gif + :link: examples/visualization + :link-type: doc - All previous commands can be executed as (`docs_hello_world.sh `_): + Generate customized FluidFlower figures and animations from OPM results. - .. code-block:: bash +.. toctree:: + :hidden: + :maxdepth: 1 - . ./tests/scripts/docs_hello_world.sh - -=================== -Adding your results -=================== - -The `publication `_ folder contains the configuration files used for the results in the -`pofff paper `_ (see :doc:`publication <./publication>` for details in the steps to reproduce the figures in the paper). - -.. code-block:: bash - - pofff -i results.toml -o YOURS -m single -t 24,48,72,96,120 -f all - -This generates the figures in the paper in addition to the new simulation labeled as the name of the output folder ("YOURS" in this case), e.g., for the compare_all_time_series.png: - -.. figure:: figs/compare_all_time_series.png - -.. tip:: - - One can always modify the plotting scripts to change the colors, line styles, font sizes, etc. - -We welcome pull request with your configuration files to the examples folder for cases with better results (less error in the error_table_satmin-0.01_conmin-0.1.csv). - -.. tip:: - - All previous commands can be executed as (`docs_adding_your_results.sh `_): - - .. code-block:: bash - - . ./tests/scripts/docs_adding_your_results.sh - -.. note:: - - One can always also generate the sparse_data.csv, time_series.csv, and spatial_map_Xh.csv files using other simulators and use the pofff tool to compare - to the experimental data, forecast, CSIRO, MIT\_M1, and CSSR data. To this end, you can add those files to a folder call "MY\_SIMULATOR" and execute: - - .. code-block:: bash - - pofff -t 24,48,72,96,120 -m none -o MY_SIMULATOR -f all - - Note that the spatial maps csvs need to be given in the x range of 0 to 2.8 m and z range of 0 to 1.2 m. - -================ -History matching -================ - -The flag **-m** can be set to **everest** or **ert** to run the history matchings, which also requires to set additional -variables in the toml configuration file (e.g., parameters to history match, number of parallel runs, random seed). For example, to run the -last iteration of the history matching in the pofff paper corresponding to `everest_iter_3.toml `_: - -.. code-block:: bash - - pofff -i everest_iter_3.toml -o everest_iter_3 -m everest -t 24,48,72,96,120 -f all - -.. warning:: - - If you are running everest_iter_3.toml locally in your machine, then you might need to decrease the number of parallel runs (cores = 50 in line 39) and maximum function evaluations (max_function_evaluations = 200000) to your system capabilities. - -The possible parameters to hm are the 13 parameters for facie 1 to 6 (e.g., permx2, pen5, np6), in addition to consider isotropic permeability (e.g., perm3), and a constant to multiply the tickness (i.e., thicknessmult). -For additional examples on how to set the history matching studies using ert or everest, -see/run the scripts in the `tests `_ and `publication folder `_. - -.. note:: - - We refer to the documentation of `everest `_ and `ert `_ - for the description of the different keywords. While via the toml configuration files in pofff we have added the most common keywords, then one could always - add additional keywords to the generated (after execution of pofff) everest (everest.yml) and ert (ert.ert) configuration files, and after running the history matching directly using the everest/ert command line executables. - Details on all supported (and format types) variables that can be assigned via toml configuration files can be found in `config.py `_. - After the runs, one can always use pofff to postprocess the data and generate the figures, running with the flag **-m none** (see the - `profiling.py `_ for an example of splitting the generation of the files, running of everest, and postprocessing). - Please raise an issue for missing keywords in the toml configuration files that you would like to be added. - -============= -Visualization -============= -To postprocess the data, `plopm `_ can be used. - -.. tip:: - You can install `plopm `_ by executing in the terminal: - - .. code-block:: bash - - pip install git+https://github.com/cssr-tools/plopm.git - -For example, if you run the `appendixb.toml `_ configuration file and change the 'inj' variable in line 17 to: - -.. code-block:: bash - - inj=[[8100, 300, 3E-7, 0], - [10200, 300, 3E-7, 3E-7], - [3300, 300, 0, 0], - [64800, 3600, 0, 0], - [345600, 21600, 0, 0]] - -Then the following GIF is generated by: - - .. code-block:: bash - - pofff -i appendixb.toml -o pofff+plopm -m single -c '5e-2' -f none - plopm -v xco2l -i 'pofff+plopm/POFFF+PLOPM' -d 16,5 -mask satnum -m gif -dpi 1000 -f 20 -loop 1 -cformat .1e -cbsfax 0.30,0.01,0.4,0.02 -interval 437.5 -maskthr 1e-5 -tunits h -cnum 5 -clabel 'CO$_2$ mass fraction in liquid [-]' -t 'FluidFlower simulation (GitHub/cssr-tools/pofff),' - -.. figure:: figs/pofff+plopm_xco2l.gif - - The total simulation time for this GIF (five days of experiment) using eight CPUs for OPM Flow in a Mac is **LESS THAN TWO MINUTES!!!** - - -See the `plopm online docmunetation `_ for additional information of supported flag parameters to generate customized PNGs and GIFs. - -.. tip:: - - All previous commands can be executed as (`docs_visualization.sh `_): - - .. code-block:: bash - - . ./tests/scripts/docs_visualization.sh + examples/hello-world + examples/history-matching + examples/visualization diff --git a/docs/text/examples/hello-world.rst b/docs/text/examples/hello-world.rst new file mode 100644 index 0000000..6baf6cd --- /dev/null +++ b/docs/text/examples/hello-world.rst @@ -0,0 +1,64 @@ +Hello world +=========== + +Overview +-------- + +The repository `examples folder +`_ contains configuration +files with low grid resolution and short simulation times for initial testing. +This example runs one of those small cases and generates benchmark products at +24, 48, and 72 hours. + +Command +------- + +.. code-block:: console + + pofff -i single.toml -t 24,48,72 + +Result +------ + +The following figure is ``map_24h.png``. Compare your generated result with this +figure to check that the example ran correctly. + +.. figure:: ../figs/map_24h.png + :alt: FluidFlower simulation and experimental contours after 24 hours + + Spatial benchmark map after 24 hours. + +Reproduce the example +--------------------- + +Run the maintained documentation script from the repository root: + +.. code-block:: console + + . ./tests/scripts/docs_hello_world.sh + +.. grid:: 1 1 2 2 + :gutter: 2 + + .. grid-item:: + + .. button-link:: https://github.com/cssr-tools/pofff/blob/main/tests/scripts/docs_hello_world.sh + :color: primary + :outline: + :expand: + + View script + + .. grid-item:: + + .. button-link:: https://raw.githubusercontent.com/cssr-tools/pofff/main/tests/scripts/docs_hello_world.sh + :color: primary + :outline: + :expand: + + View raw script + +.. button-ref:: ../examples + :color: primary + + Back to examples gallery diff --git a/docs/text/examples/history-matching.rst b/docs/text/examples/history-matching.rst new file mode 100644 index 0000000..0fbc762 --- /dev/null +++ b/docs/text/examples/history-matching.rst @@ -0,0 +1,143 @@ +History matching with ERT and Everest +===================================== + +Overview +-------- + +Set ``--mode`` to ``ert`` or ``everest`` to run history matching. These modes +require additional TOML variables, including the parameters to history match, +the number of parallel runs, resource limits, and a random seed when +reproducibility is required. + +Refer to the external references for the underlying configuration keywords: + +* `Everest configuration reference + `__ +* `ERT configuration keyword reference + `__ + +Everest example +--------------- + +The following command runs the final history-matching iteration used in the +pofff paper with ``everest_iter_3.toml``: + +.. code-block:: console + + pofff -i everest_iter_3.toml -o everest_iter_3 -m everest -t 24,48,72,96,120 -f all + +.. grid:: 1 1 2 2 + :gutter: 2 + + .. grid-item:: + + .. button-link:: https://github.com/cssr-tools/pofff/blob/main/publication/everest_iter_3.toml + :color: primary + :outline: + :expand: + + View everest_iter_3.toml + + .. grid-item:: + + .. button-link:: https://raw.githubusercontent.com/cssr-tools/pofff/main/publication/everest_iter_3.toml + :color: primary + :outline: + :expand: + + View raw everest_iter_3.toml + +.. warning:: + + The publication configuration uses ``cores = 50`` and + ``max_function_evaluations = 200000``. When running it locally, decrease the + number of parallel runs and maximum function evaluations to match the CPU, + memory, and time available on your system. + +History-matching parameters +--------------------------- + +The possible history-matching variables include the supported facies properties +for facies 1 through 6, for example ``permx2`` and ``pen5``. The current +configuration model supports the numbered properties ``poroN``, ``permN``, +``permxN``, ``permyN``, ``permzN``, ``dispercN``, ``swiN``, ``sniN``, +``penN``, ``nkrwN``, ``nkrnN``, ``npeN``, ``threN``, and ``npntN`` where +applicable. Isotropic permeability can be selected with a variable such as +``perm3``. The ``thicknessmult`` variable scales the thickness map. + +.. note:: + + History-matching values use different layouts for the two workflows. ERT + parameters contain a distribution name and two parameters. Everest + parameters contain ``[initial, minimum, maximum, scale]``. See + :doc:`../configuration_file` for the maintained TOML reference. + +Additional examples +------------------- + +Additional ERT and Everest examples are available in the pofff repository: + +* `Tests `__ +* `Publication configurations + `__ +* `Configuration data class + `__ + +Generated configurations and direct execution +--------------------------------------------- + +pofff exposes the most commonly used ERT and Everest options through TOML. After +pofff generates ``everest.yml`` or ``ert.txt``, you may add supported keywords +that are not yet exposed by pofff and then run the corresponding Everest or ERT +command-line executable directly. + +Please raise a `pofff issue `_ when a +useful TOML keyword is missing, so it can be considered for the maintained +configuration model. + +Separate generation, execution, and postprocessing +-------------------------------------------------- + +After ERT or Everest has run, pofff can postprocess the existing results and +generate figures without starting another history-matching study: + +.. code-block:: console + + pofff -o HISTORY_MATCHING_OUTPUT -m none -t 24,48,72,96,120 -f all + +The publication ``profiling.py`` workflow demonstrates how to separate file +generation, Everest execution, and postprocessing: + +.. grid:: 1 1 2 2 + :gutter: 2 + + .. grid-item:: + + .. button-link:: https://github.com/cssr-tools/pofff/blob/main/publication/profiling.py + :color: primary + :outline: + :expand: + + View profiling.py + + .. grid-item:: + + .. button-link:: https://raw.githubusercontent.com/cssr-tools/pofff/main/publication/profiling.py + :color: primary + :outline: + :expand: + + View raw profiling.py + +Outputs +------- + +ERT and Everest produce different runtime directories, but both postprocessing +workflows create a ``figures/best_simulation`` folder containing the closest or +optimal simulation and its benchmark tables and figures. See +:doc:`../output_folder` for the output layouts. + +.. button-ref:: ../examples + :color: primary + + Back to examples gallery diff --git a/docs/text/examples/visualization.rst b/docs/text/examples/visualization.rst new file mode 100644 index 0000000..62449a6 --- /dev/null +++ b/docs/text/examples/visualization.rst @@ -0,0 +1,92 @@ +Visualization with plopm +======================== + +Overview +-------- + +`plopm `_ can postprocess the OPM Flow +results generated by pofff and create customized PNG figures and GIF animations. + +Installation +------------ + +.. code-block:: console + + pip install git+https://github.com/cssr-tools/plopm.git + +Configuration +------------- + +Run ``publication/appendixb.toml`` after replacing its ``inj`` schedule with: + +.. code-block:: toml + + inj = [ + [8100, 300, 3E-7, 0], + [10200, 300, 3E-7, 3E-7], + [3300, 300, 0, 0], + [64800, 3600, 0, 0], + [345600, 21600, 0, 0], + ] + +Commands +-------- + +Generate the OPM Flow result without the pofff benchmark figures: + +.. code-block:: console + + pofff -i appendixb.toml -o pofff+plopm -m single -c 5e-2 -f none + +Generate the dissolved-CO2 animation: + +.. code-block:: console + + plopm -v xco2l -i 'pofff+plopm/POFFF+PLOPM' -fs 16,5 -mv satnum -m gif -dpi 1000 -fz 20 -gl 1 -cbf .1e -cbp 0.30,0.01,0.4,0.02 -gi 437.5 -mt 1e-5 -tu h -cbn 5 -cbl 'CO$_2$ mass fraction in liquid [-]' -t 'FluidFlower simulation (GitHub/cssr-tools/pofff),' + +Result +------ + +.. figure:: ../figs/pofff+plopm_xco2l.gif + :alt: Dissolved CO2 animation generated from a pofff simulation with plopm + + The documented five-day simulation completed in less than two minutes using + eight OPM Flow CPUs on a Mac. + +See the `plopm documentation `_ for the +complete plotting and animation reference. + +Reproduce the example +--------------------- + +Run the maintained documentation script from the repository root: + +.. code-block:: console + + . ./tests/scripts/docs_visualization.sh + +.. grid:: 1 1 2 2 + :gutter: 2 + + .. grid-item:: + + .. button-link:: https://github.com/cssr-tools/pofff/blob/main/tests/scripts/docs_visualization.sh + :color: primary + :outline: + :expand: + + View script + + .. grid-item:: + + .. button-link:: https://raw.githubusercontent.com/cssr-tools/pofff/main/tests/scripts/docs_visualization.sh + :color: primary + :outline: + :expand: + + View raw script + +.. button-ref:: ../examples + :color: primary + + Back to examples gallery diff --git a/docs/text/index.rst b/docs/text/index.rst index 7170253..400a454 100644 --- a/docs/text/index.rst +++ b/docs/text/index.rst @@ -1,25 +1,120 @@ -.. pofff documentation master file +pofff +===== -Welcome to pofff's documentation! -================================== +.. rst-class:: lead + + An open-source image-based history-matching framework for the FluidFlower + benchmark using OPM Flow, ERT, and Everest. + +**pofff** generates FluidFlower simulation models, runs OPM Flow, writes +benchmark CSV files, compares simulations with experimental and published +results, and supports history matching with ERT and Everest. + +.. grid:: 1 2 2 4 + :gutter: 3 + :margin: 4 0 4 0 + + .. grid-item-card:: :octicon:`rocket;1.2em` Get started + :link: introduction + :link-type: doc + + Learn the main simulation, benchmark, and history-matching workflows. + + .. grid-item-card:: :octicon:`download;1.2em` Install + :link: installation + :link-type: doc + + Install pofff, OPM Flow, ERT, Everest, and visualization tools. + + .. grid-item-card:: :octicon:`gear;1.2em` Configure a case + :link: configuration_file + :link-type: doc + + Define the grid, thickness, facies, sources, injection schedule, and + history-matching settings. + + .. grid-item-card:: :octicon:`book;1.2em` Follow the tutorial + :link: tutorial + :link-type: doc + + Run a simulation and add its results to the FluidFlower comparisons. + +Quick installation +------------------ + +Install the current development version: + +.. code-block:: console + + pip install git+https://github.com/cssr-tools/pofff.git + +See :doc:`installation` for virtual environments, OPM Flow, ERT, Everest, +ResInsight, plopm, optional LaTeX support, and installation from source. + +Quick start +----------- + +Run a FluidFlower simulation and generate benchmark results at 24, 48, and +72 hours: + +.. code-block:: console + + pofff -i examples/single.toml -o output -t 24,48,72 + +Generate only the OPM Flow and workflow input files: + +.. code-block:: console + + pofff -i examples/single.toml -o output -m files -f none + +Display the available command-line options: + +.. code-block:: console + + pofff --help + +See :doc:`tutorial` for a guided simulation and comparison workflow, +:doc:`examples` for focused applications, and :doc:`command-line` for exact +syntax, accepted values, defaults, and option compatibility. + +What can pofff do? +------------------ + +.. grid:: 1 2 2 2 + :gutter: 3 + + .. grid-item-card:: Generate FluidFlower models + + Create Cartesian, tensor, and corner-point grids with geological facies, + thickness maps, sources, observation sensors, and benchmark regions. + + .. grid-item-card:: Run configurable workflows + + Generate input files, run OPM Flow, process benchmark data, and create + figures independently or as a connected workflow. + + .. grid-item-card:: Write and compare benchmark results + + Export sparse time-series and dense spatial CSV data, then compare local + or external simulations with experimental and published results. + + .. grid-item-card:: Perform history matching + + Run ERT ensemble studies or Everest differential-evolution optimization, + then extract and postprocess the best simulation. .. toctree:: - :maxdepth: 4 + :hidden: + :maxdepth: 2 introduction installation configuration_file + tutorial examples publication + command-line api output_folder contributing related - about - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/text/installation.rst b/docs/text/installation.rst index ef29fab..b1330f0 100644 --- a/docs/text/installation.rst +++ b/docs/text/installation.rst @@ -1,4 +1,3 @@ -============ Installation ============ @@ -146,3 +145,19 @@ You can check if the installation of OPM Flow succeded by typing in the terminal Source build in macOS +++++++++++++++++++++ See `this repository `_ dedicated to build OPM Flow from source in the latest macOS (GitHub actions), and tested with **pycopm**, another repository in cssr-tools. + + +Verification +------------ + +.. code-block:: console + + pofff --help + flow --help + ert --help + everest --help + +Next steps +---------- + +Continue with :doc:`tutorial` or review :doc:`configuration_file`. diff --git a/docs/text/introduction.rst b/docs/text/introduction.rst index 9218256..24b5af7 100644 --- a/docs/text/introduction.rst +++ b/docs/text/introduction.rst @@ -1,36 +1,105 @@ -============ +.. _introduction: + Introduction ============ .. video:: figs/pofff_fluidflower.mp4 :width: 700 + :align: center -.. image:: ./figs/pofff.png +.. image:: figs/pofff.png + :alt: FluidFlower simulation and history matching with pofff + :align: center -This documentation describes the **pofff** tool hosted in `https://github.com/cssr-tools/pofff `_. +**pofff** is an open-source, image-based history-matching framework for the +`FluidFlower benchmark `_. It uses a +:doc:`configuration file ` to generate OPM Flow input, +run simulations, write benchmark data, compare simulations with experimental +observations, and create publication-quality figures. -Concept -------- -User-friendly open-source image-based history-matching framework for the FluidFlower benchmark study using OPM Flow. +Core workflows +-------------- -.. _overview: +* Generate Cartesian, tensor, or corner-point FluidFlower grids. +* Assign geological facies, thickness maps, source locations, reporting + regions, and observation sensors. +* Set rock, fluid, saturation-function, diffusion, and injection-schedule + properties. +* Generate OPM Flow decks without running a simulation. +* Run a complete FluidFlower simulation with OPM Flow. +* Write sparse time-series and dense spatial data in benchmark format. +* Compare simulation results with experimental, forecast, CSIRO, MIT_M1, CSSR, + and other published data. +* Run ensemble history matching with ERT. +* Run differential-evolution optimization with Everest. +* Create benchmark maps, time-series comparisons, Wasserstein-distance + metrics, error tables, and publication figures. +* Postprocess benchmark-format results generated by other simulators. -Overview --------- -The current implementation supports the following executable with the argument options: +Basic usage +----------- + +Run a FluidFlower simulation and generate benchmark results: + +.. code-block:: console + + pofff -i examples/single.toml -o output -t 24,48,72 + +Use :doc:`command-line` for exact option syntax and :doc:`examples` for +additional reproducible workflows. -.. code-block:: bash +To add a simulation to the published FluidFlower comparisons, follow the +:doc:`tutorial`. + +About the project +----------------- + +.. image:: figs/readme.png + :alt: Project context for pofff + :align: center + :width: 65% + +**pofff** is funded by the `Center for Sustainable Subsurface Resources +`_ under project number 331841. + +The framework was developed to support reproducible simulation, benchmark +processing, visualization, and history matching for the FluidFlower benchmark. +It integrates OPM Flow with ERT, Everest, FluidFlower experimental data, and +open-source visualization tools. + +See the `pofff paper +`_ for the +scientific description, benchmark results, history-matching studies, and +computational analysis. + +Citation +-------- - pofff -i name_of_input_file.toml +Landa-Marbán, D., Sandve, T. H., Both, J. W., Nordbotten, J. M., and +Gasda, S. E. (2026). Performance of an open-source image-based history +matching framework for CO2 storage. *Transport in Porous Media*, 153, 21. +https://doi.org/10.1007/s11242-025-02275-0 -where +Where to continue +----------------- --i The base name of the :doc:`toml configuration file <./configuration_file>`, ('input.toml' by default). --o The base name of the :doc:`output folder <./output_folder>` ('output' by default). --f Use 'all' to generate all benchmark figures, 'basic' to not generate the Wasserstain distance plot (it is slow), and 'none' for no figures ('basic' by default). --m Run a 'single' simulation, generate only the input 'files', generate only the csv 'data', 'everest', 'ert', 'fair', or 'none' ('none' is useful to generate only figures in combination to '-f') ('single' by default). --t Times in hours separated by commas to evaluate the metrics ('0.25' by default). --e Experimental data to history match, valid options are C1 to C5 ('C2' by default). --s The minimum saturation above which gaseous CO2 is considered for the segmentation ('1e-2' by default). --c The minimum concentration above which CO2 is considered to be dissolved for the segmentation ('1e-1' by default). --u Use the precomputed Wasserstein distance matrix values for minimum concentration of 1e-1 and 5e-2 (minimum saturation of 1e-2) to speed up the computations ('1' by default; set to '0' to compute all). +* Complete the :doc:`installation` and verify the Python package, OPM Flow, + ERT, Everest, and optional visualization tools. +* Use the :doc:`configuration_file` reference to understand simulation, + benchmark, ERT, and Everest TOML variables. +* Follow the :doc:`tutorial` to run a FluidFlower simulation and add its + results to the published benchmark comparisons. +* Browse :doc:`examples` for a small first run, external-simulator results, + history matching, and visualization with plopm. +* Use :doc:`publication` to reproduce the figures, tables, sensitivity + studies, profiling results, and accuracy-time analysis from the pofff paper. +* Review :doc:`output_folder` to understand the files generated by single-run, + ERT, and Everest workflows. +* Use :doc:`command-line` for exact CLI syntax, defaults, accepted values, and + option compatibility. +* Browse :doc:`api` for the Python modules, data classes, functions, and + internal workflow utilities. +* See :doc:`contributing` to report issues, request features, submit improved + configurations, or contribute to **pofff**. +* Explore :doc:`related` for complementary open-source simulation, + visualization, and modeling tools. diff --git a/docs/text/make.bat b/docs/text/make.bat index 32bb245..b37a2ed 100644 --- a/docs/text/make.bat +++ b/docs/text/make.bat @@ -8,7 +8,7 @@ if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. -set BUILDDIR=_build +set BUILDDIR=../_build %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( diff --git a/docs/text/options/general.rst b/docs/text/options/general.rst new file mode 100644 index 0000000..7155d9e --- /dev/null +++ b/docs/text/options/general.rst @@ -0,0 +1,135 @@ +General options +=============== + +The following options control the input configuration, output directory, +execution mode, benchmark evaluation, and segmentation thresholds. + +.. program:: pofff + +Input configuration +------------------- + +.. option:: -i , --input + + TOML configuration file. The default is ``input.toml``. + + This option is not used by the ``fair`` and ``none`` modes. + +Output directory +---------------- + +.. option:: -o , --output + + Output directory. The default is ``output``. + +Figure generation +----------------- + +.. option:: -f , --figures + + Select which benchmark figures to generate. + + Accepted values are: + + ``all`` + Generate all benchmark figures, including the Wasserstein-distance + comparisons. + + ``basic`` + Generate the basic comparison figures without the slower + Wasserstein-distance plots. This is the default. + + ``none`` + Do not generate figures. + +Execution mode +-------------- + +.. option:: -m , --mode + + Select the pofff workflow to execute. + + Accepted values are: + + ``single`` + Generate the input files, run OPM Flow, process the benchmark data, and + optionally generate figures. This is the default. + + ``files`` + Generate the input files without running OPM Flow. + + ``data`` + Generate benchmark data from existing simulation results. + + ``everest`` + Run an Everest optimization workflow. + + ``ert`` + Run an ERT ensemble history-matching workflow. + + ``fair`` + Generate the maintained FAIR benchmark comparisons using the + workflow-specific settings. + + ``none`` + Skip model generation and simulation. Use this mode to postprocess + existing benchmark-format results. + +Evaluation times +---------------- + +.. option:: -t , --times + + Comma-separated positive finite evaluation times in hours. + + The default is ``0.25``. For example: + + .. code-block:: console + + pofff -t 24,48,72,96,120 + +Experimental realization +------------------------ + +.. option:: -e , --experiment + + Experimental FluidFlower realization used for comparison or history + matching. + + Accepted values are ``C1``, ``C2``, ``C3``, ``C4``, and ``C5``. The default + is ``C2``. + +Minimum gas saturation +---------------------- + +.. option:: -s , --minimumsaturation + + Minimum gas saturation used when segmenting gaseous CO2. + + The value must be finite and lie between 0 and 1, inclusive. The default is + ``1e-2``. + +Minimum dissolved concentration +------------------------------- + +.. option:: -c , --minimumconcentration + + Minimum concentration used when segmenting dissolved CO2. + + Accepted values are ``5e-2`` and ``1e-1``. The default is ``1e-1``. + +Precomputed Wasserstein distances +--------------------------------- + +.. option:: -u , --use + + Control whether precomputed Wasserstein-distance values may be used when + available. + + Accepted values are: + + ``1`` + Use precomputed values when available. This is the default. + + ``0`` + Recalculate the Wasserstein distances. diff --git a/docs/text/options/modes.rst b/docs/text/options/modes.rst new file mode 100644 index 0000000..5d041d3 --- /dev/null +++ b/docs/text/options/modes.rst @@ -0,0 +1,4 @@ +Execution modes +=============== + +``single`` generates files, runs OPM Flow, creates benchmark data, and optionally creates figures. ``files`` creates inputs only and cannot use ``-f all``. ``data`` processes an existing simulation. ``ert`` and ``everest`` run their corresponding history-matching workflows. ``fair`` uses fixed benchmark comparison settings and rejects conflicting input, figure, time, and experiment options. ``none`` skips simulation work and is useful for plotting existing results. diff --git a/docs/text/output_folder.rst b/docs/text/output_folder.rst index 1a78e9e..98605d6 100644 --- a/docs/text/output_folder.rst +++ b/docs/text/output_folder.rst @@ -1,6 +1,5 @@ -************* Output folder -************* +============= ========== Single run @@ -42,3 +41,6 @@ in the `ert.toml `_. +This section describes how to reproduce the figures, tables, sensitivity studies, and computational results in the `pofff paper `_. -To this end, we use our friend `plopm `_. All commands are executed from the pofff main directory, -and the figures are saved in new folders corresponding to the figure number. +The workflows use `plopm `_ for visualization. Unless stated otherwise, run all commands from the pofff repository root. Each workflow writes to a directory named after the corresponding figure or group of figures. + +Prerequisites +------------- .. tip:: You can install `plopm `_ by executing in the terminal: @@ -14,16 +15,21 @@ and the figures are saved in new folders corresponding to the figure number. pip install git+https://github.com/cssr-tools/plopm.git -Figure 3: +Figure 3: geology and thickness +------------------------------- + .. code-block:: bash pofff -i examples/input.toml -o figure3 -f none - plopm -i figure3/FIGURe3 -c '101;64;147 81;124;66 181;73;57 193;127;97 127;148;191 193;147;56' -cticks '[G, F, E, D, C, ESF]' -v 'pvtnum - 1 - satnum' -grid 'black,1e-2' -remove 1,1,0,1 -d 20,15 -o figure3 -save figure3a -f 20 -clabel 'Sand' - plopm -i figure3/FIGURe3 -v 'multx * 1.75' -grid 'black,1e-2' -remove 1,1,0,1 -d 20,15 -clabel 'Thickness map [cm]' -cformat .2f -cnum 5 -f 20 -o figure3 -save figure3b + plopm -i figure3/FIGURe3 -c '101;64;147 81;124;66 181;73;57 193;127;97 127;148;191 193;147;56' -cbt '[G, F, E, D, C, ESF]' -v 'pvtnum - 1 - satnum' -ge 'black,1e-2' -hide 1,1,0,1 -fs 20,15 -o figure3 -fn figure3a -fz 20 -cbl 'Sand' + plopm -i figure3/FIGURe3 -v 'multx * 1.75' -ge 'black,1e-2' -hide 1,1,0,1 -fs 20,15 -cbl 'Thickness map [cm]' -cbf .2f -cbn 5 -fz 20 -o figure3 -fn figure3b + +Figure 4: SPE11A grid comparison +-------------------------------- Figure 4 requires running the SPE11A cases using `pyopmspe11 `_, which uses the simulation grid. You might need to edit the configuration files to -adjust the computational resources to your machine (e.g., `r3_cp_1cmish_capmax2500Pa.toml `_ is run with 32 cpus). +adjust the computational resources to your machine (e.g., `r3_cp_1cmish_capmax2500Pa.toml `_ is run with 32 CPUs). .. tip:: You can install `pyopmspe11 `_ by executing in the terminal: @@ -35,7 +41,7 @@ adjust the computational resources to your machine (e.g., `r3_cp_1cmish_capmax25 .. code-block:: bash mkdir figure4 && cd figure4 - curl -O https://raw.githubusercontent.com/OPM/pyopmspe11/refs/heads/main/benchmark/spe11a/r3_cp_1cmish_capmax2500Pa.toml + curl -O https://raw.githubusercontent.com/OPM/pyopmspe11/refs/heads/main/benchmark/spe11a/r2_Cart_1cm_capmax2500Pa.toml curl -O https://raw.githubusercontent.com/OPM/pyopmspe11/refs/heads/main/benchmark/spe11a/r3_cp_1cmish_capmax2500Pa.toml curl -O https://raw.githubusercontent.com/OPM/pyopmspe11/refs/heads/main/benchmark/spe11a/r4_Cart_1mm_capmax2500Pa.toml curl -O https://raw.githubusercontent.com/OPM/pyopmspe11/refs/heads/main/benchmark/spe11a/r5_Cart_1mm_capmax2500Pa_strictol.toml @@ -43,7 +49,10 @@ adjust the computational resources to your machine (e.g., `r3_cp_1cmish_capmax25 pyopmspe11 -i r3_cp_1cmish_capmax2500Pa.toml -o r3_cp_1cmish_capmax2500Pa -t 1 -r 280,1,120 -w 0.16666666666666666 pyopmspe11 -i r4_Cart_1mm_capmax2500Pa.toml -o r4_Cart_1mm_capmax2500Pa -t 1 -r 280,1,120 -w 0.16666666666666666 pyopmspe11 -i r5_Cart_1mm_capmax2500Pa_strictol.toml -o r5_Cart_1mm_capmax2500Pa_strictol -t 1 -r 280,1,120 -w 0.16666666666666666 - plopm -v xco2l -r 53 -mask satnum -maskthr 7e-5 -i 'r2_Cart_1cm_capmax2500Pa/flow/R2_CART_1CM_CAPMAX2500PA r3_cp_1cmish_capmax2500Pa/flow/R3_CP_1CMISH_CAPMAX2500PA r4_Cart_1mm_capmax2500Pa/flow/R4_CART_1MM_CAPMAX2500PA r5_Cart_1mm_capmax2500Pa_strictol/flow/R5_CART_1MM_CAPMAX2500PA_STRICTOL' -cnum 3 -xlnum 8 -clabel 'OPM results for SPE11A: CO$_2$ mass fraction (liquid phase) after 2 days' -d 16,6.5 -t "(a) Cartesian grid 1cm (b) Corner-point grid 1cmish (c) Cartesian grid 1mm (d) Cartesian grid 1mm stricter tolerances" -yunits cm -xunits cm -yformat .0f -xformat .0f -f 16 -save figure4 -cformat .2e -suptitle 0 -subfigs 2,2 -cbsfax 0.35,0.97,0.3,0.02 -delax 1 -c '#9ca245 #9da347 #9fa44a #a0a64d #a2a750 #a3a953 #a5aa56 #a6ac59 #a8ad5c #a9af5f #abb062 #adb164 #aeb367 #b0b46a #b1b66d #b3b770 #b4b973 #b6ba76 #b7bc79 #b9bd7c #babf7f #bcc082 #bec184 #bfc387 #c1c48a #c2c68d #c4c790 #c5c993 #c7ca96 #c8cc99 #cacd9c #cbcf9f #cdd0a2 #cfd1a4 #d0d3a7 #d2d4aa #d3d6ad #d5d7b0 #d6d9b3 #d8dab6 #d9dcb9 #dbddbc #dcdfbf #dee0c1 #e0e1c4 #e1e3c7 #e3e4ca #e4e6cd #e6e7d0 #e7e9d3 #e9ead6 #eaecd9 #eceddc #edefdf #eff0e1 #f1f1e4 #f2f3e7 #f4f4ea #f5f6ed #f7f7f0 #f8f9f3 #fafaf6 #fbfcf9 #fdfdfc #ffffff #fefbfb #fdf7f7 #fcf3f3 #fbefef #faebeb #f9e7e7 #f8e3e3 #f7e0e0 #f6dcdc #f5d8d8 #f4d4d4 #f3d0d0 #f2cccc #f1c8c8 #f0c5c5 #efc1c1 #eebdbd #edb9b9 #ecb5b5 #ebb1b1 #eaadad #e9aaaa #e8a6a6 #e7a2a2 #e69e9e #e59a9a #e49696 #e39292 #e28f8f #e18b8b #e08787 #df8383 #de7f7f #dd7b7b #dc7777 #db7474 #da7070 #d96c6c #d86868 #d76464 #d66060 #d55c5c #d45959 #d35555 #d25151 #d14d4d #d04949 #cf4545 #ce4141 #cd3e3e #cc3a3a #cb3636 #ca3232 #c92e2e #c82a2a #c72626 #c62323 #c51f1f #c41b1b #c31717 #c21313 #c10f0f #c00b0b' + plopm -v xco2l -r 53 -mv satnum -mt 7e-5 -i 'r2_Cart_1cm_capmax2500Pa/flow/R2_CART_1CM_CAPMAX2500PA r3_cp_1cmish_capmax2500Pa/flow/R3_CP_1CMISH_CAPMAX2500PA r4_Cart_1mm_capmax2500Pa/flow/R4_CART_1MM_CAPMAX2500PA r5_Cart_1mm_capmax2500Pa_strictol/flow/R5_CART_1MM_CAPMAX2500PA_STRICTOL' -cbn 3 -xnt 8 -cbl 'OPM results for SPE11A: CO$_2$ mass fraction (liquid phase) after 2 days' -fs 16,6.5 -t "(a) Cartesian grid 1cm (b) Corner-point grid 1cmish (c) Cartesian grid 1mm (d) Cartesian grid 1mm stricter tolerances" -yu cm -xu cm -yf .0f -xf .0f -fz 16 -fn figure4 -cbf .2e -st 0 -sg 2,2 -cbp 0.35,0.97,0.3,0.02 -rdl 1 -c '#9ca245 #9da347 #9fa44a #a0a64d #a2a750 #a3a953 #a5aa56 #a6ac59 #a8ad5c #a9af5f #abb062 #adb164 #aeb367 #b0b46a #b1b66d #b3b770 #b4b973 #b6ba76 #b7bc79 #b9bd7c #babf7f #bcc082 #bec184 #bfc387 #c1c48a #c2c68d #c4c790 #c5c993 #c7ca96 #c8cc99 #cacd9c #cbcf9f #cdd0a2 #cfd1a4 #d0d3a7 #d2d4aa #d3d6ad #d5d7b0 #d6d9b3 #d8dab6 #d9dcb9 #dbddbc #dcdfbf #dee0c1 #e0e1c4 #e1e3c7 #e3e4ca #e4e6cd #e6e7d0 #e7e9d3 #e9ead6 #eaecd9 #eceddc #edefdf #eff0e1 #f1f1e4 #f2f3e7 #f4f4ea #f5f6ed #f7f7f0 #f8f9f3 #fafaf6 #fbfcf9 #fdfdfc #ffffff #fefbfb #fdf7f7 #fcf3f3 #fbefef #faebeb #f9e7e7 #f8e3e3 #f7e0e0 #f6dcdc #f5d8d8 #f4d4d4 #f3d0d0 #f2cccc #f1c8c8 #f0c5c5 #efc1c1 #eebdbd #edb9b9 #ecb5b5 #ebb1b1 #eaadad #e9aaaa #e8a6a6 #e7a2a2 #e69e9e #e59a9a #e49696 #e39292 #e28f8f #e18b8b #e08787 #df8383 #de7f7f #dd7b7b #dc7777 #db7474 #da7070 #d96c6c #d86868 #d76464 #d66060 #d55c5c #d45959 #d35555 #d25151 #d14d4d #d04949 #cf4545 #ce4141 #cd3e3e #cc3a3a #cb3636 #ca3232 #c92e2e #c82a2a #c72626 #c62323 #c51f1f #c41b1b #c31717 #c21313 #c10f0f #c00b0b' + +Using submitted SPE11A CSV data ++++++++++++++++++++++++++++++++ A similar figure without the need of running the simulations (not showing the sands in the background and using the reporting grid) can be obtained by downloading the SPE11A benchmark data in csv format available at `this website `_ (the submitted benchmark data does not include the `r5_Cart_1mm_capmax2500Pa_strictol.toml `_ results): @@ -57,9 +66,12 @@ A similar figure without the need of running the simulations (not showing the sa unzip 375716 curl -L -O https://darus.uni-stuttgart.de/api/access/datafile/375707 unzip 375707 - plopm -v xco2l -i 'spe11a/opm2/spe11a_spatial_map_48h spe11a/opm3/spe11a_spatial_map_48h spe11a/opm4/spe11a_spatial_map_48h' -csv "1,2,5;1,2,5;1,2,5" -c cet_diverging_gwr_55_95_c38 -cnum 3 -xlnum 8 -clabel 'OPM results for SPE11A: CO$_2$ mass fraction (liquid phase) after 2 days' -d 16,6.5 -t "(a) Cartesian grid 1cm (b) Corner-point grid 1cmish (c) Cartesian grid 1mm" -yunits cm -xunits cm -yformat .0f -xformat .0f -f 16 -save figure4ish -cformat .2e -suptitle 0 -subfigs 2,2 -cbsfax 0.35,0.97,0.3,0.02 -delax 1 + plopm -v xco2l -i 'spe11a/opm2/spe11a_spatial_map_48h spe11a/opm3/spe11a_spatial_map_48h spe11a/opm4/spe11a_spatial_map_48h' -cc "1,2,5;1,2,5;1,2,5" -c cet_diverging_gwr_55_95_c38 -cbn 3 -xnt 8 -cbl 'OPM results for SPE11A: CO$_2$ mass fraction (liquid phase) after 2 days' -fs 16,6.5 -t "(a) Cartesian grid 1cm (b) Corner-point grid 1cmish (c) Cartesian grid 1mm" -yu cm -xu cm -yf .0f -xf .0f -fz 16 -fn figure4ish -cbf .2e -st 0 -sg 2,2 -cbp 0.35,0.97,0.3,0.02 -rdl 1 + +Figures 5 to 8 and Table 4 +-------------------------- -Figures 5 (g, h, i), 6, 7, and 8 (in addition, the values on Table 4 are obtained from the generated error_table_satmin-0.01_conmin-0.1.csv): +Figures 5 (g, h, i), 6, 7, and 8 (in addition, the values on Table 4 are obtained from the generated ``error_table_satmin-0.01_conmin-0.1.csv``): .. code-block:: bash @@ -68,36 +80,48 @@ Figures 5 (g, h, i), 6, 7, and 8 (in addition, the values on Table 4 are obtaine where fair attempts to aling with `these principles `_. -Similarly, Figures 10 (g, h, i) and 11 (in addition, the values on Table 5 are obtained from the generated error_table_satmin-0.01_conmin-0.05.csv) +Figures 10 and 11 and Table 5 +----------------------------- + +Similarly, Figures 10 (g, h, i) and 11 (in addition, the values on Table 5 are obtained from the generated ``error_table_satmin-0.01_conmin-0.05.csv``) .. code-block:: bash mkdir figures10-11 && cd figures10-11 pofff -m fair -c 0.05 -See the **Adding your results** subsection in the :doc:`examples <./examples>` to compare your customized simulation results to the paper results. +See :doc:`tutorial` to add a customized simulation to the paper comparisons. + +Figures 5d to 5f: dissolved CO2 maps +------------------------------------ For the spatial maps of CO2 concentration in Figures 5d to 5f: .. code-block:: bash pofff -i publication/results.toml -o figures5d-f -m single -t 24,48,72,96,120 - plopm -i figures5d-f/FIGURES5D-F -v xco2l -o figures5d-f -remove 1,1,1,1 -r 3 -save figure5d -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' - plopm -i figures5d-f/FIGURES5D-F -v xco2l -o figures5d-f -remove 1,1,1,1 -r 5 -save figure5e -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' - plopm -i figures5d-f/FIGURES5D-F -v xco2l -o figures5d-f -remove 1,1,1,1 -r 7 -save figure5f -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' + plopm -i figures5d-f/FIGURES5D-F -v xco2l -o figures5d-f -hide 1,1,1,1 -r 3 -fn figure5d -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' + plopm -i figures5d-f/FIGURES5D-F -v xco2l -o figures5d-f -hide 1,1,1,1 -r 5 -fn figure5e -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' + plopm -i figures5d-f/FIGURES5D-F -v xco2l -o figures5d-f -hide 1,1,1,1 -r 7 -fn figure5f -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' + +Figures 10d to 10f: dissolved CO2 maps +-------------------------------------- Similarly, for the spatial maps of CO2 concentration in Figures 10d to 10f: .. code-block:: bash pofff -i publication/appendixb.toml -o figures10d-f -c '5e-2' -m single -t 24,48,72,96,120 - plopm -i figures10d-f/FIGURES10D-F -v xco2l -o figures10d-f -remove 1,1,1,1 -r 3 -save figure10d -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' - plopm -i figures10d-f/FIGURES10D-F -v xco2l -o figures10d-f -remove 1,1,1,1 -r 5 -save figure10e -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' - plopm -i figures10d-f/FIGURES10D-F -v xco2l -o figures10d-f -remove 1,1,1,1 -r 7 -save figure10f -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' + plopm -i figures10d-f/FIGURES10D-F -v xco2l -o figures10d-f -hide 1,1,1,1 -r 3 -fn figure10d -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' + plopm -i figures10d-f/FIGURES10D-F -v xco2l -o figures10d-f -hide 1,1,1,1 -r 5 -fn figure10e -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' + plopm -i figures10d-f/FIGURES10D-F -v xco2l -o figures10d-f -hide 1,1,1,1 -r 7 -fn figure10f -c '#636b45 #636b45 #fffd00 #fffc00 #fffb00 #fffa00 #fff900 #fff800 #fff700 #fff600 #fff500 #fff400 #fff300 #fff200 #fff100 #fff000 #ffef00 #ffee00 #ffed00 #ffec00 #ffeb00 #ffea00 #ffe900 #ffe800 #ffe700 #ffe600 #ffe500 #ffe400 #ffe300 #ffe200 #ffe100 #ffe000 #ffdf00 #ffde00 #ffdd00 #ffdc00 #ffdb00 #ffda00 #ffd900 #ffd800 #ffd700 #ffd600 #ffd500 #ffd400 #ffd300 #ffd200 #ffd100 #ffd000 #ffcf00 #ffce00 #ffcd00 #ffcc00 #ffcb00 #ffca00 #ffc900 #ffc800 #ffc700 #ffc600 #ffc500 #ffc400 #ffc300 #ffc200 #ffc100 #ffc000 #ffbf00 #ffbe00 #ffbd00 #ffbc00 #ffbb00 #ffba00 #ffb900 #ffb800 #ffb700 #ffb600 #ffb500 #ffb400 #ffb300 #ffb200 #ffb100 #ffb000 #ffaf00 #ffae00 #ffad00 #ffac00 #ffab00 #ffaa00 #ffa900 #ffa800 #ffa700 #ffa600 #ffa500 #ffa400 #ffa300 #ffa200 #ffa100 #ffa000 #ff9f00 #ff9e00 #ff9d00 #ff9c00 #ff9b00 #ff9a00 #ff9900 #ff9800 #ff9700 #ff9600 #ff9500 #ff9400 #ff9300 #ff9200 #ff9100 #ff9000 #ff8f00 #ff8e00 #ff8d00 #ff8c00 #ff8b00 #ff8a00 #ff8900 #ff8800 #ff8700 #ff8600 #ff8500 #ff8400 #ff8300 #ff8200 #ff8100 #ff8000 #ff7f00 #ff7e00 #ff7d00 #ff7c00 #ff7b00 #ff7a00 #ff7900 #ff7800 #ff7700 #ff7600 #ff7500 #ff7400 #ff7300 #ff7200 #ff7100 #ff7000 #ff6f00 #ff6e00 #ff6d00 #ff6c00 #ff6b00 #ff6a00 #ff6900 #ff6800 #ff6700 #ff6600 #ff6500 #ff6400 #ff6300 #ff6200 #ff6100 #ff6000 #ff5f00 #ff5e00 #ff5d00 #ff5c00 #ff5b00 #ff5a00 #ff5900 #ff5800 #ff5700 #ff5600 #ff5500 #ff5400 #ff5300 #ff5200 #ff5100 #ff5000 #ff4f00 #ff4e00 #ff4d00 #ff4c00 #ff4b00 #ff4a00 #ff4900 #ff4800 #ff4700 #ff4600 #ff4500 #ff4400 #ff4300 #ff4200 #ff4100 #ff4000 #ff3f00 #ff3e00 #ff3d00 #ff3c00 #ff3b00 #ff3a00 #ff3900 #ff3800 #ff3700 #ff3600 #ff3500 #ff3400 #ff3300 #ff3200 #ff3100 #ff3000 #ff2f00 #ff2e00 #ff2d00 #ff2c00 #ff2b00 #ff2a00 #ff2900 #ff2800 #ff2700 #ff2600 #ff2500 #ff2400 #ff2300 #ff2200 #ff2100 #ff2000 #ff1f00 #ff1e00 #ff1d00 #ff1c00 #ff1b00 #ff1a00 #ff1900 #ff1800 #ff1700 #ff1600 #ff1500 #ff1400 #ff1300 #ff1200 #ff1100 #ff1000 #ff0f00 #ff0e00 #ff0d00 #ff0c00 #ff0b00 #ff0a00 #ff0900 #ff0800 #ff0700 #ff0600 #ff0500 #ff0400 #ff0300 #ff0200 #ff0100 #ff0000' + +MPI acceleration +---------------- .. tip:: - If your OPM flow supports MPI, then to speed up the simulations you could change Line 3 in `results.toml `_ + If your OPM Flow supports MPI, then to speed up the simulations you could change Line 3 in `results.toml `_ and `appendixb.toml `_ to: .. code-block:: bash @@ -106,13 +130,22 @@ Similarly, for the spatial maps of CO2 concentration in Figures 10d to 10f: The values in the flags were manually set trying to speed up the simulations. Then, you could also try to run the cases with different flags. -For the Computational details results in the Appendix C, we use the templated configuration file `appendixc.mako `_, -in combination to Python commands, which gives flexibility to generate the different cases. +Appendix C: computational details +--------------------------------- + +For the computational-details results in Appendix C, we use the templated configuration file `appendixc.mako `_, +together with Python commands, which gives flexibility to generate the different cases. + +Sensitivity to setup choices +++++++++++++++++++++++++++++ -The Wasserstein distance (obtained from the generated error_table_satmin-0.01_conmin-0.1.csv) for the +The Wasserstein distance (obtained from the generated ``error_table_satmin-0.01_conmin-0.1.csv``) for the base case and random seed for the sensitivity study regarding setup choices in Table 8 can be generated by executing the `sensitivity.py `_ script (if you have a Mac as well, then execute the script in -there to see the sensitivity to the operative system). +there to see the sensitivity to the operating system). + +Table 7 and Figure 12: profiling +++++++++++++++++++++++++++++++++ The values in Table 7 and Figure 12 can be generated by (increase or decrease the number of cores to adapt to your interest and computer limitations): @@ -120,7 +153,10 @@ The values in Table 7 and Figure 12 can be generated by (increase or decrease th cd publication && python3 profiling.py -Finally, for accuracy-time trade-off Table 9 you can run: +Table 9: accuracy-time trade-off +++++++++++++++++++++++++++++++++ + +For the accuracy-time trade-off in Table 9, run: .. code-block:: bash @@ -128,19 +164,43 @@ Finally, for accuracy-time trade-off Table 9 you can run: .. warning:: - These scripts are set to run with 64 cores, then you should edit them if your machine has less available cpus. + These scripts are set to run with 64 cores, then you should edit them if your machine has fewer available CPUs. -.. tip:: +Reproduce all publication results +--------------------------------- - All previous commands can be executed as (`publication.sh `_): +The maintained ``publication.sh`` script reproduces the publication workflows +described on this page. - .. code-block:: bash +.. code-block:: console - . ./tests/scripts/publication.sh + . ./tests/scripts/publication.sh - This will run the cases with 64 cores. To run the case with 16 cores instead (in a coarser grid as well), run it as: +By default, the script runs the cases with 64 cores. To run the cases with 16 +cores and a coarser grid, use: - .. code-block:: bash +.. code-block:: console + + . ./tests/scripts/publication.sh 16 + +.. grid:: 1 1 2 2 + :gutter: 2 + + .. grid-item:: + + .. button-link:: https://github.com/cssr-tools/pofff/blob/main/tests/scripts/publication.sh + :color: primary + :outline: + :expand: + + View script + + .. grid-item:: + + .. button-link:: https://raw.githubusercontent.com/cssr-tools/pofff/main/tests/scripts/publication.sh + :color: primary + :outline: + :expand: - . ./tests/scripts/publication.sh 16 + View raw script diff --git a/docs/text/related.rst b/docs/text/related.rst index 528b619..ebfefd2 100644 --- a/docs/text/related.rst +++ b/docs/text/related.rst @@ -1,67 +1,55 @@ -======= -Related -======= +Related projects +================ -Below are some tools that might be of interest; check 'em out 🙂. +.. grid:: 1 2 2 2 + :gutter: 2 -********** -pyopmspe11 -********** + .. grid-item-card:: pyopmspe11 + :class-card: related-card + :img-top: figs/pyopmspe11.gif + :link: https://github.com/OPM/pyopmspe11 -.. image:: ./figs/pyopmspe11.gif - :scale: 50% + SPE11 benchmark workflows with OPM Flow. -`A Python framework using OPM Flow for the CSP SPE11 benchmark project `_. + .. grid-item-card:: plopm + :class-card: related-card + :img-top: figs/plopm.png + :link: https://github.com/cssr-tools/plopm -***** -plopm -***** + Generate PNGs, GIFs, and VTK data from OPM Flow models. -.. image:: ./figs/plopm.png + .. grid-item-card:: pycopm + :class-card: related-card + :img-top: figs/pycopm.png + :link: https://github.com/cssr-tools/pycopm -`Quick generation of PNGs, GIFs, and VTKs from a OPM Flow type model `_. + Tailor OPM Flow geological models. -****** -pycopm -****** + .. grid-item-card:: pyopmnearwell + :class-card: related-card + :img-top: figs/pyopmnearwell.png + :link: https://github.com/cssr-tools/pyopmnearwell -.. image:: ./figs/pycopm.png - :scale: 32% + Simulate near-well dynamics with OPM Flow. -`An open-source tool to tailor OPM Flow geological models `_. + .. grid-item-card:: expreccs + :class-card: related-card + :img-top: figs/expreccs.gif + :link: https://github.com/cssr-tools/expreccs -************* -pyopmnearwell -************* + Regional and site-reservoir CO2 storage simulations. -.. image:: ./figs/pyopmnearwell.png - :scale: 60% + .. grid-item-card:: ad-micp + :class-card: related-card + :img-top: figs/ad-micp.gif + :link: https://github.com/daavid00/ad-micp -`A Python framework to simulate near well dynamics using OPM Flow `_. + CO2 leakage remediation by MICP. -******** -expreccs -******** + .. grid-item-card:: pymm + :class-card: related-card + :img-top: figs/pymm.gif + :link: https://github.com/cssr-tools/pymm -.. image:: ./figs/expreccs.gif - :scale: 50% + Image-based CFD for microsystems. -`A Python framework using OPM Flow to simulate regional and site reservoirs for CO2 storage `_. - -******* -ad-micp -******* - -.. image:: ./figs/ad-micp.gif - :scale: 40% - -`A module to study CO2 leakage remediation by microbially induced calcite precipitation (MICP) `_. - -**** -pymm -**** - -.. image:: ./figs/pymm.gif - :scale: 15% - -`An open-source image-based framework for CFD in microsystems `_. diff --git a/docs/text/tutorial.rst b/docs/text/tutorial.rst new file mode 100644 index 0000000..1436f83 --- /dev/null +++ b/docs/text/tutorial.rst @@ -0,0 +1,55 @@ +Tutorial +======== + +.. image:: ./figs/readme.png + +Add your results to the FluidFlower comparison +---------------------------------------------- + +This tutorial runs one complete pofff workflow and adds the resulting simulation +to the published FluidFlower comparisons. It is the recommended starting point +for users who already have OPM Flow installed and want to understand how model +generation, simulation, benchmark processing, and plotting connect. + +The tutorial uses ``results.toml`` and writes everything below an output folder +named ``YOURS``. The output folder name is also used as the simulation label in +the comparison figures. + +Prerequisites +------------- + +Before starting, verify that: + +* pofff is installed in the active Python environment; +* OPM Flow is available through the command configured by ``flow`` in + ``results.toml``; +* the ``results.toml`` configuration is available in the current directory, or + its path is supplied with ``--input``; +* LaTeX is installed if you want the recommended figure typography. + +Check the command-line tools with: + +.. code-block:: console + + pofff --help + flow --help + +.. note:: + + The complete workflow can require substantial time and computational + resources. Runtime depends on the grid, OPM Flow settings, requested output + times, and available processors. + +Tutorial sequence +----------------- + +.. toctree:: + :maxdepth: 1 + :numbered: + + tutorial/prepare-configuration + tutorial/run-simulation + tutorial/inspect-data + tutorial/generate-comparisons + tutorial/interpret-results + tutorial/reproduce diff --git a/docs/text/tutorial/generate-comparisons.rst b/docs/text/tutorial/generate-comparisons.rst new file mode 100644 index 0000000..6abd30d --- /dev/null +++ b/docs/text/tutorial/generate-comparisons.rst @@ -0,0 +1,53 @@ +Generate the comparison figures +=============================== + +Goal +---- + +Locate the figures that compare the ``YOURS`` result with the experimental and +published FluidFlower benchmark data. + +Configuration +------------- + +The original command used ``-f all``. This produces the basic benchmark figures +and the Wasserstein-distance comparisons. The complete set requires evaluation +times of 24, 48, 72, 96, and 120 hours. + +Command +------- + +The figures were already generated by the combined tutorial command. To +regenerate figures from existing data without rerunning OPM Flow, use: + +.. code-block:: console + + pofff -o YOURS -m none -t 24,48,72,96,120 -f all + +How it works +------------ + +In ``none`` mode, pofff skips model generation and simulation. It reads the +existing benchmark-format CSV files and recreates spatial overlays, time-series +comparisons, sparse-data comparisons, Wasserstein-distance products, and the +error table. + +Result +------ + +One of the principal outputs is: + +.. figure:: ../figs/compare_all_time_series.png + :alt: FluidFlower time-series comparison including the tutorial simulation + + Published FluidFlower results with the new simulation labeled by its output + directory name. + +The figure should include a series labeled ``YOURS``. If it does not, verify that +the output directory and benchmark CSV files are in the locations expected by +the postprocessing workflow. + +Next +---- + +Continue with :doc:`interpret-results`. diff --git a/docs/text/tutorial/inspect-data.rst b/docs/text/tutorial/inspect-data.rst new file mode 100644 index 0000000..0bfc948 --- /dev/null +++ b/docs/text/tutorial/inspect-data.rst @@ -0,0 +1,74 @@ +Inspect the benchmark data +========================== + +Goal +---- + +Understand the benchmark-format files created from the OPM Flow result before +interpreting the comparison figures. + +Configuration +------------- + +This lesson uses the output generated in :doc:`run-simulation`. No configuration +changes are required. + +Command +------- + +List the generated CSV files: + +.. code-block:: console + + find YOURS -name "*.csv" -maxdepth 3 -print + +How it works +------------ + +The main benchmark products are: + +``time_series.csv`` + Uniformly sampled pressure, mobile, immobile, dissolved, seal, and migration + quantities used by the time-series comparisons. + +``spatial_map_Xh.csv`` + Saturation and dissolved-CO2 concentration mapped to the FluidFlower + reporting grid at time ``X`` hours. + +``sparse_data.csv`` + Sparse benchmark quantities derived from the time series, including maxima, + event times, and values evaluated at 72 hours. + +``sim_metrics_0.txt`` and ``func`` + Per-observation Wasserstein metrics and the combined objective used by ERT or + Everest job workflows when those files are generated. + +Result +------ + +Confirm that the requested spatial maps exist for 24, 48, 72, 96, and 120 hours. +The CSV files may also be generated by another simulator, provided that they +follow the same benchmark layouts and coordinate ranges. + +.. note:: + + You can generate ``sparse_data.csv``, ``time_series.csv``, and + ``spatial_map_Xh.csv`` files with another simulator and use pofff to compare + those results with the experimental data, forecast, CSIRO, MIT_M1, and CSSR + data. + + Place the files in a dedicated output folder, such as ``MY_SIMULATOR``, and + run: + + .. code-block:: console + + pofff -t 24,48,72,96,120 -m none -o MY_SIMULATOR -f all + + The spatial-map CSV files must cover the FluidFlower reporting domain from + 0 to 2.8 m in the x direction and from 0 to 1.2 m in the z direction. The + files must otherwise follow the benchmark layouts expected by pofff. + +Next +---- + +Continue with :doc:`generate-comparisons`. diff --git a/docs/text/tutorial/interpret-results.rst b/docs/text/tutorial/interpret-results.rst new file mode 100644 index 0000000..8e16b7c --- /dev/null +++ b/docs/text/tutorial/interpret-results.rst @@ -0,0 +1,55 @@ +Interpret and improve the results +================================= + +Goal +---- + +Use the generated comparisons and error table to identify model settings that +could improve agreement with the FluidFlower observations. + +Configuration +------------- + +Keep the original ``results.toml`` unchanged as a baseline. Create a copy before +changing grid resolution, facies properties, capillary pressure, relative +permeability, dispersion, diffusion, thickness, or injection controls. + +Command +------- + +Run a modified case in a different output directory so that the baseline is +preserved: + +.. code-block:: console + + pofff -i modified_results.toml -o MODIFIED -m single -t 24,48,72,96,120 -f all + +How it works +------------ + +Use the outputs together: + +* time-series plots show the evolution of pressures and regional CO2 quantities; +* spatial maps show where gaseous and dissolved CO2 differ from experimental + segmentations; +* sparse-data comparisons summarize selected benchmark observables; +* ``error_table_satmin-0.01_conmin-0.1.csv`` reports relative errors, + Wasserstein distance, and their combined metric for the default thresholds. + +.. tip:: + + Change one physical or numerical choice at a time when diagnosing its effect. + Use a separate output directory for every case so results remain reproducible. + +Result +------ + +You should be able to identify whether disagreement is primarily temporal, +spatial, or associated with a specific benchmark region or observable. +Configurations producing better benchmark metrics are welcome in the repository +examples through the fork and pull request workflow. + +Next +---- + +Continue with :doc:`reproduce` to run the maintained tutorial script. diff --git a/docs/text/tutorial/prepare-configuration.rst b/docs/text/tutorial/prepare-configuration.rst new file mode 100644 index 0000000..9a15f45 --- /dev/null +++ b/docs/text/tutorial/prepare-configuration.rst @@ -0,0 +1,73 @@ +Prepare the configuration +========================= + +Goal +---- + +Review the configuration used for the tutorial and identify the settings that +control the physical model, simulation, benchmark evaluation, and figures. + +Configuration +------------- + +The tutorial uses ``results.toml``. The maintained configuration is available in +the repository publication folder: + +.. grid:: 1 1 2 2 + :gutter: 2 + + .. grid-item:: + + .. button-link:: https://github.com/cssr-tools/pofff/blob/main/publication/results.toml + :color: primary + :outline: + :expand: + + View results.toml + + .. grid-item:: + + .. button-link:: https://raw.githubusercontent.com/cssr-tools/pofff/main/publication/results.toml + :color: primary + :outline: + :expand: + + View raw results.toml + +The most important groups of settings are: + +* ``flow``: OPM Flow executable and simulator flags; +* ``grid``, ``x``, and ``z``: grid representation and refinement; +* ``thickness`` and ``mult_thickness``: FluidFlower thickness model; +* ``temperature``, ``pressure``, and ``diffusion``: initial and fluid settings; +* ``sources`` and ``inj``: injection locations and schedule; +* ``facie1`` through ``facie6``: facies-dependent rock and saturation data; +* ``krw``, ``krn``, and ``cap``: saturation-function expressions. + +See :doc:`../configuration_file` for accepted values, units, array layouts, and +validation rules. + +Command +------- + +No pofff command is required at this stage. If ``results.toml`` is not already +available locally, download it from the repository or copy it from the +``publication`` directory. + +How it works +------------ + +When the tutorial command starts, pofff validates the TOML input, creates the +shared runtime configuration, generates the selected grid, assigns facies and +benchmark regions, and renders the OPM Flow input files. + +Result +------ + +You should have a reviewed ``results.toml`` file and know which OPM Flow command +and computational resources it requests. + +Next +---- + +Continue with :doc:`run-simulation`. diff --git a/docs/text/tutorial/reproduce.rst b/docs/text/tutorial/reproduce.rst new file mode 100644 index 0000000..651cdc3 --- /dev/null +++ b/docs/text/tutorial/reproduce.rst @@ -0,0 +1,76 @@ +Reproduce the tutorial +====================== + +Goal +---- + +Run the maintained repository script that reproduces the Add your results +workflow. + +Configuration +------------- + +Run the script from the pofff repository root. Review its commands and +configuration paths before execution, especially on systems with limited CPU or +memory resources. + +Command +------- + +.. code-block:: console + + . ./tests/scripts/docs_adding_your_results.sh + +How it works +------------ + +The script runs the documented simulation and comparison workflow using the +maintained repository inputs. It provides a reproducible check that the tutorial +commands remain aligned with the current source code. + +Result +------ + +The generated output should contain the benchmark CSV products and comparison +figures described in the previous lessons. + +Reproducibility links +--------------------- + +.. grid:: 1 1 3 3 + :gutter: 2 + + .. grid-item:: + + .. button-link:: https://github.com/cssr-tools/pofff/blob/main/tests/scripts/docs_adding_your_results.sh + :color: primary + :outline: + :expand: + + View script + + .. grid-item:: + + .. button-link:: https://raw.githubusercontent.com/cssr-tools/pofff/main/tests/scripts/docs_adding_your_results.sh + :color: primary + :outline: + :expand: + + View raw script + + .. grid-item:: + + .. button-link:: https://github.com/cssr-tools/pofff/blob/main/publication/results.toml + :color: secondary + :outline: + :expand: + + View results.toml + +Where to continue +----------------- + +* Use :doc:`../examples` for external-simulator, history-matching, and plopm workflows. +* Use :doc:`../publication` to reproduce the paper figures and tables. +* Use :doc:`../command-line` for exact CLI choices and defaults. +* Use :doc:`../configuration_file` for the complete TOML reference. diff --git a/docs/text/tutorial/run-simulation.rst b/docs/text/tutorial/run-simulation.rst new file mode 100644 index 0000000..a734951 --- /dev/null +++ b/docs/text/tutorial/run-simulation.rst @@ -0,0 +1,48 @@ +Run the simulation +================== + +Goal +---- + +Generate the FluidFlower model, run OPM Flow, process benchmark data, and create +all supported comparison figures with one command. + +Configuration +------------- + +Use the reviewed ``results.toml`` configuration. The tutorial requests benchmark +output at 24, 48, 72, 96, and 120 hours and enables all figure products. + +Command +------- + +.. code-block:: console + + pofff -i results.toml -o YOURS -m single -t 24,48,72,96,120 -f all + +How it works +------------ + +The command performs the connected workflow in sequence: + +#. Generate the OPM deck, include files, grid properties, and saturation tables. +#. Run OPM Flow with the executable and flags specified by ``flow``. +#. Convert the simulation results to benchmark time-series and spatial CSV data. +#. Calculate benchmark metrics for the requested experimental realization. +#. Generate the basic comparison figures and the Wasserstein-distance products + enabled by ``-f all``. + +The output directory is named ``YOURS``. pofff also uses this name to label the +new simulation in comparison figures. + +Result +------ + +The ``YOURS`` directory contains the generated simulation input, OPM Flow +results, benchmark CSV files, metrics, and figures. See +:doc:`../output_folder` for the complete output organization. + +Next +---- + +Continue with :doc:`inspect-data`. diff --git a/docs/tutorial.html b/docs/tutorial.html new file mode 100644 index 0000000..65c67d1 --- /dev/null +++ b/docs/tutorial.html @@ -0,0 +1,721 @@ + + + + + + + + + + + Tutorial — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

Tutorial#

+_images/readme.png +
+

Add your results to the FluidFlower comparison#

+

This tutorial runs one complete pofff workflow and adds the resulting simulation +to the published FluidFlower comparisons. It is the recommended starting point +for users who already have OPM Flow installed and want to understand how model +generation, simulation, benchmark processing, and plotting connect.

+

The tutorial uses results.toml and writes everything below an output folder +named YOURS. The output folder name is also used as the simulation label in +the comparison figures.

+
+
+

Prerequisites#

+

Before starting, verify that:

+
    +
  • pofff is installed in the active Python environment;

  • +
  • OPM Flow is available through the command configured by flow in +results.toml;

  • +
  • the results.toml configuration is available in the current directory, or +its path is supplied with --input;

  • +
  • LaTeX is installed if you want the recommended figure typography.

  • +
+

Check the command-line tools with:

+
pofff --help
+flow --help
+
+
+
+

Note

+

The complete workflow can require substantial time and computational +resources. Runtime depends on the grid, OPM Flow settings, requested output +times, and available processors.

+
+
+
+

Tutorial sequence#

+ +
+
+ + +
+ + + + + + + +
+ + + + + + + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/tutorial/generate-comparisons.html b/docs/tutorial/generate-comparisons.html new file mode 100644 index 0000000..04c9f39 --- /dev/null +++ b/docs/tutorial/generate-comparisons.html @@ -0,0 +1,724 @@ + + + + + + + + + + + 4. Generate the comparison figures — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

4. Generate the comparison figures#

+
+

4.1. Goal#

+

Locate the figures that compare the YOURS result with the experimental and +published FluidFlower benchmark data.

+
+
+

4.2. Configuration#

+

The original command used -f all. This produces the basic benchmark figures +and the Wasserstein-distance comparisons. The complete set requires evaluation +times of 24, 48, 72, 96, and 120 hours.

+
+
+

4.3. Command#

+

The figures were already generated by the combined tutorial command. To +regenerate figures from existing data without rerunning OPM Flow, use:

+
pofff -o YOURS -m none -t 24,48,72,96,120 -f all
+
+
+
+
+

4.4. How it works#

+

In none mode, pofff skips model generation and simulation. It reads the +existing benchmark-format CSV files and recreates spatial overlays, time-series +comparisons, sparse-data comparisons, Wasserstein-distance products, and the +error table.

+
+
+

4.5. Result#

+

One of the principal outputs is:

+
+FluidFlower time-series comparison including the tutorial simulation +
+

Published FluidFlower results with the new simulation labeled by its output +directory name.#

+
+
+

The figure should include a series labeled YOURS. If it does not, verify that +the output directory and benchmark CSV files are in the locations expected by +the postprocessing workflow.

+
+
+

4.6. Next#

+

Continue with Interpret and improve the results.

+
+
+ + +
+ + + + + + + +
+ + + + + + + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/tutorial/inspect-data.html b/docs/tutorial/inspect-data.html new file mode 100644 index 0000000..1cf9d98 --- /dev/null +++ b/docs/tutorial/inspect-data.html @@ -0,0 +1,740 @@ + + + + + + + + + + + 3. Inspect the benchmark data — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

3. Inspect the benchmark data#

+
+

3.1. Goal#

+

Understand the benchmark-format files created from the OPM Flow result before +interpreting the comparison figures.

+
+
+

3.2. Configuration#

+

This lesson uses the output generated in Run the simulation. No configuration +changes are required.

+
+
+

3.3. Command#

+

List the generated CSV files:

+
find YOURS -name "*.csv" -maxdepth 3 -print
+
+
+
+
+

3.4. How it works#

+

The main benchmark products are:

+
+
time_series.csv

Uniformly sampled pressure, mobile, immobile, dissolved, seal, and migration +quantities used by the time-series comparisons.

+
+
spatial_map_Xh.csv

Saturation and dissolved-CO2 concentration mapped to the FluidFlower +reporting grid at time X hours.

+
+
sparse_data.csv

Sparse benchmark quantities derived from the time series, including maxima, +event times, and values evaluated at 72 hours.

+
+
sim_metrics_0.txt and func

Per-observation Wasserstein metrics and the combined objective used by ERT or +Everest job workflows when those files are generated.

+
+
+
+
+

3.5. Result#

+

Confirm that the requested spatial maps exist for 24, 48, 72, 96, and 120 hours. +The CSV files may also be generated by another simulator, provided that they +follow the same benchmark layouts and coordinate ranges.

+
+

Note

+

You can generate sparse_data.csv, time_series.csv, and +spatial_map_Xh.csv files with another simulator and use pofff to compare +those results with the experimental data, forecast, CSIRO, MIT_M1, and CSSR +data.

+

Place the files in a dedicated output folder, such as MY_SIMULATOR, and +run:

+
pofff -t 24,48,72,96,120 -m none -o MY_SIMULATOR -f all
+
+
+

The spatial-map CSV files must cover the FluidFlower reporting domain from +0 to 2.8 m in the x direction and from 0 to 1.2 m in the z direction. The +files must otherwise follow the benchmark layouts expected by pofff.

+
+
+
+

3.6. Next#

+

Continue with Generate the comparison figures.

+
+
+ + +
+ + + + + + + +
+ + + + + + + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/tutorial/interpret-results.html b/docs/tutorial/interpret-results.html new file mode 100644 index 0000000..21f7d72 --- /dev/null +++ b/docs/tutorial/interpret-results.html @@ -0,0 +1,727 @@ + + + + + + + + + + + 5. Interpret and improve the results — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

5. Interpret and improve the results#

+
+

5.1. Goal#

+

Use the generated comparisons and error table to identify model settings that +could improve agreement with the FluidFlower observations.

+
+
+

5.2. Configuration#

+

Keep the original results.toml unchanged as a baseline. Create a copy before +changing grid resolution, facies properties, capillary pressure, relative +permeability, dispersion, diffusion, thickness, or injection controls.

+
+
+

5.3. Command#

+

Run a modified case in a different output directory so that the baseline is +preserved:

+
pofff -i modified_results.toml -o MODIFIED -m single -t 24,48,72,96,120 -f all
+
+
+
+
+

5.4. How it works#

+

Use the outputs together:

+
    +
  • time-series plots show the evolution of pressures and regional CO2 quantities;

  • +
  • spatial maps show where gaseous and dissolved CO2 differ from experimental +segmentations;

  • +
  • sparse-data comparisons summarize selected benchmark observables;

  • +
  • error_table_satmin-0.01_conmin-0.1.csv reports relative errors, +Wasserstein distance, and their combined metric for the default thresholds.

  • +
+
+

Tip

+

Change one physical or numerical choice at a time when diagnosing its effect. +Use a separate output directory for every case so results remain reproducible.

+
+
+
+

5.5. Result#

+

You should be able to identify whether disagreement is primarily temporal, +spatial, or associated with a specific benchmark region or observable. +Configurations producing better benchmark metrics are welcome in the repository +examples through the fork and pull request workflow.

+
+
+

5.6. Next#

+

Continue with Reproduce the tutorial to run the maintained tutorial script.

+
+
+ + +
+ + + + + + + +
+ + + + + + + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/tutorial/prepare-configuration.html b/docs/tutorial/prepare-configuration.html new file mode 100644 index 0000000..c1ce558 --- /dev/null +++ b/docs/tutorial/prepare-configuration.html @@ -0,0 +1,733 @@ + + + + + + + + + + + 1. Prepare the configuration — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

1. Prepare the configuration#

+
+

1.1. Goal#

+

Review the configuration used for the tutorial and identify the settings that +control the physical model, simulation, benchmark evaluation, and figures.

+
+
+

1.2. Configuration#

+

The tutorial uses results.toml. The maintained configuration is available in +the repository publication folder:

+ +

The most important groups of settings are:

+
    +
  • flow: OPM Flow executable and simulator flags;

  • +
  • grid, x, and z: grid representation and refinement;

  • +
  • thickness and mult_thickness: FluidFlower thickness model;

  • +
  • temperature, pressure, and diffusion: initial and fluid settings;

  • +
  • sources and inj: injection locations and schedule;

  • +
  • facie1 through facie6: facies-dependent rock and saturation data;

  • +
  • krw, krn, and cap: saturation-function expressions.

  • +
+

See Configuration reference for accepted values, units, array layouts, and +validation rules.

+
+
+

1.3. Command#

+

No pofff command is required at this stage. If results.toml is not already +available locally, download it from the repository or copy it from the +publication directory.

+
+
+

1.4. How it works#

+

When the tutorial command starts, pofff validates the TOML input, creates the +shared runtime configuration, generates the selected grid, assigns facies and +benchmark regions, and renders the OPM Flow input files.

+
+
+

1.5. Result#

+

You should have a reviewed results.toml file and know which OPM Flow command +and computational resources it requests.

+
+
+

1.6. Next#

+

Continue with Run the simulation.

+
+
+ + +
+ + + + + + + +
+ + + + + + + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/tutorial/reproduce.html b/docs/tutorial/reproduce.html new file mode 100644 index 0000000..747a204 --- /dev/null +++ b/docs/tutorial/reproduce.html @@ -0,0 +1,734 @@ + + + + + + + + + + + 6. Reproduce the tutorial — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

6. Reproduce the tutorial#

+
+

6.1. Goal#

+

Run the maintained repository script that reproduces the Add your results +workflow.

+
+
+

6.2. Configuration#

+

Run the script from the pofff repository root. Review its commands and +configuration paths before execution, especially on systems with limited CPU or +memory resources.

+
+
+

6.3. Command#

+
. ./tests/scripts/docs_adding_your_results.sh
+
+
+
+
+

6.4. How it works#

+

The script runs the documented simulation and comparison workflow using the +maintained repository inputs. It provides a reproducible check that the tutorial +commands remain aligned with the current source code.

+
+
+

6.5. Result#

+

The generated output should contain the benchmark CSV products and comparison +figures described in the previous lessons.

+
+ +
+

6.7. Where to continue#

+ +
+
+ + +
+ + + + + + + +
+ + + + + + + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/docs/tutorial/run-simulation.html b/docs/tutorial/run-simulation.html new file mode 100644 index 0000000..65c9705 --- /dev/null +++ b/docs/tutorial/run-simulation.html @@ -0,0 +1,720 @@ + + + + + + + + + + + 2. Run the simulation — pofff documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ +
+ + + + + +
+
+ + + + +
+ + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+
+ + + + + +
+ +
+

2. Run the simulation#

+
+

2.1. Goal#

+

Generate the FluidFlower model, run OPM Flow, process benchmark data, and create +all supported comparison figures with one command.

+
+
+

2.2. Configuration#

+

Use the reviewed results.toml configuration. The tutorial requests benchmark +output at 24, 48, 72, 96, and 120 hours and enables all figure products.

+
+
+

2.3. Command#

+
pofff -i results.toml -o YOURS -m single -t 24,48,72,96,120 -f all
+
+
+
+
+

2.4. How it works#

+

The command performs the connected workflow in sequence:

+
    +
  1. Generate the OPM deck, include files, grid properties, and saturation tables.

  2. +
  3. Run OPM Flow with the executable and flags specified by flow.

  4. +
  5. Convert the simulation results to benchmark time-series and spatial CSV data.

  6. +
  7. Calculate benchmark metrics for the requested experimental realization.

  8. +
  9. Generate the basic comparison figures and the Wasserstein-distance products +enabled by -f all.

  10. +
+

The output directory is named YOURS. pofff also uses this name to label the +new simulation in comparison figures.

+
+
+

2.5. Result#

+

The YOURS directory contains the generated simulation input, OPM Flow +results, benchmark CSV files, metrics, and figures. See +Output folder for the complete output organization.

+
+
+

2.6. Next#

+

Continue with Inspect the benchmark data.

+
+
+ + +
+ + + + + + + +
+ + + + + + + +
+
+ +
+ +
+
+
+ + + + + +
+ + +
+ + \ No newline at end of file diff --git a/src/pofff/config/config.py b/src/pofff/config/config.py index a70320e..5c473e9 100644 --- a/src/pofff/config/config.py +++ b/src/pofff/config/config.py @@ -1,13 +1,13 @@ # SPDX-FileCopyrightText: 2023-2026 NORCE Research AS # SPDX-License-Identifier: GPL-3.0 -""" -Central configuration models for pofff. +"""Configuration and runtime settings shared across pofff workflows. -Defines structured configuration objects used across the codebase: -- CliConfig: raw CLI inputs -- PofffConfig: unified runtime and TOML-based configuration -""" +The data classes combine raw command-line selections, validated TOML input, +FluidFlower geometry, OPM deck arrays, history-matching options, and values +derived while grids, simulations, and benchmark products are created. +``PofffConfig`` is mutable because paths, cell indices, properties, and runtime +flags are populated progressively.""" from collections.abc import Callable, Iterable, Sequence from dataclasses import dataclass, field @@ -19,9 +19,32 @@ @dataclass(slots=True) class CliConfig: - """ - Container for command-line arguments before normalization. - """ + """Store command-line values before TOML normalization. + + Attributes + ---------- + fol + Base output directory. + deck + Directory used for generated OPM deck files. + jobs + Directory used for generated ERT and Everest job scripts. + experiment + FluidFlower experimental realization, normalized as ``run1`` through ``run5``. + times + Comma-separated benchmark evaluation times in hours. + msat + Minimum gas-saturation threshold used for segmentation. + mcon + Minimum dissolved-CO2 concentration threshold used for segmentation. + mode + Selected simulation, file-generation, history-matching, or FAIR workflow. + figures + Figure mode: ``all``, ``basic``, or ``none``. + location + Directory containing comparison or precomputed benchmark data. + use + Whether precomputed Wasserstein distances may be used.""" fol: Path deck: Path @@ -38,11 +61,74 @@ class CliConfig: @dataclass(slots=True) class PofffConfig: - """ - Central configuration object for pofff. - - Combines CLI options, TOML inputs, and derived runtime settings. - """ + """Store TOML input, CLI options, and derived pofff runtime state. + + TOML-backed attributes retain the spelling used by existing configuration files. + Derived arrays, grid dimensions, feature indices, paths, and history-matching + flags are populated while input is normalized and model files are generated. + + Attributes + ---------- + path + Package root containing geology, templates, jobs, and benchmark resources. + fol, deck, jobs + Base output, generated deck, and generated job-script directories. + experiment, times, msat, mcon + Experimental realization, evaluation times [h], and segmentation thresholds. + mode, figures, location, use + Workflow, figure selection, comparison-data location, and reuse selection. + flow + OPM Flow command and command-line options. + grid + Grid representation: ``cartesian``, ``tensor``, or ``corner-point``. + thickness + ``initial`` or ``final`` thickness map, or a positive physical thickness [m]. + mult_thickness + Positive multiplier applied to the selected thickness map. + x, z + Horizontal and vertical refinement counts. + temperature + Initial and boundary temperatures used by the deck. + pressure + Positive reference pressure used for initialization and boundary properties. + diffusion + Two molecular diffusion coefficients, converted from m²/s to m²/day. + sources + Two injection-source coordinates as ``[x, z]`` rows [m]. + inj + Injection rows containing times, rates, and optional TUNING text. + krw, krn, cap + Python expressions used to generate saturation-function tables. + cores, maxtime, delete + Shared ERT/Everest resources, run timeout, and cleanup selection. + ertargs, ensembles, enkf_alpha, errors, random_seed + ERT command options, ensemble controls, observation errors, and random seed. + min_realizations_success, max_function_evaluations, max_batch_num + Shared success requirement and Everest evaluation limits. + strategy, maxiter, popsize, tol, mutation, recombination + Differential-evolution strategy and convergence settings. + rng, callback, disp, polish, init, atol, updating, workers + Additional differential-evolution options passed to Everest. + constraints, x0, integrality, vectorized + Optional differential-evolution constraints and evaluation controls. + facies, fluxnum, fipnum, porv, multpv, dx, dz + Generated facies, region, pore-volume, and grid-size arrays for OPM input. + dims + FluidFlower dimensions in x, y, and z order [m]. + sensors, sensor_ik + Sensor coordinates [m] and zero-based grid indices. + source_ik + One-based grid indices of the two injection sources. + boxa, boxb, boxc + Opposite ``[x, z]`` corners of the benchmark reporting boxes [m]. + hm, para + History-matching definitions and fixed facies properties. + monotonic, hascellmaps, everert, tuning + Derived workflow and file-generation flags. + nxz + Numbers of simulation cells in x and z order. + data + Uppercase OPM deck base name derived from the output directory.""" # ------------------------------------------------------------------ # CLI configuration (normalized) diff --git a/src/pofff/core/pofff.py b/src/pofff/core/pofff.py index 4399bc0..8034e81 100644 --- a/src/pofff/core/pofff.py +++ b/src/pofff/core/pofff.py @@ -2,7 +2,19 @@ # SPDX-License-Identifier: GPL-3.0 # pylint: disable=R0912 -"""Main entry script for pofff.""" +"""Command-line entry point and top-level workflow coordination for pofff. + +pofff supports connected workflows for FluidFlower history matching: + +* File generation creates OPM Flow, ERT, and Everest input. +* Single execution runs OPM Flow and generates benchmark data. +* History matching runs ERT ensemble studies or Everest optimization. +* Postprocessing creates benchmark maps, metrics, and comparison figures. + +This module parses and validates CLI arguments, builds the shared configuration, +prepares output folders, dispatches the selected workflows, and reports results. +Grid construction, file writing, execution, and visualization are implemented in +the utility and visualization modules.""" import argparse import math @@ -14,19 +26,35 @@ from pofff.utils.inputvalues import build_config, load_toml, postprocess_config from pofff.utils.mapproperties import grid_and_properties from pofff.utils.runs import benchmark, data, ert, everest, flow, postprocess +from pofff.utils.terminal import ( + cli_correct_value, + cli_error_value, + pofff_error, + pofff_info, + pofff_success, + pofff_tip, +) from pofff.utils.writefile import write_files def main(argv: list[str] | None = None) -> None: - """Main pofff execution routine.""" - args = parse_args(argv) - check_cmdargs(args) + """Main pofff execution routine. + + The selected file-generation, simulation, data, history-matching, and + plotting stages are executed in dependency order. + + Parameters + ---------- + argv : list[str] | None, optional + Arguments to parse instead of the process command line.""" + args = _parse_arguments(argv) + _validate_arguments(args) pofff_path = Path(__file__).resolve().parents[1] - cli = cli_config(args, pofff_path=pofff_path) + cli = _build_cli_config(args, pofff_path=pofff_path) if cli.figures == "none" and cli.mode == "none": - print("Nothing to do since -m none -f none") + pofff_info("nothing to do because -m none and -f none were selected.") return cli.fol.mkdir(parents=True, exist_ok=True) @@ -40,29 +68,41 @@ def main(argv: list[str] | None = None) -> None: elif cli.mode == "none": cfg = build_config(pofff_path=pofff_path, cli=cli, toml={}) - prepare_simulation(cfg) + _prepare_simulation(cfg) else: - toml = load_toml(args.input) + toml = load_toml(args.input, args.mode) cfg = build_config(pofff_path=pofff_path, cli=cli, toml=toml.copy()) postprocess_config(cfg, toml) - prepare_simulation(cfg) + _prepare_simulation(cfg) - run_simulation_steps(cfg) + _run_simulation_steps(cfg) if cfg.figures in {"basic", "all"} and cfg.mode != "files": - generate_figures(cfg) + _generate_figures(cfg) msg = ( "The files have been written to" if cfg.mode == "files" else "The results have been written to" ) - print(f"\n{msg} {cfg.fol}") + pofff_success(f"{msg.lower()} ", str(cfg.fol), []) + +def _build_cli_config(args: argparse.Namespace, *, pofff_path: Path) -> CliConfig: + """Build normalized CLI configuration object. -def cli_config(args: argparse.Namespace, *, pofff_path: Path) -> CliConfig: - """Build normalized CLI configuration object.""" + Parameters + ---------- + args : argparse.Namespace + Parsed command-line arguments. + pofff_path : Path + Package root containing templates, geology, jobs, and benchmark data. + + Returns + ------- + CliConfig + Normalized command-line configuration.""" output = Path(args.output).absolute() location = ( @@ -87,8 +127,13 @@ def cli_config(args: argparse.Namespace, *, pofff_path: Path) -> CliConfig: ) -def prepare_simulation(cfg: PofffConfig) -> None: - """Prepare directories and generate all input files.""" +def _prepare_simulation(cfg: PofffConfig) -> None: + """Prepare directories and generate all input files. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" if cfg.mode == "none": cfg.deck = cfg.deck / "deck" cfg.jobs = cfg.jobs / "jobs" @@ -99,16 +144,21 @@ def prepare_simulation(cfg: PofffConfig) -> None: (cfg.fol / sub).mkdir(parents=True, exist_ok=True) if cfg.mode in ["everest", "ert"] or (cfg.everert and cfg.mode == "files"): - prepare_jobs_folder(cfg) + _prepare_jobs_folder(cfg) if cfg.mode not in {"data", "none"}: - print("\nGenerating the input files, please wait.") + pofff_info("generating the input files, please wait...") grid_and_properties(cfg) write_files(cfg) -def prepare_jobs_folder(cfg: PofffConfig) -> None: - """Copy job scripts into output/jobs and make them executable.""" +def _prepare_jobs_folder(cfg: PofffConfig) -> None: + """Copy job scripts into output/jobs and make them executable. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" src = cfg.path / "jobs" dst = cfg.fol / "jobs" shutil.copytree(src, dst, dirs_exist_ok=True) @@ -119,14 +169,19 @@ def prepare_jobs_folder(cfg: PofffConfig) -> None: script_path.chmod(0o755) -def run_simulation_steps(cfg: PofffConfig) -> None: - """Run the selected execution mode.""" +def _run_simulation_steps(cfg: PofffConfig) -> None: + """Run the selected execution mode. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" os.chdir(cfg.fol) if cfg.mode == "single": - print("\nRunning the simulation, please wait.") + pofff_info("running the simulation, please wait...") flow(cfg) - print("\nGenerating the data, please wait.") + pofff_info("generating the data, please wait...") data(cfg) elif cfg.mode == "data": data(cfg) @@ -137,14 +192,19 @@ def run_simulation_steps(cfg: PofffConfig) -> None: elif cfg.mode in {"files", "fair", "none"}: pass else: - raise SystemExit(f"Unknown -m {cfg.mode}") + pofff_error(f"unknown mode {cli_error_value(f'-m {cfg.mode}')}.") -def generate_figures(cfg: PofffConfig) -> None: - """Generate benchmark plots and comparison figures.""" +def _generate_figures(cfg: PofffConfig) -> None: + """Generate benchmark plots and comparison figures. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" if shutil.which("latex") is None: - print( - "\nLaTeX is recommended for high-quality figures.\n" + pofff_tip( + "LaTeX is recommended for high-quality figures. " "See the pofff documentation for installation instructions." ) if (cfg.fol / "jobs").exists(): @@ -156,12 +216,22 @@ def generate_figures(cfg: PofffConfig) -> None: else: os.chdir(cfg.fol) - print("\nGenerating the benchmark files, please wait.") + pofff_info("generating the benchmark files, please wait...") benchmark(cfg) -def parse_args(argv: list[str] | None = None) -> argparse.Namespace: - """Define and parse command-line arguments.""" +def _parse_arguments(argv: list[str] | None = None) -> argparse.Namespace: + """Define and parse command-line arguments. + + Parameters + ---------- + argv : list[str] | None, optional + Arguments to parse instead of the process command line. + + Returns + ------- + argparse.Namespace + Parsed command-line arguments or the corresponding runtime configuration.""" parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, description=( @@ -241,37 +311,33 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace: return parser.parse_args(argv) -def check_cmdargs(cmdargs: argparse.Namespace) -> None: +def _validate_arguments(cmdargs: argparse.Namespace) -> None: """Validate command-line arguments and incompatible operations. - The checks cover input and output paths, evaluation times, saturation and - concentration thresholds, options ignored by specialized workflows, and - combinations that do not result in any operation. - Parameters ---------- - cmdargs - Parsed arguments returned by :func:`parse_args`. + cmdargs : argparse.Namespace + Parsed command-line arguments. Raises ------ SystemExit - If an argument is invalid or an incompatible combination is requested. - """ + If an argument is invalid or incompatible with the selected workflow.""" if not cmdargs.output: - print("\nInvalid value for '-o', the output directory cannot be empty.\n") - raise SystemExit(1) + pofff_error( + f"invalid value {cli_error_value('-o')}, the output directory cannot be empty." + ) mode = cmdargs.mode if mode not in {"fair", "none"}: if not cmdargs.input: - print("\nInvalid value for '-i', the input file cannot be empty.\n") - raise SystemExit(1) + pofff_error( + f"invalid value {cli_error_value('-i')}, the input file cannot be empty." + ) if not cmdargs.input.lower().endswith(".toml"): - print( - f"\nInvalid extension for input file '-i {cmdargs.input}', " - "the valid extension is .toml.\n" + pofff_error( + f"invalid extension {cli_error_value(f'-i {cmdargs.input}')}, " + f"expected {cli_correct_value('.toml')}." ) - raise SystemExit(1) times = cmdargs.times try: time_values = [float(value.strip()) for value in times.split(",")] @@ -280,11 +346,10 @@ def check_cmdargs(cmdargs: argparse.Namespace) -> None: if not time_values or any( not math.isfinite(value) or value <= 0 for value in time_values ): - print( - f"\nInvalid value '-t {times}', expected positive finite numbers " - "separated by commas, e.g., '-t 24,48,72'.\n" + pofff_error( + f"invalid value {cli_error_value(f'-t {times}')}, expected positive " + f"finite numbers separated by commas, {cli_correct_value('e.g., -t 24,48,72')}." ) - raise SystemExit(1) minimum_saturation = cmdargs.minimumsaturation try: minimum_saturation_value = float(minimum_saturation) @@ -295,11 +360,10 @@ def check_cmdargs(cmdargs: argparse.Namespace) -> None: or minimum_saturation_value < 0 or minimum_saturation_value > 1 ): - print( - f"\nInvalid value '-s {minimum_saturation}', expected a finite " - "number between 0 and 1.\n" + pofff_error( + f"invalid value {cli_error_value(f'-s {minimum_saturation}')}, expected a " + f"finite number in {cli_correct_value('[0, 1]')}." ) - raise SystemExit(1) minimum_concentration = cmdargs.minimumconcentration try: minimum_concentration_value = float(minimum_concentration) @@ -310,11 +374,10 @@ def check_cmdargs(cmdargs: argparse.Namespace) -> None: not math.isfinite(minimum_concentration_value) or minimum_concentration_value not in valid_concentrations ): - print( - f"\nInvalid value '-c {minimum_concentration}', valid values are " - "'5e-2' and '1e-1'.\n" + pofff_error( + f"invalid value {cli_error_value(f'-c {minimum_concentration}')}, expected " + f"{cli_correct_value('5e-2')} or {cli_correct_value('1e-1')}." ) - raise SystemExit(1) if mode == "fair": ignored_options = { "-i": ("input", "input.toml"), @@ -328,16 +391,13 @@ def check_cmdargs(cmdargs: argparse.Namespace) -> None: if getattr(cmdargs, name) != default ] if invalid_options: - print( - "\nInvalid option for '-m fair'; this workflow uses its own " - "input data, figure mode, evaluation times, and experiment. " - f"Invalid options: {', '.join(invalid_options)}.\n" + pofff_error( + f"invalid combination {cli_error_value('-m fair; ' + ', '.join(invalid_options))}; " + "the FAIR workflow uses its own input data, figure mode, evaluation " + "times, and experiment." ) - raise SystemExit(1) if mode == "files" and cmdargs.figures == "all": - print( - "\nInvalid combination, '-f all' cannot be used with '-m files' " - "because this mode only generates input files. Use '-f none' or " - "omit the '-f' option.\n" + pofff_error( + f"invalid combination {cli_error_value('-m files -f all')}; file generation " + f"does not create figures, use {cli_correct_value('-f none')} or omit -f." ) - raise SystemExit(1) diff --git a/src/pofff/jobs/data.py b/src/pofff/jobs/data.py index 5729606..6b6da31 100755 --- a/src/pofff/jobs/data.py +++ b/src/pofff/jobs/data.py @@ -1,13 +1,18 @@ #!/usr/bin/env python3 # SPDX-FileCopyrightText: 2023-2026 NORCE Research AS # SPDX-License-Identifier: GPL-3.0 +# pylint: disable=R0914 -"""Write benchmark time-series and spatial (dense) data from OPM simulations.""" +"""Generate FluidFlower benchmark data from OPM Flow results. -from __future__ import annotations +The job locates the current simulation, reads restart, grid, initialization, and +summary files through the OPM Python bindings, calculates sparse benchmark time +series, maps saturation and dissolved CO2 to the reporting grid, and writes CSV +files at the requested evaluation times.""" import argparse import sys +from collections.abc import Iterable from dataclasses import dataclass, field from pathlib import Path @@ -19,6 +24,8 @@ from opm.io.ecl import ESmry as OpmSummary from scipy.interpolate import interp1d +from pofff.utils.terminal import cli_error_value, pofff_error + SECONDS_IN_DAY = 86400.0 GAS_DEN_REF = 1.86843 WAT_DEN_REF = 998.108 @@ -32,9 +39,52 @@ ArrayLike = float | NDArray -@dataclass +@dataclass(slots=True) class SimulationContext: - """Simulation-wide state, file handles, and grid information.""" + """Store simulation files, time grids, and OPM grid metadata. + + The object is initialized from command-line selections. OPM handles, report + times, active-cell indices, pore volumes, and grid dimensions are populated + after the current simulation base name is located. + + Attributes + ---------- + resolution + Numbers of benchmark reporting cells in x and z order. + dense_t + Requested dense-output times in seconds. + maps + Path to the NumPy mapping from reporting cells to simulation cells. + where + Directory containing simulation files and receiving generated CSV data. + sparse_t + Uniform sparse-output interval in seconds. + dims + FluidFlower dimensions in x, y, and z order, in metres. + sim + Simulation base path without an OPM file extension. + unrst + Open OPM restart result, or ``None`` before results are loaded. + ini + Open OPM initialization result, or ``None`` before results are loaded. + egrid + Open OPM grid result, or ``None`` before results are loaded. + smspec + Open OPM summary result, or ``None`` before results are loaded. + times + Restart times relative to the start of CO2 injection, in seconds. + times_summary + Summary times relative to the start of CO2 injection, in seconds. + time_initial + Absolute simulator time at the start of CO2 injection, in seconds. + porv + Pore volumes in global cell order. + actind + Global indices of cells with positive pore volume. + gxyz + Simulation-grid dimensions in x, y, and z order. + norst + Number of restart report steps.""" resolution: NDArray dense_t: NDArray @@ -55,48 +105,69 @@ class SimulationContext: gxyz: tuple[int, int, int] | None = None norst: int | None = None - @property - def nxz(self) -> NDArray: - """Alias for resolution to preserve original naming.""" - return self.resolution - -@dataclass +@dataclass(slots=True) class SparseResults: - """Container for sparse (time-series) benchmark quantities.""" + """Store sparse FluidFlower quantities before writing the time series. + + The state is initialized by :func:`_initialize_sparse_results` with the uniform + time grid, region identifiers, and cell dimensions. Pressure, mobile, immobile, + dissolved, seal, and migration quantities are then populated from OPM summary + and restart results and interpolated to ``times_data``. + + Attributes + ---------- + ctx + Shared simulation context and OPM file handles. + times_data + Uniform sparse-output times in seconds. + fipnum + FIPNUM values in global cell order. + dx, dz + Cell dimensions in x and z order, in metres. + pop1, pop2 + Pressure time series at the two observation points, in pascals. + moba, imma, dissa, seala + Mobile, immobile, dissolved, and seal CO2 masses in box A, in kilograms. + mobb, immb, dissb, sealb + Mobile, immobile, dissolved, and seal CO2 masses in box B, in kilograms. + sealt + Total CO2 mass in the seal regions, in kilograms. + m_c_series + Mass-center migration values calculated at restart times. + m_c + Mass-center migration values interpolated to ``times_data``, in metres.""" ctx: SimulationContext - times_data: NDArray = field(init=False) - fipnum: NDArray = field(init=False) - dx: NDArray = field(init=False) - dz: NDArray = field(init=False) - pop1: NDArray = field(init=False) - pop2: NDArray = field(init=False) - moba: NDArray = field(init=False) - imma: NDArray = field(init=False) - dissa: NDArray = field(init=False) - seala: NDArray = field(init=False) - mobb: NDArray = field(init=False) - immb: NDArray = field(init=False) - dissb: NDArray = field(init=False) - sealb: NDArray = field(init=False) - sealt: NDArray = field(init=False) + times_data: NDArray = field(default_factory=lambda: np.empty(0)) + fipnum: NDArray = field(default_factory=lambda: np.empty(0)) + dx: NDArray = field(default_factory=lambda: np.empty(0)) + dz: NDArray = field(default_factory=lambda: np.empty(0)) + pop1: NDArray = field(default_factory=lambda: np.empty(0)) + pop2: NDArray = field(default_factory=lambda: np.empty(0)) + moba: NDArray = field(default_factory=lambda: np.empty(0)) + imma: NDArray = field(default_factory=lambda: np.empty(0)) + dissa: NDArray = field(default_factory=lambda: np.empty(0)) + seala: NDArray = field(default_factory=lambda: np.empty(0)) + mobb: NDArray = field(default_factory=lambda: np.empty(0)) + immb: NDArray = field(default_factory=lambda: np.empty(0)) + dissb: NDArray = field(default_factory=lambda: np.empty(0)) + sealb: NDArray = field(default_factory=lambda: np.empty(0)) + sealt: NDArray = field(default_factory=lambda: np.empty(0)) m_c_series: list[float] = field(default_factory=list) m_c: NDArray | None = None - def __post_init__(self) -> None: - assert self.ctx.ini - assert self.ctx.times - self.times_data = np.arange( - 0.0, self.ctx.times[-1] + self.ctx.sparse_t, self.ctx.sparse_t - ) - self.fipnum = np.asarray(self.ctx.ini["FIPNUM"]) - self.dx = np.asarray(self.ctx.ini["DX"]) - self.dz = np.asarray(self.ctx.ini["DZ"]) +def main(argv: list[str] | None = None) -> None: + """Generate sparse time-series and dense spatial benchmark data. + + Parse reporting-grid settings, locate the OPM simulation, load its results, and + write the benchmark time-series and spatial-map CSV files. -def main(argv=None) -> None: - """Entry point for benchmark postprocessing.""" + Parameters + ---------- + argv : list[str] | None, optional + Arguments to parse instead of ``sys.argv[1:]``.""" parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, description="Benchmark postprocessing", @@ -104,7 +175,7 @@ def main(argv=None) -> None: parser.add_argument("-r", "--resolution", default="280,120") parser.add_argument("-t", "--time", default="24,48,72,96,120") parser.add_argument("-m", "--maps", default="cellmap.npy") - args = vars(parser.parse_known_args(argv)[0]) + args = vars(parser.parse_args(argv)) if Path("NOMONOTONIC").exists(): sys.exit(1) @@ -115,22 +186,48 @@ def main(argv=None) -> None: maps=Path(args["maps"]), ) - ctx.sim = find_simulation_base(ctx.where) - read_opm(ctx) - sparse_data(ctx) - dense_data(ctx) + ctx.sim = _find_simulation_base(ctx.where) + _read_opm_results(ctx) + _write_sparse_outputs(ctx) + _write_dense_outputs(ctx) + + +def _find_simulation_base(path: Path) -> Path: + """Locate the simulation base name from an OPM restart file. + + Parameters + ---------- + path : Path + Directory to search for a ``.UNRST`` file. + Returns + ------- + Path + Restart-file path without its extension. -def find_simulation_base(path: Path) -> Path: - """Locate simulation base name from .UNRST file.""" + Raises + ------ + SystemExit + If the directory contains no ``.UNRST`` result.""" for f in path.iterdir(): if f.suffix == ".UNRST": return f.with_suffix("") - raise FileNotFoundError("No .UNRST file found") + pofff_error( + f"no {cli_error_value('.UNRST')} simulation result was found in " + f"{cli_error_value(str(path))}." + ) + +def _read_opm_results(ctx: SimulationContext) -> None: + """Load OPM restart, initialization, grid, and summary results. -def read_opm(ctx: SimulationContext) -> None: - """Load OPM restart, grid, and summary data.""" + Populate OPM handles, injection-relative times, pore volumes, active-cell + indices, and grid dimensions on ``ctx``. + + Parameters + ---------- + ctx : SimulationContext + Mutable simulation context with an initialized simulation base path.""" assert ctx.sim ctx.unrst = OpmRestart(f"{ctx.sim}.UNRST") @@ -147,76 +244,201 @@ def read_opm(ctx: SimulationContext) -> None: ctx.time_initial = t0 or 0.0 - ctx.ini = OpmFile(f"{ctx.sim}.INIT") - ctx.egrid = OpmGrid(f"{ctx.sim}.EGRID") - ctx.smspec = OpmSummary(f"{ctx.sim}.SMSPEC") + ini = OpmFile(f"{ctx.sim}.INIT") + egrid = OpmGrid(f"{ctx.sim}.EGRID") + smspec = OpmSummary(f"{ctx.sim}.SMSPEC") + ctx.ini = ini + ctx.egrid = egrid + ctx.smspec = smspec - ctx.porv = np.asarray(ctx.ini["PORV"]) - ctx.actind = np.flatnonzero(ctx.porv > 0) + porv = np.asarray(ini["PORV"]) + ctx.porv = porv + ctx.actind = np.flatnonzero(porv > 0) - ctx.times_summary = np.r_[0.0, ctx.smspec["TIME"] * SECONDS_IN_DAY] - ctx.gxyz = tuple(ctx.egrid.dimension) + ctx.times_summary = np.r_[0.0, smspec["TIME"] * SECONDS_IN_DAY] + ctx.gxyz = tuple(egrid.dimension) ctx.norst = len(ctx.unrst.report_steps) -def sparse_data(ctx: SimulationContext) -> None: - """Compute and write sparse time-series benchmark data.""" - res = SparseResults(ctx) - - create_from_summary(ctx, res) - compute_m_c(ctx, res) - interpolate_sparse(ctx, res) - write_sparse_data(res) +def _initialize_sparse_results(ctx: SimulationContext) -> SparseResults: + """Initialize sparse time coordinates and OPM cell-property arrays. + + Parameters + ---------- + ctx : SimulationContext + Loaded simulation context with initialization data and restart times. + + Returns + ------- + SparseResults + Sparse state initialized with times, FIPNUM, DX, and DZ arrays. + + Raises + ------ + SystemExit + If initialization data or restart times are unavailable.""" + ini = ctx.ini + if ini is None: + pofff_error("OPM initialization data are unavailable for sparse processing.") + if not ctx.times: + pofff_error("no restart times are available for sparse processing.") + return SparseResults( + ctx=ctx, + times_data=np.arange(0.0, ctx.times[-1] + ctx.sparse_t, ctx.sparse_t), + fipnum=np.asarray(ini["FIPNUM"]), + dx=np.asarray(ini["DX"]), + dz=np.asarray(ini["DZ"]), + ) -def create_from_summary(ctx: SimulationContext, res: SparseResults) -> None: - """Extract sparse quantities from OPM summary vectors.""" - assert ctx.smspec +def _write_sparse_outputs(ctx: SimulationContext) -> None: + """Calculate and write the sparse benchmark time series. + + Parameters + ---------- + ctx : SimulationContext + Loaded simulation context and OPM results.""" + res = _initialize_sparse_results(ctx) + + _extract_summary_quantities(ctx, res) + _compute_mass_center_metric(ctx, res) + _interpolate_sparse_quantities(ctx, res) + _write_time_series_csv(res) + + +def _get_initial_water_pressure( + unrst: OpmRestart, + fipnum: NDArray, + region: int, +) -> float: + """Return the initial water pressure for one FIP region. + + Parameters + ---------- + unrst : OpmRestart + Open OPM restart result containing pressure and capillary pressure. + fipnum : NDArray + FIPNUM values in global cell order. + region : int + FIPNUM identifying the observation-point region. + + Returns + ------- + float + Water pressure corrected for capillary pressure, in pascals.""" + index = list(fipnum).index(region) + pressure = unrst["PRESSURE", 0][index] + return float((pressure - unrst["PCGW", 0][index]) * 1e5) + + +def _sum_summary_vectors( + smry: OpmSummary, + expressions: Iterable[str], +) -> NDArray: + """Sum selected OPM summary vectors and convert CO2 to kilograms. + + Parameters + ---------- + smry : OpmSummary + Open OPM summary result containing the requested vectors. + expressions : Iterable[str] + OPM summary-vector names to add. + + Returns + ------- + NDArray + Total mass time series in kilograms.""" + return np.asarray(sum(smry[expression] for expression in expressions)) * KMOL_TO_KG + + +def _extract_summary_quantities(ctx: SimulationContext, res: SparseResults) -> None: + """Extract pressures and regional CO2 masses from OPM summary vectors. + + Parameters + ---------- + ctx : SimulationContext + Loaded simulation context and OPM summary data. + res : SparseResults + Sparse result arrays populated in place. + + Raises + ------ + SystemExit + If summary or restart results are unavailable.""" smry = ctx.smspec + unrst = ctx.unrst + if smry is None or unrst is None: + pofff_error("OPM summary and restart data are required for sparse processing.") smry_keys = smry.keys() bwpr = sorted(k for k in smry_keys if k.startswith("BWPR") and "," in k)[:2] - def initial_pressure(fip: int) -> float: - assert ctx.unrst - idx = list(res.fipnum).index(fip) - p = ctx.unrst["PRESSURE", 0][idx] - return (p - ctx.unrst["PCGW", 0][idx]) * 1e5 - - res.pop1 = np.r_[initial_pressure(8), smry[bwpr[0]] * 1e5] - res.pop2 = np.r_[initial_pressure(9), smry[bwpr[1]] * 1e5] - - def sum_smry(exprs) -> NDArray: - return sum(smry[e] for e in exprs) * KMOL_TO_KG - - res.moba = sum_smry(f"RGKDM:{i}" for i in FIP_DISS_A) - res.imma = sum_smry(f"RGKDI:{i}" for i in FIP_DISS_A) - res.dissa = sum_smry(f"RWCD:{i}" for i in FIP_DISS_A) - - res.seala = sum_smry( - f"{k}:{i}" for i in FIP_SEAL_A for k in ("RWCD", "RGKDM", "RGKDI") + res.pop1 = np.r_[ + _get_initial_water_pressure(unrst, res.fipnum, 8), + smry[bwpr[0]] * 1e5, + ] + res.pop2 = np.r_[ + _get_initial_water_pressure(unrst, res.fipnum, 9), + smry[bwpr[1]] * 1e5, + ] + + res.moba = _sum_summary_vectors(smry, (f"RGKDM:{i}" for i in FIP_DISS_A)) + res.imma = _sum_summary_vectors(smry, (f"RGKDI:{i}" for i in FIP_DISS_A)) + res.dissa = _sum_summary_vectors(smry, (f"RWCD:{i}" for i in FIP_DISS_A)) + + res.seala = _sum_summary_vectors( + smry, + ( + f"{keyword}:{region}" + for region in FIP_SEAL_A + for keyword in ("RWCD", "RGKDM", "RGKDI") + ), ) - res.mobb = sum_smry(f"RGKDM:{i}" for i in FIP_DISS_B) - res.immb = sum_smry(f"RGKDI:{i}" for i in FIP_DISS_B) - res.dissb = sum_smry(f"RWCD:{i}" for i in FIP_DISS_B) - - res.sealb = sum_smry( - f"{k}:{i}" for i in FIP_SEAL_B for k in ("RWCD", "RGKDM", "RGKDI") + res.mobb = _sum_summary_vectors(smry, (f"RGKDM:{i}" for i in FIP_DISS_B)) + res.immb = _sum_summary_vectors(smry, (f"RGKDI:{i}" for i in FIP_DISS_B)) + res.dissb = _sum_summary_vectors(smry, (f"RWCD:{i}" for i in FIP_DISS_B)) + + res.sealb = _sum_summary_vectors( + smry, + ( + f"{keyword}:{region}" + for region in FIP_SEAL_B + for keyword in ("RWCD", "RGKDM", "RGKDI") + ), ) res.sealt = ( res.seala + res.sealb - + sum_smry(f"{k}:{i}" for i in (7, 9) for k in ("RWCD", "RGKDM", "RGKDI")) + + _sum_summary_vectors( + smry, + ( + f"{keyword}:{region}" + for region in (7, 9) + for keyword in ("RWCD", "RGKDM", "RGKDI") + ), + ) ) -def compute_m_c(ctx: SimulationContext, res: SparseResults) -> None: - """Compute mass-center migration metric.""" - assert ctx.unrst - assert ctx.gxyz - assert ctx.norst +def _compute_mass_center_metric(ctx: SimulationContext, res: SparseResults) -> None: + """Calculate the box-C mass-center migration series. + + Parameters + ---------- + ctx : SimulationContext + Loaded simulation context and restart metadata. + res : SparseResults + Sparse result state receiving migration values. + + Raises + ------ + SystemExit + If restart results or grid metadata are unavailable.""" + unrst = ctx.unrst + if unrst is None or ctx.gxyz is None or ctx.norst is None: + pofff_error("OPM restart metadata are required for the mass-center metric.") boxc = np.array([fip in FIP_BOXC for fip in res.fipnum]) nx, ny, _ = ctx.gxyz @@ -225,8 +447,8 @@ def compute_m_c(ctx: SimulationContext, res: SparseResults) -> None: boxc_z = np.roll(boxc, -nx * ny) for t_n in range(1, ctx.norst): - rss = np.asarray(ctx.unrst["RSW", t_n]) - rssat = np.asarray(ctx.unrst["RSWSAT", t_n]) + rss = np.asarray(unrst["RSW", t_n]) + rssat = np.asarray(unrst["RSWSAT", t_n]) xcw = rss / (rss + WAT_DEN_REF / GAS_DEN_REF) xcw /= rssat / (rssat + WAT_DEN_REF / GAS_DEN_REF) @@ -238,8 +460,15 @@ def compute_m_c(ctx: SimulationContext, res: SparseResults) -> None: res.m_c_series.append(mc) -def interpolate_sparse(ctx: SimulationContext, res: SparseResults) -> None: - """Interpolate sparse quantities onto uniform time grid.""" +def _interpolate_sparse_quantities(ctx: SimulationContext, res: SparseResults) -> None: + """Interpolate sparse quantities to a uniform time grid. + + Parameters + ---------- + ctx : SimulationContext + Simulation and summary time coordinates. + res : SparseResults + Sparse quantities replaced by their interpolated arrays.""" assert ctx.times_summary is not None res.m_c = interp1d( @@ -267,8 +496,13 @@ def interpolate_sparse(ctx: SimulationContext, res: SparseResults) -> None: setattr(res, name, interp1d(x, y, fill_value="extrapolate")(res.times_data)) -def write_sparse_data(res: SparseResults) -> None: - """Write sparse time-series values to CSV.""" +def _write_time_series_csv(res: SparseResults) -> None: + """Write sparse benchmark quantities to ``time_series.csv``. + + Parameters + ---------- + res : SparseResults + Interpolated pressure, mass, and migration series.""" assert res.m_c is not None header = ( @@ -290,19 +524,38 @@ def write_sparse_data(res: SparseResults) -> None: Path("time_series.csv").write_text("\n".join(lines), encoding="utf-8") -def dense_data(ctx: SimulationContext) -> None: - """Write spatial maps for selected dense output times.""" - assert ctx.unrst - assert ctx.porv is not None - assert ctx.actind is not None +def _write_dense_outputs(ctx: SimulationContext) -> None: + """Write spatial maps at the requested dense-output times. + + Map active-cell saturation and dissolved-CO2 concentration to the reporting + grid and write one CSV file for every requested restart time that is available. + + Parameters + ---------- + ctx : SimulationContext + Loaded restart data, reporting-grid resolution, and cell mapping. + + Raises + ------ + SystemExit + If restart or initialization data are unavailable.""" + unrst = ctx.unrst + porv = ctx.porv + actind = ctx.actind + if unrst is None or porv is None or actind is None: + pofff_error( + "OPM restart and initialization data are required for dense output." + ) dx, _, dz = ctx.dims refx = 0.5 * ( - np.linspace(0, dx, ctx.nxz[0] + 1)[:-1] + np.linspace(0, dx, ctx.nxz[0] + 1)[1:] + np.linspace(0, dx, ctx.resolution[0] + 1)[:-1] + + np.linspace(0, dx, ctx.resolution[0] + 1)[1:] ) refz = 0.5 * ( - np.linspace(0, dz, ctx.nxz[1] + 1)[:-1] + np.linspace(0, dz, ctx.nxz[1] + 1)[1:] + np.linspace(0, dz, ctx.resolution[1] + 1)[:-1] + + np.linspace(0, dz, ctx.resolution[1] + 1)[1:] ) cell_cent = np.load(ctx.maps).astype(int) @@ -312,23 +565,44 @@ def dense_data(ctx: SimulationContext) -> None: continue t_n = ctx.times.index(t) - sgas = np.abs(ctx.unrst["SGAS", t_n]) - rhow = ctx.unrst["WAT_DEN", t_n] - rsw = ctx.unrst["RSW", t_n] + sgas = np.abs(unrst["SGAS", t_n]) + rhow = unrst["WAT_DEN", t_n] + rsw = unrst["RSW", t_n] xlco2 = rsw / (rsw + WAT_DEN_REF / GAS_DEN_REF) - s_full = np.zeros(len(ctx.porv)) - c_full = np.zeros(len(ctx.porv)) - s_full[ctx.actind] = sgas - c_full[ctx.actind] = xlco2 * rhow + s_full = np.zeros(len(porv)) + c_full = np.zeros(len(porv)) + s_full[actind] = sgas + c_full[actind] = xlco2 * rhow hours = int(t / 3600.0) if t % 3600 == 0 else t / 3600.0 - write_dense_data(ctx, [refx, refz], s_full[cell_cent], c_full[cell_cent], hours) + _write_spatial_map_csv( + ctx, [refx, refz], s_full[cell_cent], c_full[cell_cent], hours + ) -def write_dense_data(ctx: SimulationContext, refxz, sgas, cco2, hours) -> None: - """Write single spatial map CSV.""" - nx, nz = ctx.nxz +def _write_spatial_map_csv( + ctx: SimulationContext, + refxz: list[NDArray], + sgas: NDArray, + cco2: NDArray, + hours: float, +) -> None: + """Write one saturation and concentration reporting-grid map. + + Parameters + ---------- + ctx : SimulationContext + Output location and reporting-grid resolution. + refxz : list[NDArray] + Reporting-cell centre coordinates in x and z order, in metres. + sgas : NDArray + Gas saturation in reporting-cell order. + cco2 : NDArray + Dissolved-CO2 concentration in reporting-cell order. + hours : int | float + Evaluation time in hours used in the output filename.""" + nx, nz = ctx.resolution lines = ["x,z,saturation,concentration"] for k, z in enumerate(refxz[1]): diff --git a/src/pofff/jobs/delete.py b/src/pofff/jobs/delete.py index 45d9c87..c65426a 100755 --- a/src/pofff/jobs/delete.py +++ b/src/pofff/jobs/delete.py @@ -2,7 +2,11 @@ # SPDX-FileCopyrightText: 2025-2026 NORCE Research AS # SPDX-License-Identifier: GPL-3.0 -"""Script to delete large files""" +"""Remove large generated files after a history-matching evaluation. + +The job preserves nonmonotonic evaluations and otherwise deletes simulator, +include, restart, summary, initialization, CSV, and deck files from the current +working directory.""" import sys from pathlib import Path diff --git a/src/pofff/jobs/metric.py b/src/pofff/jobs/metric.py index fb5fd54..019ef37 100755 --- a/src/pofff/jobs/metric.py +++ b/src/pofff/jobs/metric.py @@ -2,8 +2,11 @@ # Modified from https://github.com/fluidflower/general/blob/main/evaluation/emd.py and # https://github.com/fluidflower/general/blob/main/evaluation/calculate_segmented_emds.py -"""Plot spatial results and compute Wasserstein (EMD) distances -between simulated and experimental segmentations.""" +"""Calculate segmented FluidFlower Wasserstein-distance metrics. + +The job converts simulated saturation and dissolved-CO2 fields into benchmark +segmentation classes, compares them with experimental segmentations using Earth +Mover's Distance, and writes per-observation metrics and the Everest objective.""" import argparse import os @@ -15,9 +18,23 @@ from PIL import Image -def calculate_emd(file_1: str, file_2: str) -> float: - """Compute Wasserstein distance between two images. - Reproduces the original numerical behavior exactly.""" +def _calculate_wasserstein_distance(file_1: str, file_2: str) -> float: + """Calculate the Earth Mover's Distance between two segmented images. + + Both images are converted to grayscale, resized to 140 by 60 pixels, normalized, + and compared on the physical FluidFlower domain. + + Parameters + ---------- + file_1 : str + Path to the first segmented PNG image. + file_2 : str + Path to the second segmented PNG image, resized in place. + + Returns + ------- + float + Wasserstein distance in physical x-z coordinates.""" im1 = Image.open(file_1).convert("L") im1 = im1.resize((140, 60), Image.Resampling.LANCZOS) @@ -56,15 +73,32 @@ def calculate_emd(file_1: str, file_2: str) -> float: ) -def generate_segment_map( +def _segment_spatial_map( file_name: str, xlim: tuple[float, float], zlim: tuple[float, float], satmin: float, conmin: float, -) -> NDArray[np.int_]: - """Convert continuous saturation and concentration fields - into a discrete segmentation map.""" +) -> NDArray[np.int64]: + """Convert saturation and concentration fields to benchmark classes. + + Parameters + ---------- + file_name : str + Spatial-map CSV containing x, z, saturation, and concentration columns. + xlim : tuple[float, float] + Minimum and maximum x coordinates, in metres. + zlim : tuple[float, float] + Minimum and maximum z coordinates, in metres. + satmin : float + Minimum gas saturation for class 2. + conmin : float + Minimum dissolved-CO2 concentration for class 1. + + Returns + ------- + NDArray[np.int64] + Segmentation on the 120 by 280 benchmark reporting grid.""" n_x = np.arange(xlim[0], xlim[1] + 5.0e-3, 1.0e-2).size - 1 n_z = np.arange(zlim[0], zlim[1] + 5.0e-3, 1.0e-2).size - 1 @@ -96,13 +130,28 @@ def generate_segment_map( return segmentmap -def before_emd( - model_result: NDArray[np.int_], - experimental_data: NDArray[np.int_], +def _prepare_distance_images( + model_result: NDArray[np.int64], + experimental_data: NDArray[np.int64], indx: int, ) -> float: - """Create PNG images from segmentation maps and compute EMD. - PNG round-trip is required to preserve original behavior.""" + """Write segmentation images and calculate their Wasserstein distance. + + The PNG round trip is retained to preserve the established metric calculation. + + Parameters + ---------- + model_result : NDArray[np.int64] + Simulated benchmark classes on the 120 by 280 grid. + experimental_data : NDArray[np.int64] + Experimental benchmark classes on the same grid. + indx : int | str + Evaluation-time identifier used in temporary filenames. + + Returns + ------- + float + Wasserstein distance between the generated images.""" mod_image = Image.new("L", (280, 120)) mod_pixels = mod_image.load() @@ -134,11 +183,16 @@ def before_emd( exp_image.save(f"exp_{indx}.png") - return calculate_emd(f"mod_{indx}.png", f"exp_{indx}.png") + return _calculate_wasserstein_distance(f"mod_{indx}.png", f"exp_{indx}.png") def main(argv=None) -> None: - """Segment maps at requested times and compute total EMD score.""" + """Calculate segmented metrics and the Everest objective. + + Parameters + ---------- + argv : list[str] | None, optional + Arguments to parse instead of the process command line.""" parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, description="Segment maps at requested times and compute total EMD score.", @@ -154,7 +208,7 @@ def main(argv=None) -> None: file.write("-1") sys.exit() - args = vars(parser.parse_known_args(argv)[0]) + args = vars(parser.parse_args(argv)) times = [row.strip() for row in args["times"].split(",")] everest = 0.0 @@ -164,7 +218,7 @@ def main(argv=None) -> None: for time in times: file_i = f"spatial_map_{time}h.csv" - model_result = generate_segment_map( + model_result = _segment_spatial_map( file_i, (0.0, 2.8), (0.0, 1.2), @@ -185,7 +239,7 @@ def main(argv=None) -> None: dist = ( 8.5 * 100 - * before_emd( + * _prepare_distance_images( model_result, experimental_data, time, diff --git a/src/pofff/utils/inputvalues.py b/src/pofff/utils/inputvalues.py index 53da72b..e2447db 100644 --- a/src/pofff/utils/inputvalues.py +++ b/src/pofff/utils/inputvalues.py @@ -1,14 +1,30 @@ # SPDX-FileCopyrightText: 2025-2026 NORCE Research AS # SPDX-License-Identifier: GPL-3.0 +# pylint: disable=R0912,R0913,R0914,R0915,R0917,C0302 -"""Utility functions to prepare and normalize input values for pofff.""" +"""Load, validate, and normalize pofff configuration values. +This module combines command-line settings with TOML input, validates common +simulation variables and workflow-specific ERT or Everest options, extracts +facies and history-matching definitions, and populates derived runtime settings. +Validation reports accepted values through the shared terminal helpers.""" + +import ast +import math import tomllib from pathlib import Path +from typing import Any import numpy as np from pofff.config.config import CliConfig, PofffConfig +from pofff.utils.terminal import ( + cli_correct_value, + cli_error_value, + cli_warning_value, + pofff_error, + pofff_warning, +) FACIES_KEYS = { "poro", @@ -28,15 +44,42 @@ } -def load_toml(filename: str) -> dict: - """Load a TOML configuration file and return its content.""" +def load_toml(filename: str, mode: str) -> dict: + """Load and validate a TOML configuration file. + + Parameters + ---------- + filename : str + Path to the input file. + mode : str + Selected pofff execution mode. + + Returns + ------- + dict + Validated and normalized TOML configuration values. + + Raises + ------ + SystemExit + If the TOML configuration is invalid.""" with open(filename, "rb") as f: toml = tomllib.load(f) - return toml + return _validate_toml(toml, mode) def postprocess_config(cfg: PofffConfig, toml: dict) -> None: - """Enrich configuration with derived values and runtime flags.""" + """Enrich configuration with derived values and runtime flags. + + The function converts diffusion to m²/day and populates derived grid, + history-matching, and workflow flags on ``cfg``. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + toml : dict + Validated TOML values used to populate the runtime configuration.""" cfg.para.update( { "permx7": 0, @@ -53,7 +96,6 @@ def postprocess_config(cfg: PofffConfig, toml: dict) -> None: "npnt7": 2, } ) - cfg.nxz = [int(np.sum(cfg.x)), int(np.sum(cfg.z))] cfg.diffusion = 86400 * np.array(cfg.diffusion) # Convert to m²/day cfg.data = cfg.fol.name.upper() @@ -75,7 +117,14 @@ def postprocess_config(cfg: PofffConfig, toml: dict) -> None: def process_tuning(cfg: PofffConfig) -> None: - """Enable tuning and normalize injection specifications if requested.""" + """Enable tuning and normalize injection specifications if requested. + + Injection rows with TUNING text are expanded in place and ``cfg.tuning`` is enabled. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" for token in cfg.flow.split(): if "--enable-tuning" not in token: continue @@ -94,7 +143,17 @@ def process_tuning(cfg: PofffConfig) -> None: def extract_facies(data: dict) -> dict: - """Extract definitions and remove them from the main config dict.""" + """Extract definitions and remove them from the main config dict. + + Parameters + ---------- + data : dict + Configuration dictionary modified while extracting definitions. + + Returns + ------- + dict + Facies tables removed from the input dictionary.""" facies = {k: data.pop(k) for k in list(data) if k.startswith("facie")} for i in range(1, 8): for name in FACIES_KEYS: @@ -109,7 +168,21 @@ def build_config( cli: CliConfig, toml: dict, ) -> PofffConfig: - """Build and return a fully initialized PofffConfig object.""" + """Build and return a fully initialized PofffConfig object. + + Parameters + ---------- + pofff_path : Path + Package root containing templates, geology, jobs, and benchmark data. + cli : CliConfig + Cli used by this operation. + toml : dict + Validated TOML values used to populate the runtime configuration. + + Returns + ------- + PofffConfig + Runtime configuration populated from CLI and TOML values.""" facies = extract_facies(toml) cfg = PofffConfig( path=pofff_path, @@ -129,3 +202,1106 @@ def build_config( for values in facies.values(): cfg.para.update(values) return cfg + + +def _is_finite_number(value: Any) -> bool: + """Return whether a value is a finite non-Boolean number. + + Parameters + ---------- + value : Any + Value to inspect or format. + + Returns + ------- + bool + Whether the value is a finite, non-Boolean number.""" + return ( + isinstance(value, (int, float)) + and not isinstance(value, bool) + and math.isfinite(value) + ) + + +def _is_integer(value: Any) -> bool: + """Return whether a value is a non-Boolean integer. + + Parameters + ---------- + value : Any + Value to inspect or format. + + Returns + ------- + bool + Whether the value is a non-Boolean integer.""" + return isinstance(value, int) and not isinstance(value, bool) + + +def _add_validation_error(errors: list[str], message: str) -> None: + """Add a TOML validation error. + + Parameters + ---------- + errors : list[str] + Validation messages collected during the current validation pass. + message : str + Human-readable message to display or append.""" + errors.append(message) + + +def _validate_string(cfg_file: dict[str, Any], key: str, errors: list[str]) -> bool: + """Check that a TOML variable is a non-empty string. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + key : str + Configuration variable or history-matching parameter name. + errors : list[str] + Validation messages collected during the current validation pass. + + Returns + ------- + bool + Whether the variable is present and is a non-empty string.""" + if key not in cfg_file: + return False + value = cfg_file[key] + if not isinstance(value, str) or not value.strip(): + _add_validation_error( + errors, + f"variable {cli_error_value(key)} has invalid value " + f"{cli_error_value(str(value))}, expected " + f"{cli_correct_value('a non-empty string')}.", + ) + return False + return True + + +def _validate_number( + cfg_file: dict[str, Any], + key: str, + errors: list[str], + minimum: float | None = None, + maximum: float | None = None, + strict: bool = False, + integer: bool = False, +) -> bool: + """Check that a TOML variable is numeric and in the expected range. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + key : str + Configuration variable or history-matching parameter name. + errors : list[str] + Validation messages collected during the current validation pass. + minimum : float | None, optional + Optional lower bound for accepted values. + maximum : float | None, optional + Optional upper bound for accepted values. + strict : bool, optional + Whether the lower bound is exclusive. + integer : bool, optional + Whether accepted entries must be non-Boolean integers. + + Returns + ------- + bool + Whether the variable is present and satisfies the numeric constraints.""" + if key not in cfg_file: + return False + value = cfg_file[key] + valid_type = _is_integer(value) if integer else _is_finite_number(value) + if not valid_type: + expected = "an integer" if integer else "a finite number" + _add_validation_error( + errors, + f"variable {cli_error_value(key)} has invalid value " + f"{cli_error_value(str(value))}, expected {cli_correct_value(expected)}.", + ) + return False + valid = True + if minimum is not None and (value <= minimum if strict else value < minimum): + condition = ( + f"a value greater than {minimum}" + if strict + else f"a value greater than or equal to {minimum}" + ) + _add_validation_error( + errors, + f"variable {cli_error_value(key)} has invalid value " + f"{cli_error_value(str(value))}, expected {cli_correct_value(condition)}.", + ) + valid = False + if maximum is not None and value > maximum: + _add_validation_error( + errors, + f"variable {cli_error_value(key)} has invalid value " + f"{cli_error_value(str(value))}, expected " + f"{cli_correct_value(f'a value less than or equal to {maximum}')}.", + ) + valid = False + return valid + + +def _validate_array( + cfg_file: dict[str, Any], + key: str, + errors: list[str], + length: int | None = None, + integer: bool = False, + minimum: float | None = None, + strict: bool = False, + nonempty: bool = False, +) -> bool: + """Check the type, length, and numeric values of a TOML array. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + key : str + Configuration variable or history-matching parameter name. + errors : list[str] + Validation messages collected during the current validation pass. + length : int | None, optional + Required array length, when specified. + integer : bool, optional + Whether accepted entries must be non-Boolean integers. + minimum : float | None, optional + Optional lower bound for accepted values. + strict : bool, optional + Whether the lower bound is exclusive. + nonempty : bool, optional + Whether the array must contain at least one entry. + + Returns + ------- + bool + Whether the variable is present and satisfies the array constraints.""" + if key not in cfg_file: + return False + value = cfg_file[key] + if not isinstance(value, list): + _add_validation_error( + errors, + f"variable {cli_error_value(key)} has invalid type " + f"{cli_error_value(type(value).__name__)}, expected " + f"{cli_correct_value('an array')}.", + ) + return False + if length is not None and len(value) != length: + _add_validation_error( + errors, + f"variable {cli_error_value(key)} has {cli_error_value(str(len(value)))} " + f"entries, expected {cli_correct_value(str(length))}.", + ) + return False + if nonempty and not value: + _add_validation_error( + errors, + f"variable {cli_error_value(key)} must contain " + f"{cli_correct_value('at least one entry')}.", + ) + return False + valid = True + for index, entry in enumerate(value): + correct_type = _is_integer(entry) if integer else _is_finite_number(entry) + expected = "an integer" if integer else "a finite number" + if not correct_type: + _add_validation_error( + errors, + f"variable {cli_error_value(f'{key}[{index}]')} has invalid value " + f"{cli_error_value(str(entry))}, expected " + f"{cli_correct_value(expected)}.", + ) + valid = False + continue + if minimum is not None and (entry <= minimum if strict else entry < minimum): + condition = ( + f"a value greater than {minimum}" + if strict + else f"a value greater than or equal to {minimum}" + ) + _add_validation_error( + errors, + f"variable {cli_error_value(f'{key}[{index}]')} has invalid value " + f"{cli_error_value(str(entry))}, expected " + f"{cli_correct_value(condition)}.", + ) + valid = False + return valid + + +def _validate_matrix( + cfg_file: dict[str, Any], + key: str, + errors: list[str], + rows: int, + columns: int, +) -> bool: + """Check the dimensions and numeric values of a TOML matrix. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + key : str + Configuration variable or history-matching parameter name. + errors : list[str] + Validation messages collected during the current validation pass. + rows : int + Required number of matrix rows. + columns : int + Required number of entries in each matrix row. + + Returns + ------- + bool + Whether the variable is present and has the required numeric shape.""" + if key not in cfg_file: + return False + value = cfg_file[key] + if not isinstance(value, list): + _add_validation_error( + errors, + f"variable {cli_error_value(key)} has invalid type " + f"{cli_error_value(type(value).__name__)}, expected " + f"{cli_correct_value('an array of arrays')}.", + ) + return False + if len(value) != rows: + _add_validation_error( + errors, + f"variable {cli_error_value(key)} has {cli_error_value(str(len(value)))} " + f"rows, expected {cli_correct_value(str(rows))}.", + ) + return False + valid = True + for row_index, row in enumerate(value): + if not isinstance(row, list): + _add_validation_error( + errors, + f"variable {cli_error_value(f'{key}[{row_index}]')} has invalid " + f"type {cli_error_value(type(row).__name__)}, expected " + f"{cli_correct_value('an array')}.", + ) + valid = False + continue + if len(row) != columns: + _add_validation_error( + errors, + f"variable {cli_error_value(f'{key}[{row_index}]')} has " + f"{cli_error_value(str(len(row)))} entries, expected " + f"{cli_correct_value(str(columns))}.", + ) + valid = False + continue + for column_index, entry in enumerate(row): + if not _is_finite_number(entry): + _add_validation_error( + errors, + f"variable " + f"{cli_error_value(f'{key}[{row_index}][{column_index}]')} " + f"has invalid value {cli_error_value(str(entry))}, expected " + f"{cli_correct_value('a finite number')}.", + ) + valid = False + return valid + + +def _validate_expression(cfg_file: dict[str, Any], key: str, errors: list[str]) -> None: + """Check that a TOML variable contains a valid Python expression. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + key : str + Configuration variable or history-matching parameter name. + errors : list[str] + Validation messages collected during the current validation pass.""" + if not _validate_string(cfg_file, key, errors): + return + try: + ast.parse(cfg_file[key], mode="eval") + except SyntaxError as err: + _add_validation_error( + errors, + f"variable {cli_error_value(key)} contains invalid Python expression " + f"{cli_error_value(cfg_file[key])}: {err.msg}.", + ) + + +def _validate_thickness(cfg_file: dict[str, Any], errors: list[str]) -> None: + """Check the thickness-map selection or numeric thickness. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + errors : list[str] + Validation messages collected during the current validation pass.""" + if "thickness" not in cfg_file: + return + value = cfg_file["thickness"] + if isinstance(value, str) and value in {"initial", "final"}: + return + if _is_finite_number(value) and value > 0: + return + _add_validation_error( + errors, + f"variable {cli_error_value('thickness')} has invalid value " + f"{cli_error_value(str(value))}, expected " + f"{cli_correct_value('initial, final, or a positive finite number')}.", + ) + + +def _validate_facies( + cfg_file: dict[str, Any], key: str, index: int, errors: list[str] +) -> None: + """Check one fixed facies-property definition. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + key : str + Configuration variable or history-matching parameter name. + index : int + One-based facies index. + errors : list[str] + Validation messages collected during the current validation pass.""" + if key not in cfg_file: + return + facies = cfg_file[key] + if not isinstance(facies, dict): + _add_validation_error( + errors, + f"variable {cli_error_value(key)} has invalid type " + f"{cli_error_value(type(facies).__name__)}, expected " + f"{cli_correct_value('a table of facies properties')}.", + ) + return + required_names = { + "permx", + "permz", + "poro", + "disperc", + "swi", + "sni", + "pen", + "nkrw", + "nkrn", + "npe", + "thre", + "npnt", + } + required = {f"{name}{index}" for name in required_names} + allowed = {f"{name}{index}" for name in FACIES_KEYS} + for name in sorted(required - facies.keys()): + _add_validation_error( + errors, + f"missing required facies property {cli_error_value(name)} in " + f"{cli_error_value(key)}.", + ) + for name in sorted(facies.keys() - allowed): + _add_validation_error( + errors, + f"unknown or mismatched facies property {cli_error_value(name)} in " + f"{cli_error_value(key)}.", + ) + valid_values: dict[str, float | int] = {} + for name, value in facies.items(): + base = name.removesuffix(str(index)) + if name not in allowed: + continue + if base == "npnt": + valid = _is_integer(value) and value >= 2 + expected = "an integer greater than or equal to 2" + elif base in {"poro", "swi", "sni"}: + valid = _is_finite_number(value) and 0 <= value <= 1 + expected = "a value between 0 and 1" + elif base in {"nkrw", "nkrn", "npe"}: + valid = _is_finite_number(value) and value > 0 + expected = "a positive finite number" + else: + valid = _is_finite_number(value) and value >= 0 + expected = "a non-negative finite number" + if not valid: + _add_validation_error( + errors, + f"variable {cli_error_value(name)} has invalid value " + f"{cli_error_value(str(value))}, expected " + f"{cli_correct_value(expected)}.", + ) + else: + valid_values[base] = value + if "swi" in valid_values and "sni" in valid_values: + total = valid_values["swi"] + valid_values["sni"] + if total > 1: + _add_validation_error( + errors, + f"variables {cli_error_value(f'swi{index}')} and " + f"{cli_error_value(f'sni{index}')} have a combined value " + f"{cli_error_value(str(total))}, expected a combined value less " + f"than or equal to {cli_correct_value('1')}.", + ) + + +def _validate_injection_schedule(cfg_file: dict[str, Any], errors: list[str]) -> bool: + """Check injection values and return whether TUNING values are defined. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + errors : list[str] + Validation messages collected during the current validation pass. + + Returns + ------- + bool + Whether at least one injection row defines TUNING values.""" + if "inj" not in cfg_file: + return False + inj = cfg_file["inj"] + if not isinstance(inj, list): + _add_validation_error( + errors, + f"variable {cli_error_value('inj')} has invalid type " + f"{cli_error_value(type(inj).__name__)}, expected " + f"{cli_correct_value('an array of injection rows')}.", + ) + return False + if not inj: + _add_validation_error( + errors, + f"variable {cli_error_value('inj')} must contain " + f"{cli_correct_value('at least one injection row')}.", + ) + return False + tuning_defined = False + for row_index, row in enumerate(inj): + if not isinstance(row, list): + _add_validation_error( + errors, + f"variable {cli_error_value(f'inj[{row_index}]')} has invalid type " + f"{cli_error_value(type(row).__name__)}, expected " + f"{cli_correct_value('an array')}.", + ) + continue + if len(row) not in {4, 5}: + _add_validation_error( + errors, + f"variable {cli_error_value(f'inj[{row_index}]')} has " + f"{cli_error_value(str(len(row)))} entries, expected " + f"{cli_correct_value('4 entries, or 5 with TUNING values')}.", + ) + continue + for column_index in range(4): + value = row[column_index] + variable = f"inj[{row_index}][{column_index}]" + if not _is_finite_number(value): + _add_validation_error( + errors, + f"variable {cli_error_value(variable)} has invalid value " + f"{cli_error_value(str(value))}, expected " + f"{cli_correct_value('a finite number')}.", + ) + elif column_index in {0, 1} and value <= 0: + _add_validation_error( + errors, + f"variable {cli_error_value(variable)} has invalid value " + f"{cli_error_value(str(value))}, expected " + f"{cli_correct_value('a positive value')}.", + ) + elif column_index in {2, 3} and value < 0: + _add_validation_error( + errors, + f"variable {cli_error_value(variable)} has invalid value " + f"{cli_error_value(str(value))}, expected " + f"{cli_correct_value('a non-negative injection rate')}.", + ) + if len(row) == 5: + tuning_defined = True + if not isinstance(row[4], str) or not row[4].strip(): + _add_validation_error( + errors, + f"variable {cli_error_value(f'inj[{row_index}][4]')} has " + f"invalid value {cli_error_value(str(row[4]))}, expected " + f"{cli_correct_value('a non-empty TUNING string')}.", + ) + return tuning_defined + + +def _validate_history_parameter( + cfg_file: dict[str, Any], key: str, workflow: str, errors: list[str] +) -> None: + """Check an ERT distribution or Everest interval definition. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + key : str + Configuration variable or history-matching parameter name. + workflow : str + Effective simulation, ERT, or Everest workflow. + errors : list[str] + Validation messages collected during the current validation pass.""" + value = cfg_file[key] + if not isinstance(value, list): + _add_validation_error( + errors, + f"history-matching variable {cli_error_value(key)} has invalid type " + f"{cli_error_value(type(value).__name__)}, expected " + f"{cli_correct_value('an array')}.", + ) + return + if workflow == "ert": + if len(value) != 3: + _add_validation_error( + errors, + f"history-matching variable {cli_error_value(key)} has " + f"{cli_error_value(str(len(value)))} entries, expected " + f"{cli_correct_value('a distribution name and two parameters')}.", + ) + return + if not isinstance(value[0], str) or not value[0].strip(): + _add_validation_error( + errors, + f"variable {cli_error_value(f'{key}[0]')} has invalid value " + f"{cli_error_value(str(value[0]))}, expected " + f"{cli_correct_value('a non-empty distribution name')}.", + ) + for index in (1, 2): + if not _is_finite_number(value[index]): + _add_validation_error( + errors, + f"variable {cli_error_value(f'{key}[{index}]')} has invalid " + f"value {cli_error_value(str(value[index]))}, expected " + f"{cli_correct_value('a finite number')}.", + ) + if ( + isinstance(value[0], str) + and value[0].lower() == "uniform" + and all(_is_finite_number(value[index]) for index in (1, 2)) + and value[1] >= value[2] + ): + _add_validation_error( + errors, + f"history-matching variable {cli_error_value(key)} has invalid " + f"uniform bounds {cli_error_value(str(value[1:]))}, expected the " + f"lower bound to be less than {cli_correct_value('the upper bound')}.", + ) + return + if len(value) != 4: + _add_validation_error( + errors, + f"history-matching variable {cli_error_value(key)} has " + f"{cli_error_value(str(len(value)))} entries, expected " + f"{cli_correct_value('initial, minimum, maximum, and scale values')}.", + ) + return + if not all(_is_finite_number(entry) for entry in value): + for index, entry in enumerate(value): + if not _is_finite_number(entry): + _add_validation_error( + errors, + f"variable {cli_error_value(f'{key}[{index}]')} has invalid " + f"value {cli_error_value(str(entry))}, expected " + f"{cli_correct_value('a finite number')}.", + ) + return + initial, minimum, maximum, scale = value + if minimum >= maximum: + _add_validation_error( + errors, + f"history-matching variable {cli_error_value(key)} has invalid bounds " + f"{cli_error_value(str([minimum, maximum]))}, expected the minimum " + f"to be less than {cli_correct_value('the maximum')}.", + ) + elif not minimum <= initial <= maximum: + _add_validation_error( + errors, + f"variable {cli_error_value(f'{key}[0]')} has invalid value " + f"{cli_error_value(str(initial))}, expected a value between " + f"{cli_correct_value(str(minimum))} and " + f"{cli_correct_value(str(maximum))}.", + ) + if not _is_integer(scale) or scale <= 0: + _add_validation_error( + errors, + f"variable {cli_error_value(f'{key}[3]')} has invalid value " + f"{cli_error_value(str(scale))}, expected " + f"{cli_correct_value('a positive integer scale')}.", + ) + + +def _is_tuning_enabled(flow: Any) -> bool: + """Return whether TUNING is enabled in the Flow command. + + Parameters + ---------- + flow : Any + Flow used by this operation. + + Returns + ------- + bool + Whether the Flow command explicitly enables TUNING.""" + if not isinstance(flow, str): + return False + return any( + token.lower() in {"--enable-tuning=true", "--enable-tuning=1"} + for token in flow.split() + ) + + +def _effective_workflow(cfg_file: dict[str, Any], mode: str) -> str: + """Return the effective simulation, ERT, or Everest workflow. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + mode : str + Selected pofff execution mode. + + Returns + ------- + str + Effective workflow name used for validation.""" + if mode in {"ert", "everest"}: + return mode + if mode == "files" and "cores" in cfg_file: + return "everest" if "popsize" in cfg_file else "ert" + return "simulation" + + +def _warn_and_remove(cfg_file: dict[str, Any], keys: set[str], message: str) -> None: + """Warn about ineffective variables and remove them. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + keys : set[str] + Configuration variables to remove when they are ineffective. + message : str + Human-readable message to display or append.""" + present = sorted(keys & cfg_file.keys()) + if not present: + return + formatted = ", ".join(cli_warning_value(key) for key in present) + plural = len(present) != 1 + pofff_warning( + f"variable{'s' if plural else ''} {formatted} " + f"{'are' if plural else 'is'} only effective for " + f"{cli_correct_value(message)} and will be ignored." + ) + for key in present: + cfg_file.pop(key) + + +def _validate_toml(cfg_file: dict[str, Any], mode: str) -> dict[str, Any]: + """Validate and normalize TOML configuration values. + + Parameters + ---------- + cfg_file : dict[str, Any] + Cfg file used by this operation. + mode : str + Selected pofff execution mode. + + Returns + ------- + dict[str, Any] + Validated configuration values suitable for ``PofffConfig``. + + Raises + ------ + SystemExit + If one or more configuration values are invalid.""" + if not isinstance(cfg_file, dict): + pofff_error( + f"invalid TOML content {cli_error_value(type(cfg_file).__name__)}, " + f"expected {cli_correct_value('a dictionary of configuration variables')}." + ) + cfg_file = cfg_file.copy() + errors: list[str] = [] + common_required = { + "flow", + "grid", + "thickness", + "mult_thickness", + "x", + "z", + "temperature", + "pressure", + "diffusion", + "sources", + "inj", + "krw", + "krn", + "cap", + "facie1", + "facie2", + "facie3", + "facie4", + "facie5", + "facie6", + } + common_optional = {"cores", "maxtime", "delete", "monotonic"} + ert_only = {"ertargs", "ensembles", "enkf_alpha", "errors", "random_seed"} + everest_only = { + "max_function_evaluations", + "max_batch_num", + "strategy", + "maxiter", + "popsize", + "tol", + "mutation", + "recombination", + "rng", + "callback", + "disp", + "polish", + "init", + "atol", + "updating", + "workers", + "constraints", + "x0", + "integrality", + "vectorized", + } + shared_history = {"min_realizations_success"} + internal = { + "path", + "fol", + "deck", + "jobs", + "experiment", + "times", + "msat", + "mcon", + "mode", + "figures", + "location", + "use", + "facies", + "fluxnum", + "fipnum", + "porv", + "multpv", + "dx", + "dz", + "dims", + "sensors", + "sensor_ik", + "source_ik", + "boxa", + "boxb", + "boxc", + "hm", + "hascellmaps", + "everert", + "tuning", + "para", + "nxz", + "data", + } + history_keys = { + f"{name}{index}" for name in FACIES_KEYS for index in range(1, 8) + } | {"thicknessmult"} + configurable = ( + common_required + | common_optional + | ert_only + | everest_only + | shared_history + | history_keys + ) + internal_values = sorted(internal & cfg_file.keys()) + if internal_values: + formatted = ", ".join(cli_error_value(key) for key in internal_values) + plural = len(internal_values) != 1 + pofff_warning( + f"variable{'s' if plural else ''} {formatted} " + f"{'are' if plural else 'is'} managed internally and will be ignored." + ) + for key in internal_values: + cfg_file.pop(key) + unknown_values = sorted(cfg_file.keys() - configurable) + if unknown_values: + formatted = ", ".join(cli_warning_value(key) for key in unknown_values) + plural = len(unknown_values) != 1 + pofff_warning( + f"unknown TOML variable{'s' if plural else ''} {formatted} " + "will be ignored." + ) + for key in unknown_values: + cfg_file.pop(key) + workflow = _effective_workflow(cfg_file, mode) + if workflow == "simulation": + _warn_and_remove(cfg_file, ert_only, "mode = ert") + _warn_and_remove(cfg_file, everest_only, "mode = everest") + _warn_and_remove( + cfg_file, + common_optional | shared_history, + "an ERT or Everest workflow", + ) + present_history = sorted(history_keys & cfg_file.keys()) + if present_history: + formatted = ", ".join(cli_warning_value(key) for key in present_history) + plural = len(present_history) != 1 + pofff_warning( + f"history-matching variable{'s' if plural else ''} {formatted} " + f"{'are' if plural else 'is'} not effective for " + f"{cli_warning_value(f'mode = {mode}')} and will be ignored." + ) + for key in present_history: + cfg_file.pop(key) + elif workflow == "ert": + _warn_and_remove(cfg_file, everest_only, "mode = everest") + else: + _warn_and_remove(cfg_file, ert_only, "mode = ert") + required = set(common_required) + if workflow in {"ert", "everest"}: + required |= {"cores", "min_realizations_success"} + if workflow == "ert": + required |= {"ertargs", "ensembles", "enkf_alpha", "errors"} + elif workflow == "everest": + required |= {"max_function_evaluations", "popsize"} + for key in sorted(required - cfg_file.keys()): + _add_validation_error( + errors, + f"missing required TOML variable {cli_error_value(key)}.", + ) + _validate_string(cfg_file, "flow", errors) + if _validate_string(cfg_file, "grid", errors): + grid = cfg_file["grid"] + if grid not in {"cartesian", "tensor", "corner-point"}: + _add_validation_error( + errors, + f"variable {cli_error_value('grid')} has invalid value " + f"{cli_error_value(grid)}, expected one of " + f"{cli_correct_value('cartesian, corner-point, tensor')}.", + ) + else: + grid = cfg_file.get("grid") + _validate_thickness(cfg_file, errors) + _validate_number(cfg_file, "mult_thickness", errors, minimum=0, strict=True) + refinement_valid = { + key: _validate_array( + cfg_file, + key, + errors, + integer=True, + minimum=0, + strict=True, + nonempty=True, + ) + for key in ("x", "z") + } + if grid == "cartesian": + for key in ("x", "z"): + value = cfg_file.get(key) + if refinement_valid[key] and isinstance(value, list) and len(value) != 1: + _add_validation_error( + errors, + f"variable {cli_error_value(key)} has " + f"{cli_error_value(str(len(value)))} entries, expected " + f"{cli_correct_value('one entry for a Cartesian grid')}.", + ) + elif grid == "corner-point": + value = cfg_file.get("z") + if refinement_valid["z"] and isinstance(value, list) and len(value) != 11: + _add_validation_error( + errors, + f"variable {cli_error_value('z')} has " + f"{cli_error_value(str(len(value)))} entries, expected " + f"{cli_correct_value('11 entries for a corner-point grid')}.", + ) + _validate_array(cfg_file, "temperature", errors, length=2) + _validate_number(cfg_file, "pressure", errors, minimum=0, strict=True) + _validate_array(cfg_file, "diffusion", errors, length=2, minimum=0) + sources_valid = _validate_matrix(cfg_file, "sources", errors, 2, 2) + if sources_valid: + dims = [2.8, 1.2] + for source_index, source in enumerate(cfg_file["sources"]): + for axis, value in enumerate(source): + if value < 0 or value > dims[axis]: + _add_validation_error( + errors, + f"variable " + f"{cli_error_value(f'sources[{source_index}][{axis}]')} has " + f"invalid value {cli_error_value(str(value))}, expected a " + f"value between {cli_correct_value('0')} and " + f"{cli_correct_value(str(dims[axis]))}.", + ) + tuning_defined = _validate_injection_schedule(cfg_file, errors) + for key in ("krw", "krn", "cap"): + _validate_expression(cfg_file, key, errors) + for index in range(1, 7): + _validate_facies(cfg_file, f"facie{index}", index, errors) + if tuning_defined and not _is_tuning_enabled(cfg_file.get("flow")): + pofff_warning( + f"TUNING values are defined in {cli_warning_value('inj')}, but " + f"{cli_warning_value('--enable-tuning=true')} is not set in " + f"{cli_correct_value('flow')}. The TUNING values may not be effective." + ) + if "cores" in cfg_file: + _validate_number( + cfg_file, "cores", errors, minimum=0, strict=True, integer=True + ) + if "maxtime" in cfg_file: + _validate_number(cfg_file, "maxtime", errors, minimum=0) + if "delete" in cfg_file and not isinstance(cfg_file["delete"], bool): + _add_validation_error( + errors, + f"variable {cli_error_value('delete')} has invalid value " + f"{cli_error_value(str(cfg_file['delete']))}, expected " + f"{cli_correct_value('a Boolean')}.", + ) + if "monotonic" in cfg_file and not isinstance(cfg_file["monotonic"], bool): + _add_validation_error( + errors, + f"variable {cli_error_value('monotonic')} has invalid value " + f"{cli_error_value(str(cfg_file['monotonic']))}, expected " + f"{cli_correct_value('a Boolean')}.", + ) + if "min_realizations_success" in cfg_file: + _validate_number( + cfg_file, "min_realizations_success", errors, minimum=0, integer=True + ) + present_history = sorted(history_keys & cfg_file.keys()) + if workflow in {"ert", "everest"} and not present_history: + _add_validation_error( + errors, + f"missing {cli_error_value('history-matching variables')}, expected " + f"{cli_correct_value('at least one facies or thickness parameter')}.", + ) + for key in present_history: + _validate_history_parameter(cfg_file, key, workflow, errors) + if workflow == "ert": + _validate_string(cfg_file, "ertargs", errors) + _validate_number( + cfg_file, "ensembles", errors, minimum=0, strict=True, integer=True + ) + _validate_number(cfg_file, "enkf_alpha", errors, minimum=0) + _validate_array( + cfg_file, "errors", errors, minimum=0, strict=True, nonempty=True + ) + if "random_seed" in cfg_file: + _validate_number(cfg_file, "random_seed", errors, minimum=0, integer=True) + elif workflow == "everest": + _validate_number( + cfg_file, + "max_function_evaluations", + errors, + minimum=0, + strict=True, + integer=True, + ) + _validate_number( + cfg_file, "popsize", errors, minimum=0, strict=True, integer=True + ) + if "max_batch_num" in cfg_file: + _validate_number( + cfg_file, + "max_batch_num", + errors, + minimum=0, + strict=True, + integer=True, + ) + if "rng" in cfg_file: + _validate_number(cfg_file, "rng", errors, minimum=0, integer=True) + for key in ("strategy", "init", "updating"): + if key in cfg_file: + _validate_string(cfg_file, key, errors) + if "maxiter" in cfg_file: + _validate_number( + cfg_file, "maxiter", errors, minimum=0, strict=True, integer=True + ) + if "workers" in cfg_file: + _validate_number(cfg_file, "workers", errors, integer=True) + for key in ("tol", "atol"): + if key in cfg_file: + _validate_number(cfg_file, key, errors, minimum=0) + if "recombination" in cfg_file: + _validate_number(cfg_file, "recombination", errors, minimum=0, maximum=1) + for key in ("disp", "polish", "vectorized"): + if key in cfg_file and not isinstance(cfg_file[key], bool): + _add_validation_error( + errors, + f"variable {cli_error_value(key)} has invalid value " + f"{cli_error_value(str(cfg_file[key]))}, expected " + f"{cli_correct_value('a Boolean')}.", + ) + if "mutation" in cfg_file: + mutation = cfg_file["mutation"] + if _is_finite_number(mutation): + if mutation < 0: + _add_validation_error( + errors, + f"variable {cli_error_value('mutation')} has invalid value " + f"{cli_error_value(str(mutation))}, expected " + f"{cli_correct_value('a non-negative value')}.", + ) + elif isinstance(mutation, list) and len(mutation) == 2: + if not all(_is_finite_number(value) for value in mutation): + _add_validation_error( + errors, + f"variable {cli_error_value('mutation')} has invalid value " + f"{cli_error_value(str(mutation))}, expected " + f"{cli_correct_value('two finite numbers')}.", + ) + elif mutation[0] < 0 or mutation[0] >= mutation[1]: + _add_validation_error( + errors, + f"variable {cli_error_value('mutation')} has invalid range " + f"{cli_error_value(str(mutation))}, expected non-negative " + f"increasing {cli_correct_value('mutation bounds')}.", + ) + else: + _add_validation_error( + errors, + f"variable {cli_error_value('mutation')} has invalid value " + f"{cli_error_value(str(mutation))}, expected a finite number " + f"or {cli_correct_value('an array of two finite numbers')}.", + ) + unsupported = {"callback", "constraints"} & cfg_file.keys() + for key in sorted(unsupported): + _add_validation_error( + errors, + f"variable {cli_error_value(key)} cannot be configured through " + f"{cli_correct_value('TOML')}.", + ) + if cfg_file.get("monotonic") is True and workflow in {"ert", "everest"}: + groups = [] + for name in FACIES_KEYS - {"thre", "npnt"}: + groups.append( + [ + f"{name}{index}" + for index in range(1, 8) + if f"{name}{index}" in cfg_file + ] + ) + if not any(len(group) >= 2 for group in groups): + pofff_warning( + f"variable {cli_warning_value('monotonic')} is enabled, but the " + "selected history-matching variables do not define a comparable " + "facies sequence. The monotonicity constraint may have no effect." + ) + if errors: + details = "\n".join(f" - {error}" for error in errors) + pofff_error(f"invalid TOML configuration:\n{details}") + return cfg_file diff --git a/src/pofff/utils/mapproperties.py b/src/pofff/utils/mapproperties.py index a9681ef..06ecf9a 100644 --- a/src/pofff/utils/mapproperties.py +++ b/src/pofff/utils/mapproperties.py @@ -2,14 +2,19 @@ # SPDX-License-Identifier: GPL-3.0 # pylint: disable=R0913, R0914, R0917, E1102 -"""Utility functions for grid generation and spatial indexing -in geological FluidFlower-style models.""" +"""Generate FluidFlower grids and assign spatial model properties. + +The module builds Cartesian, tensor, and corner-point grids; reads geological +points, horizons, and facies polygons; applies thickness maps; locates sensors +and injection sources; classifies reporting boxes; and writes mappings from the +simulation grid to the benchmark reporting grid.""" import csv import shutil import sys from contextlib import nullcontext from pathlib import Path +from typing import Any import numpy as np from alive_progress import alive_bar @@ -21,42 +26,33 @@ def grid_and_properties(cfg: PofffConfig) -> None: - """Dispatch grid generation and spatial property assignment - based on the selected grid type.""" - polygons, points = getpolygons(cfg) + """Generate the selected grid and assign spatial model properties. + + The function updates grid dimensions, generated OPM arrays, sensor and source + indices, and the benchmark cell map on ``cfg``. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" + polygons, points = _read_geological_polygons(cfg) if cfg.grid == "corner-point": - xyz, xcoord, zcoord = corner(cfg, points) - positions(cfg, polygons, xyz=xyz, xcoord=xcoord, zcoord=zcoord) + xyz, xcoord, zcoord = _build_corner_point_coordinates(cfg, points) + _assign_spatial_properties(cfg, polygons, xyz=xyz, xcoord=xcoord, zcoord=zcoord) return dims = np.asarray(cfg.dims, float) if cfg.grid == "cartesian": xvert = np.linspace(0, dims[0], cfg.nxz[0] + 1) zvert = np.linspace(0, dims[2], cfg.nxz[1] + 1) else: - - def tensor(parts, dim): - """Construct non-uniform grid edges from part sizes.""" - return np.concatenate( - [ - np.linspace( - i * dim / len(parts), - (i + 1) * dim / len(parts), - n + 1, - endpoint=True, - )[:-1] - for i, n in enumerate(parts) - ] - + [np.array([dim])] - ) - - xvert = tensor(cfg.x, dims[0]) - zvert = tensor(cfg.z, dims[2]) + xvert = _tensor(cfg.x, dims[0]) + zvert = _tensor(cfg.z, dims[2]) cfg.nxz = [len(xvert) - 1, len(zvert) - 1] xcent = 0.5 * (xvert[:-1] + xvert[1:]) zcent = 0.5 * (zvert[:-1] + zvert[1:]) cfg.dx = list(xvert[1:] - xvert[:-1]) cfg.dz = list(zvert[1:] - zvert[:-1]) - positions(cfg, polygons, xcent=xcent, zcent=zcent) + _assign_spatial_properties(cfg, polygons, xcent=xcent, zcent=zcent) if cfg.grid == "tensor": cfg.dx = list(map(str, xvert[1:] - xvert[:-1])) dz = list(map(str, zvert[1:] - zvert[:-1])) @@ -66,12 +62,57 @@ def tensor(parts, dim): cfg.dz.extend([dz[i + 1]] * cfg.nxz[0]) -def corner( +def _tensor(parts: list[int], dimension: float) -> NDArray[np.float64]: + """Construct tensor-grid edges from per-region refinement counts. + + Divide the physical dimension into equally sized regions, then subdivide + each region according to its corresponding refinement count. + + Parameters + ---------- + parts : list[int] + Positive numbers of cells assigned to consecutive equal-sized regions. + dimension : float + Total physical length of the grid axis, in metres. + + Returns + ------- + NDArray[np.float64] + Ordered grid-edge coordinates from zero through ``dimension``, in + metres. + """ + return np.concatenate( + [ + np.linspace( + index * dimension / len(parts), + (index + 1) * dimension / len(parts), + cells + 1, + endpoint=True, + )[:-1] + for index, cells in enumerate(parts) + ] + + [np.array([dimension], dtype=np.float64)] + ) + + +def _build_corner_point_coordinates( cfg: PofffConfig, points: list[list[float]], ) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]: - """Build corner-point grid coordinates and compute cell centroids.""" - horizonts = get_lines(cfg, points) + """Build refined corner-point coordinates and cell centroids. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + points : list[list[float]] + Geological points represented by x and z coordinates in metres. + + Returns + ------- + tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]] + Cell centroids followed by refined corner x and z coordinates.""" + horizonts = _read_horizon_lines(cfg, points) xvert = np.concatenate( [ np.linspace( @@ -110,7 +151,7 @@ def corner( cfg.nxz[0] = len(xcoord) // stride - 1 cfg.nxz[1] = stride - 1 - xcoord, zcoord, cfg.nxz[0], cfg.nxz[1] = refinement_z( + xcoord, zcoord, cfg.nxz[0], cfg.nxz[1] = _refine_vertical_coordinates( xcoord, zcoord, cfg.nxz[1], np.array(cfg.z) ) @@ -141,8 +182,18 @@ def corner( return xyz, xcoord, zcoord -def handle_thickness_map(cfg: PofffConfig) -> tuple[float, NDArray, NDArray, NDArray]: - """Load and normalize thickness map and corresponding multipliers.""" +def _load_thickness_map(cfg: PofffConfig) -> tuple[float, NDArray, NDArray, NDArray]: + """Load the selected FluidFlower thickness map and multipliers. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + + Returns + ------- + tuple[float, NDArray, NDArray, NDArray] + Minimum thickness, map x and z coordinates, and normalized multipliers.""" geology = cfg.path / "geology" if cfg.thickness == "final": @@ -167,13 +218,24 @@ def handle_thickness_map(cfg: PofffConfig) -> tuple[float, NDArray, NDArray, NDA ) -def structured_handling_fluidflower( +def _assign_structured_grid_properties( cfg: PofffConfig, xcent: NDArray[np.float64], zcent: NDArray[np.float64], polygons: list[Polygon], ) -> None: - """Assign facies, boxes, sensors, and wells on structured grids.""" + """Assign properties and feature indices on a structured grid. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + xcent : NDArray[np.float64] + Cell-centre x coordinates in metres. + zcent : NDArray[np.float64] + Cell-centre z coordinates in metres. + polygons : list[Polygon] + FluidFlower facies polygons in model coordinates.""" nx, nz = cfg.nxz x = np.tile(xcent, nz) z = np.repeat(zcent, nx) @@ -182,7 +244,7 @@ def structured_handling_fluidflower( flux = np.full(len(x), -1) if cfg.thickness in {"initial", "final"}: - cfg.dims[1], xth, zth, mult = handle_thickness_map(cfg) + cfg.dims[1], xth, zth, mult = _load_thickness_map(cfg) else: cfg.dims[1] = float(cfg.thickness) show_progress = sys.stdout.isatty() @@ -205,32 +267,45 @@ def structured_handling_fluidflower( cfg.multpv.append(str(mult[idx])) cfg.fluxnum = flux.astype(str).tolist() - cfg.fipnum = classify_boxes(cfg, x, ztop, cfg.fluxnum) + cfg.fipnum = _classify_reporting_boxes(cfg, x, ztop, cfg.fluxnum) - s1, s2 = sensors_structured(cfg, xcent, zcent) + s1, s2 = _locate_structured_sensors(cfg, xcent, zcent) cfg.fipnum[s1], cfg.fipnum[s2] = "8", "9" - wells_structured(cfg, xcent, zcent) + _locate_structured_sources(cfg, xcent, zcent) if cfg.hascellmaps: - get_cellmaps(cfg, x.tolist(), ztop.tolist()) + _write_cell_map(cfg, x.tolist(), ztop.tolist()) else: shutil.copy(cfg.path / "geology/cellmap.npy", cfg.deck) -def corner_point_handling_fluidflower( +def _assign_corner_point_properties( cfg: PofffConfig, xyz: NDArray[np.float64], polygons: list[Polygon], xcoord: NDArray[np.float64], zcoord: NDArray[np.float64], ) -> None: - """Assign facies, boxes, sensors, and wells for corner-point grids.""" + """Assign properties and feature indices on a corner-point grid. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + xyz : NDArray[np.float64] + Cell-centroid coordinates with columns x, y, and z. + polygons : list[Polygon] + FluidFlower facies polygons in model coordinates. + xcoord : NDArray[np.float64] + Corner-point x coordinates arranged by pillar and vertical interface. + zcoord : NDArray[np.float64] + Corner-point z coordinates arranged by pillar and vertical interface.""" x = xyz[:, 0] ztop = cfg.dims[2] - xyz[:, 2] if cfg.thickness in {"initial", "final"}: - cfg.dims[1], xth, zth, mult = handle_thickness_map(cfg) + cfg.dims[1], xth, zth, mult = _load_thickness_map(cfg) else: cfg.dims[1] = float(cfg.thickness) @@ -255,22 +330,22 @@ def corner_point_handling_fluidflower( cfg.multpv.append(str(mult[idx])) cfg.fluxnum = flux.astype(str).tolist() - cfg.fipnum = classify_boxes(cfg, x, ztop, cfg.fluxnum) + cfg.fipnum = _classify_reporting_boxes(cfg, x, ztop, cfg.fluxnum) - s1, s2 = sensors_corner_point(cfg, x, ztop) + s1, s2 = _locate_corner_point_sensors(cfg, x, ztop) cfg.fipnum[s1], cfg.fipnum[s2] = "8", "9" - wells_corner_point(cfg, x, ztop) + _locate_corner_point_sources(cfg, x, ztop) if cfg.hascellmaps: - get_cellmaps(cfg, x.tolist(), ztop.tolist()) + _write_cell_map(cfg, x.tolist(), ztop.tolist()) else: shutil.copy(cfg.path / "geology/cellmap.npy", cfg.deck) create_corner_point_grid(cfg, xcoord, zcoord) -def positions( +def _assign_spatial_properties( cfg: PofffConfig, polygons: list[Polygon], xcent: NDArray[np.float64] | None = None, @@ -279,22 +354,53 @@ def positions( xcoord: NDArray[np.float64] | None = None, zcoord: NDArray[np.float64] | None = None, ) -> None: - """Dispatch spatial indexing for either grid type.""" + """Dispatch property assignment for the selected grid representation. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + polygons : list[Polygon] + FluidFlower facies polygons in model coordinates. + xcent : NDArray[np.float64] | None, optional + Cell-centre x coordinates in metres. + zcent : NDArray[np.float64] | None, optional + Cell-centre z coordinates in metres. + xyz : NDArray[np.float64] | None, optional + Cell-centroid coordinates with columns x, y, and z. + xcoord : NDArray[np.float64] | None, optional + Corner-point x coordinates arranged by pillar and vertical interface. + zcoord : NDArray[np.float64] | None, optional + Corner-point z coordinates arranged by pillar and vertical interface.""" if cfg.grid == "corner-point": assert xyz is not None assert xcoord is not None assert zcoord is not None - corner_point_handling_fluidflower(cfg, xyz, polygons, xcoord, zcoord) + _assign_corner_point_properties(cfg, xyz, polygons, xcoord, zcoord) else: assert xcent is not None assert zcent is not None - structured_handling_fluidflower(cfg, xcent, zcent, polygons) + _assign_structured_grid_properties(cfg, xcent, zcent, polygons) -def sensors_structured( +def _locate_structured_sensors( cfg: PofffConfig, xcent: NDArray[np.float64], zcent: NDArray[np.float64] ) -> tuple[int, int]: - """Determine structured-grid sensor indices.""" + """Locate both observation sensors on a structured grid. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + xcent : NDArray[np.float64] + Cell-centre x coordinates in metres. + zcent : NDArray[np.float64] + Cell-centre z coordinates in metres. + + Returns + ------- + tuple[int, int] + Global zero-based indices of the two sensor cells.""" sx1, sz1 = cfg.sensors[0] sx2, sz2 = cfg.sensors[1] dimz = cfg.dims[2] @@ -317,10 +423,19 @@ def sensors_structured( return i1, i2 -def wells_structured( +def _locate_structured_sources( cfg: PofffConfig, xcent: NDArray[np.float64], zcent: NDArray[np.float64] ) -> None: - """Determine structured-grid well indices.""" + """Locate both injection sources on a structured grid. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + xcent : NDArray[np.float64] + Cell-centre x coordinates in metres. + zcent : NDArray[np.float64] + Cell-centre z coordinates in metres.""" for i, w in enumerate(cfg.sources): cfg.source_ik[i] = [ int(np.argmin(np.abs(xcent - w[0]))) + 1, @@ -328,10 +443,24 @@ def wells_structured( ] -def sensors_corner_point( +def _locate_corner_point_sensors( cfg: PofffConfig, x: NDArray[np.float64], ztop: NDArray[np.float64] ) -> tuple[int, int]: - """Determine corner-point sensor indices.""" + """Locate both observation sensors on a corner-point grid. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + x : NDArray[np.float64] + Cell or plotting x coordinates in metres. + ztop : NDArray[np.float64] + Cell-centre depth coordinates measured from the model top. + + Returns + ------- + tuple[int, int] + Global zero-based indices of the two sensor cells.""" coords = np.column_stack((x, ztop)) sensors = np.array(cfg.sensors) d = ((coords[:, None] - sensors) ** 2).sum(axis=2) @@ -345,10 +474,19 @@ def sensors_corner_point( return s1, s2 -def wells_corner_point( +def _locate_corner_point_sources( cfg: PofffConfig, x: NDArray[np.float64], ztop: NDArray[np.float64] ) -> None: - """Determine corner-point well indices.""" + """Locate both injection sources on a corner-point grid. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + x : NDArray[np.float64] + Cell or plotting x coordinates in metres. + ztop : NDArray[np.float64] + Cell-centre depth coordinates measured from the model top.""" coords = np.column_stack((x, ztop)) wells = np.array(cfg.sources) d = ((coords[:, None] - wells) ** 2).sum(axis=2) @@ -361,34 +499,99 @@ def wells_corner_point( ] -def classify_boxes( +def _assign_box_region( + bounds: list[list[float]], + sand_region: str, + other_region: str, + x: NDArray[np.float64], + z: NDArray[np.float64], + fluxnum: NDArray[Any], + fipnum: NDArray[Any], +) -> None: + """Assign FIPNUM values inside one benchmark reporting box. + + Cells belonging to facies 1 receive ``sand_region``. All other cells + inside the box receive ``other_region``. The ``fipnum`` array is modified + in place. + + Parameters + ---------- + bounds : list[list[float]] + Lower-left and upper-right box corners as + ``[[xmin, zmin], [xmax, zmax]]`` in metres. + sand_region : str + FIPNUM assigned to cells in facies 1. + other_region : str + FIPNUM assigned to all other facies inside the box. + x : NDArray[np.float64] + Cell-centre x coordinates in global cell order, in metres. + z : NDArray[np.float64] + Cell-centre z coordinates in global cell order, in metres. + fluxnum : list[str] + Facies identifiers in global cell order. + fipnum : list[str] + FIPNUM values in global cell order, modified in place. + """ + inside = ( + (x >= bounds[0][0]) + & (x <= bounds[1][0]) + & (z >= bounds[0][1]) + & (z <= bounds[1][1]) + ) + unassigned = inside & (fipnum == "1") + fipnum[unassigned & (fluxnum == "1")] = sand_region + fipnum[unassigned & (fluxnum != "1")] = other_region + + +def _classify_reporting_boxes( cfg: PofffConfig, x: NDArray[np.float64], z: NDArray[np.float64], flux: list[str], ) -> list[str]: - """Assign box-specific FIP numbers based on spatial regions.""" + """Assign benchmark reporting-region FIPNUM values. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + x : NDArray[np.float64] + Cell or plotting x coordinates in metres. + z : NDArray[np.float64] + Cell or plotting z coordinates in metres. + flux : list[str] + Facies identifiers in global cell order. + + Returns + ------- + list[str] + FIPNUM values in global cell order.""" x, z, flux0 = map(np.asarray, (x, z, flux)) fip = np.full(len(x), "1", dtype=object) - def box(b, a, b2): - m = (x >= b[0][0]) & (x <= b[1][0]) & (z >= b[0][1]) & (z <= b[1][1]) - sel = m & (fip == "1") - fip[sel & (flux0 == "1")] = a - fip[sel & (flux0 != "1")] = b2 - - box(cfg.boxb, "6", "3") - box(cfg.boxc, "12", "4") - box(cfg.boxa, "5", "2") + _assign_box_region(cfg.boxb, "6", "3", x, z, flux0, fip) + _assign_box_region(cfg.boxc, "12", "4", x, z, flux0, fip) + _assign_box_region(cfg.boxa, "5", "2", x, z, flux0, fip) fip[(flux0 == "1") & (fip == "1")] = "7" return fip.tolist() -def get_cellmaps( +def _write_cell_map( cfg: PofffConfig, simxcent: NDArray[np.float64], simzcent: NDArray[np.float64] ) -> None: - """Construct mapping from structured grid to reference grid.""" + """Map benchmark reporting cells to their nearest simulation cells. + + The mapping is written as ``cellmap.npy`` below ``cfg.deck``. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + simxcent : NDArray[np.float64] + Simulation-cell x coordinates in global cell order. + simzcent : NDArray[np.float64] + Simulation-cell z coordinates in global cell order.""" refx = np.arange(0, 2.8 + 5.0e-3, 1.0e-2) refz = np.arange(0, 1.2 + 5.0e-3, 1.0e-2) refx = 0.5 * (refx[1:] + refx[:-1]) @@ -402,8 +605,22 @@ def get_cellmaps( np.save(Path(cfg.deck) / "cellmap.npy", np.argmin(d, axis=0)) -def get_lines(cfg: PofffConfig, points: list[list[float]]) -> list[list[list[float]]]: - """Read geological horizon lines.""" +def _read_horizon_lines( + cfg: PofffConfig, points: list[list[float]] +) -> list[list[list[float]]]: + """Read and order geological horizon polylines. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + points : list[list[float]] + Geological points represented by x and z coordinates in metres. + + Returns + ------- + list[list[list[float]]] + Geological horizons ordered from bottom to top.""" horizonts: list[list[list[float]]] = [] with open(cfg.path / "geology/horizonts.geo", encoding="utf8") as file: for r in csv.reader(file, delimiter=" "): @@ -416,8 +633,20 @@ def get_lines(cfg: PofffConfig, points: list[list[float]]) -> list[list[list[flo return horizonts[::-1] -def getpolygons(cfg: PofffConfig) -> tuple[list[Polygon], list[list[float]]]: - """Read geological polygons and facies definitions.""" +def _read_geological_polygons( + cfg: PofffConfig, +) -> tuple[list[Polygon], list[list[float]]]: + """Read geological points, line connectivity, and facies polygons. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + + Returns + ------- + tuple[list[Polygon], list[list[float]]] + Facies polygons and the geological point coordinates.""" polygons, points, lines, curves, facie = [], [], [], [], 0 h_ref, l_ref = 1.5 / 1490, 2.8 / 2594 @@ -457,13 +686,29 @@ def getpolygons(cfg: PofffConfig) -> tuple[list[Polygon], list[list[float]]]: return polygons, points -def refinement_z( +def _refine_vertical_coordinates( xci: NDArray[np.float64], zci: NDArray[np.float64], ncz: int, znr: NDArray[np.float64], ) -> tuple[NDArray[np.float64], NDArray[np.float64], int, int]: - """Refine grid vertically according to znr refinement factors.""" + """Subdivide geological layers by their vertical refinement factors. + + Parameters + ---------- + xci : NDArray[np.float64] + Unrefined corner-point x coordinates. + zci : NDArray[np.float64] + Unrefined corner-point z coordinates. + ncz : int + Number of unrefined vertical cells. + znr : NDArray[np.float64] + Vertical refinement factor for each geological layer. + + Returns + ------- + tuple[NDArray[np.float64], NDArray[np.float64], int, int] + Refined x and z coordinates and the new x and z cell counts.""" stride = ncz + 1 ncols = len(xci) // stride diff --git a/src/pofff/utils/runs.py b/src/pofff/utils/runs.py index b1793e8..3e05419 100644 --- a/src/pofff/utils/runs.py +++ b/src/pofff/utils/runs.py @@ -1,14 +1,18 @@ # SPDX-FileCopyrightText: 2025-2026 NORCE Research AS # SPDX-License-Identifier: GPL-3.0 -"""Utility functions for running simulations, processing data, -and generating benchmark plots.""" +"""Execute simulation, data, history-matching, and plotting workflows. + +The functions run OPM Flow and the maintained data scripts, launch ERT or +Everest, postprocess ensemble results, and coordinate benchmark maps, sparse +values, Wasserstein comparisons, and error tables.""" import subprocess from collections.abc import Sequence from pathlib import Path from pofff.config.config import PofffConfig +from pofff.utils.terminal import cli_correct_value, cli_error_value, pofff_error from pofff.visualization.benchmark import run_benchmark from pofff.visualization.error_table import run_error_table from pofff.visualization.everert import run_everert @@ -17,12 +21,32 @@ def _run(cmd: Sequence[str]) -> None: - """Execute a command and abort if it fails.""" + """Execute a command and abort if it fails. + + Parameters + ---------- + cmd : Sequence[str] + Command and arguments to execute without a shell. + + Raises + ------ + subprocess.CalledProcessError + If the command exits with a nonzero status.""" subprocess.run(cmd, check=True) def flow(cfg: PofffConfig) -> None: - """Run the OPM Flow simulator with configured options.""" + """Run the OPM Flow simulator with configured options. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + + Raises + ------ + subprocess.CalledProcessError + If OPM Flow exits with a nonzero status.""" _run( cfg.flow.split(" ") + [f"--output-dir={cfg.fol}", str(Path(cfg.fol) / f"{cfg.data}.DATA")] @@ -30,7 +54,17 @@ def flow(cfg: PofffConfig) -> None: def data(cfg: PofffConfig) -> None: - """Generate benchmark time-series and spatial data.""" + """Generate benchmark time-series and spatial data. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + + Raises + ------ + subprocess.CalledProcessError + If a data or metric script exits with a nonzero status.""" _run( [ "python", @@ -61,7 +95,12 @@ def data(cfg: PofffConfig) -> None: def benchmark(cfg: PofffConfig) -> None: - """Generate benchmark figures and comparisons.""" + """Generate benchmark figures and comparisons. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" args = [ "-e", cfg.experiment, @@ -102,10 +141,10 @@ def benchmark(cfg: PofffConfig) -> None: if cfg.figures == "all": if cfg.times != "24,48,72,96,120": - print( - "The error table requires a 120-hour simulation.\n" - "Please run with -t '24,48,72,96,120' " - f"(current: -t {cfg.times})." + pofff_error( + "the error table requires a 120-hour simulation, no " + f"{cli_error_value(f'-t {cfg.times}')} but " + f"{cli_correct_value('-t 24,48,72,96,120')}." ) else: args = [ @@ -124,17 +163,37 @@ def benchmark(cfg: PofffConfig) -> None: def everest() -> None: - """Run Everest optimization and skip interactive prompts.""" + """Run Everest optimization and skip interactive prompts. + + Raises + ------ + subprocess.CalledProcessError + If Everest exits with a nonzero status.""" _run(["everest", "run", "everest.yml", "--skip-prompt"]) def ert(cfg: PofffConfig) -> None: - """Run ERT with configured arguments.""" + """Run ERT with configured arguments. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + + Raises + ------ + subprocess.CalledProcessError + If ERT exits with a nonzero status.""" _run(["ert", *(cfg.ertargs or "").split(), "ert.txt"]) def postprocess(cfg: PofffConfig) -> None: - """Postprocess ERT and Everest simulation results.""" + """Postprocess ERT and Everest simulation results. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" args = [ "-e", str(cfg.path), diff --git a/src/pofff/utils/terminal.py b/src/pofff/utils/terminal.py new file mode 100644 index 0000000..2194ef2 --- /dev/null +++ b/src/pofff/utils/terminal.py @@ -0,0 +1,224 @@ +# SPDX-FileCopyrightText: 2026 NORCE Research AS +# SPDX-License-Identifier: GPL-3.0 + +"""Format command-line messages for pofff. + +ANSI colors are applied when supported by the selected stream. The module +provides consistent formatting for invalid, accepted, deprecated, and +informational values, plus fatal errors, warnings, tips, progress messages, and +generated-file reports.""" + +import os +import sys +from typing import NoReturn + +ANSI_BOLD_RED = "1;31" +ANSI_BOLD_YELLOW = "1;33" +ANSI_BOLD_GREEN = "1;32" +ANSI_BOLD_BLUE = "1;34" +ANSI_BOLD_MAGENTA = "1;35" +ANSI_YELLOW = "1;33" +ANSI_GREEN = "1;32" +ANSI_CYAN = "36" +ANSI_RED = "31" +ANSI_BLUE = "1;34" + + +def _supports_color(stream: object = sys.stderr) -> bool: + """Check whether an output stream supports ANSI colors. + + Parameters + ---------- + stream : object, optional + Output stream used to determine ANSI-color support. + + Returns + ------- + bool + Whether ANSI color output is enabled for the stream.""" + return ( + hasattr(stream, "isatty") + and stream.isatty() + and os.environ.get("NO_COLOR") is None + and os.environ.get("TERM") != "dumb" + ) + + +def _colorize( + text: str, + code: str, + stream: object = sys.stderr, +) -> str: + """Wrap text in an ANSI color sequence when supported. + + Parameters + ---------- + text : str + Text to format. + code : str + ANSI Select Graphic Rendition code. + stream : object, optional + Output stream used to determine ANSI-color support. + + Returns + ------- + str + Colored text, or unchanged text when colors are disabled.""" + if not _supports_color(stream): + return text + return f"\033[{code}m{text}\033[0m" + + +def cli_warning_value(value: str) -> str: + """Format a deprecated CLI option or value. + + Parameters + ---------- + value : str + Value to inspect or format. + + Returns + ------- + str + Quoted value colored as a warning when supported.""" + return _colorize(repr(value), ANSI_YELLOW) + + +def cli_correct_value(value: str) -> str: + """Format a correct CLI option or value. + + Parameters + ---------- + value : str + Value to inspect or format. + + Returns + ------- + str + Quoted value colored as a valid alternative when supported.""" + return _colorize(repr(value), ANSI_GREEN) + + +def cli_error_value(value: str) -> str: + """Format an invalid CLI option or value. + + Parameters + ---------- + value : str + Value to inspect or format. + + Returns + ------- + str + Quoted value colored as invalid when supported.""" + return _colorize(repr(value), ANSI_RED) + + +def cli_info_value(value: str) -> str: + """Format an informational CLI option or value. + + Parameters + ---------- + value : str + Value to inspect or format. + + Returns + ------- + str + Quoted value colored as information when supported.""" + return _colorize(repr(value), ANSI_BLUE) + + +def pofff_error(message: str) -> NoReturn: + """Raise a fatal command-line error. + + Parameters + ---------- + message : str + Human-readable message to display or append. + + Raises + ------ + SystemExit + Always raised with the formatted error message.""" + label = _colorize("error", ANSI_BOLD_RED) + raise SystemExit(f"{pofff_name()}: {label}: {message}") + + +def pofff_warning(message: str) -> None: + """Display a non-fatal command-line warning. + + Parameters + ---------- + message : str + Human-readable message to display or append.""" + label = _colorize("warning", ANSI_BOLD_YELLOW) + print(f"{pofff_name()}: {label}: {message}", file=sys.stderr) + + +def pofff_info(message: str) -> None: + """Display an informational command-line message. + + Parameters + ---------- + message : str + Human-readable message to display or append.""" + label = _colorize("info", ANSI_BOLD_BLUE, sys.stdout) + print(f"{pofff_name()}: {label}: {message}") + + +def pofff_tip(message: str) -> None: + """Display a command-line suggestion. + + Parameters + ---------- + message : str + Human-readable message to display or append.""" + label = _colorize("tip", ANSI_BOLD_MAGENTA, sys.stdout) + print(f"{pofff_name(sys.stdout)}: {label}: {message}") + + +def pofff_success(msg: str, output_dir: str, filenames: list[str]) -> None: + """Display the generated output location and filenames. + + Parameters + ---------- + msg : str + Optional success text printed before the output location. + output_dir : str + Directory containing the generated files. + filenames : list[str] + Generated filenames to report.""" + label = _colorize("success", ANSI_BOLD_GREEN, sys.stdout) + if not filenames: + print(f"{pofff_name()}: {label}: {msg}{output_dir}") + elif len(filenames) == 1: + print(f"{pofff_name()}: {label}: {msg}{output_dir}/{filenames[0]}") + elif len(filenames) <= 5: + print(f"{pofff_name()}: {label}{msg}") + print(f" Output directory: {output_dir}") + print(f" Files ({len(filenames)}): {', '.join(filenames)}") + else: + print(f"{pofff_name()}: {label}{msg}") + print(f" Output directory: {output_dir}") + print(f" Files ({len(filenames)}):") + for filename in filenames: + print(f" - {filename}") + + +def pofff_name(stream: object = sys.stderr) -> str: + """Format the pofff program name. + + Parameters + ---------- + stream : object, optional + Output stream used to determine ANSI-color support. + + Returns + ------- + str + Formatted program name.""" + characters = [("pofff", "1")] + return "".join( + _colorize(character, color, stream) for character, color in characters + ) diff --git a/src/pofff/utils/writefile.py b/src/pofff/utils/writefile.py index de3fc5f..eaa987a 100644 --- a/src/pofff/utils/writefile.py +++ b/src/pofff/utils/writefile.py @@ -2,7 +2,12 @@ # SPDX-License-Identifier: GPL-3.0 # pylint: disable=R0912, R0914 -"""Utility functions to write OPM/pofff files and variables.""" +"""Write OPM Flow, ERT, Everest, and generated job files. + +The module serializes grid and region keywords, renders the main OPM deck, and +creates helper scripts for boundary properties, saturation functions, regional +properties, Flow execution, copying, scaling, and monotonicity checks. It also +renders the ERT and Everest workflow configurations.""" import os import subprocess @@ -20,9 +25,18 @@ ) -def compact_format(values: list) -> list: - """Encode values using Eclipse-style n*value compaction. - Uses NumPy for efficient run-length encoding.""" +def _compact_eclipse_values(values: list) -> list: + """Encode repeated numeric values with Eclipse run-length syntax. + + Parameters + ---------- + values : list + Values to encode in their existing global order. + + Returns + ------- + list + Eclipse-formatted strings using ``n*value`` compaction.""" if len(values) == 0: return [] v = np.asarray(values, dtype=np.float64) @@ -52,8 +66,16 @@ def compact_format(values: list) -> list: def create_corner_point_grid( cfg: PofffConfig, xcoord: NDArray, zcoord: NDArray ) -> None: - """Write COORD and ZCORN sections for a corner-point grid. - Output is bit-identical to the original implementation.""" + """Write OPM COORD and ZCORN data for the corner-point grid. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state. + xcoord : NDArray + Corner-point x coordinates arranged by pillar and vertical interface. + zcoord : NDArray + Corner-point z coordinates arranged by pillar and vertical interface.""" nx, nz = cfg.nxz nk = nz + 1 @@ -64,7 +86,7 @@ def create_corner_point_grid( for i in range(nx + 1): base = i * nk tmp.append(f"{xcoord[base]:E} {y} 0 " f"{xcoord[base + nz]:E} {y:E} 0 ") - grid.extend(compact_format("".join(tmp).split())) + grid.extend(_compact_eclipse_values("".join(tmp).split())) grid.append("/\n") grid.append("ZCORN\n") tmp = [] @@ -80,7 +102,7 @@ def create_corner_point_grid( for i in range(nx): tmp.append(f"{zcoord[(i+1)*nk - 1]:E} {zcoord[(i+2)*nk - 1]:E} ") tmp.extend(tmp[-nx:]) - grid.extend(compact_format("".join(tmp).split())) + grid.extend(_compact_eclipse_values("".join(tmp).split())) grid.append("/") with open(f"{cfg.deck}/GRID.INC", "w", encoding="utf8") as f: f.write(HEADER) @@ -88,8 +110,13 @@ def create_corner_point_grid( f.write("\n") -def write_keywords(cfg: PofffConfig) -> None: - """Write OPM keyword include files (FIPNUM, FLUXNUM, DX, DZ, etc.).""" +def _write_keyword_files(cfg: PofffConfig) -> None: + """Write generated OPM property and geometry include files. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" keywords = ["fluxnum", "fipnum"] if cfg.grid == "tensor": keywords += ["dx", "dz"] @@ -97,7 +124,7 @@ def write_keywords(cfg: PofffConfig) -> None: keywords += ["multpv", "multx", "multx-", "multz", "multz-"] for name in keywords: values = getattr(cfg, "multpv" if name.startswith("mult") else name) - body = compact_format(values) + body = _compact_eclipse_values(values) with open(f"{cfg.deck}/{name.upper()}.INC", "w", encoding="utf8") as f: f.write(HEADER) f.write(f"{name.upper()}\n") @@ -106,8 +133,16 @@ def write_keywords(cfg: PofffConfig) -> None: def write_files(cfg: PofffConfig) -> None: - """Generate all OPM, ERT, and Everest files using mako templates.""" - write_keywords(cfg) + """Generate the OPM deck and optional ERT or Everest files. + + Generated files are written below ``cfg.deck``, ``cfg.jobs``, and ``cfg.fol`` + according to the selected workflow. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" + _write_keyword_files(cfg) tmpl = Template(filename=f"{cfg.path}/templates/deck.mako") mytemplate = tmpl.render( nxz=cfg.nxz, @@ -127,22 +162,22 @@ def write_files(cfg: PofffConfig) -> None: with open(f"{cfg.deck}/{cfg.data}.DATA", "w", encoding="utf8") as f: f.write(mytemplate) job_names = ["bcprop", "equalreg", "satufunc"] - render_bcprop(cfg) - render_equalreg(cfg) - render_satufunc(cfg) + _render_boundary_properties_script(cfg) + _render_region_properties_script(cfg) + _render_saturation_functions_script(cfg) if cfg.everert: - render_flow(cfg) - render_copyd(cfg) + _render_flow_script(cfg) + _render_copy_script(cfg) job_names += ["flow", "copyd"] if cfg.popsize: job_names += ["scale"] - render_scale(cfg) + _render_scale_script(cfg) with open(f"{cfg.jobs}/METRIC", "w", encoding="utf8") as f: f.write("EXECUTABLE metric.py") with open(f"{cfg.jobs}/DATA", "w", encoding="utf8") as f: f.write("EXECUTABLE data.py") - render_everest(cfg) + _render_everest_configuration(cfg) else: with open(f"{cfg.jobs}/METRIC", "w", encoding="utf8") as f: f.write("EXECUTABLE metric.py\n") @@ -153,9 +188,9 @@ def write_files(cfg: PofffConfig) -> None: with open(f"{cfg.jobs}/DATA", "w", encoding="utf8") as f: f.write("EXECUTABLE data.py\n") f.write(f"ARGLIST -t {cfg.times} -m {cfg.deck}/cellmap.npy") - render_ert(cfg) + _render_ert_configuration(cfg) if cfg.monotonic: - render_monotonic(cfg) + _render_monotonic_script(cfg) job_names += ["monotonic"] for name in job_names: script_path = f"{cfg.jobs}/{name}.py" @@ -180,8 +215,13 @@ def write_files(cfg: PofffConfig) -> None: f.write(content) -def render_monotonic(cfg: PofffConfig) -> None: - """Generate monotonic.py""" +def _render_monotonic_script(cfg: PofffConfig) -> None: + """Render the generated monotonicity-check job script. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" inc_names = ["swi", "sni", "pen", "nkrw", "nkrn", "npe"] dec_names = ["perm", "permx", "permz", "disperc"] @@ -214,8 +254,13 @@ def render_monotonic(cfg: PofffConfig) -> None: ) -def render_scale(cfg: PofffConfig) -> None: - """Generate scale.py""" +def _render_scale_script(cfg: PofffConfig) -> None: + """Render the generated Everest parameter-scaling script. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" parts = [] keys = list(cfg.hm.keys()) @@ -240,8 +285,13 @@ def render_scale(cfg: PofffConfig) -> None: ) -def render_bcprop(cfg: PofffConfig) -> None: - """Generate bcprop.py""" +def _render_boundary_properties_script(cfg: PofffConfig) -> None: + """Render the generated boundary-property job script. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" imports_block = "" if cfg.monotonic: @@ -318,8 +368,13 @@ def render_bcprop(cfg: PofffConfig) -> None: ) -def render_copyd(cfg: PofffConfig) -> None: - """Generate copyd.py""" +def _render_copy_script(cfg: PofffConfig) -> None: + """Render the generated deck-copy job script. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" monotonic_block = ( 'if os.path.exists("NOMONOTONIC"):\n' " sys.exit()\n" if cfg.monotonic @@ -336,8 +391,13 @@ def render_copyd(cfg: PofffConfig) -> None: ) -def render_flow(cfg: PofffConfig) -> None: - """Generate flow.py""" +def _render_flow_script(cfg: PofffConfig) -> None: + """Render the generated OPM Flow execution script. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" monotonic_block = ( 'if os.path.exists("NOMONOTONIC"):\n' " sys.exit()\n" if cfg.monotonic @@ -373,8 +433,13 @@ def render_flow(cfg: PofffConfig) -> None: ) -def render_equalreg(cfg: PofffConfig) -> None: - """Generate equalreg.py""" +def _render_region_properties_script(cfg: PofffConfig) -> None: + """Render the generated regional rock-property script. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" imports_block = "" if cfg.monotonic: @@ -462,8 +527,13 @@ def render_equalreg(cfg: PofffConfig) -> None: ) -def render_satufunc(cfg: PofffConfig) -> None: - """Generate satufunc.py""" +def _render_saturation_functions_script(cfg: PofffConfig) -> None: + """Render the generated saturation-function script. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" imports_block = "" if cfg.monotonic: imports_block += "import os\nimport sys\n" @@ -530,8 +600,13 @@ def render_satufunc(cfg: PofffConfig) -> None: ) -def render_ert(cfg: PofffConfig) -> None: - """Generate ert.txt""" +def _render_ert_configuration(cfg: PofffConfig) -> None: + """Render the ERT configuration and forward-model sequence. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" names = ["copyd", "equalreg", "satufunc", "bcprop", "flow"] lines = [] for name in names: @@ -560,8 +635,13 @@ def render_ert(cfg: PofffConfig) -> None: f.write(rendered) -def render_everest(cfg: PofffConfig) -> None: - """Generate everest.yml""" +def _render_everest_configuration(cfg: PofffConfig) -> None: + """Render the Everest optimization configuration. + + Parameters + ---------- + cfg : PofffConfig + Shared pofff configuration and derived runtime state.""" variables_block = "\n".join( [ f" - name: {p}\n" diff --git a/src/pofff/visualization/benchmark.py b/src/pofff/visualization/benchmark.py index b974d3f..c8495eb 100755 --- a/src/pofff/visualization/benchmark.py +++ b/src/pofff/visualization/benchmark.py @@ -2,21 +2,31 @@ # SPDX-FileCopyrightText: 2025-2026 NORCE Research AS # SPDX-License-Identifier: GPL-3.0 -"""Generate benchmark-format figures and comparisons.""" +"""Generate FluidFlower benchmark figures and comparisons. + +The module configures plotting and dispatches the maintained comparison scripts +for time series, sparse values, segmented Wasserstein distances, and mean +distance summaries.""" import argparse import shutil import subprocess -import sys from pathlib import Path import matplotlib import matplotlib.pyplot as plt +from pofff.utils.terminal import cli_correct_value, pofff_error, pofff_info + def run_benchmark(argv=None): - """Main entry point for generating benchmark figures.""" - cfg = load_parser(argv) + """Configure and generate benchmark comparison figures. + + Parameters + ---------- + argv : object, optional + Arguments to parse instead of the process command line.""" + cfg = _parse_arguments(argv) cfg["s"] = cfg["minimumsaturation"] cfg["c"] = cfg["minimumconcentration"] @@ -39,16 +49,27 @@ def run_benchmark(argv=None): } ) - benchmark(cfg) + _generate_benchmark_comparisons(cfg) + +def run(cmd): + """Execute external command with logging. -def benchmark(cfg): - """Run figure generation and benchmark comparisons.""" + Parameters + ---------- + cmd : object + Command and arguments to execute without a shell.""" + pofff_info(f"running {' '.join(map(str, cmd))}") + subprocess.run(cmd, check=True) - def run(cmd): - """Execute external command with logging.""" - print("Running:", " ".join(map(str, cmd))) - subprocess.run(cmd, check=True) + +def _generate_benchmark_comparisons(cfg): + """Run the maintained benchmark comparison scripts. + + Parameters + ---------- + cfg : object + Shared pofff configuration and derived runtime state.""" run( [ @@ -79,11 +100,10 @@ def run(cmd): if cfg["f"] == "all": if cfg["times"] != "24,48,72,96,120": - print( - "ERROR: Wasserstein distance figures require times " - "'24,48,72,96,120'." + pofff_error( + "Wasserstein distance figures require " + f"{cli_correct_value('-t 24,48,72,96,120')}." ) - sys.exit(1) if float(cfg["s"]) == 0.01 and float(cfg["c"]) in (0.05, 0.1) and cfg["u"]: run( @@ -135,11 +155,16 @@ def run(cmd): ) -def load_parser(argv): - """Define and parse command-line arguments.""" +def _parse_arguments(argv): + """Define and parse command-line arguments. + + Parameters + ---------- + argv : object + Arguments to parse instead of the process command line.""" parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, - description="Manage the generation of benchmark figures.", + description="Manage the generation of _generate_benchmark_comparisons figures.", ) parser.add_argument("-c", "--minimumconcentration", default=0.1) @@ -156,7 +181,7 @@ def load_parser(argv): help="Use precomputed Wasserstein distances (1=yes, 0=no)", ) - return vars(parser.parse_known_args(argv)[0]) + return vars(parser.parse_args(argv)) if __name__ == "__main__": diff --git a/src/pofff/visualization/error_table.py b/src/pofff/visualization/error_table.py index a7b436c..8d88e09 100755 --- a/src/pofff/visualization/error_table.py +++ b/src/pofff/visualization/error_table.py @@ -3,14 +3,19 @@ # https://github.com/fluidflower/general/blob/main/evaluation/compare_sparse_data.py # pylint: disable=R0914,R0915,C0103 -"""Quantify errors between simulation results and experimental data -and summarize them in a comparison table.""" +"""Compare simulation and experimental FluidFlower benchmark metrics. + +The module loads sparse quantities and segmented Wasserstein distances, computes +relative errors for participating groups, ranks the reported metrics, and writes +a formatted comparison table for the selected segmentation thresholds.""" import argparse import os import numpy as np +from pofff.utils.terminal import pofff_info + NUM_EXPERIMENTS = 5 # Number of experimental realizations NUM_MEASURABLES = 13 # Number of reported sparse quantities SCALING = 1e3 # Unit scaling for mass values @@ -18,8 +23,13 @@ LANL_INDEX = 5 # Index of LANL (excluded in distance stats) -def parse_arguments(argv): - """Parse command-line arguments controlling paths and thresholds.""" +def _parse_arguments(argv): + """Parse command-line arguments controlling paths and thresholds. + + Parameters + ---------- + argv : object + Arguments to parse instead of the process command line.""" parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, description="This script compares the data " @@ -52,11 +62,16 @@ def parse_arguments(argv): "-a", "--add", default="1", help="Whether to add the local result to plots." ) - return vars(parser.parse_known_args(argv)[0]) + return vars(parser.parse_args(argv)) -def load_sparse_csv(filename): - """Load a sparse CSV file, automatically handling optional headers.""" +def _load_sparse_csv(filename): + """Load a sparse CSV file, automatically handling optional headers. + + Parameters + ---------- + filename : object + Path to the input file.""" skip_header = 0 with open(filename, "r", encoding="utf8") as f: # Detect header line by first character @@ -72,9 +87,13 @@ def load_sparse_csv(filename): def run_error_table(argv=None): - """Compute error metrics and Wasserstein distances - and write a formatted comparison table to disk.""" - cmdargs = parse_arguments(argv) + """Compute and write the benchmark error comparison table. + + Parameters + ---------- + argv : object, optional + Arguments to parse instead of the process command line.""" + cmdargs = _parse_arguments(argv) path = cmdargs["path"] + "/fluidflower/" @@ -121,8 +140,8 @@ def run_error_table(argv=None): stddevs = np.zeros((NUM_MEASURABLES, numGroups)) for i, fileName in zip(range(numGroups), fileNames): - print(f"Processing {fileName}.") - data = load_sparse_csv(fileName) + pofff_info(f"processing {fileName}.") + data = _load_sparse_csv(fileName) means[:, i] = data[:, 2] stddevs[:, i] = data[:, 5] diff --git a/src/pofff/visualization/everert.py b/src/pofff/visualization/everert.py index 914ee21..793fa67 100755 --- a/src/pofff/visualization/everert.py +++ b/src/pofff/visualization/everert.py @@ -2,10 +2,12 @@ # SPDX-FileCopyrightText: 2025-2026 NORCE Research AS # SPDX-License-Identifier: GPL-3.0 -"""Postprocess Everest (ERT) ensemble and optimization studies. -Generates diagnostics, plots, and extracts best simulations.""" +"""Postprocess ERT ensembles and Everest optimization studies. -from __future__ import annotations +The module reads realizations and optimization batches, computes diagnostics, +plots simulation and parameter distributions, reports failures and +monotonicity rejections, and reconstructs the best simulation for benchmark +postprocessing.""" import argparse import csv @@ -21,24 +23,98 @@ import numpy as np from matplotlib.ticker import MaxNLocator +from pofff.utils.terminal import pofff_info -@dataclass + +@dataclass(slots=True) class Config: - """Runtime configuration and paths.""" + """Store options and paths for ERT and Everest postprocessing. + + The object is populated from command-line arguments and shared by ensemble + diagnostics, optimization processing, best-simulation extraction, and + benchmark-data generation. + + Attributes + ---------- + path + Root directory containing the ERT or Everest results and generated + figures. + times + Comma-separated benchmark evaluation times in hours. + jobs + Generated job scripts to execute when reconstructing the best + simulation. + external + Root directory containing the external FluidFlower benchmark data. + run_command + Experimental realization identifier, such as ``run2``. + maps + Path to the NumPy cell map used to generate benchmark spatial data. + min_saturation + Minimum gas-saturation threshold used to segment simulated CO2. + min_concentration + Minimum dissolved-CO2 concentration threshold used to segment simulated + CO2. + """ path: Path times: str jobs: list[str] external: Path - run: str + run_command: str maps: Path min_saturation: float min_concentration: float -@dataclass +@dataclass(slots=True) class EnsembleState: - """Holds ensemble simulations and diagnostics.""" + """Store ERT ensemble results and derived diagnostics. + + The object is initialized from the ERT output structure. Successful + realizations then populate its simulation values, misfits, parameter + distributions, realization identifiers, and per-observation diagnostics + for each iteration. + + Attributes + ---------- + observations + Observation values and uncertainties loaded from ``deck/obs.txt``. + A one-dimensional array represents one observation; otherwise, rows + contain the observed value and its uncertainty. + n_e + Number of ensemble realizations found in the simulation output. + n_i + Number of ERT iterations represented in the simulation output. + no_obs + Number of observations used to calculate the ensemble misfit. + no_para + Number of history-matching parameters found in ``parameters.txt``. + simulations + Simulated observable values grouped by iteration and realization. + sim_ens + Sum of the simulated observable values for each successful realization, + grouped by iteration. + miss_ens + Normalized least-squares misfit for each successful realization, + grouped by iteration. + par_dis + History-matching parameter values grouped by iteration. Values for all + parameters and realizations are stored in file order. + idrealisation + Original realization identifiers for successful simulations, grouped + by iteration. + num_ens + Number of successful realizations in each iteration. + cumulative + Simulated values grouped by iteration, observation, and successful + realization for cumulative-misfit plots. + para_file + Path to the most recently discovered ``parameters.txt`` file, or + ``None`` when parameter values are unavailable. + para_names + History-matching parameter names read from ``para_file``. + """ observations: np.ndarray n_e: int @@ -56,9 +132,34 @@ class EnsembleState: para_names: list[str] = field(default_factory=list) -@dataclass +@dataclass(slots=True) class OptimizationState: - """Tracks optimization progress and outcomes.""" + """Store Everest optimization progress and the best evaluation. + + The object is populated while processing optimization batches. It tracks + the best objective value reached after each batch, counts successful and + unsuccessful evaluations, and identifies the evaluation from which the + optimal simulation should be reconstructed. + + Attributes + ---------- + optimization + Best objective value reached after each optimization batch. + optimal_value + Best objective value found across all processed batches. + ind_batch + Zero-based index of the batch containing the best evaluation. + ind_sim + Zero-based evaluation index of the best solution within ``ind_batch``. + tot_eval + Total number of processed optimization evaluations. + s + Evaluation counts grouped by status and batch. Rows contain successful, + failed, and nonmonotonic evaluation counts, respectively. + x + Temporary counters for successful, failed, and nonmonotonic evaluations + in the batch currently being processed. + """ optimization: list[float] = field(default_factory=list) optimal_value: float = -np.inf @@ -69,25 +170,56 @@ class OptimizationState: x: list[int] = field(default_factory=lambda: [0, 0, 0]) -def run(cmd: list[str]) -> None: - """Execute external command and abort on failure.""" +def _run_command(cmd: list[str]) -> None: + """Execute external command and abort on failure. + + Parameters + ---------- + cmd : list[str] + Command and arguments to execute without a shell. + + Raises + ------ + subprocess.CalledProcessError + If the command exits with a nonzero status.""" subprocess.run(cmd, check=True) -def ensure_dir(path: Path): - """Create directory if missing.""" +def _ensure_directory(path: Path): + """Create directory if missing. + + Parameters + ---------- + path : Path + Input, output, or project path.""" path.mkdir(parents=True, exist_ok=True) -def save_figure(fig, path: Path, dpi=300): - """Save figure and release memory.""" +def _save_figure(fig, path: Path, dpi=300): + """Save figure and release memory. + + Parameters + ---------- + fig : object + Matplotlib figure to save and close. + path : Path + Input, output, or project path. + dpi : object, optional + Output resolution in dots per inch.""" fig.savefig(path, bbox_inches="tight", dpi=dpi) plt.close(fig) -def copy_tree_contents(src: Path, dst: Path): - """Copy contents of src into dst (cp -r src/. dst/).""" - ensure_dir(dst) +def _copy_tree_contents(src: Path, dst: Path): + """Copy contents of src into dst (cp -r src/. dst/). + + Parameters + ---------- + src : Path + Source file or directory. + dst : Path + Destination file or directory.""" + _ensure_directory(dst) for item in src.iterdir(): target = dst / item.name if item.is_dir(): @@ -96,8 +228,18 @@ def copy_tree_contents(src: Path, dst: Path): shutil.copy2(item, target) -def parse_args(argv) -> Config: - """Parse command-line arguments.""" +def _parse_arguments(argv) -> Config: + """Parse command-line arguments. + + Parameters + ---------- + argv : object + Arguments to parse instead of the process command line. + + Returns + ------- + Config + Parsed command-line arguments or the corresponding runtime configuration.""" parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, description="Visualization of optimization studies using Everest (ERT)", @@ -106,26 +248,26 @@ def parse_args(argv) -> Config: parser.add_argument("-t", "--times", default="24") parser.add_argument("-j", "--jobs", default="equalreg,bcprop,satufunc,flow") parser.add_argument("-e", "--external", default="/home/ThirdParty") - parser.add_argument("-r", "--run", default="run2") + parser.add_argument("-r", "--run_command", default="run2") parser.add_argument("-s", "--minimumsaturation", default="1e-2") parser.add_argument("-c", "--minimumconcentration", default="1e-1") parser.add_argument("-m", "--maps", default="cellmap.npy") - a = vars(parser.parse_known_args(argv)[0]) + a = vars(parser.parse_args(argv)) return Config( path=Path(a["path"]).resolve(), times=a["times"], jobs=[j.strip() for j in a["jobs"].split(",")], external=Path(a["external"]), - run=a["run"], + run_command=a["run_command"], maps=Path(a["maps"]), min_saturation=float(a["minimumsaturation"]), min_concentration=float(a["minimumconcentration"]), ) -def setup_matplotlib(): +def _configure_matplotlib(): """Apply consistent matplotlib styling.""" matplotlib.rc("font", family="monospace", size=14) plt.rcParams.update( @@ -138,8 +280,18 @@ def setup_matplotlib(): return matplotlib.colormaps["tab20"] -def initialize_ensemble(cfg: Config) -> EnsembleState: - """Initialize ensemble from simulation folders.""" +def _initialize_ensemble(cfg: Config) -> EnsembleState: + """Initialize ensemble from simulation folders. + + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + + Returns + ------- + EnsembleState + Initialized ensemble state sized from the result directories.""" obs = np.genfromtxt(cfg.path / "deck/obs.txt") no_obs = 1 if obs.ndim == 1 else len(obs) @@ -163,8 +315,19 @@ def initialize_ensemble(cfg: Config) -> EnsembleState: ) -def read_realisation(cfg: Config, state: EnsembleState, i: int, j: int): - """Read one realization and update ensemble statistics.""" +def _read_realisation(cfg: Config, state: EnsembleState, i: int, j: int): + """Read one realization and update ensemble statistics. + + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + state : EnsembleState + Mutable ensemble state populated during postprocessing. + i : int + Iteration or row index. + j : int + Realization or column index.""" base = cfg.path / f"output/simulations/realisation-{j}/iter-{i}" if not (base / "OK").exists(): return @@ -201,8 +364,17 @@ def read_realisation(cfg: Config, state: EnsembleState, i: int, j: int): state.par_dis[i].append(row[1]) -def plot_simulation_ensemble(cfg: Config, state: EnsembleState, tab20): - """Plot initial and final ensemble simulations.""" +def _plot_simulation_ensemble(cfg: Config, state: EnsembleState, tab20): + """Plot initial and final ensemble simulations. + + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + state : EnsembleState + Mutable ensemble state populated during postprocessing. + tab20 : object + Matplotlib categorical colormap used for consistent series colors.""" fig, ax = plt.subplots() x = range(1, state.no_obs + 1) @@ -254,11 +426,18 @@ def plot_simulation_ensemble(cfg: Config, state: EnsembleState, tab20): ax.set_xticks(x) ax.legend() - save_figure(fig, cfg.path / "figures/simulationensemble.png") + _save_figure(fig, cfg.path / "figures/simulationensemble.png") -def plot_misfit(cfg: Config, state: EnsembleState): - """Plot ensemble misfit per iteration.""" +def _plot_misfit(cfg: Config, state: EnsembleState): + """Plot ensemble misfit per iteration. + + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + state : EnsembleState + Mutable ensemble state populated during postprocessing.""" fig, ax = plt.subplots() ax.boxplot(state.miss_ens) ax.set_xlabel(r"Iteration [\#]") @@ -266,11 +445,18 @@ def plot_misfit(cfg: Config, state: EnsembleState): ax.set_title( r"$O_{i,j}=\frac{1}{2N_{obs}}\sum_n^{N_{obs}}((d^{n}_{i,j}-d^{n})/\sigma_n)^2$" ) - save_figure(fig, cfg.path / "figures/dist_mismatch.png") + _save_figure(fig, cfg.path / "figures/dist_mismatch.png") + +def _plot_parameter_distributions(cfg: Config, state: EnsembleState): + """Boxplots of parameter distributions. -def plot_parameter_distributions(cfg: Config, state: EnsembleState): - """Boxplots of parameter distributions.""" + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + state : EnsembleState + Mutable ensemble state populated during postprocessing.""" if not state.para_file: return @@ -295,11 +481,18 @@ def plot_parameter_distributions(cfg: Config, state: EnsembleState): ax.boxplot([ini_dist], tick_labels=["Initial"]) ax.set_title(f"Box plot of initial {state.para_names[k]} parameter") - save_figure(fig, cfg.path / "figures/parameterdistributions.png") + _save_figure(fig, cfg.path / "figures/parameterdistributions.png") + +def _extract_best_simulation(cfg: Config, state: EnsembleState): + """Extract best-fitting ensemble realization. -def extract_best_simulation(cfg: Config, state: EnsembleState): - """Extract best-fitting ensemble realization.""" + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + state : EnsembleState + Mutable ensemble state populated during postprocessing.""" sims = np.array(state.simulations[-1]) obs = state.observations @@ -313,15 +506,15 @@ def extract_best_simulation(cfg: Config, state: EnsembleState): src = cfg.path / f"output/simulations/realisation-{best_id}/iter-{state.n_i - 1}" dst = cfg.path / "figures/best_simulation" - copy_tree_contents(src, dst) + _copy_tree_contents(src, dst) old_cwd = Path.cwd() os.chdir(dst) try: - run(["python3", str(cfg.path / "jobs/copyd.py")]) + _run_command(["python3", str(cfg.path / "jobs/copyd.py")]) for job in cfg.jobs: - run(["python3", str(cfg.path / f"jobs/{job}.py")]) - run( + _run_command(["python3", str(cfg.path / f"jobs/{job}.py")]) + _run_command( [ "python3", str(cfg.path / "jobs/data.py"), @@ -331,14 +524,14 @@ def extract_best_simulation(cfg: Config, state: EnsembleState): str(cfg.maps), ] ) - run( + _run_command( [ "python3", str(cfg.path / "jobs/metric.py"), "-t", cfg.times, "-e", - cfg.run, + cfg.run_command, "-p", str(cfg.external), "-s", @@ -350,17 +543,27 @@ def extract_best_simulation(cfg: Config, state: EnsembleState): finally: os.chdir(old_cwd) - print( - f"Best: {cfg.path}/output/simulations/" + pofff_info( + f"best: {cfg.path}/output/simulations/" f"realisation-{best_id}/iter-{state.n_i - 1}" ) best_vals = state.simulations[-1][idx] - print(f"Values: {list(best_vals) if state.n_i > 1 else best_vals}") + pofff_info(f"values: {list(best_vals) if state.n_i > 1 else best_vals}") -def process_optimization(cfg: Config) -> OptimizationState: - """Process Everest optimization results.""" +def _process_optimization(cfg: Config) -> OptimizationState: + """Process Everest optimization results. + + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + + Returns + ------- + OptimizationState + Optimization history and location of the best evaluation.""" root = cfg.path / "everest_output/sim_output" opt = OptimizationState() @@ -408,8 +611,15 @@ def process_optimization(cfg: Config) -> OptimizationState: return opt -def plot_optimization(cfg: Config, opt: OptimizationState): - """Plot optimization progress.""" +def _plot_optimization(cfg: Config, opt: OptimizationState): + """Plot optimization progress. + + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + opt : OptimizationState + Processed Everest optimization state.""" fig, ax = plt.subplots() scale = 8.5 * 100 @@ -430,13 +640,20 @@ def plot_optimization(cfg: Config, opt: OptimizationState): else: ax.set_xticks(np.linspace(0, len(values) + 1, 11)) - save_figure(fig, cfg.path / "figures/optimization_results.png") + _save_figure(fig, cfg.path / "figures/optimization_results.png") -def extract_optimal_solution(cfg: Config, opt: OptimizationState): - """Extract and postprocess optimal optimization result.""" +def _extract_optimal_solution(cfg: Config, opt: OptimizationState): + """Extract and postprocess optimal optimization result. + + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + opt : OptimizationState + Processed Everest optimization state.""" dst = cfg.path / "figures/best_simulation" - ensure_dir(dst) + _ensure_directory(dst) if (cfg.path / "everest_output/sim_output/batch_0/realization-0").is_dir(): realization = "realization-0" @@ -457,10 +674,10 @@ def extract_optimal_solution(cfg: Config, opt: OptimizationState): old_cwd = Path.cwd() os.chdir(dst) try: - run(["python3", str(cfg.path / "jobs/copyd.py")]) + _run_command(["python3", str(cfg.path / "jobs/copyd.py")]) for job in cfg.jobs: - run(["python3", str(cfg.path / f"jobs/{job}.py")]) - run( + _run_command(["python3", str(cfg.path / f"jobs/{job}.py")]) + _run_command( [ "python3", str(cfg.path / "jobs/data.py"), @@ -470,14 +687,14 @@ def extract_optimal_solution(cfg: Config, opt: OptimizationState): str(cfg.maps), ] ) - run( + _run_command( [ "python3", str(cfg.path / "jobs/metric.py"), "-t", cfg.times, "-e", - cfg.run, + cfg.run_command, "-p", str(cfg.external), "-s", @@ -486,7 +703,7 @@ def extract_optimal_solution(cfg: Config, opt: OptimizationState): str(cfg.min_concentration), ] ) - run(["python3", str(cfg.path / "jobs/scale.py")]) + _run_command(["python3", str(cfg.path / "jobs/scale.py")]) finally: os.chdir(old_cwd) @@ -495,15 +712,22 @@ def extract_optimal_solution(cfg: Config, opt: OptimizationState): else: realization = "realization_0" - print( - f"Best: {cfg.path}/everest_output/sim_output/" + pofff_info( + f"best: {cfg.path}/everest_output/sim_output/" f"batch_{opt.ind_batch}/{realization}/" f"evaluation_{opt.ind_sim}" ) -def plot_observable_distribution(cfg: Config, state: EnsembleState): - """Plot observable sum distributions.""" +def _plot_observable_distribution(cfg: Config, state: EnsembleState): + """Plot observable sum distributions. + + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + state : EnsembleState + Mutable ensemble state populated during postprocessing.""" fig, ax = plt.subplots() ax.boxplot( [state.sim_ens[i] for i in range(state.n_i)], @@ -514,11 +738,18 @@ def plot_observable_distribution(cfg: Config, state: EnsembleState): ax.set_title(r"$\sum_n^{N_{obs}} d^n_{i,j}$") ax.set_xticks(range(state.n_i)) - save_figure(fig, cfg.path / "figures/dist_observable.png") + _save_figure(fig, cfg.path / "figures/dist_observable.png") + +def _plot_history_matching_mismatch(cfg: Config, state: EnsembleState): + """Plot ensemble-mean misfit per iteration. -def plot_hm_mismatch(cfg: Config, state: EnsembleState): - """Plot ensemble-mean misfit per iteration.""" + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + state : EnsembleState + Mutable ensemble state populated during postprocessing.""" fig, ax = plt.subplots() for i in range(state.n_i): @@ -536,11 +767,20 @@ def plot_hm_mismatch(cfg: Config, state: EnsembleState): ax.set_xticks(range(state.n_i)) ax.legend() - save_figure(fig, cfg.path / "figures/hm_mismatch.png") + _save_figure(fig, cfg.path / "figures/hm_mismatch.png") -def plot_cumulative_misfit(cfg: Config, state: EnsembleState, tab20): - """Plot cumulative misfit contributions per observation.""" +def _plot_cumulative_misfit(cfg: Config, state: EnsembleState, tab20): + """Plot cumulative misfit contributions per observation. + + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + state : EnsembleState + Mutable ensemble state populated during postprocessing. + tab20 : object + Matplotlib categorical colormap used for consistent series colors.""" for i in range(state.n_i): if not state.cumulative[i] or not state.cumulative[i][0]: continue @@ -580,11 +820,18 @@ def plot_cumulative_misfit(cfg: Config, state: EnsembleState, tab20): ax.set_ylabel("Cumulative misfit [-]") ax.legend() - save_figure(fig, cfg.path / f"figures/cumulative_misfit_ite-{i}.png") + _save_figure(fig, cfg.path / f"figures/cumulative_misfit_ite-{i}.png") + +def _plot_optimization_details(cfg: Config, opt: OptimizationState): + """Plot optimization success statistics. -def plot_optimization_details(cfg: Config, opt: OptimizationState): - """Plot optimization success statistics.""" + Parameters + ---------- + cfg : Config + Shared pofff configuration and derived runtime state. + opt : OptimizationState + Processed Everest optimization state.""" colors = ["g", "r", "k"] names = [ f"Succeeded (no={sum(opt.s[0])})", @@ -618,34 +865,39 @@ def plot_optimization_details(cfg: Config, opt: OptimizationState): ax.yaxis.set_major_locator(MaxNLocator(integer=True)) ax.legend() - save_figure(fig, cfg.path / "figures/details.png", dpi=900) + _save_figure(fig, cfg.path / "figures/details.png", dpi=900) def run_everert(argv=None): - """Entry point.""" - cfg = parse_args(argv) - tab20 = setup_matplotlib() + """Generate diagnostics for an ERT ensemble or Everest optimization. + + Parameters + ---------- + argv : object, optional + Arguments to parse instead of the process command line.""" + cfg = _parse_arguments(argv) + tab20 = _configure_matplotlib() - ensure_dir(cfg.path / "figures") + _ensure_directory(cfg.path / "figures") if (cfg.path / "everest_output").exists(): - opt = process_optimization(cfg) - plot_optimization(cfg, opt) - plot_optimization_details(cfg, opt) - extract_optimal_solution(cfg, opt) + opt = _process_optimization(cfg) + _plot_optimization(cfg, opt) + _plot_optimization_details(cfg, opt) + _extract_optimal_solution(cfg, opt) else: - state = initialize_ensemble(cfg) + state = _initialize_ensemble(cfg) for j in range(state.n_e): for i in range(state.n_i): - read_realisation(cfg, state, i, j) - - plot_simulation_ensemble(cfg, state, tab20) - plot_hm_mismatch(cfg, state) - plot_misfit(cfg, state) - plot_observable_distribution(cfg, state) - plot_parameter_distributions(cfg, state) - plot_cumulative_misfit(cfg, state, tab20) - extract_best_simulation(cfg, state) + _read_realisation(cfg, state, i, j) + + _plot_simulation_ensemble(cfg, state, tab20) + _plot_history_matching_mismatch(cfg, state) + _plot_misfit(cfg, state) + _plot_observable_distribution(cfg, state) + _plot_parameter_distributions(cfg, state) + _plot_cumulative_misfit(cfg, state, tab20) + _extract_best_simulation(cfg, state) if __name__ == "__main__": diff --git a/src/pofff/visualization/maps.py b/src/pofff/visualization/maps.py index 81afa4e..1dcce58 100755 --- a/src/pofff/visualization/maps.py +++ b/src/pofff/visualization/maps.py @@ -3,7 +3,11 @@ # https://github.com/fluidflower/general/blob/main/visualization/generate_segmented_images.py # pylint: disable=R0913, R0914, R0917 -"""Overlay simulated spatial maps with experimental contour boundaries.""" +"""Create spatial benchmark overlays from simulation and experiment data. + +Simulation saturation and dissolved-CO2 fields are segmented on the FluidFlower +reporting grid, combined with experimental contour boundaries and geological +lines, and written as maps for the requested evaluation times.""" import argparse import csv @@ -21,8 +25,27 @@ NZ_TARGET = 120 # Target grid size in z -def pngs(simulations, experiment, x, z, points, lines, t): - """Plot segmented simulation map with experimental contours and geology.""" +def _write_overlay_map(simulations, experiment, x, z, points, lines, t): + """Write one simulation and experiment overlay map. + + The figure is written as ``map_