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))
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
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
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
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
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)
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
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
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
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
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
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
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
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
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
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
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')
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')
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
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
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