Example #1
0
def calcCOM(m1=0.5, m2=0.5, x1=1, x2=1):
    """
    Given pynbody arrays for the mass and position of the two binary stars,
    function calculates the CoM of the two particles in order to check that
    it's still roughly 0.

    Parameters
    ----------
    
    (as pynbody SimArrays)
    m1, m2: SimArrays
        Primary and secondary mass arrays (in Msol)
    x1, x2: SimArrays
        Primary and secondary position arrays (in AU)

    Returns
    -------
    center of mass: array
        Center of mass position vector (numpy array in AU)
    """

    # Strip units from inputs
    x1 = np.asarray(isaac.strip_units(x1))
    x2 = np.asarray(isaac.strip_units(x2))
    m1 = np.asarray(isaac.strip_units(m1))
    m2 = np.asarray(isaac.strip_units(m2))

    # Compute,return CoM
    return (1.0 / (m1 + m2)) * ((m1 * x1) + (m2 * x2))
Example #2
0
def calcCOM(m1=0.5, m2=0.5, x1=1, x2=1):
    """
    Given pynbody arrays for the mass and position of the two binary stars,
    function calculates the CoM of the two particles in order to check that
    it's still roughly 0.

    Parameters
    ----------
    
    (as pynbody SimArrays)
    m1, m2: SimArrays
        Primary and secondary mass arrays (in Msol)
    x1, x2: SimArrays
        Primary and secondary position arrays (in AU)

    Returns
    -------
    center of mass: array
        Center of mass position vector (numpy array in AU)
    """

    # Strip units from inputs
    x1 = np.asarray(isaac.strip_units(x1))
    x2 = np.asarray(isaac.strip_units(x2))
    m1 = np.asarray(isaac.strip_units(m1))
    m2 = np.asarray(isaac.strip_units(m2))

    # Compute,return CoM
    return (1.0 / (m1 + m2)) * ((m1 * x1) + (m2 * x2))
Example #3
0
def binaryPrecession(s,r_in,r_out):
    """
    Computes the period of the binary precession due to an axisymmetric disk (!!!)
    given by Rafikov 2013.

    Parameters
    ----------
    s : Tipsy snapshot
    r_in, r_out : float
        inner and outer radii of the circumbinary disk [AU]
        
    Returns
    -------
    T : SimArray
        Period of binary argument of periastron precession in yr
    """
    x1 = s.stars[0]['pos'].in_units('cm')
    x2 = s.stars[1]['pos'].in_units('cm')
    v1 = s.stars[0]['vel'].in_units('cm s**-1')
    v2 = s.stars[1]['vel'].in_units('cm s**-1')
    m1 = s.stars[0]['mass'].in_units('g')
    m2 = s.stars[1]['mass'].in_units('g')    
    
    # Define required parameters in cgs
    M_bin = m1 + m2
    M_disk = np.sum(s.gas['mass']).in_units('g')    
    a = SimArray(calcSemi(x1,x2,v1,v2,m1,m2,flag=False),'au').in_units('cm') #semimajor axis in cm
    n = calcMeanMotion(x1, x2, v1, v2, m1, m2, flag=False) #mean motion in 1/s    
    r_in = SimArray(r_in,'au').in_units('cm')
    r_out = SimArray(r_out,'au').in_units('cm')    
    
    T = 8.0*np.pi*(M_bin/M_disk)*(np.power(r_out,0.5)*np.power(r_in,2.5)/(np.power(a,3)*0.5*n))
    return isaac.strip_units(T)/YEARSEC
Example #4
0
 def _generate_theta(self):
     """
     Generate angular positions
     """
     
     nParticles = self.nParticles
     
     if self.method == 'grid':
         
         r = self.r
         
         dtheta = np.sqrt(2*np.pi*(1 - r[0:-1]/r[1:]))
         dtheta = isaac.strip_units(dtheta)
         theta = np.zeros(nParticles)
         
         for n in range(nParticles - 1):
             
             # NOTE: it's import to subtract (not add) dtheta.  The particles
             # will be moving counter-clockwise.  To prevent the particle
             # spirals from kinking, the particle spirals must go out
             # clockwise
             theta[n+1] = theta[n] - dtheta[n]
             
         self.theta = theta
         
     if self.method == 'random':
         
         theta = 2*np.pi*np.random.rand(nParticles)
         self.theta = theta
Example #5
0
def estimateCBResonances(s, r_max, m_max=5, l_max=5, bins=2500):
    """
	Given pynbody snapshot star and gas SimArrays, computes the resonances of disk on binary as a function of period.
	Disk radius, in au, is convered to angular frequency which will then be used to compute corotation and inner/outer Lindblad resonances.
	Assumption: Assumes m_disk << m_bin which holds in general for simulations considered
	For reference: Kappa, omega computed in ~ 1/day intermediate units.
	Uses approximations from Artymowicz 1994

	Inputs:
	stars,gas: Pynbody snapshot .star and .gas SimArrays (in au, Msol, etc)
	r_max: maximum disk radius for calculations (au)
	bins: number of radial bins to calculate over

	Output:
	Orbital frequency for corotation and inner/outer resonances as float and 2 arrays
	"""
    stars = s.stars
    #gas = s.gas

    #Compute binary angular frequency
    #Strip units from all inputs
    x1 = np.asarray(isaac.strip_units(stars[0]['pos']))
    x2 = np.asarray(isaac.strip_units(stars[1]['pos']))
    v1 = np.asarray(isaac.strip_units(stars[0]['vel']))
    v2 = np.asarray(isaac.strip_units(stars[1]['vel']))
    m1 = np.asarray(isaac.strip_units(stars[0]['mass']))
    m2 = np.asarray(isaac.strip_units(stars[1]['mass']))
    a = AddBinary.calcSemi(x1, x2, v1, v2, m1, m2)
    #omega_b = 2.0*np.pi/AddBinary.aToP(a,m1+m2

    #Find corotation resonance where omega_d ~ omega_b
    r_c = a  #m=1 case
    o_c = 2.0 * np.pi / AddBinary.aToP(r_c, m1 + m2)

    #Find inner lindblad resonances for m = [m_min,m_max]
    #Lindblad resonance: omega = omega_pattern +/- kappa/m for int m > 1
    m_min = 1
    l_min = 1

    omega_Lo = np.zeros((m_max - m_min, l_max - l_min))
    omega_Li = np.zeros((m_max - m_min, l_max - l_min))

    #Find resonance radii, convert to angular frequency
    for m in range(m_min, m_max):
        for l in range(l_min, l_max):
            #oTmp = find_crit_radius(r,omega_d-(kappa/(float(m))),omega_b,bins) #outer LR
            oTmp = np.power(float(m + 1) / l, 2. / 3.) * a
            omega_Lo[m - m_min,
                     l - l_min] = 2.0 * np.pi / AddBinary.aToP(oTmp, m1 + m2)

            #iTmp = find_crit_radius(r,omega_d+(kappa/(float(m))),omega_b,bins) #inner LR
            iTmp = np.power(float(m - 1) / l, 2. / 3.) * a
            omega_Li[m - m_min,
                     l - l_min] = 2.0 * np.pi / AddBinary.aToP(iTmp, m1 + m2)

    return omega_Li, omega_Lo, o_c  #return inner, outer, co angular frequencies
Example #6
0
def estimateCBResonances(s,r_max,m_max=5,l_max=5,bins=2500):
	"""
	Given pynbody snapshot star and gas SimArrays, computes the resonances of disk on binary as a function of period.
	Disk radius, in au, is convered to angular frequency which will then be used to compute corotation and inner/outer Lindblad resonances.
	Assumption: Assumes m_disk << m_bin which holds in general for simulations considered
	For reference: Kappa, omega computed in ~ 1/day intermediate units.
	Uses approximations from Artymowicz 1994

	Inputs:
	stars,gas: Pynbody snapshot .star and .gas SimArrays (in au, Msol, etc)
	r_max: maximum disk radius for calculations (au)
	bins: number of radial bins to calculate over

	Output:
	Orbital frequency for corotation and inner/outer resonances as float and 2 arrays
	"""
	stars = s.stars
	#gas = s.gas

	#Compute binary angular frequency
	#Strip units from all inputs
	x1 = np.asarray(isaac.strip_units(stars[0]['pos']))
	x2 = np.asarray(isaac.strip_units(stars[1]['pos']))
	v1 = np.asarray(isaac.strip_units(stars[0]['vel']))
	v2 = np.asarray(isaac.strip_units(stars[1]['vel']))
	m1 = np.asarray(isaac.strip_units(stars[0]['mass']))
	m2 = np.asarray(isaac.strip_units(stars[1]['mass']))
	a = AddBinary.calcSemi(x1, x2, v1, v2, m1, m2)
	#omega_b = 2.0*np.pi/AddBinary.aToP(a,m1+m2

	#Find corotation resonance where omega_d ~ omega_b
	r_c = a #m=1 case
	o_c = 2.0*np.pi/AddBinary.aToP(r_c,m1+m2)
        
	#Find inner lindblad resonances for m = [m_min,m_max]
	#Lindblad resonance: omega = omega_pattern +/- kappa/m for int m > 1
	m_min = 1
	l_min = 1    

	omega_Lo = np.zeros((m_max-m_min,l_max-l_min))
	omega_Li = np.zeros((m_max-m_min,l_max-l_min))
   
	#Find resonance radii, convert to angular frequency
	for m in range(m_min,m_max):
		for l in range(l_min,l_max):
		#oTmp = find_crit_radius(r,omega_d-(kappa/(float(m))),omega_b,bins) #outer LR
			oTmp = np.power(float(m+1)/l,2./3.)*a
			omega_Lo[m-m_min,l-l_min] = 2.0*np.pi/AddBinary.aToP(oTmp,m1+m2)
		
		#iTmp = find_crit_radius(r,omega_d+(kappa/(float(m))),omega_b,bins) #inner LR
			iTmp = np.power(float(m-1)/l,2./3.)*a
			omega_Li[m-m_min,l-l_min] = 2.0*np.pi/AddBinary.aToP(iTmp,m1+m2)

	return omega_Li, omega_Lo, o_c #return inner, outer, co angular frequencies
Example #7
0
def calcCircularFrequency(x1, x2, v1, v2, m1, m2, flag=True):
    """
    Given pynbody arrays for positions and velocity of primary and secondary bodies
    and masses, calculates the circular frequency in the reduced two body system.
    Usage note: Intended for binary system, but pass x2 = v2 = 0 to use with any
    location in the disk.

    omega = (L_z)/(R^2) which assumes spherical symmetry (ok assumption here)
    L = sqrt(G*M*a*(1-e^2) for ~Keplerian

    Parameters
    ----------
    Assumed as pynbody SimArrays in simulation units (AU, scaled velocity, etc)
    
    x1,x2: arrays
        Primary and secondary position arrays (in AU)
    v1, v2: arrays
        Primary and secondary velocity arrays (km/s)
    m1, m2: floats
        Primary and secondary masses (Msol)
    Flag: Whether or not to internally convert to cgs units

    Returns
    -------
    omega: float
        Circular frequency in 1/days
    """
    e = calcEcc(x1, x2, v1, v2, m1, m2, flag=True)
    a = calcSemi(x1, x2, v1, v2, m1, m2, flag=True) * AUCM
    
    if flag:
        # Remove units since input is pynbody SimArray
        x1 = np.asarray(isaac.strip_units(x1)) * AUCM
        x2 = np.asarray(isaac.strip_units(x2)) * AUCM
        v1 = np.asarray(isaac.strip_units(v1)) * 1000 * 100 * VEL_UNIT
        v2 = np.asarray(isaac.strip_units(v2)) * 1000 * 100 * VEL_UNIT
        m1 = np.asarray(isaac.strip_units(m1)) * Msol
        m2 = np.asarray(isaac.strip_units(m2)) * Msol

    length, ax = computeLenAx(x1)

    # Calculate angular momentum assuming all arrays are nx3
    r = x1 - x2
    rMag = np.sqrt(dotProduct(r, r))
    #e = ...    
    #a = calcSemi(x1, x2, v1, v2, m1, m2, flag=False) * AUCM
    L = np.sqrt(BigG * (m1 + m2) * a * (1 - e * e))

    # Convert from 1/s to 1/day, return
    return L * DAYSEC / (rMag * rMag)
Example #8
0
def calcCircularFrequency(x1, x2, v1, v2, m1, m2, flag=True):
    """
    Given pynbody arrays for positions and velocity of primary and secondary bodies
    and masses, calculates the circular frequency in the reduced two body system.
    Usage note: Intended for binary system, but pass x2 = v2 = 0 to use with any
    location in the disk.

    omega = (L_z)/(R^2) which assumes spherical symmetry (ok assumption here)
    L = sqrt(G*M*a*(1-e^2) for ~Keplerian

    Parameters
    ----------
    Assumed as pynbody SimArrays in simulation units (AU, scaled velocity, etc)
    
    x1,x2: arrays
        Primary and secondary position arrays (in AU)
    v1, v2: arrays
        Primary and secondary velocity arrays (km/s)
    m1, m2: floats
        Primary and secondary masses (Msol)
    Flag: Whether or not to internally convert to cgs units

    Returns
    -------
    omega: float
        Circular frequency in 1/days
    """
    e = calcEcc(x1, x2, v1, v2, m1, m2, flag=True)
    a = calcSemi(x1, x2, v1, v2, m1, m2, flag=True) * AUCM

    if flag:
        # Remove units since input is pynbody SimArray
        x1 = np.asarray(isaac.strip_units(x1)) * AUCM
        x2 = np.asarray(isaac.strip_units(x2)) * AUCM
        v1 = np.asarray(isaac.strip_units(v1)) * 1000 * 100 * VEL_UNIT
        v2 = np.asarray(isaac.strip_units(v2)) * 1000 * 100 * VEL_UNIT
        m1 = np.asarray(isaac.strip_units(m1)) * Msol
        m2 = np.asarray(isaac.strip_units(m2)) * Msol

    length, ax = computeLenAx(x1)

    # Calculate angular momentum assuming all arrays are nx3
    r = x1 - x2
    rMag = np.sqrt(dotProduct(r, r))
    #e = ...
    #a = calcSemi(x1, x2, v1, v2, m1, m2, flag=False) * AUCM
    L = np.sqrt(BigG * (m1 + m2) * a * (1 - e * e))

    # Convert from 1/s to 1/day, return
    return L * DAYSEC / (rMag * rMag)
Example #9
0
def binaryPrecession(s, r_in, r_out):
    """
    Computes the period of the binary precession due to an axisymmetric disk (!!!)
    given by Rafikov 2013.

    Parameters
    ----------
    s : Tipsy snapshot
    r_in, r_out : float
        inner and outer radii of the circumbinary disk [AU]
        
    Returns
    -------
    T : SimArray
        Period of binary argument of periastron precession in yr
    """
    x1 = s.stars[0]['pos'].in_units('cm')
    x2 = s.stars[1]['pos'].in_units('cm')
    v1 = s.stars[0]['vel'].in_units('cm s**-1')
    v2 = s.stars[1]['vel'].in_units('cm s**-1')
    m1 = s.stars[0]['mass'].in_units('g')
    m2 = s.stars[1]['mass'].in_units('g')

    # Define required parameters in cgs
    M_bin = m1 + m2
    M_disk = np.sum(s.gas['mass']).in_units('g')
    a = SimArray(calcSemi(x1, x2, v1, v2, m1, m2, flag=False),
                 'au').in_units('cm')  #semimajor axis in cm
    n = calcMeanMotion(x1, x2, v1, v2, m1, m2, flag=False)  #mean motion in 1/s
    r_in = SimArray(r_in, 'au').in_units('cm')
    r_out = SimArray(r_out, 'au').in_units('cm')

    T = 8.0 * np.pi * (M_bin /
                       M_disk) * (np.power(r_out, 0.5) * np.power(r_in, 2.5) /
                                  (np.power(a, 3) * 0.5 * n))
    return isaac.strip_units(T) / YEARSEC
Example #10
0
def snapshot_gen(ICobj):
    """
    Generates a tipsy snapshot from the initial conditions object ICobj.
    
    Returns snapshot, param
    
        snapshot: tipsy snapshot
        param: dictionary containing info for a .param file
    Note: Code has been edited (dflemin3) such that now it returns a snapshot for a circumbinary disk
    where initial conditions generated assuming star at origin of mass M.  After gas initialized, replaced
    star at origin with binary system who's center of mass lies at the origin and who's mass m1 +m2 = M
    """

    print 'Generating snapshot...'
    # Constants
    G = SimArray(1.0, 'G')
    # ------------------------------------
    # Load in things from ICobj
    # ------------------------------------
    print 'Accessing data from ICs'
    settings = ICobj.settings

    # snapshot file name
    snapshotName = settings.filenames.snapshotName
    paramName = settings.filenames.paramName

    #Load user supplied snapshot (assumed to be in cwd)
    path = "/astro/store/scratch/tmp/dflemin3/nbodyshare/9au-Q1.05-129K/"
    snapshot = pynbody.load(path + snapshotName)

    # particle positions
    r = snapshot.gas['r']
    xyz = snapshot.gas['pos']

    # Number of particles
    nParticles = len(snapshot.gas)

    # molecular mass
    m = settings.physical.m

    #Pull star mass from user-supplied snapshot
    ICobj.settings.physical.M = snapshot.star[
        'mass']  #Total stellar mass in solar masses
    m_star = ICobj.settings.physical.M

    # disk mass
    m_disk = np.sum(snapshot.gas['mass'])
    m_disk = isaac.match_units(m_disk, m_star)[0]

    # mass of the gas particles
    m_particles = m_disk / float(nParticles)

    # re-scale the particles (allows making of low-mass disk)
    m_particles *= settings.snapshot.mScale

    # -------------------------------------------------
    # Assign output
    # -------------------------------------------------
    print 'Assigning data to snapshot'
    # Get units all set up
    m_unit = m_star.units
    pos_unit = r.units

    if xyz.units != r.units:

        xyz.convert_units(pos_unit)

    # time units are sqrt(L^3/GM)
    t_unit = np.sqrt((pos_unit**3) * np.power((G * m_unit), -1)).units
    # velocity units are L/t
    v_unit = (pos_unit / t_unit).ratio('km s**-1')
    # Make it a unit, save value for future conversion
    v_unit_vel = v_unit
    #Ensure v_unit_vel is the same as what I assume it is.
    assert (np.fabs(AddBinary.VEL_UNIT - v_unit_vel) <
            AddBinary.SMALL), "VEL_UNIT not equal to ChaNGa unit! Why??"

    v_unit = pynbody.units.Unit('{0} km s**-1'.format(v_unit))

    # Other settings
    metals = settings.snapshot.metals
    star_metals = metals

    # Estimate the star's softening length as the closest particle distance
    eps = r.min()

    # Make param file
    param = isaac.make_param(snapshot, snapshotName)
    param['dMeanMolWeight'] = m

    gc.collect()

    # CALCULATE VELOCITY USING calc_velocity.py.  This also estimates the
    # gravitational softening length eps

    preset = settings.changa_run.preset

    # -------------------------------------------------
    # Estimate time step for changa to use
    # -------------------------------------------------
    # Save param file
    isaac.configsave(param, paramName, 'param')
    # Save snapshot
    snapshot.write(filename=snapshotName, fmt=pynbody.tipsy.TipsySnap)
    # est dDelta
    dDelta = ICgen_utils.est_time_step(paramName, preset)
    param['dDelta'] = dDelta

    # -------------------------------------------------
    # Create director file
    # -------------------------------------------------
    # largest radius to plot
    r_director = float(0.9 * r.max())
    # Maximum surface density
    sigma_min = float(ICobj.sigma(r_director))
    # surface density at largest radius
    sigma_max = float(ICobj.sigma.input_dict['sigma'].max())
    # Create director dict
    director = isaac.make_director(sigma_min,
                                   sigma_max,
                                   r_director,
                                   filename=param['achOutName'])
    ## Save .director file
    #isaac.configsave(director, directorName, 'director')
    """
    Now that the gas disk is initializes around the primary (M=m1), add in the
    second star as specified by the user.
    """

    #Now that velocities and everything are all initialized for gas particles, create new snapshot to return in which
    #single star particle is replaced by 2, same units as above
    snapshotBinary = pynbody.new(star=2, gas=nParticles)
    snapshotBinary['eps'] = 0.01 * SimArray(
        np.ones(nParticles + 2, dtype=np.float32), pos_unit)
    snapshotBinary['metals'] = SimArray(
        np.zeros(nParticles + 2, dtype=np.float32))
    snapshotBinary['vel'].units = v_unit
    snapshotBinary['pos'].units = pos_unit
    snapshotBinary['mass'].units = snapshot['mass'].units
    snapshotBinary['rho'] = SimArray(np.zeros(nParticles + 2,
                                              dtype=np.float32))

    #Assign gas particles with calculated/given values from above
    snapshotBinary.gas['pos'] = snapshot.gas['pos']
    snapshotBinary.gas['vel'] = snapshot.gas['vel']
    snapshotBinary.gas['temp'] = snapshot.gas['temp']
    snapshotBinary.gas['rho'] = snapshot.gas['rho']
    snapshotBinary.gas['eps'] = snapshot.gas['eps']
    snapshotBinary.gas['mass'] = snapshot.gas['mass']
    snapshotBinary.gas['metals'] = snapshot.gas['metals']

    #Load Binary system obj to initialize system
    binsys = ICobj.settings.physical.binsys
    m_disk = isaac.strip_units(np.sum(snapshotBinary.gas['mass']))
    binsys.m1 = isaac.strip_units(m_star)
    binsys.m1 = binsys.m1 + m_disk
    #Recompute cartesian coords considering primary as m1+m_disk
    binsys.computeCartesian()

    x1, x2, v1, v2 = binsys.generateICs()

    #Assign position, velocity assuming CCW orbit
    snapshotBinary.star[0]['pos'] = SimArray(x1, pos_unit)
    snapshotBinary.star[0]['vel'] = SimArray(v1, v_unit)
    snapshotBinary.star[1]['pos'] = SimArray(x2, pos_unit)
    snapshotBinary.star[1]['vel'] = SimArray(v2, v_unit)
    """
    We have the binary positions about their center of mass, (0,0,0), so 
    shift the position, velocity of the gas disk to be around the primary.
    """
    snapshotBinary.gas['pos'] += snapshotBinary.star[0]['pos']
    snapshotBinary.gas['vel'] += snapshotBinary.star[0]['vel']

    #Set stellar masses: Create simArray for mass, convert units to simulation mass units
    snapshotBinary.star[0]['mass'] = SimArray(binsys.m1 - m_disk, m_unit)
    snapshotBinary.star[1]['mass'] = SimArray(binsys.m2, m_unit)
    snapshotBinary.star['metals'] = SimArray(star_metals)

    print 'Wrapping up'
    # Now set the star particle's tform to a negative number.  This allows
    # UW ChaNGa treat it as a sink particle.
    snapshotBinary.star['tform'] = -1.0

    #Set sink radius, stellar smoothing length as fraction of distance
    #from primary to inner edge of the disk
    r_sink = eps
    snapshotBinary.star[0]['eps'] = SimArray(r_sink / 2.0, pos_unit)
    snapshotBinary.star[1]['eps'] = SimArray(r_sink / 2.0, pos_unit)
    param['dSinkBoundOrbitRadius'] = r_sink
    param['dSinkRadius'] = r_sink
    param['dSinkMassMin'] = 0.9 * binsys.m2
    param['bDoSinks'] = 1

    return snapshotBinary, param, director
Example #11
0
def v_xy(f, param, changbin=None, nr=50, min_per_bin=100):
    """
    Attempts to calculate the circular velocities for particles in a thin
    (not flat) keplerian disk.  Requires ChaNGa
    
    **ARGUMENTS**
    
    f : tipsy snapshot
        For a gaseous disk
    param : dict
        a dictionary containing params for changa. (see isaac.configparser)
    changbin : str  (OPTIONAL)  
        If set, should be the full path to the ChaNGa executable.  If None, 
        an attempt to find ChaNGa is made
    nr : int (optional)
        number of radial bins to use when averaging over accelerations
    min_per_bin : int (optional)
        The minimum number of particles to be in each bin.  If there are too
        few particles in a bin, it is merged with an adjacent bin.  Thus,
        actual number of radial bins may be less than nr.
        
    **RETURNS**
    
    vel : SimArray
        An N by 3 SimArray of gas particle velocities.
    """

    if changbin is None:
        # Try to find the ChaNGa binary full path
        changbin = os.popen('which ChaNGa').read().strip()

    # Load stuff from the snapshot
    x = f.g['x']
    y = f.g['y']
    z = f.g['z']
    r = f.g['rxy']
    vel0 = f.g['vel'].copy()

    # Remove units from all quantities
    r = isaac.strip_units(r)
    x = isaac.strip_units(x)
    y = isaac.strip_units(y)
    z = isaac.strip_units(z)

    # Temporary filenames for running ChaNGa
    f_prefix = str(np.random.randint(0, 2**32))
    f_name = f_prefix + '.std'
    p_name = f_prefix + '.param'

    # Update parameters
    p_temp = param.copy()
    p_temp['achInFile'] = f_name
    p_temp['achOutName'] = f_prefix
    if 'dDumpFrameTime' in p_temp: p_temp.pop('dDumpFrameTime')
    if 'dDumpFrameStep' in p_temp: p_temp.pop('dDumpFrameStep')

    # --------------------------------------------
    # Estimate velocity from gravity only
    # --------------------------------------------
    # Note, accelerations due to gravity are calculated twice to be extra careful
    # This is so that any velocity dependent effects are properly accounted for
    # (although, ideally, there should be none)
    # The second calculation uses the updated velocities from the first
    for iGrav in range(2):
        # Save files
        f.write(filename=f_name, fmt=pynbody.tipsy.TipsySnap)
        isaac.configsave(p_temp, p_name, ftype='param')

        # Run ChaNGa, only calculating gravity
        command = 'charmrun ++local ' + changbin + ' -gas -n 0 ' + p_name

        p = subprocess.Popen(command.split(), stdout=subprocess.PIPE)

        while p.poll() is None:

            time.sleep(0.1)

        # Load accelerations
        acc_name = f_prefix + '.000000.acc2'
        a = isaac.load_acc(acc_name)

        # Clean-up
        for fname in glob.glob(f_prefix + '*'):
            os.remove(fname)

        # If a is not a vector, calculate radial acceleration.  Otherwise, assume
        # a is the radial acceleration
        a_r = a[:, 0] * x / r + a[:, 1] * y / r

        # Make sure the units are correct then remove them
        a_r = isaac.match_units(a_r, a)[0]
        a_r = isaac.strip_units(a_r)

        # Calculate cos(theta) where theta is angle above x-y plane
        cos = r / np.sqrt(r**2 + z**2)
        ar2 = a_r * r**2

        # Bin the data
        r_edges = np.linspace(r.min(), (1 + np.spacing(2)) * r.max(), nr + 1)
        ind, r_edges = isaac.digitize_threshold(r, min_per_bin, r_edges)
        ind -= 1
        nr = len(r_edges) - 1

        r_bins, ar2_mean, err = isaac.binned_mean(r, ar2, binedges=r_edges, \
        weighted_bins=True)

        # Fit lines to ar2 vs cos for each radial bin
        m = np.zeros(nr)
        b = np.zeros(nr)

        for i in range(nr):

            mask = (ind == i)
            p = np.polyfit(cos[mask], ar2[mask], 1)
            m[i] = p[0]
            b[i] = p[1]

        # Interpolate the line fits
        m_spline = isaac.extrap1d(r_bins, m)
        b_spline = isaac.extrap1d(r_bins, b)

        # Calculate circular velocity
        ar2_calc = m_spline(r) * cos + b_spline(r)
        v_calc = np.sqrt(abs(ar2_calc) / r)
        vel = f.g['vel'].copy()
        v_calc = isaac.match_units(v_calc, vel)[0]
        vel[:, 0] = -v_calc * y / r
        vel[:, 1] = v_calc * x / r

        # Assign to f
        f.g['vel'] = vel

    # --------------------------------------------
    # Estimate pressure/gas dynamics accelerations
    # --------------------------------------------
    a_grav = a
    ar2_calc_grav = ar2_calc

    # Save files
    f.write(filename=f_name, fmt=pynbody.tipsy.TipsySnap)
    isaac.configsave(p_temp, p_name, ftype='param')

    # Run ChaNGa, including SPH
    command = 'charmrun ++local ' + changbin + ' +gas -n 0 ' + p_name

    p = subprocess.Popen(command.split(), stdout=subprocess.PIPE)

    while p.poll() is None:

        time.sleep(0.1)

    # Load accelerations
    acc_name = f_prefix + '.000000.acc2'
    a_total = isaac.load_acc(acc_name)

    # Clean-up
    for fname in glob.glob(f_prefix + '*'):
        os.remove(fname)

    # Estimate the accelerations due to pressure gradients/gas dynamics
    a_gas = a_total - a_grav
    ar_gas = a_gas[:, 0] * x / r + a_gas[:, 1] * y / r
    ar_gas = isaac.strip_units(ar_gas)
    ar2_gas = ar_gas * r**2

    logr_bins, ratio, err = isaac.binned_mean(np.log(r), ar2_gas/ar2_calc_grav, nbins=nr,\
    weighted_bins=True)
    r_bins = np.exp(logr_bins)
    ratio_spline = isaac.extrap1d(r_bins, ratio)

    ar2_calc = ar2_calc_grav * (1 + ratio_spline(r))
    a_calc = ar2_calc / r**2

    v = np.sqrt(r * abs(a_calc))
    v = isaac.match_units(v, vel0.units)[0]
    vel = vel0.copy()
    vel[:, 0] = -v * y / r
    vel[:, 1] = v * x / r

    # more cleanup
    f.g['vel'] = vel0

    return vel
Example #12
0
def linearMomentumEffects(x1, x2, v1, v2, m1, m2, accretion):
    """
	Given initial binary system parameters and an array tracking the accretion events, calculate the effects of accretion
	on the semimajor axis and eccentricity of the binary system.

	Inputs: Assume all input arrays are in simulation units
	Masses of primary and secondary (m1, m2 in Msol)
	Position arrays of primary and secondary x1, x2 (in AU)
	Velocity arrays of primary and secondary v1, v2 (in km/s)
	Numpy array of accretion events of the form [m vx vy vz ...] for each accreted gas particle at time of accretion

	Output:
	Semimajor axis, eccentricity of binary system after accretion events
	"""
    #Extract masses and velocities of accreted gas particles from array of known format
    m_g = np.zeros(len(accretion))
    v = np.zeros(shape=(len(accretion), 3))

    for i in range(len(accretion)):
        m_g[i] = accretion[i, 0]
        for j in range(1, 4):
            v[i, j - 1] = accretion[i, j]

    #Strip units from all inputs, convert all into CGS
    r1 = np.asarray(isaac.strip_units(x1)) * AddBinary.AUCM
    r2 = np.asarray(isaac.strip_units(x2)) * AddBinary.AUCM
    v1 = np.asarray(isaac.strip_units(v1)) * AddBinary.VEL_UNIT * 100 * 1000
    v2 = np.asarray(isaac.strip_units(v2)) * AddBinary.VEL_UNIT * 100 * 1000
    m1 = np.asarray(isaac.strip_units(m1)) * AddBinary.Msol
    m2 = np.asarray(isaac.strip_units(m2)) * AddBinary.Msol
    m_g = m_g * AddBinary.Msol
    v = v * AddBinary.VEL_UNIT * 100 * 1000

    #Compute relative binary system quantities
    vBin = v1 - v2
    rBin = r1 - r2
    mBin = m1 + m2

    #Loop over accretion events, apply conservation of linear momentum at each step
    for i in range(len(accretion)):
        vBin = (1.0 / (mBin + m_g[i])) * (mBin * vBin + m_g[i] * v[i])
        mBin = mBin + m_g[i]

    #Compute final semimajor axis, eccentricity

    #Compute r, v, standard gravitational parameter
    magR = np.linalg.norm(rBin)
    mu = AddBinary.BigG * (mBin)
    magV = np.linalg.norm(vBin)

    #Compute specific orbital energy, angular momentum
    eps = (magV * magV / 2.0) - (mu / magR)
    h = np.cross(rBin, vBin)
    magH = np.linalg.norm(h)

    #Compute semimajor axis	in AU
    a = -mu / (2.0 * eps) / (AddBinary.AUCM)

    #Compute eccentricity
    e = np.sqrt(1 + ((2 * eps * magH * magH) / (mu * mu)))

    return a, e
Example #13
0
def snapshot_gen(ICobj):
    """
    Generates a tipsy snapshot from the initial conditions object ICobj.
    
    Returns snapshot, param
    
        snapshot: tipsy snapshot
        param: dictionary containing info for a .param file
    Note: Code has been edited (dflemin3) such that now it returns a snapshot for a circumbinary disk
    where initial conditions generated assuming star at origin of mass M.  After gas initialized, replaced
    star at origin with binary system who's center of mass lies at the origin and who's mass m1 +m2 = M
    """
    
    print 'Generating snapshot...'
    # Constants
    G = SimArray(1.0,'G')
    # ------------------------------------
    # Load in things from ICobj
    # ------------------------------------
    print 'Accessing data from ICs'
    settings = ICobj.settings
    
    # snapshot file name
    snapshotName = settings.filenames.snapshotName
    paramName = settings.filenames.paramName   
 
    # particle positions
    r = ICobj.pos.r
    xyz = ICobj.pos.xyz
    
    # Number of particles
    nParticles = ICobj.pos.nParticles
    
    # molecular mass
    m = settings.physical.m
    
    # star mass
    m_star = settings.physical.M.copy()
    
    # disk mass
    m_disk = ICobj.sigma.m_disk.copy()
    m_disk = isaac.match_units(m_disk, m_star)[0]
    
    # mass of the gas particles
    m_particles = m_disk / float(nParticles)
    
    # re-scale the particles (allows making of low-mass disk)
    m_particles *= settings.snapshot.mScale
    
    # -------------------------------------------------
    # Assign output
    # -------------------------------------------------
    print 'Assigning data to snapshot'
    # Get units all set up
    m_unit = m_star.units
    pos_unit = r.units
    
    if xyz.units != r.units:
        
        xyz.convert_units(pos_unit)
        
    # time units are sqrt(L^3/GM)
    t_unit = np.sqrt((pos_unit**3)*np.power((G*m_unit), -1)).units
    # velocity units are L/t
    v_unit = (pos_unit/t_unit).ratio('km s**-1')
    # Make it a unit, save value for future conversion
    v_unit_vel = v_unit
    #Ensure v_unit_vel is the same as what I assume it is.
    assert(np.fabs(AddBinary.VEL_UNIT-v_unit_vel)<AddBinary.SMALL),"VEL_UNIT not equal to ChaNGa unit! Why??"			
	
    v_unit = pynbody.units.Unit('{0} km s**-1'.format(v_unit))
    
    # Other settings
    metals = settings.snapshot.metals
    star_metals = metals
    
    # Generate snapshot
    # Note that empty pos, vel, and mass arrays are created in the snapshot
    snapshot = pynbody.new(star=1,gas=nParticles)
    snapshot['vel'].units = v_unit
    snapshot['eps'] = 0.01*SimArray(np.ones(nParticles+1, dtype=np.float32), pos_unit)
    snapshot['metals'] = SimArray(np.zeros(nParticles+1, dtype=np.float32))
    snapshot['rho'] = SimArray(np.zeros(nParticles+1, dtype=np.float32))
    
    snapshot.gas['pos'] = xyz
    snapshot.gas['temp'] = ICobj.T(r)
    snapshot.gas['mass'] = m_particles
    snapshot.gas['metals'] = metals
    
    snapshot.star['pos'] = SimArray([[ 0.,  0.,  0.]],pos_unit)
    snapshot.star['vel'] = SimArray([[ 0.,  0.,  0.]], v_unit)
    snapshot.star['mass'] = m_star
    snapshot.star['metals'] = SimArray(star_metals)
    # Estimate the star's softening length as the closest particle distance
    eps = r.min()
    
    # Make param file
    param = isaac.make_param(snapshot, snapshotName)
    param['dMeanMolWeight'] = m
       
    gc.collect()
    
    # CALCULATE VELOCITY USING calc_velocity.py.  This also estimates the 
    # gravitational softening length eps
    print 'Calculating circular velocity'
    preset = settings.changa_run.preset
    max_particles = global_settings['misc']['max_particles']
    calc_velocity.v_xy(snapshot, param, changa_preset=preset, max_particles=max_particles)
    
    gc.collect()
  
	# -------------------------------------------------
    # Estimate time step for changa to use
    # -------------------------------------------------
    # Save param file
    isaac.configsave(param, paramName, 'param')
    # Save snapshot
    snapshot.write(filename=snapshotName, fmt=pynbody.tipsy.TipsySnap)
    # est dDelta
    dDelta = ICgen_utils.est_time_step(paramName, preset)
    param['dDelta'] = dDelta
 
	# -------------------------------------------------
    # Create director file
    # -------------------------------------------------
    # largest radius to plot
    r_director = float(0.9 * r.max())
    # Maximum surface density
    sigma_min = float(ICobj.sigma(r_director))
    # surface density at largest radius
    sigma_max = float(ICobj.sigma.input_dict['sigma'].max())
    # Create director dict
    director = isaac.make_director(sigma_min, sigma_max, r_director, filename=param['achOutName'])
    ## Save .director file
    #isaac.configsave(director, directorName, 'director')
    
    """
    Now that the gas disk is initializes around the primary (M=m1), add in the
    second star as specified by the user.
    """    
    
    #Now that velocities and everything are all initialized for gas particles, create new snapshot to return in which
    #single star particle is replaced by 2, same units as above
    snapshotBinary = pynbody.new(star=2,gas=nParticles)
    snapshotBinary['eps'] = 0.01*SimArray(np.ones(nParticles+2, dtype=np.float32), pos_unit)
    snapshotBinary['metals'] = SimArray(np.zeros(nParticles+2, dtype=np.float32))
    snapshotBinary['vel'].units = v_unit
    snapshotBinary['pos'].units = pos_unit
    snapshotBinary['mass'].units = snapshot['mass'].units
    snapshotBinary['rho'] = SimArray(np.zeros(nParticles+2, dtype=np.float32))

    #Assign gas particles with calculated/given values from above
    snapshotBinary.gas['pos'] = snapshot.gas['pos']
    snapshotBinary.gas['vel'] = snapshot.gas['vel']
    snapshotBinary.gas['temp'] = snapshot.gas['temp']
    snapshotBinary.gas['rho'] = snapshot.gas['rho']
    snapshotBinary.gas['eps'] = snapshot.gas['eps']
    snapshotBinary.gas['mass'] = snapshot.gas['mass']
    snapshotBinary.gas['metals'] = snapshot.gas['metals']

    #Load Binary system obj to initialize system
    binsys = ICobj.settings.physical.binsys
    m_disk = isaac.strip_units(np.sum(snapshotBinary.gas['mass']))
    binsys.m1 = binsys.m1 + m_disk  
    #Recompute cartesian coords considering primary as m1+m_disk    
    binsys.computeCartesian()
    
    x1,x2,v1,v2 = binsys.generateICs()

    #Assign position, velocity assuming CCW orbit
    snapshotBinary.star[0]['pos'] = SimArray(x1,pos_unit)
    snapshotBinary.star[0]['vel'] = SimArray(v1,v_unit)
    snapshotBinary.star[1]['pos'] = SimArray(x2,pos_unit)
    snapshotBinary.star[1]['vel'] = SimArray(v2,v_unit)

    """
    We have the binary positions about their center of mass, (0,0,0), so 
    shift the position, velocity of the gas disk to be around the primary.
    """
    snapshotBinary.gas['pos'] += snapshotBinary.star[0]['pos']
    snapshotBinary.gas['vel'] += snapshotBinary.star[0]['vel']  
    
    #Set stellar masses: Create simArray for mass, convert units to simulation mass units
    snapshotBinary.star[0]['mass'] = SimArray(binsys.m1-m_disk,m_unit)
    snapshotBinary.star[1]['mass'] = SimArray(binsys.m2,m_unit)
    snapshotBinary.star['metals'] = SimArray(star_metals)
 
    #Now that everything has masses and positions, adjust positions so the 
    #system center of mass corresponds to the origin
    """    
    com = binaryUtils.computeCOM(snapshotBinary.stars,snapshotBinary.gas)
    print com
    snapshotBinary.stars['pos'] -= com
    snapshotBinary.gas['pos'] -= com   
    """
 
    print 'Wrapping up'
    # Now set the star particle's tform to a negative number.  This allows
    # UW ChaNGa treat it as a sink particle.
    snapshotBinary.star['tform'] = -1.0
    
    #Set sink radius, stellar smoothing length as fraction of distance
    #from primary to inner edge of the disk
    r_sink = eps
    snapshotBinary.star[0]['eps'] = SimArray(r_sink/2.0,pos_unit)
    snapshotBinary.star[1]['eps'] = SimArray(r_sink/2.0,pos_unit)
    param['dSinkBoundOrbitRadius'] = r_sink
    param['dSinkRadius'] = r_sink
    param['dSinkMassMin'] = 0.9 * binsys.m2
    param['bDoSinks'] = 1
    
    return snapshotBinary, param, director
Example #14
0
def findCBResonances(s,r,r_min,r_max,m_max=4,l_max=4,bins=50):
    """
    Given Tipsy snapshot, computes the resonances of disk on binary as a function of orbital angular frequency omega.
    Disk radius, in au, is convered to angular frequency which will then be used to compute corotation 
    and inner/outer Lindblad resonances.
   
   Note: r given MUST correspond to r over which de/dt was calculated.  Otherwise, scale gets all messed up
 
     Parameters
     ----------
     s: Tipsy-format snapshot
     r: array
         radius array over which de/dt was calculated
     r_min,r_max: floats
         min/maximum disk radius for calculations (au)
     bins: int
         number of radial bins to calculate over
     m_max,l_max: ints
         maximum orders of (m,l) LR

    Returns
    -------
    Orbital frequency: numpy array
        for corotation and inner/outer resonances and radii as float and numpy arrays
    """
    stars = s.stars

    m_min = 1 #m >=1 for LRs, CRs
    l_min = 1 #l >=1 for LRs, CRs

    #Compute binary angular frequency
    #Strip units from all inputs
    #x1 = np.asarray(isaac.strip_units(stars[0]['pos']))
    #x2 = np.asarray(isaac.strip_units(stars[1]['pos']))
    #v1 = np.asarray(isaac.strip_units(stars[0]['vel']))
    #v2 = np.asarray(isaac.strip_units(stars[1]['vel']))
    #m1 = np.asarray(isaac.strip_units(stars[0]['mass']))
    #m2 = np.asarray(isaac.strip_units(stars[1]['mass']))
    x1 = stars[0]['pos']
    x2 = stars[1]['pos']
    v1 = stars[0]['vel']
    v2 = stars[1]['vel']
    m1 = stars[0]['mass']
    m2 = stars[1]['mass']
     
    a = isaac.strip_units(AddBinary.calcSemi(x1, x2, v1, v2, m1, m2))
    omega_b = 2.0*np.pi/AddBinary.aToP(a,m1+m2) #In units 1/day

    #Compute omega_disk in units 1/day (like omega_binary)
    omega_d = 2.0*np.pi/AddBinary.aToP(r,m1+m2)
        
    #Compute kappa (radial epicycle frequency = sqrt(r * d(omega^2)/dr + 4*(omega^2))
    o2 = omega_d*omega_d
    dr = (r.max()-r.min())/float(bins) #Assuming r has evenly spaced bins!
    drdo2 = np.gradient(o2,dr) #I mean d/dr(omega^2)
    kappa = np.sqrt(r*drdo2 + 4.0*o2)
   
    #Allocate arrays for output 
    omega_Lo = np.zeros((m_max,l_max))
    omega_Li = np.zeros((m_max,l_max))
    o_c = np.zeros(l_max)   
 
    #Find resonance angular frequency
    for m in range(m_min,m_max+1):
        for l in range(l_min,l_max+1):
            outer = omega_d + (float(l)/m)*kappa
            inner = omega_d - (float(l)/m)*kappa
            omega_Lo[m-m_min,l-l_min] = omega_d[np.argmin(np.fabs(omega_b-outer))]
            omega_Li[m-m_min,l-l_min] = omega_d[np.argmin(np.fabs(omega_b-inner))]

            #Find corotation resonance where omega_d ~ omega_b
            o_c[l-l_min] = omega_d[np.argmin(np.fabs(omega_d-omega_b/float(l)))]

    return omega_Li, omega_Lo, o_c, omega_d, kappa
Example #15
0
def linearMomentumEffects(x1, x2, v1, v2, m1, m2, accretion):
	"""
	Given initial binary system parameters and an array tracking the accretion events, calculate the effects of accretion
	on the semimajor axis and eccentricity of the binary system.

	Inputs: Assume all input arrays are in simulation units
	Masses of primary and secondary (m1, m2 in Msol)
	Position arrays of primary and secondary x1, x2 (in AU)
	Velocity arrays of primary and secondary v1, v2 (in km/s)
	Numpy array of accretion events of the form [m vx vy vz ...] for each accreted gas particle at time of accretion

	Output:
	Semimajor axis, eccentricity of binary system after accretion events
	"""
	#Extract masses and velocities of accreted gas particles from array of known format
	m_g = np.zeros(len(accretion))	
	v = np.zeros(shape=(len(accretion),3))

	for i in range(len(accretion)):
		m_g[i] = accretion[i,0]	
		for j in range(1,4):
			v[i,j-1] = accretion[i,j]

	#Strip units from all inputs, convert all into CGS
	r1 = np.asarray(isaac.strip_units(x1))*AddBinary.AUCM
	r2 = np.asarray(isaac.strip_units(x2))*AddBinary.AUCM
	v1 = np.asarray(isaac.strip_units(v1))*AddBinary.VEL_UNIT*100*1000
	v2 = np.asarray(isaac.strip_units(v2))*AddBinary.VEL_UNIT*100*1000
	m1 = np.asarray(isaac.strip_units(m1))*AddBinary.Msol
	m2 = np.asarray(isaac.strip_units(m2))*AddBinary.Msol
	m_g = m_g*AddBinary.Msol
	v = v * AddBinary.VEL_UNIT*100*1000

	#Compute relative binary system quantities
	vBin = v1 - v2
	rBin = r1 - r2
	mBin = m1 + m2

	#Loop over accretion events, apply conservation of linear momentum at each step
	for i in range(len(accretion)):
		vBin = (1.0/(mBin+m_g[i]))*(mBin*vBin + m_g[i]*v[i])
		mBin = mBin + m_g[i]	

	#Compute final semimajor axis, eccentricity
	
	#Compute r, v, standard gravitational parameter
	magR = np.linalg.norm(rBin)
	mu = AddBinary.BigG*(mBin)
	magV = np.linalg.norm(vBin)

	#Compute specific orbital energy, angular momentum
	eps = (magV*magV/2.0) - (mu/magR)
	h = np.cross(rBin,vBin)
	magH = np.linalg.norm(h)
	
	#Compute semimajor axis	in AU
	a = -mu/(2.0*eps)/(AddBinary.AUCM)

	#Compute eccentricity
	e = np.sqrt(1 + ((2*eps*magH*magH)/(mu*mu)))

	return a,e
Example #16
0
def snapshot_gen(ICobj):
    """
    Generates a tipsy snapshot from the initial conditions object ICobj.
    
    Returns snapshot, param
    
        snapshot: tipsy snapshot
        param: dictionary containing info for a .param file
    """
    
    print 'Generating snapshot...'
    # Constants
    G = SimArray(1.0,'G')
    # ------------------------------------
    # Load in things from ICobj
    # ------------------------------------
    print 'Accessing data from ICs'
    settings = ICobj.settings
    # filenames
    snapshotName = settings.filenames.snapshotName
    paramName = settings.filenames.paramName
        
    # particle positions
    r = ICobj.pos.r
    xyz = ICobj.pos.xyz
    # Number of particles
    nParticles = ICobj.pos.nParticles
    # molecular mass
    m = settings.physical.m
    # star mass
    m_star = settings.physical.M.copy()
    # disk mass
    m_disk = ICobj.sigma.m_disk.copy()
    m_disk = isaac.match_units(m_disk, m_star)[0]
    # mass of the gas particles
    m_particles = m_disk / float(nParticles)
    # re-scale the particles (allows making of lo-mass disk)
    m_particles *= settings.snapshot.mScale
    
    # -------------------------------------------------
    # Assign output
    # -------------------------------------------------
    print 'Assigning data to snapshot'
    # Get units all set up
    m_unit = m_star.units
    pos_unit = r.units
    
    if xyz.units != r.units:
        
        xyz.convert_units(pos_unit)
        
    # time units are sqrt(L^3/GM)
    t_unit = np.sqrt((pos_unit**3)*np.power((G*m_unit), -1)).units
    # velocity units are L/t
    v_unit = (pos_unit/t_unit).ratio('km s**-1')
    # Make it a unit
    v_unit = pynbody.units.Unit('{0} km s**-1'.format(v_unit))
    
    # Other settings
    metals = settings.snapshot.metals
    star_metals = metals
    
    # -------------------------------------------------
    # Initialize snapshot
    # -------------------------------------------------
    # Note that empty pos, vel, and mass arrays are created in the snapshot
    snapshot = pynbody.new(star=1,gas=nParticles)
    snapshot['vel'].units = v_unit
    snapshot['eps'] = 0.01*SimArray(np.ones(nParticles+1, dtype=np.float32), pos_unit)
    snapshot['metals'] = SimArray(np.zeros(nParticles+1, dtype=np.float32))
    snapshot['rho'] = SimArray(np.zeros(nParticles+1, dtype=np.float32))
    
    snapshot.gas['pos'] = xyz
    snapshot.gas['temp'] = ICobj.T(r)
    snapshot.gas['mass'] = m_particles
    snapshot.gas['metals'] = metals
    
    snapshot.star['pos'] = SimArray([[ 0.,  0.,  0.]],pos_unit)
    snapshot.star['vel'] = SimArray([[ 0.,  0.,  0.]], v_unit)
    snapshot.star['mass'] = m_star
    snapshot.star['metals'] = SimArray(star_metals)
    # Estimate the star's softening length as the closest particle distance
    snapshot.star['eps'] = r.min()
    
    # Make param file
    param = isaac.make_param(snapshot, snapshotName)
    param['dMeanMolWeight'] = m
       
    gc.collect()
    
    # -------------------------------------------------
    # CALCULATE VELOCITY USING calc_velocity.py.  This also estimates the 
    # gravitational softening length eps
    # -------------------------------------------------
    print 'Calculating circular velocity'
    preset = settings.changa_run.preset
    max_particles = global_settings['misc']['max_particles']
    calc_velocity.v_xy(snapshot, param, changa_preset=preset, max_particles=max_particles)
    
    gc.collect()
    
    # -------------------------------------------------
    # Estimate time step for changa to use
    # -------------------------------------------------
    # Save param file
    isaac.configsave(param, paramName, 'param')
    # Save snapshot
    snapshot.write(filename=snapshotName, fmt=pynbody.tipsy.TipsySnap)
    # est dDelta
    dDelta = ICgen_utils.est_time_step(paramName, preset)
    param['dDelta'] = dDelta
    
    # -------------------------------------------------
    # Create director file
    # -------------------------------------------------
    # largest radius to plot
    r_director = float(0.9 * r.max())
    # Maximum surface density
    sigma_min = float(ICobj.sigma(r_director))
    # surface density at largest radius
    sigma_max = float(ICobj.sigma.input_dict['sigma'].max())
    # Create director dict
    director = isaac.make_director(sigma_min, sigma_max, r_director, filename=param['achOutName'])
    ## Save .director file
    #isaac.configsave(director, directorName, 'director')
    
    # -------------------------------------------------
    # Wrap up
    # -------------------------------------------------
    print 'Wrapping up'
    # Now set the star particle's tform to a negative number.  This allows
    # UW ChaNGa treat it as a sink particle.
    snapshot.star['tform'] = -1.0
    
    # Update params
    r_sink = isaac.strip_units(r.min())
    param['dSinkBoundOrbitRadius'] = r_sink
    param['dSinkRadius'] = r_sink
    param['dSinkMassMin'] = 0.9 * isaac.strip_units(m_star)
    param['bDoSinks'] = 1
    
    return snapshot, param, director
Example #17
0
def find_clumps(f,
                n_smooth=32,
                param=None,
                arg_string=None,
                seed=None,
                verbose=True):
    """
    Uses skid (https://github.com/N-BodyShop/skid) to find clumps in a gaseous
    protoplanetary disk.  
    
    The linking length used is equal to the gravitational softening length of
    the gas particles.
    
    The density cut-off comes from the criterion that there are n_smooth particles
    within the Hill sphere of a particle.  This is formulated mathematically as:
    
        rho_min = 3*n_smooth*Mstar/R^3
        
    where R is the distance from the star.  The trick used here is to multiply
    all particle masses by R^3 before running skid so the density cut-off is:
    
        rho_min = 3*n_smooth*Mstar
        
    **ARGUMENTS**
    
    *f* : TipsySnap, or str
        A tipsy snapshot loaded/created by pynbody -OR- a filename pointing to a
        snapshot.
    
    *n_smooth* : int (optional)
        Number of particles used in SPH calculations.  Should be the same as used
        in the simulation.  Default = 32
    
    *param* : str (optional)
        filename for a .param file for the simulation
    
    *arg_string* : str (optional)
        Additional arguments to be passed to skid.  Cannot use -tau, -d, -m, -s, -o
    
    *seed* : int
        An integer used to seed the random filename generation for temporary
        files.  Necessary for multiprocessing and should be unique for each
        thread.
        
    *verbose* : bool
        Verbosity flag.  Default is True
    
    **RETURNS**
    
    *clumps* : array, int-like
        Array containing the group number each particle belongs to, with star
        particles coming after gas particles.  A zero means the particle belongs
        to no groups
    """
    # Parse areguments
    if isinstance(f, str):

        f = pynbody.load(f, paramfile=param)

    if seed is not None:

        np.random.seed(seed)

    # Estimate the linking length as the gravitational softening length
    tau = f.g['eps'][0]

    # Calculate minimum density
    rho_min = 3 * n_smooth * f.s['mass'][0]

    # Center on star.  This is done because R used in hill-sphere calculations
    # is relative to the star
    star_pos = f.s['pos'].copy()
    f['pos'] -= star_pos

    # Scale mass by R^3
    R = isaac.strip_units(f['rxy'])
    m0 = f['mass'].copy()
    f['mass'] *= (R + tau)**3

    # Save temporary snapshot
    f_prefix = str(np.random.randint(np.iinfo(int).max))
    f_name = f_prefix + '.std'

    # Save temporary .param file
    if param is not None:

        param_name = f_prefix + '.param'
        param_dict = isaac.configparser(param, 'param')
        isaac.configsave(param_dict, param_name)

    f.write(filename=f_name, fmt=pynbody.tipsy.TipsySnap)

    f['pos'] += star_pos
    f['mass'] = m0

    command = 'totipnat < {} | skid -tau {:.2e} -d {:.2e} -m {:d} -s {:d} -o {}'\
    .format(f_name, tau, rho_min, n_smooth, n_smooth, f_prefix)
    p = subprocess.Popen(command,
                         shell=True,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE)

    if verbose:

        for line in iter(p.stdout.readline, ''):
            print line,

    p.wait()

    # Load clumps
    clumps = isaac.loadhalos(f_prefix + '.grp')

    # Cleanup
    for name in glob.glob(f_prefix + '*'):

        os.remove(name)

    return clumps
Example #18
0
def snapshot_gen(ICobj):
    """
    Generates a tipsy snapshot from the initial conditions object ICobj.
    
    Returns snapshot, param
    
        snapshot: tipsy snapshot
        param: dictionary containing info for a .param file
    Note: Code has been edited (dflemin3) such that now it returns a snapshot for a circumbinary disk
    where initial conditions generated assuming star at origin of mass M.  After gas initialized, replaced
    star at origin with binary system who's center of mass lies at the origin and who's mass m1 +m2 = M
    """

    print 'Generating snapshot...'
    # Constants
    G = SimArray(1.0, 'G')
    # ------------------------------------
    # Load in things from ICobj
    # ------------------------------------
    print 'Accessing data from ICs'
    settings = ICobj.settings

    # snapshot file name
    snapshotName = settings.filenames.snapshotName
    paramName = settings.filenames.paramName

    # particle positions
    r = ICobj.pos.r
    xyz = ICobj.pos.xyz

    # Number of particles
    nParticles = ICobj.pos.nParticles

    # molecular mass
    m = settings.physical.m

    # star mass
    m_star = settings.physical.M.copy()

    # disk mass
    m_disk = ICobj.sigma.m_disk.copy()
    m_disk = isaac.match_units(m_disk, m_star)[0]

    # mass of the gas particles
    m_particles = m_disk / float(nParticles)

    # re-scale the particles (allows making of low-mass disk)
    m_particles *= settings.snapshot.mScale

    # -------------------------------------------------
    # Assign output
    # -------------------------------------------------
    print 'Assigning data to snapshot'
    # Get units all set up
    m_unit = m_star.units
    pos_unit = r.units

    if xyz.units != r.units:

        xyz.convert_units(pos_unit)

    # time units are sqrt(L^3/GM)
    t_unit = np.sqrt((pos_unit**3) * np.power((G * m_unit), -1)).units
    # velocity units are L/t
    v_unit = (pos_unit / t_unit).ratio('km s**-1')
    # Make it a unit, save value for future conversion
    v_unit_vel = v_unit
    #Ensure v_unit_vel is the same as what I assume it is.
    assert (np.fabs(AddBinary.VEL_UNIT - v_unit_vel) <
            AddBinary.SMALL), "VEL_UNIT not equal to ChaNGa unit! Why??"

    v_unit = pynbody.units.Unit('{0} km s**-1'.format(v_unit))

    # Other settings
    metals = settings.snapshot.metals
    star_metals = metals

    # Generate snapshot
    # Note that empty pos, vel, and mass arrays are created in the snapshot
    snapshot = pynbody.new(star=1, gas=nParticles)
    snapshot['vel'].units = v_unit
    snapshot['eps'] = 0.01 * SimArray(
        np.ones(nParticles + 1, dtype=np.float32), pos_unit)
    snapshot['metals'] = SimArray(np.zeros(nParticles + 1, dtype=np.float32))
    snapshot['rho'] = SimArray(np.zeros(nParticles + 1, dtype=np.float32))

    snapshot.gas['pos'] = xyz
    snapshot.gas['temp'] = ICobj.T(r)
    snapshot.gas['mass'] = m_particles
    snapshot.gas['metals'] = metals

    snapshot.star['pos'] = SimArray([[0., 0., 0.]], pos_unit)
    snapshot.star['vel'] = SimArray([[0., 0., 0.]], v_unit)
    snapshot.star['mass'] = m_star
    snapshot.star['metals'] = SimArray(star_metals)
    # Estimate the star's softening length as the closest particle distance
    #snapshot.star['eps'] = r.min()

    # Make param file
    param = isaac.make_param(snapshot, snapshotName)
    param['dMeanMolWeight'] = m

    gc.collect()

    # CALCULATE VELOCITY USING calc_velocity.py.  This also estimates the
    # gravitational softening length eps
    print 'Calculating circular velocity'
    preset = settings.changa_run.preset
    max_particles = global_settings['misc']['max_particles']
    calc_velocity.v_xy(snapshot,
                       param,
                       changa_preset=preset,
                       max_particles=max_particles)

    gc.collect()

    # -------------------------------------------------
    # Estimate time step for changa to use
    # -------------------------------------------------
    # Save param file
    isaac.configsave(param, paramName, 'param')
    # Save snapshot
    snapshot.write(filename=snapshotName, fmt=pynbody.tipsy.TipsySnap)
    # est dDelta
    dDelta = ICgen_utils.est_time_step(paramName, preset)
    param['dDelta'] = dDelta

    # -------------------------------------------------
    # Create director file
    # -------------------------------------------------
    # largest radius to plot
    r_director = float(0.9 * r.max())
    # Maximum surface density
    sigma_min = float(ICobj.sigma(r_director))
    # surface density at largest radius
    sigma_max = float(ICobj.sigma.input_dict['sigma'].max())
    # Create director dict
    director = isaac.make_director(sigma_min,
                                   sigma_max,
                                   r_director,
                                   filename=param['achOutName'])
    ## Save .director file
    #isaac.configsave(director, directorName, 'director')

    #Now that velocities and everything are all initialized for gas particles, create new snapshot to return in which
    #single star particle is replaced by 2, same units as above
    snapshotBinary = pynbody.new(star=2, gas=nParticles)
    snapshotBinary['eps'] = 0.01 * SimArray(
        np.ones(nParticles + 2, dtype=np.float32), pos_unit)
    snapshotBinary['metals'] = SimArray(
        np.zeros(nParticles + 2, dtype=np.float32))
    snapshotBinary['vel'].units = v_unit
    snapshotBinary['pos'].units = pos_unit
    snapshotBinary['mass'].units = snapshot['mass'].units
    snapshotBinary['rho'] = SimArray(np.zeros(nParticles + 2,
                                              dtype=np.float32))

    #Assign gas particles with calculated/given values from above
    snapshotBinary.gas['pos'] = snapshot.gas['pos']
    snapshotBinary.gas['vel'] = snapshot.gas['vel']
    snapshotBinary.gas['temp'] = snapshot.gas['temp']
    snapshotBinary.gas['rho'] = snapshot.gas['rho']
    snapshotBinary.gas['eps'] = snapshot.gas['eps']
    snapshotBinary.gas['mass'] = snapshot.gas['mass']
    snapshotBinary.gas['metals'] = snapshot.gas['metals']

    #Load Binary system obj to initialize system
    binsys = ICobj.settings.physical.binsys

    x1, x2, v1, v2 = binsys.generateICs()

    #Put velocity in sim units
    #!!! Note: v_unit_vel will always be 29.785598165 km/s when m_unit = Msol and r_unit = 1 AU in kpc!!!
    #conv = v_unit_vel #km/s in sim units
    #v1 /= conv
    #v2 /= conv

    #Assign position, velocity assuming CCW orbit

    snapshotBinary.star[0]['pos'] = SimArray(x1, pos_unit)
    snapshotBinary.star[0]['vel'] = SimArray(v1, v_unit)
    snapshotBinary.star[1]['pos'] = SimArray(x2, pos_unit)
    snapshotBinary.star[1]['vel'] = SimArray(v2, v_unit)

    #Set stellar masses
    #Set Mass units
    #Create simArray for mass, convert units to simulation mass units
    priMass = SimArray(binsys.m1, m_unit)
    secMass = SimArray(binsys.m2, m_unit)

    snapshotBinary.star[0]['mass'] = priMass
    snapshotBinary.star[1]['mass'] = secMass
    snapshotBinary.star['metals'] = SimArray(star_metals)

    #Estimate stars' softening length as fraction of distance to COM
    d = np.sqrt(AddBinary.dotProduct(x1 - x2, x1 - x2))

    snapshotBinary.star[0]['eps'] = SimArray(math.fabs(d) / 4.0, pos_unit)
    snapshotBinary.star[1]['eps'] = SimArray(math.fabs(d) / 4.0, pos_unit)

    print 'Wrapping up'
    # Now set the star particle's tform to a negative number.  This allows
    # UW ChaNGa treat it as a sink particle.
    snapshotBinary.star['tform'] = -1.0

    #Set Sink Radius to be mass-weighted average of Roche lobes of two stars
    r1 = AddBinary.calcRocheLobe(binsys.m1 / binsys.m2, binsys.a)
    r2 = AddBinary.calcRocheLobe(binsys.m2 / binsys.m1, binsys.a)
    p = isaac.strip_units(binsys.m1 / (binsys.m1 + binsys.m2))

    r_sink = (r1 * p) + (r2 * (1.0 - p))
    param['dSinkBoundOrbitRadius'] = r_sink
    param['dSinkRadius'] = r_sink
    param['dSinkMassMin'] = 0.9 * isaac.strip_units(secMass)
    param['bDoSinks'] = 1

    return snapshotBinary, param, director
def find_clumps(f, n_smooth=32, param=None, arg_string=None, seed=None, verbose=True):
    """
    Uses skid (https://github.com/N-BodyShop/skid) to find clumps in a gaseous
    protoplanetary disk.  
    
    The linking length used is equal to the gravitational softening length of
    the gas particles.
    
    The density cut-off comes from the criterion that there are n_smooth particles
    within the Hill sphere of a particle.  This is formulated mathematically as:
    
        rho_min = 3*n_smooth*Mstar/R^3
        
    where R is the distance from the star.  The trick used here is to multiply
    all particle masses by R^3 before running skid so the density cut-off is:
    
        rho_min = 3*n_smooth*Mstar
        
    **ARGUMENTS**
    
    *f* : TipsySnap, or str
        A tipsy snapshot loaded/created by pynbody -OR- a filename pointing to a
        snapshot.
    
    *n_smooth* : int (optional)
        Number of particles used in SPH calculations.  Should be the same as used
        in the simulation.  Default = 32
    
    *param* : str (optional)
        filename for a .param file for the simulation
    
    *arg_string* : str (optional)
        Additional arguments to be passed to skid.  Cannot use -tau, -d, -m, -s, -o
    
    *seed* : int
        An integer used to seed the random filename generation for temporary
        files.  Necessary for multiprocessing and should be unique for each
        thread.
        
    *verbose* : bool
        Verbosity flag.  Default is True
    
    **RETURNS**
    
    *clumps* : array, int-like
        Array containing the group number each particle belongs to, with star
        particles coming after gas particles.  A zero means the particle belongs
        to no groups
    """
    # Parse areguments
    if isinstance(f, str):
        
        f = pynbody.load(f, paramfile=param)
        
    if seed is not None:
        
        np.random.seed(seed)
        
    # Estimate the linking length as the gravitational softening length
    tau = f.g['eps'][0]
    
    # Calculate minimum density
    rho_min = 3*n_smooth*f.s['mass'][0]
    
    # Center on star.  This is done because R used in hill-sphere calculations
    # is relative to the star
    star_pos = f.s['pos'].copy()
    f['pos'] -= star_pos
    
    # Scale mass by R^3
    R = isaac.strip_units(f['rxy'])
    m0 = f['mass'].copy()
    f['mass'] *= (R+tau)**3
    
    # Save temporary snapshot
    f_prefix = str(np.random.randint(np.iinfo(int).max))
    f_name = f_prefix + '.std'
    
    # Save temporary .param file
    if param is not None:
        
        param_name = f_prefix + '.param'
        param_dict = isaac.configparser(param, 'param')
        isaac.configsave(param_dict, param_name)
        
    f.write(filename=f_name, fmt=pynbody.tipsy.TipsySnap)
        
    f['pos'] += star_pos
    f['mass'] = m0
    
    command = 'totipnat < {} | skid -tau {:.2e} -d {:.2e} -m {:d} -s {:d} -o {}'\
    .format(f_name, tau, rho_min, n_smooth, n_smooth, f_prefix)
    p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    
    if verbose:
        
        for line in iter(p.stdout.readline, ''):
            print line,
            
    p.wait()
    
    # Load clumps
    clumps = isaac.loadhalos(f_prefix + '.grp')
    
    # Cleanup
    for name in glob.glob(f_prefix + '*'):
        
        os.remove(name)
        
    return clumps
Example #20
0
def rho_z(sigma, T, r, settings):
    """ 
    rho,z = rho_z(...)
    
    Calculates rho(z) to maintain hydrostatic equilibrium in a thin disc.  
    Assumes uniform temperature in the disc, and an infinite disc where 
    rho can be treated (locally) as only a function of z.
    
    Only calculates for z>=0, since the density is assumed to be symmetric
    about z=0
    
    The initial guess for rho (a gaussian) only really seems to work for
    Mstar >> Mdisc.  Otherwise the solution can diverge violently.
    
    * NUMERICAL CALCULATION OF RHO(Z) *
    The calculation proceeds using several steps.
    1) Make an initial guess for I, the integral of rho from z to inf.  This
        is an error function
    2) Modify length scale of the initial guess to minimize the residual
        for the differential equation governing I.  Use this as the new
        initial guess.
    3) Find the root I(z) for the differential equation governing I, with
        the boundary condition that I(0) = sigma/2
    4) Set rho = -dI/dz
    5) Find the root rho(z) for the diff. eq. governing rho.
    6) In order to satisfy the BC on I, scale rho so that:
        Integral(rho) = I(0)
    7) Repeat (5) and (6) until rho is rescaled by a factor closer to unity
        than rho_tol
        
        Steps 5-7 are done because the solution for I does not seem to
        satisfy the diff. eq. for rho very well.  But doing it this way 
        allows rho to satisfy the surface density profile
    
    * Arguments *
    
    sigma - The surface density at r
    __stdout__
    T - the temperature at r
    
    r - The radius at which rho is being calculated.  Should have units
    
    settings - ICobj settings (ie, ICobj.settings)
        
    * Output *
    Returns a 1D SimArray (see pynbody) of rho(z) and a 1D SimArray of z,
    with the same units as ICobj.settings.rho_calc.zmax
    
    """
    # Parse settings
    rho_tol = settings.rho_calc.rho_tol
    nz = settings.rho_calc.nz
    zmax = settings.rho_calc.zmax

    m = settings.physical.m
    M = settings.physical.M

    # Physical constants
    kB = SimArray(1.0, 'k')
    G = SimArray(1.0, 'G')

    # Set up default units
    mass_unit = M.units
    length_unit = zmax.units
    r = (r.in_units(length_unit)).copy()

    # Initial conditions/physical parameters
    rho_int = 0.5 * sigma.in_units(
        mass_unit / length_unit**2)  # Integral of rho from 0 to inf
    a = (G * M * m / (kB * T)).in_units(length_unit)
    b = (2 * np.pi * G * m / (kB * T)).in_units(length_unit / mass_unit)
    z0guess = np.sqrt(2 * r * r * r / a).in_units(
        length_unit)  # Est. scale height of disk
    z0_dummy = (2 / (b * sigma)).in_units(length_unit)

    z = np.linspace(0.0, zmax, nz)
    dz = z[[1]] - z[[0]]

    # Echo parameters used
    print '***********************************************'
    print '* Calculating rho(z)'
    print '***********************************************'
    print 'sigma          = {0} {1}'.format(sigma, sigma.units)
    print 'zmax           = {0} {1}'.format(zmax, zmax.units)
    print 'r              = {0} {1}'.format(r, r.units)
    print 'molecular mass = {0} {1}'.format(m, m.units)
    print 'Star mass      = {0} {1}'.format(M, M.units)
    print 'Temperature    = {0} {1}'.format(T, T.units)
    print ''
    print 'rho_tol        = {0}'.format(rho_tol)
    print 'nz             = {0}'.format(nz)
    print '***********************************************'
    print 'a              = {0} {1}'.format(a, a.units)
    print 'b              = {0} {1}'.format(b, b.units)
    print 'z0guess        = {0} {1}'.format(z0guess, z0guess.units)
    print '***********************************************'

    print 'z0 (from sech^2) = {0} {1}'.format(z0_dummy, z0_dummy.units)
    # --------------------------------------------------------
    # STRIP THE UNITS FROM EVERYTHING!!!
    # This has to be done because many of the scipy/numpy functions used cannot
    # handle pynbody units.  Before returning z, rho, or anything else, the
    # Units must be re-introduced
    # --------------------------------------------------------

    rho_int, a, b, z0guess, z0_dummy, z, dz, r, T, sigma \
    = isaac.strip_units([rho_int, a, b, z0guess, z0_dummy, z, dz, r, T, sigma])

    # --------------------------------------------------------
    # Check sigma and T
    # --------------------------------------------------------
    if sigma < 1e-100:

        warn('Sigma too small.  setting rho = 0')
        rho0 = np.zeros(len(z))
        # Set up units
        rho0 = isaac.set_units(rho0, mass_unit / length_unit**3)
        z = isaac.set_units(z, length_unit)

        return rho0, z

    if T > 1e100:

        warn('Temperature too large.  Setting rho = 0')
        rho0 = np.zeros(len(z))
        # Set up units
        rho0 = isaac.set_units(rho0, mass_unit / length_unit**3)
        z = isaac.set_units(z, length_unit)

        return rho0, z

    # -------------------------------------------------------------------
    # FUNCTION DEFINITIONS
    # -------------------------------------------------------------------
    def dI_dz(I_in):
        """
        Finite difference approximation of dI/dz, assuming I is odd around I(0)
        """
        I = I_in.copy()
        dI = np.zeros(len(I))
        # Fourth order center differencing
        dI[0] = (-I[2] + 8 * I[1] - 7 * I[0]) / (6 * dz)
        dI[1] = (-I[3] + 8 * I[2] - 6 * I[0] - I[1]) / (12 * dz)
        dI[2:-2] = (-I[4:] + 8 * I[3:-1] - 8 * I[1:-3] + I[0:-4]) / (12 * dz)
        # Second order backward differencing for right edge
        dI[-2:] = (3 * I[-2:] - 4 * I[-3:-1] + I[-4:-2]) / (2 * dz)

        return dI

    def d2I_dz2(I_in):

        # Finite difference for d2I/dz2 assuming it is 0 at the origin
        I = I_in.copy()
        d2I = np.zeros(len(I))
        # Boundary condition
        d2I[0] = 0
        # Centered 4th order finite difference
        d2I[1] = (-I[3] + 16 * I[2] - 30 * I[1] + 16 * I[0] -
                  (2 * I[0] - I[1])) / (12 * dz**2)
        d2I[2:-2] = (-I[4:] + 16 * I[3:-1] - 30 * I[2:-2] + 16 * I[1:-3] -
                     I[0:-4]) / (12 * (dz**2))
        # second order backward difference for right edge
        d2I[-2:] = (-2 * I[-2:] + 5 * I[-3:-1] - 4 * I[-4:-2] +
                    I[-5:-3]) / dz**2

        return d2I

    def Ires(I_in):
        """
        Calculate the residual for the differential equation governing I,
        the integral of rho from z to "infinity."
        """
        # DEFINE INITIAL CONDITION:
        I = I_in.copy()
        I[0] = rho_int
        #I[-1] = 0.0
        weight = 1.0

        res = d2I_dz2(I) + dI_dz(I) * (a * z / ((z**2 + r**2)**(1.5)) + 2 * b *
                                       (I[0] - I))

        return weight * res

    def drho_dz(rho_in):
        """
        Fourth order, centered finite difference for d(rho)/dz, assumes that
        rho is an even function.  The right-hand boundary is done using
        backward differencing
        """
        rho = rho_in.copy()
        drho = np.zeros(len(rho))
        drho[0] = 0.0  # defined by boundary condition, rho[0] = max(rho)
        drho[1] = (-rho[3] + 8 * rho[2] - 8 * rho[0] + rho[1]) / (12 * dz)
        drho[2:-2] = (-rho[4:] + 8 * rho[3:-1] - 8 * rho[1:-3] +
                      rho[0:-4]) / (12 * dz)
        drho[-2:] = (3 * rho[-2:] - 4 * rho[-3:-1] + rho[-4:-2]) / (2 * dz)

        return drho

    def residual(rho_in):
        """
        Estimate d(rho)/dz
        """
        rho = rho_in.copy()
        # Estimate integral of rho
        I = np.zeros(len(rho))
        I[1:] = nInt.cumtrapz(rho, z)
        # Estimate residual
        res = drho_dz(rho) + a * rho * z / (
            (z**2 + r**2)**(1.5)) + 2 * b * rho * I

        return res

    def erf_res(scale_size):

        testfct = rho_int * (1 - scipy.special.erf(z / scale_size))

        return abs(Ires(testfct)).sum()

    pass
    # -------------------------------------------------------------------
    # FIND RHO
    # -------------------------------------------------------------------

    maxiter = 40

    # Estimate the scale length of the error function
    z0 = opt.fminbound(erf_res, z0guess / 100.0, 5.0 * z0guess)
    print 'Length scale guess: {0} {1}'.format(z0guess, length_unit)
    print 'Final length scale: {0} {1}'.format(z0, length_unit)

    # Begin by finding I, the integral of rho (from z to inf)
    # Assuming rho is gaussian, I is an error function
    guess = rho_int * (1 - scipy.special.erf(z / z0))

    # Find the root of the differential equation for I
    f_tol = rho_int * 6e-6
    try:

        Isol = opt.newton_krylov(Ires, guess, maxiter=maxiter, f_tol=f_tol)

    except NoConvergence:
        # Assume it didn't converge because f_tol was too strict
        # Read exception
        xepshun = sys.exc_info()
        # Extract rho from the exception
        Isol = xepshun[1][0]

    # rho is the negative derivative
    rho0 = -dI_dz(Isol)

    # Now apply the diff eq on rho
    for n in range(maxiter):

        print 'Iteration {0}'.format(n + 1)
        f_tol = rho0.max() * 6e-6
        try:

            rho0 = opt.newton_krylov(residual,
                                     rho0,
                                     maxiter=maxiter,
                                     f_tol=f_tol)

        except:
            # Assume it didn't converge because f_tol was too strict
            # Read exception
            xepshun = sys.exc_info()
            # Extract rho from the exception
            rho0 = xepshun[1][0]

        rho_scale = rho_int / nInt.cumtrapz(rho0, z)[-1]
        print 'Scaling rho by {0}'.format(rho_scale)
        rho0 = rho0 * rho_scale

        if abs(1 - rho_scale) < rho_tol - 1:

            break

    if n >= maxiter:

        print 'Warning: solution to rho did not converge for r = {0}'.format(r)

    # Re-introduce units
    rho0 = isaac.set_units(rho0, mass_unit / length_unit**3)
    z = isaac.set_units(z, length_unit)

    return SimArray(rho0, 'Msol au**-3'), SimArray(z, 'au')
Example #21
0
def rho_z(sigma, T, r, settings):
    """ 
    rho,z = rho_z(...)
    
    Calculates rho(z) to maintain hydrostatic equilibrium in a thin disc.  
    Assumes uniform temperature in the disc, and an infinite disc where 
    rho can be treated (locally) as only a function of z.
    
    Only calculates for z>=0, since the density is assumed to be symmetric
    about z=0
    
    The initial guess for rho (a gaussian) only really seems to work for
    Mstar >> Mdisc.  Otherwise the solution can diverge violently.
    
    * NUMERICAL CALCULATION OF RHO(Z) *
    The calculation proceeds using several steps.
    1) Make an initial guess for I, the integral of rho from z to inf.  This
        is an error function
    2) Modify length scale of the initial guess to minimize the residual
        for the differential equation governing I.  Use this as the new
        initial guess.
    3) Find the root I(z) for the differential equation governing I, with
        the boundary condition that I(0) = sigma/2
    4) Set rho = -dI/dz
    5) Find the root rho(z) for the diff. eq. governing rho.
    6) In order to satisfy the BC on I, scale rho so that:
        Integral(rho) = I(0)
    7) Repeat (5) and (6) until rho is rescaled by a factor closer to unity
        than rho_tol
        
        Steps 5-7 are done because the solution for I does not seem to
        satisfy the diff. eq. for rho very well.  But doing it this way 
        allows rho to satisfy the surface density profile
    
    * Arguments *
    
    sigma - The surface density at r
    __stdout__
    T - the temperature at r
    
    r - The radius at which rho is being calculated.  Should have units
    
    settings - ICobj settings (ie, ICobj.settings)
        
    * Output *
    Returns a 1D SimArray (see pynbody) of rho(z) and a 1D SimArray of z,
    with the same units as ICobj.settings.rho_calc.zmax
    
    """
    # Parse settings
    rho_tol = settings.rho_calc.rho_tol
    nz = settings.rho_calc.nz
    zmax = settings.rho_calc.zmax
    
    m = settings.physical.m
    M = settings.physical.M
    
    # Physical constants
    kB = SimArray(1.0,'k')
    G = SimArray(1.0,'G')
    
    # Set up default units
    mass_unit = M.units
    length_unit = zmax.units
    r = (r.in_units(length_unit)).copy()
    
    # Initial conditions/physical parameters
    rho_int = 0.5*sigma.in_units(mass_unit/length_unit**2)   # Integral of rho from 0 to inf
    a = (G*M*m/(kB*T)).in_units(length_unit)
    b = (2*np.pi*G*m/(kB*T)).in_units(length_unit/mass_unit)
    z0guess = np.sqrt(2*r*r*r/a).in_units(length_unit)# Est. scale height of disk
    z0_dummy = (2/(b*sigma)).in_units(length_unit)

    z = np.linspace(0.0,zmax,nz)
    dz = z[[1]]-z[[0]]
    
    # Echo parameters used
    print '***********************************************'
    print '* Calculating rho(z)'
    print '***********************************************'
    print 'sigma          = {0} {1}'.format(sigma,sigma.units)
    print 'zmax           = {0} {1}'.format(zmax,zmax.units)
    print 'r              = {0} {1}'.format(r,r.units)
    print 'molecular mass = {0} {1}'.format(m,m.units)
    print 'Star mass      = {0} {1}'.format(M,M.units)
    print 'Temperature    = {0} {1}'.format(T,T.units)
    print ''
    print 'rho_tol        = {0}'.format(rho_tol)
    print 'nz             = {0}'.format(nz)
    print '***********************************************'
    print 'a              = {0} {1}'.format(a,a.units)
    print 'b              = {0} {1}'.format(b,b.units)
    print 'z0guess        = {0} {1}'.format(z0guess,z0guess.units)
    print '***********************************************'
    
    print 'z0 (from sech^2) = {0} {1}'.format(z0_dummy,z0_dummy.units)
    # --------------------------------------------------------
    # STRIP THE UNITS FROM EVERYTHING!!!
    # This has to be done because many of the scipy/numpy functions used cannot
    # handle pynbody units.  Before returning z, rho, or anything else, the
    # Units must be re-introduced
    # --------------------------------------------------------
    
    rho_int, a, b, z0guess, z0_dummy, z, dz, r, T, sigma \
    = isaac.strip_units([rho_int, a, b, z0guess, z0_dummy, z, dz, r, T, sigma])
    
    # --------------------------------------------------------
    # Check sigma and T
    # --------------------------------------------------------
    if sigma < 1e-100:
        
        warn('Sigma too small.  setting rho = 0')
        rho0 = np.zeros(len(z))
        # Set up units
        rho0 = isaac.set_units(rho0, mass_unit/length_unit**3)
        z = isaac.set_units(z, length_unit)
        
        return rho0, z
        
    if T > 1e100:
        
        warn('Temperature too large.  Setting rho = 0')
        rho0 = np.zeros(len(z))
        # Set up units
        rho0 = isaac.set_units(rho0, mass_unit/length_unit**3)
        z = isaac.set_units(z, length_unit)
        
        return rho0, z  
        
    # -------------------------------------------------------------------
    # FUNCTION DEFINITIONS
    # -------------------------------------------------------------------
    def dI_dz(I_in):
        """
        Finite difference approximation of dI/dz, assuming I is odd around I(0)
        """
        I = I_in.copy()
        dI = np.zeros(len(I))
        # Fourth order center differencing
        dI[0] = (-I[2] + 8*I[1] - 7*I[0])/(6*dz)
        dI[1] = (-I[3] + 8*I[2] - 6*I[0] - I[1])/(12*dz)
        dI[2:-2] = (-I[4:] + 8*I[3:-1] -8*I[1:-3] + I[0:-4])/(12*dz)
        # Second order backward differencing for right edge
        dI[-2:] = (3*I[-2:] -4*I[-3:-1] + I[-4:-2])/(2*dz)
        
        return dI
        
    def d2I_dz2(I_in):
        
        # Finite difference for d2I/dz2 assuming it is 0 at the origin
        I = I_in.copy()
        d2I = np.zeros(len(I))
        # Boundary condition
        d2I[0] = 0
        # Centered 4th order finite difference
        d2I[1] = (-I[3] + 16*I[2] - 30*I[1] + 16*I[0] -(2*I[0] - I[1]))/(12*dz**2)
        d2I[2:-2] = (-I[4:] + 16*I[3:-1] - 30*I[2:-2] + 16*I[1:-3] - I[0:-4])/(12*(dz**2))
        # second order backward difference for right edge
        d2I[-2:] = (-2*I[-2:] + 5*I[-3:-1] -4*I[-4:-2] + I[-5:-3])/dz**2
        
        return d2I
        
    def Ires(I_in):
        """
        Calculate the residual for the differential equation governing I,
        the integral of rho from z to "infinity."
        """
        # DEFINE INITIAL CONDITION:
        I = I_in.copy()
        I[0] = rho_int
        #I[-1] = 0.0
        weight = 1.0

        res = d2I_dz2(I) + dI_dz(I)*(a*z/((z**2 + r**2)**(1.5)) + 2*b*(I[0] - I))
        
        return weight*res
        
    def drho_dz(rho_in):
        """
        Fourth order, centered finite difference for d(rho)/dz, assumes that
        rho is an even function.  The right-hand boundary is done using
        backward differencing
        """
        rho = rho_in.copy()
        drho = np.zeros(len(rho))
        drho[0] = 0.0   # defined by boundary condition, rho[0] = max(rho)
        drho[1] = (-rho[3] + 8*rho[2] - 8*rho[0] + rho[1])/(12*dz)
        drho[2:-2] = (-rho[4:] + 8*rho[3:-1] - 8*rho[1:-3] + rho[0:-4])/(12*dz)
        drho[-2:] = (3*rho[-2:] - 4*rho[-3:-1] + rho[-4:-2])/(2*dz)
        
        return drho
        
    def residual(rho_in):
        """
        Estimate d(rho)/dz
        """
        rho = rho_in.copy()
        # Estimate integral of rho
        I = np.zeros(len(rho))
        I[1:] = nInt.cumtrapz(rho,z)
        # Estimate residual 
        res = drho_dz(rho) + a*rho*z/((z**2 + r**2)**(1.5)) + 2*b*rho*I
        
        return res
        
    def erf_res(scale_size):
        
        testfct = rho_int*(1 - scipy.special.erf(z/scale_size))
        
        return abs(Ires(testfct)).sum()
        

    pass
    # -------------------------------------------------------------------
    # FIND RHO
    # -------------------------------------------------------------------
    
    maxiter = 40
    
    # Estimate the scale length of the error function
    z0 = opt.fminbound(erf_res,z0guess/100.0,5.0*z0guess)
    print 'Length scale guess: {0} {1}'.format(z0guess, length_unit)
    print 'Final length scale: {0} {1}'.format(z0, length_unit)
    
    # Begin by finding I, the integral of rho (from z to inf)
    # Assuming rho is gaussian, I is an error function
    guess = rho_int*(1 - scipy.special.erf(z/z0))
    
    # Find the root of the differential equation for I
    f_tol = rho_int * 6e-6
    try:
        
        Isol = opt.newton_krylov(Ires,guess,maxiter=maxiter,f_tol=f_tol)
        
    except NoConvergence:
        # Assume it didn't converge because f_tol was too strict
        # Read exception
        xepshun = sys.exc_info()
        # Extract rho from the exception
        Isol = xepshun[1][0]
        
    # rho is the negative derivative
    rho0 = -dI_dz(Isol)
    
    # Now apply the diff eq on rho
    for n in range(maxiter):
        
        print 'Iteration {0}'.format(n+1)
        f_tol = rho0.max() * 6e-6
        try:
            
            rho0 = opt.newton_krylov(residual,rho0,maxiter=maxiter, f_tol=f_tol)
            
        except:
            # Assume it didn't converge because f_tol was too strict
            # Read exception
            xepshun = sys.exc_info()
            # Extract rho from the exception
            rho0 = xepshun[1][0]
            
        rho_scale = rho_int/nInt.cumtrapz(rho0,z)[-1]
        print 'Scaling rho by {0}'.format(rho_scale)
        rho0 = rho0*rho_scale
        
        if abs(1-rho_scale) < rho_tol - 1:
            
            break
        
    if n >= maxiter:
        
        print 'Warning: solution to rho did not converge for r = {0}'.format(r)
    
    # Re-introduce units
    rho0 = isaac.set_units(rho0, mass_unit/length_unit**3)
    z = isaac.set_units(z, length_unit)
    
    return SimArray(rho0,'Msol au**-3'), SimArray(z,'au')
Example #22
0
def findCBResonances(s, r, r_min, r_max, m_max=4, l_max=4, bins=50):
    """
    Given Tipsy snapshot, computes the resonances of disk on binary as a function of orbital angular frequency omega.
    Disk radius, in au, is convered to angular frequency which will then be used to compute corotation 
    and inner/outer Lindblad resonances.
   
   Note: r given MUST correspond to r over which de/dt was calculated.  Otherwise, scale gets all messed up
 
     Parameters
     ----------
     s: Tipsy-format snapshot
     r: array
         radius array over which de/dt was calculated
     r_min,r_max: floats
         min/maximum disk radius for calculations (au)
     bins: int
         number of radial bins to calculate over
     m_max,l_max: ints
         maximum orders of (m,l) LR

    Returns
    -------
    Orbital frequency: numpy array
        for corotation and inner/outer resonances and radii as float and numpy arrays
    """
    stars = s.stars

    m_min = 1  #m >=1 for LRs, CRs
    l_min = 1  #l >=1 for LRs, CRs

    #Compute binary angular frequency
    #Strip units from all inputs
    #x1 = np.asarray(isaac.strip_units(stars[0]['pos']))
    #x2 = np.asarray(isaac.strip_units(stars[1]['pos']))
    #v1 = np.asarray(isaac.strip_units(stars[0]['vel']))
    #v2 = np.asarray(isaac.strip_units(stars[1]['vel']))
    #m1 = np.asarray(isaac.strip_units(stars[0]['mass']))
    #m2 = np.asarray(isaac.strip_units(stars[1]['mass']))
    x1 = stars[0]['pos']
    x2 = stars[1]['pos']
    v1 = stars[0]['vel']
    v2 = stars[1]['vel']
    m1 = stars[0]['mass']
    m2 = stars[1]['mass']

    a = isaac.strip_units(AddBinary.calcSemi(x1, x2, v1, v2, m1, m2))
    omega_b = 2.0 * np.pi / AddBinary.aToP(a, m1 + m2)  #In units 1/day

    #Compute omega_disk in units 1/day (like omega_binary)
    omega_d = 2.0 * np.pi / AddBinary.aToP(r, m1 + m2)

    #Compute kappa (radial epicycle frequency = sqrt(r * d(omega^2)/dr + 4*(omega^2))
    o2 = omega_d * omega_d
    dr = (r.max() - r.min()) / float(bins)  #Assuming r has evenly spaced bins!
    drdo2 = np.gradient(o2, dr)  #I mean d/dr(omega^2)
    kappa = np.sqrt(r * drdo2 + 4.0 * o2)

    #Allocate arrays for output
    omega_Lo = np.zeros((m_max, l_max))
    omega_Li = np.zeros((m_max, l_max))
    o_c = np.zeros(l_max)

    #Find resonance angular frequency
    for m in range(m_min, m_max + 1):
        for l in range(l_min, l_max + 1):
            outer = omega_d + (float(l) / m) * kappa
            inner = omega_d - (float(l) / m) * kappa
            omega_Lo[m - m_min,
                     l - l_min] = omega_d[np.argmin(np.fabs(omega_b - outer))]
            omega_Li[m - m_min,
                     l - l_min] = omega_d[np.argmin(np.fabs(omega_b - inner))]

            #Find corotation resonance where omega_d ~ omega_b
            o_c[l - l_min] = omega_d[np.argmin(
                np.fabs(omega_d - omega_b / float(l)))]

    return omega_Li, omega_Lo, o_c, omega_d, kappa
Example #23
0
def v_xy(f, param, changbin=None, nr=50, min_per_bin=100):
    """
    Attempts to calculate the circular velocities for particles in a thin
    (not flat) keplerian disk.  Requires ChaNGa
    
    **ARGUMENTS**
    
    f : tipsy snapshot
        For a gaseous disk
    param : dict
        a dictionary containing params for changa. (see isaac.configparser)
    changbin : str  (OPTIONAL)  
        If set, should be the full path to the ChaNGa executable.  If None, 
        an attempt to find ChaNGa is made
    nr : int (optional)
        number of radial bins to use when averaging over accelerations
    min_per_bin : int (optional)
        The minimum number of particles to be in each bin.  If there are too
        few particles in a bin, it is merged with an adjacent bin.  Thus,
        actual number of radial bins may be less than nr.
        
    **RETURNS**
    
    vel : SimArray
        An N by 3 SimArray of gas particle velocities.
    """
    
    if changbin is None:
        # Try to find the ChaNGa binary full path
        changbin = os.popen('which ChaNGa_uw_mpi').read().strip()
        
    # Load up mpi
    
    # Load stuff from the snapshot
    x = f.g['x']
    y = f.g['y']
    z = f.g['z']
    r = f.g['rxy']
    vel0 = f.g['vel'].copy()
    
    # Remove units from all quantities
    r = isaac.strip_units(r)
    x = isaac.strip_units(x)
    y = isaac.strip_units(y)
    z = isaac.strip_units(z)
    
    # Temporary filenames for running ChaNGa
    f_prefix = str(np.random.randint(0, 2**32))
    f_name = f_prefix + '.std'
    p_name = f_prefix + '.param'
    
    # Update parameters
    p_temp = param.copy()
    p_temp['achInFile'] = f_name
    p_temp['achOutName'] = f_prefix
    if 'dDumpFrameTime' in p_temp: p_temp.pop('dDumpFrameTime')
    if 'dDumpFrameStep' in p_temp: p_temp.pop('dDumpFrameStep')
    
    # --------------------------------------------
    # Estimate velocity from gravity only
    # --------------------------------------------
    # Note, accelerations due to gravity are calculated twice to be extra careful
    # This is so that any velocity dependent effects are properly accounted for
    # (although, ideally, there should be none)
    # The second calculation uses the updated velocities from the first
    for iGrav in range(2):
        # Save files
        f.write(filename=f_name, fmt = pynbody.tipsy.TipsySnap)
        isaac.configsave(p_temp, p_name, ftype='param')
        
        # Run ChaNGa, only calculating gravity
        command = 'mpirun --mca mtl mx --mca pml cm ' + changbin + ' -gas -n 0 ' + p_name
        #command = 'charmrun ++local ' + changbin + ' -gas -n 0 ' + p_name
        
        p = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
        
        while p.poll() is None:
            
            time.sleep(0.1)
            
    
        # Load accelerations
        acc_name = f_prefix + '.000000.acc2'
        a = isaac.load_acc(acc_name)
        
        # Clean-up
        for fname in glob.glob(f_prefix + '*'): os.remove(fname)
        
        # If a is not a vector, calculate radial acceleration.  Otherwise, assume
        # a is the radial acceleration                
        a_r = a[:,0]*x/r + a[:,1]*y/r
        
        # Make sure the units are correct then remove them
        a_r = isaac.match_units(a_r, a)[0]
        a_r = isaac.strip_units(a_r)
        
        # Calculate cos(theta) where theta is angle above x-y plane
        cos = r/np.sqrt(r**2 + z**2)
        ar2 = a_r*r**2
        
        # Bin the data
        r_edges = np.linspace(r.min(), (1+np.spacing(2))*r.max(), nr + 1)
        ind, r_edges = isaac.digitize_threshold(r, min_per_bin, r_edges)
        ind -= 1
        nr = len(r_edges) - 1
        
        r_bins, ar2_mean, err = isaac.binned_mean(r, ar2, binedges=r_edges, \
        weighted_bins=True)
        
        # Fit lines to ar2 vs cos for each radial bin
        m = np.zeros(nr)
        b = np.zeros(nr)    
        
        for i in range(nr):
            
            mask = (ind == i)
            p = np.polyfit(cos[mask], ar2[mask], 1)
            m[i] = p[0]
            b[i] = p[1]
            
        # Interpolate the line fits
        m_spline = isaac.extrap1d(r_bins, m)
        b_spline = isaac.extrap1d(r_bins, b)
        
        # Calculate circular velocity
        ar2_calc = m_spline(r)*cos + b_spline(r)
        v_calc = np.sqrt(abs(ar2_calc)/r)
        vel = f.g['vel'].copy()
        v_calc = isaac.match_units(v_calc,vel)[0]
        vel[:,0] = -v_calc*y/r
        vel[:,1] = v_calc*x/r
        
        # Assign to f
        f.g['vel'] = vel
        
    # --------------------------------------------
    # Estimate pressure/gas dynamics accelerations
    # --------------------------------------------
    a_grav = a
    ar2_calc_grav = ar2_calc
    
    # Save files
    f.write(filename=f_name, fmt = pynbody.tipsy.TipsySnap)
    isaac.configsave(p_temp, p_name, ftype='param')
    
    # Run ChaNGa, including SPH
    command = 'mpirun --mca mtl mx --mca pml cm ' + changbin + ' +gas -n 0 ' + p_name
    
    p = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
    
    while p.poll() is None:
        
        time.sleep(0.1)
        
    # Load accelerations
    acc_name = f_prefix + '.000000.acc2'
    a_total = isaac.load_acc(acc_name)
    
    # Clean-up
    for fname in glob.glob(f_prefix + '*'): os.remove(fname)
    
    # Estimate the accelerations due to pressure gradients/gas dynamics
    a_gas = a_total - a_grav
    ar_gas = a_gas[:,0]*x/r + a_gas[:,1]*y/r
    ar_gas = isaac.strip_units(ar_gas)
    ar2_gas = ar_gas*r**2
    
    logr_bins, ratio, err = isaac.binned_mean(np.log(r), ar2_gas/ar2_calc_grav, nbins=nr,\
    weighted_bins=True)
    r_bins = np.exp(logr_bins)
    ratio_spline = isaac.extrap1d(r_bins, ratio)
    
    ar2_calc = ar2_calc_grav*(1 + ratio_spline(r))
    a_calc = ar2_calc/r**2
    
    v = np.sqrt(r*abs(a_calc))
    v = isaac.match_units(v, vel0.units)[0]
    vel = vel0.copy()
    vel[:,0] = -v*y/r
    vel[:,1] = v*x/r
    
    # more cleanup
    f.g['vel'] = vel0
    
    return vel
Example #24
0
def snapshot_gen(ICobj):
    """
    Generates a tipsy snapshot from the initial conditions object ICobj.
    
    Returns snapshot, param
    
        snapshot: tipsy snapshot
        param: dictionary containing info for a .param file
    """

    print 'Generating snapshot...'
    # Constants
    G = SimArray(1.0, 'G')
    # ------------------------------------
    # Load in things from ICobj
    # ------------------------------------
    print 'Accessing data from ICs'
    settings = ICobj.settings
    # filenames
    snapshotName = settings.filenames.snapshotName
    paramName = settings.filenames.paramName

    # particle positions
    r = ICobj.pos.r
    xyz = ICobj.pos.xyz
    # Number of particles
    nParticles = ICobj.pos.nParticles
    # molecular mass
    m = settings.physical.m
    # star mass
    m_star = settings.physical.M.copy()
    # disk mass
    m_disk = ICobj.sigma.m_disk.copy()
    m_disk = isaac.match_units(m_disk, m_star)[0]
    # mass of the gas particles
    m_particles = m_disk / float(nParticles)
    # re-scale the particles (allows making of lo-mass disk)
    m_particles *= settings.snapshot.mScale

    # -------------------------------------------------
    # Assign output
    # -------------------------------------------------
    print 'Assigning data to snapshot'
    # Get units all set up
    m_unit = m_star.units
    pos_unit = r.units

    if xyz.units != r.units:

        xyz.convert_units(pos_unit)

    # time units are sqrt(L^3/GM)
    t_unit = np.sqrt((pos_unit**3) * np.power((G * m_unit), -1)).units
    # velocity units are L/t
    v_unit = (pos_unit / t_unit).ratio('km s**-1')
    # Make it a unit
    v_unit = pynbody.units.Unit('{0} km s**-1'.format(v_unit))

    # Other settings
    metals = settings.snapshot.metals
    star_metals = metals

    # -------------------------------------------------
    # Initialize snapshot
    # -------------------------------------------------
    # Note that empty pos, vel, and mass arrays are created in the snapshot
    snapshot = pynbody.new(star=1, gas=nParticles)
    snapshot['vel'].units = v_unit
    snapshot['eps'] = 0.01 * SimArray(
        np.ones(nParticles + 1, dtype=np.float32), pos_unit)
    snapshot['metals'] = SimArray(np.zeros(nParticles + 1, dtype=np.float32))
    snapshot['rho'] = SimArray(np.zeros(nParticles + 1, dtype=np.float32))

    snapshot.gas['pos'] = xyz
    snapshot.gas['temp'] = ICobj.T(r)
    snapshot.gas['mass'] = m_particles
    snapshot.gas['metals'] = metals

    snapshot.star['pos'] = SimArray([[0., 0., 0.]], pos_unit)
    snapshot.star['vel'] = SimArray([[0., 0., 0.]], v_unit)
    snapshot.star['mass'] = m_star
    snapshot.star['metals'] = SimArray(star_metals)
    # Estimate the star's softening length as the closest particle distance
    snapshot.star['eps'] = r.min()

    # Make param file
    param = isaac.make_param(snapshot, snapshotName)
    param['dMeanMolWeight'] = m
    eos = (settings.physical.eos).lower()

    if eos == 'adiabatic':

        param['bGasAdiabatic'] = 1
        param['bGasIsothermal'] = 0

    param['dConstGamma']

    gc.collect()

    # -------------------------------------------------
    # CALCULATE VELOCITY USING calc_velocity.py.  This also estimates the
    # gravitational softening length eps
    # -------------------------------------------------
    print 'Calculating circular velocity'
    preset = settings.changa_run.preset
    max_particles = global_settings['misc']['max_particles']
    calc_velocity.v_xy(snapshot,
                       param,
                       changa_preset=preset,
                       max_particles=max_particles)

    gc.collect()

    # -------------------------------------------------
    # Estimate time step for changa to use
    # -------------------------------------------------
    # Save param file
    isaac.configsave(param, paramName, 'param')
    # Save snapshot
    snapshot.write(filename=snapshotName, fmt=pynbody.tipsy.TipsySnap)
    # est dDelta
    dDelta = ICgen_utils.est_time_step(paramName, preset)
    param['dDelta'] = dDelta

    # -------------------------------------------------
    # Create director file
    # -------------------------------------------------
    # largest radius to plot
    r_director = float(0.9 * r.max())
    # Maximum surface density
    sigma_min = float(ICobj.sigma(r_director))
    # surface density at largest radius
    sigma_max = float(ICobj.sigma.input_dict['sigma'].max())
    # Create director dict
    director = isaac.make_director(sigma_min,
                                   sigma_max,
                                   r_director,
                                   filename=param['achOutName'])
    ## Save .director file
    #isaac.configsave(director, directorName, 'director')

    # -------------------------------------------------
    # Wrap up
    # -------------------------------------------------
    print 'Wrapping up'
    # Now set the star particle's tform to a negative number.  This allows
    # UW ChaNGa treat it as a sink particle.
    snapshot.star['tform'] = -1.0

    # Update params
    r_sink = isaac.strip_units(r.min())
    param['dSinkBoundOrbitRadius'] = r_sink
    param['dSinkRadius'] = r_sink
    param['dSinkMassMin'] = 0.9 * isaac.strip_units(m_star)
    param['bDoSinks'] = 1

    return snapshot, param, director