Skip to content

Commit

Permalink
merge: merge branch feat-ST127-refactor-project with develop (#8)
Browse files Browse the repository at this point in the history
* refactor: minor docstring fixes

Refs: ST-2, ST-127

* refactor: update README

Refs: ST-2, ST-127

* refactor: minor efficiency fixes

Refs: ST-2, ST-5, ST-7, ST-127

* refactor: minor updates to test logic

Refs: ST-3, ST-127

* refactor: improve code legibility in main

Refs: ST-26, ST-127

* fix: remove unsupported type hinting

Refs: ST-3, ST-127

Refs: ST-1, ST-127
  • Loading branch information
gampnico authored May 25, 2023
1 parent 856bcd9 commit f07b6c6
Show file tree
Hide file tree
Showing 12 changed files with 52 additions and 59 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ limitations under the License. -->

[![Pytest and Flake8](https://github.com/gampnico/scintillometry/actions/workflows/python-app.yml/badge.svg?branch=main)](https://github.com/gampnico/scintillometry/actions/workflows/python-app.yml)

Analyse data & 2D flux footprints from Scintec's BLS scintillometers.
Development branch: [![Pytest and Linting](https://github.com/gampnico/scintillometry/actions/workflows/python-app.yml/badge.svg?branch=develop)](https://github.com/gampnico/scintillometry/actions/workflows/python-app.yml)

This repository is a complete rewrite of gampnico/ss19-feldkurs. If you have any existing forks or local clones, **please delete them**. The legacy code no longer works. No user features will be lost, but rewriting may take some time. Contributions are always welcome.
Analyse data & 2D flux footprints from Scintec's BLS scintillometers.

This package started life as part of a field course. If you spot any missing citations or licenses please [open an issue](https://github.com/gampnico/scintillometry/issues).
This project started life as part of a field course. If you spot any missing citations or licences please [open an issue](https://github.com/gampnico/scintillometry/issues).

Comprehensive documentation is available [via ReadTheDocs](https://scintillometry.readthedocs.io/en/latest/).

Expand Down
12 changes: 4 additions & 8 deletions src/scintillometry/backend/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@ class AtmosConstants(object):
BLS type.
lamda (float): BLS wavelength, |lamda| [nm].
lamda_error (float): BLS wavelength error, [nm].
m1_opt (float): Needed for |A_T| and |A_q|, from Owens (1967).
[#owens1967]_
m2_opt (float): Needed for |A_T| and |A_q|, from Owens (1967).
[#owens1967]_
at_opt (float): |A_T| coefficient for 880 nm & typical
atmospheric conditions, from Ward et al. (2013).
Expand All @@ -60,7 +56,7 @@ class AtmosConstants(object):
20°C [|Jkg^-1|].
r_dry (float): Specific gas constant for dry air,
|R_dry| [|JK^-1| |kg^-1|].
r_vapour (float): Specific gas contstant for water vapour,
r_vapour (float): Specific gas constant for water vapour,
|R_v| [|JK^-1| |kg^-1|].
ratio_rmm (float): Ratio of molecular masses of water vapour and
dry air i.e. ratio of gas constants |epsilon|.
Expand Down Expand Up @@ -149,7 +145,7 @@ def convert_pressure(self, pressure, base=True):
pressure (Union[pd.DataFrame, pd.Series]): Pressure
measurements |P| in pascals [Pa], hectopascals [hPa], or
bars [bar].
base (bool): If True, converts to pascals [Pa]. Otherwise
base (bool): If True, converts to pascals [Pa]. Otherwise,
converts to hectopascals [hPa]. Default True.
Returns:
Expand Down Expand Up @@ -186,12 +182,12 @@ def convert_temperature(self, temperature, base=True):
- T [°C] < 130 °C
This method should therefore only be used on pre-processed data
as a *convenience*. By default converts to kelvins.
as a *convenience*. By default, converts to kelvins.
Args:
temperature (Union[pd.DataFrame, pd.Series]): Temperature
measurements |T| in kelvins [K] or Celsius [°C].
base (bool): If True, converts to kelvins [K]. Otherwise
base (bool): If True, converts to kelvins [K]. Otherwise,
converts to Celsius [°C]. Default True.
Returns:
Expand Down
6 changes: 3 additions & 3 deletions src/scintillometry/backend/constructions.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ def get_mixing_ratio(self, wv_pressure, d_pressure):

# (wv_pressure * self.r_dry) / (d_pressure * self.r_vapour)
m_ratio = (wv_pressure.multiply(self.constants.r_dry)).divide(
(d_pressure).multiply(self.constants.r_vapour)
d_pressure.multiply(self.constants.r_vapour)
)

return m_ratio
Expand Down Expand Up @@ -236,7 +236,7 @@ def get_reduced_pressure(self, station_pressure, virtual_temperature, elevation)
elevation (float): Station elevation, |z_stn| [m].
Returns:
pd.DataDrame: Derived vertical measurements for mean
pd.DataFrame: Derived vertical measurements for mean
sea-level pressure, |P_MSL| [Pa].
"""

Expand Down Expand Up @@ -495,7 +495,7 @@ def get_gradient(self, data, method="backward"):
:math:`\\partial T/\\partial z` for heights |z| with time
index t.
By default the gradient is calculated using a 1-D
By default, the gradient is calculated using a 1-D
centred-differencing scheme for non-uniform meshes, since
vertical measurements are rarely made at uniform intervals.
Expand Down
2 changes: 2 additions & 0 deletions src/scintillometry/backend/iterations.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,8 @@ def most_iteration(self, dataframe, zm_bls, stable_flag, most_coeffs):
stable_flag (bool): Stability conditions. If true, assumes
stable conditions, otherwise assumes unstable
conditions.
most_coeffs (list): MOST coefficients for unstable and
stable conditions.
Returns:
pd.DataFrame: Dataframe with additional columns for Obukhov
Expand Down
10 changes: 5 additions & 5 deletions src/scintillometry/backend/transects.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def get_b_value(self, stability_name):
Returns:
float: Constant "b" accounting for height dependence of
|Cn2|. Values of "b" are from Hartogenesis et al.
|Cn2|. Values of "b" are from Hartogensis et al.
(2003) [#hartogensis2003]_, and Kleissl et al.
(2008) [#kleissl2008]_.
Expand All @@ -94,7 +94,7 @@ def get_b_value(self, stability_name):
stability condition.
"""

# Hartogenesis et al. (2003), Kleissl et al. (2008).
# Hartogensis et al. (2003), Kleissl et al. (2008).
stability_dict = {"stable": -2 / 3, "unstable": -4 / 3}

if not stability_name:
Expand Down Expand Up @@ -168,9 +168,9 @@ def get_all_path_heights(self, path_transect):
path_transect (pd.DataFrame): Parsed path transect data.
Returns:
dict[tuple[np.floating, np.floating]]: Effective and mean
path heights of transect |z_eff| and |z_mean| [m], with each
stability condition as key.
dict[str, tuple[np.floating, np.floating]]: Effective and
mean path heights of transect |z_eff| and |z_mean| [m], with
each stability condition as key.
"""

path_heights_dict = {}
Expand Down
18 changes: 9 additions & 9 deletions src/scintillometry/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@

import argparse

import scintillometry.metrics.calculations as MetricsCalculations
import scintillometry.wrangler.data_parser as DataParser
import scintillometry.metrics.calculations as calculations
import scintillometry.wrangler.data_parser as data_parser


def user_argumentation():
Expand Down Expand Up @@ -298,10 +298,10 @@ def perform_data_parsing(**kwargs):
measurements, weather observations, and topography.
"""

data_parser = DataParser.WranglerParsing()
parser = data_parser.WranglerParsing()

# Parse BLS, weather, and topographical data
datasets = data_parser.wrangle_data(
datasets = parser.wrangle_data(
bls_path=kwargs["input"],
transect_path=kwargs["transect_path"],
calibrate=kwargs["calibration"],
Expand All @@ -312,7 +312,7 @@ def perform_data_parsing(**kwargs):

# Parse vertical measurements
if kwargs["profile_prefix"]:
datasets["vertical"] = data_parser.vertical.parse_vertical(
datasets["vertical"] = parser.vertical.parse_vertical(
file_path=kwargs["profile_prefix"],
source="hatpro",
levels=None,
Expand Down Expand Up @@ -360,11 +360,11 @@ def perform_analysis(datasets, **kwargs):
covariance data.
"""

metrics_class = MetricsCalculations.MetricsWorkflow()
data_parser = DataParser.WranglerParsing()
metrics_class = calculations.MetricsWorkflow()
parser = data_parser.WranglerParsing()
metrics_data = metrics_class.calculate_standard_metrics(data=datasets, **kwargs)
if kwargs["eddy_path"]:
eddy_frame = data_parser.eddy.parse_eddy_covariance(
eddy_frame = parser.eddy.parse_eddy_covariance(
file_path=kwargs["eddy_path"], tzone=kwargs["timezone"], source="innflux"
)
metrics_data["eddy"] = eddy_frame
Expand All @@ -382,7 +382,7 @@ def main():
"""Parses command line arguments and executes analysis.
Converts command line arguments into kwargs. Imports and parses
scintillomter, weather, and transect data. If the appropriate
scintillometer, weather, and transect data. If the appropriate
arguments are specified:
- Parses vertical measurements
Expand Down
7 changes: 4 additions & 3 deletions src/scintillometry/metrics/calculations.py
Original file line number Diff line number Diff line change
Expand Up @@ -829,13 +829,14 @@ def plot_iterated_metrics(self, iterated_data, time_stamp, site_location=""):
.. todo::
ST-126: Deprecate FigurePlotter.plot_iterated_fluxes in
favour of plot_iterated_metrics.
favour of plot_iterated_metrics. `site_location` should
be deprecated in favour of `location`.
Args:
iteration_data (pd.DataFrame): TZ-aware with columns for
iterated_data (pd.DataFrame): TZ-aware with columns for
sensible heat fluxes calculated for free convection
|H_free|, and by MOST |H|.
time_id (pd.Timestamp): Local time of data collection.
time_stamp (pd.Timestamp): Local time of data collection.
site_location (str): Location of data collection. Default empty
string.
Expand Down
26 changes: 10 additions & 16 deletions src/scintillometry/wrangler/data_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def parse_iso_date(self, x, date=True):
Args:
x (str): Timestamp containing ISO-8601 duration and date,
i.e. "<ISO-8601 duration>/<ISO-8601 date>".
date (bool): If True, returns date. Otherwise returns
date (bool): If True, returns date. Otherwise, returns
duration. Default True.
Returns:
Expand Down Expand Up @@ -163,10 +163,9 @@ def parse_mnd_lines(self, line_list):
line_list (list): Lines read from .mnd file in FORMAT-1.
Returns:
dict[list, list, str, list]: Contains a list of lines of
parsed BLS data, an ordered list of variable names, the file
timestamp, and any additional header parameters in the file
header.
dict: Contains a list of lines of parsed BLS data, an
ordered list of variable names, the file timestamp, and any
additional header parameters in the file header.
Raises:
Warning: The input file does not follow FORMAT-1.
Expand Down Expand Up @@ -251,7 +250,8 @@ def parse_scintillometer(self, file_path, timezone="CET", calibration=None):
"""Parses .mnd files into dataframes.
Args:
filename (str): Path to a raw .mnd data file using FORMAT-1.
file_path (str): Path to a raw .mnd data file using
FORMAT-1.
timezone (str): Local timezone during the scintillometer's
operation. Default "CET".
calibration (list): Contains the incorrect and correct path
Expand Down Expand Up @@ -680,11 +680,7 @@ def parse_eddy_covariance(self, file_path, source="innflux", tzone=None):
"""

if source.lower() == "innflux":
eddy_data = self.parse_innflux(
file_name=file_path,
timezone=tzone,
headers=None,
)
eddy_data = self.parse_innflux(file_name=file_path, timezone=tzone)
else:
error_msg = (
f"{source.title()} measurements are not supported. Use 'innflux'."
Expand Down Expand Up @@ -806,8 +802,8 @@ def parse_hatpro(
Default 612.0.
Returns:
dict[pd.DataFrame, pd.DataFrame]: Vertical measurements from
HATPRO for temperature |T| [K], and absolute humidity
dict[str, pd.DataFrame]: Vertical measurements from HATPRO
for temperature |T| [K], and absolute humidity
|rho_v| [|gm^-3|].
"""

Expand All @@ -828,9 +824,7 @@ def parse_hatpro(
station_elevation=elevation,
)

data = {}
data["humidity"] = humidity_data
data["temperature"] = temperature_data
data = {"humidity": humidity_data, "temperature": temperature_data}

return data

Expand Down
10 changes: 5 additions & 5 deletions tests/test_backend_iterations.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def test_get_most_coefficients(self):

@pytest.mark.dependency(name="TestBackendIterationMost::test_similarity_function")
@pytest.mark.parametrize("arg_obukhov", [(-100, False), (0, True), (100, True)])
def test_similarity_function(self, arg_obukhov):
def test_similarity_function(self, arg_obukhov: tuple):
"""Compute similarity function."""

test_f_ct2 = self.test_class.similarity_function(
Expand All @@ -148,7 +148,7 @@ def test_similarity_function(self, arg_obukhov):

@pytest.mark.dependency(name="TestBackendIterationMost::test_calc_theta_star")
@pytest.mark.parametrize("arg_params", [(1.9e-04, 5.6, True), (2e-03, 3.6, False)])
def test_calc_theta_star(self, arg_params):
def test_calc_theta_star(self, arg_params: tuple):
"""Calculate temperature scale."""

test_theta = self.test_class.calc_theta_star(
Expand Down Expand Up @@ -182,7 +182,7 @@ def test_calc_obukhov_length(self, arg_theta):
"""Calculate Obukhov length."""

compare_lob = self.test_class.calc_obukhov_length(
temp=295, u_star=0.2, theta_star=mpmath.mpmathify(arg_theta)
temp=np.float64(295.0), u_star=0.2, theta_star=mpmath.mpmathify(arg_theta)
)
assert isinstance(compare_lob, mpmath.mpf)
assert (compare_lob < 0) == (arg_theta < 0) # obukhov and theta have same sign
Expand Down Expand Up @@ -224,7 +224,7 @@ def test_check_signs(self, arg_shf, arg_obukhov):
scope="class",
)
@pytest.mark.parametrize("arg_stable", [(200, True), (-100, False)])
def test_most_iteration(self, conftest_mock_merged_dataframe, arg_stable):
def test_most_iteration(self, conftest_mock_merged_dataframe, arg_stable: tuple):
"""Iterate single row of dataframe using MOST."""

test_data = conftest_mock_merged_dataframe.iloc[0].copy(deep=True)
Expand Down Expand Up @@ -310,7 +310,7 @@ def test_most_method(self, capsys, conftest_mock_merged_dataframe, arg_stable):
for key in compare_keys:
assert not (compare_most[key].isnull()).any()
assert key in compare_most.keys()
assert all(isinstance(x, (mpmath.mpf)) for x in compare_most[key])
assert all(isinstance(x, mpmath.mpf) for x in compare_most[key])

# signs match stability
assert (compare_most["obukhov"] > 0).all() == arg_stable[1]
Expand Down
2 changes: 1 addition & 1 deletion tests/test_backend_transects.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ def test_print_path_heights(self, capsys, arg_stability):
test_capture = capsys.readouterr()

self.test_transect_parameters.print_path_heights(
z_eff=34, z_mean=31.245, stability=arg_stability
z_eff=np.float64(34), z_mean=np.float64(31.245), stability=arg_stability
)
compare_capture = capsys.readouterr()

Expand Down
8 changes: 4 additions & 4 deletions tests/test_visuals_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -606,14 +606,14 @@ def test_plot_iterated_fluxes(
compare_plots = {
"shf": {
"title": "Sensible Heat Flux",
"ylabel": r"Sensible Heat Flux, [W$\cdot$m$^{-2}$]",
"xlabel": "Time, CET",
"y_label": r"Sensible Heat Flux, [W$\cdot$m$^{-2}$]",
"x_label": "Time, CET",
"plot": (compare_plots[0]),
},
"comparison": {
"title": "Sensible Heat Flux from Free Convection and Iteration",
"ylabel": r"Sensible Heat Flux, [W$\cdot$m$^{-2}$]",
"xlabel": "Time, CET",
"y_label": r"Sensible Heat Flux, [W$\cdot$m$^{-2}$]",
"x_label": "Time, CET",
"plot": (compare_plots[1]),
},
}
Expand Down
4 changes: 2 additions & 2 deletions tests/test_wrangler_data_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -891,7 +891,7 @@ def test_vertical_init(self):
@pytest.mark.dependency(
name="TestWranglerVertical::test_construct_hatpro_levels_error"
)
@pytest.mark.parametrize("arg_levels", [[(0, 1), (0)], [1.0, 30]])
@pytest.mark.parametrize("arg_levels", [[(0, 1), 0], [1.0, 30]])
def test_construct_hatpro_levels_error(self, arg_levels):
"""Raise error for incorrectly formatted scanning levels."""

Expand All @@ -910,7 +910,7 @@ def test_construct_hatpro_levels(self, arg_levels):
compare_scan = self.test_wrangler_vertical.construct_hatpro_levels(
levels=arg_levels
)
assert isinstance(compare_scan, (list))
assert isinstance(compare_scan, list)
assert all(isinstance(x, int) for x in compare_scan)

@pytest.mark.dependency(
Expand Down

0 comments on commit f07b6c6

Please sign in to comment.