Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
ROperator_BasicNary.hxx
Go to the documentation of this file.
1#ifndef TMVA_SOFIE_ROPERATOR_BASICNARY
2#define TMVA_SOFIE_ROPERATOR_BASICNARY
3
5#include "TMVA/ROperator.hxx"
6#include "TMVA/RModel.hxx"
7
8#include <vector>
9#include <sstream>
10#include <algorithm>
11
12namespace TMVA{
13namespace Experimental{
14namespace SOFIE{
15
17
18template<typename T, EBasicNaryOperator Op>
20
21template<typename T>
23 static const std::string Name() {return "Max";}
24 static std::string Expr(const std::vector<std::string> &inputs)
25 {
26 std::stringstream out;
27 out << "std::max({ " << inputs[0];
28 for (size_t i = 1; i < inputs.size(); i++) {
29 out << ", " << inputs[i];
30 }
31 out << "})";
32 return out.str();
33 }
34 static std::string Op(const std::string &res, std::vector<std::string> &inputs)
35 {
36 return res + " = " + Expr(inputs) + ";\n";
37 }
38 static size_t Func(const std::vector<size_t> &values) { return *std::max_element(values.begin(), values.end()); }
39};
40
41template<typename T>
43 static const std::string Name() {return "Min";}
44 static std::string Expr(const std::vector<std::string> &inputs)
45 {
46 std::stringstream out;
47 out << "std::min({ " << inputs[0];
48 for (size_t i = 1; i < inputs.size(); i++) {
49 out << ", " << inputs[i];
50 }
51 out << "})";
52 return out.str();
53 }
54 static std::string Op(const std::string &res, std::vector<std::string> &inputs)
55 {
56 return res + " = " + Expr(inputs) + ";\n";
57 }
58 static size_t Func(const std::vector<size_t> &values) { return *std::min_element(values.begin(), values.end()); }
59};
60
61template <typename T>
63 static const std::string Name() {return "Mean";}
64 static std::string Expr(const std::vector<std::string> &inputs)
65 {
66 std::stringstream out;
67 out << "((" << inputs[0];
68 for (size_t i = 1; i < inputs.size(); i++) {
69 out << " + " << inputs[i];
70 }
71 // divide using the tensor type to avoid narrowing conversions
72 out << ") / " << ConvertTypeToString(GetTemplatedType(T{})) << "(" << inputs.size() << "))";
73 return out.str();
74 }
75 static std::string Op(const std::string &res, std::vector<std::string> &inputs)
76 {
77 return res + " = " + Expr(inputs) + ";\n";
78 }
79 static size_t Func(const std::vector<size_t> &values)
80 {
81 size_t sum = 0;
82 for (auto &v : values)
83 sum += v;
84 return sum / values.size();
85 }
86};
87
88template<typename T>
90 static const std::string Name() {return "Sum";}
91 static std::string Expr(const std::vector<std::string> &inputs)
92 {
93 std::stringstream out;
94 out << "(" << inputs[0];
95 for (size_t i = 1; i < inputs.size(); i++) {
96 out << " + " << inputs[i];
97 }
98 out << ")";
99 return out.str();
100 }
101 static std::string Op(const std::string &res, std::vector<std::string> &inputs)
102 {
103 return res + " = " + Expr(inputs) + ";\n";
104 }
105 static size_t Func(const std::vector<size_t> &values)
106 {
107 size_t sum = 0;
108 for (auto &v : values)
109 sum += v;
110 return sum;
111 }
112};
113
114template <typename T, EBasicNaryOperator Op>
116{
117
118private:
119
120 std::vector<std::string> fNInputs;
121 std::string fNY;
122 std::vector<std::vector<Dim>> fShapeInputs;
123
124 std::vector<std::string> fNBroadcastedInputs;
125 std::vector<size_t> fShapeY;
126 std::vector<Dim> fDimShapeY;
127
128 bool fBroadcast = false;
129
130 std::string fType;
131
132public:
134
135 ROperator_BasicNary( const std::vector<std::string> & inputNames, const std::string& nameY):
136 fNY(UTILITY::Clean_name(nameY)){
137 fNInputs.reserve(inputNames.size());
138 for (auto & name : inputNames)
140
141 fInputTensorNames.resize(fNInputs.size());
142 std::transform(fNInputs.begin(), fNInputs.end(), fInputTensorNames.begin(),
143 [](const std::string& s) -> std::string_view { return s; });
145 }
146
147 // type of output given input
148 std::vector<ETensorType> TypeInference(std::vector<ETensorType> input) override {
149 return input;
150 }
151
152 // shape of output tensors given input tensors
153 std::vector<std::vector<size_t>> ShapeInference(std::vector<std::vector<size_t>> input) override {
154 auto ret = std::vector<std::vector<size_t>>(1, input[0]);
155 return ret;
156 }
157
158 // Case where all inputs are rank <= 1 INT64 tensors known at initialization time and at least one of
159 // them is a shape tensor. The output is then also a shape tensor (its values, possibly symbolic, are
160 // computed here) and no code needs to be generated for this operator.
162 {
163 bool hasShapeTensor = false;
164 bool isScalar = true;
165 size_t length = 1;
166 for (auto &name : fNInputs) {
167 if (model.GetTensorType(name) != ETensorType::INT64)
168 return false;
169 if (!model.IsShapeTensor(name) && !model.IsInitializedTensor(name))
170 return false;
171 hasShapeTensor |= model.IsShapeTensor(name);
172 auto shape = model.GetTensorShape(name);
173 if (shape.size() > 1)
174 return false;
175 if (!shape.empty()) {
176 isScalar = false;
177 // only scalars or tensors of the same length can be combined here
178 if (shape[0] != 1 && length != 1 && shape[0] != length)
179 return false;
180 length = std::max(length, shape[0]);
181 }
182 }
183 if (!hasShapeTensor)
184 return false;
185
186 // collect the values of every input as Dim's, broadcasting the scalars and the size-1 tensors
187 std::vector<std::vector<Dim>> values(fNInputs.size(), std::vector<Dim>(length));
188 for (size_t i = 0; i < fNInputs.size(); i++) {
189 auto &name = fNInputs[i];
190 if (model.IsShapeTensor(name)) {
191 auto &dims = model.GetShapeTensorValues(name);
192 for (size_t j = 0; j < length; j++)
193 values[i][j] = (dims.size() == 1) ? dims[0] : dims[j];
194 } else {
195 auto data = static_cast<int64_t *>(model.GetInitializedTensorData(name).get());
196 size_t n = ConvertShapeToLength(model.GetTensorShape(name));
197 for (size_t j = 0; j < length; j++)
198 values[i][j] = Dim{static_cast<size_t>(data[(n == 1) ? 0 : j])};
199 // deliberately not flagged as non-writable: the same initializer may still be
200 // read at run time by another operator, and a non-writable tensor gets no
201 // member emitted at all. Leaving it costs a few bytes in the weight file.
202 }
203 }
204
205 std::vector<Dim> outputValues(length);
206 for (size_t j = 0; j < length; j++) {
207 bool isConstant = true;
208 std::vector<size_t> dims(fNInputs.size());
209 std::vector<std::string> exprs(fNInputs.size());
210 for (size_t i = 0; i < fNInputs.size(); i++) {
211 isConstant &= !values[i][j].isParam;
212 dims[i] = values[i][j].dim;
213 // cast to size_t so that the parametric dimensions and the literals have a common type
214 exprs[i] = "size_t(" + values[i][j].GetVal() + ")";
215 }
216 if (isConstant)
218 else
219 outputValues[j] = Dim{NaryOperatorTraits<T, Op>::Expr(exprs), static_cast<size_t>(-1)};
220 }
221 model.AddShapeTensor(fNY, outputValues, isScalar);
222 fIsOutputConstant = true;
223 if (model.Verbose()) {
224 std::cout << NaryOperatorTraits<T, Op>::Name() << " : --> " << fNY << " "
225 << ConvertDimShapeToString(outputValues) << " (shape)" << std::endl;
226 }
227 return true;
228 }
229
230 void Initialize(RModel& model) override {
231 std::vector<std::vector<size_t>> inputShapes;
232 for (auto &it : fNInputs) {
233 if (!model.CheckIfTensorAlreadyExist(it)) {
234 throw std::runtime_error("TMVA SOFIE BasicNary Op Input Tensor " + it + " is not found in model");
235 }
236 }
238 return;
239 for (auto &it : fNInputs) {
240 fShapeInputs.push_back(model.GetDimTensorShape(it));
241 if (fNInputs.size()> 2) {
242 if (model.IsDimInputTensor(it))
243 throw std::runtime_error("TMVA SOFIE BasicNary : supports only 2 inputs for dynamic tensors");
244 else
245 inputShapes.push_back(model.GetTensorShape(it));
246 }
247 }
248 // Find the common shape of the input tensors
249 if (fShapeInputs.size() > 2 ) {
250 // support dynamic tensors now for input list of size=2
253 } else if (fShapeInputs.size() == 2 ) {
255 // use same code as in BinaryOperator (need to extend for input sizes > 2)
256 fBroadcast = ret.first;
257 fDimShapeY = ret.second;
258 // case of all parametric shapes and MultiDirectionalBroadcastShape return the max of the 2
259 // need to do before we declare the output tensor shape and the broadcasted ones
260 if (ret.first & 4) {
261 // check if one of the parameter is an input dimension
262 // define function to find this
263 auto IsInputDimParam = [&](const std::string &p) {
264 auto inputNames = model.GetInputTensorNames();
265 for (auto &input : inputNames) {
266 for (auto &i_s : model.GetDimTensorShape(input)) {
267 if (i_s.isParam && i_s.param == p)
268 return true;
269 }
270 }
271 return false;
272 };
273 auto & shapeA = fShapeInputs[0];
274 auto & shapeB = fShapeInputs[1];
275 for (size_t i = 0; i < fDimShapeY.size(); i++) {
276 auto &s = fDimShapeY[i];
277 if (s.isParam && s.param.find("std::max") != std::string::npos) {
278 if (IsInputDimParam(shapeA[i].param)) {
279 // case dim is 1 we indicate that the input parameter is equal to 1
280 if (shapeA[i].dim != 1)
281 s = shapeA[i];
282 else
283 s = shapeB[i];
284 } else if (IsInputDimParam(shapeB[i].param)) {
285 if (shapeB[i].dim != 1)
286 s = shapeB[i];
287 else
288 s = shapeA[i];
289 }
290 }
291 }
292 }
293 } else if (fShapeInputs.size() == 1 ) {
295 }
296 if (!fShapeY.empty())
297 model.AddIntermediateTensor(fNY, model.GetTensorType(fNInputs[0]), fShapeY);
298 else
299 model.AddIntermediateTensor(fNY, model.GetTensorType(fNInputs[0]), fDimShapeY);
300
301
302 fType = ConvertTypeToString(model.GetTensorType(fNInputs[0]));
303
304 if (model.Verbose()) {
305 std::cout << NaryOperatorTraits<T, Op>::Name() << " : ";
306 if (fNInputs.size() == 2)
307 std::cout << ConvertDimShapeToString(fShapeInputs[0]) << " , "
309 std::cout << " --> " << ConvertDimShapeToString(fDimShapeY) << std::endl;
310 }
311 }
312
313 std::string Generate(std::string OpName) override {
315 return "";
316 OpName = "op_" + OpName;
317 if (fDimShapeY.empty()) {
318 throw std::runtime_error("TMVA SOFIE BasicNary called to Generate without being initialized first");
319 }
320 std::stringstream out;
322 out << SP << "\n//------ BasicNary operator\n";
323
324 int nInputs = fNInputs.size();
325
326 if (nInputs == 1) {
327 out << SP << "std::copy(tensor_" << fNInputs[0] << ", tensor_" << fNInputs[0] << " + ";
328 out << length << ", tensor_" << fNY << ");\n";
329 } else {
330
331 // implement operator without broadcasting, but using loos on all indices
332 std::vector<std::vector<Dim>> inputStrides(nInputs);
333 for (int i = 0; i < nInputs; i++)
335
337
338 // make loop on output indices
339 std::string compute_idx_Y;
340 int nloop = 0;
341 if (fDimShapeY.empty() ||
342 std::all_of(fDimShapeY.begin(), fDimShapeY.end(), [](Dim d) { return d.dim == 1 || d.GetVal() == "1"; })) {
343 compute_idx_Y = "0";
344 } else {
345 for (size_t i = 0; i < fDimShapeY.size(); ++i) {
346 if (fDimShapeY[i].dim != 1 && fDimShapeY[i].GetVal() != "1") {
347 nloop++;
348 for (int j = 0; j < nloop; j++) out << SP;
349 out << "for (size_t idx_" << i << " = 0; idx_" << i << " < " << fDimShapeY[i]
350 << "; ++idx_" << i << "){\n";
351 compute_idx_Y += "idx_" + std::to_string(i);
352 if (stridesY[i].GetVal() != "1")
353 compute_idx_Y += " * " + stridesY[i].GetVal();
354 compute_idx_Y += " + ";
355 }
356 }
357 // remove last 3 characters " + "
358 for (int j = 0; j < 3; j++)
359 compute_idx_Y.pop_back();
360 }
361 // find indices for input tensors
362 std::vector<std::string> inputs(nInputs);
363 for (int ipt = 0; ipt < nInputs; ipt++ ) {
364 std::string compute_idx_X;
365 auto & shape = fShapeInputs[ipt];
366 auto & stride = inputStrides[ipt];
367 if (shape.empty() ||
368 std::all_of(shape.begin(), shape.end(), [](Dim d) { return d.dim == 1 || d.GetVal() == "1"; })) {
369 compute_idx_X = "0";
370 } else {
371 for (size_t i = 0; i < shape.size(); ++i) {
372 if (shape[i].dim == 1 || shape[i].GetVal() == "1")
373 continue;
374 compute_idx_X += "idx_" + std::to_string(i + (fDimShapeY.size() - shape.size()));
375 if (stride[i].GetVal() != "1")
376 compute_idx_X += " * " + stride[i].GetVal();
377 compute_idx_X += " + ";
378 }
379 // remove last 3 character " + "
380 for (int j = 0; j < 3; j++)
381 compute_idx_X.pop_back();
382 }
383 inputs[ipt] = "tensor_" + fNInputs[ipt] + "[" + compute_idx_X + "]";
384 }
385
386 // perform the operation
387 for (int j = 0; j < nloop + 1; j++) out << SP;
388 std::string output = "tensor_" + fNY + "[" + compute_idx_Y + "]";
390
391 for (int i = nloop; i > 0; i--) {
392 for (int j = 0; j < i; j++) out << SP;
393 out << "}\n";
394 }
395 }
396 return out.str();
397 }
398
399 std::vector<std::string> GetStdLibs() override {return { std::string("cmath") }; }
400};
401
402}//SOFIE
403}//Experimental
404}//TMVA
405
406
407#endif //TMVA_SOFIE_ROPERATOR_BasicNary
#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 length
char name[80]
Definition TGX11.cxx:142
std::vector< std::vector< size_t > > ShapeInference(std::vector< std::vector< size_t > > input) override
std::vector< ETensorType > TypeInference(std::vector< ETensorType > input) override
ROperator_BasicNary(const std::vector< std::string > &inputNames, const std::string &nameY)
std::vector< std::string > GetStdLibs() override
std::string Generate(std::string OpName) override
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
const Int_t n
Definition legend1.C:16
std::string Clean_name(std::string input_tensor_name)
std::vector< size_t > MultidirectionalBroadcastShape(std::vector< std::vector< size_t > >)
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::vector< Dim > ConvertShapeToDim(const std::vector< size_t > &shape)
Convert shape from integer format to dynamic one (based on Dim)
ETensorType GetTemplatedType(T)
std::string ConvertTypeToString(ETensorType type)
std::string ConvertDimShapeToLength(const std::vector< Dim > &shape)
create variable transformations
static std::string Expr(const std::vector< std::string > &inputs)
static std::string Op(const std::string &res, std::vector< std::string > &inputs)
static std::string Expr(const std::vector< std::string > &inputs)
static std::string Op(const std::string &res, std::vector< std::string > &inputs)
static std::string Expr(const std::vector< std::string > &inputs)
static std::string Op(const std::string &res, std::vector< std::string > &inputs)
static std::string Expr(const std::vector< std::string > &inputs)
static std::string Op(const std::string &res, std::vector< std::string > &inputs)
static uint64_t sum(uint64_t i)
Definition Factory.cxx:2335