Skip to content
Open
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
26 changes: 18 additions & 8 deletions OceanOSSE/__init__.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
"""
OceanOSSE

Python toolbox for performing Observing System Simulation Experiments (OSSEs) in ocean general circulation models.
Python toolbox for performing Observing System Simulation Experiments (OSSEs)
in ocean general circulation models.
"""

__author__ = "Ollie Tooth (oliver.tooth@noc.ac.uk)"
__credits__ = "National Oceanography Centre (NOC), Southampton, UK"

from importlib.metadata import version as _version

from OceanOSSE import (
cli,
pipeline,
)
from OceanOSSE import cli, pipeline
from OceanOSSE.gridding.regridder import Regridder
from OceanOSSE.io.dataloader import DataLoader
from OceanOSSE.io.datawriter import DataWriter
from OceanOSSE.sampling.sampler import ErrorKernel, ObsSampler
from OceanOSSE.sampling.sampler_nearest_neighbour import NNSampler

try:
__version__ = _version("OceanOSSE")
except Exception:
# Local copy or not installed with setuptools.
# Disable minimum version checks on downstream libraries.
__version__ = "9999.0.0"

__all__ = ("cli", "pipeline")
__all__ = (
"cli",
"pipeline",
"DataLoader",
"DataWriter",
"ErrorKernel",
"ObsSampler",
"Regridder",
)
117 changes: 116 additions & 1 deletion OceanOSSE/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,119 @@
Created By: Ollie Tooth (oliver.tooth@noc.ac.uk)
"""

# -- Import Dependencies -- #
# -- Import dependencies -- #
import logging
import sys

import typer
from typing_extensions import Annotated, Optional

from OceanOSSE.pipeline import describe_pipeline, run_pipeline

from .__init__ import __version__

app = typer.Typer()
logger = logging.getLogger(__name__)


# -- Define CLI Functions -- #
def create_header(
config_path: str,
log_path: str,
) -> None:
"""
Add OceanOSSE header to log.

Parameters:
-----------
config_path : str
Filepath to OceanOSSE config .toml file.
log_path : str
Filepath to OceanOSSE log file.
"""
logger.info(
f"""
╔══════════════════════════════════════════════════════════════╗
║ OceanOSSE ║
║ Ocean Observing System Simulation Experiment Tool ║
╠══════════════════════════════════════════════════════════════╣
OceanOSSE Version : {__version__}
Python Version : {sys.version.split()[0]}
Config File : {config_path}
Log File : {log_path}
╚══════════════════════════════════════════════════════════════╝
""",
extra={"simple": True},
)


def init_logging(log_path: str) -> None:
"""
Initialise OceanOSSE logging.

Parameters:
-----------
log_path : str
Filepath to log file. If None, logs to 'ocean_osse.log'.
"""
# === Validate Inputs === #
if not isinstance(log_path, str):
raise TypeError("log_path must be a string.")

logging.basicConfig(
format="⦿══⦿ OceanOSSE ⦿══⦿ ║ %(levelname)10s ║ %(asctime)s ║ %(message)s",
level=logging.INFO,
datefmt="%Y-%m-%d %H:%M:%S",
handlers=[logging.FileHandler(log_path), logging.StreamHandler()],
)


# === Create Typer App === #
@app.callback()
def main() -> None:
"""
Main callback for Typer app to allow
single run command to be defined.
"""
pass


@app.command()
def run(
config: Annotated[str, typer.Argument(help="Path to OceanOSSE config .toml file")],
log: Annotated[
Optional[str],
typer.Option(
help="Path to write OceanOSSE log file", rich_help_panel="Options"
),
] = "ocean_osse.log",
dry_run: Annotated[
Optional[bool],
typer.Option(
help="Describe OceanOSSE workflow without execution.",
rich_help_panel="Options",
),
] = False,
) -> None:
"""
Run OceanOSSE workflow defined by configuration (.toml) file in current process.
"""
# === Initialise Logging === #
init_logging(log_path=log)
create_header(config_path=config, log_path=log)

# === Run OceanOSSE === #
args = {
"config_file": config,
"log_filepath": log,
}
if dry_run:
describe_pipeline(args=args)
else:
run_pipeline(args=args)

logging.info("✔ OceanOSSE Completed ✔")


if __name__ == "__main__":
app()
122 changes: 122 additions & 0 deletions OceanOSSE/gridding/regridder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"""
regridder.py

Description: Regridding module for OceanOSSE package.

Created By: OceanOSSE Development Team (NOC, UK)
"""

# -- Import Dependencies -- #
from __future__ import annotations

import abc
import logging
from typing import Self

import xarray as xr

logger = logging.getLogger(__name__)


# -- Regridder Abstract Base Class -- #
class Regridder(abc.ABC):
"""
Abstract base class for regridding synthetic ocean observations onto
the original model grid, using methods such as objective analysis
or interpolation.

Parameters
----------
target_grid : xarray.Dataset or None, optional
Dataset describing the target grid (coordinates, masks, etc.).
"""

def __init__(
self,
target_grid: xr.Dataset | None = None,
) -> None:
if target_grid is not None and not isinstance(target_grid, xr.Dataset):
raise TypeError("``target_grid`` must be an xarray.Dataset or None.")
self._target_grid = target_grid

def __repr__(self) -> str:
has_grid = self._target_grid is not None
return f"{type(self).__name__}(target_grid={'<Dataset>' if has_grid else None})"

@classmethod
@abc.abstractmethod
def from_config(cls, config: dict) -> Self:
"""
Construct a Regridder from the from the `[regridding]` table of
the .toml configuration file.

Parameters
----------
config : dict
Configuration dictionary containing input parameters from .toml
configuration file.

Returns
-------
Self
Initialised Regridder instance.
"""
...

@abc.abstractmethod
def regrid(self, ds: xr.Dataset) -> xr.Dataset:
"""
Regrid the synthetic observation dataset onto the target grid.

Parameters
----------
ds : xarray.Dataset
Synthetic observations dataset.

Returns
-------
xarray.Dataset
Dataset of synthetic observations regridded onto target grid.
"""
...


# -- Regridder Implementations -- #


class TestRegridder(Regridder):
"""
Regridder used for testing and scaffold validation.

Returns the the synthetic observations dataset unchanged.
"""

@classmethod
def from_config(cls, config: dict) -> Self:
"""
Instantiate a TestRegridder from the `[regridding]` table of
the .toml configuration file.
"""
return cls()

def regrid(self, ds: xr.Dataset) -> xr.Dataset:
"""
Regrid the synthetic observation dataset onto the target grid.

Parameters
----------
ds : xarray.Dataset
Synthetic observations dataset.

Returns
-------
xarray.Dataset
Dataset of synthetic observations (unchanged from input).
"""
logger.debug(
"Regridding synthetic observations with TestRegridder -> returns input dataset unchanged."
)
logging.info(
"--> Completed: Regridded synthetic observations with TestRegridder."
)
return ds
Loading
Loading