Skip to content

Commit

Permalink
C++: Add C++ library
Browse files Browse the repository at this point in the history
  • Loading branch information
ZedThree committed Jan 12, 2024
1 parent 2813ac8 commit 2bd3fde
Show file tree
Hide file tree
Showing 12 changed files with 1,222 additions and 10 deletions.
13 changes: 13 additions & 0 deletions Dependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,20 @@ include(cmake/CPM.cmake)
# targets
function(nc_complex_setup_dependencies)
include(CTest)
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})

if(BUILD_TESTING AND NOT TARGET Catch2::Catch2WithMain)
cpmaddpackage("gh:catchorg/Catch2@3.3.2")
endif()

find_package(netCDF REQUIRED)

if (nc_complex_BUILD_CXX)
if (nc_complex_DOWNLOAD_NETCDF_CXX)
set(NCXX_ENABLE_TESTS OFF CACHE BOOL "" FORCE)
cpmaddpackage("gh:ZedThree/netcdf-cxx4#722cbd5")
else()
find_package(netCDFCxx REQUIRED)
endif()
endif()
endfunction()
7 changes: 7 additions & 0 deletions ProjectOptions.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ macro(nc_complex_setup_options)
ON
nc_complex_ENABLE_HARDENING
OFF)
option(nc_complex_BUILD_CXX "Build C++ API" OFF)
cmake_dependent_option(
nc_complex_DOWNLOAD_NETCDF_CXX
"Download and build netCDF C++ API automatically (recommended)"
ON
nc_complex_BUILD_CXX
OFF)

nc_complex_supports_sanitizers()

Expand Down
36 changes: 33 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ nc-complex
==========

`nc-complex` is a lightweight, drop-in extension for netCDF that
handles reading and writing complex numbers. Currently there is just a
C API, but this is being integrated into [netcdf4-python][netcdf4],
and C++ and Fortran APIs are planned.
handles reading and writing complex numbers. Currently there are C and
C++ APIs, and it has been integrated into [netcdf4-python][netcdf4]. A
Fortran API is also planned.

The `nc-complex` library understands most of the major existing
conventions for storing complex numbers, including as a compound
Expand Down Expand Up @@ -71,6 +71,35 @@ length two -- however, `nc-complex` handles all this under the hood,
and so the same snippet above will work the same, whichever convention
is being used in the file.

C++
---

The C++ API inherits from the [netcdf-cxx4][netcdf_cxx4] API and aims
to be a completely drop-in replacement. Just `#include` our header and
change the `netCDF::` namespace to `nc_complex::`:

```C++
#include "nc_complex/nc_complex_cpp.h"

nc_complex::NcFile nc_file{filename, nc_complex::NcFile::FileMode::read};
```
If you already have `using namespace netCDF`, changing this to `using
namespace nc_complex` should be sufficient to start using complex
numbers.
`NcVar::putVar` and `NcVar::getVar` will accept pointers to complex
numbers, and you can define a new variable with a complex type like
so:
```C++
using namespace nc_complex;
NcFile nc_file{full_filename, NcFile::FileMode::newFile};
const NcDim x_dim = nc_file.addDim("x", len_x);
auto var = nc_file.addVar("data", NcDoubleComplex{}, x_dim);
```


Limitations
-----------

Expand Down Expand Up @@ -320,6 +349,7 @@ Licence
`nc-complex` is released under the MIT licence.
[netcdf4]: http://unidata.github.io/netcdf4-python/
[netcdf_cxx4]: https://github.com/Unidata/netcdf-cxx4
[cpp_memcpy_example]: https://en.cppreference.com/w/c/language/arithmetic_types#Complex_floating_types
[h5py]: https://docs.h5py.org/en/stable/index.html
[hdf5jl]: https://juliaio.github.io/HDF5.jl/stable/
Expand Down
2 changes: 1 addition & 1 deletion include/nc_complex/nc_complex.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ typedef float _Complex float_complex;
/// @name Helper functions
///@{
/// Helper functions for converting between (pointers to) C++ and C complex types
namespace plasmafair {
namespace nc_complex {
NC_COMPLEX_EXPORT inline double_complex* cpp_to_c_complex(std::complex<double>* data) {
return reinterpret_cast<double_complex*>(data);
}
Expand Down
272 changes: 272 additions & 0 deletions include/nc_complex/nc_complex_cpp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
#include <complex>
#include <cstddef>
#include <netcdf>
#include <vector>

#include "nc_complex.h"
#include "nc_complex/nc_complex_export.h"

namespace nc_complex {

// Bring in everything we aren't wrapping from the netCDF namespace into our
// one. Then all names can be used from our namespace
using netCDF::NcAtomicType;
using netCDF::NcCompoundType;
using netCDF::NcOpaqueType;
using netCDF::NcType;
using netCDF::NcVlenType;

using netCDF::ncByte;
using netCDF::ncChar;
using netCDF::ncDouble;
using netCDF::ncFloat;
using netCDF::ncInt;
using netCDF::ncInt64;
using netCDF::ncShort;
using netCDF::ncString;
using netCDF::ncUbyte;
using netCDF::ncUint;
using netCDF::ncUint64;
using netCDF::ncUshort;

using netCDF::NcDim;

using netCDF::NcAtt;
using netCDF::NcGroupAtt;
using netCDF::NcVarAtt;

using netCDF::ncCheck;
using netCDF::ncCheckDataMode;
using netCDF::ncCheckDefineMode;

namespace exceptions {
using namespace netCDF::exceptions;
}

class NcVar;

class NC_COMPLEX_EXPORT NcGroup : public netCDF::NcGroup {
public:
using netCDF::NcGroup::NcGroup;

// Convert base class to specialised class. We need this so we can
// wrap methods on `netCDF::NcGroup` and return this class instead
// of `netCDF::NcVar`.
explicit NcGroup(const netCDF::NcGroup& other) : netCDF::NcGroup(other) {}

NcGroup getParentGroup() const {
return NcGroup{netCDF::NcGroup::getParentGroup()};
}
std::multimap<std::string, NcGroup> getGroups(
NcGroup::GroupLocation location = ChildrenGrps
) const;
std::set<NcGroup> getGroups(
const std::string& name, NcGroup::GroupLocation location = ChildrenGrps
) const;
NcGroup getGroup(
const std::string& name, NcGroup::GroupLocation location = ChildrenGrps
) const;
NcGroup addGroup(const std::string& name) const;

std::multimap<std::string, NcVar> getVars(NcGroup::Location location = Current)
const;
std::set<NcVar> getVars(
const std::string& name, NcGroup::Location location = Current
) const;
NcVar getVar(const std::string& name, NcGroup::Location location = Current) const;

NcVar addVar(const std::string& name, const NcType& ncType) const;
NcVar addVar(const std::string& name, const NcAtomicType& ncType) const;

NcVar addVar(
const std::string& name, const std::string& typeName, const std::string& dimName
) const;
NcVar addVar(const std::string& name, const NcType& ncType, const NcDim& ncDim)
const;
NcVar addVar(
const std::string& name, const NcAtomicType& ncType, const NcDim& ncDim
) const;

NcVar addVar(
const std::string& name,
const std::string& typeName,
const std::vector<std::string>& dimNames
) const;
NcVar addVar(
const std::string& name,
const NcType& ncType,
const std::vector<NcDim>& ncDimVector
) const;
NcVar addVar(
const std::string& name,
const NcAtomicType& ncType,
const std::vector<NcDim>& ncDimVector
) const;
};

// We have to completely reimplement `NcFile` so that it inherits from *our*
// `NcGroup`

class NC_COMPLEX_EXPORT NcFile : public NcGroup {
public:
enum FileMode {
read, //!< File exists, open read-only.
write, //!< File exists, open for writing.
replace, //!< Create new file, even if already exists.
newFile //!< Create new file, fail if already exists.
};

enum FileFormat {
classic, //!< Classic format, classic data model
classic64, //!< 64-bit offset format, classic data model
nc4, //!< (default) netCDF-4/HDF5 format, enhanced data model
nc4classic //!< netCDF-4/HDF5 format, classic data model
};

NcFile() = default;
NcFile(
const std::string& filePath,
FileMode fMode,
FileFormat fFormat = FileFormat::nc4
);

~NcFile() override;

/// Do not allow definition of NcFile involving copying any NcFile or NcGroup.
/// Because the destructor closes the file and releases al resources such
/// an action could leave NcFile objects in an invalid state
NcFile& operator=(const NcGroup& rhs) = delete;
NcFile& operator=(const NcFile& rhs) = delete;
NcFile(const NcGroup& rhs) = delete;
NcFile(const NcFile& rhs) = delete;

NcFile& operator=(NcFile&& rhs) = delete;
NcFile(NcFile&& rhs) = delete;

void open(
const std::string& filePath,
FileMode fMode,
FileFormat fFormat = FileFormat::nc4
);
void close();

void sync();
void set_Fill(int fillmode, int* old_modep);
void redef();
void enddef();
};

class NC_COMPLEX_EXPORT NcFloatComplex : public NcAtomicType {
public:
NcFloatComplex() : NcAtomicType(PFNC_FLOAT_COMPLEX) {}
};

class NC_COMPLEX_EXPORT NcDoubleComplex : public NcAtomicType {
public:
NcDoubleComplex() : NcAtomicType(PFNC_DOUBLE_COMPLEX) {}
};

class NC_COMPLEX_EXPORT NcFloatComplexDim : public NcAtomicType {
public:
NcFloatComplexDim() : NcAtomicType(PFNC_FLOAT_COMPLEX_DIM) {}
};

class NC_COMPLEX_EXPORT NcDoubleComplexDim : public NcAtomicType {
public:
NcDoubleComplexDim() : NcAtomicType(PFNC_DOUBLE_COMPLEX_DIM) {}
};

class NC_COMPLEX_EXPORT NcVar : public netCDF::NcVar {
public:
using netCDF::NcVar::NcVar;

// Convert base class to specialised class. We need this so we can
// wrap methods on `netCDF::NcGroup` and return this class instead
// of `netCDF::NcVar`.
explicit NcVar(const netCDF::NcVar& other) : netCDF::NcVar(other) {}

using netCDF::NcVar::getVar;
using netCDF::NcVar::putVar;

void getVar(std::complex<double>* dataValues) const;
void getVar(const std::vector<size_t>& index, std::complex<double>* dataValues)
const;
void getVar(
const std::vector<size_t>& startp,
const std::vector<size_t>& countp,
std::complex<double>* dataValues
) const;
void getVar(
const std::vector<size_t>& startp,
const std::vector<size_t>& countp,
const std::vector<ptrdiff_t>& stridep,
std::complex<double>* dataValues
) const;

void getVar(std::complex<float>* dataValues) const;
void getVar(const std::vector<size_t>& index, std::complex<float>* dataValues)
const;
void getVar(
const std::vector<size_t>& startp,
const std::vector<size_t>& countp,
std::complex<float>* dataValues
) const;
void getVar(
const std::vector<size_t>& startp,
const std::vector<size_t>& countp,
const std::vector<ptrdiff_t>& stridep,
std::complex<float>* dataValues
) const;

void putVar(const std::complex<double>* dataValues) const;
void putVar(
const std::vector<size_t>& index, const std::complex<double>* dataValues
) const;
void putVar(
const std::vector<size_t>& startp,
const std::vector<size_t>& countp,
const std::complex<double>* dataValues
) const;
void putVar(
const std::vector<size_t>& startp,
const std::vector<size_t>& countp,
const std::vector<ptrdiff_t>& stridep,
const std::complex<double>* dataValues
) const;

void putVar(const std::complex<float>* dataValues) const;
void putVar(const std::vector<size_t>& index, const std::complex<float>* dataValues)
const;
void putVar(
const std::vector<size_t>& startp,
const std::vector<size_t>& countp,
const std::complex<float>* dataValues
) const;
void putVar(
const std::vector<size_t>& startp,
const std::vector<size_t>& countp,
const std::vector<ptrdiff_t>& stridep,
const std::complex<float>* dataValues
) const;

/// Returns true if the variable is complex
bool isComplex() const;
/// Returns true if the variable is complex and uses a compound datatype
bool isComplexType() const;
/// Returns true if the variable is complex and uses a complex dimension
bool hasComplexDim() const;

/*! The the number of dimensions. */
int getDimCount() const;

/*! Gets the i'th NcDim object. */
netCDF::NcDim getDim(int i) const;

/*! Gets the set of NcDim objects. */
std::vector<netCDF::NcDim> getDims() const;

private:
int groupId() const { return this->getParentGroup().getId(); }
};

} // namespace nc_complex
Loading

0 comments on commit 2bd3fde

Please sign in to comment.