Skip to content

Commit

Permalink
Merge pull request #59 from azogue/remove-scipy-dependency
Browse files Browse the repository at this point in the history
📦️ Remove `scipy` dependency
  • Loading branch information
azogue authored Apr 8, 2024
2 parents 3746a02 + 24e15d1 commit d4d1026
Show file tree
Hide file tree
Showing 10 changed files with 3,087 additions and 83 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ repos:
- id: ruff-format

- repo: "https://github.com/pre-commit/pre-commit-hooks"
rev: "v4.5.0"
rev: "v4.6.0"
hooks:
- id: "end-of-file-fixer"
- id: "trailing-whitespace"
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.11.0] - 📦️ Remove scipy dependency - 2024-04-08

##### Changes

- ⚡️ Use simple **1D interpolator** to project lines, instead of `scipy.interpolate.interp1d`
- ✅ Add tests for convex hulls of given points
- ⚡️ Use simple **convex hull algorithm**, to compute zones to group annotated points, instead of using `scipy.spatial.ConvexHull`
- 📦️ Bump version, removing `scipy` dependency

## [0.10.0] - 📦 Migrate to pydantic v2 - 2024-04-07

Move to recent versions of `pydantic`, and also upgrade internal _linters_, moving to `ruff-format`
Expand Down
44 changes: 1 addition & 43 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 3 additions & 14 deletions psychrochart/chartdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@
GetVapPresFromHumRatio,
isIP,
)
from scipy.interpolate import interp1d

from psychrochart.models.curves import PsychroCurve, PsychroCurves
from psychrochart.models.styles import AnnotationStyle, CurveStyle
from psychrochart.util import solve_curves_with_iteration
from psychrochart.util import Interp1D, solve_curves_with_iteration

f_vec_hum_ratio_from_vap_press = np.vectorize(GetHumRatioFromVapPres)
f_vec_moist_air_enthalpy = np.vectorize(GetMoistAirEnthalpy)
Expand Down Expand Up @@ -273,12 +272,7 @@ def make_constant_enthalpy_lines(
)
/ _factor_out_h()
)
t_sat_interpolator = interp1d(
h_in_sat,
saturation_curve.x_data,
fill_value="extrapolate",
assume_sorted=True,
)
t_sat_interpolator = Interp1D(h_in_sat, saturation_curve.x_data)
h_min = (
GetMoistAirEnthalpy(
dbt_min_seen or saturation_curve.x_data[0],
Expand Down Expand Up @@ -412,12 +406,7 @@ def make_constant_specific_volume_lines(
temps_max_constant_v = f_vec_dry_temp_from_spec_vol(
np.array(v_objective), w_humidity_ratio_min / _factor_out_w(), pressure
)
t_sat_interpolator = interp1d(
v_in_sat,
saturation_curve.x_data,
fill_value="extrapolate",
assume_sorted=True,
)
t_sat_interpolator = Interp1D(v_in_sat, saturation_curve.x_data)
t_sat_points = solve_curves_with_iteration(
"CONSTANT VOLUME",
v_objective,
Expand Down
37 changes: 17 additions & 20 deletions psychrochart/plot_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from matplotlib.axes import Axes
from matplotlib.path import Path
from matplotlib.text import Annotation
from scipy.spatial import ConvexHull, QhullError

from psychrochart.chart_entities import (
ChartRegistry,
Expand All @@ -25,7 +24,7 @@
PsychroCurves,
)
from psychrochart.models.styles import CurveStyle, ZoneStyle
from psychrochart.util import mod_color
from psychrochart.util import convex_hull_graham_scan, mod_color


def _annotate_label(
Expand Down Expand Up @@ -427,33 +426,31 @@ def plot_annots_dbt_rh(ax: Axes, annots: ChartAnnots) -> dict[str, Artist]:
line_gid = make_item_gid("point", name=point.label or name)
reg_artist(line_gid, artist_point, annot_artists)

if ConvexHull is None or not annots.areas:
if not annots.areas:
return annot_artists

for convex_area in annots.areas:
int_points = np.array(
[annots.get_point_by_name(key) for key in convex_area.point_names]
)
raw_points = [
annots.get_point_by_name(key) for key in convex_area.point_names
]
try:
assert len(int_points) >= 3
hull = ConvexHull(int_points)
except (AssertionError, QhullError): # pragma: no cover
logging.error(f"QhullError with points: {int_points}")
continue
points_hull_x, points_hull_y = convex_hull_graham_scan(raw_points)
except (AssertionError, IndexError): # pragma: no cover
logging.error("Convex hull error with points: %s", raw_points)
return annot_artists

area_gid = make_item_gid(
"convexhull", name=",".join(convex_area.point_names)
)
for i, simplex in enumerate(hull.simplices):
[artist_contour] = ax.plot(
int_points[simplex, 0],
int_points[simplex, 1],
**convex_area.line_style,
)
reg_artist(area_gid + f"_s{i+1}", artist_contour, annot_artists)
[artist_contour] = ax.plot(
[*points_hull_x, points_hull_x[0]],
[*points_hull_y, points_hull_y[0]],
**convex_area.line_style,
)
reg_artist(area_gid + "_edge", artist_contour, annot_artists)
[artist_area] = ax.fill(
int_points[hull.vertices, 0],
int_points[hull.vertices, 1],
points_hull_x,
points_hull_y,
**convex_area.fill_style,
)
reg_artist(area_gid, artist_area, annot_artists)
Expand Down
5 changes: 2 additions & 3 deletions psychrochart/process_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
SetUnitSystem,
SI,
)
from scipy.interpolate import interp1d

from psychrochart.chartdata import (
get_rh_max_min_in_limits,
Expand All @@ -24,6 +23,7 @@
from psychrochart.chartzones import make_zone_curve
from psychrochart.models.config import ChartConfig, ChartLimits, DEFAULT_ZONES
from psychrochart.models.curves import PsychroChartModel
from psychrochart.util import Interp1D

spec_vol_vec = np.vectorize(psy.GetMoistAirVolume)

Expand Down Expand Up @@ -53,10 +53,9 @@ def _gen_interior_lines(config: ChartConfig, chart: PsychroChartModel) -> None:
# check if sat curve cuts x-axis with T > config.dbt_min
dbt_min_seen: float | None = None
if chart.saturation.y_data[0] < config.w_min:
temp_sat_interpolator = interp1d(
temp_sat_interpolator = Interp1D(
chart.saturation.y_data,
chart.saturation.x_data,
assume_sorted=True,
)
dbt_min_seen = temp_sat_interpolator(config.w_min)

Expand Down
82 changes: 82 additions & 0 deletions psychrochart/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,88 @@
TESTING_MODE = os.getenv("PYTEST_CURRENT_TEST") is not None


class Interp1D:
"""Simple 1D interpolation with extrapolation."""

def __init__(self, x: Sequence[float], y: Sequence[float]):
self.x = np.array(x)
self.y = np.array(y)

def __call__(self, x_new: float) -> float:
"""Linear interpolation with extrapolation."""
# Perform linear interpolation
for i in range(len(self.x) - 1):
if self.x[i] <= x_new <= self.x[i + 1]:
slope = (self.y[i + 1] - self.y[i]) / (
self.x[i + 1] - self.x[i]
)
return float(self.y[i] + slope * (x_new - self.x[i]))

# Extrapolation
assert x_new < self.x[0] or x_new > self.x[-1]
i = 1 if x_new < self.x[0] else -1
slope = (self.y[i] - self.y[i - 1]) / (self.x[i] - self.x[i - 1])
return float(self.y[i] + slope * (x_new - self.x[i]))


def orientation(
p: tuple[float, float],
q: tuple[float, float],
r: tuple[float, float],
) -> int:
"""
Function to find orientation of ordered triplet (p, q, r).
Returns:
0 : Colinear points
1 : Clockwise points
2 : Counterclockwise points
"""
val = (q[1] - p[1]) * (r[0] - q[0]) - (q[0] - p[0]) * (r[1] - q[1])
if val == 0: # pragma: no cover
return 0 # Colinear
elif val > 0:
return 1 # Clockwise
else:
return 2 # Counterclockwise


def convex_hull_graham_scan(
points: list[tuple[float, float]],
) -> tuple[list[float], list[float]]:
"""Function to compute the convex hull of a set of 2-D points."""
# If number of points is less than 3, convex hull is not possible
numpoints = len(points)
assert numpoints >= 3

# Find the leftmost point
leftp = min(points)

# Sort points based on polar angle with respect to the leftmost point
sorted_points = sorted(
[p for p in points if p != leftp],
key=lambda x: (
np.arctan2(x[1] - leftp[1], x[0] - leftp[0]),
x[0],
x[1],
),
)

# Initialize an empty stack to store points on the convex hull
# Start from the leftmost point and proceed to build the convex hull
hull = [leftp, sorted_points[0]]
for i in range(1, len(sorted_points)):
while (
len(hull) > 1
and orientation(hull[-2], hull[-1], sorted_points[i]) != 2
):
hull.pop()
hull.append(sorted_points[i])

hull_x, hull_y = list(zip(*hull))
assert len(hull_x) >= 2
return list(hull_x), list(hull_y)


def _iter_solver(
initial_value: np.ndarray,
objective_value: np.ndarray,
Expand Down
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ log_date_format = "%Y-%m-%d %H:%M:%S"

[tool.poetry]
name = "psychrochart"
version = "0.10.0"
version = "0.11.0"
description = "A python 3 library to make psychrometric charts and overlay information on them"
authors = ["Eugenio Panadero <[email protected]>"]
packages = [
Expand Down Expand Up @@ -92,7 +92,6 @@ include = ["CHANGELOG.md"]
[tool.poetry.dependencies]
python = ">=3.10,<3.13"
matplotlib = ">=3.7"
scipy = ">=1.10"
psychrolib = ">=2.5"
pydantic = ">=2.3.0"
python-slugify = ">=8.0.1"
Expand Down
Loading

0 comments on commit d4d1026

Please sign in to comment.