[Git][debian-gis-team/netcdf4-python][master] 6 commits: New upstream version 1.7.1

Bas Couwenberg (@sebastic) gitlab at salsa.debian.org
Tue Jun 18 04:41:27 BST 2024



Bas Couwenberg pushed to branch master at Debian GIS Project / netcdf4-python


Commits:
60d82e28 by Bas Couwenberg at 2024-06-18T05:28:35+02:00
New upstream version 1.7.1
- - - - -
e2ea9a79 by Bas Couwenberg at 2024-06-18T05:28:37+02:00
Update upstream source from tag 'upstream/1.7.1'

Update to upstream version '1.7.1'
with Debian dir 61702372f86158f120b122bd251ac06c5d33a61f
- - - - -
e4f8d47f by Bas Couwenberg at 2024-06-18T05:30:47+02:00
New upstream release.

- - - - -
910a16d9 by Bas Couwenberg at 2024-06-18T05:32:13+02:00
Drop nc-complex.patch, included upstream.

- - - - -
984fb983 by Bas Couwenberg at 2024-06-18T05:34:35+02:00
Update copyright file.

- - - - -
d36b49bb by Bas Couwenberg at 2024-06-18T05:34:52+02:00
Set distribution to unstable.

- - - - -


16 changed files:

- − .ci/build_deps.sh
- .github/workflows/cibuildwheel.yml
- .gitmodules
- Changelog
- MANIFEST.in
- README.md
- debian/changelog
- debian/copyright
- − debian/patches/nc-complex.patch
- debian/patches/series
- + external/README
- + external/nc_complex/include/generated_fallbacks/nc_complex_version.h
- + external/nc_complex/include/nc_complex/nc_complex.h
- + external/nc_complex/src/nc_complex.c
- pyproject.toml
- src/netCDF4/_netCDF4.pyx


Changes:

=====================================
.ci/build_deps.sh deleted
=====================================
@@ -1,28 +0,0 @@
-#!/usr/bin/bash
-
-set -ex
-
-
-download_and_build_netcdf() {
-  if [ ! -d "netcdf-c" ]; then
-      netcdf_url=https://github.com/Unidata/netcdf-c
-      netcdf_src=netcdf-c
-      netcdf_build=netcdf-build
-
-      git clone ${netcdf_url} -b v4.9.2 ${netcdf_src}
-
-      cmake ${netcdf_src} -B ${netcdf_build} \
-            -DENABLE_NETCDF4=on \
-            -DENABLE_HDF5=on \
-            -DENABLE_DAP=on \
-            -DENABLE_TESTS=off \
-            -DENABLE_PLUGIN_INSTALL=off \
-            -DBUILD_SHARED_LIBS=on \
-            -DCMAKE_BUILD_TYPE=Release
-
-      cmake --build ${netcdf_build} \
-            --target install
-fi
-}
-
-download_and_build_netcdf


=====================================
.github/workflows/cibuildwheel.yml
=====================================
@@ -47,14 +47,16 @@ jobs:
   build_bdist:
     name: "Build ${{ matrix.os }} (${{ matrix.arch }}) wheels"
     runs-on: ${{ matrix.os }}
+    # Prevent hanging when building from emulation like aarch64.
+    timeout-minutes: 300
     strategy:
       fail-fast: false
       matrix:
         include:
           - os: ubuntu-22.04
             arch: x86_64
-          # - os: ubuntu-22.04
-          #   arch: aarch64
+          - os: ubuntu-22.04
+            arch: aarch64
           - os: macos-14
             arch: arm64
             CIBW_ENVIRONMENT: MACOSX_DEPLOYMENT_TARGET=14.0
@@ -66,8 +68,14 @@ jobs:
     - uses: actions/checkout at v4
       with:
         fetch-depth: 0
-        submodules: 'true'
-    
+
+    # For aarch64 support
+    # https://cibuildwheel.pypa.io/en/stable/faq/#emulation
+    - uses: docker/setup-qemu-action at v3
+      with:
+        platforms: all
+      if: runner.os == 'Linux' && matrix.arch == 'aarch64'
+
     - name: Build oldest and newest Python
       shell: bash
       # On PRs we run only oldest and newest Python versions to reduce CI load.
@@ -84,22 +92,23 @@ jobs:
         echo "Setting CIBW_SKIP=$CIBW_SKIP"
 
     - name: "Building ${{ matrix.os }} (${{ matrix.arch }}) wheels"
-      uses: pypa/cibuildwheel at v2.18.1
+      uses: pypa/cibuildwheel at v2.19.1
       env:
         CIBW_SKIP: ${{ env.CIBW_SKIP }}
         CIBW_ARCHS: ${{ matrix.arch }}
         CIBW_BUILD_FRONTEND: build
-        CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28
-        CIBW_BEFORE_BUILD_LINUX: >
-          dnf install -y epel-release
-          && dnf install -y hdf5-devel libcurl-devel
-          && sh .ci/build_deps.sh
+        CIBW_MANYLINUX_X86_64_IMAGE: ghcr.io/ocefpaf/manylinux2014_x86_64-netcdf
+        CIBW_MANYLINUX_AARCH64_IMAGE: ghcr.io/ocefpaf/manylinux2014_aarch64-netcdf
+        # Emulation testing is slow, testing only latest Python.
+        CIBW_TEST_SKIP: "cp38-*_aarch64 cp39-*_aarch64 cp310-*_aarch64 cp311-*_aarch64"
         CIBW_ENVIRONMENT: ${{ matrix.CIBW_ENVIRONMENT }}
         CIBW_BEFORE_BUILD_MACOS: brew install hdf5 netcdf
         CIBW_TEST_REQUIRES: pytest cython packaging
         CIBW_TEST_COMMAND: >
           python -c "import netCDF4; print(f'netCDF4 v{netCDF4.__version__}')"
           && pytest -s -rxs -v {project}/test
+          && URL="https://icdc.cen.uni-hamburg.de/thredds/dodsC/ftpthredds/hamtide/m2.hamtide11a.nc"
+          && python -c "from netCDF4 import Dataset; nc=Dataset(\"${URL}\"); print(nc)"
 
     - uses: actions/upload-artifact at v4
       with:
@@ -119,7 +128,6 @@ jobs:
       - uses: actions/checkout at v4
         with:
           fetch-depth: 0
-          submodules: 'true'
 
       - uses: actions/setup-python at v4
         name: Install Python


=====================================
.gitmodules
=====================================
@@ -1,3 +0,0 @@
-[submodule "external/nc_complex"]
-	path = external/nc_complex
-	url = https://github.com/PlasmaFAIR/nc-complex.git


=====================================
Changelog
=====================================
@@ -1,3 +1,8 @@
+ version 1.7.1 (tag v1.7.1rel)
+===============================
+ * include nc_complex source code from v0.2.0 tag (instead of using submodule).
+ * add aarch64 wheels.
+
  version 1.7.0 (tag v1.7.0rel)
 ===============================
  * add support for complex numbers via `auto_complex` keyword to `Dataset` (PR #1295)


=====================================
MANIFEST.in
=====================================
@@ -1,5 +1,6 @@
 include docs/index.html
 recursive-include man *
+recursive-include external *
 include MANIFEST.in
 include README.htmldocs
 include Changelog
@@ -17,6 +18,9 @@ include src/netCDF4/plugins/empty.txt
 include include/netCDF4.pxi
 include include/mpi-compat.h
 include include/membuf.pyx
+include include/netcdf-compat.h
+include include/no_parallel_support_imports.pxi.in
+include include/parallel_support_imports.pxi.in
 include *.md
 include *.py
 include *.release


=====================================
README.md
=====================================
@@ -10,7 +10,9 @@
 ## News
 For details on the latest updates, see the [Changelog](https://github.com/Unidata/netcdf4-python/blob/master/Changelog).
 
-06/??/2024: Version [1.7.0](https://pypi.python.org/pypi/netCDF4/1.7.0) released. Add support for complex numbers via `auto_complex` keyword to `Dataset` ([PR #1295](https://github.com/Unidata/netcdf4-python/pull/1295))
+06/17/2024: Version [1.7.1](https://pypi.python.org/pypi/netCDF4/1.7.1) released. Fixes for wheels, no code changes.
+
+06/13/2024: Version [1.7.0](https://pypi.python.org/pypi/netCDF4/1.7.0) released. Add support for complex numbers via `auto_complex` keyword to `Dataset` ([PR #1295](https://github.com/Unidata/netcdf4-python/pull/1295))
 
 10/20/2023: Version [1.6.5](https://pypi.python.org/pypi/netCDF4/1.6.5) released. 
 Fix for issue #1271 (mask ignored if bool MA assinged to uint8 var), 


=====================================
debian/changelog
=====================================
@@ -1,3 +1,11 @@
+netcdf4-python (1.7.1-1) unstable; urgency=medium
+
+  * New upstream release.
+  * Drop nc-complex.patch, included upstream.
+  * Update copyright file.
+
+ -- Bas Couwenberg <sebastic at debian.org>  Tue, 18 Jun 2024 05:34:38 +0200
+
 netcdf4-python (1.7.0-1) unstable; urgency=medium
 
   * New upstream release.


=====================================
debian/copyright
=====================================
@@ -7,6 +7,10 @@ Files: *
 Copyright: 2008, Jeffrey Whitaker
 License: Expat
 
+Files: external/nc_complex/*
+Copyright: 2023, Peter Hill
+License: Expat
+
 Files: debian/*
 Copyright: 2015, Ross Gammon <rossgammon at mail.dk>
 License: ISC


=====================================
debian/patches/nc-complex.patch deleted
=====================================
@@ -1,1177 +0,0 @@
-Description: Add nc-complex sources.
-Origin: https://github.com/PlasmaFAIR/nc-complex/tree/37310ed00f3910974bdefefcdfa4787588651f59
-Bug: https://github.com/Unidata/netcdf4-python/issues/1331
-
---- /dev/null
-+++ b/external/nc_complex/include/generated_fallbacks/nc_complex_version.h
-@@ -0,0 +1,6 @@
-+// This is a fallback header for when building the library without
-+// CMake -- you probably should use CMake to auto-generate this instead
-+#define NC_COMPLEX_GIT_SHA1 "unknown"
-+#define NC_COMPLEX_GIT_VERSION "0.1.0"
-+#define NC_COMPLEX_GIT_STATE "unknown"
-+#define NC_COMPLEX_GIT_DATE "unknown"
---- /dev/null
-+++ b/external/nc_complex/include/nc_complex/nc_complex.h
-@@ -0,0 +1,291 @@
-+/// nc-complex: A lightweight, drop-in extension for complex number support in
-+/// netCDF
-+///
-+/// Copyright (C) 2023 Peter Hill
-+///
-+/// SPDX-License-Identifier: MIT
-+
-+#ifndef PLASMA_FAIR_NC_COMPLEX
-+#define PLASMA_FAIR_NC_COMPLEX
-+
-+// This header is required when building as a DLL on Windows and is
-+// automatically generated by CMake. If you're not using CMake (and
-+// not on Windows) for some reason, then define `NC_COMPLEX_NO_EXPORT`
-+// to skip this.
-+#ifndef NC_COMPLEX_NO_EXPORT
-+#include "nc_complex/nc_complex_export.h"
-+#else
-+#define NC_COMPLEX_EXPORT
-+#endif
-+
-+#include <complex.h>
-+#include <netcdf.h>
-+#include <stdbool.h>
-+#include <stddef.h>
-+
-+#ifdef __cplusplus
-+#include <complex>
-+#endif
-+
-+//@{
-+/// Portable typedefs for complex numbers
-+///
-+/// These become aliases for `std::complex` with C++.
-+#ifdef _MSC_VER
-+typedef _Dcomplex double_complex;
-+typedef _Fcomplex float_complex;
-+#else
-+#if defined(__cplusplus) && defined(__clang__)
-+using double_complex = std::complex<double>;
-+using float_complex = std::complex<float>;
-+#else
-+typedef double _Complex double_complex;
-+typedef float _Complex float_complex;
-+#endif
-+#endif
-+//@}
-+
-+#ifdef __cplusplus
-+/// @name Helper functions
-+///@{
-+/// Helper functions for converting between (pointers to) C++ and C complex types
-+NC_COMPLEX_EXPORT inline double_complex* cpp_to_c_complex(std::complex<double>* data) {
-+    return reinterpret_cast<double_complex*>(data);
-+}
-+
-+NC_COMPLEX_EXPORT inline std::complex<double>* c_to_cpp_complex(double_complex* data) {
-+    return reinterpret_cast<std::complex<double>*>(data);
-+}
-+
-+NC_COMPLEX_EXPORT inline float_complex* cpp_to_c_complex(std::complex<float>* data) {
-+    return reinterpret_cast<float_complex*>(data);
-+}
-+
-+NC_COMPLEX_EXPORT inline std::complex<float>* c_to_cpp_complex(float_complex* data) {
-+    return reinterpret_cast<std::complex<float>*>(data);
-+}
-+///@}
-+extern "C" {
-+#endif
-+
-+/// @name Complex datatype defines
-+/// Datatype for complex numbers, for use with \rstref{pfnc_def_var}
-+///  
-+/// @note
-+/// These *only* work when defining a variable with \rstref{pfnc_def_var}. To
-+/// check the type of an existing variable use \rstref{pfnc_var_is_complex}, and
-+/// to check if it is specifically using a compound datatype or a dimension use
-+/// \rstref{pfnc_var_is_complex_type} or \rstref{pfnc_var_has_complex_dimension}
-+/// respectively.
-+/// @endnote  
-+///@{  
-+
-+/// Uses complex compound datatype with netCDF4 format, and complex dimension otherwise
-+#define PFNC_FLOAT_COMPLEX (NC_FIRSTUSERTYPEID - 4)
-+/// Always use a complex dimension, regardless of file format
-+#define PFNC_FLOAT_COMPLEX_DIM (NC_FIRSTUSERTYPEID - 3)
-+/// Uses complex compound datatype with netCDF4 format, and complex dimension otherwise
-+#define PFNC_DOUBLE_COMPLEX (NC_FIRSTUSERTYPEID - 2)
-+/// Always use a complex dimension, regardless of file format
-+#define PFNC_DOUBLE_COMPLEX_DIM (NC_FIRSTUSERTYPEID - 1)
-+///@}
-+
-+/// Return true if variable is complex
-+NC_COMPLEX_EXPORT bool pfnc_var_is_complex(int ncid, int varid);
-+/// Return true if variable is complex and uses a compound datatype
-+NC_COMPLEX_EXPORT bool pfnc_var_is_complex_type(int ncid, int varid);
-+/// Return true if variable is complex and has a complex dimension
-+/// (assumed to be the last dimension)
-+NC_COMPLEX_EXPORT bool pfnc_var_has_complex_dimension(int ncid, int varid);
-+
-+/// Return true if dimension is complex
-+NC_COMPLEX_EXPORT bool pfnc_is_complex_dim(int ncid, int dim_id);
-+
-+/// Get the ID for the complex datatype with `double` elements, creating it if it doesn't already exist
-+NC_COMPLEX_EXPORT int pfnc_get_double_complex_typeid(int ncid, nc_type* nc_typeid);
-+/// Get the ID for the complex datatype with `float` elements, creating it if it doesn't already exist
-+NC_COMPLEX_EXPORT int pfnc_get_float_complex_typeid(int ncid, nc_type* nc_typeid);
-+
-+/// Get complex dimension, creating one if it doesn't already exist
-+NC_COMPLEX_EXPORT int pfnc_get_complex_dim(int ncid, int* nc_dim);
-+
-+/// Get the base numerical type of a complex type
-+///
-+/// Returns the type of the components for a compound type, or the
-+/// type of an element for a dimension type.
-+NC_COMPLEX_EXPORT int pfnc_complex_base_type(
-+    int ncid, nc_type nc_typeid, nc_type* base_type_id
-+);
-+
-+/// Get the base numerical type of a complex variable
-+NC_COMPLEX_EXPORT int pfnc_inq_var_complex_base_type(
-+    int ncid, int varid, nc_type* nc_typeid
-+);
-+
-+/// Return some information about the `nc-complex` library
-+NC_COMPLEX_EXPORT const char* pfnc_inq_libvers(void);
-+
-+/// @name Wrappers
-+/// Wrappers for the equivalent `nc_*` functions that correctly handle
-+/// the start/count/stride arrays for complex dimensions.
-+///
-+/// When the variable is stored using a complex dimension, the file
-+/// representation has one more dimension than the user-visible
-+/// in-memory representation. For example, a 1D array:
-+///
-+/// ```c
-+/// double_complex data[5];
-+/// ```
-+///
-+/// would be represented in the file with two dimensions (when not
-+/// using a compound datatype!), and so if we use the standard netCDF
-+/// API we would need to use `{5, 2}` for the `countp` arguments, for
-+/// example, while using nc-complex, we only need `{5}`.
-+///  
-+/// NOTE: The `pfnc_put/get*` functions do *not* currently handle
-+/// conversion between `float/double` base types
-+///@{
-+
-+/// Extension to `nc_def_var` that also accepts
-+/// \rstref{PFNC_FLOAT_COMPLEX}, \rstref{PFNC_FLOAT_COMPLEX_DIM},
-+/// \rstref{PFNC_DOUBLE_COMPLEX}, and \rstref{PFNC_DOUBLE_COMPLEX_DIM}
-+NC_COMPLEX_EXPORT int pfnc_def_var(
-+    int ncid,
-+    const char* name,
-+    nc_type xtype,
-+    int ndims,
-+    const int* dimidsp,
-+    int* varidp
-+);
-+
-+NC_COMPLEX_EXPORT int pfnc_put_vara_double_complex(
-+    int ncid,
-+    int varid,
-+    const size_t* startp,
-+    const size_t* countp,
-+    const double_complex* op
-+);
-+
-+NC_COMPLEX_EXPORT int pfnc_get_vara_double_complex(
-+    int ncid, int varid, const size_t* startp, const size_t* countp, double_complex* ip
-+);
-+
-+NC_COMPLEX_EXPORT int pfnc_put_vars_double_complex(
-+    int ncid,
-+    int varid,
-+    const size_t* startp,
-+    const size_t* countp,
-+    const ptrdiff_t* stridep,
-+    const double_complex* op
-+);
-+
-+NC_COMPLEX_EXPORT int pfnc_get_vars_double_complex(
-+    int ncid,
-+    int varid,
-+    const size_t* startp,
-+    const size_t* countp,
-+    const ptrdiff_t* stridep,
-+    double_complex* ip
-+);
-+
-+NC_COMPLEX_EXPORT int pfnc_put_var1_double_complex(
-+    int ncid, int varid, const size_t* indexp, const double_complex* data
-+);
-+NC_COMPLEX_EXPORT int pfnc_get_var1_double_complex(
-+    int ncid, int varid, const size_t* indexp, double_complex* data
-+);
-+
-+NC_COMPLEX_EXPORT int pfnc_put_vara_float_complex(
-+    int ncid,
-+    int varid,
-+    const size_t* startp,
-+    const size_t* countp,
-+    const float_complex* op
-+);
-+
-+NC_COMPLEX_EXPORT int pfnc_get_vara_float_complex(
-+    int ncid, int varid, const size_t* startp, const size_t* countp, float_complex* ip
-+);
-+
-+NC_COMPLEX_EXPORT int pfnc_put_vars_float_complex(
-+    int ncid,
-+    int varid,
-+    const size_t* startp,
-+    const size_t* countp,
-+    const ptrdiff_t* stridep,
-+    const float_complex* op
-+);
-+
-+NC_COMPLEX_EXPORT int pfnc_get_vars_float_complex(
-+    int ncid,
-+    int varid,
-+    const size_t* startp,
-+    const size_t* countp,
-+    const ptrdiff_t* stridep,
-+    float_complex* ip
-+);
-+
-+NC_COMPLEX_EXPORT int pfnc_put_var1_float_complex(
-+    int ncid, int varid, const size_t* indexp, const float_complex* data
-+);
-+NC_COMPLEX_EXPORT int pfnc_get_var1_float_complex(
-+    int ncid, int varid, const size_t* indexp, float_complex* data
-+);
-+
-+NC_COMPLEX_EXPORT int pfnc_inq_var(
-+    int ncid,
-+    int varid,
-+    char* name,
-+    nc_type* xtypep,
-+    int* ndimsp,
-+    int* dimidsp,
-+    int* nattsp
-+);
-+
-+// NOLINTBEGIN(modernize-use-nullptr)
-+NC_COMPLEX_EXPORT inline int pfnc_inq_varndims(int ncid, int varid, int* ndimsp) {
-+    return pfnc_inq_var(ncid, varid, NULL, NULL, ndimsp, NULL, NULL);
-+}
-+NC_COMPLEX_EXPORT inline int pfnc_inq_vardimid(int ncid, int varid, int* dimidsp) {
-+    return pfnc_inq_var(ncid, varid, NULL, NULL, NULL, dimidsp, NULL);
-+}
-+// NOLINTEND(modernize-use-nullptr)
-+
-+NC_COMPLEX_EXPORT int pfnc_def_var_chunking(
-+    int ncid, int varid, int storage, const size_t* chunksizesp
-+);
-+NC_COMPLEX_EXPORT int pfnc_inq_var_chunking(
-+    int ncid, int varid, int* storagep, size_t* chunksizesp
-+);
-+
-+NC_COMPLEX_EXPORT int pfnc_get_vara(
-+    int ncid, int varid, const size_t* startp, const size_t* countp, void* ip
-+);
-+NC_COMPLEX_EXPORT int pfnc_get_vars(
-+    int ncid,
-+    int varid,
-+    const size_t* startp,
-+    const size_t* countp,
-+    const ptrdiff_t* stridep,
-+    void* ip
-+);
-+
-+NC_COMPLEX_EXPORT int pfnc_put_vara(
-+    int ncid, int varid, const size_t* startp, const size_t* countp, const void* op
-+);
-+
-+NC_COMPLEX_EXPORT int pfnc_put_vars(
-+    int ncid,
-+    int varid,
-+    const size_t* startp,
-+    const size_t* countp,
-+    const ptrdiff_t* stridep,
-+    const void* op
-+);
-+///@}
-+
-+#ifdef __cplusplus
-+}
-+#endif
-+
-+#endif
---- /dev/null
-+++ b/external/nc_complex/src/nc_complex.c
-@@ -0,0 +1,867 @@
-+#include "nc_complex/nc_complex.h"
-+
-+#include <ctype.h>
-+#include <netcdf.h>
-+#include <stdbool.h>
-+#include <stddef.h>
-+#include <stdlib.h>
-+#include <string.h>
-+
-+#include "nc_complex_version.h"
-+
-+// NOLINTBEGIN(bugprone-assignment-in-if-condition)
-+#define CHECK(func)           \
-+    do {                      \
-+        int res;              \
-+        if ((res = (func))) { \
-+            return res;       \
-+        }                     \
-+    } while (0)
-+// NOLINTEND(bugprone-assignment-in-if-condition)
-+
-+// Vector of ones for get/put_var1 functions
-+static const size_t coord_one[NC_MAX_VAR_DIMS] = {1};
-+
-+static const char* double_complex_struct_name = "_PFNC_DOUBLE_COMPLEX_TYPE";
-+static const char* float_complex_struct_name = "_PFNC_FLOAT_COMPLEX_TYPE";
-+
-+#define COMPLEX_DIM_NAME "_pfnc_complex"
-+static const char* complex_dim_name = COMPLEX_DIM_NAME;
-+
-+static const char* known_dim_names[] = {COMPLEX_DIM_NAME, "complex", "ri"};
-+static const size_t num_known_dim_names =
-+    sizeof(known_dim_names) / sizeof(known_dim_names[0]);
-+
-+static const char pfnc_libvers[] = NC_COMPLEX_GIT_VERSION;
-+
-+const char* pfnc_inq_libvers(void) {
-+    return pfnc_libvers;
-+}
-+
-+bool pfnc_var_is_complex(int ncid, int varid) {
-+    return pfnc_var_is_complex_type(ncid, varid)
-+        || pfnc_var_has_complex_dimension(ncid, varid);
-+}
-+
-+int pfnc_complex_base_type(int ncid, nc_type nc_typeid, nc_type* base_type_id) {
-+    if (nc_typeid < NC_MAX_ATOMIC_TYPE) {
-+        *base_type_id = nc_typeid;
-+        return NC_NOERR;
-+    }
-+
-+    // TODO: This should probably handle vlens too
-+
-+    return nc_inq_compound_field(
-+        ncid, nc_typeid, 0, NULL, NULL, base_type_id, NULL, NULL
-+    );
-+}
-+
-+int pfnc_inq_var_complex_base_type(int ncid, int varid, nc_type* nc_typeid) {
-+    nc_type var_type_id;
-+    CHECK(nc_inq_vartype(ncid, varid, &var_type_id));
-+    return pfnc_complex_base_type(ncid, var_type_id, nc_typeid);
-+}
-+
-+/// Return true if a compound type is compatible with a known convention
-+bool compound_type_is_compatible(int ncid, nc_type nc_typeid) {
-+    // Does the name matching a known convention?
-+    char name[NC_MAX_NAME + 1];
-+    nc_inq_compound_name(ncid, nc_typeid, name);
-+    if (name == double_complex_struct_name) {
-+        return true;
-+    }
-+
-+    // Does it have exactly two fields?
-+    size_t num_fields;
-+    nc_inq_compound_nfields(ncid, nc_typeid, &num_fields);
-+    if (num_fields != 2) {
-+        return false;
-+    }
-+
-+    // As far as I can tell, all conventions put the real part first and
-+    // the imaginary part second. I'm pretty sure all native language
-+    // types are also this way round. That means we don't have to worry
-+    // about trying both combinations!
-+    char real_name[NC_MAX_NAME + 1];
-+    size_t real_offset;
-+    nc_type real_field_type;
-+    int real_rank;
-+    nc_inq_compound_field(
-+        ncid, nc_typeid, 0, real_name, &real_offset, &real_field_type, &real_rank, NULL
-+    );
-+
-+    // If it's not a floating type, we're not interested
-+    if (!(real_field_type == NC_FLOAT || real_field_type == NC_DOUBLE)) {
-+        return false;
-+    }
-+    // Also needs to be scalar
-+    if (real_rank != 0) {
-+        return false;
-+    }
-+
-+    // Now check names. For now, just check it starts with "r", in any case
-+    if (tolower(real_name[0]) != 'r') {
-+        return false;
-+    }
-+
-+    char imag_name[NC_MAX_NAME + 1];
-+    size_t imag_offset;
-+    nc_type imag_field_type;
-+    int imag_rank;
-+    nc_inq_compound_field(
-+        ncid, nc_typeid, 1, imag_name, &imag_offset, &imag_field_type, &imag_rank, NULL
-+    );
-+
-+    // Both component types better match
-+    if (imag_field_type != real_field_type) {
-+        return false;
-+    }
-+    if (imag_rank != 0) {
-+        return false;
-+    }
-+    if (tolower(imag_name[0]) != 'i') {
-+        return false;
-+    }
-+
-+    return true;
-+}
-+
-+/// Return true if file already has a complex type with the given base type
-+bool file_has_complex_struct(int ncid, nc_type* typeidp, nc_type base_type) {
-+    // Simplest case, check for our type name
-+    int err = nc_inq_typeid(ncid, double_complex_struct_name, typeidp);
-+    if (err == NC_NOERR) {
-+        return true;
-+    }
-+
-+    int ntypes;
-+    err = nc_inq_typeids(ncid, &ntypes, NULL);
-+    if (err != NC_NOERR) {
-+        return false;
-+    }
-+
-+    bool result = false;
-+
-+    nc_type* typeids = malloc((size_t)ntypes * sizeof(nc_type));
-+    err = nc_inq_typeids(ncid, NULL, typeids);
-+    if (err != NC_NOERR) {
-+        goto cleanup;
-+    }
-+
-+    for (size_t i = 0; i < (size_t)ntypes; i++) {
-+        if (compound_type_is_compatible(ncid, typeids[i])) {
-+            nc_type base_type_id;
-+            err = pfnc_complex_base_type(ncid, typeids[i], &base_type_id);
-+            if (err != NC_NOERR) {
-+                goto cleanup;
-+            }
-+            if (base_type_id == base_type) {
-+                *typeidp = typeids[i];
-+                result = true;
-+                goto cleanup;
-+            }
-+        }
-+    }
-+cleanup:
-+    free(typeids);
-+    return result;
-+}
-+
-+/// Return true if a given dimension matches a known convention
-+bool pfnc_is_complex_dim(int ncid, int dim_id) {
-+    size_t length;
-+    nc_inq_dimlen(ncid, dim_id, &length);
-+
-+    // Definitely can only be exactly two. Note that we can't catch
-+    // unlimited dimensions that only have two records so far.
-+    if (length != 2) {
-+        return false;
-+    }
-+
-+    // Not sure if this is the best way, but here we are.
-+    char name[NC_MAX_NAME + 1];
-+    nc_inq_dimname(ncid, dim_id, name);
-+
-+    const size_t name_length = strlen(name);
-+
-+    // Check against known names of complex dimensions
-+    for (size_t i = 0; i < num_known_dim_names; i++) {
-+        if (strncmp(name, known_dim_names[i], name_length) == 0) {
-+            return true;
-+        }
-+    }
-+
-+    return false;
-+}
-+
-+/// Return true if a variable uses the dimension-convention
-+bool pfnc_var_has_complex_dimension(int ncid, int nc_varid) {
-+    int num_dims;
-+    nc_inq_varndims(ncid, nc_varid, &num_dims);
-+
-+    int* dim_ids = (int*)malloc((size_t)num_dims * sizeof(int));
-+    nc_inq_vardimid(ncid, nc_varid, dim_ids);
-+
-+    // Now we check if any of the dimensions match one of our known
-+    // conventions. Do we need to check all of them, or just the
-+    // first/last?
-+    for (int i = 0; i < num_dims; i++) {
-+        if (pfnc_is_complex_dim(ncid, dim_ids[i])) {
-+            free(dim_ids);
-+            return true;
-+        }
-+    }
-+
-+    free(dim_ids);
-+    return false;
-+}
-+
-+/// Return true if a netCDF datatype is a compound type
-+bool is_compound_type(int ncid, int type_id) {
-+    // There appears to be no API for detecting whether a type ID is a
-+    // primitive type, so we have to check ourselves
-+    if (type_id <= NC_MAX_ATOMIC_TYPE) {
-+        return false;
-+    }
-+
-+    int class_type;
-+    nc_inq_user_type(ncid, type_id, NULL, NULL, NULL, NULL, &class_type);
-+    return class_type == NC_COMPOUND;
-+}
-+
-+/// Copy an array meant for a complex-dimensioned variable
-+size_t* copy_complex_dim_size_t_array(
-+    const size_t* old_array, int numdims, size_t complex_dim_value
-+) {
-+    size_t* new_buffer = NULL;
-+
-+    if (old_array != NULL) {
-+        new_buffer = (size_t*)malloc(sizeof(size_t) * (size_t)numdims);
-+
-+        size_t last_dim = (size_t)(numdims - 1);
-+        for (size_t i = 0; i < last_dim; i++) {
-+            new_buffer[i] = old_array[i];
-+        }
-+
-+        new_buffer[last_dim] = complex_dim_value;
-+    }
-+    return new_buffer;
-+}
-+
-+ptrdiff_t* copy_complex_dim_ptrdiff_t_array(
-+    const ptrdiff_t* old_array, int numdims, ptrdiff_t complex_dim_value
-+) {
-+    ptrdiff_t* new_buffer = NULL;
-+
-+    if (old_array != NULL) {
-+        new_buffer = (ptrdiff_t*)malloc(sizeof(ptrdiff_t) * (size_t)numdims);
-+
-+        size_t last_dim = (size_t)(numdims - 1);
-+        for (size_t i = 0; i < last_dim; i++) {
-+            new_buffer[i] = old_array[i];
-+        }
-+
-+        new_buffer[last_dim] = complex_dim_value;
-+    }
-+    return new_buffer;
-+}
-+
-+bool pfnc_var_is_complex_type(int ncid, int varid) {
-+    nc_type var_type_id;
-+    if (nc_inq_vartype(ncid, varid, &var_type_id)) {
-+        return false;
-+    }
-+
-+    if (is_compound_type(ncid, var_type_id)) {
-+        return compound_type_is_compatible(ncid, var_type_id);
-+    }
-+    return false;
-+}
-+
-+size_t complex_type_size(nc_type base_type) {
-+    switch (base_type) {
-+    case NC_FLOAT:
-+        return sizeof(float_complex);
-+    case NC_DOUBLE:
-+        return sizeof(double_complex);
-+    default:
-+        return 0;
-+    }
-+}
-+
-+size_t base_type_size(nc_type base_type) {
-+    switch (base_type) {
-+    case NC_FLOAT:
-+        return sizeof(float);
-+    case NC_DOUBLE:
-+        return sizeof(double);
-+    default:
-+        return 0;
-+    }
-+}
-+
-+int get_or_make_complex_struct(
-+    int ncid, nc_type* nc_typeid, nc_type base_type, const char* struct_name
-+) {
-+    // TODO: Error if not netCDF4
-+
-+    if (file_has_complex_struct(ncid, nc_typeid, base_type)) {
-+        return NC_NOERR;
-+    }
-+
-+    const size_t complex_size = complex_type_size(base_type);
-+    if (complex_size == 0) {
-+        return NC_EBADTYPE;
-+    }
-+    const size_t base_size = base_type_size(base_type);
-+    if (base_size == 0) {
-+        return NC_EBADTYPE;
-+    }
-+
-+    CHECK(nc_def_compound(ncid, complex_size, struct_name, nc_typeid));
-+    CHECK(nc_insert_compound(ncid, *nc_typeid, "r", 0, base_type));
-+    CHECK(nc_insert_compound(ncid, *nc_typeid, "i", base_size, base_type));
-+
-+    return NC_NOERR;
-+}
-+
-+int pfnc_get_double_complex_typeid(int ncid, nc_type* nc_typeid) {
-+    return get_or_make_complex_struct(
-+        ncid, nc_typeid, NC_DOUBLE, double_complex_struct_name
-+    );
-+}
-+
-+int pfnc_get_float_complex_typeid(int ncid, nc_type* nc_typeid) {
-+    return get_or_make_complex_struct(
-+        ncid, nc_typeid, NC_FLOAT, float_complex_struct_name
-+    );
-+}
-+
-+int pfnc_get_complex_dim(int ncid, int* nc_dim) {
-+    int num_dims;
-+    CHECK(nc_inq_ndims(ncid, &num_dims));
-+
-+    int* dim_ids = (int*)malloc((size_t)num_dims * sizeof(int));
-+    int ierr = nc_inq_dimids(ncid, NULL, dim_ids, true);
-+    if (ierr != NC_NOERR) {
-+        goto cleanup;
-+    }
-+
-+    // Now we check if any of the dimensions match one of our known
-+    // conventions. Do we need to check all of them, or just the
-+    // first/last?
-+    for (int i = 0; i < num_dims; i++) {
-+        if (pfnc_is_complex_dim(ncid, dim_ids[i])) {
-+            *nc_dim = dim_ids[i];
-+            goto cleanup;
-+        }
-+    }
-+
-+    ierr = nc_def_dim(ncid, complex_dim_name, 2, nc_dim);
-+
-+cleanup:
-+    free(dim_ids);
-+    return ierr;
-+}
-+
-+int pfnc_put_vara_double_complex(
-+    int ncid,
-+    int varid,
-+    const size_t* startp,
-+    const size_t* countp,
-+    const double_complex* op
-+) {
-+    return pfnc_put_vars_double_complex(ncid, varid, startp, countp, NULL, op);
-+}
-+
-+int pfnc_get_vara_double_complex(
-+    int ncid, int varid, const size_t* startp, const size_t* countp, double_complex* ip
-+) {
-+    return pfnc_get_vars_double_complex(ncid, varid, startp, countp, NULL, ip);
-+}
-+
-+int pfnc_put_vars_double_complex(
-+    int ncid,
-+    int varid,
-+    const size_t* startp,
-+    const size_t* countp,
-+    const ptrdiff_t* stridep,
-+    const double_complex* op
-+) {
-+    if (!pfnc_var_is_complex(ncid, varid)) {
-+        return NC_EBADTYPE;
-+    }
-+
-+    // TODO: handle converting different float sizes
-+
-+    // Check if we can get away without fudging count/start sizes
-+    if (((startp == NULL) && (countp == NULL) && (stridep == NULL))
-+        || !pfnc_var_has_complex_dimension(ncid, varid)) {
-+        return nc_put_vars(ncid, varid, startp, countp, stridep, op);
-+    }
-+
-+    // The real variable has a complex dimension, but we're pretending
-+    // it doesn't, so now we need start/count arrays of the real size
-+
-+    int numdims = 0;
-+    CHECK(nc_inq_varndims(ncid, varid, &numdims));
-+
-+    // Copy start/count buffers, appending an extra element for the
-+    // complex dimension. This dimension starts at 0 and has 2 elements
-+    size_t* start_buffer = copy_complex_dim_size_t_array(startp, numdims, 0);
-+    size_t* count_buffer = copy_complex_dim_size_t_array(countp, numdims, 2);
-+    ptrdiff_t* stride_buffer = copy_complex_dim_ptrdiff_t_array(stridep, numdims, 1);
-+
-+    const int ierr =
-+        nc_put_vars(ncid, varid, start_buffer, count_buffer, stride_buffer, op);
-+
-+    if (start_buffer != NULL) {
-+        free(start_buffer);
-+    }
-+    if (count_buffer != NULL) {
-+        free(count_buffer);
-+    }
-+    if (stride_buffer != NULL) {
-+        free(stride_buffer);
-+    }
-+    return ierr;
-+}
-+
-+int pfnc_get_vars_double_complex(
-+    int ncid,
-+    int varid,
-+    const size_t* startp,
-+    const size_t* countp,
-+    const ptrdiff_t* stridep,
-+    double_complex* ip
-+) {
-+    if (!pfnc_var_is_complex(ncid, varid)) {
-+        return NC_EBADTYPE;
-+    }
-+
-+    // TODO: handle converting different float sizes
-+
-+    // Check if we can get away without fudging count/start sizes
-+    if (((startp == NULL) && (countp == NULL) && (stridep == NULL))
-+        || !pfnc_var_has_complex_dimension(ncid, varid)) {
-+        return nc_get_vars(ncid, varid, startp, countp, stridep, ip);
-+    }
-+
-+    // The real variable has a complex dimension, but we're pretending
-+    // it doesn't, so now we need start/count arrays of the real size
-+
-+    int numdims = 0;
-+    CHECK(nc_inq_varndims(ncid, varid, &numdims));
-+
-+    // Copy start/count buffers, appending an extra element for the
-+    // complex dimension. This dimension starts at 0 and has 2 elements
-+    size_t* start_buffer = copy_complex_dim_size_t_array(startp, numdims, 0);
-+    size_t* count_buffer = copy_complex_dim_size_t_array(countp, numdims, 2);
-+    ptrdiff_t* stride_buffer = copy_complex_dim_ptrdiff_t_array(stridep, numdims, 1);
-+
-+    const int ierr =
-+        nc_get_vars(ncid, varid, start_buffer, count_buffer, stride_buffer, ip);
-+
-+    if (start_buffer != NULL) {
-+        free(start_buffer);
-+    }
-+    if (count_buffer != NULL) {
-+        free(count_buffer);
-+    }
-+    if (stride_buffer != NULL) {
-+        free(stride_buffer);
-+    }
-+    return ierr;
-+}
-+
-+int pfnc_put_var1_double_complex(
-+    int ncid, int varid, const size_t* indexp, const double_complex* data
-+) {
-+    return pfnc_put_vara_double_complex(ncid, varid, indexp, coord_one, data);
-+}
-+
-+int pfnc_get_var1_double_complex(
-+    int ncid, int varid, const size_t* indexp, double_complex* data
-+) {
-+    return pfnc_get_vara_double_complex(ncid, varid, indexp, coord_one, data);
-+}
-+
-+int pfnc_put_vara_float_complex(
-+    int ncid,
-+    int varid,
-+    const size_t* startp,
-+    const size_t* countp,
-+    const float_complex* op
-+) {
-+    return pfnc_put_vars_float_complex(ncid, varid, startp, countp, NULL, op);
-+}
-+
-+int pfnc_get_vara_float_complex(
-+    int ncid, int varid, const size_t* startp, const size_t* countp, float_complex* ip
-+) {
-+    return pfnc_get_vars_float_complex(ncid, varid, startp, countp, NULL, ip);
-+}
-+
-+int pfnc_put_vars_float_complex(
-+    int ncid,
-+    int varid,
-+    const size_t* startp,
-+    const size_t* countp,
-+    const ptrdiff_t* stridep,
-+    const float_complex* op
-+) {
-+    if (!pfnc_var_is_complex(ncid, varid)) {
-+        return NC_EBADTYPE;
-+    }
-+
-+    // TODO: handle converting different float sizes
-+
-+    // Check if we can get away without fudging count/start sizes
-+    if (((startp == NULL) && (countp == NULL) && (stridep == NULL))
-+        || !pfnc_var_has_complex_dimension(ncid, varid)) {
-+        return nc_put_vars(ncid, varid, startp, countp, stridep, op);
-+    }
-+
-+    // The real variable has a complex dimension, but we're pretending
-+    // it doesn't, so now we need start/count arrays of the real size
-+
-+    int numdims = 0;
-+    CHECK(nc_inq_varndims(ncid, varid, &numdims));
-+
-+    // Copy start/count buffers, appending an extra element for the
-+    // complex dimension. This dimension starts at 0 and has 2 elements
-+    size_t* start_buffer = copy_complex_dim_size_t_array(startp, numdims, 0);
-+    size_t* count_buffer = copy_complex_dim_size_t_array(countp, numdims, 2);
-+    ptrdiff_t* stride_buffer = copy_complex_dim_ptrdiff_t_array(stridep, numdims, 1);
-+
-+    const int ierr =
-+        nc_put_vars(ncid, varid, start_buffer, count_buffer, stride_buffer, op);
-+
-+    if (start_buffer != NULL) {
-+        free(start_buffer);
-+    }
-+    if (count_buffer != NULL) {
-+        free(count_buffer);
-+    }
-+    if (stride_buffer != NULL) {
-+        free(stride_buffer);
-+    }
-+    return ierr;
-+}
-+
-+int pfnc_get_vars_float_complex(
-+    int ncid,
-+    int varid,
-+    const size_t* startp,
-+    const size_t* countp,
-+    const ptrdiff_t* stridep,
-+    float_complex* ip
-+) {
-+    if (!pfnc_var_is_complex(ncid, varid)) {
-+        return NC_EBADTYPE;
-+    }
-+
-+    // TODO: handle converting different float sizes
-+
-+    // Check if we can get away without fudging count/start sizes
-+    if (((startp == NULL) && (countp == NULL) && (stridep == NULL))
-+        || !pfnc_var_has_complex_dimension(ncid, varid)) {
-+        return nc_get_vars(ncid, varid, startp, countp, stridep, ip);
-+    }
-+
-+    // The real variable has a complex dimension, but we're pretending
-+    // it doesn't, so now we need start/count arrays of the real size
-+
-+    int numdims = 0;
-+    CHECK(nc_inq_varndims(ncid, varid, &numdims));
-+
-+    // Copy start/count buffers, appending an extra element for the
-+    // complex dimension. This dimension starts at 0 and has 2 elements
-+    size_t* start_buffer = copy_complex_dim_size_t_array(startp, numdims, 0);
-+    size_t* count_buffer = copy_complex_dim_size_t_array(countp, numdims, 2);
-+    ptrdiff_t* stride_buffer = copy_complex_dim_ptrdiff_t_array(stridep, numdims, 1);
-+
-+    const int ierr =
-+        nc_get_vars(ncid, varid, start_buffer, count_buffer, stride_buffer, ip);
-+
-+    if (start_buffer != NULL) {
-+        free(start_buffer);
-+    }
-+    if (count_buffer != NULL) {
-+        free(count_buffer);
-+    }
-+    if (stride_buffer != NULL) {
-+        free(stride_buffer);
-+    }
-+    return ierr;
-+}
-+
-+int pfnc_put_var1_float_complex(
-+    int ncid, int varid, const size_t* indexp, const float_complex* data
-+) {
-+    return pfnc_put_vara_float_complex(ncid, varid, indexp, coord_one, data);
-+}
-+
-+int pfnc_get_var1_float_complex(
-+    int ncid, int varid, const size_t* indexp, float_complex* data
-+) {
-+    return pfnc_get_vara_float_complex(ncid, varid, indexp, coord_one, data);
-+}
-+
-+int pfnc_def_var(
-+    int ncid,
-+    const char* name,
-+    nc_type xtype,
-+    int ndims,
-+    const int* dimidsp,
-+    int* varidp
-+) {
-+    // If it's not a complex number, we don't need to do anything
-+    if (!(xtype == PFNC_DOUBLE_COMPLEX || xtype == PFNC_DOUBLE_COMPLEX_DIM
-+          || xtype == PFNC_FLOAT_COMPLEX || xtype == PFNC_FLOAT_COMPLEX_DIM)) {
-+        return nc_def_var(ncid, name, xtype, ndims, dimidsp, varidp);
-+    }
-+
-+    const bool base_is_double =
-+        (xtype == PFNC_DOUBLE_COMPLEX || xtype == PFNC_DOUBLE_COMPLEX_DIM);
-+
-+    // Check the format used by this file. If it's some variation on the
-+    // classic model, then we have to use a complex dimension. Also,
-+    // NcZarr, for some reason doesn't support compound types (yet?).
-+    // I _think_ DAP supports compound types
-+    int format = 0;
-+    int mode = 0;
-+    CHECK(nc_inq_format_extended(ncid, &format, &mode));
-+
-+    if ((format == NC_FORMAT_CLASSIC || format == NC_FORMAT_NETCDF4_CLASSIC)
-+        || (mode == NC_FORMATX_NCZARR)) {
-+        xtype = base_is_double ? PFNC_DOUBLE_COMPLEX_DIM : PFNC_FLOAT_COMPLEX_DIM;
-+    }
-+
-+    if (xtype == PFNC_DOUBLE_COMPLEX_DIM || xtype == PFNC_FLOAT_COMPLEX_DIM) {
-+        // Using a complex dimension. We need to get the complex dimension
-+        // used in this file and append it to the list of dimensions
-+        // passed in by the user
-+
-+        int complex_dim = 0;
-+        CHECK(pfnc_get_complex_dim(ncid, &complex_dim));
-+
-+        int new_dims = ndims + 1;
-+        int* dim_ids_buffer = (int*)malloc((size_t)new_dims * sizeof(int));
-+        for (size_t i = 0; i < (size_t)ndims; i++) {
-+            dim_ids_buffer[i] = dimidsp[i];
-+        }
-+        dim_ids_buffer[ndims] = complex_dim;
-+
-+        const nc_type base_type = base_is_double ? NC_DOUBLE : NC_FLOAT;
-+
-+        const int ierr =
-+            nc_def_var(ncid, name, base_type, new_dims, dim_ids_buffer, varidp);
-+        free(dim_ids_buffer);
-+        return ierr;
-+    }
-+
-+    // Using a complex type. We need to get the complex type used in
-+    // this file and pass that as `xtype`
-+    nc_type complex_type = 0;
-+    if (base_is_double) {
-+        CHECK(pfnc_get_double_complex_typeid(ncid, &complex_type));
-+    } else {
-+        CHECK(pfnc_get_float_complex_typeid(ncid, &complex_type));
-+    }
-+
-+    return nc_def_var(ncid, name, complex_type, ndims, dimidsp, varidp);
-+}
-+
-+int pfnc_inq_var(
-+    int ncid,
-+    int varid,
-+    char* name,
-+    nc_type* xtypep,
-+    int* ndimsp,
-+    int* dimidsp,
-+    int* nattsp
-+) {
-+    if (!pfnc_var_has_complex_dimension(ncid, varid)) {
-+        return nc_inq_var(ncid, varid, name, xtypep, ndimsp, dimidsp, nattsp);
-+    }
-+
-+    // Tricky bit: if variable has complex dimension, and user used
-+    // pfnc_inq_varndims, then dimidsp is one smaller than netCDF thinks
-+    // it should be. So we'll have to allocate our own array of the
-+    // correct size and copy out of that.
-+
-+    // This buffer will point to either the user's array, or our own one
-+    int* buffer = dimidsp;
-+    int numdims = 0;
-+
-+    if (dimidsp != NULL) {
-+        CHECK(nc_inq_varndims(ncid, varid, &numdims));
-+        buffer = (int*)malloc(sizeof(int) * (size_t)numdims);
-+    }
-+
-+    int ierr = nc_inq_var(ncid, varid, name, xtypep, &numdims, buffer, nattsp);
-+
-+    if (ierr != NC_NOERR) {
-+        goto cleanup;
-+    }
-+
-+    if (dimidsp != NULL) {
-+        if (numdims <= 0) {
-+            // This should never happen
-+            goto cleanup;
-+        }
-+        const size_t other_dims = (size_t)(numdims - 1);
-+        for (size_t i = 0; i < other_dims; i++) {
-+            dimidsp[i] = buffer[i];
-+        }
-+    }
-+
-+    if (ndimsp != NULL) {
-+        *ndimsp = numdims - 1;
-+    }
-+
-+cleanup:
-+    free(buffer);
-+    return ierr;
-+}
-+
-+int pfnc_def_var_chunking(int ncid, int varid, int storage, const size_t* chunksizesp) {
-+    if (chunksizesp == NULL || !pfnc_var_has_complex_dimension(ncid, varid)) {
-+        return nc_def_var_chunking(ncid, varid, storage, chunksizesp);
-+    }
-+
-+    // The real variable has a complex dimension, but we're pretending
-+    // it doesn't, so now we need start/count arrays of the real size
-+
-+    int numdims = 0;
-+    CHECK(nc_inq_varndims(ncid, varid, &numdims));
-+
-+    // Copy chunksize buffer, appending an extra element for the
-+    // complex dimension
-+    size_t* chunk_buffer = copy_complex_dim_size_t_array(chunksizesp, numdims, 2);
-+
-+    const int ierr = nc_def_var_chunking(ncid, varid, storage, chunk_buffer);
-+    free(chunk_buffer);
-+    return ierr;
-+}
-+
-+int pfnc_inq_var_chunking(int ncid, int varid, int* storagep, size_t* chunksizesp) {
-+    if (chunksizesp == NULL || !pfnc_var_has_complex_dimension(ncid, varid)) {
-+        return nc_inq_var_chunking(ncid, varid, storagep, chunksizesp);
-+    }
-+
-+    int numdims = 0;
-+
-+    CHECK(nc_inq_varndims(ncid, varid, &numdims));
-+
-+    // Copy chunksize buffer, appending an extra element for the
-+    // complex dimension
-+    size_t* chunk_buffer = copy_complex_dim_size_t_array(chunksizesp, numdims, 2);
-+
-+    const int ierr = nc_inq_var_chunking(ncid, varid, storagep, chunk_buffer);
-+
-+    if (ierr != NC_NOERR) {
-+        goto cleanup;
-+    }
-+
-+    const size_t other_dims = (size_t)(numdims - 1);
-+    for (size_t i = 0; i < other_dims; i++) {
-+        chunksizesp[i] = chunk_buffer[i];
-+    }
-+
-+cleanup:
-+    free(chunk_buffer);
-+    return ierr;
-+}
-+
-+int pfnc_get_vara(
-+    int ncid, int varid, const size_t* startp, const size_t* countp, void* ip
-+) {
-+    if (pfnc_var_is_complex(ncid, varid)) {
-+        nc_type base_type;
-+        CHECK(pfnc_inq_var_complex_base_type(ncid, varid, &base_type));
-+        switch (base_type) {
-+        case NC_DOUBLE:
-+            return pfnc_get_vara_double_complex(ncid, varid, startp, countp, ip);
-+        case NC_FLOAT:
-+            return pfnc_get_vara_float_complex(ncid, varid, startp, countp, ip);
-+        default:
-+            return NC_EBADTYPE;
-+        }
-+    }
-+
-+    return nc_get_vara(ncid, varid, startp, countp, ip);
-+}
-+
-+int pfnc_put_vara(
-+    int ncid, int varid, const size_t* startp, const size_t* countp, const void* op
-+) {
-+    if (pfnc_var_is_complex(ncid, varid)) {
-+        nc_type base_type;
-+        CHECK(pfnc_inq_var_complex_base_type(ncid, varid, &base_type));
-+        switch (base_type) {
-+        case NC_DOUBLE:
-+            return pfnc_put_vara_double_complex(ncid, varid, startp, countp, op);
-+        case NC_FLOAT:
-+            return pfnc_put_vara_float_complex(ncid, varid, startp, countp, op);
-+        default:
-+            return NC_EBADTYPE;
-+        }
-+    }
-+    return nc_put_vara(ncid, varid, startp, countp, op);
-+}
-+
-+int pfnc_put_vars(
-+    int ncid,
-+    int varid,
-+    const size_t* startp,
-+    const size_t* countp,
-+    const ptrdiff_t* stridep,
-+    const void* op
-+) {
-+    if (pfnc_var_is_complex(ncid, varid)) {
-+        nc_type base_type;
-+        CHECK(pfnc_inq_var_complex_base_type(ncid, varid, &base_type));
-+        switch (base_type) {
-+        case NC_DOUBLE:
-+            return pfnc_put_vars_double_complex(
-+                ncid, varid, startp, countp, stridep, op
-+            );
-+        case NC_FLOAT:
-+            return pfnc_put_vars_float_complex(
-+                ncid, varid, startp, countp, stridep, op
-+            );
-+        default:
-+            return NC_EBADTYPE;
-+        }
-+    }
-+    return nc_put_vars(ncid, varid, startp, countp, stridep, op);
-+}
-+
-+int pfnc_get_vars(
-+    int ncid,
-+    int varid,
-+    const size_t* startp,
-+    const size_t* countp,
-+    const ptrdiff_t* stridep,
-+    void* ip
-+) {
-+    if (pfnc_var_is_complex(ncid, varid)) {
-+        nc_type base_type;
-+        CHECK(pfnc_inq_var_complex_base_type(ncid, varid, &base_type));
-+        switch (base_type) {
-+        case NC_DOUBLE:
-+            return pfnc_get_vars_double_complex(
-+                ncid, varid, startp, countp, stridep, ip
-+            );
-+        case NC_FLOAT:
-+            return pfnc_get_vars_float_complex(
-+                ncid, varid, startp, countp, stridep, ip
-+            );
-+        default:
-+            return NC_EBADTYPE;
-+        }
-+    }
-+    return nc_get_vars(ncid, varid, startp, countp, stridep, ip);
-+}


=====================================
debian/patches/series
=====================================
@@ -1,2 +1 @@
 rpath.patch
-nc-complex.patch


=====================================
external/README
=====================================
@@ -0,0 +1 @@
+* 20240616:  remove submodule, include v0.2.0 tag source files (https://github.com/PlasmaFAIR/nc-complex/releases/tag/v0.2.0).


=====================================
external/nc_complex/include/generated_fallbacks/nc_complex_version.h
=====================================
@@ -0,0 +1,4 @@
+#define NC_COMPLEX_GIT_SHA1 "37310ed00f3910974bdefefcdfa4787588651f59"
+#define NC_COMPLEX_GIT_VERSION "v0.2.0"
+#define NC_COMPLEX_GIT_STATE "clean"
+#define NC_COMPLEX_GIT_DATE "2023-12-08"


=====================================
external/nc_complex/include/nc_complex/nc_complex.h
=====================================
@@ -0,0 +1,291 @@
+/// nc-complex: A lightweight, drop-in extension for complex number support in
+/// netCDF
+///
+/// Copyright (C) 2023 Peter Hill
+///
+/// SPDX-License-Identifier: MIT
+
+#ifndef PLASMA_FAIR_NC_COMPLEX
+#define PLASMA_FAIR_NC_COMPLEX
+
+// This header is required when building as a DLL on Windows and is
+// automatically generated by CMake. If you're not using CMake (and
+// not on Windows) for some reason, then define `NC_COMPLEX_NO_EXPORT`
+// to skip this.
+#ifndef NC_COMPLEX_NO_EXPORT
+#include "nc_complex/nc_complex_export.h"
+#else
+#define NC_COMPLEX_EXPORT
+#endif
+
+#include <complex.h>
+#include <netcdf.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+#include <complex>
+#endif
+
+//@{
+/// Portable typedefs for complex numbers
+///
+/// These become aliases for `std::complex` with C++.
+#ifdef _MSC_VER
+typedef _Dcomplex double_complex;
+typedef _Fcomplex float_complex;
+#else
+#if defined(__cplusplus) && defined(__clang__)
+using double_complex = std::complex<double>;
+using float_complex = std::complex<float>;
+#else
+typedef double _Complex double_complex;
+typedef float _Complex float_complex;
+#endif
+#endif
+//@}
+
+#ifdef __cplusplus
+/// @name Helper functions
+///@{
+/// Helper functions for converting between (pointers to) C++ and C complex types
+NC_COMPLEX_EXPORT inline double_complex* cpp_to_c_complex(std::complex<double>* data) {
+    return reinterpret_cast<double_complex*>(data);
+}
+
+NC_COMPLEX_EXPORT inline std::complex<double>* c_to_cpp_complex(double_complex* data) {
+    return reinterpret_cast<std::complex<double>*>(data);
+}
+
+NC_COMPLEX_EXPORT inline float_complex* cpp_to_c_complex(std::complex<float>* data) {
+    return reinterpret_cast<float_complex*>(data);
+}
+
+NC_COMPLEX_EXPORT inline std::complex<float>* c_to_cpp_complex(float_complex* data) {
+    return reinterpret_cast<std::complex<float>*>(data);
+}
+///@}
+extern "C" {
+#endif
+
+/// @name Complex datatype defines
+/// Datatype for complex numbers, for use with \rstref{pfnc_def_var}
+///  
+/// @note
+/// These *only* work when defining a variable with \rstref{pfnc_def_var}. To
+/// check the type of an existing variable use \rstref{pfnc_var_is_complex}, and
+/// to check if it is specifically using a compound datatype or a dimension use
+/// \rstref{pfnc_var_is_complex_type} or \rstref{pfnc_var_has_complex_dimension}
+/// respectively.
+/// @endnote  
+///@{  
+
+/// Uses complex compound datatype with netCDF4 format, and complex dimension otherwise
+#define PFNC_FLOAT_COMPLEX (NC_FIRSTUSERTYPEID - 4)
+/// Always use a complex dimension, regardless of file format
+#define PFNC_FLOAT_COMPLEX_DIM (NC_FIRSTUSERTYPEID - 3)
+/// Uses complex compound datatype with netCDF4 format, and complex dimension otherwise
+#define PFNC_DOUBLE_COMPLEX (NC_FIRSTUSERTYPEID - 2)
+/// Always use a complex dimension, regardless of file format
+#define PFNC_DOUBLE_COMPLEX_DIM (NC_FIRSTUSERTYPEID - 1)
+///@}
+
+/// Return true if variable is complex
+NC_COMPLEX_EXPORT bool pfnc_var_is_complex(int ncid, int varid);
+/// Return true if variable is complex and uses a compound datatype
+NC_COMPLEX_EXPORT bool pfnc_var_is_complex_type(int ncid, int varid);
+/// Return true if variable is complex and has a complex dimension
+/// (assumed to be the last dimension)
+NC_COMPLEX_EXPORT bool pfnc_var_has_complex_dimension(int ncid, int varid);
+
+/// Return true if dimension is complex
+NC_COMPLEX_EXPORT bool pfnc_is_complex_dim(int ncid, int dim_id);
+
+/// Get the ID for the complex datatype with `double` elements, creating it if it doesn't already exist
+NC_COMPLEX_EXPORT int pfnc_get_double_complex_typeid(int ncid, nc_type* nc_typeid);
+/// Get the ID for the complex datatype with `float` elements, creating it if it doesn't already exist
+NC_COMPLEX_EXPORT int pfnc_get_float_complex_typeid(int ncid, nc_type* nc_typeid);
+
+/// Get complex dimension, creating one if it doesn't already exist
+NC_COMPLEX_EXPORT int pfnc_get_complex_dim(int ncid, int* nc_dim);
+
+/// Get the base numerical type of a complex type
+///
+/// Returns the type of the components for a compound type, or the
+/// type of an element for a dimension type.
+NC_COMPLEX_EXPORT int pfnc_complex_base_type(
+    int ncid, nc_type nc_typeid, nc_type* base_type_id
+);
+
+/// Get the base numerical type of a complex variable
+NC_COMPLEX_EXPORT int pfnc_inq_var_complex_base_type(
+    int ncid, int varid, nc_type* nc_typeid
+);
+
+/// Return some information about the `nc-complex` library
+NC_COMPLEX_EXPORT const char* pfnc_inq_libvers(void);
+
+/// @name Wrappers
+/// Wrappers for the equivalent `nc_*` functions that correctly handle
+/// the start/count/stride arrays for complex dimensions.
+///
+/// When the variable is stored using a complex dimension, the file
+/// representation has one more dimension than the user-visible
+/// in-memory representation. For example, a 1D array:
+///
+/// ```c
+/// double_complex data[5];
+/// ```
+///
+/// would be represented in the file with two dimensions (when not
+/// using a compound datatype!), and so if we use the standard netCDF
+/// API we would need to use `{5, 2}` for the `countp` arguments, for
+/// example, while using nc-complex, we only need `{5}`.
+///  
+/// NOTE: The `pfnc_put/get*` functions do *not* currently handle
+/// conversion between `float/double` base types
+///@{
+
+/// Extension to `nc_def_var` that also accepts
+/// \rstref{PFNC_FLOAT_COMPLEX}, \rstref{PFNC_FLOAT_COMPLEX_DIM},
+/// \rstref{PFNC_DOUBLE_COMPLEX}, and \rstref{PFNC_DOUBLE_COMPLEX_DIM}
+NC_COMPLEX_EXPORT int pfnc_def_var(
+    int ncid,
+    const char* name,
+    nc_type xtype,
+    int ndims,
+    const int* dimidsp,
+    int* varidp
+);
+
+NC_COMPLEX_EXPORT int pfnc_put_vara_double_complex(
+    int ncid,
+    int varid,
+    const size_t* startp,
+    const size_t* countp,
+    const double_complex* op
+);
+
+NC_COMPLEX_EXPORT int pfnc_get_vara_double_complex(
+    int ncid, int varid, const size_t* startp, const size_t* countp, double_complex* ip
+);
+
+NC_COMPLEX_EXPORT int pfnc_put_vars_double_complex(
+    int ncid,
+    int varid,
+    const size_t* startp,
+    const size_t* countp,
+    const ptrdiff_t* stridep,
+    const double_complex* op
+);
+
+NC_COMPLEX_EXPORT int pfnc_get_vars_double_complex(
+    int ncid,
+    int varid,
+    const size_t* startp,
+    const size_t* countp,
+    const ptrdiff_t* stridep,
+    double_complex* ip
+);
+
+NC_COMPLEX_EXPORT int pfnc_put_var1_double_complex(
+    int ncid, int varid, const size_t* indexp, const double_complex* data
+);
+NC_COMPLEX_EXPORT int pfnc_get_var1_double_complex(
+    int ncid, int varid, const size_t* indexp, double_complex* data
+);
+
+NC_COMPLEX_EXPORT int pfnc_put_vara_float_complex(
+    int ncid,
+    int varid,
+    const size_t* startp,
+    const size_t* countp,
+    const float_complex* op
+);
+
+NC_COMPLEX_EXPORT int pfnc_get_vara_float_complex(
+    int ncid, int varid, const size_t* startp, const size_t* countp, float_complex* ip
+);
+
+NC_COMPLEX_EXPORT int pfnc_put_vars_float_complex(
+    int ncid,
+    int varid,
+    const size_t* startp,
+    const size_t* countp,
+    const ptrdiff_t* stridep,
+    const float_complex* op
+);
+
+NC_COMPLEX_EXPORT int pfnc_get_vars_float_complex(
+    int ncid,
+    int varid,
+    const size_t* startp,
+    const size_t* countp,
+    const ptrdiff_t* stridep,
+    float_complex* ip
+);
+
+NC_COMPLEX_EXPORT int pfnc_put_var1_float_complex(
+    int ncid, int varid, const size_t* indexp, const float_complex* data
+);
+NC_COMPLEX_EXPORT int pfnc_get_var1_float_complex(
+    int ncid, int varid, const size_t* indexp, float_complex* data
+);
+
+NC_COMPLEX_EXPORT int pfnc_inq_var(
+    int ncid,
+    int varid,
+    char* name,
+    nc_type* xtypep,
+    int* ndimsp,
+    int* dimidsp,
+    int* nattsp
+);
+
+// NOLINTBEGIN(modernize-use-nullptr)
+NC_COMPLEX_EXPORT inline int pfnc_inq_varndims(int ncid, int varid, int* ndimsp) {
+    return pfnc_inq_var(ncid, varid, NULL, NULL, ndimsp, NULL, NULL);
+}
+NC_COMPLEX_EXPORT inline int pfnc_inq_vardimid(int ncid, int varid, int* dimidsp) {
+    return pfnc_inq_var(ncid, varid, NULL, NULL, NULL, dimidsp, NULL);
+}
+// NOLINTEND(modernize-use-nullptr)
+
+NC_COMPLEX_EXPORT int pfnc_def_var_chunking(
+    int ncid, int varid, int storage, const size_t* chunksizesp
+);
+NC_COMPLEX_EXPORT int pfnc_inq_var_chunking(
+    int ncid, int varid, int* storagep, size_t* chunksizesp
+);
+
+NC_COMPLEX_EXPORT int pfnc_get_vara(
+    int ncid, int varid, const size_t* startp, const size_t* countp, void* ip
+);
+NC_COMPLEX_EXPORT int pfnc_get_vars(
+    int ncid,
+    int varid,
+    const size_t* startp,
+    const size_t* countp,
+    const ptrdiff_t* stridep,
+    void* ip
+);
+
+NC_COMPLEX_EXPORT int pfnc_put_vara(
+    int ncid, int varid, const size_t* startp, const size_t* countp, const void* op
+);
+
+NC_COMPLEX_EXPORT int pfnc_put_vars(
+    int ncid,
+    int varid,
+    const size_t* startp,
+    const size_t* countp,
+    const ptrdiff_t* stridep,
+    const void* op
+);
+///@}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif


=====================================
external/nc_complex/src/nc_complex.c
=====================================
@@ -0,0 +1,867 @@
+#include "nc_complex/nc_complex.h"
+
+#include <ctype.h>
+#include <netcdf.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "nc_complex_version.h"
+
+// NOLINTBEGIN(bugprone-assignment-in-if-condition)
+#define CHECK(func)           \
+    do {                      \
+        int res;              \
+        if ((res = (func))) { \
+            return res;       \
+        }                     \
+    } while (0)
+// NOLINTEND(bugprone-assignment-in-if-condition)
+
+// Vector of ones for get/put_var1 functions
+static const size_t coord_one[NC_MAX_VAR_DIMS] = {1};
+
+static const char* double_complex_struct_name = "_PFNC_DOUBLE_COMPLEX_TYPE";
+static const char* float_complex_struct_name = "_PFNC_FLOAT_COMPLEX_TYPE";
+
+#define COMPLEX_DIM_NAME "_pfnc_complex"
+static const char* complex_dim_name = COMPLEX_DIM_NAME;
+
+static const char* known_dim_names[] = {COMPLEX_DIM_NAME, "complex", "ri"};
+static const size_t num_known_dim_names =
+    sizeof(known_dim_names) / sizeof(known_dim_names[0]);
+
+static const char pfnc_libvers[] = NC_COMPLEX_GIT_VERSION;
+
+const char* pfnc_inq_libvers(void) {
+    return pfnc_libvers;
+}
+
+bool pfnc_var_is_complex(int ncid, int varid) {
+    return pfnc_var_is_complex_type(ncid, varid)
+        || pfnc_var_has_complex_dimension(ncid, varid);
+}
+
+int pfnc_complex_base_type(int ncid, nc_type nc_typeid, nc_type* base_type_id) {
+    if (nc_typeid < NC_MAX_ATOMIC_TYPE) {
+        *base_type_id = nc_typeid;
+        return NC_NOERR;
+    }
+
+    // TODO: This should probably handle vlens too
+
+    return nc_inq_compound_field(
+        ncid, nc_typeid, 0, NULL, NULL, base_type_id, NULL, NULL
+    );
+}
+
+int pfnc_inq_var_complex_base_type(int ncid, int varid, nc_type* nc_typeid) {
+    nc_type var_type_id;
+    CHECK(nc_inq_vartype(ncid, varid, &var_type_id));
+    return pfnc_complex_base_type(ncid, var_type_id, nc_typeid);
+}
+
+/// Return true if a compound type is compatible with a known convention
+bool compound_type_is_compatible(int ncid, nc_type nc_typeid) {
+    // Does the name matching a known convention?
+    char name[NC_MAX_NAME + 1];
+    nc_inq_compound_name(ncid, nc_typeid, name);
+    if (name == double_complex_struct_name) {
+        return true;
+    }
+
+    // Does it have exactly two fields?
+    size_t num_fields;
+    nc_inq_compound_nfields(ncid, nc_typeid, &num_fields);
+    if (num_fields != 2) {
+        return false;
+    }
+
+    // As far as I can tell, all conventions put the real part first and
+    // the imaginary part second. I'm pretty sure all native language
+    // types are also this way round. That means we don't have to worry
+    // about trying both combinations!
+    char real_name[NC_MAX_NAME + 1];
+    size_t real_offset;
+    nc_type real_field_type;
+    int real_rank;
+    nc_inq_compound_field(
+        ncid, nc_typeid, 0, real_name, &real_offset, &real_field_type, &real_rank, NULL
+    );
+
+    // If it's not a floating type, we're not interested
+    if (!(real_field_type == NC_FLOAT || real_field_type == NC_DOUBLE)) {
+        return false;
+    }
+    // Also needs to be scalar
+    if (real_rank != 0) {
+        return false;
+    }
+
+    // Now check names. For now, just check it starts with "r", in any case
+    if (tolower(real_name[0]) != 'r') {
+        return false;
+    }
+
+    char imag_name[NC_MAX_NAME + 1];
+    size_t imag_offset;
+    nc_type imag_field_type;
+    int imag_rank;
+    nc_inq_compound_field(
+        ncid, nc_typeid, 1, imag_name, &imag_offset, &imag_field_type, &imag_rank, NULL
+    );
+
+    // Both component types better match
+    if (imag_field_type != real_field_type) {
+        return false;
+    }
+    if (imag_rank != 0) {
+        return false;
+    }
+    if (tolower(imag_name[0]) != 'i') {
+        return false;
+    }
+
+    return true;
+}
+
+/// Return true if file already has a complex type with the given base type
+bool file_has_complex_struct(int ncid, nc_type* typeidp, nc_type base_type) {
+    // Simplest case, check for our type name
+    int err = nc_inq_typeid(ncid, double_complex_struct_name, typeidp);
+    if (err == NC_NOERR) {
+        return true;
+    }
+
+    int ntypes;
+    err = nc_inq_typeids(ncid, &ntypes, NULL);
+    if (err != NC_NOERR) {
+        return false;
+    }
+
+    bool result = false;
+
+    nc_type* typeids = malloc((size_t)ntypes * sizeof(nc_type));
+    err = nc_inq_typeids(ncid, NULL, typeids);
+    if (err != NC_NOERR) {
+        goto cleanup;
+    }
+
+    for (size_t i = 0; i < (size_t)ntypes; i++) {
+        if (compound_type_is_compatible(ncid, typeids[i])) {
+            nc_type base_type_id;
+            err = pfnc_complex_base_type(ncid, typeids[i], &base_type_id);
+            if (err != NC_NOERR) {
+                goto cleanup;
+            }
+            if (base_type_id == base_type) {
+                *typeidp = typeids[i];
+                result = true;
+                goto cleanup;
+            }
+        }
+    }
+cleanup:
+    free(typeids);
+    return result;
+}
+
+/// Return true if a given dimension matches a known convention
+bool pfnc_is_complex_dim(int ncid, int dim_id) {
+    size_t length;
+    nc_inq_dimlen(ncid, dim_id, &length);
+
+    // Definitely can only be exactly two. Note that we can't catch
+    // unlimited dimensions that only have two records so far.
+    if (length != 2) {
+        return false;
+    }
+
+    // Not sure if this is the best way, but here we are.
+    char name[NC_MAX_NAME + 1];
+    nc_inq_dimname(ncid, dim_id, name);
+
+    const size_t name_length = strlen(name);
+
+    // Check against known names of complex dimensions
+    for (size_t i = 0; i < num_known_dim_names; i++) {
+        if (strncmp(name, known_dim_names[i], name_length) == 0) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+/// Return true if a variable uses the dimension-convention
+bool pfnc_var_has_complex_dimension(int ncid, int nc_varid) {
+    int num_dims;
+    nc_inq_varndims(ncid, nc_varid, &num_dims);
+
+    int* dim_ids = (int*)malloc((size_t)num_dims * sizeof(int));
+    nc_inq_vardimid(ncid, nc_varid, dim_ids);
+
+    // Now we check if any of the dimensions match one of our known
+    // conventions. Do we need to check all of them, or just the
+    // first/last?
+    for (int i = 0; i < num_dims; i++) {
+        if (pfnc_is_complex_dim(ncid, dim_ids[i])) {
+            free(dim_ids);
+            return true;
+        }
+    }
+
+    free(dim_ids);
+    return false;
+}
+
+/// Return true if a netCDF datatype is a compound type
+bool is_compound_type(int ncid, int type_id) {
+    // There appears to be no API for detecting whether a type ID is a
+    // primitive type, so we have to check ourselves
+    if (type_id <= NC_MAX_ATOMIC_TYPE) {
+        return false;
+    }
+
+    int class_type;
+    nc_inq_user_type(ncid, type_id, NULL, NULL, NULL, NULL, &class_type);
+    return class_type == NC_COMPOUND;
+}
+
+/// Copy an array meant for a complex-dimensioned variable
+size_t* copy_complex_dim_size_t_array(
+    const size_t* old_array, int numdims, size_t complex_dim_value
+) {
+    size_t* new_buffer = NULL;
+
+    if (old_array != NULL) {
+        new_buffer = (size_t*)malloc(sizeof(size_t) * (size_t)numdims);
+
+        size_t last_dim = (size_t)(numdims - 1);
+        for (size_t i = 0; i < last_dim; i++) {
+            new_buffer[i] = old_array[i];
+        }
+
+        new_buffer[last_dim] = complex_dim_value;
+    }
+    return new_buffer;
+}
+
+ptrdiff_t* copy_complex_dim_ptrdiff_t_array(
+    const ptrdiff_t* old_array, int numdims, ptrdiff_t complex_dim_value
+) {
+    ptrdiff_t* new_buffer = NULL;
+
+    if (old_array != NULL) {
+        new_buffer = (ptrdiff_t*)malloc(sizeof(ptrdiff_t) * (size_t)numdims);
+
+        size_t last_dim = (size_t)(numdims - 1);
+        for (size_t i = 0; i < last_dim; i++) {
+            new_buffer[i] = old_array[i];
+        }
+
+        new_buffer[last_dim] = complex_dim_value;
+    }
+    return new_buffer;
+}
+
+bool pfnc_var_is_complex_type(int ncid, int varid) {
+    nc_type var_type_id;
+    if (nc_inq_vartype(ncid, varid, &var_type_id)) {
+        return false;
+    }
+
+    if (is_compound_type(ncid, var_type_id)) {
+        return compound_type_is_compatible(ncid, var_type_id);
+    }
+    return false;
+}
+
+size_t complex_type_size(nc_type base_type) {
+    switch (base_type) {
+    case NC_FLOAT:
+        return sizeof(float_complex);
+    case NC_DOUBLE:
+        return sizeof(double_complex);
+    default:
+        return 0;
+    }
+}
+
+size_t base_type_size(nc_type base_type) {
+    switch (base_type) {
+    case NC_FLOAT:
+        return sizeof(float);
+    case NC_DOUBLE:
+        return sizeof(double);
+    default:
+        return 0;
+    }
+}
+
+int get_or_make_complex_struct(
+    int ncid, nc_type* nc_typeid, nc_type base_type, const char* struct_name
+) {
+    // TODO: Error if not netCDF4
+
+    if (file_has_complex_struct(ncid, nc_typeid, base_type)) {
+        return NC_NOERR;
+    }
+
+    const size_t complex_size = complex_type_size(base_type);
+    if (complex_size == 0) {
+        return NC_EBADTYPE;
+    }
+    const size_t base_size = base_type_size(base_type);
+    if (base_size == 0) {
+        return NC_EBADTYPE;
+    }
+
+    CHECK(nc_def_compound(ncid, complex_size, struct_name, nc_typeid));
+    CHECK(nc_insert_compound(ncid, *nc_typeid, "r", 0, base_type));
+    CHECK(nc_insert_compound(ncid, *nc_typeid, "i", base_size, base_type));
+
+    return NC_NOERR;
+}
+
+int pfnc_get_double_complex_typeid(int ncid, nc_type* nc_typeid) {
+    return get_or_make_complex_struct(
+        ncid, nc_typeid, NC_DOUBLE, double_complex_struct_name
+    );
+}
+
+int pfnc_get_float_complex_typeid(int ncid, nc_type* nc_typeid) {
+    return get_or_make_complex_struct(
+        ncid, nc_typeid, NC_FLOAT, float_complex_struct_name
+    );
+}
+
+int pfnc_get_complex_dim(int ncid, int* nc_dim) {
+    int num_dims;
+    CHECK(nc_inq_ndims(ncid, &num_dims));
+
+    int* dim_ids = (int*)malloc((size_t)num_dims * sizeof(int));
+    int ierr = nc_inq_dimids(ncid, NULL, dim_ids, true);
+    if (ierr != NC_NOERR) {
+        goto cleanup;
+    }
+
+    // Now we check if any of the dimensions match one of our known
+    // conventions. Do we need to check all of them, or just the
+    // first/last?
+    for (int i = 0; i < num_dims; i++) {
+        if (pfnc_is_complex_dim(ncid, dim_ids[i])) {
+            *nc_dim = dim_ids[i];
+            goto cleanup;
+        }
+    }
+
+    ierr = nc_def_dim(ncid, complex_dim_name, 2, nc_dim);
+
+cleanup:
+    free(dim_ids);
+    return ierr;
+}
+
+int pfnc_put_vara_double_complex(
+    int ncid,
+    int varid,
+    const size_t* startp,
+    const size_t* countp,
+    const double_complex* op
+) {
+    return pfnc_put_vars_double_complex(ncid, varid, startp, countp, NULL, op);
+}
+
+int pfnc_get_vara_double_complex(
+    int ncid, int varid, const size_t* startp, const size_t* countp, double_complex* ip
+) {
+    return pfnc_get_vars_double_complex(ncid, varid, startp, countp, NULL, ip);
+}
+
+int pfnc_put_vars_double_complex(
+    int ncid,
+    int varid,
+    const size_t* startp,
+    const size_t* countp,
+    const ptrdiff_t* stridep,
+    const double_complex* op
+) {
+    if (!pfnc_var_is_complex(ncid, varid)) {
+        return NC_EBADTYPE;
+    }
+
+    // TODO: handle converting different float sizes
+
+    // Check if we can get away without fudging count/start sizes
+    if (((startp == NULL) && (countp == NULL) && (stridep == NULL))
+        || !pfnc_var_has_complex_dimension(ncid, varid)) {
+        return nc_put_vars(ncid, varid, startp, countp, stridep, op);
+    }
+
+    // The real variable has a complex dimension, but we're pretending
+    // it doesn't, so now we need start/count arrays of the real size
+
+    int numdims = 0;
+    CHECK(nc_inq_varndims(ncid, varid, &numdims));
+
+    // Copy start/count buffers, appending an extra element for the
+    // complex dimension. This dimension starts at 0 and has 2 elements
+    size_t* start_buffer = copy_complex_dim_size_t_array(startp, numdims, 0);
+    size_t* count_buffer = copy_complex_dim_size_t_array(countp, numdims, 2);
+    ptrdiff_t* stride_buffer = copy_complex_dim_ptrdiff_t_array(stridep, numdims, 1);
+
+    const int ierr =
+        nc_put_vars(ncid, varid, start_buffer, count_buffer, stride_buffer, op);
+
+    if (start_buffer != NULL) {
+        free(start_buffer);
+    }
+    if (count_buffer != NULL) {
+        free(count_buffer);
+    }
+    if (stride_buffer != NULL) {
+        free(stride_buffer);
+    }
+    return ierr;
+}
+
+int pfnc_get_vars_double_complex(
+    int ncid,
+    int varid,
+    const size_t* startp,
+    const size_t* countp,
+    const ptrdiff_t* stridep,
+    double_complex* ip
+) {
+    if (!pfnc_var_is_complex(ncid, varid)) {
+        return NC_EBADTYPE;
+    }
+
+    // TODO: handle converting different float sizes
+
+    // Check if we can get away without fudging count/start sizes
+    if (((startp == NULL) && (countp == NULL) && (stridep == NULL))
+        || !pfnc_var_has_complex_dimension(ncid, varid)) {
+        return nc_get_vars(ncid, varid, startp, countp, stridep, ip);
+    }
+
+    // The real variable has a complex dimension, but we're pretending
+    // it doesn't, so now we need start/count arrays of the real size
+
+    int numdims = 0;
+    CHECK(nc_inq_varndims(ncid, varid, &numdims));
+
+    // Copy start/count buffers, appending an extra element for the
+    // complex dimension. This dimension starts at 0 and has 2 elements
+    size_t* start_buffer = copy_complex_dim_size_t_array(startp, numdims, 0);
+    size_t* count_buffer = copy_complex_dim_size_t_array(countp, numdims, 2);
+    ptrdiff_t* stride_buffer = copy_complex_dim_ptrdiff_t_array(stridep, numdims, 1);
+
+    const int ierr =
+        nc_get_vars(ncid, varid, start_buffer, count_buffer, stride_buffer, ip);
+
+    if (start_buffer != NULL) {
+        free(start_buffer);
+    }
+    if (count_buffer != NULL) {
+        free(count_buffer);
+    }
+    if (stride_buffer != NULL) {
+        free(stride_buffer);
+    }
+    return ierr;
+}
+
+int pfnc_put_var1_double_complex(
+    int ncid, int varid, const size_t* indexp, const double_complex* data
+) {
+    return pfnc_put_vara_double_complex(ncid, varid, indexp, coord_one, data);
+}
+
+int pfnc_get_var1_double_complex(
+    int ncid, int varid, const size_t* indexp, double_complex* data
+) {
+    return pfnc_get_vara_double_complex(ncid, varid, indexp, coord_one, data);
+}
+
+int pfnc_put_vara_float_complex(
+    int ncid,
+    int varid,
+    const size_t* startp,
+    const size_t* countp,
+    const float_complex* op
+) {
+    return pfnc_put_vars_float_complex(ncid, varid, startp, countp, NULL, op);
+}
+
+int pfnc_get_vara_float_complex(
+    int ncid, int varid, const size_t* startp, const size_t* countp, float_complex* ip
+) {
+    return pfnc_get_vars_float_complex(ncid, varid, startp, countp, NULL, ip);
+}
+
+int pfnc_put_vars_float_complex(
+    int ncid,
+    int varid,
+    const size_t* startp,
+    const size_t* countp,
+    const ptrdiff_t* stridep,
+    const float_complex* op
+) {
+    if (!pfnc_var_is_complex(ncid, varid)) {
+        return NC_EBADTYPE;
+    }
+
+    // TODO: handle converting different float sizes
+
+    // Check if we can get away without fudging count/start sizes
+    if (((startp == NULL) && (countp == NULL) && (stridep == NULL))
+        || !pfnc_var_has_complex_dimension(ncid, varid)) {
+        return nc_put_vars(ncid, varid, startp, countp, stridep, op);
+    }
+
+    // The real variable has a complex dimension, but we're pretending
+    // it doesn't, so now we need start/count arrays of the real size
+
+    int numdims = 0;
+    CHECK(nc_inq_varndims(ncid, varid, &numdims));
+
+    // Copy start/count buffers, appending an extra element for the
+    // complex dimension. This dimension starts at 0 and has 2 elements
+    size_t* start_buffer = copy_complex_dim_size_t_array(startp, numdims, 0);
+    size_t* count_buffer = copy_complex_dim_size_t_array(countp, numdims, 2);
+    ptrdiff_t* stride_buffer = copy_complex_dim_ptrdiff_t_array(stridep, numdims, 1);
+
+    const int ierr =
+        nc_put_vars(ncid, varid, start_buffer, count_buffer, stride_buffer, op);
+
+    if (start_buffer != NULL) {
+        free(start_buffer);
+    }
+    if (count_buffer != NULL) {
+        free(count_buffer);
+    }
+    if (stride_buffer != NULL) {
+        free(stride_buffer);
+    }
+    return ierr;
+}
+
+int pfnc_get_vars_float_complex(
+    int ncid,
+    int varid,
+    const size_t* startp,
+    const size_t* countp,
+    const ptrdiff_t* stridep,
+    float_complex* ip
+) {
+    if (!pfnc_var_is_complex(ncid, varid)) {
+        return NC_EBADTYPE;
+    }
+
+    // TODO: handle converting different float sizes
+
+    // Check if we can get away without fudging count/start sizes
+    if (((startp == NULL) && (countp == NULL) && (stridep == NULL))
+        || !pfnc_var_has_complex_dimension(ncid, varid)) {
+        return nc_get_vars(ncid, varid, startp, countp, stridep, ip);
+    }
+
+    // The real variable has a complex dimension, but we're pretending
+    // it doesn't, so now we need start/count arrays of the real size
+
+    int numdims = 0;
+    CHECK(nc_inq_varndims(ncid, varid, &numdims));
+
+    // Copy start/count buffers, appending an extra element for the
+    // complex dimension. This dimension starts at 0 and has 2 elements
+    size_t* start_buffer = copy_complex_dim_size_t_array(startp, numdims, 0);
+    size_t* count_buffer = copy_complex_dim_size_t_array(countp, numdims, 2);
+    ptrdiff_t* stride_buffer = copy_complex_dim_ptrdiff_t_array(stridep, numdims, 1);
+
+    const int ierr =
+        nc_get_vars(ncid, varid, start_buffer, count_buffer, stride_buffer, ip);
+
+    if (start_buffer != NULL) {
+        free(start_buffer);
+    }
+    if (count_buffer != NULL) {
+        free(count_buffer);
+    }
+    if (stride_buffer != NULL) {
+        free(stride_buffer);
+    }
+    return ierr;
+}
+
+int pfnc_put_var1_float_complex(
+    int ncid, int varid, const size_t* indexp, const float_complex* data
+) {
+    return pfnc_put_vara_float_complex(ncid, varid, indexp, coord_one, data);
+}
+
+int pfnc_get_var1_float_complex(
+    int ncid, int varid, const size_t* indexp, float_complex* data
+) {
+    return pfnc_get_vara_float_complex(ncid, varid, indexp, coord_one, data);
+}
+
+int pfnc_def_var(
+    int ncid,
+    const char* name,
+    nc_type xtype,
+    int ndims,
+    const int* dimidsp,
+    int* varidp
+) {
+    // If it's not a complex number, we don't need to do anything
+    if (!(xtype == PFNC_DOUBLE_COMPLEX || xtype == PFNC_DOUBLE_COMPLEX_DIM
+          || xtype == PFNC_FLOAT_COMPLEX || xtype == PFNC_FLOAT_COMPLEX_DIM)) {
+        return nc_def_var(ncid, name, xtype, ndims, dimidsp, varidp);
+    }
+
+    const bool base_is_double =
+        (xtype == PFNC_DOUBLE_COMPLEX || xtype == PFNC_DOUBLE_COMPLEX_DIM);
+
+    // Check the format used by this file. If it's some variation on the
+    // classic model, then we have to use a complex dimension. Also,
+    // NcZarr, for some reason doesn't support compound types (yet?).
+    // I _think_ DAP supports compound types
+    int format = 0;
+    int mode = 0;
+    CHECK(nc_inq_format_extended(ncid, &format, &mode));
+
+    if ((format == NC_FORMAT_CLASSIC || format == NC_FORMAT_NETCDF4_CLASSIC)
+        || (mode == NC_FORMATX_NCZARR)) {
+        xtype = base_is_double ? PFNC_DOUBLE_COMPLEX_DIM : PFNC_FLOAT_COMPLEX_DIM;
+    }
+
+    if (xtype == PFNC_DOUBLE_COMPLEX_DIM || xtype == PFNC_FLOAT_COMPLEX_DIM) {
+        // Using a complex dimension. We need to get the complex dimension
+        // used in this file and append it to the list of dimensions
+        // passed in by the user
+
+        int complex_dim = 0;
+        CHECK(pfnc_get_complex_dim(ncid, &complex_dim));
+
+        int new_dims = ndims + 1;
+        int* dim_ids_buffer = (int*)malloc((size_t)new_dims * sizeof(int));
+        for (size_t i = 0; i < (size_t)ndims; i++) {
+            dim_ids_buffer[i] = dimidsp[i];
+        }
+        dim_ids_buffer[ndims] = complex_dim;
+
+        const nc_type base_type = base_is_double ? NC_DOUBLE : NC_FLOAT;
+
+        const int ierr =
+            nc_def_var(ncid, name, base_type, new_dims, dim_ids_buffer, varidp);
+        free(dim_ids_buffer);
+        return ierr;
+    }
+
+    // Using a complex type. We need to get the complex type used in
+    // this file and pass that as `xtype`
+    nc_type complex_type = 0;
+    if (base_is_double) {
+        CHECK(pfnc_get_double_complex_typeid(ncid, &complex_type));
+    } else {
+        CHECK(pfnc_get_float_complex_typeid(ncid, &complex_type));
+    }
+
+    return nc_def_var(ncid, name, complex_type, ndims, dimidsp, varidp);
+}
+
+int pfnc_inq_var(
+    int ncid,
+    int varid,
+    char* name,
+    nc_type* xtypep,
+    int* ndimsp,
+    int* dimidsp,
+    int* nattsp
+) {
+    if (!pfnc_var_has_complex_dimension(ncid, varid)) {
+        return nc_inq_var(ncid, varid, name, xtypep, ndimsp, dimidsp, nattsp);
+    }
+
+    // Tricky bit: if variable has complex dimension, and user used
+    // pfnc_inq_varndims, then dimidsp is one smaller than netCDF thinks
+    // it should be. So we'll have to allocate our own array of the
+    // correct size and copy out of that.
+
+    // This buffer will point to either the user's array, or our own one
+    int* buffer = dimidsp;
+    int numdims = 0;
+
+    if (dimidsp != NULL) {
+        CHECK(nc_inq_varndims(ncid, varid, &numdims));
+        buffer = (int*)malloc(sizeof(int) * (size_t)numdims);
+    }
+
+    int ierr = nc_inq_var(ncid, varid, name, xtypep, &numdims, buffer, nattsp);
+
+    if (ierr != NC_NOERR) {
+        goto cleanup;
+    }
+
+    if (dimidsp != NULL) {
+        if (numdims <= 0) {
+            // This should never happen
+            goto cleanup;
+        }
+        const size_t other_dims = (size_t)(numdims - 1);
+        for (size_t i = 0; i < other_dims; i++) {
+            dimidsp[i] = buffer[i];
+        }
+    }
+
+    if (ndimsp != NULL) {
+        *ndimsp = numdims - 1;
+    }
+
+cleanup:
+    free(buffer);
+    return ierr;
+}
+
+int pfnc_def_var_chunking(int ncid, int varid, int storage, const size_t* chunksizesp) {
+    if (chunksizesp == NULL || !pfnc_var_has_complex_dimension(ncid, varid)) {
+        return nc_def_var_chunking(ncid, varid, storage, chunksizesp);
+    }
+
+    // The real variable has a complex dimension, but we're pretending
+    // it doesn't, so now we need start/count arrays of the real size
+
+    int numdims = 0;
+    CHECK(nc_inq_varndims(ncid, varid, &numdims));
+
+    // Copy chunksize buffer, appending an extra element for the
+    // complex dimension
+    size_t* chunk_buffer = copy_complex_dim_size_t_array(chunksizesp, numdims, 2);
+
+    const int ierr = nc_def_var_chunking(ncid, varid, storage, chunk_buffer);
+    free(chunk_buffer);
+    return ierr;
+}
+
+int pfnc_inq_var_chunking(int ncid, int varid, int* storagep, size_t* chunksizesp) {
+    if (chunksizesp == NULL || !pfnc_var_has_complex_dimension(ncid, varid)) {
+        return nc_inq_var_chunking(ncid, varid, storagep, chunksizesp);
+    }
+
+    int numdims = 0;
+
+    CHECK(nc_inq_varndims(ncid, varid, &numdims));
+
+    // Copy chunksize buffer, appending an extra element for the
+    // complex dimension
+    size_t* chunk_buffer = copy_complex_dim_size_t_array(chunksizesp, numdims, 2);
+
+    const int ierr = nc_inq_var_chunking(ncid, varid, storagep, chunk_buffer);
+
+    if (ierr != NC_NOERR) {
+        goto cleanup;
+    }
+
+    const size_t other_dims = (size_t)(numdims - 1);
+    for (size_t i = 0; i < other_dims; i++) {
+        chunksizesp[i] = chunk_buffer[i];
+    }
+
+cleanup:
+    free(chunk_buffer);
+    return ierr;
+}
+
+int pfnc_get_vara(
+    int ncid, int varid, const size_t* startp, const size_t* countp, void* ip
+) {
+    if (pfnc_var_is_complex(ncid, varid)) {
+        nc_type base_type;
+        CHECK(pfnc_inq_var_complex_base_type(ncid, varid, &base_type));
+        switch (base_type) {
+        case NC_DOUBLE:
+            return pfnc_get_vara_double_complex(ncid, varid, startp, countp, ip);
+        case NC_FLOAT:
+            return pfnc_get_vara_float_complex(ncid, varid, startp, countp, ip);
+        default:
+            return NC_EBADTYPE;
+        }
+    }
+
+    return nc_get_vara(ncid, varid, startp, countp, ip);
+}
+
+int pfnc_put_vara(
+    int ncid, int varid, const size_t* startp, const size_t* countp, const void* op
+) {
+    if (pfnc_var_is_complex(ncid, varid)) {
+        nc_type base_type;
+        CHECK(pfnc_inq_var_complex_base_type(ncid, varid, &base_type));
+        switch (base_type) {
+        case NC_DOUBLE:
+            return pfnc_put_vara_double_complex(ncid, varid, startp, countp, op);
+        case NC_FLOAT:
+            return pfnc_put_vara_float_complex(ncid, varid, startp, countp, op);
+        default:
+            return NC_EBADTYPE;
+        }
+    }
+    return nc_put_vara(ncid, varid, startp, countp, op);
+}
+
+int pfnc_put_vars(
+    int ncid,
+    int varid,
+    const size_t* startp,
+    const size_t* countp,
+    const ptrdiff_t* stridep,
+    const void* op
+) {
+    if (pfnc_var_is_complex(ncid, varid)) {
+        nc_type base_type;
+        CHECK(pfnc_inq_var_complex_base_type(ncid, varid, &base_type));
+        switch (base_type) {
+        case NC_DOUBLE:
+            return pfnc_put_vars_double_complex(
+                ncid, varid, startp, countp, stridep, op
+            );
+        case NC_FLOAT:
+            return pfnc_put_vars_float_complex(
+                ncid, varid, startp, countp, stridep, op
+            );
+        default:
+            return NC_EBADTYPE;
+        }
+    }
+    return nc_put_vars(ncid, varid, startp, countp, stridep, op);
+}
+
+int pfnc_get_vars(
+    int ncid,
+    int varid,
+    const size_t* startp,
+    const size_t* countp,
+    const ptrdiff_t* stridep,
+    void* ip
+) {
+    if (pfnc_var_is_complex(ncid, varid)) {
+        nc_type base_type;
+        CHECK(pfnc_inq_var_complex_base_type(ncid, varid, &base_type));
+        switch (base_type) {
+        case NC_DOUBLE:
+            return pfnc_get_vars_double_complex(
+                ncid, varid, startp, countp, stridep, ip
+            );
+        case NC_FLOAT:
+            return pfnc_get_vars_float_complex(
+                ncid, varid, startp, countp, stridep, ip
+            );
+        default:
+            return NC_EBADTYPE;
+        }
+    }
+    return nc_get_vars(ncid, varid, startp, countp, stridep, ip);
+}


=====================================
pyproject.toml
=====================================
@@ -3,7 +3,7 @@ requires = [
     "Cython>=0.29",
     "oldest-supported-numpy ; python_version < '3.9'",
     "numpy>=2.0.0rc1 ; python_version >= '3.9'",
-    "setuptools>=61",
+    "setuptools>=61", "setuptools_scm[toml]>=3.4"
 ]
 build-backend = "setuptools.build_meta"
 
@@ -75,3 +75,5 @@ where = ["src"]
 
 [tool.pytest.ini_options]
 pythonpath = ["test"]
+
+[tool.setuptools_scm]


=====================================
src/netCDF4/_netCDF4.pyx
=====================================
@@ -1,4 +1,4 @@
-"""Version 1.7.0
+"""Version 1.7.1
 -------------
 
 # Introduction
@@ -1272,7 +1272,7 @@ import sys
 import functools
 from typing import Union
 
-__version__ = "1.7.0"
+__version__ = "1.7.1"
 
 # Initialize numpy
 import posixpath



View it on GitLab: https://salsa.debian.org/debian-gis-team/netcdf4-python/-/compare/3a9efecb01a1e14543eb6bb302bc7c7b36c6fea3...d36b49bbe31a798d6ed6cfee3d24a3e53f5ce8d1

-- 
This project does not include diff previews in email notifications.
View it on GitLab: https://salsa.debian.org/debian-gis-team/netcdf4-python/-/compare/3a9efecb01a1e14543eb6bb302bc7c7b36c6fea3...d36b49bbe31a798d6ed6cfee3d24a3e53f5ce8d1
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20240618/13d12e45/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list