NGen
Loading...
Searching...
No Matches
bmi_utilities.hpp
1#ifndef NGEN_BMI_UTILITIES_HPP
2#define NGEN_BMI_UTILITIES_HPP
3
4#include <algorithm>
5#include <cstdint>
6#include <iterator>
7#include <memory>
8#include <stdexcept>
9#include <string>
10#include <vector>
11#include <boost/type_index.hpp>
12#include "bmi/Bmi_Adapter.hpp"
13
14/*
15 * Typed access to BMI model variables.
16 *
17 * A BMI adapter reports a variable in three ways the standard does not tie together: a type, as a
18 * string; a size, as a count of bytes; and a buffer or pointer carrying no type at all. Reading or
19 * writing one therefore means choosing a C++ type, sizing a buffer, and trusting the two agree.
20 *
21 * Prefer the helpers below to calling Bmi_Adapter::GetValue() or GetValuePtr() and casting the
22 * result:
23 *
24 * with_bmi_type() resolve a type name to a C++ type; the one table the others are built on
25 * GetValue<T>() read a variable, converted to T
26 * GetValuePtr<T>() borrow the model's own storage, typed
27 * get_value_as_type() build a value in the variable's declared type
28 * get_values_as_type() build a buffer in the variable's declared type
29 * require_item_size() confirm a type name and a reported item size describe the same width
30 *
31 * The raw adapter calls stay public -- the adapters implement them, and a caller may genuinely want
32 * bytes -- but they are not the ordinary way to read or write a variable.
33 */
34
35namespace models {
36 namespace bmi {
38 struct unknown_bmi_type : std::runtime_error {
39 explicit unknown_bmi_type(const std::string &type_name)
40 : std::runtime_error("No C++ type corresponds to the BMI type name '" + type_name + "'")
42
43 std::string type_name;
44 };
45
85 template <typename F>
86 auto with_bmi_type(const std::string &type, F &&f)
87 {
88 if (type == "double" || type == "double precision")
89 return f.template operator()<double>();
90
91 if (type == "long double")
92 return f.template operator()<long double>();
93
94 if (type == "float" || type == "real")
95 return f.template operator()<float>();
96
97 // The stdint spellings, by typedef rather than by hand: int64_t is long on some platforms
98 // and long long on others, and the typedef is right on both.
99 if (type == "int8" || type == "int8_t")
100 return f.template operator()<std::int8_t>();
101
102 if (type == "uint8" || type == "uint8_t")
103 return f.template operator()<std::uint8_t>();
104
105 if (type == "int16" || type == "int16_t")
106 return f.template operator()<std::int16_t>();
107
108 if (type == "uint16" || type == "uint16_t")
109 return f.template operator()<std::uint16_t>();
110
111 if (type == "int32" || type == "int32_t")
112 return f.template operator()<std::int32_t>();
113
114 if (type == "uint32" || type == "uint32_t")
115 return f.template operator()<std::uint32_t>();
116
117 if (type == "int64" || type == "int64_t")
118 return f.template operator()<std::int64_t>();
119
120 if (type == "uint64" || type == "uint64_t")
121 return f.template operator()<std::uint64_t>();
122
123 if (type == "short" || type == "short int" || type == "signed short" || type == "signed short int")
124 return f.template operator()<short>();
125
126 if (type == "unsigned short" || type == "unsigned short int")
127 return f.template operator()<unsigned short>();
128
129 if (type == "int" || type == "signed" || type == "signed int" || type == "integer")
130 return f.template operator()<int>();
131
132 if (type == "unsigned" || type == "unsigned int")
133 return f.template operator()<unsigned int>();
134
135 if (type == "long" || type == "long int" || type == "signed long" || type == "signed long int")
136 return f.template operator()<long>();
137
138 if (type == "unsigned long" || type == "unsigned long int")
139 return f.template operator()<unsigned long>();
140
141 if (type == "long long" || type == "long long int" || type == "signed long long" || type == "signed long long int")
142 return f.template operator()<long long>();
143
144 if (type == "unsigned long long" || type == "unsigned long long int")
145 return f.template operator()<unsigned long long>();
146
147 throw unknown_bmi_type(type);
148 }
149
164 inline void require_item_size(const std::string &type, size_t item_size, const std::string &described)
165 {
166 const size_t declared = with_bmi_type(type, []<typename T>() -> size_t { return sizeof(T); });
167 if (declared != item_size) {
168 throw std::runtime_error("Cannot use " + described + ": type '" + type + "' is " +
169 std::to_string(declared) + " bytes, but the model reports an item size of " +
170 std::to_string(item_size) + " bytes.");
171 }
172 }
173
174 namespace helper {
175
187 template<typename TO, typename FROM>
188 static inline std::vector<TO> make_vector(const FROM* data, const size_t& count){
189 //Let Return Value Optimization (move semantics) help here (no copy of vector)
190 return std::vector<TO>(data, data+count);
191 }
192
193 }
194
208 template <typename T, typename Iterator>
209 std::shared_ptr<T> as_c_array(Iterator begin, Iterator end){
210 //Make a shared pointer large enough to hold all elements
211 //This is a CONTIGUOUS array of type T
212 //Must provide a custom deleter to delete the array
213 std::shared_ptr<T> ptr( new T[std::distance(begin, end)], [](T *p) { delete[] p; } );
214 std::transform(begin, end, ptr.get(), [](auto const& in){ return static_cast<T>(in); });
215 return ptr;
216 }
217
227 template <typename Iterator>
228 std::shared_ptr<void> get_values_as_type(std::string type, Iterator begin, Iterator end)
229 {
230 return with_bmi_type(type, [&]<typename T>() -> std::shared_ptr<void> {
231 return as_c_array<T>(begin, end);
232 });
233 }
234
243 template<typename T>
244 std::shared_ptr<void> get_value_as_type(std::string type, T value)
245 {
246 return with_bmi_type(type, [&]<typename U>() -> std::shared_ptr<void> {
247 return std::make_shared<U>(static_cast<U>(value));
248 });
249 }
250
269 template <typename T>
270 T *GetValuePtr(Bmi_Adapter &model, const std::string &name)
271 {
272 const size_t item_size = static_cast<size_t>(model.GetVarItemsize(name));
273 if (item_size != sizeof(T)) {
274 throw std::runtime_error("Cannot get pointer to variable " + name + " of model " +
275 model.get_model_name() + " as a " + std::to_string(sizeof(T)) +
276 " byte type; its items are " + std::to_string(item_size) + " bytes.");
277 }
278 return static_cast<T *>(model.GetValuePtr(name));
279 }
280
290 template <typename T>
291 std::vector<T> GetValue(Bmi_Adapter& model, const std::string& name) {
292 //TODO make model const ref
293 int total_mem = model.GetVarNbytes(name);
294 int item_size = model.GetVarItemsize(name);
295 if( total_mem == 0 || item_size == 0){
296 // Early stop if no items to be returned (also prevents possible divide by 0 computing num_items)
297 // Becuase this function is used by others which assume the return value to have AT LEAST
298 // 1 value, then this is a terminal error. This happens when the BMI model hasn't properly
299 // initialized/allocated/set the value to something meaningful. This can happen, for example,
300 // with grid data if the variables themselves are dynamically allocated based on grid properties
301 // but the grid is 0, then we essentially get a sentinal pointer with no data in it.
302 // one possible way to handle this would be to ensure a single element vector with NaN were returned here
303 // but since this function has to deal with both int and floating point types, that isn't possible
304 // because int has not NaN representation. So the best we can do at this point is raise a runtime exception
305 throw std::runtime_error("Unable to get value of variable "+name+". Model "+ model.get_model_name() +
306 " reports no valid items (Nbytes = "+std::to_string(total_mem) +
307 ", Itemsize = "+std::to_string(item_size)+".");
308 }
309 int num_items = total_mem/item_size;
310 //Determine what type we need to cast from
311 std::string type = model.get_analogous_cxx_type(model.GetVarType(name), item_size);
312
313 //C++ form of malloc
314 void* data = ::operator new(total_mem); //Possible to allocate 0 bytes...
315 // Use smart pointer to ensure cleanup on throw/out of scope...
316 // This works, and is relatively cheap since the lambda is stateless, only one instance should be created.
317 auto sptr = std::shared_ptr<void>(data, [](void *p) { ::operator delete(p); });
318 //Delegate to specific adapter's GetValue()
319 //Note, may be able to optimize this furthur using GetValuePtr
320 //which would avoid copying in the BMI model and copying again here
321 model.GetValue(name, data);
322
323 try {
324 require_item_size(type, static_cast<size_t>(item_size),
325 "variable '" + name + "' of model '" + model.get_model_name() + "'");
326 return with_bmi_type(type, [&]<typename S>() -> std::vector<T> {
327 return helper::make_vector<T>(static_cast<const S*>(data), num_items);
328 });
329 } catch (const unknown_bmi_type&) {
330 // The shared table knows the type name, but not what was being read or as what.
331 throw std::runtime_error("Unable to get value of variable " + name +
332 " as " + boost::typeindex::type_id<T>().pretty_name() + ": no logic for converting variable type " + type);
333 }
334 }
335 }
336}
337
338#endif //NGEN_BMI_UTILITIES_HPP
virtual std::string GetVarType(std::string name)=0
virtual void * GetValuePtr(std::string name)=0
virtual void GetValue(std::string name, void *dest)=0
virtual int GetVarItemsize(std::string name)=0
virtual int GetVarNbytes(std::string name)=0
Abstract adapter interface for C++ classes to interact with the essential aspects of external models ...
Definition Bmi_Adapter.hpp:16
std::string get_model_name()
Get the model name.
Definition Bmi_Adapter.cpp:124
virtual const std::string get_analogous_cxx_type(const std::string &external_type_name, const size_t item_size)
Reduce a type name the backing model reports to one models::bmi::with_bmi_type resolves.
Definition Bmi_Adapter.hpp:81
static std::vector< TO > make_vector(const FROM *data, const size_t &count)
make a vector of type
Definition bmi_utilities.hpp:188
Definition deserialization.hpp:40
std::vector< T > GetValue(Bmi_Adapter &model, const std::string &name)
Copy values from a BMI model adapter and box them into a vector.
Definition bmi_utilities.hpp:291
void require_item_size(const std::string &type, size_t item_size, const std::string &described)
Throw unless type names a C++ type of exactly item_size bytes.
Definition bmi_utilities.hpp:164
T * GetValuePtr(Bmi_Adapter &model, const std::string &name)
Get a pointer to a model variable's own storage, addressed as.
Definition bmi_utilities.hpp:270
std::shared_ptr< void > get_value_as_type(std::string type, T value)
Gets a single value, casted based on type then returned as a typeless (void) pointer.
Definition bmi_utilities.hpp:244
std::shared_ptr< T > as_c_array(Iterator begin, Iterator end)
Template function for copying iterator range into contiguous array.
Definition bmi_utilities.hpp:209
std::shared_ptr< void > get_values_as_type(std::string type, Iterator begin, Iterator end)
Gets values in iterator range, casted based on type then returned as typeless (void) pointer.
Definition bmi_utilities.hpp:228
auto with_bmi_type(const std::string &type, F &&f)
Call f with the C++ type that a BMI type name denotes.
Definition bmi_utilities.hpp:86
Definition AbstractCLibBmiAdapter.hpp:6
STL namespace.
Thrown when a BMI type name has no C++ type behind it.
Definition bmi_utilities.hpp:38
std::string type_name
Definition bmi_utilities.hpp:43
unknown_bmi_type(const std::string &type_name)
Definition bmi_utilities.hpp:39