def setup_sinks(IC, snapshot, param): """ Sets up snapshot and param for stars that are sinks. Parameters ---------- IC : IC obj snapshot : SimSnap param : param dict Returns ------- None """ units = diskpy.pychanga.units_from_param(param) # Set the star tforms to a negative number. This allows UW ChaNGa treat # stars as sink particles snapshot.star['tform'] = -1.0 # Set sink radius for stars r_sink = sink_radius(IC) r_sink = float(strip_units(r_sink)) param['dSinkBoundOrbitRadius'] = r_sink param['dSinkRadius'] = r_sink # Set sink mass to be 90% of the smallest star Mstar = snapshot.s['mass'].min() Mstar.convert_units(units['m_unit']) param['dSinkMassMin'] = 0.9 * strip_units(Mstar) # Turn sinks on param['bDoSinks'] = 1
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. Deprecated 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(strip_units(x1)) x2 = np.asarray(strip_units(x2)) m1 = np.asarray(strip_units(m1)) m2 = np.asarray(strip_units(m2)) # Compute,return CoM return (1.0 / (m1 + m2)) * ((m1 * x1) + (m2 * x2))
def setup(IC, R): """ Initialize values for solving vertical hydrostatic equlibrium. Parameters ---------- IC : IC object Initial conditions object R : SimArray Radius at which to solve hydrostatic equilibrium Returns ------- z : array Dimensionless z bins, in units of h r : float Radius in units of h c : float Constant in residual equation zscale : SimArray Amount to scale z (all lengths) by to get dimensions back. ie, Z(actual) = z*zscale rhoscale : SimArray Scale for dimensionless rho """ # Load everything from IC sigma = IC.sigma(R) T = IC.T(R) M = IC.settings.physical.M m = IC.settings.physical.m zmax = IC.settings.rho_calc.zmax nz = IC.settings.rho_calc.nz # Set-up constants a = G * M * m / (kB * T) b = 2 * np.pi * G * m / (kB * T) ha = np.sqrt(R * R * R / a).in_units('au') c = b * ha * sigma c.convert_units('1') h = estHeight(sigma, T, M, m, R) zscale = h rhoscale = sigma / h # Set-up z bins if zmax is None: zmax = 6 * h zmax = strip_units((zmax / h).in_units('1')) z = np.linspace(0, zmax, nz) # Make everything dimensionless r = strip_units((R / h).in_units('1')) c = strip_units(c) return z, r, c, zscale, rhoscale
def setup(IC, R): """ Initialize values for solving vertical hydrostatic equlibrium. Parameters ---------- IC : IC object Initial conditions object R : SimArray Radius at which to solve hydrostatic equilibrium Returns ------- z : array Dimensionless z bins, in units of h r : float Radius in units of h c : float Constant in residual equation zscale : SimArray Amount to scale z (all lengths) by to get dimensions back. ie, Z(actual) = z*zscale rhoscale : SimArray Scale for dimensionless rho """ # Load everything from IC sigma = IC.sigma(R) T = IC.T(R) M = IC.settings.physical.M m = IC.settings.physical.m zmax = IC.settings.rho_calc.zmax nz = IC.settings.rho_calc.nz # Set-up constants a = G*M*m/(kB*T) b = 2*np.pi*G*m/(kB*T) h = np.sqrt(R*R*R/a).in_units('au') c = b*h*sigma c.convert_units('1') zscale = h rhoscale = sigma/h # Set-up z bins if zmax is None: zmax = 6*h zmax = strip_units((zmax/h).in_units('1')) z = np.linspace(0, zmax, nz) # Make everything dimensionless r = strip_units((R/h).in_units('1')) c = strip_units(c) return z, r, c, zscale, rhoscale
def sink_radius(IC): """ Determine a reasonable sink radius for the star particles depending on the star system type (e.g., single star, binary, etc...) Parameters ---------- IC : IC object Returns ------- r_sink : SimArray Sink radius for star particles """ # Set up the sink radius starMode = IC.settings.physical.starMode.lower() if starMode == 'binary': binsys = IC.settings.physical.binsys #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 = strip_units(binsys.m1/(binsys.m1 + binsys.m2)) r_sink = (r1*p) + (r2*(1.0-p)) else: r_sink = IC.pos.r.min() return r_sink
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 strip_units(T)/YEARSEC
def _generate_theta(self): """ Generate angular positions """ nParticles = self.nParticles if self.method == 'glass': #already done in generate_r assert (len(self.theta) == nParticles) if self.method == 'grid': r = self.r dtheta = np.sqrt(2 * np.pi * (1 - r[0:-1] / r[1:])) dtheta = 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': np.random.seed(self._seed) theta = 2 * np.pi * np.random.rand(nParticles) self.theta = theta
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 = 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': np.random.seed(self._seed) theta = 2*np.pi*np.random.rand(nParticles) self.theta = theta
def T_adiabatic(self, r): """ Estimates the adiabatic temperature profile as a function of r setup_interior must be run first """ # if not self.adiabatic_ready: # # # Try to setup the adiabatic profile # self.setup_interior() # p = self.p # A = self.Tscale # print A # # r = match_units(r, 'au')[0] # sigma = self._parent.sigma(r) # sigma.convert_units('Msol au**-2') # sigma = strip_units(sigma) # r = strip_units(r) # return A * ((sigma**2)/(r**3))**p b = 1.5 sigma = self._parent.sigma(r) r_a = self.r_a r = match_units(r, r_a.units)[0] x = (r/r_a).in_units('1') sigma_a = self.sigma_a y = (sigma/sigma_a).in_units('1') x = strip_units(x) y = strip_units(y) gamma = self._parent.settings.physical.gamma p = (gamma-1.)/(gamma+1.) T_a = self.T_a T = T_a * (y**(2*p)) * (x**-b) return T # r = strip_units(match_units(r,'au')[0]) # # return A * (r**2)
def setup_r_bins(IC, r=None): if IC.settings.rho_calc.nr is not None: nr = IC.settings.rho_calc.nr rmax = IC.sigma.r_bins[[-1]] rbins = np.linspace(0, rmax, nr) return rbins if r is None: # Setup the initial r bins rmax = IC.sigma.r_bins[[-1]] nr = len(IC.sigma.r_bins) * 10 #r = np.linspace(0, rmax, nr) # dflemin3 Nov 4, 2015: made units more explicit # via SimArrays r_units = IC.sigma.r_bins.units r = SimArray(np.linspace(0, rmax, nr),r_units) bin_error_tol = IC.settings.rho_calc.r_bin_tol minbins = IC.settings.rho_calc.min_r_bins # Estimate the disk height M = IC.settings.physical.M m = IC.settings.physical.m T = IC.T(r) h = h_est(r, M, m, T, gamma=1) # Estimate midplane density sigma = IC.sigma(r) rho0 = rho0_est(h, sigma) # Estimate a reasonable function tolerance for the bins # This is done by taking a weighted mean of midplane density: weighted # by the number of particles at each radius (the PDF) w = abs(IC.sigma.pdf(r)) w /= w.sum() w = strip_units(w) # also weight the midplane density rho0 = w*rho0 # Now do the mean rho0mean = (rho0*w).sum() ftol = bin_error_tol * rho0mean # Estimate reasonable bins. This is done by removing as many bins as # possible to still allow the midplane density to be well resolved rbins = resolvedbins(r, rho0, minbins=minbins, ftol=ftol) rbins = rbins.copy() print '{} radial bins used for density calculation'.format(len(rbins)) return rbins
def setup_r_bins(IC, r=None): if IC.settings.rho_calc.nr is not None: nr = IC.settings.rho_calc.nr rmax = IC.sigma.r_bins[[-1]] rbins = np.linspace(0, rmax, nr) return rbins if r is None: # Setup the initial r bins rmax = IC.sigma.r_bins[[-1]] nr = len(IC.sigma.r_bins) * 10 #r = np.linspace(0, rmax, nr) # dflemin3 Nov 4, 2015: made units more explicit # via SimArrays r_units = IC.sigma.r_bins.units r = SimArray(np.linspace(0, rmax, nr), r_units) bin_error_tol = IC.settings.rho_calc.r_bin_tol minbins = IC.settings.rho_calc.min_r_bins # Estimate the disk height M = IC.settings.physical.M m = IC.settings.physical.m T = IC.T(r) h = h_est(r, M, m, T, gamma=1) # Estimate midplane density sigma = IC.sigma(r) rho0 = rho0_est(h, sigma) # Estimate a reasonable function tolerance for the bins # This is done by taking a weighted mean of midplane density: weighted # by the number of particles at each radius (the PDF) w = abs(IC.sigma.pdf(r)) w /= w.sum() w = strip_units(w) # also weight the midplane density rho0 = w * rho0 # Now do the mean rho0mean = (rho0 * w).sum() ftol = bin_error_tol * rho0mean # Estimate reasonable bins. This is done by removing as many bins as # possible to still allow the midplane density to be well resolved rbins = resolvedbins(r, rho0, minbins=minbins, ftol=ftol) rbins = rbins.copy() print '{} radial bins used for density calculation'.format(len(rbins)) return rbins
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 Deprecated (just use sqrt(G*M/a^3))...why did I make this? 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(strip_units(x1)) * AUCM x2 = np.asarray(strip_units(x2)) * AUCM v1 = np.asarray(strip_units(v1)) * 1000 * 100 * VEL_UNIT v2 = np.asarray(strip_units(v2)) * 1000 * 100 * VEL_UNIT m1 = np.asarray(strip_units(m1)) * Msol m2 = np.asarray(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 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 = 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 = 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 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 = make_director(sigma_min, sigma_max, r_director, filename=param['achOutName']) ## Save .director file #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 = strip_units(np.sum(snapshotBinary.gas['mass'])) binsys.m1 = 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 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 = 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 = 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 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 = make_director(sigma_min, sigma_max, r_director, filename=param["achOutName"]) ## Save .director file # 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 = strip_units(np.sum(snapshotBinary.gas["mass"])) binsys.m1 = 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 make_director(sigma_min, sigma_max, r, resolution=1200, filename='snapshot'): """ Makes a director dictionary for ChaNGa runs based on the min/max surface density, maximum image radius, and image resolution for a gaseous protoplanetary disk. The created dictionary can be saved with diskpy.utils.configsave The method is to use an example director file (saved as default.director) which works for one simulation and scale the various parameters accordingly. default.director should have a commented line in it which reads: #sigma_max float where float is the maximum surface density of the simulation in simulation units. **ARGUMENTS** sigma_min : float The surface density that corresponds to 0 density on the image (ie the minimum threshold). Required for setting the dynamic range sigma_max : float Maximum surface density in the simulation r : float Maximum radius to plot out to resolution : int or float Number of pixels in image. The image is shape (resolution, resolution) filename : str prefix to use for saving the images. Example: if filename='snapshot', then the outputs will be of form 'snapshot.000000000.ppm' **RETURNS** director : dict A .director dictionary. Can be saved with diskpy.utils.configsave """ # ----------------------------------------------------------- # Parse defaults to get scale factor for c # ----------------------------------------------------------- sigma_min, sigma_max, r = strip_units([sigma_min, sigma_max, r]) defaults = configparser(_directordefault) if '#sigma_max' not in defaults: raise KeyError,'Default .director file should have a line e.g. << #sigma_max 0.01 >>' sigma_max0 = defaults['#sigma_max'] c0 = defaults['colgas'][3] n0 = defaults['size'][0] r0 = defaults['eye'][2] A = (c0 * float(n0)**2)/(sigma_max0 * r0**2) # ----------------------------------------------------------- # Create new director dictionary # ----------------------------------------------------------- director = copy.deepcopy(defaults) director.pop('#sigma_max', None) logscale_min = sigma_min/sigma_max if pynbody.units.has_units(logscale_min): logscale_min = float(logscale_min.in_units('1')) c = A * float(sigma_max * r**2 /float(resolution)**2) director['colgas'][3] = c director['size'] = [resolution, resolution] director['eye'][2] = r director['file'] = filename return director
def make_binary(IC, snapshot): """ Turns a snapshot for a single star into a snapshot of a binary system Parameters ---------- IC : IC object snapshot : SimSnap Single star system to turn into a binary Returns ------- snapshotBinary : SimSnap A binary version of the simulation snapshot """ # Initialize snapshot snapshotBinary = pynbody.new(star=2, gas=len(snapshot.g)) # Copy gas particles over for key in snapshot.gas.keys(): snapshotBinary.gas[key] = snapshot.gas[key] # Load Binary system obj to initialize system starMode = IC.settings.physical.starMode.lower() binsys = IC.settings.physical.binsys if starMode == 'stype': # Treate the primary as a star of mass mStar + mDisk m_disk = strip_units(np.sum(snapshotBinary.gas['mass'])) binsys.m1 += m_disk binsys.computeCartesian() x1,x2,v1,v2 = binsys.generateICs() #Assign star parameters assuming CCW orbit snapshotBinary.star[0]['pos'] = x1 snapshotBinary.star[0]['vel'] = v1 snapshotBinary.star[1]['pos'] = x2 snapshotBinary.star[1]['vel'] = v2 #Set stellar masses priMass = binsys.m1 secMass = binsys.m2 snapshotBinary.star[0]['mass'] = priMass snapshotBinary.star[1]['mass'] = secMass snapshotBinary.star['metals'] = snapshot.s['metals'] if starMode == 'stype': # 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'] # Remove disk mass from the effective star mass snapshotBinary[0]['mass'] -= m_disk binsys.m1 -= m_disk # Star smoothing snapshotBinary.star['eps'] = snapshot.star['eps'] elif starMode == 'binary': # Estimate stars' softening length as fraction of distance to COM d = np.sqrt(AddBinary.dotProduct(x1-x2,x1-x2)) pos_unit = snapshotBinary['pos'].units snapshotBinary.star['eps'] = SimArray(abs(d)/4.0,pos_unit) return snapshotBinary
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 = utils.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 = utils.configparser(param, 'param') utils.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 = loadhalos(f_prefix + '.grp') # Cleanup for name in glob.glob(f_prefix + '*'): os.remove(name) return clumps
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 !!! NOTE: This function is awful and deprecated --- do NOT use it. Instead, use calc_LB_resonance !!! 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 gas = s.gas m_min = 1 #m >=1 for LRs, CRs l_min = 1 #l >=1 for LRs, CRs #Compute binary angular frequency 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 = 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 #Make r steps smaller for higher accuracy r_arr = np.linspace(r.min(),r.max(),len(r)*10) #Compute mass of disk interior to given r mask = np.zeros((len(gas),len(r_arr)),dtype=bool) m_disk = np.zeros(len(r_arr)) for i in range(0,len(r_arr)): mask[:,i] = gas['rxy'] < r_arr[i] m_disk[i] = np.sum(gas['mass'][mask[:,i]]) #Compute omega_disk in units 1/day (like omega_binary) omega_d = 2.0*np.pi/AddBinary.aToP(r_arr,m1+m2+m_disk) #Compute kappa (radial epicycle frequency = sqrt(r * d(omega^2)/dr + 4*(omega^2)) o2 = omega_d*omega_d dr = r_arr[1] - r_arr[0] #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_arr*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)))] #Rescale omega_d, kappa to be of length bins again omega_d = np.linspace(omega_d.min(),omega_d.max(),bins) kappa = np.linspace(kappa.min(),kappa.max(),bins) return omega_Li, omega_Lo, o_c, omega_d, kappa
def meshinterp(xedges, y, z, kind='linear', bounds_error=False, fill_value=0, assume_sorted=True): """ Generates a 2D interpolating function for z defined on a non-uniform mesh Handles units Parameters ---------- xedges : array 1D array defining the x bin edges, monotonically increasing y : array 2D array defining y values. shape (nx, ny), where nx is the number of xedges and ny is the number of y-points at each x-bin So, y[i, :] are the monotonically increasing y values at xedges[i] z : array 2D array of z(x,y). shape (nx, ny) = y.shape kind : str (optional) Sets the kind of interpolation to perform [see scipy.interpolate.interp1d] bounds_error : bool (optional) Flag to raise error if values outside of y are called [see scipy.interpolate.interp1d] fill_value : float (optional) Sets the value to fill with if bounds_error = True [see scipy.interpolate.interp1d] assume_sorted : bool [see scipy.interpolate.interp1d] Returns ------- meshspline(x, y): callable interpolation function Function which can be called on x, y pairs to give the interpolated value of z. Values outside of the range of y are set to fill_value. x values outside the range of xedges are set to the boundary of xedges """ # Check shapes if z.shape != y.shape: raise ValueError, 'y and z must have same shape' if len(xedges) != len(y): raise ValueError, 'x and y must have same len' # Handle units pos = [xedges, y, z] units = [] for a in pos: if pb.units.has_units(a): units.append(a.units) else: units.append(None) xedges, y, z = strip_units(pos) # Setup bin information binsize = xedges[1:] - xedges[0:-1] xmin = xedges[0] xmax = xedges[-1] nbins = len(xedges) - 1 # set up spliness splines = [] for i in range(nbins + 1): # perform interpolation to make spline splines.append(interp1d(y[i], z[i], kind=kind, \ bounds_error=bounds_error, fill_value=fill_value)) # Assume_sorted doesn't work in older scipy versions # # perform interpolation to make spline # splines.append(interp1d(y[i], z[i], kind=kind, \ # bounds_error=bounds_error, fill_value=fill_value, \ # assume_sorted=assume_sorted)) # Define the callable interplation function to return def meshspline(x1, y1): """ Callable interpolation function, interoplates the value of z at points (x1, y1) Parameters ---------- x1, y1 : array x and y points to evaluate z at. Must be the same shape. ie, x1[i], y1[i] define a point (x, y). If @x1 or @y1 have no units, they are assumed to have the units of the nodes used to make the interpolator. Otherwise they are converted to the proper units Returns ------- z(x1, y1) : array z evaluated at @x1, @y1 """ # Handle units x1 = strip_units(match_units(x1, units[0])[0]) y1 = strip_units(match_units(y1, units[1])[0]) # Setup x and y points to estimate z at x1 = np.asarray(x1).copy() y1 = np.asarray(y1) if len(x1.shape) < 1: x1 = x1[None] if len(y1.shape) < 1: y1 = y1[None] # Flatten arrays shape = x1.shape nElements = np.prod(shape) x1 = np.reshape(x1, [nElements]) y1 = np.reshape(y1, [nElements]) # Deal with xs outside of boundaries x1[x1 < xmin] = xmin x1[x1 > xmax] = xmax # Find bin indices ind = np.digitize(x1, xedges) - 1 ind[ind < 0] = 0 ind[ind > nbins - 1] = nbins - 1 # Get bin info for every point xlo = xedges[ind] xhi = xedges[ind + 1] dx = binsize[ind] # Get weights for bins (distance from bin edges) wlo = (xhi - x1) / dx whi = (x1 - xlo) / dx # Get function values at left and right xedges flo = np.zeros(x1.shape) fhi = np.zeros(x1.shape) for i in range(nbins): # Select everything in bin i mask = (ind == i) if np.any(mask): # Retrieve function values flo[mask] = splines[i](y1[mask]) fhi[mask] = splines[i + 1](y1[mask]) # Take a weighted average of the function values at left and right # bin edges fout = wlo * flo + whi * fhi # Unflatten fout: fout = np.reshape(fout, shape) return SimArray(fout, units[2]) return meshspline
def binned_mean(x, y, bins=10, nbins=None, binedges=None, weights=None, weighted_bins=False, ret_bin_edges=False): """ Bins y according to x and takes the average for each bin. bins can either be an integer (the number of bins to use) or an array of binedges. bins will be overridden by nbins or binedges Optionally (for compatibility reasons) if binedges is specified, the x-bins are defined by binedges. Otherwise the x-bins are determined by nbins If weights = None, equal weights are assumed for the average, otherwise weights for each data point should be specified y_err (error in y) is calculated as the standard deviation in y for each bin, divided by sqrt(N), where N is the number of counts in each bin IF weighted_bins is True, the bin centers are calculated as a center of mass NaNs are ignored for the input. Empty bins are returned with nans RETURNS a tuple of (bin_centers, y_mean, y_err) if ret_bin_edges=False else, Returns (bin_edges, y_mean, y_err) """ if (isinstance(bins, int)) and (nbins is None): nbins = bins elif (hasattr(bins, "__iter__")) and (binedges is None): binedges = bins if binedges is not None: nbins = len(binedges) - 1 else: binedges = np.linspace(x.min(), (1 + np.spacing(2)) * x.max(), nbins + 1) if weights is None: weights = np.ones(x.shape) weights = strip_units(weights) # Pre-factor for weighted STD: A = 1 / (1 - (weights ** 2).sum()) # Initialize y_mean = np.zeros(nbins) y_std = np.zeros(nbins) # Find the index bins for each data point ind = np.digitize(x, binedges) - 1 # Ignore nans nan_ind = np.isnan(y) N = np.histogram(x, binedges)[0] # Initialize bin_centers (try to retain units) bin_centers = 0.0 * binedges[1:] for i in range(nbins): # Indices to use mask = (ind == i) & (~nan_ind) # Set up the weighting w = weights[mask].copy() w /= w.sum() A = 1 / (1 - (w ** 2).sum()) # y_mean[i] = np.nanmean(y[mask]) y_mean[i] = (w * y[mask]).sum() var = A * (w * (y[mask] - y_mean[i]) ** 2).sum() y_std[i] = np.sqrt(var) # y_std[i] = np.std(y[use_ind]) if weighted_bins: # Center of mass of x positions bin_centers[i] = (w * x[mask]).sum() y_mean = match_units(y_mean, y)[0] y_err = y_std / np.sqrt(N) y_err = match_units(y_err, y)[0] y_mean[N == 0] = np.nan y_err[N == 0] = np.nan if not weighted_bins: bin_centers = (binedges[0:-1] + binedges[1:]) / 2.0 binedges = match_units(binedges, x)[0] bin_centers = match_units(bin_centers, x)[0] else: bin_centers[N == 0] = np.nan if ret_bin_edges: return binedges, y_mean, y_err else: return bin_centers, y_mean, y_err
def meshinterp(xedges, y, z, kind="linear", bounds_error=False, fill_value=0, assume_sorted=True): """ Generates a 2D interpolating function for z defined on a non-uniform mesh Handles units Parameters ---------- xedges : array 1D array defining the x bin edges, monotonically increasing y : array 2D array defining y values. shape (nx, ny), where nx is the number of xedges and ny is the number of y-points at each x-bin So, y[i, :] are the monotonically increasing y values at xedges[i] z : array 2D array of z(x,y). shape (nx, ny) = y.shape kind : str (optional) Sets the kind of interpolation to perform [see scipy.interpolate.interp1d] bounds_error : bool (optional) Flag to raise error if values outside of y are called [see scipy.interpolate.interp1d] fill_value : float (optional) Sets the value to fill with if bounds_error = True [see scipy.interpolate.interp1d] assume_sorted : bool [see scipy.interpolate.interp1d] Returns ------- meshspline(x, y): callable interpolation function Function which can be called on x, y pairs to give the interpolated value of z. Values outside of the range of y are set to fill_value. x values outside the range of xedges are set to the boundary of xedges """ # Check shapes if z.shape != y.shape: raise ValueError, "y and z must have same shape" if len(xedges) != len(y): raise ValueError, "x and y must have same len" # Handle units pos = [xedges, y, z] units = [] for a in pos: if pb.units.has_units(a): units.append(a.units) else: units.append(None) xedges, y, z = strip_units(pos) # Setup bin information binsize = xedges[1:] - xedges[0:-1] xmin = xedges[0] xmax = xedges[-1] nbins = len(xedges) - 1 # set up spliness splines = [] for i in range(nbins + 1): # perform interpolation to make spline splines.append( interp1d( y[i], z[i], kind=kind, bounds_error=bounds_error, fill_value=fill_value, assume_sorted=assume_sorted ) ) # Define the callable interplation function to return def meshspline(x1, y1): """ Callable interpolation function, interoplates the value of z at points (x1, y1) Parameters ---------- x1, y1 : array x and y points to evaluate z at. Must be the same shape. ie, x1[i], y1[i] define a point (x, y). If @x1 or @y1 have no units, they are assumed to have the units of the nodes used to make the interpolator. Otherwise they are converted to the proper units Returns ------- z(x1, y1) : array z evaluated at @x1, @y1 """ # Handle units x1 = strip_units(match_units(x1, units[0])[0]) y1 = strip_units(match_units(y1, units[1])[0]) # Setup x and y points to estimate z at x1 = np.asarray(x1).copy() y1 = np.asarray(y1) if len(x1.shape) < 1: x1 = x1[None] if len(y1.shape) < 1: y1 = y1[None] # Flatten arrays shape = x1.shape nElements = np.prod(shape) x1 = np.reshape(x1, [nElements]) y1 = np.reshape(y1, [nElements]) # Deal with xs outside of boundaries x1[x1 < xmin] = xmin x1[x1 > xmax] = xmax # Find bin indices ind = np.digitize(x1, xedges) - 1 ind[ind < 0] = 0 ind[ind > nbins - 1] = nbins - 1 # Get bin info for every point xlo = xedges[ind] xhi = xedges[ind + 1] dx = binsize[ind] # Get weights for bins (distance from bin edges) wlo = (xhi - x1) / dx whi = (x1 - xlo) / dx # Get function values at left and right xedges flo = np.zeros(x1.shape) fhi = np.zeros(x1.shape) for i in range(nbins): # Select everything in bin i mask = ind == i if np.any(mask): # Retrieve function values flo[mask] = splines[i](y1[mask]) fhi[mask] = splines[i + 1](y1[mask]) # Take a weighted average of the function values at left and right # bin edges fout = wlo * flo + whi * fhi # Unflatten fout: fout = np.reshape(fout, shape) return SimArray(fout, units[2]) return meshspline
def meshspline(x1, y1): """ Callable interpolation function, interoplates the value of z at points (x1, y1) Parameters ---------- x1, y1 : array x and y points to evaluate z at. Must be the same shape. ie, x1[i], y1[i] define a point (x, y). If @x1 or @y1 have no units, they are assumed to have the units of the nodes used to make the interpolator. Otherwise they are converted to the proper units Returns ------- z(x1, y1) : array z evaluated at @x1, @y1 """ # Handle units x1 = strip_units(match_units(x1, units[0])[0]) y1 = strip_units(match_units(y1, units[1])[0]) # Setup x and y points to estimate z at x1 = np.asarray(x1).copy() y1 = np.asarray(y1) if len(x1.shape) < 1: x1 = x1[None] if len(y1.shape) < 1: y1 = y1[None] # Flatten arrays shape = x1.shape nElements = np.prod(shape) x1 = np.reshape(x1, [nElements]) y1 = np.reshape(y1, [nElements]) # Deal with xs outside of boundaries x1[x1 < xmin] = xmin x1[x1 > xmax] = xmax # Find bin indices ind = np.digitize(x1, xedges) - 1 ind[ind < 0] = 0 ind[ind > nbins - 1] = nbins - 1 # Get bin info for every point xlo = xedges[ind] xhi = xedges[ind + 1] dx = binsize[ind] # Get weights for bins (distance from bin edges) wlo = (xhi - x1) / dx whi = (x1 - xlo) / dx # Get function values at left and right xedges flo = np.zeros(x1.shape) fhi = np.zeros(x1.shape) for i in range(nbins): # Select everything in bin i mask = (ind == i) if np.any(mask): # Retrieve function values flo[mask] = splines[i](y1[mask]) fhi[mask] = splines[i + 1](y1[mask]) # Take a weighted average of the function values at left and right # bin edges fout = wlo * flo + whi * fhi # Unflatten fout: fout = np.reshape(fout, shape) return SimArray(fout, units[2])
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. Also requires tipsy tools (https://github.com/N-BodyShop/tipsy_tools), specifically totipnat 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 """ # Check for skid and totipnat err = [] skid_path = utils.which('skid') if skid_path is None: err.append('<skid not found : https://github.com/N-BodyShop/skid>') totipnat_path = utils.which('totipnat') if totipnat_path is None: err.append('<totipnat (part of tipsy tools) not found : ' 'https://github.com/N-BodyShop/tipsy_tools>') if len(err) > 0: err = '\n'.join(err) raise RuntimeError, err # 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 = utils.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 = utils.configparser(param, 'param') utils.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 = 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 """ 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 = 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 = 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 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 = make_director(sigma_min, sigma_max, r_director, filename=param['achOutName']) ## Save .director file #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 = strip_units(r.min()) param['dSinkBoundOrbitRadius'] = r_sink param['dSinkRadius'] = r_sink param['dSinkMassMin'] = 0.9 * strip_units(m_star) param['bDoSinks'] = 1 return snapshot, 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 !!! NOTE: This function is awful and deprecated --- do NOT use it. Instead, use calc_LB_resonance !!! 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 gas = s.gas m_min = 1 #m >=1 for LRs, CRs l_min = 1 #l >=1 for LRs, CRs #Compute binary angular frequency 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 = 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 #Make r steps smaller for higher accuracy r_arr = np.linspace(r.min(), r.max(), len(r) * 10) #Compute mass of disk interior to given r mask = np.zeros((len(gas), len(r_arr)), dtype=bool) m_disk = np.zeros(len(r_arr)) for i in range(0, len(r_arr)): mask[:, i] = gas['rxy'] < r_arr[i] m_disk[i] = np.sum(gas['mass'][mask[:, i]]) #Compute omega_disk in units 1/day (like omega_binary) omega_d = 2.0 * np.pi / AddBinary.aToP(r_arr, m1 + m2 + m_disk) #Compute kappa (radial epicycle frequency = sqrt(r * d(omega^2)/dr + 4*(omega^2)) o2 = omega_d * omega_d dr = r_arr[1] - r_arr[0] #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_arr * 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)))] #Rescale omega_d, kappa to be of length bins again omega_d = np.linspace(omega_d.min(), omega_d.max(), bins) kappa = np.linspace(kappa.min(), kappa.max(), bins) 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(strip_units(x1))*AddBinary.AUCM r2 = np.asarray(strip_units(x2))*AddBinary.AUCM v1 = np.asarray(strip_units(v1))*AddBinary.VEL_UNIT*100*1000 v2 = np.asarray(strip_units(v2))*AddBinary.VEL_UNIT*100*1000 m1 = np.asarray(strip_units(m1))*AddBinary.Msol m2 = np.asarray(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 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(strip_units(x1)) * AddBinary.AUCM r2 = np.asarray(strip_units(x2)) * AddBinary.AUCM v1 = np.asarray(strip_units(v1)) * AddBinary.VEL_UNIT * 100 * 1000 v2 = np.asarray(strip_units(v2)) * AddBinary.VEL_UNIT * 100 * 1000 m1 = np.asarray(strip_units(m1)) * AddBinary.Msol m2 = np.asarray(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 _est_r_adiabatic(self): """ Estimates the radius at which the temperature profile should be made adiabatic (for adiabatic disks only!). This is defined the be the first radius at which the entropy gradient switches from negative to positive Returns ------- r_adiabatic : SimArray radius Notes ----- Entropy gradients are estimated assuming: * Star gravity dominates disk self-gravity * Disk is thin * No vertical temperature gradient """ ICobj = self._parent if not hasattr(ICobj, 'sigma'): raise RuntimeError, 'Sigma not found in initial conditions object' gamma = ICobj.settings.physical.gamma p = (gamma - 1.)/(gamma + 1.) a = 1/p r = ICobj.sigma.r_bins temp = strip_units(self.T_nocut(r)) sigma = strip_units(ICobj.sigma(r)) r1 = strip_units(r) # Entropy/mass (up to a multiplicative and an additive constant) s = 0.5*a*np.log(temp) + 1.5*np.log(r1) - np.log(sigma) # Entropy gradient (up to a multiplicative constant) ds = np.gradient(s) # Find all negative to positive zero crossings neg = ds < 0 ind = np.where(neg[0:-1] & ~neg[1:])[0] + 1 if len(ind) > 1: print 'WARNING: Multiple r_adiabatics found. Selecting the lowest' r_adiabatic = r[[ind[0]]] elif len(ind) < 1: print 'WARNING: could not find r_adiabatic' r_adiabatic = SimArray([0.],'au') else: r_adiabatic = r[ind] return r_adiabatic
def binned_mean(x, y, bins=10, nbins=None, binedges = None, weights=None,\ weighted_bins=False, ret_bin_edges=False, binind=None): """ Bins y according to x and takes the average for each bin. RETURNS a tuple of (bin_centers, y_mean, y_err) if ret_bin_edges=False else, Returns (bin_edges, y_mean, y_err) Parameters ---------- x, y : array-like Bin and average y according to x bins : int or arraylike Either number of bins or an array of binedges nbins : int (optional) For backward compatibility, prefer using bins binedges : array-like (optional) For backward compatibility, prefer using bins weights : array-like Weights to use for data points. Assume weights=1 (uniform) weighted_bins : bool If true, the bin centers will be calculated as weighted centers ret_bin_edges : bool Return bin centers instead of edges. Default = False binind : list of arrays An optional list of arrays which specify which indices of x, y belong to which bin. This can be used for speed. when using a pynbody profile, pynbody will generate such a list: >>> p = pynbody.analysis.profile.Profile(f) >>> p.binind # binind For the returned bins to be consistent, the user should supply the used binedges. """ x = np.asanyarray(x) y = np.asanyarray(y) if (isinstance(bins, int)) and (nbins is None): nbins = bins elif (hasattr(bins, '__iter__')) and (binedges is None): binedges = bins if binedges is not None: nbins = len(binedges) - 1 else: binedges = np.linspace(x.min(), (1 + np.spacing(2)) * x.max(), nbins + 1) if weights is None: weights = np.ones(x.shape) weights = strip_units(weights) # Pre-factor for weighted STD: A = 1 / (1 - (weights**2).sum()) # Initialize y_mean = np.zeros(nbins) y_std = np.zeros(nbins) if binind is None: # Find the index bins for each data point ind = np.digitize(x, binedges) - 1 N = np.histogram(x, binedges)[0] else: N = np.array([ind.sum() for ind in binind]) # Ignore nans nan_ind = np.isnan(y) # Initialize bin_centers (try to retain units) bin_centers = 0.0 * binedges[1:] for i in range(nbins): if binind is None: #Indices to use mask = (ind == i) & (~nan_ind) # Set up the weighting w = weights[mask].copy() yvals = y[mask] xvals = x[mask] else: select_ind = binind[i] w = weights[select_ind] yvals = y[select_ind] xvals = x[select_ind] w /= w.sum() A = 1 / (1 - (w**2).sum()) y_mean[i] = (w * yvals).sum() var = A * (w * (yvals - y_mean[i])**2).sum() y_std[i] = np.sqrt(var) if weighted_bins: # Center of mass of x positions bin_centers[i] = (w * xvals).sum() y_mean = match_units(y_mean, y)[0] y_err = y_std / np.sqrt(N) y_err = match_units(y_err, y)[0] y_mean[N == 0] = np.nan y_err[N == 0] = np.nan if not weighted_bins: bin_centers = (binedges[0:-1] + binedges[1:]) / 2.0 binedges = match_units(binedges, x)[0] bin_centers = match_units(bin_centers, x)[0] else: bin_centers[N == 0] = np.nan if ret_bin_edges: return binedges, y_mean, y_err else: return bin_centers, y_mean, y_err
def meshspline(x1, y1): """ Callable interpolation function, interoplates the value of z at points (x1, y1) Parameters ---------- x1, y1 : array x and y points to evaluate z at. Must be the same shape. ie, x1[i], y1[i] define a point (x, y). If @x1 or @y1 have no units, they are assumed to have the units of the nodes used to make the interpolator. Otherwise they are converted to the proper units Returns ------- z(x1, y1) : array z evaluated at @x1, @y1 """ # Handle units x1 = strip_units(match_units(x1, units[0])[0]) y1 = strip_units(match_units(y1, units[1])[0]) # Setup x and y points to estimate z at x1 = np.asarray(x1).copy() y1 = np.asarray(y1) if len(x1.shape) < 1: x1 = x1[None] if len(y1.shape) < 1: y1 = y1[None] # Flatten arrays shape = x1.shape nElements = np.prod(shape) x1 = np.reshape(x1, [nElements]) y1 = np.reshape(y1, [nElements]) # Deal with xs outside of boundaries x1[x1 < xmin] = xmin x1[x1 > xmax] = xmax # Find bin indices ind = np.digitize(x1, xedges) - 1 ind[ind < 0] = 0 ind[ind > nbins - 1] = nbins - 1 # Get bin info for every point xlo = xedges[ind] xhi = xedges[ind + 1] dx = binsize[ind] # Get weights for bins (distance from bin edges) wlo = (xhi - x1) / dx whi = (x1 - xlo) / dx # Get function values at left and right xedges flo = np.zeros(x1.shape) fhi = np.zeros(x1.shape) for i in range(nbins): # Select everything in bin i mask = ind == i if np.any(mask): # Retrieve function values flo[mask] = splines[i](y1[mask]) fhi[mask] = splines[i + 1](y1[mask]) # Take a weighted average of the function values at left and right # bin edges fout = wlo * flo + whi * fhi # Unflatten fout: fout = np.reshape(fout, shape) return SimArray(fout, units[2])
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 = 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 = 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 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 = make_director(sigma_min, sigma_max, r_director, filename=param['achOutName']) ## Save .director file #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 = 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 * strip_units(secMass) param['bDoSinks'] = 1 return snapshotBinary, param, director
def make_director(sigma_min, sigma_max, r, resolution=1200, filename='snapshot'): """ Makes a director dictionary for ChaNGa runs based on the min/max surface density, maximum image radius, and image resolution for a gaseous protoplanetary disk. The created dictionary can be saved with diskpy.utils.configsave The method is to use an example director file (saved as default.director) which works for one simulation and scale the various parameters accordingly. default.director should have a commented line in it which reads: #sigma_max float where float is the maximum surface density of the simulation in simulation units. **ARGUMENTS** sigma_min : float The surface density that corresponds to 0 density on the image (ie the minimum threshold). Required for setting the dynamic range sigma_max : float Maximum surface density in the simulation r : float Maximum radius to plot out to resolution : int or float Number of pixels in image. The image is shape (resolution, resolution) filename : str prefix to use for saving the images. Example: if filename='snapshot', then the outputs will be of form 'snapshot.000000000.ppm' **RETURNS** director : dict A .director dictionary. Can be saved with diskpy.utils.configsave """ # ----------------------------------------------------------- # Parse defaults to get scale factor for c # ----------------------------------------------------------- sigma_min, sigma_max, r = strip_units([sigma_min, sigma_max, r]) defaults = _directordefault if '#sigma_max' not in defaults: raise KeyError, 'Default .director file should have a line e.g. << #sigma_max 0.01 >>' sigma_max0 = defaults['#sigma_max'] c0 = defaults['colgas'][3] n0 = defaults['size'][0] r0 = defaults['eye'][2] A = (c0 * float(n0)**2) / (sigma_max0 * r0**2) # ----------------------------------------------------------- # Create new director dictionary # ----------------------------------------------------------- director = copy.deepcopy(defaults) director.pop('#sigma_max', None) logscale_min = sigma_min / sigma_max if pynbody.units.has_units(logscale_min): logscale_min = float(logscale_min.in_units('1')) c = A * float(sigma_max * r**2 / float(resolution)**2) director['colgas'][3] = c director['size'] = [resolution, resolution] director['eye'][2] = r director['file'] = filename return director