diff --git a/.github/workflows/ci.yml b/.github/workflows/mkdocs.yml similarity index 85% rename from .github/workflows/ci.yml rename to .github/workflows/mkdocs.yml index 1b3bf2d..f665ec5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/mkdocs.yml @@ -1,11 +1,11 @@ -name: ci +name: mkdocs on: - push: - branches: - - master - - main + release: + types: [published] + permissions: contents: write + jobs: deploy: runs-on: ubuntu-latest @@ -18,7 +18,7 @@ jobs: - uses: actions/setup-python@v5 with: python-version: 3.x - - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV + - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV - uses: actions/cache@v4 with: key: mkdocs-material-${{ env.cache_id }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1ed8d18 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,53 @@ +name: Upload Python Package + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + release-build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: Build release distributions + run: | + python -m pip install build + python -m build + + - name: Upload distributions + uses: actions/upload-artifact@v4 + with: + name: release-dists + path: dist/ + + pypi-publish: + runs-on: ubuntu-latest + + needs: + - release-build + + permissions: + id-token: write + + environment: + name: pypi + url: https://pypi.org/project/shimeri/ + + steps: + - name: Retrieve release distributions + uses: actions/download-artifact@v4 + with: + name: release-dists + path: dist/ + + - name: Publish release distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.python-version b/.python-version index 1445aee..8e34c81 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.10.14 +3.9.19 diff --git a/pyproject.toml b/pyproject.toml index babb4a0..19098aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "shimeri" -version = "0.1.1" +version = "0.1.2" description = "A Python package for calculating psychrometric properties of moist air and plotting psychrometric charts." authors = [ { name = "Shoji, Yutaka", email = "ytk.shoji@gmail.com" } @@ -11,7 +11,7 @@ dependencies = [ "plotly>=5.10", ] readme = "README.md" -requires-python = ">= 3.10" +requires-python = ">= 3.9" [project.urls] Homepage = "https://github.com/yutaka-shoji/shimeri" diff --git a/requirements-dev.lock b/requirements-dev.lock index 1836b01..54ee306 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -29,6 +29,11 @@ griffe==0.48.0 # via mkdocstrings-python idna==3.7 # via requests +importlib-metadata==8.2.0 + # via markdown + # via mkdocs + # via mkdocs-get-deps + # via mkdocstrings jinja2==3.1.4 # via mkdocs # via mkdocs-material @@ -105,7 +110,11 @@ six==1.16.0 # via python-dateutil tenacity==8.5.0 # via plotly +typing-extensions==4.12.2 + # via mkdocstrings urllib3==2.2.2 # via requests watchdog==4.0.1 # via mkdocs +zipp==3.19.2 + # via importlib-metadata diff --git a/src/shimeri/psychrometricchart.py b/src/shimeri/psychrometricchart.py index 8926241..0cad6ab 100644 --- a/src/shimeri/psychrometricchart.py +++ b/src/shimeri/psychrometricchart.py @@ -1,6 +1,9 @@ +from typing import Union + import numpy as np -from numpy.typing import ArrayLike, NDArray import plotly.graph_objects as go +from numpy.typing import ArrayLike, NDArray + from shimeri.psychrometrics import PsychrometricCalculator @@ -79,7 +82,10 @@ def __init__(self, pressure: float = 101.325): ) def add_points( - self, en: NDArray[np.float64] | float, hr: NDArray[np.float64] | float, **kwargs + self, + en: Union[NDArray[np.float64], float], + hr: Union[NDArray[np.float64], float], + **kwargs, ): """ Add points to the psychrometric chart. @@ -116,7 +122,7 @@ def add_points( def draw_iso_rh_line( self, rh: float, - db_range: list[float] | NDArray[np.float64] = [-10, 70], + db_range: Union[list[float], NDArray[np.float64]] = [-10, 70], **kwargs, ): """ @@ -138,7 +144,7 @@ def draw_iso_rh_line( def draw_iso_db_line( self, db: float, - rh_range: list[float] | NDArray[np.float64] = [0, 100], + rh_range: Union[list[float], NDArray[np.float64]] = [0, 100], **kwargs, ): """ @@ -160,7 +166,7 @@ def draw_iso_db_line( def draw_iso_hr_line( self, hr: float, - db_range: list[float] | NDArray[np.float64] = [-10, 70], + db_range: Union[list[float], NDArray[np.float64]] = [-10, 70], **kwargs, ): """ @@ -182,7 +188,7 @@ def draw_iso_hr_line( def draw_iso_en_line( self, en: float, - db_range: list[float] | NDArray[np.float64] = [-10, 70], + db_range: Union[list[float], NDArray[np.float64]] = [-10, 70], **kwargs, ): """ diff --git a/src/shimeri/psychrometrics.py b/src/shimeri/psychrometrics.py index 0a57b87..f3a2660 100644 --- a/src/shimeri/psychrometrics.py +++ b/src/shimeri/psychrometrics.py @@ -1,11 +1,10 @@ import warnings -from typing import Callable +from typing import Callable, Union import numpy as np -from numpy.typing import NDArray, ArrayLike +from numpy.typing import ArrayLike, NDArray from scipy.optimize import root - # CONSTANTS MOL_WEIGHT_WATER = 18.0153 # g/mol MOL_WEIGHT_AIR = 28.9645 # g/mol @@ -36,11 +35,11 @@ def get_all( hr: ArrayLike = np.nan, en: ArrayLike = np.nan, ) -> tuple[ - NDArray[np.float64] | float, - NDArray[np.float64] | float, - NDArray[np.float64] | float, - NDArray[np.float64] | float, - NDArray[np.float64] | float, + Union[NDArray[np.float64], float], + Union[NDArray[np.float64], float], + Union[NDArray[np.float64], float], + Union[NDArray[np.float64], float], + Union[NDArray[np.float64], float], ]: """ Calculate all psychrometric variables given any two of them. @@ -244,7 +243,7 @@ def get_hr_from_db_rh(self, db: ArrayLike, rh: ArrayLike) -> NDArray[np.float64] # Calculate partial pressure of water vapor pw = rh * ps / 100 # Calculate humidity ratio - return (MOL_WEIGHT_WATER / MOL_WEIGHT_AIR) * pw / (self.pressure - pw) * 1e3 + return self.get_hr_from_pw(pw) def get_rh_from_db_hr(self, db: ArrayLike, hr: ArrayLike) -> NDArray[np.float64]: """ @@ -288,10 +287,45 @@ def get_db_from_hr_en(hr: ArrayLike, en: ArrayLike) -> NDArray[np.float64]: # Calculate dry bulb temperature return (en - 2501 * hr * 1e-3) / (1.006 + 1.86 * hr * 1e-3) + def get_hr_from_db_wb(self, db: ArrayLike, wb: ArrayLike) -> NDArray[np.float64]: + """ + Calculate humidity ratio from dry bulb temperature and wet bulb temperature. + + Args: + db: Dry bulb temperature (degC). + wb: Wet bulb temperature (degC). + + Returns: + Humidity ratio (g/kg). + """ + # Broadcast the input arrays to the same shape + db, wb = np.broadcast_arrays(db, wb) + # Calculate saturation pressure at wet bulb temperature + ps_wb = get_saturation_pressure(wb) + # Calculate psychrometer constant + c = self._psychrometer_constant(wb) + # Calculate partial pressure of water vapor + pw = ps_wb - c * self.pressure * (db - wb) + # Calculate humidity ratio + return self.get_hr_from_pw(pw) + + def get_hr_from_pw(self, pw: ArrayLike) -> NDArray[np.float64]: + """ + Calculate humidity ratio from partial pressure of water vapor. + + Args: + pw: Partial pressure of water vapor (kPa). + + Returns: + Humidity ratio (g/kg). + """ + pw = np.asarray(pw) + return (MOL_WEIGHT_WATER / MOL_WEIGHT_AIR) * pw / (self.pressure - pw) * 1e3 + @staticmethod - def _psychrometer_constant(wb: float) -> float: + def _psychrometer_constant(wb: ArrayLike) -> NDArray[np.float64]: """Calculate the psychrometer constant.""" - return 0.000662 if wb >= 0.01 else 0.000583 + return np.where(np.asarray(wb) >= 0.01, 0.000662, 0.000583) def get_saturation_pressure(temp: ArrayLike) -> NDArray[np.float64]: