Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-08-16 09:21:39

0001 #ifndef TMVA_SOFIE_ROPERATOR_CLIP
0002 #define TMVA_SOFIE_ROPERATOR_CLIP
0003 
0004 #include "TMVA/SOFIE_common.hxx"
0005 #include "TMVA/ROperator.hxx"
0006 #include "TMVA/RModel.hxx"
0007 
0008 #include <limits>
0009 #include <sstream>
0010 #include <string>
0011 #include <vector>
0012 
0013 namespace TMVA {
0014 namespace Experimental {
0015 namespace SOFIE {
0016 
0017 // ---------------------------------------------------------------------------
0018 // ROperator_Clip
0019 //
0020 // ONNX spec: Y = max(min_val, min(max_val, X))  element-wise
0021 //
0022 // The min and max bounds are optional in the ONNX spec:
0023 //   - if fNMin is empty  → no lower clipping  (effectively -inf)
0024 //   - if fNMax is empty  → no upper clipping  (effectively +inf)
0025 //
0026 // Bounds can be provided either as:
0027 //   (a) initializer / constant tensors (scalar, shape []),
0028 //   (b) runtime input tensors          (resolved at Generate time),
0029 //   (c) compile-time float literals    (via the fMin / fMax attributes).
0030 //
0031 // The implementation follows the Selu operator style exactly:
0032 //   - static shape stored in fShape
0033 //   - dynamic shape stored in fDimShape
0034 //   - a flat loop over all elements in Generate()
0035 // ---------------------------------------------------------------------------
0036 
0037 template <typename T>
0038 class ROperator_Clip final : public ROperator {
0039 private:
0040 
0041    // Tensor names
0042    std::string fNX;       // input data
0043    std::string fNY;       // output
0044    std::string fNMin;     // optional: tensor name for min bound
0045    std::string fNMax;     // optional: tensor name for max bound
0046 
0047 
0048    // Static shape (non-dynamic path, mirrors Selu)
0049    std::vector<size_t> fShape;
0050 
0051    // Dynamic shape (Dim-aware, for dynamic input tensors)
0052    std::vector<Dim> fDimShape;
0053    bool fIsDynamic = false;
0054 
0055    // Compile-time bound values — used when bounds are constant tensors
0056    // Initialised to the ONNX defaults (no clipping)
0057    T fMin =  std::numeric_limits<T>::lowest();   // -inf equivalent
0058    T fMax =  std::numeric_limits<T>::max();      //  +inf equivalent
0059 
0060    // Flags indicating whether each bound is:
0061    //   - absent (no input provided)
0062    //   - a constant resolved at Initialize time
0063    //   - a runtime tensor that must be read in the generated code
0064    bool fHasMin         = false;
0065    bool fHasMax         = false;
0066    bool fMinIsConstant  = false;
0067    bool fMaxIsConstant  = false;
0068 
0069 public:
0070 
0071    ROperator_Clip() {}
0072 
0073    // Constructor for the common case where bounds are tensor inputs
0074    // (follows ONNX node input order: X, min, max)
0075    ROperator_Clip(std::string nameX,
0076                   std::string nameY,
0077                   std::string nameMin = "",
0078                   std::string nameMax = "")
0079       : fNX  (UTILITY::Clean_name(nameX)),
0080         fNY  (UTILITY::Clean_name(nameY)),
0081         fNMin(nameMin.empty() ? "" : UTILITY::Clean_name(nameMin)),
0082         fNMax(nameMax.empty() ? "" : UTILITY::Clean_name(nameMax))
0083    {
0084       fInputTensorNames  = { fNX };
0085       if (!fNMin.empty()) fInputTensorNames.push_back(fNMin);
0086       if (!fNMax.empty()) fInputTensorNames.push_back(fNMax);
0087       fOutputTensorNames = { fNY };
0088    }
0089 
0090    // Convenience constructor when bounds are known scalars at model-build time
0091    ROperator_Clip(std::string nameX,
0092                   std::string nameY,
0093                   T minVal,
0094                   T maxVal)
0095       : fNX (UTILITY::Clean_name(nameX)),
0096         fNY (UTILITY::Clean_name(nameY)),
0097         fMin(minVal), fMax(maxVal),
0098         fHasMin(true), fHasMax(true),
0099         fMinIsConstant(true), fMaxIsConstant(true)
0100    {
0101       fInputTensorNames  = { fNX };
0102       fOutputTensorNames = { fNY };
0103    }
0104 
0105 
0106    // -----------------------------------------------------------------------
0107    void Initialize(RModel& model) override
0108    {
0109       // ---- validate main input ------------------------------------------
0110       if (!model.CheckIfTensorAlreadyExist(fNX))
0111          throw std::runtime_error(
0112             "TMVA SOFIE Clip Op Input Tensor " + fNX + " is not found in model");
0113 
0114       // ---- collect shape (static or dynamic, mirrors BasicBinary) -------
0115       if (model.IsDynamicTensor(fNX)) {
0116          fIsDynamic = true;
0117          fDimShape  = model.GetDynamicTensorShape(fNX);
0118       } else {
0119          fShape    = model.GetTensorShape(fNX);
0120          fDimShape = ConvertShapeToDim(fShape);
0121       }
0122 
0123       // ---- resolve min bound --------------------------------------------
0124       if (!fNMin.empty() && model.CheckIfTensorAlreadyExist(fNMin)) {
0125          fHasMin = true;
0126          if (model.IsInitializedTensor(fNMin)) {
0127             // constant scalar tensor — read value now
0128             auto data = static_cast<T*>(model.GetInitializedTensorData(fNMin).get());
0129             fMin            = data[0];
0130             fMinIsConstant  = true;
0131             model.SetNotWritableInitializedTensor(fNMin);
0132          }
0133          // else: runtime input — will be dereferenced in generated code
0134       }
0135 
0136       // ---- resolve max bound --------------------------------------------
0137       if (!fNMax.empty() && model.CheckIfTensorAlreadyExist(fNMax)) {
0138          fHasMax = true;
0139          if (model.IsInitializedTensor(fNMax)) {
0140             auto data = static_cast<T*>(model.GetInitializedTensorData(fNMax).get());
0141             fMax            = data[0];
0142             fMaxIsConstant  = true;
0143             model.SetNotWritableInitializedTensor(fNMax);
0144          }
0145       }
0146 
0147       // ---- register output tensor ---------------------------------------
0148       if (fIsDynamic)
0149          model.AddIntermediateTensor(fNY, model.GetTensorType(fNX), fDimShape);
0150       else
0151          model.AddIntermediateTensor(fNY, model.GetTensorType(fNX), fShape);
0152 
0153       if (model.Verbose()) {
0154          std::cout << "Clip : " << fNX << " "
0155                    << ConvertShapeToString(fShape);
0156          if (fHasMin)
0157             std::cout << "  min=" << (fMinIsConstant
0158                        ? std::to_string(fMin) : fNMin + "(runtime)");
0159          if (fHasMax)
0160             std::cout << "  max=" << (fMaxIsConstant
0161                        ? std::to_string(fMax) : fNMax + "(runtime)");
0162          std::cout << " --> " << fNY << "\n";
0163       }
0164 
0165       // only needs <algorithm> and <limits> — no cmath
0166       model.AddNeededStdLib("algorithm");
0167       model.AddNeededStdLib("limits");
0168    }
0169 
0170 
0171    // -----------------------------------------------------------------------
0172    // Generate
0173    // -----------------------------------------------------------------------
0174    std::string Generate(std::string OpName) override
0175    {
0176       OpName = "op_" + OpName;
0177 
0178       if (fShape.empty() && fDimShape.empty())
0179          throw std::runtime_error(
0180             "TMVA SOFIE Operator Clip called to Generate without being initialized first");
0181 
0182       std::stringstream out;
0183       out << SP << "\n//------ CLIP " << OpName << "\n";
0184 
0185       // ---- build the length expression (static or dynamic) -------------
0186       std::string length = ConvertDimShapeToLength(fDimShape);
0187 
0188       // ---- build min/max expressions for the generated code ------------
0189       //
0190       //  Priority:
0191       //    1. compile-time constant value  → emit literal
0192       //    2. runtime input tensor         → emit tensor_<name>[0]  (scalar)
0193       //    3. not provided                 → emit numeric_limits extreme
0194       //
0195       std::string minExpr, maxExpr;
0196 
0197       if (fMinIsConstant) {
0198          minExpr = ToStringHighPrec(fMin);
0199       } else if (fHasMin) {
0200          minExpr = "tensor_" + fNMin + "[0]";  // scalar input tensor
0201       } else {
0202          // No lower bound — use lowest representable value
0203          minExpr = "std::numeric_limits<" + TensorType<T>::Name()
0204                    + ">::lowest()";
0205       }
0206 
0207       if (fMaxIsConstant) {
0208          maxExpr = ToStringHighPrec(fMax);
0209       } else if (fHasMax) {
0210          maxExpr = "tensor_" + fNMax + "[0]";
0211       } else {
0212          // No upper bound — use max representable value
0213          maxExpr = "std::numeric_limits<" + TensorType<T>::Name()
0214                    + ">::max()";
0215       }
0216 
0217       auto tensorValue = [](const std::string & name, const std::string & index) {
0218          std::stringstream s;
0219          s << "tensor_" << name << "[" << index << "]";
0220          return s.str();
0221       };
0222 
0223       // ---- flat element loop (identical structure to Selu) -------------
0224       out << SP << "for (int id = 0; id < " << length << " ; id++) {\n";
0225       std::string firstExpr = fHasMax ? "std::min(" + maxExpr + ", " + tensorValue(fNX, "id") + ")" : tensorValue(fNX, "id");
0226       std::string secondExpr  = fHasMin ? "std::max(" + minExpr + ", " + firstExpr + ")" : firstExpr;
0227       out << SP << SP << tensorValue(fNY, "id") << " = " << secondExpr << ";\n";
0228       out << SP << "}\n";
0229 
0230       return out.str();
0231    }
0232 
0233 
0234 private:
0235 
0236    // Helper: convert a T value to string with enough precision
0237    std::string ToStringHighPrec(T val) const {
0238       std::ostringstream ss;
0239       ss << std::setprecision(std::numeric_limits<T>::max_digits10) << val;
0240       // add dot if missing
0241       if (ss.str().find(".") == std::string::npos) ss << ".";
0242       // append 'f' suffix for float literals so generated code compiles
0243       // cleanly without implicit double→float conversion warnings
0244       if (std::is_same<T, float>::value) ss << "f";
0245       return ss.str();
0246    }
0247 };
0248 
0249 } // namespace SOFIE
0250 } // namespace Experimental
0251 } // namespace TMVA
0252 
0253 #endif // TMVA_SOFIE_ROPERATOR_CLIP