Metadata-Version: 2.4
Name: oafuncs
Version: 0.0.99.80
Summary: Oceanic and Atmospheric Functions
Home-page: https://github.com/OAKun/OAFuncs
Author: Kun Liu
Author-email: liukun0312@stu.ouc.edu.cn
License: MIT
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Requires-Python: >=3.10.0
Description-Content-Type: text/markdown
License-File: LICENSE.txt
Requires-Dist: rich
Provides-Extra: data
Requires-Dist: numpy; extra == "data"
Requires-Dist: scipy; extra == "data"
Requires-Dist: pandas; extra == "data"
Requires-Dist: xarray; extra == "data"
Provides-Extra: interp
Requires-Dist: numpy; extra == "interp"
Requires-Dist: xarray; extra == "interp"
Requires-Dist: xesmf; extra == "interp"
Requires-Dist: stratify; python_version >= "3.11" and extra == "interp"
Requires-Dist: pyinterp; python_version >= "3.11" and extra == "interp"
Provides-Extra: nc
Requires-Dist: numpy; extra == "nc"
Requires-Dist: xarray; extra == "nc"
Requires-Dist: dask; extra == "nc"
Requires-Dist: netCDF4; extra == "nc"
Provides-Extra: draw
Requires-Dist: cftime; extra == "draw"
Requires-Dist: imageio; extra == "draw"
Requires-Dist: numpy; extra == "draw"
Requires-Dist: matplotlib; extra == "draw"
Requires-Dist: cartopy; extra == "draw"
Requires-Dist: opencv-python; extra == "draw"
Requires-Dist: pillow; extra == "draw"
Provides-Extra: geo
Requires-Dist: global-land-mask; extra == "geo"
Requires-Dist: numpy; extra == "geo"
Requires-Dist: xarray; extra == "geo"
Requires-Dist: geopandas; extra == "geo"
Requires-Dist: rasterio; extra == "geo"
Requires-Dist: salem; extra == "geo"
Provides-Extra: down
Requires-Dist: numpy; extra == "down"
Requires-Dist: beautifulsoup4; extra == "down"
Requires-Dist: httpx; extra == "down"
Requires-Dist: lxml; extra == "down"
Requires-Dist: openpyxl; extra == "down"
Requires-Dist: pandas; extra == "down"
Requires-Dist: requests; extra == "down"
Requires-Dist: xarray; extra == "down"
Requires-Dist: netCDF4; extra == "down"
Requires-Dist: matplotlib; extra == "down"
Requires-Dist: xlrd; extra == "down"
Provides-Extra: sign
Requires-Dist: beautifulsoup4; extra == "sign"
Requires-Dist: lxml; extra == "sign"
Requires-Dist: requests; extra == "sign"
Provides-Extra: office
Requires-Dist: openpyxl; extra == "office"
Requires-Dist: xlrd; extra == "office"
Requires-Dist: python-docx; extra == "office"
Requires-Dist: python-pptx; extra == "office"
Provides-Extra: parallel
Requires-Dist: psutil; extra == "parallel"
Requires-Dist: dask; extra == "parallel"
Provides-Extra: all
Requires-Dist: beautifulsoup4; extra == "all"
Requires-Dist: cartopy; extra == "all"
Requires-Dist: cftime; extra == "all"
Requires-Dist: dask; extra == "all"
Requires-Dist: geopandas; extra == "all"
Requires-Dist: global-land-mask; extra == "all"
Requires-Dist: httpx; extra == "all"
Requires-Dist: imageio; extra == "all"
Requires-Dist: lxml; extra == "all"
Requires-Dist: matplotlib; extra == "all"
Requires-Dist: netCDF4; extra == "all"
Requires-Dist: numpy; extra == "all"
Requires-Dist: opencv-python; extra == "all"
Requires-Dist: openpyxl; extra == "all"
Requires-Dist: pandas; extra == "all"
Requires-Dist: pillow; extra == "all"
Requires-Dist: psutil; extra == "all"
Requires-Dist: pyinterp; python_version >= "3.11" and extra == "all"
Requires-Dist: python-docx; extra == "all"
Requires-Dist: python-pptx; extra == "all"
Requires-Dist: rasterio; extra == "all"
Requires-Dist: requests; extra == "all"
Requires-Dist: salem; extra == "all"
Requires-Dist: scipy; extra == "all"
Requires-Dist: stratify; python_version >= "3.11" and extra == "all"
Requires-Dist: xarray; extra == "all"
Requires-Dist: xesmf; extra == "all"
Requires-Dist: xlrd; extra == "all"
Provides-Extra: full
Requires-Dist: beautifulsoup4; extra == "full"
Requires-Dist: cartopy; extra == "full"
Requires-Dist: cftime; extra == "full"
Requires-Dist: dask; extra == "full"
Requires-Dist: geopandas; extra == "full"
Requires-Dist: global-land-mask; extra == "full"
Requires-Dist: httpx; extra == "full"
Requires-Dist: imageio; extra == "full"
Requires-Dist: lxml; extra == "full"
Requires-Dist: matplotlib; extra == "full"
Requires-Dist: netCDF4; extra == "full"
Requires-Dist: numpy; extra == "full"
Requires-Dist: opencv-python; extra == "full"
Requires-Dist: openpyxl; extra == "full"
Requires-Dist: pandas; extra == "full"
Requires-Dist: pillow; extra == "full"
Requires-Dist: psutil; extra == "full"
Requires-Dist: pyinterp; python_version >= "3.11" and extra == "full"
Requires-Dist: python-docx; extra == "full"
Requires-Dist: python-pptx; extra == "full"
Requires-Dist: rasterio; extra == "full"
Requires-Dist: requests; extra == "full"
Requires-Dist: salem; extra == "full"
Requires-Dist: scipy; extra == "full"
Requires-Dist: stratify; python_version >= "3.11" and extra == "full"
Requires-Dist: xarray; extra == "full"
Requires-Dist: xesmf; extra == "full"
Requires-Dist: xlrd; extra == "full"
Provides-Extra: science
Requires-Dist: cartopy; extra == "science"
Requires-Dist: cftime; extra == "science"
Requires-Dist: dask; extra == "science"
Requires-Dist: geopandas; extra == "science"
Requires-Dist: global-land-mask; extra == "science"
Requires-Dist: imageio; extra == "science"
Requires-Dist: matplotlib; extra == "science"
Requires-Dist: netCDF4; extra == "science"
Requires-Dist: numpy; extra == "science"
Requires-Dist: opencv-python; extra == "science"
Requires-Dist: pandas; extra == "science"
Requires-Dist: pillow; extra == "science"
Requires-Dist: pyinterp; python_version >= "3.11" and extra == "science"
Requires-Dist: rasterio; extra == "science"
Requires-Dist: salem; extra == "science"
Requires-Dist: scipy; extra == "science"
Requires-Dist: stratify; python_version >= "3.11" and extra == "science"
Requires-Dist: xarray; extra == "science"
Requires-Dist: xesmf; extra == "science"
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license
Dynamic: license-file
Dynamic: provides-extra
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary


# OAFuncs

Oceanic and Atmospheric Functions for Python.

OAFuncs provides utilities for NetCDF files, geoscience arrays, plotting, geographic masks, downloads, file operations, model data, and small workflow tools.

## Installation

```bash
pip install oafuncs
```

Optional dependency groups:

```text
data, interp, nc, draw, geo, down, office, parallel, sign, science, all, full
```

## Package Structure

```text
oafuncs
|-- oa_nc.py          NetCDF utilities
|-- oa_data.py        data interpolation helpers
|-- oa_geo.py         geographic and land/ocean mask helpers
|-- oa_draw.py        plotting helpers
|-- oa_cmap.py        colormap helpers
|-- oa_file.py        file operation helpers
|-- oa_date.py        date and timing helpers
|-- oa_down/          download helpers
|-- oa_model/         ROMS and WRF helpers
|-- oa_linux.py       Linux, HPC, and Slurm helpers
|-- oa_office.py      Office document helpers
|-- oa_sign/          website sign-in helpers
`-- oa_tool.py        command, progress bar, email, and parallel helpers
```

## Preserved Historical Version

`0.0.99.75` is the last version before the Slurm submission redesign on
2026-10-17. Its archive, `version/oafuncs_0.0.99.75/`, is a permanent reference
for the previous implementation. Preserve it during future cleanup: do not
delete, overwrite, or modify it, including when pruning other old versions.
The redesigned submission behavior below describes the current source tree.

## Public API

### `oa_nc`

| Function | Purpose |
| --- | --- |
| `save` | Write arrays, DataArrays, or Datasets to NetCDF. |
| `merge` | Merge multiple NetCDF files. |
| `modify` | Modify a variable value or variable attribute. |
| `rename` | Rename a NetCDF variable or dimension. |
| `check` | Check whether a NetCDF file is readable. |
| `convert_longitude` | Convert longitude coordinates between `[-180, 180]` and `[0, 360]`. |
| `isel` | Select NetCDF data by dimension indices. |
| `draw` | Plot variables from a NetCDF file or xarray object. |
| `compress` | Apply NetCDF compression. |
| `unscale` | Decode and remove `scale_factor` / `add_offset` storage encoding. |

`oa_nc.save(write_mode="w")` and `oa_nc.merge()` write to a temporary file
before replacing an existing output. Failed writes preserve the previous file;
append mode (`write_mode="a"`) still updates the file in place.
For NumPy input, `preserve_mask_values=True` keeps underlying masked values;
`False` writes them as missing values. Explicit `missing_value` values remain
missing even for integer arrays, and all-NaN arrays remain missing rather than 0.
`merge()` preserves raw encoding and input order: units, calendars, non-merged
variables and non-merged coordinates must match. It raises `ValueError` for
incompatible inputs rather than converting time units or aligning different grids.
`oa_nc.rename()` checks variable and dimension name conflicts before changing
either name, so a conflicting coordinate rename does not leave a partial change.

`oa_nc.draw()` accepts a NetCDF path, `xarray.Dataset`, or `xarray.DataArray`.
File-backed workloads use `parallel="auto"` by default: small batches remain
serial, while sufficiently large collections of independent 2D slices use
`oafuncs.oa_tool.Pex` with automatic worker sizing. Use `parallel=False` to
force serial rendering or `parallel=True` to force Pex. The default output is
300 dpi; set `save_dpi=600` when publication-resolution PNG files are needed.
On Windows, Pex calls must run below an `if __name__ == "__main__":` guard.
For strongly skewed fields, `robust=True` displays colors using the 1st–99th
percentiles while the title continues to report the complete data range and
the clipped color range. The default `robust=False` preserves the full range.

### `oa_data`

| Function | Purpose |
| --- | --- |
| `ensure_list` | Convert a value to a list. |
| `interp_vertical` | Interpolate along the fixed `z` axis in `[t, z, y, x]` order. |
| `interp_horizontal` | Interpolate horizontal fields over the fixed `y, x` axes. |

### `oa_geo`

`mask_land_ocean()` preserves existing xarray dimension coordinates, including
x/y grid indices. Longitude and latitude describe mask positions; two-dimensional
longitude/latitude arrays must have the same shape.

| Function | Purpose |
| --- | --- |
| `earth_distance` | Calculate great-circle distance between two lon/lat points. |
| `mask_shapefile` | Mask a 2D field using a shapefile. |
| `mask_land_ocean` | Keep land or ocean values using a land/ocean mask. |
| `is_land` | Check whether a lon/lat point is on land. |
| `is_ocean` | Check whether a lon/lat point is in the ocean. |

### `oa_draw`

| Function | Purpose |
| --- | --- |
| `fig_minus` | Format minus signs in axis or colorbar tick labels. |
| `gif` | Create a GIF from image files. |
| `movie` | Create a video from image files. |
| `setup_map` | Configure a Cartopy map. |
| `ticks_symmetric` | Generate ticks symmetric around a center value. |
| `font_CN` | Configure Chinese font support for Matplotlib. |

`oa_draw.gif(frame_duration=0.2)` uses seconds (200 milliseconds per frame).
Unreadable images are skipped; automatic dimensions come from the first readable image.

### `oa_cmap`

| Function | Purpose |
| --- | --- |
| `show` | Preview a colormap. |
| `to_color` | Convert a colormap to a list of colors. |
| `create` | Create a custom colormap from colors or an RGB text file. |
| `get` | Load a Matplotlib or custom colormap. |
| `random_color` | Generate a random hex color. |

### `oa_file`

| Function | Purpose |
| --- | --- |
| `script_dir` | Return the caller script directory, independent of current working directory. |
| `find` | Find files by pattern. |
| `link` | Create symbolic links with wildcard support. |
| `copy` | Copy files or directories with wildcard support. |
| `move` | Move files or directories with wildcard support. |
| `rename` | Rename files by replacing text in filenames. |
| `remove_empty` | Remove empty folders recursively. |
| `remove` | Delete files or directories by pattern. |
| `size` | Return file size in the requested unit. |
| `replace` | Replace text content by `replace_dict` and save it to `output_path`. |

Example:

```python
from oafuncs.oa_file import replace

replace(
    input_path="config.txt",
    replace_dict={"old_value": "new_value"},
    output_path=r"D:\run\config.txt",
)
```

`output_path` accepts either a file name, such as `"result.txt"`, or an absolute
file path. A file name is saved in the input file's directory. Directory-only paths
and relative paths containing directories are rejected to avoid ambiguous output.
Both direct-text and key-value replacement atomically replace the output path
and preserve permission bits. A file link at that path becomes an independent
file, leaving its source and other links unchanged. Outputs inside symbolic-link
directories or Windows junctions are rejected to avoid modifying source folders.
`replace()` still returns `None` on success. Read/write, permission and atomic
replacement errors now propagate to the caller, including `FileNotFoundError`,
`PermissionError` and other `OSError` exceptions; directory-link rejection raises
`ValueError`. Unmatched text or keys retain the existing no-op behavior.

### `oa_date`

| Function / Class | Purpose |
| --- | --- |
| `month_days` | Return the number of days in a month. |
| `hour_range` | Generate datetime strings at an hourly interval. |
| `adjust_time` | Offset a base time by a time delta. |
| `timeit` | Decorator class for timing function execution. |

### `oa_down`

| Function | Purpose |
| --- | --- |
| `download` | Download HYCOM data for one time or a time range. |
| `draw_time_range` | Draw the available HYCOM time range. |
| `download_by_doi` | Download literature files from DOI lists. |
| `downloader` | Send a download task to Internet Download Manager. |
| `get_ua` | Return the default user-agent string for download requests. |

Literature downloads require a PDF header and the configured `min_size_kb`
before replacing an existing file. This is a basic content check, not a full
PDF integrity check. Each mirror receives its own `tries_each_url` budget;
an HTTP failure moves on to the next mirror and failure records are deduplicated.

For `hycom_3hourly.download()`, `interval_hours` for a time range must be a positive multiple of 3. When using IDM, `idm_timeout` controls how many seconds without a completed valid file are allowed before remaining files are reported as failed; it defaults to 24 hours.

### `oa_model`

| Function | Purpose |
| --- | --- |
| `interp` | Interpolate and remap ROMS data to target lon/lat/depth grids. |
| `header_record` | Build a WRF little_r header record. |
| `data_record` | Build a WRF little_r data record. |
| `ending_record` | Build a WRF little_r ending record. |
| `tail_record` | Build a WRF little_r tail record. |

The little_r helpers retain the existing fixed-width record layout and units.
Overlong text fields are truncated to their documented widths; invalid record
lengths raise `ValueError` rather than terminating the caller's Python process.

### `oa_linux`

| Function | Purpose |
| --- | --- |
| `patch_slurm` | Update selected `#SBATCH` directives in a Slurm script. |
| `submit` | Submit one Slurm job to one or more eligible partitions. |
| `job_state` | Query compact live or historical Slurm job state. |
| `is_linux` | Check whether the current system is Linux. |
| `get_jobid` | Return an ID only when exactly one current-user active job matches. |
| `scancel_dir` | Request cancellation of active jobs in one exact directory. |

`patch_slurm()` replaces selected options in place without accumulating blank
lines, preserving other options on the same line and trailing comments. Duplicate
target options are removed; empty directive lines are dropped and comments are
retained as ordinary comments. Resource counts must be positive integers;
booleans, strings, fractions and nonpositive counts return `False` without
writing the script. Updates are
written to a sibling temporary file and atomically replace the supplied script
path, preserving permission bits. A symbolic or hard link at that path becomes
an independent file; its source and other links keep their original contents.
This also applies to script updates made by `submit()`.
Paths inside symbolic-link directories or Windows junctions are rejected.

```python
from oafuncs import oa_linux

job_id = oa_linux.submit(
    workdir='/path/to/case',
    queues=['cpu_parallel', 'bigmem', 'dcu'],
    max_jobs=50,
    wait_start=False,
    wait_done=False,
)
```

`submit()` filters candidates by partition visibility, node count, time limit,
and the per-user debug submission cap. It submits exactly one job: multiple
eligible queues use a shared request such as
`#SBATCH -p cpu_parallel,bigmem,dcu`; one queue uses a single partition.
No replicas, queue switching, or cancellation/resubmission are performed.

Node limits are read dynamically from partition `MinNodes`/`MaxNodes`, including
for custom queue names. An explicit node count or range must overlap the
partition's allowed range; a queue with `MaxNodes=1` is therefore excluded from
an eight-node request without any name preset. If no `-N`/`--nodes` is specified,
Slurm chooses the node count from the remaining resource request. On failed or
incomplete queries, known single-node queues retain their fallback restriction;
unknown limits produce a warning and remain for Slurm to validate. Account/QOS
limits are still checked by Slurm, not inferred from partition node limits.

For multiple eligible queues, an existing active `#SBATCH --mem` is capped at
the smallest queue-specific safe request, normally the smallest configured
node memory minus 15 GiB (with existing conservative fallback values when the
query fails). Lower requests are preserved. If `--mem` is absent, memory
settings are left unchanged, including any `--mem-per-cpu` directive.
A single eligible queue adjusts memory only when an active `--mem` directive
already exists. For either queue count, missing or commented-out `--mem`
directives are never added or enabled; scheduler defaults apply when unset.
Explicit `--mem-per-cpu` or `--mem-per-gpu` settings are preserved for either
queue count; conflicting memory modes are rejected before writing the script.
Including `debug` sets the shared job time limit to `02:00:00`.

`max_jobs` counts the current user's jobs and waits for one submission slot.
Both waiting options default to False:
the job ID is returned immediately after submission. `wait_start=True` waits
until the job is observed running; `wait_done=True` waits for confirmed
successful completion and takes precedence over `wait_start`. If a short job
finishes between polls, accounting must confirm success before either waiting
mode returns its ID. Failure or an unconfirmed final state returns `None`.
A failed live-state query also checks accounting for a confirmed final state;
it does not trigger resubmission. The original working
directory is restored on exit.

The obsolete `race`, `race_queues`, `timeout`, and `wait` arguments have been
removed. Remove the first three from existing calls and replace `wait=True`
with `wait_done=True`. Multi-partition submission is internal to
`submit()` and has no separate public function.

Job matching uses one current-user query and includes configuring, completing,
suspended and other active states. Path filters accept `~`, relative paths and
paths containing spaces; matches are exact, with no recursive directory or
prefix matching. `get_jobid()` returns `None` for zero or multiple matches.
Query/parse failures raise `RuntimeError` in matching and cancellation APIs.
The former `dir_queued()` has been removed. Use `get_jobid(path=workdir)` when
each directory has at most one active job; remember that `None` also means
multiple matches, so it is not a general test for an empty directory.

`job_state(job_id)` checks accounting when the live state is unavailable and
returns compact codes such as `PD`, `R`, `CD`, `F` or `CA`. Set `history=False`
for live queries only. No available record returns `None`; an unresolved query
failure raises `RuntimeError`, and an invalid numeric ID raises `ValueError`.
`CD` reports scheduler state; completion waiting also validates exit codes.

Use Slurm's `scancel job_id` for a single job. The former public `cancel_job()`
has been removed. `scancel_dir(path)` cancels only current-user active jobs
whose WorkDir exactly matches the directory; names, shared script locations
and child directories do not match. Returned IDs indicate that `scancel`
accepted the request; cleanup may still be in progress. Query or cancellation
failures raise `RuntimeError`.

### `oa_office`

| Function | Purpose |
| --- | --- |
| `extract_pptx_notes` | Extract PowerPoint notes to a Word document. |

### `oa_sign`

| Function | Purpose |
| --- | --- |
| `sign_in_love_ocean` | Sign in to 52ocean. |
| `sign_in_meteorological_home` | Sign in to Meteorological Home. |
| `sign_in_scientific_research` | Sign in to AbleSci. |

### `oa_tool`

On Windows, `Pex` uses its own spawn context without changing the caller's
global multiprocessing start method. Calls still require the main guard.

`Pex()` chooses workers from CPU limits and current memory headroom: the minimum
of host available memory, every visible ancestor cgroup's remaining allowance,
and the local Slurm allocation minus current job usage. It leaves 2% headroom
unassigned. Clean inactive file cache is conservatively treated as reclaimable;
dirty/writeback pages are excluded. Slurm's allocation alone is never reported
as free memory. If job-cgroup usage is unavailable, a warning and
`memory_headroom_gb` statistics identify the process-tree fallback, which cannot
account for sibling job steps or unmapped file cache.

When task memory is unknown, the first real task runs alone; its result is kept
and its incremental memory cost, multiplied by 1.05, sizes the remaining pool
without a fixed minimum reservation. The Linux probe subtracts the pre-pool
parent PSS baseline from parent-plus-worker PSS, and also measures private worker
memory (USS). This excludes inherited shared pages while accounting for new
private/shared allocations. Small pools and explicit `max_workers` caps also
use this probe. Before creating the actual pool, headroom is refreshed and its
process count is capped using the full per-worker reservation, including startup
costs; this also covers all workers started together by Linux `fork`.

Tasks are submitted
individually, with at most one outstanding task per worker, and replenishment
pauses when current headroom is insufficient. Each worker reserves only the
growth still needed to reach its estimated peak; resident idle workers cannot
fund other workers' reservations. Unknown worker slots reserve their full cost,
and scheduling uses the largest eligible growth costs because the next worker
PID is not guaranteed. USS is preferred for reusable private memory, with
conservative PSS/RSS fallbacks. Later observed private worker peaks may raise
the estimate, including an explicit reservation that proves too small.
Already running tasks are allowed to finish. If no task is
running and the next estimated worker cannot fit, `MemoryError` is raised.
Sampling cannot guarantee protection from an unseen instantaneous task peak.
For known heavy or heterogeneous tasks, `memory_per_worker_gb` (also available
as `OAFUNCS_PEX_MEMORY_PER_WORKER_GB`) supplies the per-worker reservation in
GiB, including worker overhead, and skips probing. Calls and result order are
unchanged; worker counts remain automatic by default.

| Function / Class | Purpose |
| --- | --- |
| `run_command` | Run an external command safely without using a shell. |
| `Pex` | Parallel executor wrapper with progress and timing. `PEx` remains as a deprecated compatibility alias until 2026-12-22. |
| `email` | Send an email. |
| `pbar` | Create a colored progress bar. |

## Links

- PyPI: <https://pypi.org/project/oafuncs>
- GitHub: <https://github.com/OAKun/OAFuncs>
- Download stats: <https://pypistats.org/packages/oafuncs>
- Wiki: <https://deepwiki.com/OAKun/OAFuncs>

## License

MIT License. See [LICENSE.txt](LICENSE.txt).
