From 74e961ffaa3c3ea0b1b7e626ad367e5e4a309ed9 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Sun, 8 Sep 2024 12:39:58 +0200 Subject: [PATCH] use dask.array functions instead of numpy (#367) * use dask.array functions instead of numpy * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- RELEASE_NOTES.rst | 6 ++++-- atlite/convert.py | 23 ++++++++++++----------- atlite/csp.py | 5 +++-- atlite/datasets/era5.py | 5 +++-- atlite/pv/irradiation.py | 10 ++++------ atlite/pv/orientation.py | 33 +++++++++++++++++---------------- atlite/pv/solar_position.py | 19 ++++++++----------- atlite/resource.py | 5 +++-- 8 files changed, 54 insertions(+), 52 deletions(-) diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 030cf46e..b73463f2 100755 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -8,8 +8,10 @@ Release Notes ############# -.. Upcoming Release -.. ================ +Upcoming Release +================ + +* Use ``dask.array`` functions in favour of ``numpy`` functions. Version 0.2.14 ============== diff --git a/atlite/convert.py b/atlite/convert.py index 97e354b8..89e7891a 100644 --- a/atlite/convert.py +++ b/atlite/convert.py @@ -17,7 +17,9 @@ import pandas as pd import xarray as xr from dask import compute, delayed +from dask.array import absolute, arccos, cos, maximum, mod, radians, sin, sqrt from dask.diagnostics import ProgressBar +from numpy import pi from scipy.sparse import csr_matrix logger = logging.getLogger(__name__) @@ -1005,23 +1007,23 @@ def convert_line_rating( k = ( 2.424e-2 + 7.477e-5 * (Tfilm - T0) - 4.407e-9 * (Tfilm - T0) ** 2 ) # thermal conductivity - anglediff = ds["wnd_azimuth"] - np.deg2rad(psi) - Phi = np.abs(np.mod(anglediff + np.pi / 2, np.pi) - np.pi / 2) + anglediff = ds["wnd_azimuth"] - radians(psi) + Phi = absolute(mod(anglediff + pi / 2, pi) - pi / 2) K = ( - 1.194 - np.cos(Phi) + 0.194 * np.cos(2 * Phi) + 0.368 * np.sin(2 * Phi) + 1.194 - cos(Phi) + 0.194 * cos(2 * Phi) + 0.368 * sin(2 * Phi) ) # wind direction factor Tdiff = Ts - Ta qcf1 = K * (1.01 + 1.347 * reynold**0.52) * k * Tdiff # (3a) in [1] qcf2 = K * 0.754 * reynold**0.6 * k * Tdiff # (3b) in [1] - qcf = np.maximum(qcf1, qcf2) + qcf = maximum(qcf1, qcf2) # natural convection - qcn = 3.645 * np.sqrt(rho) * D**0.75 * Tdiff**1.25 + qcn = 3.645 * sqrt(rho) * D**0.75 * Tdiff**1.25 # convection loss is the max between forced and natural - qc = np.maximum(qcf, qcn) + qc = maximum(qcf, qcn) # 2. Radiated Loss qr = 17.8 * D * epsilon * ((Ts / 100) ** 4 - (Ta / 100) ** 4) @@ -1035,14 +1037,13 @@ def convert_line_rating( solar_position = Position(ds["solar_altitude"], ds["solar_azimuth"]) else: solar_position = SolarPosition(ds) - Phi_s = np.arccos( - np.cos(solar_position.altitude) - * np.cos((solar_position.azimuth) - np.deg2rad(psi)) + Phi_s = arccos( + cos(solar_position.altitude) * cos((solar_position.azimuth) - radians(psi)) ) - qs = alpha * Q * A * np.sin(Phi_s) + qs = alpha * Q * A * sin(Phi_s) - Imax = np.sqrt((qc + qr - qs) / R) + Imax = sqrt((qc + qr - qs) / R) return Imax.min("spatial") if isinstance(Imax, xr.DataArray) else Imax diff --git a/atlite/csp.py b/atlite/csp.py index 42111b65..dab9461e 100644 --- a/atlite/csp.py +++ b/atlite/csp.py @@ -10,6 +10,7 @@ import logging import numpy as np +from dask.array import radians, sin from atlite.pv.solar_position import SolarPosition @@ -42,7 +43,7 @@ def calculate_dni(ds, solar_position=None, altitude_threshold=3.75): solar_position = SolarPosition(ds) # solar altitude expected in rad, convert degrees (easier to specifcy) to match - altitude_threshold = np.deg2rad(altitude_threshold) + altitude_threshold = radians(altitude_threshold) # Sanitation of altitude values: # Prevent high calculated DNI values during low solar altitudes (sunset / dawn) @@ -53,6 +54,6 @@ def calculate_dni(ds, solar_position=None, altitude_threshold=3.75): # Calculate DNI and remove NaNs introduced during altitude sanitation # DNI is determined either by dividing by cos(azimuth) or sin(altitude) - dni = ds["influx_direct"] / np.sin(altitude) + dni = ds["influx_direct"] / sin(altitude) return dni diff --git a/atlite/datasets/era5.py b/atlite/datasets/era5.py index 4c1f04d4..63e72682 100644 --- a/atlite/datasets/era5.py +++ b/atlite/datasets/era5.py @@ -20,6 +20,7 @@ import numpy as np import pandas as pd import xarray as xr +from dask.array import arctan2, sqrt from dask import compute, delayed from numpy import atleast_1d @@ -135,11 +136,11 @@ def get_data_wind(retrieval_params): ) ds = _rename_and_clean_coords(ds) - ds["wnd100m"] = np.sqrt(ds["u100"] ** 2 + ds["v100"] ** 2).assign_attrs( + ds["wnd100m"] = sqrt(ds["u100"] ** 2 + ds["v100"] ** 2).assign_attrs( units=ds["u100"].attrs["units"], long_name="100 metre wind speed" ) # span the whole circle: 0 is north, π/2 is east, -π is south, 3π/2 is west - azimuth = np.arctan2(ds["u100"], ds["v100"]) + azimuth = arctan2(ds["u100"], ds["v100"]) ds["wnd_azimuth"] = azimuth.where(azimuth >= 0, azimuth + 2 * np.pi) ds = ds.drop_vars(["u100", "v100"]) ds = ds.rename({"fsr": "roughness"}) diff --git a/atlite/pv/irradiation.py b/atlite/pv/irradiation.py index 4ae0e6e8..f2a5980e 100644 --- a/atlite/pv/irradiation.py +++ b/atlite/pv/irradiation.py @@ -7,9 +7,7 @@ import logging import numpy as np -import pandas as pd -import xarray as xr -from numpy import cos, deg2rad, fmax, fmin, sin, sqrt +from dask.array import cos, fmax, fmin, radians, sin, sqrt logger = logging.getLogger(__name__) @@ -73,7 +71,7 @@ def DiffuseHorizontalIrrad(ds, solar_position, clearsky_model, influx): ) # Set diffuse fraction to one when the sun isn't up - # fraction = fraction.where(sinaltitude >= sin(deg2rad(threshold))).fillna(1.0) + # fraction = fraction.where(sinaltitude >= sin(radians(threshold))).fillna(1.0) # fraction = fraction.rename('fraction index') return (influx * fraction).rename("diffuse horizontal") @@ -110,7 +108,7 @@ def TiltedDiffuseIrrad(ds, solar_position, surface_orientation, direct, diffuse) # fixup: clip all negative values (unclear why it gets negative) # note: REatlas does not do the fixup if logger.isEnabledFor(logging.WARNING): - if ((diffuse_t < 0.0) & (sinaltitude > sin(deg2rad(1.0)))).any(): + if ((diffuse_t < 0.0) & (sinaltitude > sin(radians(1.0)))).any(): logger.warning( "diffuse_t exhibits negative values above altitude threshold." ) @@ -254,7 +252,7 @@ def clip(influx, influx_max): # values, leading to big overall errors from the 1/sinaltitude factor. # => Suppress irradiation below solar altitudes of 1 deg. - cap_alt = solar_position["altitude"] < deg2rad(altitude_threshold) + cap_alt = solar_position["altitude"] < radians(altitude_threshold) result = result.where(~(cap_alt | (direct + diffuse <= 0.01)), 0) result.attrs["units"] = "W m**-2" diff --git a/atlite/pv/orientation.py b/atlite/pv/orientation.py index 4e680e81..95140223 100644 --- a/atlite/pv/orientation.py +++ b/atlite/pv/orientation.py @@ -8,7 +8,8 @@ import numpy as np import xarray as xr -from numpy import cos, deg2rad, pi, sin +from dask.array import arccos, arcsin, arctan, cos, logical_and, radians, sin +from numpy import pi def get_orientation(name, **params): @@ -50,14 +51,14 @@ def make_latitude_optimal(): def latitude_optimal(lon, lat, solar_position): slope = np.empty_like(lat.values) - below_25 = np.abs(lat.values) <= deg2rad(25) - below_50 = np.abs(lat.values) <= deg2rad(50) + below_25 = np.abs(lat.values) <= np.radians(25) + below_50 = np.abs(lat.values) <= np.radians(50) slope[below_25] = 0.87 * np.abs(lat.values[below_25]) slope[~below_25 & below_50] = 0.76 * np.abs( lat.values[~below_25 & below_50] - ) + deg2rad(0.31) - slope[~below_50] = np.deg2rad(40.0) + ) + np.radians(0.31) + slope[~below_50] = np.radians(40.0) # South orientation for panels on northern hemisphere and vice versa azimuth = np.where(lat.values < 0, 0, pi) @@ -70,8 +71,8 @@ def latitude_optimal(lon, lat, solar_position): def make_constant(slope, azimuth): - slope = deg2rad(slope) - azimuth = deg2rad(azimuth) + slope = radians(slope) + azimuth = radians(azimuth) def constant(lon, lat, solar_position): return dict(slope=slope, azimuth=azimuth) @@ -80,7 +81,7 @@ def constant(lon, lat, solar_position): def make_latitude(azimuth=180): - azimuth = deg2rad(azimuth) + azimuth = radians(azimuth) def latitude(lon, lat, solar_position): return dict(slope=lat, azimuth=azimuth) @@ -100,8 +101,8 @@ def SurfaceOrientation(ds, solar_position, orientation, tracking=None): tracking of one-axis trackers. No. NREL/TP-6A20-58891. National Renewable Energy Lab.(NREL), Golden, CO (United States), 2013. """ - lon = deg2rad(ds["lon"]) - lat = deg2rad(ds["lat"]) + lon = radians(ds["lon"]) + lat = radians(ds["lat"]) orientation = orientation(lon, lat, solar_position) surface_slope = orientation["slope"] @@ -119,11 +120,11 @@ def SurfaceOrientation(ds, solar_position, orientation, tracking=None): axis_azimuth = orientation[ "azimuth" ] # here orientation['azimuth'] refers to the azimuth of the tracker axis. - rotation = np.arctan( + rotation = arctan( (cos(sun_altitude) / sin(sun_altitude)) * sin(sun_azimuth - axis_azimuth) ) surface_slope = abs(rotation) - surface_azimuth = axis_azimuth + np.arcsin( + surface_azimuth = axis_azimuth + arcsin( sin(rotation / sin(surface_slope)) ) # the 2nd part yields +/-1 and determines if the panel is facing east or west cosincidence = cos(surface_slope) * sin(sun_altitude) + sin( @@ -135,7 +136,7 @@ def SurfaceOrientation(ds, solar_position, orientation, tracking=None): "slope" ] # here orientation['slope'] refers to the tilt of the tracker axis. - rotation = np.arctan( + rotation = arctan( (cos(sun_altitude) * sin(sun_azimuth - surface_azimuth)) / ( cos(sun_altitude) * cos(sun_azimuth - surface_azimuth) * sin(axis_tilt) @@ -143,7 +144,7 @@ def SurfaceOrientation(ds, solar_position, orientation, tracking=None): ) ) - surface_slope = np.arccos(cos(rotation) * cos(axis_tilt)) + surface_slope = arccos(cos(rotation) * cos(axis_tilt)) azimuth_difference = sun_azimuth - surface_azimuth azimuth_difference = np.where( @@ -153,12 +154,12 @@ def SurfaceOrientation(ds, solar_position, orientation, tracking=None): azimuth_difference < -pi, 2 * pi + azimuth_difference, azimuth_difference ) rotation = np.where( - np.logical_and(rotation < 0, azimuth_difference > 0), + logical_and(rotation < 0, azimuth_difference > 0), rotation + pi, rotation, ) rotation = np.where( - np.logical_and(rotation > 0, azimuth_difference < 0), + logical_and(rotation > 0, azimuth_difference < 0), rotation - pi, rotation, ) diff --git a/atlite/pv/solar_position.py b/atlite/pv/solar_position.py index d28ba31a..234ff35e 100644 --- a/atlite/pv/solar_position.py +++ b/atlite/pv/solar_position.py @@ -8,7 +8,8 @@ import pandas as pd import xarray as xr -from numpy import arccos, arcsin, arctan2, cos, deg2rad, pi, sin +from dask.array import arccos, arcsin, arctan2, cos, radians, sin +from numpy import pi def SolarPosition(ds, time_shift="0H"): @@ -57,11 +58,7 @@ def SolarPosition(ds, time_shift="0H"): } if rvs.issubset(set(ds.data_vars)): - solar_position = ds[rvs] - solar_position = solar_position.rename( - {v: v.replace("solar_", "") for v in rvs} - ) - return solar_position + return ds[rvs].rename({v: v.replace("solar_", "") for v in rvs}) warn( """The calculation method and handling of solar position variables will change. @@ -86,20 +83,20 @@ def SolarPosition(ds, time_shift="0H"): minute = minute.chunk(chunks) L = 280.460 + 0.9856474 * n # mean longitude (deg) - g = deg2rad(357.528 + 0.9856003 * n) # mean anomaly (rad) - l = deg2rad(L + 1.915 * sin(g) + 0.020 * sin(2 * g)) # ecliptic long. (rad) - ep = deg2rad(23.439 - 4e-7 * n) # obliquity of the ecliptic (rad) + g = radians(357.528 + 0.9856003 * n) # mean anomaly (rad) + l = radians(L + 1.915 * sin(g) + 0.020 * sin(2 * g)) # ecliptic long. (rad) + ep = radians(23.439 - 4e-7 * n) # obliquity of the ecliptic (rad) ra = arctan2(cos(ep) * sin(l), cos(l)) # right ascencion (rad) lmst = (6.697375 + (hour + minute / 60.0) + 0.0657098242 * n) * 15.0 + ds[ "lon" ] # local mean sidereal time (deg) - h = (deg2rad(lmst) - ra + pi) % (2 * pi) - pi # hour angle (rad) + h = (radians(lmst) - ra + pi) % (2 * pi) - pi # hour angle (rad) dec = arcsin(sin(ep) * sin(l)) # declination (rad) # alt and az from [2] - lat = deg2rad(ds["lat"]) + lat = radians(ds["lat"]) # Clip before arcsin to prevent values < -1. from rounding errors; can # cause NaNs later alt = arcsin( diff --git a/atlite/resource.py b/atlite/resource.py index 72943277..825b7358 100644 --- a/atlite/resource.py +++ b/atlite/resource.py @@ -20,6 +20,7 @@ import pkg_resources import requests import yaml +from dask.array import radians from scipy.signal import fftconvolve from atlite.utils import arrowdict @@ -170,8 +171,8 @@ def get_cspinstallationconfig(installation): da = da.rename({"azimuth": "azimuth [deg]", "altitude": "altitude [deg]"}) da = da.assign_coords( { - "altitude": np.deg2rad(da["altitude [deg]"]), - "azimuth": np.deg2rad(da["azimuth [deg]"]), + "altitude": radians(da["altitude [deg]"]), + "azimuth": radians(da["azimuth [deg]"]), } ) da = da.swap_dims({"altitude [deg]": "altitude", "azimuth [deg]": "azimuth"})