Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
9e3af3b
configure specific warnings
ssolson May 19, 2025
6d39f6d
np.trapz => np.trapezoid
ssolson May 20, 2025
fe4312e
FutureWarning: ChainedAssignmentError: :
ssolson May 20, 2025
1be55a6
FutureWarning: The behavior of 'to_datetime' with 'unit' when parsing…
ssolson May 20, 2025
b898817
PendingDeprecationWarning: vert: bool will be deprecate
ssolson May 20, 2025
421ed38
FutureWarning: Passing literal html to 'read_html' is deprecated
ssolson May 20, 2025
b74df78
FutureWarning: Support for nested sequences for 'parse_dates' in pd.r…
ssolson May 20, 2025
732d7eb
FutureWarning: errors='ignore' is deprecated and will raise in a fut…
ssolson May 20, 2025
ff0b731
FutureWarning: Support for nested sequences for 'parse_dates' in pd.…
ssolson May 20, 2025
5b91d90
FutureWarning: Downcasting behavior in `replace` is deprecated and wi…
ssolson May 20, 2025
7ee2c99
test_cdip.py only--DeprecationWarning: datetime.datetime.utcfromtimes…
akeeste Jul 29, 2025
d974c5c
Merge branch 'develop' of https://github.com/MHKiT-Software/MHKiT-Pyt…
akeeste Jul 29, 2025
ec312f1
Dolfyn.time--DeprecationWarning: datetime.datetime.utcfromtimestamp()…
akeeste Jul 29, 2025
fc2ca67
pd.to_datetime - fix edge case when string is already datetime format…
akeeste Aug 12, 2025
2293b93
parse NDBC date times from csv with separate headers
akeeste Aug 12, 2025
f722dff
fix a couple warnings in TRTS example
akeeste Aug 12, 2025
c04d121
increase TRTS example time
akeeste Aug 12, 2025
af8d858
Revert "increase TRTS example time"
akeeste Aug 13, 2025
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
418 changes: 179 additions & 239 deletions examples/ADCP_Delft3D_TRTS_example.ipynb

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions mhkit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@

_rmc()

# Ignore future warnings
_warn.simplefilter(action="ignore", category=FutureWarning)
# Use targeted warning configuration
from mhkit.warnings import configure_warnings

configure_warnings()

__version__ = "v0.9.0"

Expand Down
2 changes: 1 addition & 1 deletion mhkit/acoustics/sel.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def sound_exposure_level(
# from weighted mean square values
band = spsd.sel(freq=slice(fmin, fmax))
w = w.sel(freq=slice(fmin, fmax))
exposure = np.trapz(band * w, band["freq"])
exposure = np.trapezoid(band * w, band["freq"])

# Sound exposure level (L_{E,p}) = (L_{p,rms} + 10log10(t))
sel = 10 * np.log10(exposure / reference) + 10 * np.log10(
Expand Down
4 changes: 2 additions & 2 deletions mhkit/acoustics/spl.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def sound_pressure_level(
# Mean square sound pressure in a specified frequency band from mean square values
band = spsd.sel(freq=slice(fmin, fmax))
freqs = band["freq"]
pressure_squared = np.trapz(band, freqs)
pressure_squared = np.trapezoid(band, freqs)

# Mean square sound pressure level
mspl = 10 * np.log10(pressure_squared / reference)
Expand Down Expand Up @@ -197,7 +197,7 @@ def _band_sound_pressure_level(
else:
spsd_slc = spsd.sel(freq=slice(*band_range))

pressure_squared.loc[{"freq_bins": key}] = np.trapz(spsd_slc, x)
pressure_squared.loc[{"freq_bins": key}] = np.trapezoid(spsd_slc, x)

# Mean square sound pressure level in dB rel 1 uPa
mspl = 10 * np.log10(pressure_squared / reference)
Expand Down
4 changes: 2 additions & 2 deletions mhkit/dolfyn/adp/discharge.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ def _calc_discharge(vel, x, depth, surface_zoff=None):
sign = -1
else:
sign = 1
return sign * np.trapz(np.trapz(vel, depth, axis=0), x)
return sign * np.trapezoid(np.trapezoid(vel, depth, axis=0), x)

# Extrapolate to bed
vel = ds["vel"].copy()
Expand Down Expand Up @@ -265,7 +265,7 @@ def _calc_discharge(vel, x, depth, surface_zoff=None):
(0.5 * rho * speed**3).mean().item()
) # kg/m^3 * m^3/s^3 = kg/s^3 = W/m^2
hydraulic_depth = abs(
np.trapz((water_depth - surface_offset)[_xinds], xy[0][_xinds])
np.trapezoid((water_depth - surface_offset)[_xinds], xy[0][_xinds])
) / (
xy[0][_xinds].max() - xy[0][_xinds].min()
) # area / surface-width
Expand Down
4 changes: 2 additions & 2 deletions mhkit/dolfyn/adv/turbulence.py
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ def _integral_TE01(self, I_tke, theta):
x = np.arange(-20, 20, 1e-2) # I think this is a long enough range.
out = np.empty_like(I_tke.flatten())
for i, (b, t) in enumerate(zip(I_tke.flatten(), theta.flatten())):
out[i] = np.trapz(
out[i] = np.trapezoid(
cbrt(x**2 - 2 / b * np.cos(t) * x + b ** (-2)) * np.exp(-0.5 * x**2),
x,
)
Expand Down Expand Up @@ -660,7 +660,7 @@ def integral_length_scales(self, a_cov, U_mag, fs=None):
T_int = np.zeros(acov.shape[:2])
for i in range(3):
for t in range(a_cov["time"].size):
T_int[i, t] = np.trapz(acov[i, t][: zero_crossing[i, t]], dx=1 / fs)
T_int[i, t] = np.trapezoid(acov[i, t][: zero_crossing[i, t]], dx=1 / fs)

L_int = U_mag.values * T_int

Expand Down
17 changes: 8 additions & 9 deletions mhkit/dolfyn/time.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,18 +123,17 @@ def epoch2date(ep_time, offset_hr=0, to_str=False):
elif not isinstance(ep_time, (np.ndarray, list)):
ep_time = [ep_time]

######### IMPORTANT #########
# Note the use of `utcfromtimestamp` here, rather than `fromtimestamp`
# This is CRITICAL! See the difference between those functions here:
# https://docs.python.org/3/library/datetime.html#datetime.datetime.fromtimestamp
# Long story short: `fromtimestamp` used system-specific timezone
# info to calculate the datetime object, but returns a
# timezone-agnostic object.
if offset_hr != 0:
delta = timedelta(hours=offset_hr)
time = [datetime.utcfromtimestamp(t) + delta for t in ep_time]
time = [
datetime.fromtimestamp(t, timezone.utc).replace(tzinfo=None) + delta
for t in ep_time
]
else:
time = [datetime.utcfromtimestamp(t) for t in ep_time]
time = [
datetime.fromtimestamp(t, timezone.utc).replace(tzinfo=None)
for t in ep_time
]
Comment on lines -126 to +136
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jmcvey3 can you review this solution to using datetime.utcfromtimestamp()? I don't know the timeline on deprecation but we'll have to move away from this eventually

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@akeeste Yeah we discovered a long time ago that using the "fromtimestamp" function returns a different result depending on what timezone your local machine is running in. Using "utcfromtimestamp" was the only way to guarantee that the epoch time in seconds (what those return) is always the same value.

I think your solution here will work

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks @jmcvey3. I saw the same behavior when fromtimestamp is used without a timezone argument. Using it with timezone.utc however gives an identical datetime as utcfromtimestamp but with timezone metadata. I scrub tzinfo from the output to match exactly what utcfromtimestamp returns.


if to_str:
time = date2str(time)
Expand Down
6 changes: 3 additions & 3 deletions mhkit/river/io/d3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@ def variable_interpolation(

if len(idx[0]):
for i in idx[0]:
transformed_data[var][i] = interp.griddata(
transformed_data.loc[i, var] = interp.griddata(
data_raw[var][["x", "y", "waterdepth"]],
data_raw[var][var],
[points["x"][i], points["y"][i], points["waterdepth"][i]],
Expand Down Expand Up @@ -815,7 +815,7 @@ def turbulent_intensity(

if len(idx[0]):
for i in idx[0]:
turbulent_data[var][i] = interp.griddata(
turbulent_data.loc[i, var] = interp.griddata(
turbulent_data_raw[var][["x", "y", "waterdepth"]],
turbulent_data_raw[var][var],
[points["x"][i], points["y"][i], points["waterdepth"][i]],
Expand All @@ -837,7 +837,7 @@ def turbulent_intensity(
zero_ind = neg_index[0][zero_bool]
non_zero_ind = neg_index[0][~zero_bool]
turbulent_data.loc[zero_ind, "turkin1"] = np.zeros(len(zero_ind))
turbulent_data.loc[non_zero_ind, "turkin1"] = [np.nan] * len(non_zero_ind)
turbulent_data.loc[non_zero_ind, "turkin1"] = np.nan

turbulent_data["turbulent_intensity"] = (
np.sqrt(2 / 3 * turbulent_data["turkin1"]) / u_mag * 100
Expand Down
2 changes: 1 addition & 1 deletion mhkit/river/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ def energy_produced(
# Sample range for pdf
x = np.linspace(edges.min(), edges.max(), 1000)
# Calculate the expected value of power
expected_power = np.trapz(x * hist_dist.pdf(x), x=x)
expected_power = np.trapezoid(x * hist_dist.pdf(x), x=x)
# Note: Built-in Expected Value method often throws warning
# EV = hist_dist.expect(lb=edges.min(), ub=edges.max())
# Calculate energy
Expand Down
4 changes: 2 additions & 2 deletions mhkit/tests/wave/io/test_cdip.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ def test_dates_to_timestamp(self):
self.test_nc, start_date=start_date, end_date=end_date
)

start_dt = datetime.utcfromtimestamp(start_stamp).replace(tzinfo=pytz.UTC)
end_dt = datetime.utcfromtimestamp(end_stamp).replace(tzinfo=pytz.UTC)
start_dt = datetime.fromtimestamp(start_stamp, pytz.UTC)
end_dt = datetime.fromtimestamp(end_stamp, pytz.UTC)

self.assertEqual(start_dt, start_date)
self.assertEqual(end_dt, end_date)
Expand Down
9 changes: 7 additions & 2 deletions mhkit/tidal/io/noaa.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,12 @@ def _xml_to_dataframe(response: requests.Response) -> tuple[pd.DataFrame, dict]:
[pd.DataFrame(obs.attrib, index=[0]) for obs in data], ignore_index=True
)

df["t"] = pd.to_datetime(df.t)
try:
df["t"] = pd.to_datetime(pd.to_numeric(df.t), unit="ms")
except ValueError:
# Don't convert df.t to numeric if its a datetime formatted string
df["t"] = pd.to_datetime(df.t)

df = df.set_index("t")
df.drop_duplicates(inplace=True)

Expand Down Expand Up @@ -547,7 +552,7 @@ def read_noaa_json(filename: str, to_pandas: bool = True) -> tuple[pd.DataFrame,
# Remainder is DataFrame
data = pd.DataFrame.from_dict(json_data)
# Convert from epoch to date time
data.index = pd.to_datetime(data.index, unit="ms")
data.index = pd.to_datetime(pd.to_numeric(data.index), unit="ms")

except ValueError: # using cache.py format
if "metadata" in json_data:
Expand Down
26 changes: 26 additions & 0 deletions mhkit/warnings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import warnings

# Only suppress specific, reviewed warnings here.
# Example: Suppress a known FutureWarning from a specific dependency
# warnings.filterwarnings(
# "ignore",
# category=FutureWarning,
# module=r"^some_dependency\.module$",
# message=r"This is a known harmless future warning."
# )

# Add more targeted filters as needed, after review.


def configure_warnings():
"""
Call this function at package import to apply MHKiT's targeted warning filters.
"""
# Example: Uncomment and edit below to suppress a specific warning
# warnings.filterwarnings(
# "ignore",
# category=FutureWarning,
# module=r"^some_dependency\.module$",
# message=r"This is a known harmless future warning."
# )
pass
5 changes: 4 additions & 1 deletion mhkit/wave/graphics.py
Original file line number Diff line number Diff line change
Expand Up @@ -839,7 +839,10 @@ def plot_boxplot(Hs, buoy_title=None):
bp2 = plt.subplot(gs[1, :])
meanprops = dict(linewidth=2.5, marker="|", markersize=25)
bp2_example = bp2.boxplot(
bp_sample2, vert=False, flierprops=flierprops, medianprops=medianprops
bp_sample2,
orientation="horizontal",
flierprops=flierprops,
medianprops=medianprops,
)
sample_mean = 2.3
bp2.scatter(sample_mean, 1, marker="|", color="g", linewidths=1.0, s=200)
Expand Down
28 changes: 20 additions & 8 deletions mhkit/wave/io/ndbc.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
from collections import OrderedDict as _OrderedDict
from collections import defaultdict as _defaultdict
from io import BytesIO
from io import BytesIO, StringIO
import re
import requests
import zlib
Expand All @@ -19,6 +19,9 @@
convert_nested_dict_and_pandas,
)

# Set pandas option to opt-in to future behavior
pd.set_option("future.no_silent_downcasting", True)


def read_file(file_name, missing_values=["MM", 9999, 999, 99], to_pandas=True):
"""
Expand Down Expand Up @@ -102,21 +105,25 @@ def read_file(file_name, missing_values=["MM", 9999, 999, 99], to_pandas=True):
header=None,
names=header,
comment="#",
parse_dates=[parse_vals],
)
# If first line is not commented, then the first row can be used as header
else:
data = pd.read_csv(
file_name, sep="\\s+", header=0, comment="#", parse_dates=[parse_vals]
)
data = pd.read_csv(file_name, sep="\\s+", header=0, comment="#")

# Convert index to datetime
date_column = "_".join(parse_vals)
data[date_column] = (
data[parse_vals].apply(lambda val: val.astype("string")).agg(" ".join, axis=1)
)

data["Time"] = pd.to_datetime(data[date_column], format=date_format)
data.index = data["Time"].values

# Remove date columns
del data[date_column]
del data["Time"]
for val in parse_vals:
del data[val]

# If there was a row of units, convert to dictionary
if units_exist:
Expand All @@ -126,7 +133,11 @@ def read_file(file_name, missing_values=["MM", 9999, 999, 99], to_pandas=True):

# Convert columns to numeric data if possible, otherwise leave as string
for column in data:
data[column] = pd.to_numeric(data[column], errors="ignore")
try:
data[column] = pd.to_numeric(data[column])
except (ValueError, TypeError):
# Keep as string if conversion fails
pass

# Convert column names to float if possible (handles frequency headers)
# if there is non-numeric name, just leave all as strings.
Expand All @@ -136,7 +147,8 @@ def read_file(file_name, missing_values=["MM", 9999, 999, 99], to_pandas=True):
data.columns = data.columns

# Replace indicated missing values with nan
data.replace(missing_values, np.nan, inplace=True)
data = data.replace(missing_values, np.nan)
data = data.infer_objects(copy=False)

if not to_pandas:
data = convert_to_dataset(data)
Expand Down Expand Up @@ -234,7 +246,7 @@ def available_data(
msg = f"request.get({ndbc_data}) failed by returning code of {response.status_code}"
raise Exception(msg)

filenames = pd.read_html(response.text)[0].Name.dropna()
filenames = pd.read_html(StringIO(response.text))[0].Name.dropna()
buoys = _parse_filenames(parameter, filenames)

available_data = buoys.copy(deep=True)
Expand Down
Loading