Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
ROperator_Where.hxx
Go to the documentation of this file.
1#ifndef TMVA_SOFIE_ROperator_Where
2#define TMVA_SOFIE_ROperator_Where
3
5#include "TMVA/ROperator.hxx"
6#include "TMVA/RModel.hxx"
7
8#include <sstream>
9
10namespace TMVA{
11namespace Experimental{
12namespace SOFIE{
13
14
15
16template<typename T>
18private:
19
20 bool fIsInputBoolTensor = false;
21
22
23 std::string fNX;
24 std::string fNY;
25 std::string fNC;
26 std::string fNBroadcastedX;
27 std::string fNBroadcastedY;
28 std::string fNBroadcastedC;
29 std::string fNZ;
30
31
32
33 // static shapes (used when tensors are not dynamic) )
34 std::vector<size_t> fShapeX;
35 std::vector<size_t> fShapeY;
36 std::vector<size_t> fShapeC;
37 std::vector<size_t> fShapeZ;
38
39 // Dynamic generic shapes
40 std::vector<Dim> fDimShapeC;
41 std::vector<Dim> fDimShapeX;
42 std::vector<Dim> fDimShapeY;
43 std::vector<Dim> fDimShapeZ;
44
45 // Broadcast flag: mirrors convention of BasicBinary
46 // bit 0: broadcast Y->X (Y needs expanding)
47 // bit 1: broadcast X->Y (X needs expanding)
48 // bit 2: broadcast C->Z (C needs expanding)
49 // bit 4: shapes may differ at runtime (dynamic)
51
52public:
54 ROperator_Where(const std::string & nameC, const std::string & nameX, const std::string & nameY, const std::string & nameZ):
55 fNX(UTILITY::Clean_name(nameX)), fNY(UTILITY::Clean_name(nameY)), fNC(UTILITY::Clean_name(nameC)), fNZ(UTILITY::Clean_name(nameZ)){
58 }
59
60 // type of output given input
61 std::vector<ETensorType> TypeInference(std::vector<ETensorType> input) override {
62 return input;
63 }
64
65 // shape of output tensors given input tensors
66 std::vector<std::vector<size_t>> ShapeInference(std::vector<std::vector<size_t>> input) override {
67 // assume now inputs have same shape (no broadcasting)
68 auto ret = std::vector<std::vector<size_t>>(1, input[0]); // return vector size 1 with first input
69 return ret;
70 }
71
72 void Initialize(RModel& model) override {
73 // input must be a graph input, or already initialized intermediate tensor
74 if (!model.CheckIfTensorAlreadyExist(fNX)){
75 throw std::runtime_error(std::string("TMVA SOFIE Where Op Input Tensor ") + fNX + "is not found in model");
76 }
77 if (!model.CheckIfTensorAlreadyExist(fNY)) {
78 throw std::runtime_error(std::string("TMVA SOFIE Where Op Input Tensor ") + fNY + "is not found in model");
79 }
80 if (!model.CheckIfTensorAlreadyExist(fNC)) {
81 throw std::runtime_error(std::string("TMVA SOFIE Where Op Input Tensor ") + fNC + "is not found in model");
82 }
83 // check if fNC input tensor is boolean
84 if (model.IsReadyInputTensor(fNC))
85 fIsInputBoolTensor = true;
86
87 // ---------------------------------------------------------------- //
88 // Collect shapes – dynamic or static
89 // ---------------------------------------------------------------- //
90 int dynamicInputs = 0; // bitmask: bit0=C, bit1=X, bit2=Y
91
92 if (model.IsDynamicTensor(fNC)) {
94 dynamicInputs |= 1;
95 } else {
96 fShapeC = model.GetTensorShape(fNC);
98 }
99 if (model.IsDynamicTensor(fNX)) {
101 dynamicInputs |= 2;
102 } else {
103 fShapeX = model.GetTensorShape(fNX);
105 }
106 if (model.IsDynamicTensor(fNY)) {
108 dynamicInputs |= 4;
109 } else {
110 fShapeY = model.GetTensorShape(fNY);
112 }
113
114
115 if (model.Verbose()) {
116 if (dynamicInputs & 1)
117 std::cout << "Where : condition " << fNC << " is dynamic " << ConvertDimShapeToString(fDimShapeC) << "\n";
118 if (dynamicInputs & 2)
119 std::cout << "Where : " << fNX << " is dynamic " << ConvertDimShapeToString(fDimShapeX) << "\n";
120 if (dynamicInputs & 4)
121 std::cout << "Where : Y " << fNZ << " is dynamic " << ConvertDimShapeToString(fDimShapeZ) << "\n";
122 }
123
124 // ---------------------------------------------------------------- //
125 // Static path: all shapes known at code-gen time
126 // ---------------------------------------------------------------- //
127 if (dynamicInputs == 0) {
128
130 if (broadcast) {
131 // find shape to broadcast between X,Y,C looking for max length
135 bool broadcastX = false, broadcastY = false, broadcastC = false;
136 if (lengthX >= lengthY && lengthX >= lengthC) {
138 // broadcast Y and C if different than X
141 } else if (lengthY >= lengthX && lengthY >= lengthC) {
143 // broadcast X and C if different than Y
146 } else if (lengthC >= lengthX && lengthC >= lengthY) {
148 // broadcast X and Y if different than C
151 }
152
153 // Broadcast X to Z
154 if (broadcastX) {
155 fNBroadcastedX = "BC_" + fNX + "_to_" + fNZ;
156 if (model.IsInitializedTensor(fNX)) {
157 auto data = model.GetInitializedTensorData(fNX);
158 std::shared_ptr<void> broadcastedData(
159 UTILITY::UnidirectionalBroadcast(static_cast<T *>(data.get()), fShapeX, fShapeZ),
160 std::default_delete<T[]>());
161 // Update the data and the shape of X
164 } else {
165 // I need to prepend to shape of X the extra dimensions added for broadcasting to Z
166 if (fShapeX.size() < fShapeZ.size()) {
167 size_t nPrepend = fShapeZ.size() - fShapeX.size();
168 fShapeX.insert(fShapeX.begin(), nPrepend, 1);
169 }
170 }
171 }
172 // Broadcast Y to Z
173 if (broadcastY) {
174 fNBroadcastedY = "BC_" + fNY + "_to_" + fNZ;
175 if (model.IsInitializedTensor(fNY)) {
176 auto data = model.GetInitializedTensorData(fNY);
177 std::shared_ptr<void> broadcastedData(
178 UTILITY::UnidirectionalBroadcast(static_cast<T *>(data.get()), fShapeY, fShapeZ),
179 std::default_delete<T[]>());
180 // do not update tensor B but add broadcasted one (since it can be input to some other operators)
183 } else {
184 // I need to prepend to shape of Y the extra dimensions added for broadcasting to Z
185 if (fShapeY.size() < fShapeZ.size()) {
186 size_t nPrepend = fShapeZ.size() - fShapeY.size();
187 fShapeY.insert(fShapeY.begin(), nPrepend, 1);
188 }
189
190 }
191 }
192 // Broadcast C to Z
193 if (broadcastC) {
194 fNBroadcastedC = "BC_" + fNC + "_to_" + fNZ;
195 if (model.IsInitializedTensor(fNC)) {
196 auto data = model.GetInitializedTensorData(fNC);
197 std::shared_ptr<void> broadcastedData(
198 UTILITY::UnidirectionalBroadcast(static_cast<T *>(data.get()), fShapeC, fShapeZ),
199 std::default_delete<T[]>());
200 // do not update tensor C but add broadcasted one (since it can be input to some other operators)
203 } else {
204 // I need to prepend to shape of C the extra dimensions added for broadcasting to Z
205 if (fShapeC.size() < fShapeZ.size()) {
206 size_t nPrepend = fShapeZ.size() - fShapeC.size();
207 fShapeC.insert(fShapeC.begin(), nPrepend, 1);
208 }
209 }
210 }
211 } else {
213 }
214 // check case of constant output (if all inputs are defined)
215 if (model.IsInitializedTensor(fNC)) {
216 std::string nameC = fNBroadcastedC.empty() ? fNC : fNBroadcastedC;
217 auto dataC = static_cast<bool *>(model.GetInitializedTensorData(nameC).get());
219 T *dataX = nullptr;
220 T *dataY = nullptr;
221 std::vector<Dim> shapeDataX;
222 std::vector<Dim> shapeDataY;
223 if (model.IsInitializedTensor(fNX)) {
224 std::string nameX = fNBroadcastedX.empty() ? fNX : fNBroadcastedX;
225 dataX = static_cast<T *>(model.GetInitializedTensorData(nameX).get());
226 // flag tensors to not be written in a file
228 } else if (model.IsShapeTensor(fNX)) {
230 }
231 if (model.IsInitializedTensor(fNY)) {
232 std::string nameY = fNBroadcastedY.empty() ? fNY : fNBroadcastedY;
233 dataY = static_cast<T *>(model.GetInitializedTensorData(nameY).get());
235 } else if (model.IsShapeTensor(fNY)) {
237 }
238 std::vector<T> dataZ; // used in case output is constant tensor
239 std::vector<Dim> shapeDataZ; // used in case output is a shape tensor (can be also constant if all
240 // dimensions are not parametric)
241 // if fNC (condition) is initialized we know the output is a shape or a constant tensor,
242 // so we can compute it at initialization and add it as a constant tensor to the model
243 // (and not add the operator output as intermediate tensor to the model)
244 bool isOutputConstantTensor = true;
245 if (dataX && dataY) {
247 for (size_t i = 0; i < dataZ.size(); i++)
248 dataZ[i] = (dataC[i]) ? dataX[i] : dataY[i];
249 if (model.Verbose())
250 std::cout << "data A and B : dataZ constant: " << ConvertValuesToString(dataZ) << std::endl;
251 } else if (dataX && shapeDataY.size() > 0) {
253 for (size_t i = 0; i < shapeDataZ.size(); i++) {
254 shapeDataZ[i] = (dataC[i]) ? Dim{size_t(dataX[i])} : shapeDataY[i];
255 isOutputConstantTensor &= !shapeDataZ[i].isParam;
256 }
257 if (model.Verbose())
258 std::cout << "data A but shapeB " << ConvertDimShapeToString(shapeDataY) << " "
259 << isOutputConstantTensor << std::endl;
260 } else if (dataY && shapeDataX.size() > 0) {
262 for (size_t i = 0; i < shapeDataZ.size(); i++) {
263 shapeDataZ[i] = (dataC[i]) ? shapeDataY[i] : Dim{size_t(dataY[i])};
264 isOutputConstantTensor &= !shapeDataZ[i].isParam;
265 }
266 if (model.Verbose())
267 std::cout << "data B but shapeA " << ConvertDimShapeToString(shapeDataX) << " "
268 << isOutputConstantTensor << std::endl;
269 } else if (shapeDataY.size() > 0 && shapeDataX.size() > 0) {
271 for (size_t i = 0; i < shapeDataZ.size(); i++) {
272 shapeDataZ[i] = (dataC[i]) ? shapeDataX[i] : shapeDataY[i];
273 isOutputConstantTensor &= !shapeDataZ[i].isParam;
274 }
275 if (model.Verbose())
276 std::cout << " shapeA and B " << ConvertDimShapeToString(shapeDataX) << " shapeB "
278 }
279 fIsOutputConstant = true;
280 // add as constant or shape tensor depending on the case
281 if (dataZ.size() > 0)
282 model.AddConstantTensor<T>(fNZ, fShapeZ, dataZ.data());
283 else if (shapeDataZ.size() > 0)
284 model.AddShapeTensor(fNZ, shapeDataZ, fShapeZ.size() == 0);
285 else {
286 fIsOutputConstant = false;
287 }
288 if (fIsOutputConstant && model.Verbose())
289 std::cout << "Where op ---> " << fNZ << " " << ConvertShapeToString(fShapeZ) << " : "
291 << ((dataZ.size() > 0) ? " (constant)" : " (shape)") << std::endl;
292
293 // output is a constant tensor
295 fOutputTensorNames.pop_back();
296 }
297 if (!fIsOutputConstant) {
298
301 if (model.Verbose())
302 std::cout << "Where : condition : " << fNC << " " << ConvertShapeToString(fShapeC) << " X "
303 << fNX << " " << ConvertShapeToString(fShapeX) << " Y " << fNY << " "
304 << ConvertShapeToString(fShapeY) << " ---> " << fNZ << " " << ConvertShapeToString(fShapeZ)
305 << std::endl;
306 }
307 } else {
308 // ---------------------------------------------------------------- //
309 // Dynamic path: at least one input has a parametric shape
310 // Need to use BroadcastShape to find output shape
311 // ---------------------------------------------------------------- //
313 fBroadcastFlag = retXY.first;
314 fDimShapeZ = retXY.second;
316 fBroadcastFlag |= retCZ.first;
317 fDimShapeZ = retCZ.second;
318
319 // Resolve std::max params to actual input dim params (same logic as BasicBinary)
320 if (fBroadcastFlag & 4) {
321 auto IsInputDimParam = [&](const std::string &p) {
322 for (auto &input : model.GetInputTensorNames())
323 for (auto &s : model.GetDimTensorShape(input))
324 if (s.isParam && s.param == p) return true;
325 return false;
326 };
327 for (size_t i = 0; i < fDimShapeZ.size(); i++) {
328 auto &s = fDimShapeZ[i];
329 if (s.isParam && s.param.find("std::max") != std::string::npos) {
330 // prefer A dim over B dim
331 if (i < fDimShapeX.size() && IsInputDimParam(fDimShapeX[i].param)) {
332 s = (fDimShapeX[i].dim != 1) ? fDimShapeX[i] : fDimShapeY[i];
333 } else if (i < fDimShapeY.size() && IsInputDimParam(fDimShapeY[i].param)) {
334 s = (fDimShapeY[i].dim != 1) ? fDimShapeY[i] : fDimShapeX[i];
335 }
336 }
337 }
338 }
339 // I need to prepend to shape of X,Y,C the extra dimensions added for broadcasting to Z
340 if (fDimShapeX.size() < fDimShapeZ.size()) {
341 size_t nPrepend = fDimShapeZ.size() - fDimShapeX.size();
342 fDimShapeX.insert(fDimShapeX.begin(), nPrepend, Dim{1});
343 }
344 if (fDimShapeY.size() < fDimShapeZ.size()) {
345 size_t nPrepend = fDimShapeZ.size() - fDimShapeY.size();
346 fDimShapeY.insert(fDimShapeY.begin(), nPrepend, Dim{1});
347 }
348 if (fDimShapeC.size() < fDimShapeZ.size()) {
349 size_t nPrepend = fDimShapeZ.size() - fDimShapeC.size();
350 fDimShapeC.insert(fDimShapeC.begin(), nPrepend, Dim{1});
351 }
352
354
355 if (model.Verbose())
356 std::cout << "Where (dynamic) : C=" << ConvertDimShapeToString(fDimShapeC)
359 << " --> Y=" << ConvertDimShapeToString(fDimShapeZ) << "\n";
360 }
361 }
362
363 std::string GenerateInitCode() override {
364 std::stringstream out;
365 return out.str();
366 }
367
368 std::string Generate(std::string opName) override {
369
370 opName = "op_" + opName;
371 std::stringstream out;
372 out << SP << "\n//------ WHERE " << opName << " --> " << ConvertDimShapeToString(fDimShapeZ) << "\n";
373 if (fIsOutputConstant) return out.str();
374
375
376 // ---------------------------------------------------------------- //
377 // Runtime broadcast validation (dynamic shapes, flag bit 4)
378 // ---------------------------------------------------------------- //
379 if (fBroadcastFlag & 4) {
383 out << SP << "if (" << lengthX << " != " << lengthY << " || "
384 << lengthX << " != " << lengthC << ") {\n";
385 for (size_t i = 0; i < fDimShapeZ.size(); i++) {
386 // validate X vs Z
387 if (i < fDimShapeX.size() && fDimShapeX[i].isParam) {
388 out << SP << SP << "if (" << fDimShapeX[i] << " != 1 && "
389 << fDimShapeX[i] << " != " << fDimShapeZ[i] << ")\n";
390 out << SP << SP << SP
391 << "throw std::runtime_error(\"SOFIE Where: cannot broadcast A dim " << i << " in " << opName << "\");\n";
392 }
393 // validate Y vs Z
394 if (i < fDimShapeY.size() && fDimShapeY[i].isParam) {
395 out << SP << SP << "if (" << fDimShapeY[i] << " != 1 && "
396 << fDimShapeY[i] << " != " << fDimShapeZ[i] << ")\n";
397 out << SP << SP << SP
398 << "throw std::runtime_error(\"SOFIE Where: cannot broadcast B dim " << i << " in " << opName << "\");\n";
399 }
400 // validate C vs Z
401 if (i < fDimShapeC.size() && fDimShapeC[i].isParam) {
402 out << SP << SP << "if (" << fDimShapeC[i] << " != 1 && "
403 << fDimShapeC[i] << " != " << fDimShapeZ[i] << ")\n";
404 out << SP << SP << SP
405 << "throw std::runtime_error(\"SOFIE Where: cannot broadcast C dim " << i << " in " << opName << "\");\n";
406 }
407 }
408 out << SP << "}\n";
409 }
410 // implement now where using teh strides and looping on the different dimensions
411 // ---------------------------------------------------------------- //
412 // Generate loop(s) with per-dimension stride-based index arithmetic
413 // ---------------------------------------------------------------- //
418
419 auto buildIdxExpr = [&](const std::vector<Dim> &dimShape,
420 const std::vector<Dim> &strides,
421 size_t rankZ) -> std::string {
422 if (dimShape.empty() ||
423 std::all_of(dimShape.begin(), dimShape.end(),
424 [](Dim d) { return d.dim == 1 || d.GetVal() == "1"; }))
425 return "0";
426 std::string expr;
427 size_t offset = rankZ - dimShape.size();
428 for (size_t i = 0; i < dimShape.size(); ++i) {
429 if (dimShape[i].dim == 1 || dimShape[i].GetVal() == "1") continue;
430 expr += "idx_" + std::to_string(i + offset);
431 if (strides[i].GetVal() != "1")
432 expr += " * " + strides[i].GetVal();
433 expr += " + ";
434 }
435 if (expr.size() >= 3)
436 for (int j = 0; j < 3; j++) expr.pop_back(); // remove trailing " + "
437 return expr.empty() ? "0" : expr;
438 };
439
440 std::string idxX = buildIdxExpr(fDimShapeX, stridesX, fDimShapeZ.size());
441 std::string idxY = buildIdxExpr(fDimShapeY, stridesY, fDimShapeZ.size());
442 std::string idxC = buildIdxExpr(fDimShapeC, stridesC, fDimShapeZ.size());
443
444 // Emit nested loops over output shape
445 int nloop = 0;
446 std::string idxZ;
447 // case Z is a scalar (all dimensions are 1) or Z has no dimension
448 if (fDimShapeZ.empty() ||
449 std::all_of(fDimShapeZ.begin(), fDimShapeZ.end(),
450 [](Dim d) { return d.dim == 1 || d.GetVal() == "1"; })) {
451 idxZ = "0";
452 } else {
453 for (size_t i = 0; i < fDimShapeZ.size(); ++i) {
454 if (fDimShapeZ[i].dim != 1 && fDimShapeZ[i].GetVal() != "1") {
455 nloop++;
456 for (int j = 0; j < nloop; j++) out << SP;
457 out << "for (size_t idx_" << i << " = 0; idx_" << i
458 << " < " << fDimShapeZ[i] << "; ++idx_" << i << ") {\n";
459 idxZ += "idx_" + std::to_string(i);
460 if (stridesZ[i].GetVal() != "1")
461 idxZ += " * " + stridesZ[i].GetVal();
462 idxZ += " + ";
463 }
464 }
465 if (idxZ.size() >= 3)
466 for (int j = 0; j < 3; j++) idxZ.pop_back();
467 }
468
469 // Inner assignment
470 for (int j = 0; j < nloop + 1; j++) out << SP;
471 out << "tensor_" << fNZ << "[" << idxZ << "] = "
472 << "tensor_" << fNC << "[" << idxC << "] ? "
473 << "tensor_" << fNX << "[" << idxX << "] : "
474 << "tensor_" << fNY << "[" << idxY << "];\n";
475
476 // Close loops
477 for (int i = nloop; i > 0; i--) {
478 for (int j = 0; j < i; j++) out << SP;
479 out << "}\n";
480 }
481
482 return out.str();
483 }
484
485
486};
487
488}//SOFIE
489}//Experimental
490}//TMVA
491
492
493#endif //TMVA_SOFIE_ROperator_Where
#define d(i)
Definition RSha256.hxx:102
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
winID h TVirtualViewer3D TVirtualGLPainter p
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void data
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void input
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h offset
const_iterator begin() const
const_iterator end() const
std::vector< size_t > GetTensorShape(const std::string &name) const
Definition RModel.cxx:64
std::vector< Dim > GetDimTensorShape(const std::string &name) const
Definition RModel.cxx:100
bool IsDynamicTensor(const std::string &name) const
Definition RModel.cxx:286
void AddIntermediateTensor(std::string tensor_name, ETensorType type, std::vector< Dim > dim_shape)
Definition RModel.cxx:301
bool CheckIfTensorAlreadyExist(std::string tensor_name)
Definition RModel.cxx:157
void AddConstantTensor(std::string tensor_name, ETensorType type, std::vector< std::size_t > shape, std::shared_ptr< void > data)
Definition RModel.cxx:232
bool IsShapeTensor(const std::string &name) const
check if a tensor is a shape tensor
Definition RModel.cxx:260
bool IsInitializedTensor(const std::string &name) const
Definition RModel.cxx:273
std::vector< Dim > GetDynamicTensorShape(const std::string &name) const
Definition RModel.cxx:111
std::shared_ptr< void > GetInitializedTensorData(std::string tensor_name)
Definition RModel.cxx:366
void SetNotWritableInitializedTensor(const std::string &tensor_name)
Definition RModel.cxx:375
ETensorType GetTensorType(std::string name) const
Definition RModel.cxx:125
const std::vector< std::string > & GetInputTensorNames() const
Definition RModel.hxx:206
const std::vector< Dim > & GetShapeTensorValues(const std::string &tensor_name) const
Definition RModel.cxx:268
bool IsReadyInputTensor(const std::string &name) const
Definition RModel.cxx:295
void AddShapeTensor(const std::string &name, const std::vector< Dim > &shapeValues, bool scalar=false)
Definition RModel.cxx:242
std::vector< std::vector< size_t > > ShapeInference(std::vector< std::vector< size_t > > input) override
std::string Generate(std::string opName) override
std::vector< ETensorType > TypeInference(std::vector< ETensorType > input) override
ROperator_Where(const std::string &nameC, const std::string &nameX, const std::string &nameY, const std::string &nameZ)
std::vector< std::string_view > fInputTensorNames
Definition ROperator.hxx:50
bool fIsOutputConstant
flag to identify if operator has a constant output (no need to generate code)
Definition ROperator.hxx:47
const std::string SP
space used to correctly indent the generated C++ code
Definition ROperator.hxx:45
std::vector< std::string_view > fOutputTensorNames
Definition ROperator.hxx:51
bool AreSameShape(const std::vector< size_t > &, const std::vector< size_t > &)
std::vector< size_t > MultidirectionalBroadcastShape(std::vector< std::vector< size_t > >)
T * UnidirectionalBroadcast(const T *data, const std::vector< size_t > &shape, const std::vector< size_t > &targetShape)
std::vector< size_t > ComputeStrideFromShape(const std::vector< size_t > &shape)
compute stride of a tensor given its shape (assume layout is row-major)
std::string ConvertDimShapeToString(const std::vector< Dim > &shape)
std::size_t ConvertShapeToLength(const std::vector< size_t > &shape)
std::string ConvertValuesToString(size_t n, const T *data, size_t maxprint=-1)
std::vector< Dim > ConvertShapeToDim(const std::vector< size_t > &shape)
Convert shape from integer format to dynamic one (based on Dim)
std::string ConvertDimShapeToLength(const std::vector< Dim > &shape)
std::string ConvertShapeToString(const std::vector< size_t > &shape)
create variable transformations