text: r"""
  Module defining Pyclaw geometry objects.
  """
  
  from __future__ import absolute_import
  from __future__ import print_function
  import numpy as np
  
  import warnings
  import six
  from six.moves import range
  from six.moves import zip
  deprec_message = "'edges' has been deprecated; please use 'nodes' instead."
  # ============================================================================
  #  Default function definitions
  # ============================================================================
  
  # Default mapc2p functions
  def identity_map_1d(x):
      return x,
  
  def identity_map_2d(x,y):
      return x,y
  
  def identity_map_3d(x,y,z):
      return x,y,z
  
  identity_map={'1': identity_map_1d,
                '2': identity_map_2d,
                '3': identity_map_3d}
  
  class Grid(object):
      r"""
      Representation of a single grid.
  
      :Dimension information:
  
          Each dimension has an associated name with it that can be accessed via
          that name such as ``grid.x.num_cells`` which would access the x dimension's
          number of cells.
  
      :Properties:
  
          If the requested property has multiple values, a list will be returned
          with the corresponding property belonging to the dimensions in order.
  
      :Initialization:
  
          Input:
           - *dimensions* - (list of :class:`Dimension`) Dimensions that are to
             be associated with this grid
  
          Output:
           - (:class:`grid`) Initialized grid object
  
      A PyClaw grid is usually constructed from a tuple of PyClaw Dimension objects:
  
          >>> from clawpack.pyclaw.geometry import Dimension, Grid
          >>> x = Dimension(0.,1.,10,name='x')
          >>> y = Dimension(-1.,1.,25,name='y')
          >>> grid = Grid((x,y))
          >>> print(grid)
          2-dimensional domain (x,y)
          No mapping
          Extent:  [0.0, 1.0] x [-1.0, 1.0]
          Cells:  10 x 25
  
      We can query various properties of the grid:
  
          >>> grid.num_dim
          2
          >>> grid.num_cells
          [10, 25]
          >>> grid.lower
          [0.0, -1.0]
          >>> grid.delta # Returns [dx, dy]
          [0.1, 0.08]
  
      A grid can be extended to higher dimensions using the add_dimension() method:
  
          >>> z=Dimension(-2.0,2.0,21,name='z')
          >>> grid.add_dimension(z)
          >>> grid.num_dim
          3
          >>> grid.num_cells
          [10, 25, 21]
  
      Coordinates
      ===========
      We can get the x, y, and z-coordinate arrays of cell nodes and centers from the grid.
      Properties beginning with 'c' refer to the computational (unmapped) domain, while
      properties beginning with 'p' refer to the physical (mapped) domain.  For grids with
      no mapping, the two are identical.  Also note the difference between 'center' and
      'centers'.
  
          >>> import numpy as np
          >>> np.set_printoptions(precision=2)  # avoid doctest issues with roundoff
          >>> grid.c_center([1,2,3])
          array([ 0.15, -0.8 , -1.33])
          >>> grid.p_nodes[0][0,0,0]
          0.0
          >>> grid.p_nodes[1][0,0,0]
          -1.0
          >>> grid.p_nodes[2][0,0,0]
          -2.0
  
      It's also possible to get coordinates for ghost cell arrays:
  
          >>> x = Dimension(0.,1.,5,name='x')
          >>> grid1d = Grid([x])
          >>> grid1d.c_centers
          [array([0.1, 0.3, 0.5, 0.7, 0.9])]
          >>> grid1d.c_centers_with_ghost(2)
          [array([-0.3, -0.1,  0.1,  0.3,  0.5,  0.7,  0.9,  1.1,  1.3])]
  
      Mappings
      ========
      A grid mapping can be used to solve in a domain that is not rectangular,
      or to adjust the local spacing of grid cells.  For instance, we can
      use smaller cells on the left and larger cells on the right by doing:
  
          >>> double = lambda xarr : np.array([x**2 for x in xarr])
          >>> grid1d.mapc2p = double
          >>> grid1d.p_centers
          array([0.01, 0.09, 0.25, 0.49, 0.81])
  
      Note that the 'nodes' (or nodes) of the mapped grid are the mapped values
      of the computational nodes.  In general, they are not the midpoints between
      mapped centers:
  
          >>> grid1d.p_nodes
          array([0.  , 0.04, 0.16, 0.36, 0.64, 1.  ])
      """
  
      def __getattr__(self,key):
          # Provide dimension attribute lists when requested from Grid object.
          # Note that this only gets called when one requests an attribute
          # that the grid itself doesn't possess.
          if key in ['num_cells','lower','upper','delta','units','centers','nodes',
                      'on_lower_boundary','on_upper_boundary']:
              return self.get_dim_attribute(key)
          else:
              raise AttributeError("'Grid' object has no attribute '"+key+"'")
  
      # ========== Property Definitions ========================================
      @property
      def num_dim(self):
          r"""(int) - Number of dimensions"""
          return len(self._dimensions)
      @property
      def dimensions(self):
          r"""(list) - List of :class:`Dimension` objects defining the
                  grid's extent and resolution"""
          return [getattr(self,name) for name in self._dimensions]
      @property
      def c_centers(self):
          r"""(list of ndarray(...)) - List containing the arrays locating
                    the computational locations of cell centers, see
                    :meth:`_compute_c_centers` for more info."""
          self._compute_c_centers()
          return self._c_centers
      @property
      def c_nodes(self):
          r"""(list of ndarray(...)) - List containing the arrays locating
                    the computational locations of cell nodes, see
                    :meth:`_compute_c_nodes` for more info."""
          self._compute_c_nodes()
          return self._c_nodes
      @property
      def p_centers(self):
          r"""(list of ndarray(...)) - List containing the arrays locating
                    the physical locations of cell centers, see
                    :meth:`_compute_p_centers` for more info."""
          self._compute_p_centers()
          return self._p_centers
      @property
      def p_nodes(self):
          r"""(list of ndarray(...)) - List containing the arrays locating
                    the physical locations of cell nodes, see
                    :meth:`_compute_p_nodes` for more info."""
          self._compute_p_nodes()
          return self._p_nodes
      @property
      def mapc2p(self):
          return self._mapc2p
      @mapc2p.setter
      def mapc2p(self,mapc2p):
          self._mapc2p = mapc2p
          self._clear_cached_values()
  
  
      # ========== Class Methods ===============================================
      def __init__(self,dimensions):
          r"""
          Instantiate a Grid object
  
          See :class:`Grid` for more info.
          """
  
          # ========== Attribute Definitions ===================================
          r"""(func) - Coordinate mapping function"""
          self.gauges = []
          r"""(list) - List of gauges' indices to be filled by add_gauges
          method.
          """
          self.gauge_file_names  = []
          r"""(list) - List of file names to write gauge values to"""
          self.gauge_files = []
          r"""(list) - List of file objects to write gauge values to"""
          self.gauge_dir_name = '_gauges'
          r"""(string) - Name of the output directory for gauges. If the
          `Controller` class is used to run the application, this directory by
          default will be created under the `Controller` `outdir` directory.
          """
          self._p_centers = None
          self._p_nodes = None
          self._c_centers = None
          self._c_nodes = None
  
          # Dimension parsing
          if isinstance(dimensions,Dimension):
              dimensions = [dimensions]
          self._dimensions = []
          for dim in dimensions:
              self.add_dimension(dim)
  
          super(Grid,self).__init__()
  
      def _clear_cached_values(self):
          self._p_centers = None
          self._p_nodes = None
          self._c_centers = None
          self._c_nodes = None
  
      # ========== Dimension Manipulation ======================================
      def add_dimension(self,dimension):
          r"""
          Add the specified dimension to this patch
  
          :Input:
           - *dimension* - (:class:`Dimension`) Dimension to be added
          """
  
          # Add dimension to name list and as an attribute
          if dimension.name in self._dimensions:
              raise Exception('Unable to add dimension. A dimension'+
                              ' of the same name: {name}, already exists.'
                              .format(name=dimension.name))
  
          self._dimensions.append(dimension.name)
          setattr(self,dimension.name,dimension)
          self._clear_cached_values()
          # Reset mapping as it presumably makes no sense now
          self.mapc2p = identity_map[str(self.num_dim)]
  
      def get_dim_attribute(self,attr):
          r"""
          Returns a tuple of all dimensions' attribute attr
          """
          return [getattr(dim,attr) for dim in self.dimensions]
  
      def __copy__(self):
          return self.__class__(self)
  
      def __str__(self):
          output = "%s-dimensional domain " % str(self.num_dim)
          output += "("+",".join([dim.name for dim in self.dimensions])+")\n"
          if self.mapc2p in list(identity_map.values()):
              output += "No mapping\n"
              output += "Extent:  "
          else:
              output += "Mapping function: "+self.mapc2p.__name__+"\n"
              output += "Computational domain: "
          output += " x ".join(["[{:.2}, {:.2}]".format(dim.lower, dim.upper)
                               for dim in self.dimensions])
          output += "\n"
          output += "Cells:  "
          output += " x ".join(["{}".format(dim.num_cells) for dim in self.dimensions])
          return output
  
      # ========== Coordinates =============================================
      def _compute_c_centers(self, recompute=False):
          r"""Calculate the coordinates of the centers in the computational domain.
  
          :Input:
           - *recompute* - (bool) Whether to force a recompute of the arrays
          """
          if recompute or (self._c_centers is None) or \
             any([c is None for c in self.get_dim_attribute('_centers')]):
              index = np.indices(self.num_cells)
              self._c_centers = []
              for i,center_array in enumerate(self.get_dim_attribute('centers')):
                  self._c_centers.append(center_array[index[i,...]])
  
      def _compute_c_nodes(self, recompute=False):
          r"""Calculate the coordinates of the nodes in the computational domain.
  
          :Input:
           - *recompute* - (bool) Whether to force a recompute of the arrays
          """
          if recompute or (self._c_nodes is None) or \
             any([c is None for c in self.get_dim_attribute('_nodes')]):
              index = np.indices(n+1 for n in self.num_cells)
              self._c_nodes = []
              for i,edge_array in enumerate(self.get_dim_attribute('nodes')):
                  self._c_nodes.append(edge_array[index[i,...]])
  
      def _compute_p_centers(self, recompute=False):
          r"""Calculate the coordinates of the centers in the physical domain.
  
          :Input:
           - *recompute* - (bool) Whether to force a recompute of the arrays
          """
          if recompute or (self._p_centers is None) or \
             any([c is None for c in self.get_dim_attribute('_centers')]):
              self._compute_c_centers(recompute=recompute)
              self._p_centers = self.mapc2p(*self._c_centers)
  
      def _compute_p_nodes(self, recompute=False):
          r"""Calculate the coordinates of the nodes (corners) in the physical domain.
  
          :Input:
           - *recompute* - (bool) Whether to force a recompute of the arrays
          """
          if recompute or (self._p_nodes is None) or \
             any([c is None for c in self.get_dim_attribute('_nodes')]):
              self._compute_c_nodes(recompute=recompute)
              self._p_nodes = self.mapc2p(*self._c_nodes)
  
      def c_center(self,ind):
          r"""Compute center of computational cell with index ind."""
  
          index = [np.array(i) for i in ind]
          return np.array([self.c_centers[i][index] for i in range(self.num_dim)])
  
      def p_center(self,ind):
          r"""Compute center of physical cell with index ind."""
          return self.mapc2p(*self.c_center(ind))
  
      def c_centers_with_ghost(self, num_ghost):
          r"""
          Calculate the coordinates of the cell centers, including
          ghost cells, in the computational domain.
  
          :Input:
           - *num_ghost* - (int) Number of ghost cell layers
          """
          index = np.indices(n+2*num_ghost for n in self.num_cells)
          centers = []
          for i,dim in enumerate(self.dimensions):
              center_array = dim.centers_with_ghost(num_ghost)
              centers.append(center_array[index[i,...]])
          return centers
  
      def c_nodes_with_ghost(self, num_ghost):
          r"""
          Calculate the coordinates of the cell nodes (corners), including
          ghost cells, in the computational domain.
  
          :Input:
           - *num_ghost* - (int) Number of ghost cell layers
          """
          index = np.indices(n+2*num_ghost+1 for n in self.num_cells)
          nodes = []
          for i,dim in enumerate(self.dimensions):
              edge_array = dim.nodes_with_ghost(num_ghost)
              nodes.append(edge_array[index[i,...]])
          return nodes
  
      def p_centers_with_ghost(self,num_ghost):
          return self.mapc2p(*self.c_centers_with_ghost(num_ghost))
  
      def p_nodes_with_ghost(self,num_ghost):
          return self.mapc2p(*self.c_nodes_with_ghost(num_ghost))
  
      # ========================================================================
      # Edges: deprecated; will be removed in 6.0
      @property
      def c_edges(self):
          warnings.warn(deprec_message)
          return self.c_nodes
      @property
      def p_edges(self):
          warnings.warn(deprec_message)
          return self.p_nodes
      def p_edges_with_ghost(self,num_ghost):
          warnings.warn(deprec_message)
          return self.p_nodes_with_ghost(num_ghost)
      def c_edges_with_ghost(self, num_ghost):
          warnings.warn(deprec_message)
          return self.c_nodes_with_ghost(num_ghost)
      # ========================================================================
  
      # ========================================================================
      #  Gauges
      # ========================================================================
      def add_gauges(self,gauge_coords):
          r"""
          Determine the cell indices of each gauge and make a list of all gauges
          with their cell indices.
          """
          for gauge in gauge_coords:
              # Check if gauge belongs to this grid:
              if all(self.lower[n]<=gauge[n]<self.upper[n] for n in range(self.num_dim)):
                  # Set indices relative to this grid
                  gauge_index = [int(round((gauge[n]-self.lower[n])/self.delta[n]))
                                 for n in range(self.num_dim)]
                  gauge_file_name = 'gauge'+'_'.join(str(coord) for coord in gauge)+'.txt'
                  self.gauge_file_names.append(gauge_file_name)
                  self.gauges.append(gauge_index)
  
      def setup_gauge_files(self,outdir):
          r"""
          Creates and opens file objects for gauges.
          """
          import os
          gauge_path = os.path.join(outdir,self.gauge_dir_name)
          if not os.path.exists(gauge_path):
              try:
                  os.makedirs(gauge_path)
              except OSError:
                  print("gauge directory already exists, ignoring")
  
          for gauge in self.gauge_file_names:
              gauge_file = os.path.join(gauge_path,gauge)
              if os.path.isfile(gauge_file):
                  os.remove(gauge_file)
              self.gauge_files.append(open(gauge_file,'a'))
  
      def plot(self,num_ghost=0,mapped=True,mark_nodes=False,mark_centers=False):
          r"""Make a plot of the grid.
  
          By default the plot uses the mapping
          grid.mapc2p and does not show any ghost cells.  This can be modified
          via the arguments `mapped` and `num_ghost`.
  
          Returns a handle to the plot axis object.
          """
          import matplotlib.pyplot as plt
          if self.num_dim == 2:
              fig, ax = plt.subplots(1,1)
              if num_ghost>0:
                  if mapped:
                      xe, ye = self.p_nodes_with_ghost(num_ghost)
                  else:
                      xe, ye = self.c_nodes_with_ghost(num_ghost)
                  p = ax.pcolormesh(xe,ye,0*xe,edgecolors='k',cmap='bwr',alpha=0.2)
                  p.set_clim(-1,1)
              if mapped:
                  xe, ye = self.p_nodes
                  xc, yc = self.p_centers
              else:
                  xe, ye = self.c_nodes
                  xc, yc = self.c_centers
              p = ax.pcolormesh(xe,ye,0*xe,edgecolors='k',cmap='bwr')
              p.set_clim(-1,1)
              if mark_nodes:
                  ax.plot(xe,ye,'or')
              if mark_centers:
                  ax.plot(xc,yc,'ob')
              ax.axis('equal')
              ax.set_xlabel(self.dimensions[0].name)
              ax.set_ylabel(self.dimensions[1].name)
              return ax
          else:
              raise Exception('Grid plotting implemented for 2D grids only.')
  
      def _check_validity(self):
          for dim in self.dimensions:
              dim._check_validity()
          assert type(self.num_cells) is int, 'Dimension.num_cells must be an integer'
          assert type(self.lower) is float, 'Dimension.lower must be a float'
          assert type(self.upper) is float, 'Dimension.upper must be a float'
          assert self.num_cells>0, 'Dimension.num_cells must be positive'
          assert self.upper > self.lower, 'Dimension.upper must be greater than lower'
  
  
  # ============================================================================
  #  Dimension Object
  # ============================================================================
  class Dimension(object):
      r"""
      Basic class representing a dimension of a Patch object
  
      :Initialization:
  
      Required arguments, in order:
       - *lower* - (float) Lower extent of dimension
       - *upper* - (float) Upper extent of dimension
       - *num_cells* - (int) Number of cells
  
      Optional (keyword) arguments:
       - *name* - (string) string Name of dimension
       - *units* - (string) Type of units, used for informational purposes only
  
      Output:
       - (:class:`Dimension`) - Initialized Dimension object
  
      Example:
  
      >>> from clawpack.pyclaw.geometry import Dimension
      >>> x = Dimension(0.,1.,100,name='x')
      >>> print(x)
      Dimension x:  (num_cells,delta,[lower,upper]) = (100,0.01,[0.0,1.0])
      >>> x.name
      'x'
      >>> x.num_cells
      100
      >>> x.delta
      0.01
      >>> x.nodes[0]
      0.0
      >>> x.nodes[1]
      0.01
      >>> x.nodes[-1]
      1.0
      >>> x.centers[-1]
      0.995
      >>> len(x.centers)
      100
      >>> len(x.nodes)
      101
      """
  
      @property
      def delta(self):
          r"""(float) - Size of an individual, computational cell"""
          return (self.upper-self.lower) / float(self.num_cells)
  
      # ========== Edges: deprecated; will be removed in 6.0 =======
      @property
      def edges(self):
          warnings.warn(deprec_message)
          return self.nodes
  
      def edges_with_ghost(self,num_ghost):
          warnings.warn(deprec_message)
          return self.nodes_with_ghost(num_ghost)
      # ========================================================================
  
      # ========== Centers and nodes ========================================
      @property
      def nodes(self):
          r"""(ndarrary(:)) - Location of all cell edge coordinates
          for this dimension"""
          if self._nodes is None:
              self._nodes = np.empty(self.num_cells+1)
              for i in range(0,self.num_cells+1):
                  self._nodes[i] = self.lower + i*self.delta
          return self._nodes
      @property
      def centers(self):
          r"""(ndarrary(:)) - Location of all cell center coordinates
          for this dimension"""
          if self._centers is None:
              self._centers = np.empty(self.num_cells)
              for i in range(0,self.num_cells):
                  self._centers[i] = self.lower + (i+0.5)*self.delta
          return self._centers
      @property
      def lower(self):
          return self._lower
      @lower.setter
      def lower(self,lower):
          self._lower = float(lower)
          self._centers = None  # Reset cached arrays
          self._nodes = None
          self._check_validity()
      @property
      def upper(self):
          return self._upper
      @upper.setter
      def upper(self,upper):
          self._upper = float(upper)
          self._centers = None  # Reset cached arrays
          self._nodes = None
          self._check_validity()
      @property
      def num_cells(self):
          return self._num_cells
      @num_cells.setter
      def num_cells(self,num_cells):
          self._num_cells = int(num_cells)
          self._centers = None  # Reset cached arrays
          self._nodes = None
          self._check_validity()
  
      def centers_with_ghost(self,num_ghost):
          r"""(ndarrary(:)) - Location of all cell center coordinates
          for this dimension, including centers of ghost cells."""
          centers = self.centers
          pre = self.lower+(np.arange(-num_ghost,0)+0.5)*self.delta
          post = self.upper + self.delta * (np.arange(num_ghost) + 0.5)
          return np.hstack((pre,centers,post))
  
      def nodes_with_ghost(self,num_ghost):
          r"""(ndarrary(:)) - Location of all edge coordinates
          for this dimension, including nodes of ghost cells."""
          nodes   = self.nodes
          pre  = np.linspace(self.lower-num_ghost*self.delta,self.lower-self.delta,num_ghost)
          post = np.linspace(self.upper+self.delta, self.upper+num_ghost*self.delta,num_ghost)
          return np.hstack((pre,nodes,post))
  
      def __init__(self, lower, upper, num_cells, name='x',
                   on_lower_boundary=None,on_upper_boundary=None, units=None):
          r"""
          Create a Dimension object.
  
          See :class:`Dimension` for full documentation
          """
          if isinstance(lower,six.string_types):
              raise Exception('Passing dimension name as first argument is deprecated. \
                               Pass it as a keyword argument instead.')
  
          self._nodes = None
          self._centers = None
          self._centers_with_ghost = None
          self._nodes_with_ghost = None
  
          self._lower = float(lower)
          self._upper = float(upper)
          self._num_cells = int(num_cells)
          self.name = name
          self.on_lower_boundary = on_lower_boundary
          self.on_upper_boundary = on_upper_boundary
          self.units = units
  
          self._check_validity()
  
      def _check_validity(self):
          assert isinstance(self.num_cells,int), 'Dimension.num_cells must be an integer; got %s' % type(self.num_cells)
          assert isinstance(self.lower,float), 'Dimension.lower must be a float'
          assert isinstance(self.upper,float), 'Dimension.upper must be a float'
          assert self.num_cells>0, 'Dimension.num_cells must be positive'
          assert self.upper > self.lower, 'Dimension.upper must be greater than lower'
  
      def __str__(self):
          output = "Dimension %s" % self.name
          if self.units:
              output += " (%s)" % self.units
          output += ":  (num_cells,delta,[lower,upper]) = (%s,%s,[%s,%s])" \
              % (self.num_cells,self.delta,self.lower,self.upper)
          return output
  
      def __len__(self):
          return self.num_cells
  
  
  # ============================================================================
  #  Pyclaw Patch object definition
  # ============================================================================
  class Patch(object):
      """
      :Global Patch information:
  
          Each patch has a value for :attr:`level` and :attr:`patch_index`.
      """
      # Global properties
      @property
      def num_cells_global(self):
          r"""(list) - List of the number of cells in each dimension"""
          return self.get_dim_attribute('num_cells')
      @property
      def lower_global(self):
          r"""(list) - Lower coordinate extents of each dimension"""
          return self.get_dim_attribute('lower')
      @property
      def upper_global(self):
          r"""(list) - Upper coordinate extends of each dimension"""
          return self.get_dim_attribute('upper')
      @property
      def num_dim(self):
          r"""(int) - Number of dimensions"""
          return len(self._dimensions)
      @property
      def dimensions(self):
          r"""(list) - List of :class:`Dimension` objects defining the
                  grid's extent and resolution"""
          return [getattr(self,name) for name in self._dimensions]
      @property
      def delta(self):
          r"""(list) - List of computational cell widths"""
          return self.get_dim_attribute('delta')
      @property
      def name(self):
          r"""(list) - List of names of each dimension"""
          return self._dimensions
  
      def __init__(self,dimensions):
          self.level = 1
          r"""(int) - AMR level this patch belongs to, ``default = 1``"""
          self.patch_index = 1
          r"""(int) - Patch number of current patch, ``default = 0``"""
  
          if isinstance(dimensions,Dimension):
              dimensions = [dimensions]
          self._dimensions = []
          for dim in dimensions:
              dim.on_lower_boundary = True
              dim.on_upper_boundary = True
              self.add_dimension(dim)
  
          self.grid = Grid(dimensions)
  
          super(Patch,self).__init__()
  
      def add_dimension(self,dimension):
          r"""
          Add the specified dimension to this patch
  
          :Input:
           - *dimension* - (:class:`Dimension`) Dimension to be added
          """
  
          # Add dimension to name list and as an attribute
          if dimension.name in self._dimensions:
              raise Exception('Unable to add dimension. A dimension'+
                              ' of the same name: {name}, already exists.'
                              .format(name=dimension.name))
  
          self._dimensions.append(dimension.name)
          setattr(self,dimension.name,dimension)
  
      def get_dim_attribute(self,attr):
          r"""
          Returns a tuple of all dimensions' attribute attr
          """
          return [getattr(getattr(self,name),attr) for name in self._dimensions]
  
      def __deepcopy__(self,memo={}):
          import copy
          result = self.__class__(copy.deepcopy(self.dimensions))
          result.__init__(copy.deepcopy(self.dimensions))
          result.grid.mapc2p = self.grid.mapc2p
  
          for attr in ('level','patch_index'):
              setattr(result,attr,copy.deepcopy(getattr(self,attr)))
  
          return result
  
      def __str__(self):
          output = "Patch %s:\n" % self.patch_index
          output += '\n'.join((str(getattr(self,dim)) for dim in self._dimensions))
          return output
  
  # ============================================================================
  #  Pyclaw Domain object definition
  # ============================================================================
  class Domain(object):
      r"""
      A Domain is a list of Patches.
  
      A Domain may be initialized in the following ways:
  
          1. Using 3 arguments, which are in order
              - A list of the lower boundaries in each dimension
              - A list of the upper boundaries in each dimension
              - A list of the number of cells to be used in each dimension
  
          2. Using a single argument, which is
              - A list of dimensions; or
              - A list of patches.
  
      :Examples:
  
          >>> from clawpack import pyclaw
          >>> domain = pyclaw.Domain( (0.,0.), (1.,1.), (100,100))
          >>> print(domain.num_dim)
          2
          >>> print(domain.grid.num_cells)
          [100, 100]
      """
      @property
      def num_dim(self):
          r"""(int) - :attr:`Patch.num_dim` of base patch"""
          return self._get_base_patch_attribute('num_dim')
      @property
      def patch(self):
          r"""(:class:`Patch`) - First patch is returned"""
          return self.patches[0]
      @property
      def grid(self):
          r"""(list) - :attr:`Patch.grid` of base patch"""
          return self._get_base_patch_attribute('grid')
  
      def __init__(self,*arg):
          if len(arg)>1:
              lower = arg[0]
              upper = arg[1]
              n     = arg[2]
              dims = []
              names = ['x','y','z']
              names = names[:len(n)+1]
              for low,up,nn,name in zip(lower,upper,n,names):
                  dims.append(Dimension(low,up,nn,name=name))
              self.patches = [Patch(dims)]
          else:
              geom = arg[0]
              if not isinstance(geom,list) and not isinstance(geom,tuple):
                  geom = [geom]
              if isinstance(geom[0],Patch):
                  self.patches = geom
              elif isinstance(geom[0],Dimension):
                  self.patches = [Patch(geom)]
  
      def _get_base_patch_attribute(self, name):
          r"""
          Return base patch attribute name
  
          :Output:
           - (id) - Value of attribute from ``self.patches[0]``
          """
          return getattr(self.patches[0],name)
  
      def __deepcopy__(self,memo={}):
          import copy
          result = self.__class__(copy.deepcopy(self.patches))
          result.__init__(copy.deepcopy(self.patches))
  
          return result
  
  
  if __name__ == "__main__":
      import doctest
      doctest.testmod()
  