Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate stabilized Morph functionality to boilercv #195

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/194.change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Migrate stabilized `Morph` functionality to `boilercv`
17 changes: 9 additions & 8 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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 = [
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -250,15 +251,15 @@ 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
(r"py:.*", r"PySide6\..+"), # ? https://bugreports.qt.io/browse/PYSIDE-2215
# ? 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\..+"),
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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,
Expand Down Expand Up @@ -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."""

Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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[
Expand All @@ -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."""
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -30,7 +31,6 @@
Kind,
V,
)
from boilercv_pipeline.equations import Morph

APP = App()
"""CLI."""
Expand Down
2 changes: 1 addition & 1 deletion pipeline/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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'",
Expand Down
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
34 changes: 14 additions & 20 deletions scripts/boilercv_tools/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -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>{NAME_PAT})(?P<op>{OP_PAT})(?P<rev>.+)"
"""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."""
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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]"
blakeNaccarato marked this conversation as resolved.
Show resolved Hide resolved
"""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>{NAME_PAT})(?P<op>{OP_PAT})(?P<rev>.+)$", requirements
):
op = direct["op"]
if not isinstance(op, str) or op not in ops:
raise ValueError(f"Invalid operator in {direct.groups()}")
Expand Down Expand Up @@ -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())
Loading
Loading