class_docstr: This helper test class takes in any supported type of and runs some tests.
  This inherits the TestBaseDataSparsifierRuner wherein some functions are
  over-ridden to take accomodate the specific sparsifier.
  TODO: Change the structure by creating a separate test case class for each
        member function
class_docstr_tok: ["This","helper","test","class","takes","in","any","supported","type","of","and","runs","some","tests",".","This","inherits","the","TestBaseDataSparsifierRuner","wherein","some","functions","are","over-ridden","to","take","accomodate","the","specific","sparsifier",".","TODO",":","Change","the","structure","by","creating","a","separate","test","case","class","for","each","member","function"]
docstr: This function gets the bounds on actual sparsity.
  Note::
      Although we specify the sparsity_level parameter, this does not mean that
      the actual sparsity obtained after sparsification is the same as sparsity_level.
      The actual sparsity depends largely on the shape and the data itself.
docstr_tok: ["This","function","gets","the","bounds","on","actual","sparsity",".","Note",":",":","Although","we","specify","the","sparsity_level","parameter",",","this","does","not","mean","that","the","actual","sparsity","obtained","after","sparsification","is","the","same","as","sparsity_level",".","The","actual","sparsity","depends","largely","on","the","shape","and","the","data","itself","."]
code: def _get_bounds_on_actual_sparsity(config, tensor_shape):
      r"""This function gets the bounds on actual sparsity.
      Note::
          Although we specify the sparsity_level parameter, this does not mean that
          the actual sparsity obtained after sparsification is the same as sparsity_level.
          The actual sparsity depends largely on the shape and the data itself.
      """
      sparsity_level = config["sparsity_level"]
      zeros_per_block = config["zeros_per_block"]
      sparse_block_shape = config["sparse_block_shape"]
  
      height, width = tensor_shape[-2], tensor_shape[-1]
      block_height, block_width = sparse_block_shape
      number_blocks = math.ceil(height / block_height) * math.ceil(
          width / block_width
      )
      values_per_block = block_height * block_width
  
      if zeros_per_block == 0:
          return (1.0, 1.0)
      else:
          # min value assumes zeros_per_block is 1
          min_values_sparsified = round(number_blocks * sparsity_level)
          # max value assumes actual zeros_per_block
          max_values_sparsified = min_values_sparsified * min(
              values_per_block, zeros_per_block
          )
          lower_bound = min_values_sparsified / (height * width)
          upper_bound = min(
              1.0, max_values_sparsified / (height * width)
          )
  
          lower_bound, upper_bound = round(lower_bound, 3), round(
              upper_bound, 3
          )
          return lower_bound, upper_bound
  
imports: import logging
  import torch
  from torch.nn.utils.parametrize import is_parametrized
  from torch.testing._internal.common_utils import TestCase
  from typing import Tuple
  from torch import nn
  import itertools
  import math
  import copy
  from torch.ao.pruning._experimental.data_sparsifier import BaseDataSparsifier, DataNormSparsifier
  from torch.ao.pruning._experimental.data_sparsifier.quantization_utils import post_training_sparse_quantize
label_desc: Use image node_id 2 for calling the _NormDataSparsifierTestCase obj's underlying member method code with example usage: obj._get_bounds_on_actual_sparsity(config, tensor_shape) and returns: int,  int, lower_bound,  upper_bound