class_docstr: ``torch.optim.Optimizer`` wrapper that adds additional functionality to clip per
  sample gradients and add Gaussian noise.
  
  Can be used with any ``torch.optim.Optimizer`` subclass as an underlying optimizer.
  ``DPOptimzer`` assumes that parameters over which it performs optimization belong
  to GradSampleModule and therefore have the ``grad_sample`` attribute.
  
  On a high level ``DPOptimizer``'s step looks like this:
  1) Aggregate ``p.grad_sample`` over all parameters to calculate per sample norms
  2) Clip ``p.grad_sample`` so that per sample norm is not above threshold
  3) Aggregate clipped per sample gradients into ``p.grad``
  4) Add Gaussian noise to ``p.grad`` calibrated to a given noise multiplier and
  max grad norm limit (``std = noise_multiplier * max_grad_norm``).
  5) Call underlying optimizer to perform optimization step
  
  Examples:
      >>> module = MyCustomModel()
      >>> optimizer = torch.optim.SGD(module.parameters(), lr=0.1)
      >>> dp_optimizer = DPOptimizer(
      ...     optimizer=optimizer,
      ...     noise_multiplier=1.0,
      ...     max_grad_norm=1.0,
      ...     expected_batch_size=4,
      ... )
docstr: Returns number of batches currently accumulated and not yet processed.
  
  In other words ``accumulated_iterations`` tracks the number of forward/backward
  passed done in between two optimizer steps. The value would typically be 1,
  but there are possible exceptions.
  
  Used by privacy accountants to calculate real sampling rate.
docstr_tok: ["Returns","number","of","batches","currently","accumulated","and","not","yet","processed",".","In","other","words","``","accumulated_iterations","``","tracks","the","number","of","forward\/backward","passed","done","in","between","two","optimizer","steps",".","The","value","would","typically","be","1",",","but","there","are","possible","exceptions",".","Used","by","privacy","accountants","to","calculate","real","sampling","rate","."]
code: def accumulated_iterations(self) -> int:
      """
      Returns number of batches currently accumulated and not yet processed.
  
      In other words ``accumulated_iterations`` tracks the number of forward/backward
      passed done in between two optimizer steps. The value would typically be 1,
      but there are possible exceptions.
  
      Used by privacy accountants to calculate real sampling rate.
      """
      vals = []
      for p in self.params:
          if not hasattr(p, "grad_sample"):
              raise ValueError(
                  "Per sample gradient not found. Are you using GradSampleModule?"
              )
          if isinstance(p.grad_sample, torch.Tensor):
              vals.append(1)
          elif isinstance(p.grad_sample, list):
              vals.append(len(p.grad_sample))
          else:
              raise ValueError(
                  f"Unexpected grad_sample type: {type(p.grad_sample)}"
              )
  
      if len(set(vals)) > 1:
          raise ValueError(
              "Number of accumulated steps is inconsistent across parameters"
          )
      return vals[0]
  
imports: from __future__ import annotations
  import logging
  from typing import Callable, List, Optional, Union
  import torch
  from opacus.optimizers.utils import params
  from opt_einsum.contract import contract
  from torch import nn
  from torch.optim import Optimizer
label_desc: Use image node_id 7 for calling the DPOptimizer obj's underlying member method code with example usage: obj.accumulated_iterations() and returns: vals