class_docstr: Transforms the weights of a Conv2D kernel to a format suitable for Wingorad convolution
  For further information, refer to Lavin & Gray, 2015 - Fast Algorithms for Convolutional Neural Networks
code: def transform(self, model, node):
      if isinstance(node, Conv1D):
          if node.get_attr("filt_width", 3) == 3:
              # First, transpose to a format suitable for the Winograd algorithm (F, C, W)
              # Note, this assumes a format post-resource strategy optimizer, that is (F, W, C)
              # Therefore, (F, W, C) => (F, C, W)
              node.weights["weight"].data = np.transpose(
                  node.weights["weight"].data, axes=[0, 2, 1]
              )
  
              # Temporary copy of data
              weights = node.weights["weight"].data
  
              # Expand weight dimensionality (3) => (4)
              node.weights["weight"].data = np.zeros(
                  (weights.shape[0], weights.shape[1], 4)
              )
  
              # Transformation matrices for 3x1 kernels
              G = np.array(
                  [
                      [1, 0, 0],
                      [0.5, 0.5, 0.5],
                      [0.5, -0.5, 0.5],
                      [0, 0, 1],
                  ]
              )
  
              # Transformation GfG'
              for filter in range(0, weights.data.shape[0]):
                  for channel in range(0, weights.data.shape[1]):
                      node.weights["weight"].data[filter][
                          channel
                      ] = np.matmul(G, weights[filter][channel])
                      node.weights["weight"].data_length = node.weights[
                          "weight"
                      ].data.size
  
              # Winograd's minimal filtering algorithm transforms the weight matrix
              # This transformation consists of addition and division (by 2&4) of the weight matrix
              # Therefore, increase precision (if needed), to accomodate for new weights
              # This error is only noticeable for low precisions, such as those used with QKeras
  
              # Integer precision is only updated if it exceeds the one defined in hls4ml config
              maximum_value_rounded = int(
                  math.ceil(np.abs(node.weights["weight"].data).max())
              )
              if (
                  maximum_value_rounded.bit_length() + 1
                  > node.weights["weight"].type.precision.integer
              ):
                  node.weights["weight"].type.precision.integer = (
                      maximum_value_rounded.bit_length() + 1
                  )
                  node.weights["weight"].type.precision.width += (
                      maximum_value_rounded.bit_length()
                      + 1
                      - node.weights["weight"].type.precision.integer
                  )
  
              # Fractional precision is increased by 2 bits (division by 4),
              # for low-precision (less than 8) fractional weights
              if node.weights["weight"].type.precision.fractional < 8:
                  node.weights["weight"].type.precision.fractional += 2
                  node.weights["weight"].type.precision.width += 2
  
              # Modified kernel size
              node.set_attr("impl_filt_width", 4)
  
      elif isinstance(node, Conv2D):
          if (
              node.get_attr("filt_height", 3) == 3
              and node.get_attr("filt_width", 3) == 3
          ):
              # First, transpose to a format suitable for the Winograd algorithm (F, C, H, W)
              # Note, this assumes a format post-resource strategy optimizer, that is (F, H, W, C)
              # Therefore, (F, H, W, C) => (F, C, H, W)
              node.weights["weight"].data = np.transpose(
                  node.weights["weight"].data, axes=[0, 3, 1, 2]
              )
  
              # Temporary copy of data
              weights = node.weights["weight"].data
  
              # Expand weight dimensionality (3x3) => (4x4)
              node.weights["weight"].data = np.zeros(
                  (weights.shape[0], weights.shape[1], 4, 4)
              )
  
              # Transformation matrices for 3x3 kernels
              G = np.array(
                  [
                      [1, 0, 0],
                      [0.5, 0.5, 0.5],
                      [0.5, -0.5, 0.5],
                      [0, 0, 1],
                  ]
              )
              GT = np.array(
                  [
                      [1, 0.5, 0.5, 0],
                      [0, 0.5, -0.5, 0],
                      [0, 0.5, 0.5, 1],
                  ]
              )
  
              # Transformation GfG'
              for filter in range(0, weights.data.shape[0]):
                  for channel in range(0, weights.data.shape[1]):
                      node.weights["weight"].data[filter][
                          channel
                      ] = np.matmul(
                          np.matmul(G, weights[filter][channel]), GT
                      )
                      node.weights["weight"].data_length = node.weights[
                          "weight"
                      ].data.size
  
              # Winograd's minimal filtering algorithm transforms the weight matrix
              # This transformation consists of addition and division (by 2&4) of the weight matrix
              # Therefore, increase precision (if needed), to accomodate for new weights
              # This error is only noticeable for low precisions, such as those used with QKeras
  
              # Integer precision is only updated if it exceeds the one defined in hls4ml config
              maximum_value_rounded = int(
                  math.ceil(np.abs(node.weights["weight"].data).max())
              )
              if (
                  maximum_value_rounded.bit_length() + 1
                  > node.weights["weight"].type.precision.integer
              ):
                  node.weights["weight"].type.precision.integer = (
                      maximum_value_rounded.bit_length() + 1
                  )
                  node.weights["weight"].type.precision.width += (
                      maximum_value_rounded.bit_length()
                      + 1
                      - node.weights["weight"].type.precision.integer
                  )
  
              # Fractional precision is increased by 2 bits (division by 4),
              # for low-precision (less than 8) fractional weights
              if node.weights["weight"].type.precision.fractional < 8:
                  node.weights["weight"].type.precision.fractional += 2
                  node.weights["weight"].type.precision.width += 2
  
              # Modified kernel size
              node.set_attr("impl_filt_height", 4)
              node.set_attr("impl_filt_width", 4)
      else:
          raise Exception(
              f"Unexpected layer {node.class_name} with Winograd kernel optimizer"
          )
  
      node.set_attr("_winograd_transformation_applied", True)
  
      return False
  
imports: import math
  import numpy
  from hls4ml.model.layers import Conv1D, Conv2D
  from hls4ml.model.optimizer import OptimizerPass
label_desc: Use image node_id 2 for calling the ApplyWinogradKernelTransformation obj's underlying member method code with example usage: obj.transform(model, node) and returns: False