Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
ROperator_Concat.hxx
Go to the documentation of this file.
1#ifndef TMVA_SOFIE_ROPERATOR_Concat
2 #define TMVA_SOFIE_ROPERATOR_Concat
3
4
5 #include "TMVA/SOFIE_common.hxx"
6 #include "TMVA/ROperator.hxx"
7 #include "TMVA/RModel.hxx"
8
9 #include <sstream>
10 #include <algorithm>
11 #include <iterator>
12 #include <iomanip>
13 #include <limits>
14
15 namespace TMVA{
16 namespace Experimental{
17 namespace SOFIE{
18
20 {
21 private:
22 int fAxis=0;
23 int fnewAxis=0;
24 std::vector<std::string> fInputs;
25 std::string fOutput;
26 std::vector<Dim>fOutputShape;
27 std::vector<Dim> fOutputShapeData; // in case output is a shape tensor we store here the output shape value data (can be parametric)
28 std::vector<std::vector<Dim>> fInputShapes;
29
30 public:
31
33 ROperator_Concat(std::vector<std::string> inputs, int axis, int newAxis, std::string output):
34 fAxis(axis), fnewAxis(newAxis), fOutput(UTILITY::Clean_name(output)) {
35 fInputs.reserve(inputs.size());
36 for (auto & name : inputs)
38
39 fInputTensorNames.resize(fInputs.size());
40 std::transform(fInputs.begin(), fInputs.end(), fInputTensorNames.begin(),
41 [](const std::string& s) -> std::string_view { return s; });
43 }
44
45 // get shape of output given inputs. It is going to be called after initialized
46 std::vector<std::vector<size_t>> ShapeInference(std::vector<std::vector<size_t>> inputs) {
47 std::vector<std::vector<size_t>> ret(1);
48 // treat negative axis case
49 if (fAxis<0) {
50 fAxis = inputs[0].size()+fAxis;
51 }
52 if (fAxis < 0 || fAxis >= (int) inputs[0].size())
53 throw std::runtime_error("TMVA SOFIE Concat Op - invalid axis value ");
54
55 int concat_dim=0;
56 // case of Concat (fNewAxis = 0) and not ConcatFromSequence
57 if(fnewAxis == 0){
58 for (size_t i = 0; i < inputs.size(); i++) {
59 if (i > 0 && inputs[i].size() != inputs[i - 1].size())
60 throw std::runtime_error("TMVA SOFIE Concat Op - input tensors have different shapes " +
62 for (size_t iaxis = 0; iaxis < inputs[i].size(); iaxis++) {
63 if ((int)iaxis == fAxis)
64 concat_dim += inputs[i][iaxis];
65 else if (i > 0 && inputs[i][iaxis] != inputs[i - 1][iaxis])
66 throw std::runtime_error("TMVA SOFIE Concat Op - input tensors have wrong shapes " +
67 ConvertShapeToString(inputs[i]) + " and " +
69 }
70 }
71
72 // output shape
73 ret[0] = inputs[0];
74 ret[0][fAxis] = concat_dim;
75 }
76 std::vector<int> stack;
77 // case ConCatFromSequence
78 if(fnewAxis == 1){
79 for(size_t i = 0; i < inputs.size(); i++) {
80 if (i > 0 && inputs[i].size() != inputs[i-1].size() )
81 throw std::runtime_error("TMVA SOFIE Concat Op - input tensors have different shapes " + fInputs[i] + " : " +
82 ConvertShapeToString(inputs[i]) + " and " + fInputs[i-1] + " : " + ConvertShapeToString(inputs[i-1]));
83 for (size_t iaxis = 0; iaxis < inputs[i].size(); iaxis++) {
84 if ((int) iaxis == fAxis)
85 stack.push_back(inputs[i][iaxis]);
86 else
87 if (i> 0 && inputs[i][iaxis] != inputs[i-1][iaxis])
88 throw std::runtime_error("TMVA SOFIE Concat Op - input tensors have wrong shapes " +
90 }
91
92 }
93 for(auto it:stack)
94 ret[0].push_back(it);
95 }
96
97 return ret;
98 }
99
100 // get shape of output given inputs. It is going to be called after initialized
101 std::vector<Dim> ShapeInference(const std::vector<std::vector<Dim>> & inputs, const RModel & model) {
102 std::vector<Dim> ret(inputs[0].size());
103 // treat negative axis case
104 if (fAxis<0) {
105 fAxis = inputs[0].size()+fAxis;
106 }
107 if (fAxis < 0 || fAxis >= (int) inputs[0].size())
108 throw std::runtime_error("TMVA SOFIE Concat Op - invalid axis value ");
109
111 if(fnewAxis == 0){
112 for (size_t i = 0; i < inputs.size(); i++) {
113 if (i > 0 && inputs[i].size() != inputs[i - 1].size())
114 throw std::runtime_error("TMVA SOFIE Concat Op - input tensors have different shapes " + fInputs[i] + " : " +
115 ConvertDimShapeToString(inputs[i]) + " and " + fInputs[i-1] + " : " + ConvertDimShapeToString(inputs[i - 1]));
116 for (size_t iaxis = 0; iaxis < inputs[i].size(); iaxis++) {
117 if ((int)iaxis == fAxis) {
118 // support both integer and params shape for the concatenation axis
119 if (concat_dim.param.empty() && concat_dim.dim == 0)
120 concat_dim = inputs[i][iaxis];
121 else if (inputs[i][iaxis].isParam || concat_dim.isParam) {
122 concat_dim =
123 Dim{ concat_dim.GetVal() + std::string(" + ") + inputs[i][iaxis].GetVal(),
124 static_cast<size_t>(-1)};
125 } else {
126 concat_dim = Dim { concat_dim.dim + inputs[i][iaxis].dim };
127 }
128 }
129 else if (i == 0) {
130 ret[iaxis] = inputs[i][iaxis];
131 }
132 else if ((!inputs[i][iaxis].isParam && !ret[iaxis].isParam) && (inputs[i][iaxis].dim != ret[iaxis].dim)) {
133 throw std::runtime_error("TMVA SOFIE Concat Op - input tensors have wrong shapes " +
134 ConvertDimShapeToString(inputs[i]) + " and " +
136 }
137 else if (!inputs[i][iaxis].isParam && ret[iaxis].isParam){
138 // if shape is not parametric use it
139 ret[iaxis] = inputs[i][iaxis];
140 }
141 else if (inputs[i][iaxis].isParam && ret[iaxis].isParam) {
142 // check which parameter is first in RModel list
143 auto & dimNames = model.GetDimShapeNames();
144 auto p1 = std::find(dimNames.begin(), dimNames.end(), inputs[i][iaxis].param);
145 auto p2 = std::find(dimNames.begin(), dimNames.end(), ret[iaxis].param);
146 if (p1 < p2) ret[iaxis] = inputs[i][iaxis];
147 }
148
149 }
150 // add parenthesis in case is an expression
151 if (concat_dim.isParam && concat_dim.dim == static_cast<size_t>(-1))
152 concat_dim = Dim{ std::string("(") + concat_dim.GetVal() + std::string(")"), concat_dim.dim };
153 }
154
155 // output shape for concatenated axis
157
158 }
159 // case of stacking (not supported yet)
160 // here we need to check that input shapes are the same
161 // for example for fAxis == 0
162 // output shapes: [inputs.size(), inputs[0][0], inputs[0][1],....]
163 if(fnewAxis == 1){
164 throw std::runtime_error("TMVA SOFIE Concat Op - stacking (i.e. COncatFromSequence with new_axis=1) is not supported ");
165 }
166 return ret;
167 }
168
169 void Initialize(RModel& model) override {
170 // the generated code may use the Copy inference helper
171 model.AddNeededHelperFunction("Copy");
172 std::vector<std::vector<size_t>> inputIntShapes;
173 for (auto &it : fInputs) {
174 if (model.CheckIfTensorAlreadyExist(it) == false) {
175 throw std::runtime_error("TMVA SOFIE Concat Op Input Tensor " + it + " is not found in model");
176 }
177 fInputShapes.push_back(model.GetDimTensorShape(it));
178 if (!model.IsDynamicTensor(it)) {
180 }
181 }
182 if (inputIntShapes.size() == fInputs.size()) {
183 // if all input shapes are static we can compute output shape at initialization time
186 if (model.Verbose())
187 std::cout << "Initialize Concat operator with defined inputs shapes, "
188 << "output has shape " << ConvertShapeToString(outputIntShape) << std::endl;
189
190 } else {
191 // if at least one input shape is dynamic we need to compute output shape using the symbolic expression for the dimensions
193 if (model.Verbose())
194 std::cout << "Initialize Concat operator with dynamic inputs shapes, "
195 << "output has shape " << ConvertDimShapeToString(fOutputShape) << std::endl;
196 }
197
198 // check if concat has constant inputs , axis 0(concat contigous memory and type is integer)
199 bool isOutputShape = false;
200
201 // if (model.GetTensorType(fInputs[0]) == ETensorType::INT64 && fAxis == 0) {
202 fIsOutputConstant = true;
203 isOutputShape = true;
204
205 for (auto &input : fInputs) {
206 if (model.IsDynamicTensor(input)) {
207 fIsOutputConstant = false;
208 isOutputShape = false;
209 break;
210 }
211 if (!model.IsInitializedTensor(input)) {
212 if (model.IsShapeTensor(input)) {
213 // if it is a shape tensor we can have constant output if the shapes are defined)
214 auto shapeData = model.GetShapeTensorValues(input);
216 if (!isShapeFullyDefined) {
217 fIsOutputConstant = false;
218 } else {
219 // if shape is fully defined we can consider output as constant and we can compute the output
220 // shape at initialization time
222 }
223 // inputs are then shape tensors and output is a shape tensor
224 isOutputShape = true;
225 } else {
226 // case of standard intermediate tensor
227 fIsOutputConstant = false;
228 isOutputShape = false;
229 break;
230 }
231 } else {
233 }
234 }
235 //}
236
237 if (fIsOutputConstant) {
238 auto outputShape = ConvertShapeToInt(fOutputShape); // conversion must be possible
239 std::vector<int64_t> outputData(ConvertShapeToLength(outputShape));
240 size_t offset = 0;
241 for (auto &input : fInputs) {
242 auto inputData = static_cast<int64_t *>(model.GetInitializedTensorData(input).get());
243 auto inputShape = model.GetTensorShape(input); // shape is not dynamic if it is constant
247 // the data of the input tensor don't need to be written in the generated code and data file
248 model.SetNotWritableInitializedTensor(input);
249 }
250 model.AddConstantTensor<int64_t>(fOutput, outputShape, outputData.data());
251 if (model.Verbose()) {
252 std::cout << "output of Concat is a constant tensor " << ConvertShapeToString(outputShape) << " : "
253 << ConvertValuesToString(outputData) << " (constant)" << std::endl;
254 }
255 } else if (isOutputShape) {
256 auto outputShape = ConvertShapeToInt(fOutputShape); // conversion must be possible
257 if (outputShape.size() != 1)
258 throw std::runtime_error("TMVA SOFIE Concat Op - output shape for shape tensor must have rank 1");
259 // output shape is a rank 1 tensor with size equal to the output rank
260 std::vector<Dim> outputData(outputShape[0]);
261 size_t offset = 0;
262 for (auto &input : fInputs) {
263 std::vector<Dim> inputData;
264 auto inputShape = model.GetTensorShape(input); // shape is not dynamic
265 size_t inputLength = ConvertShapeToLength(inputShape); // shape can be a scalar
266 if (model.IsShapeTensor(input)) {
267 inputData = model.GetShapeTensorValues(input);
268 } else if (model.IsInitializedTensor(input)) {
269 inputData.resize(inputLength);
270 auto intData = static_cast<int64_t *>(model.GetInitializedTensorData(input).get());
271 for (size_t i = 0; i < inputData.size(); i++)
272 inputData[i] = Dim{static_cast<size_t>(intData[i])};
273 } else {
274 // this should not happen
275 throw std::runtime_error("TMVA SOFIE Concat Operator- invalid tensor input " + input +
276 " for shape output type");
277 }
278 std::copy(inputData.begin(), inputData.end(), outputData.begin() + offset);
280 }
281 // add output tensor
282 model.AddShapeTensor(fOutput, outputData, false); // cannot be a scalar
284 if (model.Verbose()) {
285 std::cout << "output of Concat is a shape tensor " << ConvertShapeToString(outputShape) << " : "
286 << ConvertDimShapeToString(outputData) << " (shape)" << std::endl;
287 }
288 fIsOutputParamShape = true;
289 }
291 model.AddIntermediateTensor(fOutput, model.GetTensorType(fInputs[0]), fOutputShape);
292 if (model.Verbose()) {
293 std::cout << "Concat ---> " << fOutput << " " << ConvertDimShapeToString(fOutputShape) << std::endl;
294 }
295 }
296 }
297
298 std::string Generate(std::string opName) override {
299 opName = "op_" + opName;
300 std::stringstream out;
301 out<<"\n//--------- Concat " << opName << " --> " << fOutput << " " << ConvertDimShapeToString(fOutputShape) << "\n";
302
303 if (fIsOutputConstant) return out.str();
304
306 // output is a shape tensor defined by the concatenation of the input shapes
307 out << "// output is a shape tensor defined by the concatenation of the input shapes\n";
308 for (int i = 0; i < static_cast<int>(fOutputShape
309 [0].dim); i++) {
310 out << SP << "tensor_" << fOutput << "[" << i << "] = " << fOutputShapeData[i] << ";\n";
311 }
312 return out.str();
313 }
314 // special case when memory is contiguous
315 bool hasShapeOnes = true;
316 for(int i = 0; i<fAxis; ++i){
317 if(fInputShapes[0][i].dim !=1){
318 hasShapeOnes = false;
319 break;
320 }
321 }
322 if (fAxis == 0 || hasShapeOnes) {
323 std::string offset;
324 for(size_t i=0; i<fInputs.size(); ++i) {
326 out << SP << "Copy(tensor_" << fOutput;
327 if (i > 0)
328 out << offset;
329 offset += " + " + length;
330 out << ", " << "tensor_" << fInputs[i] << ", " + length << ");\n";
331 }
332 }
333 else {
334
336 std::vector<std::vector<Dim>> inStrides(fInputs.size());
337 int idx = 0;
338 for ( auto &s : inStrides) {
340 idx++;
341 }
342 for (int i = 0; i < fAxis; ++i) {
343 // loop on dimensions
344 out << SP << "for (size_t i" << i << " = 0; i" << i << " < " << fOutputShape[i].GetVal() << "; ++i" << i <<") {\n";
345 }
346
347 out << SP << SP << SP << "int idxOut = ";
348 for (int k = 0; k < fAxis; k++) {
349 if (k > 0) out << " + ";
350 out << outStride[k].GetVal() << "*i" << k;
351 }
352 out << ";\n";
353
354 for (size_t j = 0; j < fInputs.size(); j++) {
355 if (j>0)
356 out << SP << SP << SP << "idxOut += " << inStrides[j-1][fAxis-1].GetVal() << ";\n";
357 out << SP << SP << SP << "int idxIn" << j <<" = ";
358 for (int k = 0; k < fAxis; k++) {
359 if (k > 0) out << " + ";
360 out << inStrides[j][k].GetVal() << "*i" << k;
361 }
362 out << ";\n";
363 out << SP << SP << SP << "for (size_t iC = 0; iC < " << inStrides[j][fAxis-1].GetVal() << "; ++iC) {\n";
364 out << SP << SP << SP << SP << "tensor_" << fOutput << "[idxOut+iC] = tensor_" << fInputs[j] << "[idxIn" << j << "+iC];\n";
365 out << SP << SP << SP << "}\n";
366 // concatenate the axis values
367 }
368 for (int i = 0; i < fAxis; ++i) {
369 out << SP << "}\n";
370 }
371 }
372
373 return out.str();
374 }
375 };
376 }//SOFIE
377 }//Experimental
378 }//TMVA
379
380 #endif //TMVA_SOFIE_ROPERATOR_CONCAT
size_t size(const MatrixT &matrix)
retrieve the size of a square matrix
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 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
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 length
char name[80]
Definition TGX11.cxx:142
const_iterator begin() const
const_iterator end() const
std::vector< Dim > ShapeInference(const std::vector< std::vector< Dim > > &inputs, const RModel &model)
std::vector< std::vector< size_t > > ShapeInference(std::vector< std::vector< size_t > > inputs)
std::vector< std::vector< Dim > > fInputShapes
ROperator_Concat(std::vector< std::string > inputs, int axis, int newAxis, std::string output)
std::string Generate(std::string opName) override
std::vector< std::string_view > fInputTensorNames
Definition ROperator.hxx:44
bool fIsOutputParamShape
flag to identify of the output represents a parametric shape (can be known at compile time)
Definition ROperator.hxx:42
bool fIsOutputConstant
flag to identify if operator has a constant output (no need to generate code)
Definition ROperator.hxx:41
const std::string SP
space used to correctly indent the generated C++ code
Definition ROperator.hxx:40
std::vector< std::string_view > fOutputTensorNames
Definition ROperator.hxx:45
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, 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::vector< size_t > ConvertShapeToInt(const std::vector< Dim > &shape)
Convert shape based on Dim to integer format.
std::string ConvertDimShapeToLength(const std::vector< Dim > &shape)
std::string ConvertShapeToString(const std::vector< size_t > &shape)
create variable transformations