forked from ecmwf/atlas
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Pack vector components into higher-rank vector fields. (ecmwf#218)
- Loading branch information
Showing
5 changed files
with
515 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
/* | ||
* (C) Crown Copyright 2024 Met Office | ||
* | ||
* This software is licensed under the terms of the Apache Licence Version 2.0 | ||
* which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. | ||
*/ | ||
|
||
#include "atlas/util/PackVectorFields.h" | ||
|
||
#include <map> | ||
#include <string> | ||
#include <type_traits> | ||
#include <vector> | ||
|
||
#include "atlas/array.h" | ||
#include "atlas/array/helpers/ArrayForEach.h" | ||
#include "atlas/functionspace.h" | ||
#include "atlas/option.h" | ||
#include "atlas/runtime/Exception.h" | ||
#include "atlas/util/Config.h" | ||
#include "eckit/config/LocalConfiguration.h" | ||
|
||
namespace atlas { | ||
namespace util { | ||
|
||
namespace { | ||
|
||
using eckit::LocalConfiguration; | ||
|
||
using array::DataType; | ||
using array::helpers::arrayForEachDim; | ||
|
||
void addOrReplaceField(FieldSet& fieldSet, const Field& field) { | ||
const auto fieldName = field.name(); | ||
if (fieldSet.has(fieldName)) { | ||
fieldSet[fieldName] = field; | ||
} else { | ||
fieldSet.add(field); | ||
} | ||
} | ||
|
||
Field& getOrCreateField(FieldSet& fieldSet, const FunctionSpace& functionSpace, | ||
const Config& config) { | ||
const auto fieldName = config.getString("name"); | ||
if (!fieldSet.has(fieldName)) { | ||
fieldSet.add(functionSpace.createField(config)); | ||
} | ||
return fieldSet[fieldName]; | ||
} | ||
|
||
void checkFieldCompatibility(const Field& componentField, | ||
const Field& vectorField) { | ||
ATLAS_ASSERT(componentField.functionspace().size() == | ||
vectorField.functionspace().size()); | ||
ATLAS_ASSERT(componentField.levels() == vectorField.levels()); | ||
ATLAS_ASSERT(componentField.variables() == 0); | ||
ATLAS_ASSERT(vectorField.variables() > 0); | ||
|
||
const auto checkStandardShape = [](const Field& field) { | ||
// Check for "standard" Atlas field shape. | ||
auto dim = 0; | ||
const auto rank = field.rank(); | ||
const auto shape = field.shape(); | ||
if (field.functionspace().size() != shape[dim++]) { | ||
return false; | ||
} | ||
if (const auto levels = field.levels(); | ||
levels && (dim >= rank || levels != shape[dim++])) { | ||
return false; | ||
} | ||
if (const auto variables = field.variables(); | ||
variables && (dim >= rank || variables != shape[dim++])) { | ||
return false; | ||
} | ||
if (dim != rank) { | ||
return false; | ||
} | ||
return true; | ||
}; | ||
|
||
ATLAS_ASSERT(checkStandardShape(componentField)); | ||
ATLAS_ASSERT(checkStandardShape(vectorField)); | ||
} | ||
|
||
template <typename ComponentField, typename VectorField, typename Functor> | ||
void copyFieldData(ComponentField& componentField, VectorField& vectorField, | ||
const Functor& copier) { | ||
checkFieldCompatibility(componentField, vectorField); | ||
|
||
const auto copyArrayData = [&](auto value, auto rank) { | ||
// Resolve value-type and rank from arguments. | ||
using Value = decltype(value); | ||
constexpr auto Rank = decltype(rank)::value; | ||
|
||
// Iterate over fields. | ||
auto vectorView = array::make_view<Value, Rank>(vectorField); | ||
auto componentView = array::make_view<Value, Rank - 1>(componentField); | ||
constexpr auto Dims = std::make_integer_sequence<int, Rank - 1>{}; | ||
arrayForEachDim(Dims, execution::par, std::tie(componentView, vectorView), | ||
copier); | ||
}; | ||
|
||
const auto selectRank = [&](auto value) { | ||
switch (vectorField.rank()) { | ||
case 2: | ||
return copyArrayData(value, std::integral_constant<int, 2>{}); | ||
case 3: | ||
return copyArrayData(value, std::integral_constant<int, 3>{}); | ||
default: | ||
ATLAS_THROW_EXCEPTION("Unsupported vector field rank: " + | ||
std::to_string(vectorField.rank())); | ||
} | ||
}; | ||
|
||
const auto selectType = [&]() { | ||
switch (vectorField.datatype().kind()) { | ||
case DataType::kind<double>(): | ||
return selectRank(double{}); | ||
case DataType::kind<float>(): | ||
return selectRank(float{}); | ||
case DataType::kind<long>(): | ||
return selectRank(long{}); | ||
case DataType::kind<int>(): | ||
return selectRank(int{}); | ||
default: | ||
ATLAS_THROW_EXCEPTION("Unknown datatype: " + | ||
std::to_string(vectorField.datatype().kind())); | ||
} | ||
}; | ||
|
||
selectType(); | ||
} | ||
|
||
} // namespace | ||
|
||
FieldSet pack_vector_fields(const FieldSet& fields, FieldSet packedFields) { | ||
// Get the number of variables for each vector field. | ||
auto vectorSizeMap = std::map<std::string, idx_t>{}; | ||
for (const auto& field : fields) { | ||
auto vectorFieldName = std::string{}; | ||
if (field.metadata().get("vector_field_name", vectorFieldName)) { | ||
++vectorSizeMap[vectorFieldName]; | ||
} | ||
} | ||
auto vectorIndexMap = std::map<std::string, idx_t>{}; | ||
|
||
// Pack vector fields. | ||
for (const auto& field : fields) { | ||
auto vectorFieldName = std::string{}; | ||
if (!field.metadata().get("vector_field_name", vectorFieldName)) { | ||
// Not a vector component field. | ||
addOrReplaceField(packedFields, field); | ||
continue; | ||
} | ||
|
||
// Field is vector field component. | ||
const auto& componentField = field; | ||
|
||
// Get or create vector field. | ||
const auto vectorFieldConfig = | ||
option::name(vectorFieldName) | | ||
option::levels(componentField.levels()) | | ||
option::vector(vectorSizeMap[vectorFieldName]) | | ||
option::datatype(componentField.datatype()); | ||
auto& vectorField = getOrCreateField( | ||
packedFields, componentField.functionspace(), vectorFieldConfig); | ||
|
||
// Copy field data. | ||
const auto vectorIndex = vectorIndexMap[vectorFieldName]++; | ||
const auto copier = [&](auto&& componentElem, auto&& vectorElem) { | ||
vectorElem(vectorIndex) = componentElem; | ||
}; | ||
copyFieldData(componentField, vectorField, copier); | ||
|
||
// Copy metadata. | ||
const auto componentFieldMetadata = componentField.metadata(); | ||
auto componentFieldMetadataVector = std::vector<LocalConfiguration>{}; | ||
vectorField.metadata().get("component_field_metadata", | ||
componentFieldMetadataVector); | ||
componentFieldMetadataVector.push_back(componentFieldMetadata); | ||
vectorField.metadata().set("component_field_metadata", | ||
componentFieldMetadataVector); | ||
} | ||
return packedFields; | ||
} | ||
|
||
FieldSet unpack_vector_fields(const FieldSet& fields, FieldSet unpackedFields) { | ||
for (const auto& field : fields) { | ||
auto componentFieldMetadataVector = std::vector<LocalConfiguration>{}; | ||
if (!field.metadata().get("component_field_metadata", | ||
componentFieldMetadataVector)) { | ||
// Not a vector field. | ||
addOrReplaceField(unpackedFields, field); | ||
continue; | ||
} | ||
|
||
// Field is vector. | ||
const auto& vectorField = field; | ||
|
||
auto vectorIndex = 0; | ||
for (const auto& componentFieldMetadata : componentFieldMetadataVector) { | ||
|
||
// Get or create field. | ||
auto componentFieldName = std::string{}; | ||
componentFieldMetadata.get("name", componentFieldName); | ||
const auto componentFieldConfig = | ||
option::name(componentFieldName) | | ||
option::levels(vectorField.levels()) | | ||
option::datatype(vectorField.datatype()); | ||
auto& componentField = getOrCreateField( | ||
unpackedFields, vectorField.functionspace(), componentFieldConfig); | ||
|
||
// Copy field data. | ||
const auto copier = [&](auto&& componentElem, auto&& vectorElem) { | ||
componentElem = vectorElem(vectorIndex); | ||
}; | ||
copyFieldData(componentField, vectorField, copier); | ||
|
||
// Copy metadata. | ||
componentField.metadata() = componentFieldMetadata; | ||
|
||
++vectorIndex; | ||
} | ||
} | ||
return unpackedFields; | ||
} | ||
|
||
} // namespace util | ||
} // namespace atlas |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/* | ||
* (C) Crown Copyright 2024 Met Office | ||
* | ||
* This software is licensed under the terms of the Apache Licence Version 2.0 | ||
* which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include "atlas/field.h" | ||
|
||
namespace eckit { | ||
class LocalConfiguration; | ||
} | ||
|
||
namespace atlas { | ||
namespace util { | ||
|
||
/// @brief Packs vector field components into vector fields | ||
/// | ||
/// @details Iterates through @param fields and creates vector fields from any | ||
/// component field with the "vector name" string metadata. These, as | ||
/// well as any present scalar fields, are added to the return-value | ||
/// field set. | ||
/// Note, a mutable @param packedFields field set can be supplied if | ||
/// one needs to guarantee the order of the packed fields | ||
FieldSet pack_vector_fields(const FieldSet& fields, | ||
FieldSet packedFields = FieldSet{}); | ||
|
||
/// @brief Unpacks vector field into vector field components. | ||
/// | ||
/// @details Undoes "pack" operation when a set of packed fields are supplied | ||
/// as @param fields. A mutable @param unpackedFields field set can be | ||
/// supplied if one needs to guarantee the order of the unpacked | ||
/// fields. | ||
FieldSet unpack_vector_fields(const FieldSet& fields, | ||
FieldSet unpackedFields = FieldSet{}); | ||
|
||
} // namespace util | ||
} // namespace atlas |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.