Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[flake8]
ignore = E203, W503
max-line-length=120
18 changes: 18 additions & 0 deletions .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Run pre-commit
on:
- push
- pull_request

jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install pre-commit
run: pip install pre-commit
- name: Run pre-commit
run: pre-commit run flake8 --all-files
9 changes: 9 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
repos:
- repo: https://github.com/psf/black
rev: stable
hooks:
- id: black
- repo: https://github.com/PyCQA/flake8.git
rev: 7.3.0
hooks:
- id: flake8
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,27 @@ make: *** [Makefile:48: elmer_grid.o] Error 1
```

*Solution:* `sudo apt-get install libproj-dev`

# Developer notes

## Code formatting

Automated checks for PEP8 compiance are implemented following [^1] with some modifications. Please install pre-commit first:

```sh
pipx install pre-commit
```

Then, install the pre-commit hooks by running the following command from this folder:

```sh
pre-commit install
```

The pre-commit hooks will now run automatically on every commit.

Note: You can bypass this check with `--no-verify`. Please note that the CI will also check for PEP8 compliance and fail if the code is not PEP8 compliant. Therefore, it is recommended to use the pre-commit hooks locally before pushing code to this repository.

----

[^1]: https://ljvmiranda921.github.io/notebook/2018/06/21/precommits-using-black-and-flake8/
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tool.black]
line-length = 120
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pre-commit~=4.0
253 changes: 174 additions & 79 deletions src/coupling.py

Large diffs are not rendered by default.

188 changes: 107 additions & 81 deletions src/dummy.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,23 @@
from pathlib import Path
import argparse

from ebfm import INIT, LOOP_general_functions, LOOP_climate_forcing, LOOP_EBM, LOOP_SNOW, LOOP_mass_balance
from ebfm import (
INIT,
LOOP_general_functions,
LOOP_climate_forcing,
LOOP_EBM,
LOOP_SNOW,
LOOP_mass_balance,
)
from ebfm import LOOP_write_to_file, FINAL_create_restart_file
from ebfm.grid import GridInputType

from mpi4py import MPI
from utils import setup_logging
import logging

from typing import NamedTuple


log_levels = {
'file':logging.DEBUG, # log level for logging to file
"file": logging.DEBUG, # log level for logging to file
0: logging.INFO, # log level for rank 0
# 1: logging.DEBUG, # to log other ranks to console define log level here
}
Expand All @@ -29,50 +33,68 @@ def main():

parser = argparse.ArgumentParser()

parser.add_argument("--couple-to-elmer-ice",
action="store_true",
help="Enable coupling with Elmer/Ice models via YAC")

parser.add_argument("--couple-to-icon-atmo",
action="store_true",
help="Enable coupling with ICON via YAC")

parser.add_argument("--coupler-config",
type=Path,
help="Path to the coupling configuration file (YAC coupler_config.yaml).")

parser.add_argument("--elmer-mesh",
type=Path,
help="Path to the Elmer mesh file. Either --elmer-mesh or --matlab-mesh is required.")

parser.add_argument("--matlab-mesh",
type=Path,
help="Path to the MATLAB mesh file. Either --elmer-mesh or --matlab-mesh is required.")

parser.add_argument("--netcdf-mesh",
type=Path,
help="Path to the NetCDF mesh file. Optional if using --elmer-mesh. If --netcdf-mesh is provided elevations will be read from the given NetCDF mesh file.")

parser.add_argument("--is-partitioned-elmer-mesh",
action="store_true",
help="Indicate if the provided Elmer mesh is partitioned for parallel runs.")

parser.add_argument("--use-part",
type=int,
default=MPI.COMM_WORLD.rank + 1,
help="If using a partitioned Elmer mesh, allows to specify which partition ID to use for this run. If not provided, the MPI rank + 1 will be used as partition ID.")
parser.add_argument(
"--couple-to-elmer-ice",
action="store_true",
help="Enable coupling with Elmer/Ice models via YAC",
)

parser.add_argument(
"--couple-to-icon-atmo",
action="store_true",
help="Enable coupling with ICON via YAC",
)

parser.add_argument(
"--coupler-config",
type=Path,
help="Path to the coupling configuration file (YAC coupler_config.yaml).",
)

parser.add_argument(
"--elmer-mesh",
type=Path,
help="Path to the Elmer mesh file. Either --elmer-mesh or --matlab-mesh is required.",
)

parser.add_argument(
"--matlab-mesh",
type=Path,
help="Path to the MATLAB mesh file. Either --elmer-mesh or --matlab-mesh is required.",
)

parser.add_argument(
"--netcdf-mesh",
type=Path,
help="Path to the NetCDF mesh file. Optional if using --elmer-mesh."
" If --netcdf-mesh is provided elevations will be read from the given NetCDF mesh file.",
)

parser.add_argument(
"--is-partitioned-elmer-mesh",
action="store_true",
help="Indicate if the provided Elmer mesh is partitioned for parallel runs.",
)

parser.add_argument(
"--use-part",
type=int,
default=MPI.COMM_WORLD.rank + 1,
help="If using a partitioned Elmer mesh, allows to specify which partition ID to use for this run. "
"If not provided, the MPI rank + 1 will be used as partition ID.",
)

args = parser.parse_args()

logger.info(f"Done parsing command line arguments.")
logger.debug(f"Parsed the following command line arguments:")
logger.info("Done parsing command line arguments.")
logger.debug("Parsed the following command line arguments:")
for arg, val in vars(args).items():
logger.debug(f" {arg}: {val}")

# Model setup & initialization
grid, time2, io, phys = INIT.init_config(args)
C = INIT.init_constants()
grid = INIT.init_grid(grid, io, args)
grid, time2, io, phys = INIT.init_config(args)
C = INIT.init_constants()
grid = INIT.init_grid(grid, io, args)

OUT, IN, OUTFILE = INIT.init_initial_conditions(C, grid, io, time2)

Expand All @@ -81,17 +103,17 @@ def main():
# TODO consider introducing an ebfm_adapter_config.yaml
coupler = coupling.init(
coupler_config=args.coupler_config,
ebfm_coupling_config=Path('dummies') / 'EBFM' / 'ebfm-config.yaml',
ebfm_coupling_config=Path("dummies") / "EBFM" / "ebfm-config.yaml",
couple_with_icon_atmo=args.couple_to_icon_atmo,
couple_with_elmer_ice=args.couple_to_elmer_ice,
)
coupling.setup(coupler, grid["mesh"], time2)
else:
coupler = coupling.NoCoupler('ebfm')
coupler = coupling.NoCoupler("ebfm")

# Time-loop
logger.info('Entering time loop...')
for t in range(1, time2['tn'] + 1):
logger.info("Entering time loop...")
for t in range(1, time2["tn"] + 1):
# Print time to screen
time2 = LOOP_general_functions.print_time(t, time2)

Expand All @@ -100,81 +122,85 @@ def main():
# Read and prepare climate input
if coupler and coupler.couple_to_icon_atmo:
# Exchange data with ICON
logger.info('Data exchange with ICON')
logger.debug('Started...')
logger.info("Data exchange with ICON")
logger.debug("Started...")
data_to_icon = {
'albedo': OUT['albedo'],
"albedo": OUT["albedo"],
}

data_from_icon = coupler.exchange_icon_atmo(data_to_icon)

logger.debug('Done.')
logger.debug('Received the following data from ICON:', data_from_icon)

IN['P'] = data_from_icon['pr']*time2['dt']*C['dayseconds']*1e-3 # convert units from kg m-2 s-1 to m w.e.
IN['snow'] = data_from_icon['pr_snow']
IN['SWin'] = data_from_icon['rsds']
IN['LWin'] = data_from_icon['rlds']
IN['C'] = data_from_icon['clt']
IN['WS'] = data_from_icon['sfcwind']
IN['T'] = data_from_icon['tas']
IN['rain'] = IN['P'] - IN['snow'] # TODO: make this more flexible and configurable
IN['q'][:] = 0 # TODO: Read q from ICON instead and convert to RH
IN['Pres'][:] = 101500 # TODO: Read Pres from ICON instead
logger.debug("Done.")
logger.debug("Received the following data from ICON:", data_from_icon)

IN["P"] = (
data_from_icon["pr"] * time2["dt"] * C["dayseconds"] * 1e-3
) # convert units from kg m-2 s-1 to m w.e.
IN["snow"] = data_from_icon["pr_snow"]
IN["SWin"] = data_from_icon["rsds"]
IN["LWin"] = data_from_icon["rlds"]
IN["C"] = data_from_icon["clt"]
IN["WS"] = data_from_icon["sfcwind"]
IN["T"] = data_from_icon["tas"]
IN["rain"] = IN["P"] - IN["snow"] # TODO: make this more flexible and configurable
IN["q"][:] = 0 # TODO: Read q from ICON instead and convert to RH
IN["Pres"][:] = 101500 # TODO: Read Pres from ICON instead

IN, OUT = LOOP_climate_forcing.main(C, grid, IN, t, time2, OUT, coupler)

# Run surface energy balance model
OUT = LOOP_EBM.main(C, OUT, IN, time2, grid, coupler)

# Run snow & firn model
OUT = LOOP_SNOW.main(C, OUT, IN, time2['dt'], grid, phys)
OUT = LOOP_SNOW.main(C, OUT, IN, time2["dt"], grid, phys)

# Calculate surface mass balance
OUT = LOOP_mass_balance.main(OUT, IN, C)

if coupler.couple_to_elmer_ice:
# Exchange data with Elmer
logger.info('Data exchange with Elmer/Ice')
logger.debug('Started...')
logger.info("Data exchange with Elmer/Ice")
logger.debug("Started...")

data_to_elmer = {
'smb': OUT['smb'],
'T_ice': OUT['T_ice'],
'runoff': OUT['runoff'],
"smb": OUT["smb"],
"T_ice": OUT["T_ice"],
"runoff": OUT["runoff"],
}
data_from_elmer = coupler.exchange_elmer_ice(data_to_elmer)
logger.debug('Done.')
logger.debug('Received the following data from Elmer/Ice:', data_from_elmer)
logger.debug("Done.")
logger.debug("Received the following data from Elmer/Ice:", data_from_elmer)

IN['h'] = data_from_elmer['h']
grid['z'] = IN['h'][0].ravel()
IN["h"] = data_from_elmer["h"]
grid["z"] = IN["h"][0].ravel()
# TODO add gradient field later
# IN['dhdx'] = data_from_elmer('dhdx')
# IN['dhdy'] = data_from_elmer('dhdy')

# Write output to files (only in uncoupled run and for unpartitioned grid)
if not grid['is_partitioned'] and not coupler.has_coupling:
assert (grid['input_type'] is GridInputType.MATLAB), \
"Output writing currently only implemented for MATLAB input grids."
if not grid["is_partitioned"] and not coupler.has_coupling:
assert (
grid["input_type"] is GridInputType.MATLAB
), "Output writing currently only implemented for MATLAB input grids."
io, OUTFILE = LOOP_write_to_file.main(OUTFILE, io, OUT, grid, t, time2, C)
pass
elif grid['is_partitioned'] or coupler.has_coupling:
logger.warning('Skipping writing output to file for coupled or partitioned runs.')
elif grid["is_partitioned"] or coupler.has_coupling:
logger.warning("Skipping writing output to file for coupled or partitioned runs.")
else:
logger.error('Unhandled case in output writing.')
logger.error("Unhandled case in output writing.")
raise Exception("Unhandled case in output writing.")

# Write restart file
if not grid['is_partitioned'] and not coupler.has_coupling:
if not grid["is_partitioned"] and not coupler.has_coupling:
FINAL_create_restart_file.main(OUT, io)

logger.info('Time loop completed.')
logger.info("Time loop completed.")

if coupler.has_coupling:
coupling.finalize(coupler)

logger.info('Closing down EBFM.')
logger.info("Closing down EBFM.")


# Entry point for script execution
if __name__ == "__main__":
Expand Down
10 changes: 7 additions & 3 deletions src/ebfm/FINAL_create_restart_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from netCDF4 import Dataset, date2num
import numpy as np


def main(OUT, io):

def create_boot_file():
Expand Down Expand Up @@ -51,7 +52,11 @@ def create_boot_file():

return True

OUT['timelastsnow_netCDF'] = date2num(OUT['timelastsnow'], units="days since 1970-01-01 00:00:00", calendar="gregorian")
OUT["timelastsnow_netCDF"] = date2num(
OUT["timelastsnow"],
units="days since 1970-01-01 00:00:00",
calendar="gregorian",
)
OUT = {
"subZ": OUT["subZ"],
"subW": OUT["subW"],
Expand All @@ -66,6 +71,5 @@ def create_boot_file():
"alb_snow": OUT["alb_snow"],
}


# Create the boot file
create_boot_file()
create_boot_file()
Loading