Skip to content

Commit

Permalink
merge: release 1.0.0 (#3)
Browse files Browse the repository at this point in the history
* refactor(wrangler)!: add class inheritance to data parsing

Parsing functions are now relegated to appropriate classes. All
Wrangler* classes should inherit IO methods from the WranglerIO
class, and if necessary initialise methods in WranglerTransform
as `self.transform`.

Refs: ST-3, ST-5, ST-120

* refactor(backend): add class inheritance to derivations

Refs: ST-3, ST-6, ST-120

* refactor!: add class inheritance

Relegates standalone methods to relevant classes.
Refactors API to futureproof source selection.

Refs: ST-3, ST-5, ST-7, ST-120

* refactor(interface)!: updates main to use new frontend classes

Refs: ST-26, ST-120

* ci: run CI on release branches

Refs: ST-1, ST-3, ST-11

* chore: update versioning and fix ci refs for release

Refs: ST-1, ST-119

* fix(build): fix typo in Makefile pkg

Refs: ST-1

* ci: add ci for develop branch

Refs: ST-1, ST-3

* refactor(wrangler): refactor constants inheritance

Refs: ST-3, ST-5

* docs: update README for release

Refs: ST-2

* docs: clarify available and planned features

Refs: ST-2
  • Loading branch information
gampnico authored May 12, 2023
1 parent 81c4b54 commit 74ce4b5
Show file tree
Hide file tree
Showing 18 changed files with 2,025 additions and 1,405 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ name: Pytest and Linting

on:
push:
branches: ["main", "release-**"]
branches: ["main", "develop", "release-**"]
pull_request:
branches: ["main", "release-**"]
branches: ["main", "develop", "release-**"]

permissions:
contents: read
Expand All @@ -21,7 +21,7 @@ jobs:
strategy:
fail-fast: true
matrix:
python-version: ["3.8", "3.10"]
python-version: ["3.8", "3.9", "3.10", "3.11"]

steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ scalene: ## Profile with scalene (Python 3.9+)
-t "CET" -v

.PHONY: pkg
pkg: tests doc --build ## Run test, build documentation, build package
pkg: tests docs --build ## Run test, build documentation, build package

--install-deps: --check-python --hook-manager # Private: install core dependencies only
@echo "\nInstalling dependencies (core)..."
Expand Down
74 changes: 50 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,19 @@ Analyse data & 2D flux footprints from Scintec's BLS scintillometers.

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.

This package started life as part of a field course. If you spot any missing citations or licenses please contact me directly or open an issue.
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-tools/issues).

# Processing Scintillometry Data in Complex Terrain

Scintillometry-Tools is configured for scintillometer experiments in Austria
using public or local data (ZAMG, InnFLUX), but is easily modified to work with
other data sources. Note that external data sources may have different licensing
constraints.
Scintillometry-Tools is configured for scintillometer experiments in Austria using public or local data (ZAMG, InnFLUX), but is easily modified to work with other data sources. Note that external data sources may have different licensing constraints.

The package is currently in alpha and may change or break often. Support is only available for Python 3.8+ on debian-based Linux distros.

## Installation

Scintillometry-Tools supports installation as an editable with pip or conda.
Scintillometry-Tools supports package installation with pip, or from source as an editable with pip or conda. **Installing as an editable is recommended**, as it allows you to call command line arguments directly on the package instead of writing a frontend.

### Install with Conda/Mamba
### Install from Source with Conda/Mamba

Create or activate your preferred conda environment and run:

Expand All @@ -61,6 +58,14 @@ Installation uses conda if mamba is unavailable. Micromamba may also work, but i

If conda/mamba are not your package managers, then run:

```bash
python3 -m pip install --upgrade scintillometry
```

Note that this installation method does not provide documentation or a Makefile, and you cannot easily use the package from the command line.

To install from source (recommended):

```bash
git clone https://github.com/gampnico/scintillometry-tools.git
pip install -e .
Expand All @@ -83,28 +88,41 @@ Data processing:
- Parse scintillometry data from Scintec's BLS series of large aperture scintillometers (.mnd files).
- Recalibrate data if the scintillometer was incorrectly set up (e.g. wrong dip switch settings).
- Parse topographical data as path transect.
- Download and parse meteorological data.
- Parse InnFLUX and HATPRO data (local files only).
- Parse meteorological data.
- Parse innFLUX and HATPRO data.

Metrics:
- Calculate effective path heights under various stability conditions.
- Derive C<sub>T</sub><sup>2</sup> values from C<sub>n</sub><sup>2</sup> if these were not collected.
- Estimate the time where stability conditions change.
- Estimate the time when stability conditions change.
- Estimate the boundary layer height.
- Compute parameters such as Obukhov length, friction velocity, etc.
- Compute kinematic and sensible SHF. Supports free convection and iteration with MOST: several sets of coefficients are available for MOST functions, based on previous studies.
- Compute kinematic and sensible heat fluxes. Supports free convection and iteration with MOST: several sets of coefficients are available for MOST functions, based on previous studies.

Visualisation:
- Produces path transects.
- Produces time series of scintillometry and meteorological data.
- Produces regression plots between calculated parameters and external data sources.
- Produces vertical profiles.
- Produces plots for derived or iterated variables.
- Produces comparisons between calculated parameters and external data sources.

Currently implemented MOST functions:
- **an1988**: E.L. Andreas (1988), [DOI: 10.1364/JOSAA.5.000481](https://opg.optica.org/josaa/abstract.cfm?uri=josaa-5-4-481)
- **li2012**: D. Li et al. (2012), [DOI:10.1007/s10546-011-9660-y](https://link.springer.com/article/10.1007/s10546-011-9660-y)
- **wy1971**: Wyngaard et al. (1971), [DOI: 10.1364/JOSA.61.001646](https://opg.optica.org/josa/abstract.cfm?uri=josa-61-12-1646)
- **wy1973**: Wyngaard et al. (1973) in Kooijmans and Hartogensis (2016), [DOI: 10.1007/s10546-016-0152-y](https://link.springer.com/article/10.1007/s10546-016-0152-y)
- **ma2014**: Maronga et al. (2014), [DOI: 10.1007/s10546-014-9955-x](https://link.springer.com/article/10.1007/s10546-014-9955-x)
- **br2014**: Braam et al. (2014), [DOI: 10.1007/s10546-014-9938-y](https://link.springer.com/article/10.1007/s10546-014-9938-y)

## Footprint Climatology (Roadmap)

These features are under development.

Metrics:
- Process 2D flux footprints generated by Natascha Kljun's online model, available [here](http://footprint.kljun.net/).
- Adjust topography and stitch footprints together.

Visualisation:
- Produce regression plots between calculated parameters and external data sources.
- Overlay stitched footprints onto map.

## Workflow
Expand All @@ -119,7 +137,7 @@ List all available arguments with:

```bash
python3 ./src/scintillometry/main.py -h
make commands # if you prefer less typing
make commands # if you installed from source
```

Navigate to the package root in the terminal. Calculate and plot surface
Expand All @@ -130,11 +148,11 @@ python3 ./src/scintillometry/main.py -i "./<path_to_input>/<bls_data>.mnd" \
-p "./<path_to_transect>/<transect_data>.csv" -t "CET"
```

If you are not using the scintillometer in Austria, you will need to find and parse topographical data yourself. Add or modify functions in ``wrangler/data_parsing.py`` to parse data from other scintillometers, organisations, or countries.
If you are not using the scintillometer in Austria, you will need to find and parse topographical and meteorological data yourself. Add parsing functions to classes in ``wrangler/data_parsing.py`` to parse data from other scintillometers, organisations, or countries. A step-by-step guide on how to do this is given in the module's docstring.

### Make Things Simple

The provided Makefile has many uses. View all the available commands:
If you installed from source, the provided Makefile has many uses. View all the available commands:

```bash
make help # display help for Makefile targets
Expand All @@ -151,31 +169,39 @@ src/scintillometry/main.py [-h] [-i <input_data>] [-p <path_data>] [-d] [...] [-

### Import as Package

Scintillometry-Tools and its submodules can be imported as a Python module:
Scintillometry-Tools and its submodules can be imported as Python modules:

```python
import scintillometry
from scintillometry.wrangler.data_parser import parse_scintillometer
from scintillometry.wrangler.data_parser import WranglerParsing

parser = WranglerParsing()
dataframe = parser.scintillometer.parse_scintillometer(file_path="./data.mnd")
weather = parser.weather.parse_weather(file_path="./weather.csv")
weather = parser.weather.transform.change_index_frequency(weather, "60S")
```

MOST functions are stored in their respective class:

```python
from scintillometry.backend.iterations import IterationMost

workflow = IterationMost()
workflow.most_method(dataframe, eff_h, stability, coeff_id="an1988")
iteration = IterationMost()
iteration.most_method(dataframe, eff_h, stability, coeff_id="an1988")
```

These classes inherit from the AtmosConstants class:
Many classes initialise atmospheric constants using the AtmosConstants class:

```python
from scintillometry.metrics.calculations import MetricsWorkflow
from scintillometry.backend.constants import AtmosConstants

constants = AtmosConstants()
kelvin = constants.kelvin # 273.15
workflow = MetricsWorkflow()
kelvin = workflow.constants.kelvin # 273.15
assert isinstance(workflow.constants, AtmosConstants) # True
```

For more information see the API section of the documentation.

# Acknowledgements

This project would not be possible without the invaluable contributions from Josef Zink, Dr. Manuela Lehner, and Dr. Helen Ward.
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def setup(app):
project = "Scintillometry Tools"
copyright = f"2019-{date.today().year}, Scintillometry Tools Contributors"
author = "Scintillometry Tools Contributors"
release = "0.37.a0"
release = "1.0.0"

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
Expand Down
9 changes: 8 additions & 1 deletion docs/source/development.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,18 @@ With Python 3.9+, optionally install `Scalene`_ to profile your code.
Contribution Guidelines
------------------------

Follow `Git-Flow`_ where possible, but please feel free to create feature
branches that branch off from ``develop``.

Prefix branches with ``feat-``, ``feature-``, or ``hotfix-``, followed by the
issue number and a description, e.g. ``feat-I3-description``.

Avoid excess conflicts by following these guidelines:

- Write commit messages in the style of `conventional commits`_.
- Write tests before committing features.
- Push many small commits instead of a single large one.
- Push new features to new branches. Never push to main.
- Push new features to ``develop``. Never push to ``main``.
- Push documentation separately. Don't push built documentation.
- Follow the `Google Style Guide`_.
- Format all code with `black`_, line length 88.
Expand All @@ -59,6 +65,7 @@ Avoid excess conflicts by following these guidelines:
- Break lines in Markdown only at the end of paragraphs.
- Spaces, not tabs.

.. _`Git-Flow`: https://nvie.com/posts/a-successful-git-branching-model/
.. _`conventional commits`: https://www.conventionalcommits.org/en/v1.0.0/
.. _`Google Style Guide`: https://google.github.io/styleguide/pyguide.html
.. _`black`: https://black.readthedocs.io/en/stable/
Expand Down
13 changes: 9 additions & 4 deletions docs/source/intro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ Data processing:
- Recalibrate data if the scintillometer was incorrectly set up (e.g. wrong
dip switch settings).
- Parse topographical data as path transect.
- Download and parse meteorological data.
- Parse InnFLUX and HATPRO data (local files only).
- Parse meteorological data.
- Parse innFLUX and HATPRO data.

Metrics:
- Calculate effective path heights under various stability conditions.
Expand All @@ -54,9 +54,10 @@ Metrics:
based on previous studies.

Visualisation:
- Produces path transects.
- Produces time series of scintillometry and meteorological data.
- Produces regression plots between calculated parameters and external data
- Produces vertical profiles.
- Produces plots for derived or iterated variables.
- Produces comparisons between calculated parameters and external data
sources.

Currently implemented MOST functions:
Expand All @@ -77,6 +78,8 @@ Currently implemented MOST functions:
Footprint Climatology (Roadmap)
*******************************

These features are under development.

Metrics:
- Process 2D flux footprints generated by Natascha Kljun's online model,
available here_.
Expand All @@ -85,6 +88,8 @@ Metrics:
.. _here: http://footprint.kljun.net/

Visualisation:
- Produce regression plots between calculated parameters and external data
sources.
- Overlay stitched footprints onto map.

Example Workflow (Terminal)
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "scintillometry"
version = "0.37.a0"
version = "1.0.0"
authors = [
{ name="Scintillometry-Tools Contributors", email="" },
]
Expand All @@ -20,6 +20,7 @@ classifiers = [
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Environment :: Console",
"Topic :: Scientific/Engineering :: Atmospheric Science",
"Topic :: Scientific/Engineering :: Information Analysis",
Expand Down
34 changes: 22 additions & 12 deletions src/scintillometry/backend/constructions.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@
from scintillometry.backend.constants import AtmosConstants


class ProfileConstructor(AtmosConstants):
class ProfileConstructor:
"""Constructs vertical profiles.
Attributes:
constants (AtmosConstants): Inherited atmospheric constants.
"""

def __init__(self):
super().__init__()
self.constants = AtmosConstants()
Expand All @@ -58,7 +64,7 @@ def get_water_vapour_pressure(self, abs_humidity, temperature):
"""

# abs_humidity * temperature * self.r_vapour
wvp = abs_humidity.multiply(temperature).multiply(self.r_vapour)
wvp = abs_humidity.multiply(temperature).multiply(self.constants.r_vapour)

return wvp

Expand Down Expand Up @@ -94,9 +100,9 @@ def get_air_pressure(self, pressure, air_temperature, z_target, z_ref=0):
# pressure * np.exp(
# (-self.g * (z_target - z_ref)) / (air_temperature * self.r_dry)
# )
gdz = -self.g * (z_target - z_ref)
gdz = -self.constants.g * (z_target - z_ref)
alt_pressure = pressure.multiply(
np.exp((air_temperature.multiply(self.r_dry)).rdiv(gdz))
np.exp((air_temperature.multiply(self.constants.r_dry)).rdiv(gdz))
)

return alt_pressure
Expand Down Expand Up @@ -183,8 +189,8 @@ 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.r_dry)).divide(
(d_pressure).multiply(self.r_vapour)
m_ratio = (wv_pressure.multiply(self.constants.r_dry)).divide(
(d_pressure).multiply(self.constants.r_vapour)
)

return m_ratio
Expand Down Expand Up @@ -237,7 +243,7 @@ def get_reduced_pressure(self, station_pressure, virtual_temperature, elevation)
# station_pressure * np.exp(
# elevation / (virtual_temperature * (self.r_dry / np.abs(self.g)))
# )
alpha = self.r_dry / np.abs(self.g)
alpha = self.constants.r_dry / np.abs(self.constants.g)
mslp = station_pressure.multiply(
(np.exp(virtual_temperature.multiply(alpha).rdiv(elevation)))
)
Expand All @@ -264,9 +270,9 @@ def get_potential_temperature(self, temperature, pressure):
"""

# temperature * (self.ref_pressure / pressure) ** (self.r_dry / self.cp)
factor = self.r_dry / self.cp
factor = self.constants.r_dry / self.constants.cp
potential_temperature = temperature.multiply(
(pressure.rdiv(self.ref_pressure)).pow(factor)
(pressure.rdiv(self.constants.ref_pressure)).pow(factor)
)

return potential_temperature
Expand Down Expand Up @@ -372,7 +378,7 @@ def get_lapse_rates(self, temperature, mixing_ratio):
)

unsaturated_temperature = self.extrapolate_column(
dataframe=temperature, gradient=-self.dalr
dataframe=temperature, gradient=-self.constants.dalr
)
saturated_temperature = self.extrapolate_column(
dataframe=temperature, gradient=-moist_adiabatic_lapse
Expand Down Expand Up @@ -571,7 +577,9 @@ def get_bulk_richardson(self, potential_temperature, meteo_data):
)

# delta_theta * delta_z * self.g
numerator = delta_theta.multiply((heights[-1] - heights[0])).multiply(self.g)
numerator = delta_theta.multiply((heights[-1] - heights[0])).multiply(
self.constants.g
)
# mean_potential_temperature * (wind_speed ** 2)
denominator = (
potential_temperature[heights]
Expand Down Expand Up @@ -686,6 +694,8 @@ def get_n_squared(self, potential_temperature, scheme="backward"):
data=potential_temperature, method=scheme
)

n_squared = potential_temperature.rdiv(self.g).multiply(grad_pot_temperature)
n_squared = potential_temperature.rdiv(self.constants.g).multiply(
grad_pot_temperature
)

return n_squared
Loading

0 comments on commit 74ce4b5

Please sign in to comment.