docs: def cache(horizon):
      """
      Put a wrapper around a model function
  
      Decorators with parameters are tricky, you have to
      essentially create a decorator that returns a decorator,
      which itself then returns the function wrapper.
  
      Parameters
      ----------
      horizon: string
          - 'step' means cache just until the next timestep
          - 'run' means cache until the next initialization of the model
  
      Returns
      -------
      new_func: decorated function
          function wrapping the original function, handling caching
  
      """
  
      def cache_step(func):
          """ Decorator for caching at a step level"""
  
          @wraps(func)
          def cached(*args):
              """Step wise cache function"""
              try:  # fails if cache is out of date or not instantiated
                  data = func.__globals__['__data']
                  assert cached.cache_t == data['time']()
                  assert hasattr(cached, 'cache_val')
                  assert cached.cache_val is not None
              except (AssertionError, AttributeError):
                  cached.cache_val = func(*args)
                  data = func.__globals__['__data']
                  cached.cache_t = data['time']()
              return cached.cache_val
  
          return cached
  
      def cache_run(func):
          """ Decorator for caching at  the run level"""
  
          @wraps(func)
          def cached(*args):
              """Run wise cache function"""
              try:  # fails if cache is not instantiated
                  return cached.cache_val
              except AttributeError:
                  cached.cache_val = func(*args)
                  return cached.cache_val
  
          return cached
  
      if horizon == 'step':
          return cache_step
  
      elif horizon == 'run':
          return cache_run
  
      else:
          raise (AttributeError('Bad horizon for cache decorator'))