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