Metadata-Version: 2.4
Name: architeuthis
Version: 0.2.0
Summary: An open-source, mesh-free and differntiable ocean weather routing and decision-making framework for hybrid sailing ships
Author-email: Jules Richeux <j.richeux@neoline.eu>
License-Expression: GPL-3.0-only
Project-URL: Homepage, https://github.com/julesricheux/Architeuthis
Project-URL: Repository, https://github.com/julesricheux/Architeuthis
Project-URL: Issues, https://github.com/julesricheux/Architeuthis/issues
Keywords: meteorology,oceanography,weather routing,ocean routing,ship routing,marine navigation,maritime transport,voyage optimization,route optimization,decision support,ship performance,sailing ships,wind propulsion,hybrid propulsion,sustainable shipping,ensemble forecasting,geospatial analysis,mesh-free methods,differentiable programming,automatic differentiation,scientific computing,numerical optimization
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Operating System :: OS Independent
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy
Requires-Dist: scipy
Requires-Dist: pandas
Requires-Dist: matplotlib
Requires-Dist: seaborn
Requires-Dist: sympy
Requires-Dist: casadi
Requires-Dist: xarray
Requires-Dist: geojson
Requires-Dist: pillow
Requires-Dist: requests
Requires-Dist: herbie-data
Requires-Dist: copernicusmarine
Requires-Dist: cartopy
Requires-Dist: metpy
Requires-Dist: netcdf4
Requires-Dist: geopandas
Requires-Dist: eumdac
Requires-Dist: h5py
Requires-Dist: cdsapi
Requires-Dist: cfgrib
Requires-Dist: pyproj
Requires-Dist: sortedcontainers
Requires-Dist: tqdm
Provides-Extra: app
Requires-Dist: flask; extra == "app"
Requires-Dist: dash<4,>=3.2; extra == "app"
Requires-Dist: dash-extensions; extra == "app"
Requires-Dist: dash-leaflet; extra == "app"
Requires-Dist: dash-mantine-components; extra == "app"
Requires-Dist: dash-iconify; extra == "app"
Provides-Extra: test
Requires-Dist: architeuthis[app]; extra == "test"
Requires-Dist: pytest>=8; extra == "test"
Requires-Dist: pytest-cov; extra == "test"
Dynamic: license-file

<!-- Logo placeholder: add the file at media/logo-architeuthis-color.png -->
<img alt="Architeuthis logo" src="media/logo-architeuthis-color.png" width="100%">

<div align="center">

# Architeuthis: Weather Routing for Wind-powered Hybrid Vessels 🦑

**Minimize fuel on a fixed schedule, with real forecasts, constraints and custom vessel model.**

<!-- Badges -->

[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/architeuthis)](https://pypi.org/project/architeuthis/)
[![PyPI](https://img.shields.io/pypi/v/architeuthis.svg)](https://pypi.org/project/architeuthis/)
[![Tests](https://github.com/julesricheux/Architeuthis/actions/workflows/tests.yml/badge.svg?branch=develop)](https://github.com/julesricheux/Architeuthis/actions/workflows/tests.yml)
[![Downloads](https://static.pepy.tech/badge/architeuthis)](https://pepy.tech/projects/architeuthis)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](LICENSE)

## 📚 [User Guide](docs/user_guide.md) | ❔ [Issues](https://github.com/julesricheux/Architeuthis/issues)

<!-- (Badges) -->

</div>

---

## What is Architeuthis?

An open-source marine weather and decision-making framework for hybrid sailing ships. It couples mesh-free, fully differentiable methods in a unified optimization environment to compute optimal routes under real-world constraints.

"Mesh-free" means that routes are not searched on a graph. The waypoints are continuous variables of a single nonlinear program, and weather, bathymetry and vessel performance are differentiable tables queried at those waypoints. [CasADi](https://github.com/casadi/casadi) differentiates through everything and [IPOPT](https://github.com/coin-or/Ipopt) solves the problem.

**Key Features:**
- ⛽ **Fuel-optimal routes** - Minimize the fuel of a voyage with a fixed departure and arrival time
- 🚢 **Motor and hybrid vessels** - Bring your vessel as a performance table, with or without sails
- 🌊 **Wind, waves and currents** - Ready-made IFS, GFS, ensemble and Mercator sources
- 🧊 **Real-world constraints** - Land, shallow water, exclusion zones, the iceberg limit, maximum power and your own constraints
- 🎲 **Forecast uncertainty** - Optimize each ensemble member, and test a plan against other models, members or vessels
- 📊 **Ready-to-use outputs** - A4 reports, maps, tables, GPX tracks and an interactive viewer

**Keywords:** weather routing, voyage optimization, fuel saving, wind-assisted ship propulsion, hybrid vessel, passage planning, nonlinear optimization, CasADi, IPOPT, GRIB2, xarray, python

**Architeuthis** is the tool used for the **onshore weather routing support** for [Neoliner Origin](https://www.neoline.eu/) 🌬️⛵⚓

---

## 🚀 Quick Start

### ⚙️ Installation

Architeuthis needs Python 3.11 or later.

**With pip:**
```bash
pip install architeuthis
```

Add the interactive viewer with:
```bash
pip install "architeuthis[app]"
```

**From source, with conda:**
```bash
git clone https://github.com/julesricheux/Architeuthis.git
```
```bash
cd Architeuthis
```
```bash
conda env create -f environment.yaml
```

The environment is named `py313-architeuthis`. The package then needs no installation: run everything from the repository root.

### 💡 Simple Example

This example routes a vessel across the North Atlantic with recent ECMWF forecasts. It needs no credentials, only a polar file for your vessel (see [Your Vessel](#-your-vessel)).

```python
import pandas as pd
import xarray as xr

from architeuthis.core import RoutingAnalysis
from architeuthis.forecast import DeterministicHerbieForecast, Forecast, Topography
from architeuthis.report import ReportCollection
from architeuthis.route import Route, Waypoint
from architeuthis.toolbox.plot_utils import plot_report
from architeuthis.vessel import Vessel
from architeuthis.voyage import Voyage

domain = dict(min_lon=-60., max_lon=0., min_lat=38., max_lat=56.)

# Yesterday's 00 UTC forecast, with its lead times in hours
today = pd.Timestamp("now").floor("1d")
date = today - pd.Timedelta(days=1)
steps = list(range(0, 145, 3)) + list(range(150, 241, 6))

atmosphere = DeterministicHerbieForecast("wind", model="ifs", product="oper", regex=r":10[u|v]:", fxx=steps, date=date, **domain)
atmosphere.load_data()
atmosphere.add_interpolator("u10", "valid_time", "latitude", "longitude")
atmosphere.add_interpolator("v10", "valid_time", "latitude", "longitude")

wave = DeterministicHerbieForecast("wave", model="ifs", product="wave", regex=r":swh:|:mwd:", fxx=steps, date=date, **domain)
wave.load_data()
wave.add_interpolator("swh", "valid_time", "latitude", "longitude")
wave.add_interpolator("mwd", "valid_time", "latitude", "longitude")

# Calm water on the grid of the wind. For real currents, see CopernicusForecast in examples/post_leg_rewind.py
calm = xr.zeros_like(atmosphere.data["u10"])
current = Forecast("calm_current", date=None, fxx=None, **domain)
current.data = xr.Dataset({"uo": calm, "vo": calm})
current.add_interpolator("uo", "valid_time", "latitude", "longitude")
current.add_interpolator("vo", "valid_time", "latitude", "longitude")

topography = Topography("topography", **domain)
topography.load_data()
topography.add_interpolator("distance_to_iso0", "lat", "lon", var_key="z")

# Specific fuel consumption in g/kWh, loads and power in kW
vessel = Vessel("vessel", filepath="my_vessel_polar.nc", sfc=220., hotel_load=194., max_power=4100.)
vessel.load_data()
for key in ("bhp", "max_bhp", "sails_contribution"):
    vessel.add_interpolator(key, "stw", "tws", "twa", "hs", "wa", method="linear")

# The reference route and the schedule, in UTC
route = Route("crossing", [Waypoint(47.725, -4.380, "Penmarch"), Waypoint(46.551, -53.321, "Cape Race")])
voyage = Voyage("crossing", etd=today, eta=today + pd.Timedelta(days=8), route=route)

# Optimize
analysis = RoutingAnalysis("crossing", vessel, voyage, atmosphere, wave, current, topography)
report = analysis.run()

# Results
reports = ReportCollection([report])
print(reports.summary())
reports.to_gpx()     # a track for your navigation system
reports.save()       # for the viewer
plot_report(report)  # one A4 page: speed, sails, power, waves and wind against time
```

---

## 🚢 Your Vessel

Architeuthis does not include a vessel model. You provide a NetCDF performance table, which can come from a velocity prediction program, sea trials or a simpler model. A motor vessel and a hybrid vessel only differ by the content of this table: for a motor vessel, `sails_contribution` is zero.

| Variable | Meaning | Unit |
| --- | --- | --- |
| `bhp` | Average brake power | kW |
| `max_bhp` | Brake power with load fluctuations, checked against `max_power` | kW |
| `sails_contribution` | Share of the propulsion from sails, for the reports | % |

Each variable has the five dimensions below, in this order:

| Dimension | Meaning | Unit |
| --- | --- | --- |
| `stw` | Speed through water | kt |
| `tws` | True wind speed | kt |
| `twa` | True wind angle, 0 to 180 | deg |
| `hs` | Significant wave height | m |
| `wa` | Wave angle, 0 to 180 | deg |

`sfc` and `hotel_load` are given to `Vessel` as constants or as functions of the power, and `sfc` must be set because the objective is zero without it. A polar named `..._eff45.nc` gets the label `eff45` in reports, or you can give any `label`.

---

## 🔑 Credentials

IFS, GFS, HRDPS, ETOPO and the iceberg limit need no account. The other providers are free but need one, and only when you use them:

| You want | Provider | Environment variables |
| --- | --- | --- |
| Currents, `forecast.CopernicusForecast` | [Copernicus Marine](#copernicus-marine-currents) | `CMEMS_USER`, `CMEMS_PWD` |
| Reanalysis, `forecast.CDSForecast` | [Climate Data Store](#climate-data-store-era5) | `CDS_API_KEY` |
| Satellite observations, `satellite.ASCATData`, `satellite.PoseidonData` | [EUMETSAT](#eumetsat-satellite-observations) | `EUMDAC_USER`, `EUMDAC_PWD` |

### > Setting an Environment Variable

Architeuthis reads these variables **when it is imported**, so set them first, then open a new terminal or restart Spyder. The examples below use `CMEMS_USER`: do the same for each variable.

**Windows:** search for *Edit environment variables for your account* in the Start menu, then choose *New*. Or, in PowerShell:
```powershell
setx CMEMS_USER "your-username"
```

**Linux and macOS:** add this line to `~/.bashrc` or `~/.zshrc`:
```bash
export CMEMS_USER="your-username"
```

**In a conda environment,** so that the values only apply there, then activate the environment again:
```bash
conda env config vars set CMEMS_USER="your-username" -n py313-architeuthis
```

**For one session only,** before any Architeuthis import:
```python
import os
from getpass import getpass

os.environ["CMEMS_USER"] = "your-username"
os.environ["CMEMS_PWD"] = getpass("Copernicus Marine password: ")

from architeuthis.forecast import CopernicusForecast
```

To check which variables Python can see, without showing their values:
```python
import os

for name in ("CMEMS_USER", "CMEMS_PWD", "CDS_API_KEY", "EUMDAC_USER", "EUMDAC_PWD"):
    print(name, "is set" if os.environ.get(name) else "is missing")
```

> 🔒 **Keep credentials out of your code.** Anything committed to a repository stays in its history, even after it is removed from the files, and a public repository exposes it to everyone. These methods store values in plain text in your user profile, so keep them out of shared machines and shared folders. If a credential leaks, change it at the provider right away. A value typed in a command also stays in the shell history.

### Copernicus Marine (currents)

1. Create a free account at [data.marine.copernicus.eu/register](https://data.marine.copernicus.eu/register).
2. Set `CMEMS_USER` to your username and `CMEMS_PWD` to your password.
3. Check them, which prints `True` if they are valid:
```python
import os

import copernicusmarine

print(copernicusmarine.login(os.environ["CMEMS_USER"], os.environ["CMEMS_PWD"], check_credentials_valid=True))
```

If you prefer, leave the variables unset and store your credentials once with `copernicusmarine login`. The toolbox then finds them in `~/.copernicusmarine/`. The startup log still says that they are incomplete, which you can ignore.

### Climate Data Store (ERA5)

1. Create a free account at the [Climate Data Store](https://cds.climate.copernicus.eu/how-to-api).
2. Copy your *Personal Access Token* from your profile page, and set `CDS_API_KEY` to it.
3. Open the page of each dataset you need, such as [ERA5 single levels](https://cds.climate.copernicus.eu/datasets/reanalysis-era5-single-levels?tab=download), and accept its Terms of Use. Downloads are refused until you do.

At its first download, Architeuthis writes the token to `~/.cdsapirc` and uses this file from then on. **If you change your token, update or delete this file**, because a new `CDS_API_KEY` does not replace it. You can also write the file yourself and leave the variable unset:
```text
url: https://cds.climate.copernicus.eu/api
key: your-personal-access-token
```

### EUMETSAT (satellite observations)

Despite their names, these two variables do **not** take your login and password:

1. Create a free EUMETSAT account and open the [API key page](https://api.eumetsat.int/api-key/).
2. Set `EUMDAC_USER` to your **Consumer Key** and `EUMDAC_PWD` to your **Consumer Secret**.
3. Check them, which prints an expiry date, or `401 Unauthorized` if they are wrong:
```python
import os

import eumdac

print(eumdac.AccessToken((os.environ["EUMDAC_USER"], os.environ["EUMDAC_PWD"])).expiration)
```

`eumdac set-credentials` configures the `eumdac` command only, so Architeuthis still needs the variables.

---

### 🗺️ The Routing Problem

- **Objective:** total fuel, `sum((bhp + hotel_load) * dt * sfc) / 1e6`, in tonnes.
- **Fixed:** departure and arrival, ETD and ETA. The schedule is a requirement and the speed is an outcome.
- **Variables:** the positions along the route, and optionally a factor that blends the reference route towards two alternatives.
- **Constraints:** by default, brake power below `max_power` and every position in navigable water. Your own constraints can replace them.

---

### 🙏 Credits

Architeuthis stands on the work of others, with our thanks:

- **[AeroSandbox](https://github.com/peterdsharpe/AeroSandbox)** by Peter D. Sharpe (MIT). The `numpy`, `optimization` and `modeling` packages are adapted from version 4.2.9 ([license](https://github.com/peterdsharpe/AeroSandbox/blob/master/LICENSE.txt)).
- **[Herbie](https://github.com/blaylockbk/Herbie)** by Brian K. Blaylock (MIT) downloads the forecasts, and its README inspired the structure of this one.
- **[CasADi](https://github.com/casadi/casadi)** (LGPL) and **[IPOPT](https://github.com/coin-or/Ipopt)** (EPL) differentiate and solve the routing problem.
- **[Copernicus Marine Toolbox](https://github.com/mercator-ocean/copernicus-marine-toolbox)**, **[xarray](https://github.com/pydata/xarray)**, **[Cartopy](https://github.com/SciTools/cartopy)**, **[Dash](https://github.com/plotly/dash)** and the other dependencies listed in [`pyproject.toml`](pyproject.toml).
- The forecast, ocean, relief and ice data of their providers.

## Contributing

Please report bugs or suggest features via [issues](https://github.com/julesricheux/Architeuthis/issues) and **star this repository!** ⭐

## ⚖️ License

[GNU General Public License v3.0](LICENSE).
