code: def lnlike(self, p, use_cache=True):
          
          if use_cache and self._cache_key is not None and np.all(p==self._cache_key):
              return self._cache_val
          self._cache_key = p
  
          pardict = self.p2pardict(p)
  
          # lnlike from photometry
          lnl = 0
          for n in self:
              if n is not self:
                  lnl += n.lnlike(pardict, use_cache=use_cache)
              if not np.isfinite(lnl):
                  self._cache_val = -np.inf
                  return -np.inf
  
          # lnlike from spectroscopy
          for l in self.spectroscopy:
              for prop,(val,err) in self.spectroscopy[l].items():
                  mod = self.get_leaf(l).evaluate(pardict[l], prop)
                  lnl += -0.5*(val - mod)**2/err**2
              if not np.isfinite(lnl):
                  self._cache_val = -np.inf
                  return -np.inf
  
          # enforce limits
          for l in self.limits:
              for prop,(vmin,vmax) in self.limits[l].items():
                  mod = self.get_leaf(l).evaluate(pardict[l], prop)
                  if mod < vmin or mod > vmax or not np.isfinite(mod):
                      self._cache_val = -np.inf
                      return -np.inf
  
          # lnlike from parallax
          for s,(val,err) in self.parallax.items():
              dist = pardict['{}_0'.format(s)][3]
              mod = 1./dist * 1000.
              lnl += -0.5*(val-mod)**2/err**2
  
          if not np.isfinite(lnl):
              self._cache_val = -np.inf
              return -np.inf
  
          self._cache_val = lnl
          return lnl
docs: takes parameter vector, constructs pardict, returns sum of lnlikes of non-leaf nodes