# pyGVEC

## python bindings - behind the scenes

```{figure} /static/pygvec_flowchart.png
:width: 80 %
:align: center
:alt: map to buried treasure

The schematic overview of the different layers that make up pyGVEC.
```

* The main part of GVEC is written in modern Fortran (>= Fortran 2003) and compiled as `gveclib`.
* The `gvec` and `gvec_post` executables are linked with that library.
* Because the interface between Fortran and Python only works via a C-ABI that does not support any *modern* features of Fortran, the bindings need several layers of wrappers:
  * A set of Fortran modules that define the API to be exposed to python. Here we can use all Fortran features that are supported by `f90wrap`.
  * Automatically generated Fortran 90 wrappers generated by [f90wrap](https://github.com/jameskermode/f90wrap).
  * Automatically generated C wrappers generated by [f2py](https://numpy.org/devdocs/f2py/index.html) (we use `f90wrap-f2py`).
  * Automatically generated Python wrappers generated by [f90wrap](https://github.com/jameskermode/f90wrap).
  * A set of python modules that encapsulate the wrappers to provide safety and convenience.
* The python package for GVEC is separated into several parts:
  * The compiled *extension* (`gveclib` + Fortran wrappers + C wrappers) as a shared-library object: `gvec._libpygvec`
  * The generated python wrapper: `gvec.lib`
  * A set of core modules, whose key functionality is exposed in the `gvec` namespace: `gvec.run`, `gvec.Run`, `gvec.State`, etc.
    * The encapsulation of the *state* for postprocessing: `gvec.core.state`
    * The encapsulation for running GVEC: `gvec.core.run`
    * The registration and recursive computation of quantities: `gvec.core.compute`
  * The built-in quantities: `gvec.quantities`
  * A general utility module: `gvec.util` (which may not depend on other modules of gvec!)
  * Additional utility modules: `gvec.fourier`, `gvec.surface`, `gvec.vtk`
  * A set of scripts:
    * The main entry point: `gvec.scripts.main` - `pygvec` & `pygvec convert-params`
    * Running GVEC (with stages, current optimization & diagnostics): `gvec.scripts.run` - `pygvec run`
    * Converting to CAS3D: `gvec.scripts.cas3d` - `pygvec to-cas3d`
    * Loading a QUASR configuration: `gvec.scripts.quasr` - `pygvec load-quasr`

## Development Installations

* The default `pip install` creates a temporary virtual environment for building the package. This so called "build isolation" means that every install will recompile GVEC from scratch.
* By specifying `--no-build-isolation` this feature can be disabled. `pip` will now use the current virtual environment and create a CMake build directory `pybuild`. There you can also inspect the generated wrappers.
* It is also possible to enable an "editable" install, although this feature (provided by `scikit_build_core` is still experimental). Then whenever you `import gvec` a rebuild is triggered.

The recommended installation for local development of the python bindings is therefore:
```bash
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
pip install --no-build-isolation -ve .
```

## pre-commit

To keep the python formatting consistent and prevent binary data within the jupyter notebooks to be committed, we use `pre-commit`:
```bash
pip install pre-commit
pre-commit install
```
Whenever you now commit, a *git hook* will run `ruff` and `nbclean` against all the staged files. It will also try to fix the formatting and clean the notebooks automatically, so you usually just have to `git add` the files that failed the hook again.
