# -*- coding: cp1252 -*-
from copy import copy
import realSoma
from math import sqrt, sin, cos, pi
from misc import *
from params import *

import params

class Section:
        def __init__(self):
                self.index = -1
                self.points = []
                self.sons = []
                self.parent = None
                
class Neuron:
        def __init__(self): 
                self.dend = []
                self.apic = None
                self.tuft = []
                self.soma = None
class Extreme:
        SOMA = -1; DENDRITE = 0; APICAL = 1; TUFT = 2
        def __init__(self):
                self.sec = None
                self.phi = 0.
                self.theta = 0.
                self.dist = 0.
                self.limit = 0.
                self.extr_type = Extreme.SOMA
                self.can_bifurc = False
                self.bif_dist = 0.
                self.bif_limit = 0.
                self.depth = 0
                self.basePhi = 0.
                self.baseTheta = 0.
                



# find intersection with Ellipsoid
def EllipsoidLineIntersec(u, p, axis):
  return Ellipsoid(params.bulbCenter,axis).intersection(p,u)

# Ellipsoidd with intersection
def EllipsoidIntersec(p, axis):
  e = Ellipsoid(params.bulbCenter, axis)
  p = e.project(p)
  lamb, phi = e.toElliptical(p)[1:]
  return p, pi*0.5-lamb, phi
  



new_version = False

if new_version:

        
  ''' init the soma position '''
  def gen_soma_pos(r, glompos, mgid):
    nmg=params.Nmitral_per_glom
    glomproj, phi_base, theta_base = EllipsoidIntersec(glompos, params.somaAxis[0])
    phi_base = 0.5*pi - phi_base
    phi_phase = 2*pi/nmg*(mgid%nmg)
    
    # generate a new position
    def gen_pos():
      phi = mgid%nmg * 2*pi / nmg + r.uniform(-0.4*pi/nmg, 0.4*pi/nmg) + phi_phase
      theta = r.normal(0.5*pi, 0.05*(pi*0.5)**2)
      rho = r.uniform(0.2, 1) * params.GLOM_DIST
      phi, theta = convert_direction(phi, theta, phi_base, theta_base)
      return Spherical.xyz(rho, phi, theta, glomproj)

    # check if the position is good
    def in_soma_zone(pos):
      soma_inf = Ellipsoid(bulbCenter, somaAxis[0])
      soma_sup = Ellipsoid(bulbCenter, somaAxis[1])
      return soma_inf.normalRadius(pos) > 1. and soma_sup.normalRadius(pos) < 1.

    while True:
      pos = gen_pos()
      if in_soma_zone(pos):
        break

    return pos

  ''' init the soma '''
  def mk_soma(mgid, nrn):
    sec = Section()
    glompos = params.glomRealCoords[int(mgid / params.Nmitral_per_glom)]
    r = params.ranstream(mgid, params.stream_soma)
    somapos = gen_soma_pos(r, glompos, mgid)

    import realSoma
    index = int(r.discunif(0, realSoma.N_SOMA-1))
    sec.points = realSoma.realSoma(index, somapos)
    nrn.soma = sec

    return somapos


  def can_change_type(extr, glompos):
    if extr.extr_type == Extreme.APICAL:
      return distance(extr.sec.points[-1], glompos) < params.GLOM_RADIUS
    return False

  ''' check the barrier '''
  def feasible(p, extr, nrn, glompos):
    if extr.extr_type == Extreme.APICAL:
      return distance(p, glompos) < distance(extr.sec.points[-1], glompos)
    elif extr.extr_type == Extreme.TUFT:
      d = distance(p, glompos)/params.GLOM_RADIUS
      return d <= 1 and d >= 0.75
    return True
    
  # noise
  def noise(r, b,c=0,d=0):
    return rLaplace(r, 0., b)

  # generate a walk, contains the growing rules.
  def genWalk(r, extr, glomPos):


          
          r = r[extr.extr_type]
          rho = WALK_RHO
          phi = extr.phi
          theta = extr.theta
          diam = 1.                        
          pts = extr.sec.points
          
          if extr.extr_type == Extreme.APICAL:
                  diam = APIC_DIAM
                  dphi = noise(r, params.NS_PHI_B)
                  dtheta = noise(r, params.NS_THETA_B)
                  phi += dphi
                  theta += dtheta
                  absphi, abstheta = convert_direction(phi, theta, extr.basePhi, extr.baseTheta)
                  p = Spherical.xyz(rho, absphi, abstheta, extr.sec.points[-1])
                  p += [ diam ]
                  return p, phi, theta
          elif extr.extr_type == Extreme.TUFT:
                  #def correctTuft(norm, p):
                  #        _rho, phi, theta = Spherical.to(getP(norm, versor(glomPos, p), p), extr.sec.points[-1])
                  #        return phi, theta
                  #
                  #
                  #_p = Spherical.xyz(rho, phi, theta, pts[-1])
                  #dglom = distance(_p, glomPos)
                  #if dglom > 0.9 * GLOM_RADIUS:
                  #        phi, theta = correctTuft(dglom - .9 * GLOM_RADIUS, _p)
                  #elif dglom < 0.6 * GLOM_RADIUS:
                  #        phi, theta = correctTuft(dglom - .6 * GLOM_RADIUS, _p)
                  ##
                  diam = TUFT_DIAM
                  dphi = noise(r, params.NS_PHI_B)
                  dtheta = noise(r, params.NS_THETA_B)
                  phi += dphi
                  theta += dtheta
                  absphi, abstheta = convert_direction(phi, theta, extr.basePhi, extr.baseTheta)
                  p = Spherical.xyz(rho, absphi, abstheta, extr.sec.points[-1])
                  p += [ diam ]
          elif extr.extr_type == Extreme.DENDRITE:
                  ## diam
                  diam = gen_dend_diam(extr.dist)
                  pts = extr.sec.points

                  # first curvature
                  #if len(pts) < 5 and extr.depth == 0:
                    #      theta += (pi / 3. - pi / 15) / 4
                  #        _phi, _theta = ConvertDirection(phi, theta, extr.basePhi, extr.baseTheta, True)
                   #       p = Spherical.xyz(rho, _phi, _theta, pts[-1])

                  
                  # simulate Ellipsoid pression
                  def EllipsoidPression(p, axis, up, k):
                          e = Ellipsoid(bulbCenter, axis)
                          h = e.normalRadius(p)

                          # check if the pression if from up or down to the surface
                          if up:
                                  h_check = h > 1.
                                  h_diff = h
                          else:
                                  h_check = h < 1.
                                  h_diff = 1. - h
                                  
                          q = None
                          if h_check:
                                  q, _lamb, _phi = EllipsoidIntersec(p, axis)
                          else:
                                  _h, _lamb, _phi = e.toElliptical(p)
                                  F = h_diff * k
                                  q = [ -F * sin(_lamb) * cos(_phi) + p[0], -F * cos(_lamb) * cos(_phi) + p[1], -F * sin(_phi) + p[2] ]
                                  
                          vNew = versor(q, pts[-1])
                          _p = getP(rho, vNew, pts[-1])
                          _rho, _phi, _theta = Spherical.to(_p, pts[-1])
                          return _p, _phi, _theta

                  # check
                  _phi, _theta = convert_direction(phi, theta, extr.basePhi, extr.baseTheta)
                  p = Spherical.xyz(rho, _phi, _theta, pts[-1])
                  if not inSomaZone(pts[-1]):
                          e = Ellipsoid(bulbCenter, bulbAxis)
                          if inSomaZone(p):
                                  p, _phi, _theta = EllipsoidPression(p, somaAxis[1], True, 0.)
                                  phi, theta = convert_direction(_phi, _theta, extr.basePhi, extr.baseTheta, True)
                          elif e.normalRadius(pts[-1]) < e.normalRadius(p):
                                  #p, _phi, _theta = EllipsoidPression(p, bulbAxis, True, .6)
                                  p, _phi, _theta = EllipsoidPression(p, bulbAxis, True, GROW_RESISTANCE)
                                  phi, theta = convert_direction(_phi, _theta, extr.basePhi, extr.baseTheta, True)

                  _phi += noise(r, NS_PHI_B, NS_PHI_MIN, NS_PHI_MAX)
                  _theta += noise(r, NS_THETA_B, NS_THETA_MIN, NS_THETA_MAX)
                  p = Spherical.xyz(rho, _phi, _theta, pts[-1])                
                  p.append(diam)
                  return p, phi, theta



                  
          # add the noise        
          phi += noise(r, NS_PHI_B, NS_PHI_MIN, NS_PHI_MAX)
          theta += noise(r, NS_THETA_B, NS_THETA_MIN, NS_THETA_MAX)
          p = Spherical.xyz(rho, phi, theta, extr.sec.points[-1])
          p.append(diam)                               
          return p, phi, theta

  def canDelete(extr, flag_fail):
    if extr.extr_type != Extreme.APICAL:
      return (((extr.dist >= extr.limit or abs(extr.dist - extr.limit) < WALK_RHO) and not extr.can_bifurc) or flag_fail)
    
    return False

  def canBifurcate(extr):
    if extr.extr_type == Extreme.DENDRITE:
      return extr.can_bifurc and (extr.bif_dist >= extr.bif_limit or abs(extr.bif_dist - extr.bif_limit) < WALK_RHO)
    
    return False


                                                                                       
  def updateExtr(extr, p, phi, theta):
          d = distance(p, extr.sec.points[-1])
          extr.dist += d
          extr.bif_dist += d
          extr.sec.points += [ p ]                
          extr.phi = phi
          extr.theta = theta
          
  def mkDendrite(r, depth, parent):
          sec = Section()
          sec.parent = parent
          if parent: parent.sons.append(sec)
          ##
          extr = Extreme()
          extr.extr_type = Extreme.DENDRITE
          extr.sec = sec
          
          # bifurcation decision
          extr.can_bifurc = depth < len(BIFURC_PROB) and r.uniform(0, 1) <= BIFURC_PROB[depth] and extr.dist < (DEND_LEN_MAX - DEND_LEN_MIN)
          extr.depth = depth
         #extr.can_bifurc = False
          if extr.can_bifurc:
                  if depth <= 0:
                          min_len = BIFURC_LEN_MIN_1
                          max_len = BIFURC_LEN_MAX_1
                          mu = BIFURC_LEN_MU_1
                  else:
                          min_len = BIFURC_LEN_MIN_2
                          max_len = BIFURC_LEN_MAX_2
                          mu = BIFURC_LEN_MU_2
                  bl = r.negexp(mu)
                  while bl < min_len or bl > max_len: bl = r.repick()
                  extr.bif_limit = bl
          else:
                  minLen = DEND_LEN_MIN
                  if extr.dist > 0: minLen = extr.dist + DEND_LEN_MIN
                  
                  dendLen = r.normal(DEND_LEN_MU, DEND_LEN_VAR)
                  while dendLen < minLen or dendLen > DEND_LEN_MAX: dendLen = r.repick()
                  extr.limit = dendLen
                  
          return sec, extr




  def mkApical(mid, somaPos, glomPos, nrn, extrLs):
          sec = Section()
          sec.points = [ copy(somaPos) ]
          sec.points[-1].append(APIC_DIAM)
          sec.parent = nrn.soma
          nrn.soma.sons.append(sec)
          nrn.apic = sec
          extr = Extreme()
          extr.sec = sec
          #rho, phi, theta = Spherical.to(glomPos, somaPos)
          extr.phi = 0; extr.theta = 0
          extr.extr_type = Extreme.APICAL
          extr.limit = APIC_LEN_MAX
          extrLs.append(extr)



  def mkTuft(r, extrLs, nrn, pos, glomPos,basePhi,baseTheta):
          # orientation respect glomerulus
          _rho, gl_phi, gl_theta = Spherical.to(glomPos, pos)
          
          # make tuft        
          TUFTS = int(r.discunif(N_MIN_TUFT, N_MAX_TUFT))
          for i in range(TUFTS):
                  sec = Section()
                  sec.index = i
                  sec.parent = nrn.apic
                  nrn.apic.sons.append(sec)
                  nrn.tuft.append(sec)
                  sec.points = [ copy(nrn.apic.points[-1]) ]
                  sec.points[-1][-1] = TUFT_DIAM
                  extr = Extreme()
                  extr.sec = sec
                  extr.phi, extr.theta = convert_direction(i * 2 * pi / TUFTS * (1 + 0.75 * r.uniform(-0.5 / TUFTS, 0.5 / TUFTS)), pi / 4, gl_phi, gl_theta)
                  extr.limit = r.uniform(TUFT_MIN_LEN, TUFT_MAX_LEN)
                  extr.extr_type = Extreme.TUFT
                  extrLs.append(extr)
                  extrLs[-1].basePhi=basePhi;extrLs[-1].baseTheta=baseTheta
          


  # change extremity, especially at the end of apical                                                             
  def change_type(r, extrLs, i, nrn, glomPos):
                                   
          mkTuft(r[Extreme.TUFT], extrLs, nrn, extrLs[i].sec.points[-1], glomPos,extrLs[i].basePhi,extrLs[i].baseTheta)
          


  # make a bifurcation
  def Bifurcate(r, extrLs, i, nrn):
          r = r[Extreme.DENDRITE]
          def get_phi():
                  phi = r.negexp(BIFURC_PHI_MU)
                  while phi < BIFURC_PHI_MIN or phi > BIFURC_PHI_MAX: phi = r.repick()
                  return phi

          
          sec1, extr1 = mkDendrite(r, extrLs[i].depth + 1, extrLs[i].sec)
          sec1.points = [ copy(extrLs[i].sec.points[-1]) ]
          sec1.index = len(nrn.dend)
          nrn.dend.append(sec1)
          
          extr1.phi = extrLs[i].phi + get_phi(); extr1.theta = extrLs[i].theta
          extr1.dist = extrLs[i].dist
          extr1.basePhi = extrLs[i].basePhi; extr1.baseTheta = extrLs[i].baseTheta
          extrLs.append(extr1)

          sec2, extr2 = mkDendrite(r, extrLs[i].depth + 1, extrLs[i].sec)
          sec2.points = [ copy(extrLs[i].sec.points[-1]) ]
          sec2.index = len(nrn.dend)
          nrn.dend.append(sec2)
          extr2.basePhi = extrLs[i].basePhi; extr2.baseTheta = extrLs[i].baseTheta
          extr2.phi = extrLs[i].phi - get_phi(); extr2.theta = extrLs[i].theta
          extr2.dist = extrLs[i].dist
          extrLs.append(extr2)
          

  def inSomaZone(p):
          somaInf = Ellipsoid(bulbCenter, somaAxis[0]); somaSup = Ellipsoid(bulbCenter, somaAxis[1])
          return somaInf.normalRadius(p) >= 1. and somaSup.normalRadius(p) < 1.


  # initialization of algorithm
  def initGrow(mid, noDend):
          
          r = [ ranstream(mid, stream_dend), ranstream(mid, stream_apic), ranstream(mid, stream_tuft) ]
          extrLs = [ ]
          nrn = Neuron()
          somaPos, glomPos = mkSoma(mid, nrn)
          
          mkApical(r[Extreme.APICAL], somaPos, glomPos, nrn, extrLs)

          # make dendrites.
          if not noDend:
                  # get basic inclination
                  somaLvl = Ellipsoid(bulbCenter, somaAxis[0])
                  h, phi_base, theta_base = somaLvl.toElliptical(somaPos)
                  theta_base = pi / 2 - theta_base; phi_base = pi / 2 - phi_base
                  _r = r[Extreme.DENDRITE]
                  
                  ###
                  DENDRITES = int(_r.negexp(N_MEAN_DEND - N_MIN_DEND)) + N_MIN_DEND
                  while DENDRITES > N_MAX_DEND: DENDRITES = int(_r.negexp(N_MEAN_DEND - N_MIN_DEND)) + N_MIN_DEND
                  nmg=params.Nmitral_per_glom
                  dphi=2*pi/DENDRITES
                  for i in range(DENDRITES):
                          phiPhase = i*dphi + 2*pi/nmg*(mid%nmg)
                          
                          sec, extr = mkDendrite(_r, 0, nrn.soma)
                          p = copy(somaPos); p.append(gen_dend_diam(0.))
                          sec.points = [ p ]
                          sec.index = i              
                          nrn.dend.append(sec)
                          
                          ## initial direction
                          phi = phiPhase + _r.uniform(-0.4,0.4)*dphi
                          
                          theta = pi / 3. # - _r.uniform(pi / 15, pi / 12)
                          newPhi, newTheta = convert_direction(phi, theta, phi_base, theta_base)
                          extr.phi = phi; extr.theta = theta
                          extr.basePhi = phi_base; extr.baseTheta = theta_base
                          extrLs.append(extr)
          return r, nrn, extrLs, glomPos


  
  def Grow(mid, noDend):
          r = nrn = extrLs = glomPos = None
          r, nrn, extrLs, glomPos = initGrow(mid, noDend) # initialization        
          
          it = 0
          while it < GROW_MAX_ITERATIONS and len(extrLs) > 0:
                  i = 0
                  while i < len(extrLs):
                          j = 0
                          while j < GROW_MAX_ATTEMPTS:
                                  p, phi, theta = genWalk(r, extrLs[i], glomPos)
                                  
                                  if feasible(p, extrLs[i], nrn, glomPos):
                                          updateExtr(extrLs[i], p, phi, theta)
                                          break
                                  j += 1
                                           
                          if canDelete(extrLs[i], j >= GROW_MAX_ATTEMPTS):
                                  if len(extrLs[i].sec.points) <= 1 and extrLs[i].extr_type == Extreme.DENDRITE:
                                          if extrLs[i].sec.index < (len(nrn.dend) - 1):
                                                  for q in range(extrLs[i].sec.index + 1, len(nrn.dend)):
                                                          nrn.dend[q].index -= 1
                                          del nrn.dend[extrLs[i].sec.index] 
                                  
                                  del extrLs[i]        
                          elif can_change_type(extrLs[i], glomPos):
                                  change_type(r, extrLs, i, nrn, glomPos)
                                  del extrLs[i]
                          elif canBifurcate(extrLs[i]):
                                 Bifurcate(r, extrLs, i, nrn)
                                 del extrLs[i]
                          else:
                                  i += 1
                  it += 1
                  
          return nrn

  def genMitral(mid): return Grow(mid, False)
  def genSomaApicalTuft(mid): return Grow(mid, True)

else:

  def inSomaZone(p):
          somaInf = Ellipsoid(bulbCenter, somaAxis[0]); somaSup = Ellipsoid(bulbCenter, somaAxis[1])
          return somaInf.normalRadius(p) >= 1. and somaSup.normalRadius(p) < 1.
      
  ''' init the soma position '''
  def gen_soma_pos(r, glompos, mgid):
    nmg=params.Nmitral_per_glom
    glomproj1, phi_base, theta_base = EllipsoidIntersec(glompos, params.somaAxis[0])
    glomproj2 = EllipsoidIntersec(glompos, params.somaAxis[1])[0]
    glomproj = centroid([ glomproj1, glomproj2 ])
    
    phi_base = 0.5*pi - phi_base
    
    # generate a new position
    def gen_pos():
      phi = 2*pi/nmg*(mgid%nmg) +2*pi/nmg*r.uniform(-0.4,0.4)
      
      theta = r.normal(0.5*pi,0.05*(0.5*pi)**2)
      rho = r.uniform(0.5, 1) * params.GLOM_DIST
      phi, theta = convert_direction(phi, theta, phi_base, theta_base)
      return Spherical.xyz(rho, phi, theta, glomproj)

    # check if the position is good
    def in_soma_zone(pos):
      soma_inf = Ellipsoid(bulbCenter, somaAxis[0])
      soma_sup = Ellipsoid(bulbCenter, somaAxis[1])
      return soma_inf.normalRadius(pos) > 1. and soma_sup.normalRadius(pos) < 1.

    while True:
      pos = gen_pos()
      if in_soma_zone(pos):
        break

    return pos

  ''' init the soma '''
  def mk_soma(mgid, nrn):
    sec = Section()
    glompos = params.glomRealCoords[int(mgid / params.Nmitral_per_glom)]
    r = params.ranstream(mgid, params.stream_soma)
    somapos = gen_soma_pos(r, glompos, mgid)

    import realSoma
    index = int(r.discunif(0, realSoma.N_SOMA-1))
    sec.points = realSoma.realSoma(index, somapos)
    nrn.soma = sec

    return somapos

  ''' check the barrier '''
  def feasible(p, extr, nrn, glompos):
    if extr.extr_type == Extreme.APICAL:
      return distance(p, glompos) < distance(extr.sec.points[-1], glompos)
    elif extr.extr_type == Extreme.TUFT:
      d = distance(p, glompos)/params.GLOM_RADIUS
      return d <= 1 and d >= 0.75
    return True

  # noise
  def noise(r, b, minval, maxval):
          ns = rLaplace(r, 0., b)
          while ns < minval or ns > maxval: ns = rLaplace(r, 0., b)
          return ns

  # generate a walk, contains the growing rules.
  def genWalk(r, extr, glomPos):


          
          r = r[extr.extr_type]
          rho = WALK_RHO; phi = extr.phi; theta = extr.theta; diam = 1.                        
          pts = extr.sec.points
          if extr.extr_type == Extreme.APICAL:
                  _rho, phi, theta = Spherical.to(glomPos, extr.sec.points[-1])
                  diam = APIC_DIAM                
          elif extr.extr_type == Extreme.TUFT:
                  def correctTuft(norm, p):
                          _rho, phi, theta = Spherical.to(getP(norm, versor(glomPos, p), p), extr.sec.points[-1])
                          return phi, theta
                  #
                  
                  _p = Spherical.xyz(rho, phi, theta, pts[-1])
                  dglom = distance(_p, glomPos)
                  if dglom > 0.9 * GLOM_RADIUS:
                          phi, theta = correctTuft(dglom - .9 * GLOM_RADIUS, _p)
                  elif dglom < 0.6 * GLOM_RADIUS:
                          phi, theta = correctTuft(dglom - .6 * GLOM_RADIUS, _p)
                  ##
                  diam = TUFT_DIAM
          elif extr.extr_type == Extreme.DENDRITE:
                  ## diam
                  diam = gen_dend_diam(extr.dist)
                  pts = extr.sec.points

                  # first curvature
                  #if len(pts) < 5 and extr.depth == 0:
                    #      theta += (pi / 3. - pi / 15) / 4
                  #        _phi, _theta = ConvertDirection(phi, theta, extr.basePhi, extr.baseTheta, True)
                   #       p = Spherical.xyz(rho, _phi, _theta, pts[-1])

                  
                  # simulate Ellipsoid pression
                  def EllipsoidPression(p, axis, up, k):
                          e = Ellipsoid(bulbCenter, axis)
                          h = e.normalRadius(p)

                          # check if the pression if from up or down to the surface
                          if up:
                                  h_check = h > 1.
                                  h_diff = h
                          else:
                                  h_check = h < 1.
                                  h_diff = 1. - h
                                  
                          q = None
                          if h_check:
                                  q, _lamb, _phi = EllipsoidIntersec(p, axis)
                          else:
                                  _h, _lamb, _phi = e.toElliptical(p)
                                  F = h_diff * k
                                  q = [ -F * sin(_lamb) * cos(_phi) + p[0], -F * cos(_lamb) * cos(_phi) + p[1], -F * sin(_phi) + p[2] ]
                                  
                          vNew = versor(q, pts[-1])
                          _p = getP(rho, vNew, pts[-1])
                          _rho, _phi, _theta = Spherical.to(_p, pts[-1])
                          return _p, _phi, _theta

                  # check
                  _phi, _theta = convert_direction(phi, theta, extr.basePhi, extr.baseTheta)
                  p = Spherical.xyz(rho, _phi, _theta, pts[-1])
                  if not inSomaZone(pts[-1]):
                          e = Ellipsoid(bulbCenter, bulbAxis)
                          if inSomaZone(p):
                                  p, _phi, _theta = EllipsoidPression(p, somaAxis[1], True, 0.)
                                  phi, theta = convert_direction(_phi, _theta, extr.basePhi, extr.baseTheta, True)
                          elif e.normalRadius(pts[-1]) < e.normalRadius(p):
                                  #p, _phi, _theta = EllipsoidPression(p, bulbAxis, True, .6)
                                  p, _phi, _theta = EllipsoidPression(p, bulbAxis, True, GROW_RESISTANCE)
                                  phi, theta = convert_direction(_phi, _theta, extr.basePhi, extr.baseTheta, True)

                  _phi += noise(r, NS_PHI_B, NS_PHI_MIN, NS_PHI_MAX)
                  _theta += noise(r, NS_THETA_B, NS_THETA_MIN, NS_THETA_MAX)
                  p = Spherical.xyz(rho, _phi, _theta, pts[-1])                
                  p.append(diam)
                  return p, phi, theta



                  
          # add the noise        
          phi += noise(r, NS_PHI_B, NS_PHI_MIN, NS_PHI_MAX)
          theta += noise(r, NS_THETA_B, NS_THETA_MIN, NS_THETA_MAX)
          p = Spherical.xyz(rho, phi, theta, extr.sec.points[-1])
          p.append(diam)                               
          return p, phi, theta

  def canDelete(extr, flag_fail):
    if extr.extr_type != Extreme.APICAL:
      return (((extr.dist >= extr.limit or abs(extr.dist - extr.limit) < WALK_RHO) and not extr.can_bifurc) or flag_fail)
    
    return False

  def canBifurcate(extr):
    if extr.extr_type == Extreme.DENDRITE:
      return extr.can_bifurc and (extr.bif_dist >= extr.bif_limit or abs(extr.bif_dist - extr.bif_limit) < WALK_RHO)
    
    return False

  def canChangeTypeOfExtr(extr, glomPos): return extr.extr_type == Extreme.APICAL and (extr.dist >= extr.limit or distance(extr.sec.points[-1], glomPos) < GLOM_RADIUS)
                                                                                       
  def updateExtr(extr, p, phi, theta):
          d = distance(p, extr.sec.points[-1])
          extr.dist += d
          extr.bif_dist += d
          extr.sec.points += [ p ]                
          extr.phi = phi
          extr.theta = theta
          
  def mkDendrite(r, depth, parent):
          sec = Section()
          sec.parent = parent
          if parent: parent.sons.append(sec)
          ##
          extr = Extreme()
          extr.extr_type = Extreme.DENDRITE
          extr.sec = sec
          
          # bifurcation decision
          extr.can_bifurc = depth < len(BIFURC_PROB) and r.uniform(0, 1) <= BIFURC_PROB[depth] and extr.dist < (DEND_LEN_MAX - DEND_LEN_MIN)
          extr.depth = depth
         #extr.can_bifurc = False
          if extr.can_bifurc:
                  if depth <= 0:
                          min_len = BIFURC_LEN_MIN_1
                          max_len = BIFURC_LEN_MAX_1
                          mu = BIFURC_LEN_MU_1
                  else:
                          min_len = BIFURC_LEN_MIN_2
                          max_len = BIFURC_LEN_MAX_2
                          mu = BIFURC_LEN_MU_2
                  bl = r.negexp(mu)
                  while bl < min_len or bl > max_len: bl = r.repick()
                  extr.bif_limit = bl
          else:
                  minLen = DEND_LEN_MIN
                  if extr.dist > 0: minLen = extr.dist + DEND_LEN_MIN
                  
                  dendLen = r.normal(DEND_LEN_MU, DEND_LEN_VAR)
                  while dendLen < minLen or dendLen > DEND_LEN_MAX: dendLen = r.repick()
                  extr.limit = dendLen
                  
          return sec, extr



  def mkApical(mid, somaPos, glomPos, nrn, extrLs):
          sec = Section()
          sec.points = [ copy(somaPos) ]
          sec.points[-1].append(APIC_DIAM)
          sec.parent = nrn.soma
          nrn.soma.sons.append(sec)
          nrn.apic = sec
          extr = Extreme()
          extr.sec = sec
          rho, phi, theta = Spherical.to(glomPos, somaPos)
          extr.phi = phi; extr.theta = theta
          extr.extr_type = Extreme.APICAL
          extr.limit = APIC_LEN_MAX
          extrLs.append(extr)



  def mkTuft(r, extrLs, nrn, pos, glomPos):
          # orientation respect glomerulus
          _rho, gl_phi, gl_theta = Spherical.to(glomPos, pos)
          
          # make tuft        
          TUFTS = int(r.discunif(N_MIN_TUFT, N_MAX_TUFT))
          for i in range(TUFTS):
                  sec = Section()
                  sec.index = i
                  sec.parent = nrn.apic
                  nrn.apic.sons.append(sec)
                  nrn.tuft.append(sec)
                  sec.points = [ copy(nrn.apic.points[-1]) ]
                  sec.points[-1][-1] = TUFT_DIAM
                  extr = Extreme()
                  extr.sec = sec
                  extr.phi, extr.theta = convert_direction(i * 2 * pi / TUFTS * (1 + 0.75 * r.uniform(-0.5 / TUFTS, 0.5 / TUFTS)), pi / 4, gl_phi, gl_theta)
                  extr.limit = r.uniform(TUFT_MIN_LEN, TUFT_MAX_LEN)
                  extr.extr_type = Extreme.TUFT
                  extrLs.append(extr)
          
          

  # change extremity, especially at the end of apical                                                             
  def changeTypeExtr(r, extrLs, i, nrn, glomPos):
          
          if distance(extrLs[i].sec.points[-1], glomPos) != GLOM_RADIUS:
                  pos = getP(distance(extrLs[i].sec.points[-1], glomPos) - GLOM_RADIUS,
                             versor(glomPos, extrLs[i].sec.points[-1]),
                             extrLs[i].sec.points[-1])
                  stretchSection(extrLs[i].sec.points, pos)
                  
                  
          mkTuft(r[Extreme.TUFT], extrLs, nrn, pos, glomPos)
          
                  
  # make a bifurcation
  def Bifurcate(r, extrLs, i, nrn):
          r = r[Extreme.DENDRITE]
          def get_phi():
                  phi = r.negexp(BIFURC_PHI_MU)
                  while phi < BIFURC_PHI_MIN or phi > BIFURC_PHI_MAX: phi = r.repick()
                  return phi
          
          sec1, extr1 = mkDendrite(r, extrLs[i].depth + 1, extrLs[i].sec)
          sec1.points = [ copy(extrLs[i].sec.points[-1]) ]
          sec1.index = len(nrn.dend)
          nrn.dend.append(sec1)
          
          extr1.phi = extrLs[i].phi + get_phi(); extr1.theta = extrLs[i].theta
          extr1.dist = extrLs[i].dist
          extr1.basePhi = extrLs[i].basePhi; extr1.baseTheta = extrLs[i].baseTheta
          extrLs.append(extr1)

          sec2, extr2 = mkDendrite(r, extrLs[i].depth + 1, extrLs[i].sec)
          sec2.points = [ copy(extrLs[i].sec.points[-1]) ]
          sec2.index = len(nrn.dend)
          nrn.dend.append(sec2)
          extr2.basePhi = extrLs[i].basePhi; extr2.baseTheta = extrLs[i].baseTheta
          extr2.phi = extrLs[i].phi - get_phi(); extr2.theta = extrLs[i].theta
          extr2.dist = extrLs[i].dist
          extrLs.append(extr2)
          



  # initialization of algorithm
  def initGrow(mid, noDend):
          
          r = [ ranstream(mid, stream_dend), ranstream(mid, stream_apic), ranstream(mid, stream_tuft) ]
          extrLs = [ ]
          nrn = Neuron()

          glomPos = params.glomRealCoords[int(mid/params.Nmitral_per_glom)]
          somaPos = mk_soma(mid, nrn)
          
          mkApical(r[Extreme.APICAL], somaPos, glomPos, nrn, extrLs)

          # make dendrites.
          if not noDend:
                  # get basic inclination
                  somaLvl = Ellipsoid(bulbCenter, somaAxis[0])
                  h, phi_base, theta_base = somaLvl.toElliptical(somaPos)
                  theta_base = pi / 2 - theta_base; phi_base = pi / 2 - phi_base
                  _r = r[Extreme.DENDRITE]
                  
                  ###
                  DENDRITES = int(_r.negexp(N_MEAN_DEND - N_MIN_DEND)) + N_MIN_DEND
                  while DENDRITES > N_MAX_DEND: DENDRITES = int(_r.negexp(N_MEAN_DEND - N_MIN_DEND)) + N_MIN_DEND
                  
                  nmg=params.Nmitral_per_glom
                  dphi=2*pi/DENDRITES
                  phi_phase=_r.uniform(0,2*pi)
                  for i in range(DENDRITES):
                          phiPhase = i*dphi + phi_phase #2*pi/nmg*(mid%nmg)
                          
                          sec, extr = mkDendrite(_r, 0, nrn.soma)
                          p = copy(somaPos); p.append(gen_dend_diam(0.))
                          sec.points = [ p ]
                          sec.index = i              
                          nrn.dend.append(sec)
                          
                          ## initial direction
                          phi = phiPhase
                          
                          theta = pi / 3.
                          newPhi, newTheta = convert_direction(phi, theta, phi_base, theta_base)
                          extr.phi = phi; extr.theta = theta
                          extr.basePhi = phi_base; extr.baseTheta = theta_base
                          extrLs.append(extr)
          return r, nrn, extrLs, glomPos

  def Grow(mid, noDend):
          r = nrn = extrLs = glomPos = None
          r, nrn, extrLs, glomPos = initGrow(mid, noDend) # initialization        
          
          it = 0
          while it < GROW_MAX_ITERATIONS and len(extrLs) > 0:
                  i = 0
                  while i < len(extrLs):
                          j = 0
                          while j < GROW_MAX_ATTEMPTS:
                                  p, phi, theta = genWalk(r, extrLs[i], glomPos)
                                  
                                  if feasible(p, extrLs[i], nrn, glomPos):
                                          updateExtr(extrLs[i], p, phi, theta)
                                          break
                                  j += 1
                                           
                          if canDelete(extrLs[i], j >= GROW_MAX_ATTEMPTS):
                                  if len(extrLs[i].sec.points) <= 1 and extrLs[i].extr_type == Extreme.DENDRITE:
                                          if extrLs[i].sec.index < (len(nrn.dend) - 1):
                                                  for q in range(extrLs[i].sec.index + 1, len(nrn.dend)):
                                                          nrn.dend[q].index -= 1
                                          del nrn.dend[extrLs[i].sec.index] 
                                  
                                  del extrLs[i]        
                          elif canChangeTypeOfExtr(extrLs[i], glomPos):
                                  changeTypeExtr(r, extrLs, i, nrn, glomPos)
                                  del extrLs[i]
                          elif canBifurcate(extrLs[i]):
                                 Bifurcate(r, extrLs, i, nrn)
                                 del extrLs[i]
                          else:
                                  i += 1
                  it += 1
                  
          return nrn

  def genMitral(mid): return Grow(mid, False)
  def genSomaApicalTuft(mid): return Grow(mid, True)