diff --git a/changelog/194.change.md b/changelog/194.change.md new file mode 100644 index 00000000..642d442a --- /dev/null +++ b/changelog/194.change.md @@ -0,0 +1 @@ +Migrate stabilized `Morph` functionality to `boilercv` diff --git a/docs/conf.py b/docs/conf.py index 4079e00d..45a9208b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -4,6 +4,9 @@ from hashlib import sha256 from pathlib import Path +from ruamel.yaml import YAML +from sphinx.application import Sphinx + from boilercv_docs import DOCS, PYPROJECT, chdir_docs from boilercv_docs.intersphinx import get_ispx, get_rtd, get_url from boilercv_docs.nbs import init_nb_env @@ -12,8 +15,6 @@ from boilercv_pipeline.correlations.dimensionless_bubble_diameter.morphs import ( EQUATIONS, ) -from ruamel.yaml import YAML -from sphinx.application import Sphinx # ! Initialization patch_nbs() @@ -45,17 +46,17 @@ """Package version.""" # ! Intersphinx and related ISPX_MAPPING: dict[str, IspxMappingValue] = { - **{pkg: get_rtd(pkg) for pkg in ["myst_parser", "nbformat", "numpydoc"]}, + **{pkg: get_rtd(pkg) for pkg in ["myst_parser", "nbformat", "numpydoc", "tomlkit"]}, **{pkg: get_rtd(pkg, latest=True) for pkg in ["pyqtgraph"]}, "jupyterbook": get_url("jupyterbook.org/en"), "numpy": get_url("numpy.org/doc"), "matplotlib": get_url("matplotlib.org"), "pytest": get_url("docs.pytest.org/en"), "sympy": get_url("docs.sympy.org", latest=True), + "colorcet": get_ispx("https://colorcet.holoviz.org/"), "cv2": get_ispx("docs.opencv.org/2.4"), - "python": get_ispx("docs.python.org/3"), "pandas": get_ispx("pandas.pydata.org/docs"), - "colorcet": get_ispx("https://colorcet.holoviz.org/"), + "python": get_ispx("docs.python.org/3"), } """Intersphinx mapping.""" TIPPY_RTD_URLS = [ @@ -142,7 +143,7 @@ def dpath(path: Path, rel: Path = DOCS) -> str: "sphinxcontrib.towncrier", ] suppress_warnings = [ - "autodoc2.dup_item" # "Duplicate items in boilercv_tests.test_morph + "autodoc2.dup_item" # "Duplicate items in boilercv_tests.test_morphs ] # ! Theme html_title = PACKAGE @@ -250,6 +251,7 @@ def dpath(path: Path, rel: Path = DOCS) -> str: (r"py:.*", r"_pytest\..+"), (r"py:.*", r"boilercore\..+"), (r"py:.*", r"numpy\.typing\..+"), + (r"py:.*", r"tomlkit\.container\..+"), # ? sympy: https://github.com/sympy/sympy/issues/17619#issuecomment-536781620 (r"py:.*", r"sympy\..+"), (r"py:.*", r"pydantic\..+"), # ? https://github.com/pydantic/pydantic/issues/1339 @@ -257,8 +259,7 @@ def dpath(path: Path, rel: Path = DOCS) -> str: # ? TypeAlias: https://github.com/sphinx-doc/sphinx/issues/10785 (r"py:class", rf"{PACKAGE}.*\.types\..+"), (r"py:class", rf"{PACKAGE}_pipeline\.captivate\.previews\..+"), - (r"py:class", rf"{PACKAGE}_pipeline\.equations\..+"), - (r"py:class", rf"{PACKAGE}_tests\.test_morph\..+"), + (r"py:.*", rf"{PACKAGE}_tests\.test_morphs\..+"), # ? Until done with Pydantic v1 (r"py:.*", r"pydantic\.v1\..+"), ] diff --git a/docs/experiments/e230920_subcool/find_centers.ipynb b/docs/experiments/e230920_subcool/find_centers.ipynb index a1325a06..a7de51d8 100644 --- a/docs/experiments/e230920_subcool/find_centers.ipynb +++ b/docs/experiments/e230920_subcool/find_centers.ipynb @@ -33,10 +33,6 @@ "\n", "paths = init()\n", "\n", - "from boilercv_docs.nbs import HIDE, nowarn, style_df\n", - "from boilercv_pipeline.experiments.e230920_subcool import GBC, bounded_ax\n", - "from boilercv_pipeline.experiments.e240215_plotting import cool, warm\n", - "from boilercv_pipeline.sets import get_contours_df, get_dataset\n", "from geopandas import GeoDataFrame, points_from_xy\n", "from matplotlib.pyplot import subplots\n", "from myst_nb import glue\n", @@ -45,6 +41,10 @@ "from shapely import LinearRing\n", "\n", "from boilercv.images import scale_bool\n", + "from boilercv_docs.nbs import HIDE, nowarn, style_df\n", + "from boilercv_pipeline.experiments.e230920_subcool import GBC, bounded_ax\n", + "from boilercv_pipeline.experiments.e240215_plotting import cool, warm\n", + "from boilercv_pipeline.sets import get_contours_df, get_dataset\n", "\n", "COMPARE_WITH_TRACKPY = True\n", "\"\"\"Whether to get centers using the Trackpy approach.\"\"\"\n", diff --git a/docs/experiments/e230920_subcool/find_objects.ipynb b/docs/experiments/e230920_subcool/find_objects.ipynb index 40b4f688..60660251 100644 --- a/docs/experiments/e230920_subcool/find_objects.ipynb +++ b/docs/experiments/e230920_subcool/find_objects.ipynb @@ -33,10 +33,6 @@ "\n", "paths = init()\n", "\n", - "from boilercv_docs.nbs import HIDE, nowarn, style_df\n", - "from boilercv_pipeline.experiments.e230920_subcool import GBC, bounded_ax\n", - "from boilercv_pipeline.experiments.e240215_plotting import cool, warm\n", - "from boilercv_pipeline.sets import get_contours_df, get_dataset\n", "from geopandas import GeoDataFrame, points_from_xy\n", "from numpy import pi, sqrt\n", "from pandas import DataFrame, IndexSlice, NamedAgg\n", @@ -44,6 +40,10 @@ "from shapely import LinearRing, Polygon\n", "\n", "from boilercv.images import scale_bool\n", + "from boilercv_docs.nbs import HIDE, nowarn, style_df\n", + "from boilercv_pipeline.experiments.e230920_subcool import GBC, bounded_ax\n", + "from boilercv_pipeline.experiments.e240215_plotting import cool, warm\n", + "from boilercv_pipeline.sets import get_contours_df, get_dataset\n", "\n", "COMPARE_WITH_TRACKPY = True\n", "\"\"\"Whether to get objects using the Trackpy approach.\"\"\"\n", diff --git a/docs/experiments/e230920_subcool/find_tracks.ipynb b/docs/experiments/e230920_subcool/find_tracks.ipynb index 46d92019..b80e2f65 100644 --- a/docs/experiments/e230920_subcool/find_tracks.ipynb +++ b/docs/experiments/e230920_subcool/find_tracks.ipynb @@ -33,6 +33,9 @@ "\n", "paths = init()\n", "\n", + "from numpy import diff, linalg\n", + "from pandas import DataFrame, read_hdf\n", + "\n", "from boilercv_docs.nbs import HIDE, display_dataframe_with_math, nowarn, style_df\n", "from boilercv_pipeline.experiments.e230920_subcool import (\n", " GBC,\n", @@ -43,8 +46,6 @@ " transform_cols,\n", ")\n", "from boilercv_pipeline.sets import get_dataset\n", - "from numpy import diff, linalg\n", - "from pandas import DataFrame, read_hdf\n", "\n", "with nowarn(capture=True):\n", " from trackpy import link, quiet\n", diff --git a/docs/experiments/e230920_subcool/find_tracks_trackpy.ipynb b/docs/experiments/e230920_subcool/find_tracks_trackpy.ipynb index 2ef721ed..39e5add2 100644 --- a/docs/experiments/e230920_subcool/find_tracks_trackpy.ipynb +++ b/docs/experiments/e230920_subcool/find_tracks_trackpy.ipynb @@ -27,6 +27,23 @@ "\n", "paths = init()\n", "\n", + "from matplotlib.figure import Figure\n", + "from matplotlib.pyplot import subplot_mosaic, subplots\n", + "from numpy import diff, linalg, logspace\n", + "from pandas import CategoricalDtype, DataFrame, read_hdf\n", + "from seaborn import lineplot, move_legend, scatterplot\n", + "\n", + "from boilercv.data import apply_to_img_da\n", + "from boilercv.dimensionless_params import (\n", + " fourier,\n", + " jakob,\n", + " kinematic_viscosity,\n", + " prandtl,\n", + " reynolds,\n", + " thermal_diffusivity,\n", + ")\n", + "from boilercv.images import scale_bool\n", + "from boilercv.images.cv import Op, Transform, transform\n", "from boilercv_docs.nbs import HIDE, nowarn, style_df\n", "from boilercv_pipeline.correlations.dimensionless_bubble_diameter import (\n", " florschuetz_chao_1965,\n", @@ -47,23 +64,6 @@ "from boilercv_pipeline.experiments.e240215_plotting import cool, warm\n", "from boilercv_pipeline.models.params import PARAMS\n", "from boilercv_pipeline.sets import get_dataset\n", - "from matplotlib.figure import Figure\n", - "from matplotlib.pyplot import subplot_mosaic, subplots\n", - "from numpy import diff, linalg, logspace\n", - "from pandas import CategoricalDtype, DataFrame, read_hdf\n", - "from seaborn import lineplot, move_legend, scatterplot\n", - "\n", - "from boilercv.data import apply_to_img_da\n", - "from boilercv.dimensionless_params import (\n", - " fourier,\n", - " jakob,\n", - " kinematic_viscosity,\n", - " prandtl,\n", - " reynolds,\n", - " thermal_diffusivity,\n", - ")\n", - "from boilercv.images import scale_bool\n", - "from boilercv.images.cv import Op, Transform, transform\n", "\n", "with nowarn(capture=True):\n", " from trackpy import batch, link, locate, quiet\n", diff --git a/docs/experiments/e230920_subcool/get_thermal_data.ipynb b/docs/experiments/e230920_subcool/get_thermal_data.ipynb index fcf977b3..408e58fb 100644 --- a/docs/experiments/e230920_subcool/get_thermal_data.ipynb +++ b/docs/experiments/e230920_subcool/get_thermal_data.ipynb @@ -30,11 +30,12 @@ "\n", "paths = init()\n", "\n", - "from boilercv_docs.nbs import HIDE\n", - "from boilercv_pipeline.experiments.e230920_subcool import ALL_THERMAL_DATA\n", "from pandas import DataFrame, read_csv\n", "from seaborn import scatterplot\n", "\n", + "from boilercv_docs.nbs import HIDE\n", + "from boilercv_pipeline.experiments.e230920_subcool import ALL_THERMAL_DATA\n", + "\n", "BOILING = 97.33 # (C) inferred from mean pressure over the time span\n", "TIME = \"time\"\n", "BASE_TEMP = \"T0cal (C)\"\n", diff --git a/docs/experiments/e230920_subcool/plot_tracks.ipynb b/docs/experiments/e230920_subcool/plot_tracks.ipynb index 09a29587..e8502064 100644 --- a/docs/experiments/e230920_subcool/plot_tracks.ipynb +++ b/docs/experiments/e230920_subcool/plot_tracks.ipynb @@ -32,6 +32,21 @@ "from boilercv_docs.nbs import init\n", "\n", "paths = init()\n", + "from matplotlib.figure import Figure\n", + "from matplotlib.pyplot import subplot_mosaic, subplots\n", + "from numpy import diff, log10, logspace\n", + "from pandas import DataFrame, Series, read_hdf\n", + "from seaborn import lineplot, move_legend, scatterplot\n", + "\n", + "from boilercv.dimensionless_params import (\n", + " fourier,\n", + " jakob,\n", + " kinematic_viscosity,\n", + " prandtl,\n", + " reynolds,\n", + " thermal_diffusivity,\n", + ")\n", + "from boilercv.images import scale_bool\n", "from boilercv_docs.nbs import HIDE\n", "from boilercv_pipeline.correlations.dimensionless_bubble_diameter import (\n", " akiyama_1973,\n", @@ -57,21 +72,6 @@ ")\n", "from boilercv_pipeline.experiments.e240215_plotting import cool, warm12\n", "from boilercv_pipeline.sets import get_dataset\n", - "from matplotlib.figure import Figure\n", - "from matplotlib.pyplot import subplot_mosaic, subplots\n", - "from numpy import diff, log10, logspace\n", - "from pandas import DataFrame, Series, read_hdf\n", - "from seaborn import lineplot, move_legend, scatterplot\n", - "\n", - "from boilercv.dimensionless_params import (\n", - " fourier,\n", - " jakob,\n", - " kinematic_viscosity,\n", - " prandtl,\n", - " reynolds,\n", - " thermal_diffusivity,\n", - ")\n", - "from boilercv.images import scale_bool\n", "\n", "S = Series\n", "\n", diff --git a/pipeline/boilercv_pipeline/correlations/dimensionless_bubble_diameter/morphs.py b/pipeline/boilercv_pipeline/correlations/dimensionless_bubble_diameter/morphs.py index bd66155b..276ddd1f 100644 --- a/pipeline/boilercv_pipeline/correlations/dimensionless_bubble_diameter/morphs.py +++ b/pipeline/boilercv_pipeline/correlations/dimensionless_bubble_diameter/morphs.py @@ -4,34 +4,34 @@ from pathlib import Path from re import sub from string import whitespace -from typing import Annotated, Any, ClassVar, Generic, Self, TypeAlias, overload +from typing import Any, ClassVar, Generic, Self, overload from numpy import linspace, pi -from pydantic import BaseModel, Field, PlainValidator, model_validator +from pydantic import BaseModel, Field, model_validator from pydantic_core import PydanticUndefinedType -from sympy import Basic, symbols, sympify from tomlkit import parse from tomlkit.container import Container from tomlkit.items import Item +from boilercv.morphs import BaseMorph, Morph from boilercv_pipeline.correlations.dimensionless_bubble_diameter.types import ( + LOCALS, Eq, Expectation, + Expr, FormsRepl, - JsonStrPlainSerializer, K, Kind, Leaf, + Locals, Node, Param, Repl, Sym, V, kinds, - params, - syms, + solve_syms, ) -from boilercv_pipeline.equations import BaseMorph, Morph base = Path(__file__).with_suffix(".toml") EQUATIONS_TOML = base.with_stem("equations") @@ -49,8 +49,6 @@ for find, repl in {"{0}": r"\o", "{b0}": r"\b0"}.items() ) """Replacements to make after parsing LaTeX from PNGs.""" -PARAMS = Morph[Param, Sym](dict(zip(params, syms, strict=True))) -"""Parameters.""" KWDS = Morph[Param, Expectation]({ "bubble_fourier": linspace(start=0.0, stop=5.0e-3, num=10), "bubble_jakob": 1.0, @@ -125,23 +123,6 @@ class Forms(DefaultMorph[Kind, str]): DefaultMorph.register(Forms) -def validate_expr(v: Basic | str): - """Validate expression.""" - return ( - v - if isinstance(v, Basic) - else sympify(v, locals=LOCALS.model_dump(), evaluate=False) - ) - - -Expr: TypeAlias = Annotated[ - Basic, PlainValidator(validate_expr), JsonStrPlainSerializer -] -"""Expression.""" -solve_syms: tuple[Sym, ...] = ("Fo_0", "beta") -"""Symbols to solve for.""" - - class Soln(BaseModel): """All solutions.""" @@ -159,20 +140,6 @@ class Solns(DefaultMorph[Sym, Soln]): DefaultMorph.register(Solns) -Locals: TypeAlias = Morph[Sym, Expr] -"""Locals.""" -LOCALS = Locals( - dict( - zip( - syms, - symbols(list(PARAMS.values()), nonnegative=True, real=True, finite=True), - strict=False, - ) - ) -) -"""Local variables.""" - - def set_equation_forms(i: Forms, symbols: Locals) -> Forms: """Set equation forms.""" return i.pipe( diff --git a/pipeline/boilercv_pipeline/correlations/dimensionless_bubble_diameter/types.py b/pipeline/boilercv_pipeline/correlations/dimensionless_bubble_diameter/types.py index b022d88c..11b6d6c9 100644 --- a/pipeline/boilercv_pipeline/correlations/dimensionless_bubble_diameter/types.py +++ b/pipeline/boilercv_pipeline/correlations/dimensionless_bubble_diameter/types.py @@ -14,6 +14,9 @@ from numpy import float64 from numpy.typing import NDArray from pydantic import PlainSerializer, PlainValidator +from sympy import Basic, symbols, sympify + +from boilercv.morphs import Morph T = TypeVar("T") K = TypeVar("K") @@ -45,6 +48,8 @@ class Repl(NamedTuple, Generic[T]): """Symbol.""" syms: tuple[Sym, ...] = get_args(Sym) """Symbols.""" +solve_syms: tuple[Sym, ...] = ("Fo_0", "beta") +"""Symbols to solve for.""" FormsRepl: TypeAlias = Repl[Kind] """Forms replacements.""" Param: TypeAlias = Literal[ @@ -71,3 +76,33 @@ class Repl(NamedTuple, Generic[T]): """Leaf node.""" Node: TypeAlias = dict[Any, "Node | Leaf"] """General node.""" + + +def validate_expr(v: Basic | str): + """Validate expression.""" + return ( + v + if isinstance(v, Basic) + else sympify(v, locals=LOCALS.model_dump(), evaluate=False) + ) + + +Expr: TypeAlias = Annotated[ + Basic, PlainValidator(validate_expr), JsonStrPlainSerializer +] +"""Expression.""" + +Locals: TypeAlias = Morph[Sym, Expr] +"""Locals.""" +PARAMS = Morph[Param, Sym](dict(zip(params, syms, strict=True))) +"""Parameters.""" +LOCALS = Locals( + dict( + zip( + syms, + symbols(list(PARAMS.values()), nonnegative=True, real=True, finite=True), + strict=False, + ) + ) +) +"""Local variables.""" diff --git a/pipeline/boilercv_pipeline/equations/convert_latex_to_sympy.py b/pipeline/boilercv_pipeline/equations/convert_latex_to_sympy.py index c2a182c7..0791d1c5 100644 --- a/pipeline/boilercv_pipeline/equations/convert_latex_to_sympy.py +++ b/pipeline/boilercv_pipeline/equations/convert_latex_to_sympy.py @@ -12,6 +12,7 @@ from tomlkit.items import Table from tqdm import tqdm +from boilercv.morphs import Morph from boilercv_pipeline.correlations import PIPX from boilercv_pipeline.correlations.dimensionless_bubble_diameter.equations import ( FormsRepl, @@ -30,7 +31,6 @@ Kind, V, ) -from boilercv_pipeline.equations import Morph APP = App() """CLI.""" diff --git a/pipeline/pyproject.toml b/pipeline/pyproject.toml index 740381f9..ef81aa6b 100644 --- a/pipeline/pyproject.toml +++ b/pipeline/pyproject.toml @@ -24,7 +24,7 @@ dependencies = [ "pillow>=10.0.0", "ploomber-engine>=0.0.30", "pyarrow>=14.0.1", - "pydantic>=2.7.1", # ? RootModel improvements + "pydantic>=2.7.1", "pyqtgraph>=0.13.3", # ? Wheels are missing on Linux for PySide6, `uv` doesn't like it "pyside6-addons==6.7.0; sys_platform!='linux'", diff --git a/pyproject.toml b/pyproject.toml index df721abd..95bd01da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ dependencies = [ "numpy>=1.24.4", "pandas[hdf5,performance]>=2.0.2", "pillow>=10.0.0", + "pydantic>=2.7.1", # ? RootModel improvements "pytz>=2023.3", "xarray[accel,io,parallel]>=2023.7.0", ] @@ -193,7 +194,7 @@ reportUnusedCallResult = "none" cache-dir = ".cache/.ruff_cache" extend-exclude = ["bin", "submodules"] extend-include = ["*.ipynb"] -src = ["src"] +src = ["src", "docs", "pipeline", "scripts", "tests"] output-format = "grouped" fix = true preview = true @@ -279,7 +280,7 @@ convention = "numpy" "SIM300", # Allow constants (expectations) on the RHS "SLF001", # Allow private member access in tests ] -"tests/boilercv_tests/test_morph.py" = [ +"tests/boilercv_tests/test_morphs.py" = [ "D101", # Allow not documenting a test class "D103", # Allow not documenting a test function "E302", # Allow manual spacing of test classes close to their headings diff --git a/scripts/boilercv_tools/sync.py b/scripts/boilercv_tools/sync.py index 5263c0bf..1805ad5f 100644 --- a/scripts/boilercv_tools/sync.py +++ b/scripts/boilercv_tools/sync.py @@ -84,23 +84,6 @@ class Dep: ) """Supported Python versions.""" -# ! Checking -NAME_PAT = r"[A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9._-]*[A-Za-z0-9]" -"""Regular expression for a legal Python package name. - -See: https://packaging.python.org/en/latest/specifications/name-normalization/#name-format -""" -OP_PAT = r" @ |==" -"""Regular expression for valid version separators in {meth}`~boilercv_tools.sync.compile` output.""" -DEP_PAT = rf"(?P{NAME_PAT})(?P{OP_PAT})(?P.+)" -"""Regular expression for a dependency and its version specifier.""" -VIAS_PAT = r"(?:\n\s{4}.+)+" -"""Regular expression for vias metadata printed in `uv pip compile` output.""" -DIRECT_VIA_PAT = r"\(.+\)" -"""Regular expression for valid version separators in the lock file.""" -META_VALUE_PREFIX_PAT = r"#\s{3}" -"""Regular expression for value in a metadata section.""" - def check_compilation(high: bool = False) -> str: """Check compilation, re-lock if incompatible, and return the requirements.""" @@ -215,7 +198,7 @@ class Compiler: """Paths compiled from, such as `requirements.in` or `pyproject.toml`.""" def get_command(self) -> tuple[datetime, list[str]]: - """Command to reproduce {py:attr}`requirements`.""" + """Command to reproduce compilation requirements.""" time = datetime.now(UTC) return time, [ "bin/uv", @@ -270,11 +253,22 @@ def from_lock( ) +NAME_PAT = r"[A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9._-]*[A-Za-z0-9]" +"""Regular expression for a legal Python package name. + +See: https://packaging.python.org/en/latest/specifications/name-normalization/#name-format +""" +OP_PAT = "|".join(ops) +"""Regular expression for valid version separators.""" + + def get_directs() -> dict[str, Dep]: """Get directs.""" directs: dict[str, Dep] = {} _, requirements = compile(Compiler(no_deps=True)) - for direct in finditer(rf"(?m)^{DEP_PAT}$", requirements): + for direct in finditer( + rf"(?m)^(?P{NAME_PAT})(?P{OP_PAT})(?P.+)$", requirements + ): op = direct["op"] if not isinstance(op, str) or op not in ops: raise ValueError(f"Invalid operator in {direct.groups()}") @@ -382,5 +376,5 @@ def get_submodule_info(kind: SubmoduleInfoKind) -> list[str]: def escape(path: str | Path) -> str: - """Escape a path, suitable for passing to e.g. {meth}`~run`.""" + """Escape a path, suitable for passing to e.g. {func}`~subprocess.run`.""" return quote(Path(path).as_posix()) diff --git a/scripts/preview_sympy.ipynb b/scripts/preview_sympy.ipynb index dcd89744..82b14cda 100644 --- a/scripts/preview_sympy.ipynb +++ b/scripts/preview_sympy.ipynb @@ -17,6 +17,10 @@ }, "outputs": [], "source": [ + "from docs.boilercv_docs.nbs import HIDE\n", + "from sympy import sympify\n", + "from tomlkit import parse\n", + "\n", "from boilercv_pipeline.correlations.dimensionless_bubble_diameter.equations import (\n", " EQUATIONS_TOML,\n", " SYMPY_REPLS,\n", @@ -25,9 +29,6 @@ " LOCALS,\n", " SUBS,\n", ")\n", - "from docs.boilercv_docs.nbs import HIDE\n", - "from sympy import sympify\n", - "from tomlkit import parse\n", "\n", "NAME = \"florschuetz_chao_1965\"\n", "\"\"\"Name of the equation to preview.\"\"\"\n", diff --git a/pipeline/boilercv_pipeline/equations/__init__.py b/src/boilercv/morphs/__init__.py similarity index 90% rename from pipeline/boilercv_pipeline/equations/__init__.py rename to src/boilercv/morphs/__init__.py index 135c7487..49328228 100644 --- a/pipeline/boilercv_pipeline/equations/__init__.py +++ b/src/boilercv/morphs/__init__.py @@ -1,4 +1,4 @@ -"""Equations.""" +"""Morphs.""" from __future__ import annotations @@ -12,9 +12,6 @@ ClassVar, Generic, Literal, - NamedTuple, - ParamSpec, - Protocol, Self, TypeVar, get_args, @@ -26,68 +23,22 @@ from pydantic import BaseModel, ConfigDict, Field, RootModel, ValidationError -T = TypeVar("T", contravariant=True) -R = TypeVar("R", covariant=True) -P = ParamSpec("P") - - -class TypeType(Protocol[T, R, P]): # noqa: D101 - def __call__(self, i: T, /, *args: P.args, **kwds: P.kwargs) -> R: ... # noqa: D102 - - -K = TypeVar("K") -V = TypeVar("V") - - -class TypeMap(Protocol[T, K, V, P]): # noqa: D101 - def __call__( # noqa: D102 - self, i: T, /, *args: P.args, **kwds: P.kwargs - ) -> MutableMapping[K, V]: ... - - -class TypeDict(Protocol[T, K, V, P]): # noqa: D101 - def __call__( # noqa: D102 - self, i: T, /, *args: P.args, **kwds: P.kwargs - ) -> dict[K, V]: ... - - -class MapType(Protocol[K, V, R, P]): # noqa: D101 - def __call__( # noqa: D102 - self, i: MutableMapping[K, V], /, *args: P.args, **kwds: P.kwargs - ) -> R: ... - - -class DictType(Protocol[K, V, R, P]): # noqa: D101 - def __call__( # noqa: D102 - self, i: dict[K, V], /, *args: P.args, **kwds: P.kwargs - ) -> R: ... - - -RK = TypeVar("RK") -RV = TypeVar("RV") - - -class MapMap(Protocol[K, V, RK, RV, P]): # noqa: D101 - def __call__( # noqa: D102 - self, i: MutableMapping[K, V], /, *args: P.args, **kwds: P.kwargs - ) -> MutableMapping[RK, RV]: ... - - -class DictDict(Protocol[K, V, RK, RV, P]): # noqa: D101 - def __call__( # noqa: D102 - self, i: dict[K, V], /, *args: P.args, **kwds: P.kwargs - ) -> dict[RK, RV]: ... - - -KT = TypeVar("KT", bound=type) -VT = TypeVar("VT", bound=type) - - -class Types(NamedTuple): - """Inner types for mapings.""" - - key: type - value: type +from boilercv.morphs.types import ( + RK, + RV, + DictDict, + DictType, + K, + MapMap, + MapType, + P, + R, + TypeDict, + TypeMap, + Types, + TypeType, + V, +) class MorphCommon(MutableMapping[K, V], ABC, Generic[K, V]): # noqa: PLR0904 diff --git a/src/boilercv/morphs/types.py b/src/boilercv/morphs/types.py new file mode 100644 index 00000000..78d2048c --- /dev/null +++ b/src/boilercv/morphs/types.py @@ -0,0 +1,67 @@ +"""Morph types.""" + +from collections.abc import MutableMapping +from typing import NamedTuple, ParamSpec, Protocol, TypeVar + +T = TypeVar("T", contravariant=True) +R = TypeVar("R", covariant=True) +P = ParamSpec("P") + + +class TypeType(Protocol[T, R, P]): # noqa: D101 + def __call__(self, i: T, /, *args: P.args, **kwds: P.kwargs) -> R: ... # noqa: D102 + + +K = TypeVar("K") +V = TypeVar("V") + + +class TypeMap(Protocol[T, K, V, P]): # noqa: D101 + def __call__( # noqa: D102 + self, i: T, /, *args: P.args, **kwds: P.kwargs + ) -> MutableMapping[K, V]: ... + + +class TypeDict(Protocol[T, K, V, P]): # noqa: D101 + def __call__( # noqa: D102 + self, i: T, /, *args: P.args, **kwds: P.kwargs + ) -> dict[K, V]: ... + + +class MapType(Protocol[K, V, R, P]): # noqa: D101 + def __call__( # noqa: D102 + self, i: MutableMapping[K, V], /, *args: P.args, **kwds: P.kwargs + ) -> R: ... + + +class DictType(Protocol[K, V, R, P]): # noqa: D101 + def __call__( # noqa: D102 + self, i: dict[K, V], /, *args: P.args, **kwds: P.kwargs + ) -> R: ... + + +RK = TypeVar("RK") +RV = TypeVar("RV") + + +class MapMap(Protocol[K, V, RK, RV, P]): # noqa: D101 + def __call__( # noqa: D102 + self, i: MutableMapping[K, V], /, *args: P.args, **kwds: P.kwargs + ) -> MutableMapping[RK, RV]: ... + + +class DictDict(Protocol[K, V, RK, RV, P]): # noqa: D101 + def __call__( # noqa: D102 + self, i: dict[K, V], /, *args: P.args, **kwds: P.kwargs + ) -> dict[RK, RV]: ... + + +KT = TypeVar("KT", bound=type) +VT = TypeVar("VT", bound=type) + + +class Types(NamedTuple): + """Inner types for mapings.""" + + key: type + value: type diff --git a/tests/boilercv_tests/test_equations/test_dimensionless_bubble_diameter.py b/tests/boilercv_tests/test_equations/test_dimensionless_bubble_diameter.py index 40104ca3..c11242b8 100644 --- a/tests/boilercv_tests/test_equations/test_dimensionless_bubble_diameter.py +++ b/tests/boilercv_tests/test_equations/test_dimensionless_bubble_diameter.py @@ -4,6 +4,9 @@ from tomllib import loads import pytest +from numpy import allclose +from sympy import lambdify + from boilercv_pipeline.correlations import dimensionless_bubble_diameter from boilercv_pipeline.correlations.dimensionless_bubble_diameter import ( equations as symbolic, @@ -12,8 +15,6 @@ EXPECTATIONS_TOML, KWDS, ) -from numpy import allclose -from sympy import lambdify lambdify # noqa: B018 diff --git a/tests/boilercv_tests/test_morph.py b/tests/boilercv_tests/test_morphs.py similarity index 99% rename from tests/boilercv_tests/test_morph.py rename to tests/boilercv_tests/test_morphs.py index dd2d9ab7..d70528f2 100644 --- a/tests/boilercv_tests/test_morph.py +++ b/tests/boilercv_tests/test_morphs.py @@ -7,9 +7,10 @@ from typing import TYPE_CHECKING, Any, Generic, Literal, TypeAlias import pytest -from boilercv_pipeline.equations import K, Morph, TypeType, V from pydantic import ValidationError +from boilercv.morphs import K, Morph, TypeType, V + # fmt: off # * MARK: Constants