class_docstr: Wrap another tf optimizer with BoltOn privacy protocol.
  
  BoltOn optimizer wraps another tf optimizer to be used
  as the visible optimizer to the tf model. No matter the optimizer
  passed, "BoltOn" enables the bolt-on model to control the learning rate
  based on the strongly convex loss.
  
  To use the BoltOn method, you must:
  1. instantiate it with an instantiated tf optimizer and StrongConvexLoss.
  2. use it as a context manager around your .fit method internals.
  
  This can be accomplished by the following:
  optimizer = tf.optimizers.SGD()
  loss = privacy.bolt_on.losses.StrongConvexBinaryCrossentropy()
  bolton = BoltOn(optimizer, loss)
  with bolton(*args) as _:
    model.fit()
  The args required for the context manager can be found in the __call__
  method.
  
  For more details on the strong convexity requirements, see:
  Bolt-on Differential Privacy for Scalable Stochastic Gradient
  Descent-based Analytics by Xi Wu et. al.
docstr: Reroutes to _internal_optimizer. See super/_internal_optimizer.
docstr_tok: ["Reroutes","to","_internal_optimizer",".","See","super\/_internal_optimizer","."]
code: def apply_gradients(
      self, *args, **kwargs
  ):  # pylint: disable=arguments-differ
      """Reroutes to _internal_optimizer. See super/_internal_optimizer."""
      out = self._internal_optimizer.apply_gradients(*args, **kwargs)
      self.project_weights_to_r()
      return out
  
imports: import tensorflow.compat.v1
  from tensorflow.compat.v1.python.keras.optimizer_v2 import optimizer_v2
  from tensorflow.compat.v1.python.ops import math_ops
  from tensorflow_privacy.privacy.bolt_on.losses import StrongConvexMixin
label_desc: Use image node_id 11 for calling the BoltOn obj's underlying member method code with example usage: obj.apply_gradients() and returns: out