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 <algorithm> // for std::all_of
9#include <sstream>
10#include <vector>
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>
115class ROperator_BasicNary final : public ROperator
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 // Case where all inputs are rank <= 1 INT64 tensors known at initialization time and at least one of
148 // them is a shape tensor. The output is then also a shape tensor (its values, possibly symbolic, are
149 // computed here) and no code needs to be generated for this operator.
151 {
152 bool hasShapeTensor = false;
153 bool isScalar = true;
154 size_t length = 1;
155 for (auto &name : fNInputs) {
156 if (model.GetTensorType(name) != ETensorType::INT64)
157 return false;
158 if (!model.IsShapeTensor(name) && !model.IsInitializedTensor(name))
159 return false;
160 hasShapeTensor |= model.IsShapeTensor(name);
161 auto shape = model.GetTensorShape(name);
162 if (shape.size() > 1)
163 return false;
164 if (!shape.empty()) {
165 isScalar = false;
166 // only scalars or tensors of the same length can be combined here
167 if (shape[0] != 1 && length != 1 && shape[0] != length)
168 return false;
169 length = std::max(length, shape[0]);
170 }
171 }
172 if (!hasShapeTensor)
173 return false;
174
175 // collect the values of every input as Dim's, broadcasting the scalars and the size-1 tensors
176 std::vector<std::vector<Dim>> values(fNInputs.size(), std::vector<Dim>(length));
177 for (size_t i = 0; i < fNInputs.size(); i++) {
178 auto &name = fNInputs[i];
179 if (model.IsShapeTensor(name)) {
180 auto &dims = model.GetShapeTensorValues(name);
181 for (size_t j = 0; j < length; j++)
182 values[i][j] = (dims.size() == 1) ? dims[0] : dims[j];
183 } else {
184 auto data = static_cast<int64_t *>(model.GetInitializedTensorData(name).get());
185 size_t n = ConvertShapeToLength(model.GetTensorShape(name));
186 for (size_t j = 0; j < length; j++)
187 values[i][j] = Dim{static_cast<size_t>(data[(n == 1) ? 0 : j])};
188 // deliberately not flagged as non-writable: the same initializer may still be
189 // read at run time by another operator, and a non-writable tensor gets no
190 // member emitted at all. Leaving it costs a few bytes in the weight file.
191 }
192 }
193
194 std::vector<Dim> outputValues(length);
195 for (size_t j = 0; j < length; j++) {
196 bool isConstant = true;
197 std::vector<size_t> dims(fNInputs.size());
198 std::vector<std::string> exprs(fNInputs.size());
199 for (size_t i = 0; i < fNInputs.size(); i++) {
200 isConstant &= !values[i][j].isParam;
201 dims[i] = values[i][j].dim;
202 // cast to size_t so that the parametric dimensions and the literals have a common type
203 exprs[i] = "size_t(" + values[i][j].GetVal() + ")";
204 }
205 if (isConstant)
206 outputValues[j] = Dim{NaryOperatorTraits<T, Op>::Func(dims)};
207 else
208 outputValues[j] = Dim{NaryOperatorTraits<T, Op>::Expr(exprs), static_cast<size_t>(-1)};
209 }
210 model.AddShapeTensor(fNY, outputValues, isScalar);
211 fIsOutputConstant = true;
212 if (model.Verbose()) {
213 std::cout << NaryOperatorTraits<T, Op>::Name() << " : --> " << fNY << " "
214 << ConvertDimShapeToString(outputValues) << " (shape)" << std::endl;
215 }
216 return true;
217 }
218
219 void Initialize(RModel& model) override {
220 std::vector<std::vector<size_t>> inputShapes;
221 for (auto &it : fNInputs) {
222 if (!model.CheckIfTensorAlreadyExist(it)) {
223 throw std::runtime_error("TMVA SOFIE BasicNary Op Input Tensor " + it + " is not found in model");
224 }
225 }
227 return;
228 for (auto &it : fNInputs) {
229 fShapeInputs.push_back(model.GetDimTensorShape(it));
230 if (fNInputs.size()> 2) {
231 if (model.IsDimInputTensor(it))
232 throw std::runtime_error("TMVA SOFIE BasicNary : supports only 2 inputs for dynamic tensors");
233 else
234 inputShapes.push_back(model.GetTensorShape(it));
235 }
236 }
237 // Find the common shape of the input tensors
238 if (fShapeInputs.size() > 2 ) {
239 // support dynamic tensors now for input list of size=2
240 auto shapeY = UTILITY::MultidirectionalBroadcastShape(inputShapes);
242 } else if (fShapeInputs.size() == 2 ) {
244 // use same code as in BinaryOperator (need to extend for input sizes > 2)
245 fBroadcast = ret.first;
246 fDimShapeY = ret.second;
247 // case of all parametric shapes and MultiDirectionalBroadcastShape return the max of the 2
248 // need to do before we declare the output tensor shape and the broadcasted ones
249 if (ret.first & 4) {
250 // check if one of the parameter is an input dimension
251 // define function to find this
252 auto IsInputDimParam = [&](const std::string &p) {
253 auto inputNames = model.GetInputTensorNames();
254 for (auto &input : inputNames) {
255 for (auto &i_s : model.GetDimTensorShape(input)) {
256 if (i_s.isParam && i_s.param == p)
257 return true;
258 }
259 }
260 return false;
261 };
262 auto & shapeA = fShapeInputs[0];
263 auto & shapeB = fShapeInputs[1];
264 for (size_t i = 0; i < fDimShapeY.size(); i++) {
265 auto &s = fDimShapeY[i];
266 if (s.isParam && s.param.find("std::max") != std::string::npos) {
267 if (IsInputDimParam(shapeA[i].param)) {
268 // case dim is 1 we indicate that the input parameter is equal to 1
269 if (shapeA[i].dim != 1)
270 s = shapeA[i];
271 else
272 s = shapeB[i];
273 } else if (IsInputDimParam(shapeB[i].param)) {
274 if (shapeB[i].dim != 1)
275 s = shapeB[i];
276 else
277 s = shapeA[i];
278 }
279 }
280 }
281 }
282 } else if (fShapeInputs.size() == 1 ) {
284 }
285 if (!fShapeY.empty())
286 model.AddIntermediateTensor(fNY, model.GetTensorType(fNInputs[0]), fShapeY);
287 else
288 model.AddIntermediateTensor(fNY, model.GetTensorType(fNInputs[0]), fDimShapeY);
289
290
291 fType = ConvertTypeToString(model.GetTensorType(fNInputs[0]));
292
293 if (model.Verbose()) {
294 std::cout << NaryOperatorTraits<T, Op>::Name() << " : ";
295 if (fNInputs.size() == 2)
296 std::cout << ConvertDimShapeToString(fShapeInputs[0]) << " , "
298 std::cout << " --> " << ConvertDimShapeToString(fDimShapeY) << std::endl;
299 }
300 }
301
302 std::string Generate(std::string OpName) override {
304 return "";
305 OpName = "op_" + OpName;
306 if (fDimShapeY.empty()) {
307 throw std::runtime_error("TMVA SOFIE BasicNary called to Generate without being initialized first");
308 }
309 std::stringstream out;
311 out << SP << "\n//------ BasicNary operator\n";
312
313 int nInputs = fNInputs.size();
314
315 if (nInputs == 1) {
316 out << SP << "std::copy(tensor_" << fNInputs[0] << ", tensor_" << fNInputs[0] << " + ";
317 out << length << ", tensor_" << fNY << ");\n";
318 } else {
319
320 // implement operator without broadcasting, but using loos on all indices
321 std::vector<std::vector<Dim>> inputStrides(nInputs);
322 for (int i = 0; i < nInputs; i++)
323 inputStrides[i] = UTILITY::ComputeStrideFromShape(fShapeInputs[i]);
324
326
327 // make loop on output indices
328 std::string compute_idx_Y;
329 int nloop = 0;
330 if (fDimShapeY.empty() ||
331 std::all_of(fDimShapeY.begin(), fDimShapeY.end(), [](Dim d) { return d.dim == 1 || d.GetVal() == "1"; })) {
332 compute_idx_Y = "0";
333 } else {
334 for (size_t i = 0; i < fDimShapeY.size(); ++i) {
335 if (fDimShapeY[i].dim != 1 && fDimShapeY[i].GetVal() != "1") {
336 nloop++;
337 for (int j = 0; j < nloop; j++) out << SP;
338 out << "for (size_t idx_" << i << " = 0; idx_" << i << " < " << fDimShapeY[i]
339 << "; ++idx_" << i << "){\n";
340 compute_idx_Y += "idx_" + std::to_string(i);
341 if (stridesY[i].GetVal() != "1")
342 compute_idx_Y += " * " + stridesY[i].GetVal();
343 compute_idx_Y += " + ";
344 }
345 }
346 // remove last 3 characters " + "
347 for (int j = 0; j < 3; j++)
348 compute_idx_Y.pop_back();
349 }
350 // find indices for input tensors
351 std::vector<std::string> inputs(nInputs);
352 for (int ipt = 0; ipt < nInputs; ipt++ ) {
353 std::string compute_idx_X;
354 auto & shape = fShapeInputs[ipt];
355 auto & stride = inputStrides[ipt];
356 if (shape.empty() ||
357 std::all_of(shape.begin(), shape.end(), [](Dim d) { return d.dim == 1 || d.GetVal() == "1"; })) {
358 compute_idx_X = "0";
359 } else {
360 for (size_t i = 0; i < shape.size(); ++i) {
361 if (shape[i].dim == 1 || shape[i].GetVal() == "1")
362 continue;
363 compute_idx_X += "idx_" + std::to_string(i + (fDimShapeY.size() - shape.size()));
364 if (stride[i].GetVal() != "1")
365 compute_idx_X += " * " + stride[i].GetVal();
366 compute_idx_X += " + ";
367 }
368 // remove last 3 character " + "
369 for (int j = 0; j < 3; j++)
370 compute_idx_X.pop_back();
371 }
372 inputs[ipt] = "tensor_" + fNInputs[ipt] + "[" + compute_idx_X + "]";
373 }
374
375 // perform the operation
376 for (int j = 0; j < nloop + 1; j++) out << SP;
377 std::string output = "tensor_" + fNY + "[" + compute_idx_Y + "]";
378 out << NaryOperatorTraits<T,Op>::Op(output, inputs);
379
380 for (int i = nloop; i > 0; i--) {
381 for (int j = 0; j < i; j++) out << SP;
382 out << "}\n";
383 }
384 }
385 return out.str();
386 }
387
388 std::vector<std::string> GetStdLibs() override {return { std::string("cmath") }; }
389};
390
391}//SOFIE
392}//Experimental
393}//TMVA
394
395
396#endif //TMVA_SOFIE_ROPERATOR_BasicNary
#define d(i)
Definition RSha256.hxx:102
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
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:44
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
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