text: # Copyright 2018 The TensorFlow Authors. All Rights Reserved.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  # ==============================================================================
  """Utility to get distribution strategy related contexts."""
  
  from __future__ import absolute_import
  from __future__ import division
  from __future__ import print_function
  
  from tensorflow.python.framework import ops
  from tensorflow.python.util.lazy_loader import LazyLoader
  
  
  # There is a circular dependency between this and `distribute` module. So we
  # load it lazily to workaround this.
  distribute_lib = LazyLoader(
      "distribute_lib", globals(),
      "tensorflow.python.training.distribute")
  
  # ------------------------------------------------------------------------------
  # Internal API for setting the current thread mode as being either in a
  # tower or cross-tower context for a particular distribution strategy.
  
  
  class _ThreadMode(object):
  
    def __init__(self, dist, cross, tower):
      self.distribution_strategy = dist
      self.cross_tower_context = cross
      self.tower_context = tower
  
  
  class _CrossTowerThreadMode(_ThreadMode):
  
    def __init__(self, distribution_strategy):
      _ThreadMode.__init__(
          self, distribution_strategy, distribution_strategy, None)
  
  
  class _InTowerThreadMode(_ThreadMode):
  
    def __init__(self, tower_ctx):
      _ThreadMode.__init__(
          self, tower_ctx.distribution_strategy, None, tower_ctx)
  
  
  def _push_per_thread_mode(context):
    ops.get_default_graph()._distribution_strategy_stack.append(context)  # pylint: disable=protected-access
  
  
  def _pop_per_thread_mode():
    ops.get_default_graph()._distribution_strategy_stack.pop(-1)  # pylint: disable=protected-access
  
  
  class _DefaultTowerThreadMode(_ThreadMode):
    """Type of default value returned by `_get_per_thread_mode()`.
  
    Used when the thread-local stack is empty.
    """
  
    def __init__(self):
      _ThreadMode.__init__(self, _get_default_distribution_strategy(), None,
                           _get_default_tower_context())
  
  
  def _get_per_thread_mode():
    try:
      return ops.get_default_graph()._distribution_strategy_stack[-1]  # pylint: disable=protected-access
    except (AttributeError, IndexError):
      return _get_default_tower_mode()
  
  
  # ------------------------------------------------------------------------------
  # Public API for accessing the current thread mode
  
  
  def get_tower_context():
    """Returns the current TowerContext or None if in a cross-tower context.
  
    Note that execution:
    1. starts in the default (single-tower) tower context (this function
       will return the default TowerContext object);
    2. switches to cross-tower context (in which case this will return
       None) when entering a `with DistributionStrategy.scope():` block;
    3. switches to a (non-default) tower context inside
       `call_for_each_tower(fn, ...)`;
    4. if `fn` calls `get_tower_context()->merge_call(merge_fn, ...)`, then
       inside `merge_fn` you are back in the cross-tower context (and again
       this function will return None).
  
    Note that you can also go directly from step 1 to 4 to switch to a
    cross-tower context for the default `DistributionStrategy`. You may
    also switch from the cross-tower context of 4 to a tower context by
    calling `call_for_each_tower()`, jumping back to step 3.
  
    Most `DistributionStrategy` methods may only be executed in
    a cross-tower context, in a tower context you should use the
    `TowerContext` API instead.
  
    Returns:
      The current `TowerContext` object when in a tower context scope, else None.
  
      Exactly one of `get_tower_context()` and `get_cross_tower_context()`
      will return None in a particular block.
    """
    return _get_per_thread_mode().tower_context
  
  
  def get_cross_tower_context():
    """Returns the current DistributionStrategy if in a cross-tower context.
  
    Note that execution:
    1. starts in the default (single-tower) tower context;
    2. switches to cross-tower context when entering a
       `with DistributionStrategy.scope():` block;
    3. switches to a (non-default) tower context inside
       `call_for_each_tower(fn, ...)`;
    4. if `fn` calls `get_tower_context()->merge_call(merge_fn, ...)`, then
       inside `merge_fn` you are back in the cross-tower context.
  
    Note that you can also go directly from step 1 to 4 to switch to a
    cross-tower context for the default `DistributionStrategy`. You may
    also switch from the cross-tower context of 4 to a tower context by
    calling `call_for_each_tower()`, jumping back to step 3.
  
    Most `DistributionStrategy` methods may only be executed in
    a cross-tower context.
  
    Returns:
      Returns the current `DistributionStrategy` object in a cross-tower
      context, or None.
  
      Exactly one of `get_tower_context()` and `get_cross_tower_context()`
      will return None in a particular block.
    """
    return _get_per_thread_mode().cross_tower_context
  
  
  def get_distribution_strategy():
    """Returns the current `DistributionStrategy` object.
  
    Prefer to use `get_tower_context()` or `get_cross_tower_context()`
    instead when possible.
  
    Returns:
      A `DistributionStrategy` object. Inside a
      `with distribution_strategy.scope()` block, it returns
      `distribution_strategy`, otherwise it returns the default
      (single-tower) `DistributionStrategy` object.
    """
    return _get_per_thread_mode().distribution_strategy
  
  
  def has_distribution_strategy():
    """Return if there is a current non-default `DistributionStrategy`.
  
    Returns:
      True if inside a `with distribution_strategy.scope():`.
    """
    return get_distribution_strategy() is not _get_default_distribution_strategy()
  
  
  # ------------------------------------------------------------------------------
  # Defaults that are used when no distribution strategy is explicitly created.
  # We create them lazily in a function so that we can workaround the circular
  # dependency on distribute_lib. See lazy loader at the top of this file.
  
  _defaults = {
      "distribution_strategy": None,
      "tower_context": None,
      "tower_mode": None
  }
  
  
  def _get_default_distribution_strategy():
    if _defaults["distribution_strategy"] is None:
      _defaults["distribution_strategy"] = (
          distribute_lib._DefaultDistributionStrategy())  # pylint: disable=protected-access
    return _defaults["distribution_strategy"]
  
  
  def _get_default_tower_context():
    if _defaults["tower_context"] is None:
      _defaults["tower_context"] = distribute_lib.TowerContext(
          _get_default_distribution_strategy(), tower_id=0)
    return _defaults["tower_context"]
  
  
  def _get_default_tower_mode():
    if _defaults["tower_mode"] is None:
      _defaults["tower_mode"] = _DefaultTowerThreadMode()
    return _defaults["tower_mode"]
  