Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
ROperator_Slice.hxx
Go to the documentation of this file.
1#ifndef TMVA_SOFIE_ROPERATOR_SLICE
2#define TMVA_SOFIE_ROPERATOR_SLICE
3
5#include "TMVA/ROperator.hxx"
6#include "TMVA/RModel.hxx"
7
8#include <cassert>
9#include <sstream>
10#include <numeric>
11
12namespace TMVA{
13namespace Experimental{
14namespace SOFIE{
15
16// slice operator
17
18template <typename IType>
20{
21
22private:
23
24 // flags to indicate if start/end and steps are not defined at compiled time
25 bool fIsStartUndef = false;
26 bool fIsEndUndef = false;
27 bool fIsStepUndef = false;
28 bool fIdentitySlice = false;
29 std::string fNData; // input data tensor name
30 std::string fNOutput; // output data name
31 std::vector<std::string> fNames; // tensor names for meta(axis) information
32 std::vector<Dim> fShapeInput; // input shape data
33 std::vector<Dim> fShapeOutput; // output shape data
34 // saved Start/End.Steps are corrected from initial ONNX for negative/default values
35 // and are available for each axis
36 std::vector<Dim> fStart; // starting values of slices for all axes
37 std::vector<Dim> fEnd; // End values of slices for all axes
38 std::vector<Dim> fSteps; // step values of slices for all axes
39 std::vector<Dim> fStartDims; // input starting values of slices
40 std::vector<Dim> fEndDims; // input End values of slices
41 std::vector<Dim> fStepDims; // input step values of slices
42 std::vector<IType> fAxes; // axes for input start/emd/step values
43
44 std::vector<std::vector<IType>> fAttributes; // attributes for the version <=10 case
45
46
47public:
48
50
51 // ctor for versions >= 10
52 ROperator_Slice(std::string nameData, std::vector<std::string> names, std::string nameOutput)
53 : fNData(UTILITY::Clean_name(nameData)),
54 fNOutput(UTILITY::Clean_name(nameOutput))
55 {
56 fNames.resize(4);
57 // axes and steps can be optional
58 for (size_t i = 0; i < names.size(); ++i) {
59 fNames[i] = UTILITY::Clean_name(names[i]);
60 }
61
64 }
65 // ctor for versions < 10
66 ROperator_Slice(std::string nameData, std::vector<IType> starts, std::vector<IType> ends, std::vector<IType> axes, std::string nameOutput)
67 : fNData(UTILITY::Clean_name(nameData)),
68 fNOutput(UTILITY::Clean_name(nameOutput))
69 {
70 fAttributes.push_back(starts);
71 fAttributes.push_back(ends);
72 fAttributes.push_back(axes);
73 }
74
75
76
77 void Initialize(RModel& model) override {
78 if (model.CheckIfTensorAlreadyExist(fNData) == false){ //input must be a graph input, or already initialized intermediate tensor
79 throw std::runtime_error("TMVA Slice Op Input Tensor is not found in model");
80 }
81
82 std::vector<std::vector<Dim>> shapes;
84 shapes.push_back(fShapeInput);
85
86 std::vector<std::vector<IType>> itensors(4);
87
88 if (fNames.size() > 0) { // size has to be equal to 4
89 // loop on the extra 2 or 3 or 4 inputs
90 for (size_t i = 0; i < 4; ++i) {
91 if (!fNames[i].empty()) {
92 if (model.IsInitializedTensor(fNames[i])) {
93 auto dptr = model.GetInitializedTensorData(fNames[i]);
94 auto tensor = static_cast<IType *>(dptr.get());
95 auto vec = model.GetTensorShape(fNames[i]);
96 assert(vec.size() == 1);
97 itensors[i] = std::vector<IType>(tensor, tensor + vec[0]);
98
99 } else if (model.IsShapeTensor(fNames[i])) {
100 // case is a shape tensor
101 if (i == 0) {
103 } else if (i == 1) {
105 } else if (i == 3) {
107 }
108 } else {
109 // case is an intermediate tensor
110 auto shape = model.GetTensorShape(fNames[i]);
111 size_t s = shape[0];
112 for (size_t k = 0; k < s; k++) {
113 if (i == 0) {
114 fStartDims.push_back( Dim{std::string("start_") + fNOutput + "_" + std::to_string(k)});
115 fIsStartUndef = true;
116 } else if (i == 1) {
117 fEndDims.push_back(Dim{std::string("end_") + fNOutput + "_" + std::to_string(k)});
118 fIsEndUndef = true;
119 } else if (i == 3) {
120 fStepDims.push_back(Dim{std::string("step_") + fNOutput + "_" + std::to_string(k)});
121 fIsStepUndef = true;
122 }
123 }
124 }
125 }
126 }
127 } else {
128 // old slice versions
129 assert(fAttributes.size() > 1);
130 for (size_t i = 0; i < fAttributes.size(); i++) {
131 itensors[i] = fAttributes[i];
132 }
133 }
134 size_t dim = fShapeInput.size();
135
136 // default values
137 fSteps = std::vector<Dim>(dim, Dim{1});
138 fStart = std::vector<Dim>(dim, Dim{0});
140
141 // default axes
142 if (itensors[2].empty()) {
143 fAxes.resize(dim);
144 std::iota(fAxes.begin(), fAxes.end(), 0);
145 } else {
146 fAxes = itensors[2];
147 for (size_t i = 0; i < fAxes.size(); i++) {
148 // negative axes - they count from the back
149 if (fAxes[i] < 0) fAxes[i] = dim + fAxes[i];
150 if (fAxes[i] < 0 || fAxes[i] >= static_cast<IType>(dim))
151 throw std::runtime_error("TMVA Slice Op : invalid axis value " + std::to_string(fAxes[i]) +
152 " for " + std::to_string(i));
153 }
154 }
155 // Loop on axis to get start/end/step values
156 for (size_t i = 0; i < fAxes.size(); i++) {
157 if (!itensors[0].empty() )
158 fStartDims.push_back(Dim{ static_cast<size_t>(itensors[0][i])});
159 if (fStartDims.empty())
160 throw std::runtime_error("TMVA Slice Op : Missing start input tensor");
161
162 if (!itensors[1].empty())
163 fEndDims.push_back(Dim{ static_cast<size_t>(itensors[1][i])});
164 else if (fEndDims.empty())
165 throw std::runtime_error("TMVA Slice Op : Missing end input tensor");
166
167 if (!itensors[3].empty()) {
168 fStepDims.push_back(Dim{ static_cast<size_t>(itensors[3][i])});
169 }
170 else if (fStepDims.size() < fAxes.size()) // this can happen since it is optional
171 fStepDims.push_back(Dim{size_t(1)});
172
173 if (!fShapeInput[fAxes[i]].isParam) {
174 size_t iAxisDim = fShapeInput[fAxes[i]].dim;
175 //correct values if too large or too small
176 IType istart = 0;
177 if (!fStartDims[i].isParam) {
178 istart = static_cast<IType>(fStartDims[i].dim);
179 if (istart < 0) istart = iAxisDim + istart;
180 }
181 IType iend = static_cast<IType>(iAxisDim);
182 if (!fEndDims[i].isParam) {
183 iend = static_cast<IType>(fEndDims[i].dim);
184 if (iend < 0) iend = iAxisDim + iend;
185 }
186 //steps
187 IType istep = 1;
188 if (!fStepDims[i].isParam) {
189 istep = static_cast<IType>(fStepDims[i].dim);
190 } else {
191 throw std::runtime_error("TMVA Slice Op : parametric step inputs are not supported");
192 }
193 // clamp start end values depending on steps
194 // start must be [0,N] for positive steps or [0,N-1] for negative
195 // end must be [0,N] for positive steps or [-1, N-1] for negative
196 if (istart < 0) istart = 0;
197 if (istep > 0) {
198 if (istart > static_cast<IType>(iAxisDim)) istart = static_cast<IType>(iAxisDim);
199 if (iend < 0) iend = 0;
200 if (iend > static_cast<IType>(iAxisDim)) iend = static_cast<IType>(iAxisDim);
201 } else if (istep < 0) {
202 if (istart > static_cast<IType>(iAxisDim)-1) istart = static_cast<IType>(iAxisDim) -1;
203 if (iend < -1) iend = -1;
204 if (iend > static_cast<IType>(iAxisDim)-1) iend = static_cast<IType>(iAxisDim) -1;
205 } else {
206 throw std::runtime_error("TMVA Slice Op : invalid step value " + std::to_string(istep) +
207 " for " + std::to_string(i));
208 }
209 // for parametric values clamping we will done at run time
210 if (fStartDims[i].isParam)
211 fStart[fAxes[i]] = fStartDims[i];
212 else
213 fStart[fAxes[i]] = Dim{size_t(istart)};
214 if (fStartDims[i].isParam)
215 fEnd[fAxes[i]] = fEndDims[i];
216 else
217 fEnd[fAxes[i]] = Dim{size_t(iend)};
218
219 fSteps[fAxes[i]] = Dim{size_t(istep)};
220 } else {
221 //std::cout << i << " Param dim for " << fAxes[i] << " " << fShapeInput[fAxes[i]] << std::endl;
222 // correct only negative values
223 if (!fStartDims[i].isParam) {
224 IType istart = static_cast<IType>(fStartDims[i].dim);
225 if (istart < 0) {
226 std::string sstart = std::string("(") + fShapeInput[fAxes[i]].param + "-" + std::to_string(-istart) +")";
227 fStart[fAxes[i]] = Dim{sstart,size_t(-1)};
228 } else {
229 fStart[fAxes[i]] = Dim{size_t(istart)};
230 }
231 } else {
232 fStart[fAxes[i]] = fStartDims[i];
233 }
234 if (!fEndDims[i].isParam) {
235 IType iend = static_cast<IType>(fEndDims[i].dim);
236 if (iend < 0) {
237 std::string send = std::string("(") + fShapeInput[fAxes[i]].param + "-" + std::to_string(-iend) +")";
238 fEnd[fAxes[i]] = Dim{send,size_t(-1)};
239 } else if (iend == std::numeric_limits<IType>::max()){
240 fEnd[fAxes[i]] = fShapeInput[fAxes[i]];
241 } else {
242 fEnd[fAxes[i]] = Dim{size_t(iend)};
243 }
244 } else {
245 fEnd[fAxes[i]] = fEndDims[i];
246 }
247
248 fSteps[fAxes[i]] = fStepDims[i];
249 }
250
251 }
252 // find output shape
253 fShapeOutput.resize(dim);
254 for (size_t i = 0; i < dim; i++) {
255 if (!fEnd[i].isParam && !fStart[i].isParam && !fSteps[i].isParam) {
256 int64_t istart = static_cast<int64_t>(fStart[i].dim);
257 int64_t iend = static_cast<int64_t>(fEnd[i].dim);
258 int64_t istep= static_cast<int64_t>(fSteps[i].dim);
259 int64_t s = (iend-istart)/istep;
260 fShapeOutput[i] = Dim{static_cast<size_t>(s)};
261 } else {
262 std::string s;
263 if (fStart[i].GetVal() != "0")
264 s = "(" + fEnd[i].GetVal() + "-" + fStart[i].GetVal() + ")";
265 else
266 s = fEnd[i].GetVal();
267 if (fSteps[i].GetVal() != "1") {
268 s.insert(0,"(");
269 s += ")/" + fSteps[i].GetVal() + ")";
270 }
271 fShapeOutput[i] = Dim{s,size_t(-1)};
272 // add also the shape parameters to RModel to declare them when
273 // allocating output tensor
274 if (fEnd[i].isParam && fEnd[i].dim != size_t(-1))
275 model.AddShapeParam(fEnd[i].param,fEnd[i].dim );
276 if (fStart[i].isParam && fStart[i].dim != size_t(-1))
277 model.AddShapeParam(fStart[i].param,fStart[i].dim );
278 if (fSteps[i].isParam && fSteps[i].dim != size_t(-1))
279 model.AddShapeParam(fSteps[i].param,fSteps[i].dim );
280
281 }
282 }
283 // case input is a constant tensor and of int64 type
284 if (model.IsInitializedTensor(fNData) && model.GetTensorType(fNData) == ETensorType::INT64) {
285 fIsOutputConstant = true;
286 auto inputData = static_cast<int64_t*>(model.GetInitializedTensorData(fNData).get());
287 size_t outputSize = ConvertShapeToLength(ConvertShapeToInt(fShapeOutput));
288 std::vector<int64_t> outputData(outputSize);
289 std::vector<size_t> inputStride = UTILITY::ComputeStrideFromShape(ConvertShapeToInt(fShapeInput));
290 std::cout << "slice " << ConvertDimShapeToString(fShapeInput) << " output size " << outputSize << " " << ConvertDimShapeToString(fShapeOutput) << std::endl;
291 std::cout << " start - end -steps \n";
292 for (size_t ii = 0; ii< fStart.size(); ii++)
293 std::cout << fStart[ii] << " " << fEnd[ii] << " " << fSteps[ii] << std::endl;
294 // perform slice using a recursive function- need to use two lambda functions for this
295 auto sliceRecursive = [&](size_t iaxis, size_t & outIdx, size_t & inOffset) {
296 auto slice_impl = [&](size_t iax, size_t & outputIdx, size_t & inputOffset, auto & sliceRecImpl) {
297 std::cout << "SLice_impl " << fStart.size() << " " << fEnd.size() << " " << fSteps.size() << " " << iax << std::endl;
298 if (fStart[iax].isParam || fEnd[iax].isParam || fSteps[iax].isParam)
299 throw std::runtime_error("TMVA Slice Op : cannot have parametric values when input is constant");
300 // compute indices
301 std::vector<IType> indices;
302 for (IType i = (IType) fStart[iax].dim; (IType(fSteps[iax].dim) > 0) ? i < IType(fEnd[iax].dim) : i > IType(fEnd[iax].dim); i += IType(fSteps[iax].dim) )
303 indices.push_back(i);
304 if (iax == dim-1) { // last axis
305 std::cout << "SLice_impl last axis: " << indices.size() << " : ";
306 for (size_t i = 0; i < indices.size(); i++) {
307 std::cout << outputIdx << " , " << indices[i] << " " << inputOffset << " ; ";
308 outputData[outputIdx] = inputData[inputOffset + indices[i]];
309 outputIdx++;
310 }
311 std::cout << std::endl;
312 return;
313 } else {
314 std::cout << "SLice_impl else : " << indices.size() << " : ";
315 for (size_t i = 0; i < indices.size(); i++) {
316 std::cout << inputStride[iax] << " , " << indices[i] << " " << inputOffset << " ";
317 size_t offset = inputOffset + inputStride[iax]*indices[i];
319 }
320 std::cout << std::endl;
321 }
322 };
324 };
325 size_t idx = 0;
326 size_t offset = 0;
327 sliceRecursive(0, idx, offset);
328
329 model.AddConstantTensor<int64_t>(fNOutput, ConvertShapeToInt(fShapeOutput), outputData.data());
330 if (model.Verbose()) {
331 std::cout << "Slice: output is a constant tensor " << ConvertShapeToString(fShapeOutput) << " : "
332 << ConvertValuesToString(outputData) << std::endl;
333 }
334 }
335 else {
336 // check if Slice is just an Identity operator in case start = 0, end = input_shape and step=1
337 size_t ndim = fShapeInput.size();
338 fIdentitySlice = fShapeOutput.size() == ndim;
339 // check also if input data is not input to the model. In that case we copy the data since we cannot just copy from the input pointer
340 fIdentitySlice &= !model.IsReadyInputTensor(fNData);
341 for (size_t idim = 0; idim < ndim; idim++) {
342 if (!fIdentitySlice) break;
343 fIdentitySlice &= (fStart[idim].GetVal() == "0");
344 fIdentitySlice &= (fSteps[idim].GetVal() == "1");
345 fIdentitySlice &= (fEnd[idim].GetVal() == fShapeOutput[idim].GetVal());
346 }
347
348 model.AddIntermediateTensor(fNOutput, model.GetTensorType(fNData), fShapeOutput);
349 if (fIdentitySlice) model.AddAliasTensor(fNOutput, fNData);
350
351 if (model.Verbose()) {
352 std::cout << "Slice " << fNData << " " << ConvertShapeToString(fShapeInput)
353 << "---> " << fNOutput << " " << ConvertShapeToString(fShapeOutput);
354 if (fIdentitySlice) std::cout << " (using alias tensor since slice is an identity) ";
355 std::cout << std::endl;
356
357 }
358 }
359 }
360
361 std::string Generate(std::string opName) override {
362
363 if (fShapeInput.empty() || fShapeOutput.empty()){
364 throw std::runtime_error("TMVA SOFIE Slice Op called to Generate without being initialized first");
365 }
366
367 std::stringstream out;
368
369 out << "///------- Slice operator " << opName << "---> " << fNOutput << " "
370 << ConvertDimShapeToString(fShapeOutput) << "\n" << std::endl;
371 if (fIsOutputConstant) return out.str(); //no op for constant tensors
372
373 size_t ndim = fShapeInput.size();
374
375 if (fIdentitySlice) {
376 out << "/// Slice is just an identity (copy pointers) \n";
377 out << SP << "tensor_" << fNOutput << " = tensor_" << fNData << ";\n";
378 return out.str();
379 }
380
381 // loop on the dimensions depending no the orders
382 auto strides = UTILITY::ComputeStrideFromShape(fShapeInput);
383
384
385 out << SP << "{\n"; // define operator scope
386 for (size_t i = 0; i < fStepDims.size(); i++) {
387 if (fStepDims[i].isParam) {
388 if (fIsStepUndef)
389 out << SP << "size_t " << fStepDims[i] << " = tensor_" << fNames[3] << "[" << i << "];\n";
390 }
391 }
392 // special case for parametric values for start/end. Need to do clipping
393 for (size_t i = 0; i < fStartDims.size(); i++) {
394 if (fStartDims[i].isParam && fStartDims[i].param != fShapeInput[fAxes[i]].param) {
395 std::string s_start = "start_" + std::to_string(i);
396 if (fIsStartUndef) {
397 s_start = fStartDims[i].param;
398 out << SP << "size_t " << s_start << " = tensor_" << fNames[0] << "[" << i << "];\n";
399 } else {
400 out << SP << "size_t " << s_start << " = " << fStartDims[i] << ";\n";
401 fStart[fAxes[i]] = s_start; // need to use this value later when slicing
402 }
403 out << SP << "if (" << s_start << " < 0) " << s_start << " += " << fShapeInput[fAxes[i]] <<";\n";
404 out << SP << "if (" << s_start << " < 0) " << s_start << " = 0;\n";
405 if (!fStepDims[i].isParam) {
406 if (static_cast<IType>(fStepDims[i].dim) > 0 )
407 out << SP << "if (" << s_start << " > " << fShapeInput[fAxes[i]] << " ) " << s_start << " = " << fShapeInput[fAxes[i]] <<";\n";
408 else
409 out << SP << "if (" << s_start << " > " << fShapeInput[fAxes[i]] << " - 1" << " ) " << s_start << " = " << fShapeInput[fAxes[i]] << " - 1;\n";
410 }
411 }
412 // special case if step is negative and shape are equal and step is negative
413 else if (fStartDims[i].isParam && fStartDims[i].param == fShapeInput[fAxes[i]].param && !fStepDims[i].isParam && static_cast<IType>(fStepDims[i].dim) < 0 ) {
414 fStart[fAxes[i]] = Dim{ fStartDims[i].param + "-1" };
415 }
416 }
417 // now to for end
418 for (size_t i = 0; i < fEndDims.size(); i++) {
419 if (fEndDims[i].isParam && fEndDims[i].param != fShapeInput[fAxes[i]].param) {
420 std::string s_end = "end_" + std::to_string(i);
421 if (fIsEndUndef) {
422 s_end = fEndDims[i].param;
423 out << SP << "size_t " << s_end << " = tensor_" << fNames[1] << "[" << i << "];\n";
424 } else {
425 out << SP << "size_t " << s_end << " = " << fEndDims[i] << ";\n";
426 fEnd[fAxes[i]] = s_end; // need to use this value later when slicing
427 }
428 out << SP << "if (" << s_end << " < 0) " << s_end << " += " << fShapeInput[fAxes[i]] <<";\n";
429 if (!fStepDims[i].isParam) {
430 if (static_cast<IType>(fStepDims[i].dim) > 0 ) {
431 out << SP << "if (" << s_end << " < 0) " << s_end << " = 0;\n";
432 out << SP << "if (" << s_end << " > " << fShapeInput[fAxes[i]] << " ) " << s_end << " = " << fShapeInput[fAxes[i]] <<";\n";
433 } else {
434 out << SP << "if (" << s_end << " < -1) " << s_end << " = -1;\n";
435 out << SP << "if (" << s_end << " > " << fShapeInput[fAxes[i]] << " - 1" << " ) " << s_end << " = " << fShapeInput[fAxes[i]] << " - 1;\n";
436 }
437 }
438 }
439 // special case if step is negative and shape are equal and step is negative
440 else if (fEndDims[i].isParam && fEndDims[i].param == fShapeInput[fAxes[i]].param && !fStepDims[i].isParam && static_cast<IType>(fStepDims[i].dim) < 0 ) {
441 fEnd[fAxes[i]] = Dim{ fEndDims[i].param + "-1" };
442 }
443 }
444
445 out << SP << "size_t iOut = 0;\n";
446 std::string MSP = SP;
447 for (size_t idim = 0; idim < ndim; idim++) {
448 out << MSP << "for (size_t i" << idim << " = " << fStart[idim] << "; i" << idim << " < " << fEnd[idim]
449 << "; i" << idim << "+= " << fSteps[idim] << ") {\n";
450 MSP += SP;
451 if (idim < ndim-1) out << MSP << "size_t stride" << idim << " = " << strides[idim] << "*i" << idim << ";\n";
452 }
453 out << MSP << "size_t iInput = ";
454 for (size_t idim = 0; idim < ndim-1; idim++) out << " stride" << idim << " + ";
455 // here should be step size ?
456 out << "i" << ndim-1 << ";\n";
457 out << MSP << "tensor_" << fNOutput << "[iOut++] = tensor_" <<fNData << "[iInput];\n";
458 for (size_t idim = 0; idim < ndim; idim++) {
459 MSP = MSP.replace(0,SP.length(),"");
460 out << MSP << "}\n";
461 }
462 out << SP << "}\n"; // end operator scope
463
464 return out.str();
465 }
466
467};
468
469}//SOFIE
470}//Experimental
471}//TMVA
472
473
474#endif //TMVA_SOFIE_ROPERATOR_SLICE
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
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
std::vector< size_t > GetTensorShape(const std::string &name) const
Definition RModel.cxx:29
std::vector< Dim > GetDimTensorShape(const std::string &name) const
Definition RModel.cxx:65
bool CheckIfTensorAlreadyExist(std::string tensor_name)
Definition RModel.cxx:122
bool IsShapeTensor(const std::string &name) const
check if a tensor is a shape tensor
Definition RModel.cxx:221
bool IsInitializedTensor(const std::string &name) const
Definition RModel.cxx:234
std::shared_ptr< void > GetInitializedTensorData(std::string tensor_name)
Definition RModel.cxx:327
const std::vector< Dim > & GetShapeTensorValues(const std::string &tensor_name) const
Definition RModel.cxx:229
std::vector< std::vector< IType > > fAttributes
ROperator_Slice(std::string nameData, std::vector< IType > starts, std::vector< IType > ends, std::vector< IType > axes, std::string nameOutput)
ROperator_Slice(std::string nameData, std::vector< std::string > names, std::string nameOutput)
std::string Generate(std::string opName) override
std::vector< std::string_view > fInputTensorNames
Definition ROperator.hxx:47
std::vector< std::string_view > fOutputTensorNames
Definition ROperator.hxx:48
std::string Clean_name(std::string input_tensor_name)
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)
std::vector< size_t > ConvertShapeToInt(const std::vector< Dim > &shape)
Convert shape based on Dim to integer format.
std::string ConvertShapeToString(const std::vector< size_t > &shape)
create variable transformations