def __init__(self, config=None, optable_args=None, dashboard_args=None): super(Star, self).__init__() self.log = logging.getLogger(__name__) self.data_log = logging.getLogger("data") self.telem_log = logging.getLogger("telemetry") self.telem_log.propagate = False self._call_count = 0 self._warning_count = {"Neg": 0} self.name = current_process().name self._config = DottedConfiguration(config) self._config.dn = DottedConfiguration self._integrator_name = self.config.get("System.Integrator.Method", "scipy") self._max_warnings = self.config["System.Integrator"].get( "Warnings", 100) self._integrator = getattr(self, self._integrator_name) self._logmode = self.config.get("System.Integrator.LogScale", True) self._plotting = self.config.get("System.Integrator.LivePlot", True) np.set_printoptions(**self.config["System.Numpy.PrintOptions"]) if optable_args is None: self.log.debug("Starting Opacity Table from Scratch") self._opacity = ObjectThread( OpacityTable, ikwargs=dict(fkey=self._config["Data.Opacity.Config"], X=self.X, Y=self.Y, snap=self._config["System.Opacity.Snap"], error=self._config["System.Opacity.Error"], wmax=self._config["System.Opacity.Warnings"], efkey=self._config.get( "System.Opacity.ExtendedKey", None)), locking=True, timeout=self._config["System.Opacity.Timeout"]) self.opacity.start() self.log.debug("Started Opacity Table From Scratch") else: self.log.debug("Starting Opacity Table from Arguments") self._opacity = ObjectManager(**optable_args) self.log.debug("Started Opacity Table from Arguments") if dashboard_args is not None: self._dashboard = ObjectManager(**dashboard_args) from astropysics.constants import Rs, Lsun, Ms self.Pc_Guess = float(self._config["Star.Initial.Pc"]) self.Tc_Guess = float(self._config["Star.Initial.Tc"]) self.R_Guess = float(self._config["Star.Initial.Rs"]) * Rs self.L_Guess = float(self._config["Star.Initial.Ls"]) * Lsun self.dm_Guess = float(self._config["Star.Initial.dm"]) * Ms self.fp = float(self._config["Star.Integration.fp"]) * self.M self._update_frequency = self.config["System.Dashboard.Update"] self._save_frequency = self.config["System.Outputs.Update"]
def __init__(self, config=None, optable_args=None, dashboard_args=None): super(Star, self).__init__() self.log = logging.getLogger(__name__) self.data_log = logging.getLogger("data") self.telem_log = logging.getLogger("telemetry") self.telem_log.propagate = False self._call_count = 0 self._warning_count = { "Neg" : 0 } self.name = current_process().name self._config = DottedConfiguration(config) self._config.dn = DottedConfiguration self._integrator_name = self.config.get("System.Integrator.Method","scipy") self._max_warnings = self.config["System.Integrator"].get("Warnings",100) self._integrator = getattr(self,self._integrator_name) self._logmode = self.config.get("System.Integrator.LogScale",True) self._plotting = self.config.get("System.Integrator.LivePlot",True) np.set_printoptions(**self.config["System.Numpy.PrintOptions"]) if optable_args is None: self.log.debug("Starting Opacity Table from Scratch") self._opacity = ObjectThread(OpacityTable, ikwargs=dict(fkey=self._config["Data.Opacity.Config"],X=self.X,Y=self.Y, snap=self._config["System.Opacity.Snap"],error=self._config["System.Opacity.Error"], wmax=self._config["System.Opacity.Warnings"],efkey=self._config.get("System.Opacity.ExtendedKey",None)), locking=True,timeout=self._config["System.Opacity.Timeout"]) self.opacity.start() self.log.debug("Started Opacity Table From Scratch") else: self.log.debug("Starting Opacity Table from Arguments") self._opacity = ObjectManager(**optable_args) self.log.debug("Started Opacity Table from Arguments") if dashboard_args is not None: self._dashboard = ObjectManager(**dashboard_args) from astropysics.constants import Rs, Lsun, Ms self.Pc_Guess = float(self._config["Star.Initial.Pc"]) self.Tc_Guess = float(self._config["Star.Initial.Tc"]) self.R_Guess = float(self._config["Star.Initial.Rs"]) * Rs self.L_Guess = float(self._config["Star.Initial.Ls"]) * Lsun self.dm_Guess = float(self._config["Star.Initial.dm"]) * Ms self.fp = float(self._config["Star.Integration.fp"]) * self.M self._update_frequency = self.config["System.Dashboard.Update"] self._save_frequency = self.config["System.Outputs.Update"]
def __init__(self, fkey,load=True, filename="OPAL.yml", X=None, Y=None, snap=False, error=True, wmax=100, efkey=None): super(OpacityTable, self).__init__() # Initialize our attribute values # Compositional Values self._X = None self._Y = None self._dXc = None self._dXo = None self._interpolator = None # Object for the interpolator self._snap = snap # Snap out-of-bounds objects to the grid. self._error = error # Use python errors, or return np.nan self._warnings = { "NaNs" : 0 } self._warnings_max = wmax # Logging Values: self.log = logging.getLogger(__name__) # Set up the configuration self.fkey = fkey self.cfg = DottedConfiguration({}) self.cfg.load(filename,silent=False) self.cfg.dn = DottedConfiguration self.table_type = self.cfg[self.fkey].get('type','single') self.table_comp = (X,Y) # Load the tables (from cached .npy files if appropriate) self.log.debug("Loading Tables...") if load: try: self.load() except IOError: self.read() self.save() else: self.read() self.log.debug("Tables Loaded: %s",repr(self._tbls.shape)) # Make ourselves a nearest-neighbor composition interpolator self._composition_interpolator() # If we have a composition, we should use it. if X is not None and Y is not None: self.composition(X,Y) if efkey is not None: self.log.debug("Loading extension tables") if self.cfg[efkey].get('type','single') == 'single': e_comp, e_tbls = read_table_opal(efkey,cfg=self.cfg) self.log.debug("Read Opacity Tables from %(filename)s" % self.cfg[efkey]) elif self.cfg[efkey].get('type','single') == 'split': e_comp, e_tbls = read_standalone_opal(self.X,self.Z,efkey,cfg=self.cfg) self.log.debug("Read Opacity Tables from %(filename)s" % self.cfg[efkey]) self._tbls = merge_tables(self._comp,e_comp,self._tbls,e_tbls) self.log.debug("Read Extension Opacity Tables from %(filename)s" % self.cfg[efkey]) self._temperature_density_interpolator() self.log.debug("Opacity Interpolator Initialzied")
class OpacityTable(object): """This object can interpolate across opacity table information. We use a nearest point interpolator to find the composition table that best matches the requested composition. A linear interpolation is then used to find requested opacity value. """ def __init__(self, fkey,load=True, filename="OPAL.yml", X=None, Y=None, snap=False, error=True, wmax=100, efkey=None): super(OpacityTable, self).__init__() # Initialize our attribute values # Compositional Values self._X = None self._Y = None self._dXc = None self._dXo = None self._interpolator = None # Object for the interpolator self._snap = snap # Snap out-of-bounds objects to the grid. self._error = error # Use python errors, or return np.nan self._warnings = { "NaNs" : 0 } self._warnings_max = wmax # Logging Values: self.log = logging.getLogger(__name__) # Set up the configuration self.fkey = fkey self.cfg = DottedConfiguration({}) self.cfg.load(filename,silent=False) self.cfg.dn = DottedConfiguration self.table_type = self.cfg[self.fkey].get('type','single') self.table_comp = (X,Y) # Load the tables (from cached .npy files if appropriate) self.log.debug("Loading Tables...") if load: try: self.load() except IOError: self.read() self.save() else: self.read() self.log.debug("Tables Loaded: %s",repr(self._tbls.shape)) # Make ourselves a nearest-neighbor composition interpolator self._composition_interpolator() # If we have a composition, we should use it. if X is not None and Y is not None: self.composition(X,Y) if efkey is not None: self.log.debug("Loading extension tables") if self.cfg[efkey].get('type','single') == 'single': e_comp, e_tbls = read_table_opal(efkey,cfg=self.cfg) self.log.debug("Read Opacity Tables from %(filename)s" % self.cfg[efkey]) elif self.cfg[efkey].get('type','single') == 'split': e_comp, e_tbls = read_standalone_opal(self.X,self.Z,efkey,cfg=self.cfg) self.log.debug("Read Opacity Tables from %(filename)s" % self.cfg[efkey]) self._tbls = merge_tables(self._comp,e_comp,self._tbls,e_tbls) self.log.debug("Read Extension Opacity Tables from %(filename)s" % self.cfg[efkey]) self._temperature_density_interpolator() self.log.debug("Opacity Interpolator Initialzied") def _composition_interpolator(self): """Creates the compositional (X,Y,Z) interpolator""" from scipy.interpolate import NearestNDInterpolator points = self._comp[:,:4] values = self._comp[:,4] self.log.debug("Composition Interpolating in %d dimensions on %d points" % (points.shape[1],points.shape[0])) self.log.debug("Input Shapes: [x,y]=%r, [v]=%r" % (repr(points.shape), repr(values.shape))) nans = (np.sum(np.isnan(points)), np.sum(np.isnan(values))) if nans[0] > 0 or nans[1] > 0: self.log.debug("Input NaNs: [x,y]=%g, [v]=%g" % nans) self._table_number = NearestNDInterpolator(points,values) def _temperature_density_interpolator(self): """Creates the temperature-density interpolator""" from scipy.interpolate import LinearNDInterpolator self.log.debug("Initializing Main Interpolator...") points = self.tbl[:,:2] values = self.tbl[:,2] points = points[np.isfinite(values)] values = values[np.isfinite(values)] self.log.debug("Interpolating Opacity in %d dimensions on %d points" % (points.shape[1],points.shape[0])) self.log.debug(u"Input Shapes: [logR,logT]=%r, [κ]=%r" % (repr(points.shape), repr(values.shape))) nans = (np.sum(np.isnan(points)), np.sum(np.isnan(values))) if nans[0] > 0 or nans[1] > 0: log.debug(u"Input NaNs: [logR,logT]=%g, [κ]=%g" % nans) self._interpolator = LinearNDInterpolator(points,values) self.log.debug("Created Opacity Interpolator") def read(self): """Read the opacity tables from an OPAL file.""" if self.table_type == 'single': self._comp, self._tbls = read_table_opal(self.fkey,cfg=self.cfg) self.log.debug("Read Opacity Tables from %(filename)s" % self.cfg[self.fkey]) elif self.table_type == 'split': X,Y = self.table_comp Z = 1.0 - X - Y self._comp, self._tbls = read_standalone_opal(X,Z,self.fkey,cfg=self.cfg) self.log.debug("Read Opacity Tables from %(filename)s" % self.cfg[self.fkey] % dict(X=X,Z=Z)) def load(self): """Load the interpolator values from a pair of files. Will load from two numpy files the composition table and the opacity tables.""" c = self.cfg[self.fkey] self._comp = np.load("%s.comp.npy" % c["filename"]) self._tbls = np.load("%s.tbls.npy" % c["filename"]) self.log.debug("Loaded Opacity Tables from Numpy Files: %(filename)s.*.npy" % self.cfg[self.fkey]) def save(self): """Save this interpolator to a numpy file. The composition and tables will be saved to separate tables.""" c = self.cfg[self.fkey] np.save("%s.comp.npy" % c["filename"],self._comp) np.save("%s.tbls.npy" % c["filename"],self._tbls) self.log.debug("Saved Opacity Tables to Numpy Files: %(filename)s.*.npy" % self.cfg[self.fkey]) @property def X(self): """Fraction of H atoms""" return self._X @property def Y(self): """Fraction of He atoms""" return self._Y @property def Z(self): """Fraction of 'metal' atoms""" return (1.0 - self.X - self.Y - self.dXo - self.dXc) @property def dXo(self): """Fraction of oxygen greater than Z""" return self._dXo @property def dXc(self): """Fraction of carbon greater than Z""" return self._dXc @property def n(self): """Table number currently in use.""" return self._n @property def tbl(self): """The table!""" return np.asarray(self._tbls[int(self.n)]) def properties(self): """Return the properties of this object as a tuple: (n, X, Y, Z, dXc, dXo) """ return (self.n, self.X, self.Y, self.Z, self.dXc, self.dXo) def composition(self,X,Y,dXc=0.0,dXo=0.0): ur"""Set the composition for this sytem. Composition must total to 1.0. Assumes that given X, Y, dXc, dXo, that .. math:: X + Y + dXc + dXo = 1 - Z :param X: Fractional content of hydrogen. :param Y: Fractional content of helium. :param dXc: Fractional content of carbon. :param dXo: Fractional content of oxygen. This function will interpolate to the nearest composition value that it can find. As such, beware that the requested composition will be moved to the nearest acceptable composition in the current opacity tables. """ assert X + Y + dXc + dXo <= 1.0, u"Composition must be less than or equal to 1.0! ∑X=%0.1f" % (X + Y + dXc + dXo) if X == self.X and Y == self.Y and dXc == self.dXc and dXo == self.dXo: self.log.debug("Values are unchanged") return point = np.atleast_2d(np.array([X,Y,dXc,dXo])) self._n = self._table_number(point)[0] self._X = self._comp[self.n,0] self._Y = self._comp[self.n,1] self._dXc = self._comp[self.n,2] self._dXo = self._comp[self.n,3] if X != self.X or Y != self.Y or dXc != self.dXc or dXo != self.dXo: self.log.warning("Interoplation reached a nearby value, not exact requested composition: X=%.4f, Y=%.4f, dXc=%.4f, dXo=%.4f" % (self.X, self.Y, self.dXc, self.dXo)) self._temperature_density_interpolator() @classmethod def invert_points(cls,logR,logT): ur"""Return a log(rho) and log(T) for us. :param logR: An array (or single value) of :math:`\log(R)` (Table Units). :param logT: An array (or single value) of :math:`\log(T)` (Table Units). :returns: An array of [:math:`\log(\rho)`, :math:`\log(T)` ]. """ logR = np.asarray(logR) logT = np.asarray(logT) R = np.power(10,logR) T = np.power(10,logT) T6 = 1e-6 * np.power(10,logT) rho = R * np.power(T6,3) logrho = np.log10(rho) return np.vstack((logrho,logT)).T @classmethod def make_points(cls,logrho,logT): ur"""Convert the units for a point into proper table units. :param logrho: An array (or single value) of :math:`\log(\rho)`. :param logT: An array (or single value) of :math:`\log(T)`. :returns: An array of [:math:`\log(R)`, :math:`\log(T)`]. """ logrho = np.asarray(logrho) logT = np.asarray(logT) T6 = 1e-6 * (np.power(10,logT)) rho = np.power(10,logrho) R = rho / (np.power(T6,3)) logR = np.log10(R) return np.vstack((logR,logT)).T def validate(self,points,RMode=False,Describe=False): ur"""Return a boolean if the points guessed are in the table at all. :param np.array points: An array of :math:`\log(\rho)` and :math:`\log(T)` to be checked. :param bool RMode: Whether to assume [logR,logT] (else, assume [logrho,logT]) :returns: True if the points fit within the table. """ if not RMode: points = self.make_points(*points) try: self.__valid__(points) except OpacityTableError as e: if Describe: return False, e.code, e.codes[e.code] else: return False else: if Describe: return True, 0, "No Error" else: return True def bounds(self,logR=None,logT=None): """Return the bounds of the selected opacity table.""" top = np.array([np.max(self.tbl[:,i]) for i in xrange(2)]) bot = np.array([np.min(self.tbl[:,i]) for i in xrange(2)]) return np.vstack((top,bot)).T def snap(self,points): """Take a pair of points and place them back on the valid area.""" maxes = np.array([(points[:,i] <= np.max(self.tbl[:,i])).any() for i in xrange(2)]).all() mines = np.array([(points[:,i] >= np.min(self.tbl[:,i])).any() for i in xrange(2)]).all() if (mines and maxes): return points for point in points: for ind,ele in enumerate(point): vmax, vmin = np.max(self.tbl[:,ind]),np.min(self.tbl[:,ind]) if ele > vmax: point[ind] = vmax elif ele < vmin: point[ind] = vmin return points def __valid__(self,points): ur"""Check the range of this point compared to the opacity table range. :param np.array points: An array of :math:`\log(\rho)` and :math:`\log(T)` to be checked. :raises: :exc:`ValueError` when points are out of bounds. """ maxes = np.array([(points[:,i] <= np.max(self.tbl[:,i])).any() for i in xrange(2)]).all() mines = np.array([(points[:,i] >= np.min(self.tbl[:,i])).any() for i in xrange(2)]).all() if (mines and maxes): return True cols = {0:"logR",1:"logT"} for point in points: for ind,ele in enumerate(point): vmax, vmin = np.max(self.tbl[:,ind]),np.min(self.tbl[:,ind]) if ele > vmax: raise OpacityTableError(msg="BOUNDS: %s=%g > %g" % (cols[ind],ele,vmax),code=2**(ind+2),val=vmax) elif ele < vmin: raise OpacityTableError(msg="BOUNDS: %s=%g < %g" % (cols[ind],ele,vmin),code=2**(ind+2)+1,val=vmin) raise OpacityTableError(msg="BOUNDS: Error Index Unknown!!!, %r" % points,code=2**4) def lookup(self,points=None,logrho=None,logT=None): ur""" A lookup function for our interpolator. Does the pure lookup. :param points: An array of :math:`\log(\rho)` and :math:`\log(T)` :param logrho: An array (or single value) of :math:`\log(\rho)` used only when `points` is not provided. :param logT: An array (or single value) of :math:`\log(T)` used only when `points` is not provided. :returns: κ, an array of the rosseland mean opacity. """ if self._interpolator is None: raise OpacityTableError(code=2**1) if points is None: assert logrho is not None, u"Must provide a log(ρ)=?" assert logT is not None, u"Must provide a log(T)=?" logrho = np.asarray(logrho) logT = np.asarray(logT) points = self.make_points(logrho=logrho,logT=logT) else: points = self.make_points(logrho=points[:,0],logT=points[:,1]) if self._snap: points = self.snap(points) if self._error: self.__valid__(points) kappa = self._interpolator(points) return kappa def kappa(self,logrho=None,logT=None,rho=None,T=None): ur"""Return a rosseland mean opacity at a temperature and density. :param logrho: Logarithmic Density, base 10, :math:`\log(\rho)` :param rho: Density. Accepts `logrho` or `rho`. :param logT: Logarithmic Temperature, :math:`\log(T)` :param T: Temperature. Accepts `logT` or `T`. :returns: κ, the rosseland mean opacity. """ assert (logrho is not None) ^ (rho is not None), u"Must provide one and only one value for ρ." assert (logT is not None) ^ (T is not None), u"Must provide one and only one value for T" logT = logT if logT is not None else np.log10(T) logrho = logrho if logrho is not None else np.log10(rho) kappa = np.power(10,self.lookup(logT=logT,logrho=logrho)) knans = np.sum(np.isnan(kappa)) if knans > 0: self._warnings["NaNs"] += 1 if knans > 0 and self._warnings["NaNs"] < self._warnings_max: if len(kappa) == 1: self.log.warning("Opacity Table Returned NaN: Kappa: %g logT: %g, logrho: %g" % (kappa,logT,logrho)) else: inans = np.sum(np.isnan(logT)) + np.sum(np.isnan(logrho)) inputs = logT.size + logrho.size self.log.warning("Opacity Table Returned NaNs: Kappas: %d/%d, Inputs: %d/%d" % (knans,kappa.size,inans,inputs)) if T is not None and rho is not None: self.log.debug("T: %s Rho: %s" % (T,rho)) self.log.debug("K: %s" % kappa) elif knans > 0 and self._warnings["NaNs"] == self._warnings_max: self.log.warning("Caught %d NaN Warnings. Future warnings will be suppressed." % self._warnings["NaNs"]) if np.isnan(kappa).any() and self._error: raise ValueError("BOUNDS: Interpolator returned NaN") return kappa
class Star(object): """A simple configured star object.""" def __init__(self, config=None, optable_args=None, dashboard_args=None): super(Star, self).__init__() self.log = logging.getLogger(__name__) self.data_log = logging.getLogger("data") self.telem_log = logging.getLogger("telemetry") self.telem_log.propagate = False self._call_count = 0 self._warning_count = {"Neg": 0} self.name = current_process().name self._config = DottedConfiguration(config) self._config.dn = DottedConfiguration self._integrator_name = self.config.get("System.Integrator.Method", "scipy") self._max_warnings = self.config["System.Integrator"].get( "Warnings", 100) self._integrator = getattr(self, self._integrator_name) self._logmode = self.config.get("System.Integrator.LogScale", True) self._plotting = self.config.get("System.Integrator.LivePlot", True) np.set_printoptions(**self.config["System.Numpy.PrintOptions"]) if optable_args is None: self.log.debug("Starting Opacity Table from Scratch") self._opacity = ObjectThread( OpacityTable, ikwargs=dict(fkey=self._config["Data.Opacity.Config"], X=self.X, Y=self.Y, snap=self._config["System.Opacity.Snap"], error=self._config["System.Opacity.Error"], wmax=self._config["System.Opacity.Warnings"], efkey=self._config.get( "System.Opacity.ExtendedKey", None)), locking=True, timeout=self._config["System.Opacity.Timeout"]) self.opacity.start() self.log.debug("Started Opacity Table From Scratch") else: self.log.debug("Starting Opacity Table from Arguments") self._opacity = ObjectManager(**optable_args) self.log.debug("Started Opacity Table from Arguments") if dashboard_args is not None: self._dashboard = ObjectManager(**dashboard_args) from astropysics.constants import Rs, Lsun, Ms self.Pc_Guess = float(self._config["Star.Initial.Pc"]) self.Tc_Guess = float(self._config["Star.Initial.Tc"]) self.R_Guess = float(self._config["Star.Initial.Rs"]) * Rs self.L_Guess = float(self._config["Star.Initial.Ls"]) * Lsun self.dm_Guess = float(self._config["Star.Initial.dm"]) * Ms self.fp = float(self._config["Star.Integration.fp"]) * self.M self._update_frequency = self.config["System.Dashboard.Update"] self._save_frequency = self.config["System.Outputs.Update"] def set_fitting_point(self, point): """Set a new fitting point for this routine""" self.fp = point def set_guesses(self, R, L, Pc, Tc): """Set the initial guess vector""" self.Pc_Guess = Pc self.Tc_Guess = Tc self.R_Guess = R self.L_Guess = L def get_guesses(self): """Return the guess array""" return np.array( [self.R_Guess, self.L_Guess, self.Pc_Guess, self.Tc_Guess]) def integral(self, y, x, i): """docstring for integral""" x = np.asarray(x) y = np.asarray(y) self._call_count += 1 dy = self.fprime(xs=x, ys=y, mu=self.mu, optable=self.opacity, X=self.X, XCNO=self.Z, cfg=self.config["Data.Energy"])[:, 0] if self._logmode: x = np.power(10, x) update = self._update_frequency != 0 and self._call_count % self._update_frequency == 0 telem = self._save_frequency != 0 and self._call_count % self._save_frequency == 0 if (y < 0).any() or update or telem: rho = density(P=y[2], T=y[3], mu=self.mu) eps = dy[1] if self._logmode: eps /= (x * np.log(10)) self.opacity.kappa(T=y[3], rho=rho) rgrad = radiative_gradient(T=y[3], P=y[2], l=y[1], m=x, rho=rho, optable=self.opacity) agrad = grad(rgrad) self.opacity.kappa(T=y[3], rho=rho) kappa = self.opacity.retrieve() if (y < 0).any(): self._warning_count["Neg"] += 1 if self._warning_count["Neg"] == self._max_warnings: self.log.warning( "Future Negative Value Warnings will be suppressed. Passed maximum number of warnings: %d" % self._max_warnings) elif self._warning_count["Neg"] < self._max_warnings: self.log.warning( u"%s y<0 at: \nx=%r, \ny=%r, \ndy=%r, \nρ=%g \nε=%g \n∇=%g \nκ=%g" % (i, x, y, dy, rho, eps, agrad, kappa[0])) if update: if self._plotting: self.append_dashboard(x, y, rho, agrad, kappa, eps, line=i + self.name) self.dashboard.update("live") self.log.info( u"%s %d calls at: \nx=%r, \ny=%r, \ndy=%r, \nρ=%g \nε=%g \n∇=%g \nκ=%g" % (i, self._call_count, x, y, dy, rho, eps, agrad, kappa[0])) if telem: self.telem_log.info(u"%r %r %r %g %g %g %g" % (x, y, dy, rho, eps, agrad, kappa[0])) return dy @property def integrator(self): """The actual integrator function!""" return self._integrator @property def fprime(self): """The correct fprime function for integration""" if self._logmode: return log_derivatives else: return derivatives def show_surface_start(self): """Show a detailed view of the start of integration at the center""" self.fp = (1 - 1e-3) * self.M self._config["System.Outputs.Size"] = 10 return self.surface() def show_center_start(self): """Show a detailed view of the start of integration at the center""" self.fp = 1e30 self._config["System.Outputs.Size"] = 10 return self.center() def center(self, plotting=True): """Run the center integration.""" self._plotting = plotting self.log.debug("Getting Inner Boundaries") self.log.debug( "Initially, star has Tc=%g, Pc=%g, M=%g, m=%g, convective=%s" % (self.Tc_Guess, self.Pc_Guess, self.M, self.dm_Guess, self.config["Star.Initial.Convective"])) center_ic = inner_boundary( Pc=self.Pc_Guess, Tc=self.Tc_Guess, M=self.M, mu=self.mu, m=self.dm_Guess, optable=self.opacity, X=self.X, XCNO=self.XCNO, cfg=self.config["Data.Energy"], convective=self.config["Star.Initial.Convective"]) if self._logmode: integrator = "LogCenter" ms = np.linspace(np.log10(self.dm_Guess), np.log10(self.fp), self._config["System.Outputs.Size"]) else: integrator = "Center" ms = np.logspace(np.log10(self.dm_Guess), np.log10(self.fp), self._config["System.Outputs.Size"]) self.log.debug("Inner Conditions (%s): x=%g, y=%r" % (integrator, self.dm_Guess, np.array(center_ic))) self.log.debug("Starting %s Integration" % integrator) return self.integrator(ms, center_ic, integrator) def surface(self, plotting=True): """Run an integration from the surface to the inner edge""" self._plotting = plotting self.log.debug("Getting Outer Boundaries") outer_ic = outer_boundary(R=self.R_Guess, L=self.L_Guess, M=self.M, mu=self.mu, optable=self.opacity) (r, l, P, T) = outer_ic if self._logmode: integrator = "LogSurface" ms = np.linspace(np.log10(self.fp), np.log10(self.M), self._config["System.Outputs.Size"])[::-1] else: integrator = "Surface" ms = np.logspace(np.log10(self.fp), np.log10(self.M), self._config["System.Outputs.Size"])[::-1] self.log.debug("Surface Conditions (%s): x=%g, y=%r" % (integrator, self.M, np.array(outer_ic))) self.log.debug("Starting %s Integration" % integrator) return self.integrator(ms, outer_ic, integrator) def pystellar(self, xs, ics, integrator): """Run an integration from the central point to the outer edge.""" ys, data = integrate(self.integral, xs, ics, args=(integrator, ), **self.config["System.Integrator.PyStellar"] [integrator]["Arguments"]) self.log.debug("Finished %s Integration" % integrator) if self._logmode: xs = np.power(10, xs) rho = density(P=ys[:, 2], T=ys[:, 3], mu=self.mu) eps = dldm(T=ys[:, 3], rho=rho, X=self.X, XCNO=self.XCNO, cfg=self.config["Data.Energy"]) self.opacity.kappa(T=ys[:, 3], rho=rho) rgrad = radiative_gradient(T=ys[:, 3], P=ys[:, 2], l=ys[:, 1], m=xs, rho=rho, optable=self.opacity) agrad = grad(rgrad) self.opacity.kappa(T=ys[:, 3], rho=rho) kappa = self.opacity.retrieve() all_data = np.vstack( map(np.atleast_2d, (xs, ys.T, rho, eps, rgrad, agrad, kappa))).T np.savetxt( self.config["System.Outputs.Data.Integration"] % {'integrator': integrator}, all_data) if self._plotting and np.isfinite(all_data).all(): self.update_dashboard(xs, ys.T, rho, agrad, kappa, eps, line=integrator + self.name, figure='split') self.dashboard.update("integration", "integrationextras") elif not np.isfinite(all_data).all(): self.log.debug( "Skipping integration plots due to non-finite data.") self.log.debug("Plotted %s Integration" % integrator) return ys, xs, data def scipy(self, xs, ics, integrator): """Run an integration from the central point to the outer edge.""" self.log.debug("Calling %s Scipy Integration" % integrator) import scipy.integrate ys, data = scipy.integrate.odeint( self.integral, ics, xs, args=(integrator, ), full_output=True, **self.config["System.Integrator.Scipy"][integrator]["Arguments"]) self.log.debug( "Finished %s Integration: %d timesteps, %d function calls." % (integrator, data["nst"][-1], data["nfe"][-1])) derivs = self.fprime(xs=xs, ys=ys, mu=self.mu, optable=self.opacity, X=self.X, XCNO=self.Z, cfg=self.config["Data.Energy"]) if self._logmode: xs = np.power(10, xs) rho = density(P=ys[:, 2], T=ys[:, 3], mu=self.mu) eps = dldm(T=ys[:, 3], rho=rho, X=self.X, XCNO=self.XCNO, cfg=self.config["Data.Energy"]) self.opacity.kappa(T=ys[:, 3], rho=rho) rgrad = radiative_gradient(T=ys[:, 3], P=ys[:, 2], l=ys[:, 1], m=xs, rho=rho, optable=self.opacity) agrad = grad(rgrad) self.opacity.kappa(T=ys[:, 3], rho=rho) kappa = self.opacity.retrieve() all_data = np.vstack( map(np.atleast_2d, (xs, ys.T, rho, eps, rgrad, agrad, kappa))).T np.savetxt( self.config["System.Outputs.Data.Integration"] % {'integrator': integrator}, all_data) if self._plotting: self.update_dashboard(xs, ys.T, rho, agrad, kappa, eps, line=integrator + self.name, figure='split') self.dashboard.update("integration", "integrationextras") elif np.isnan(all_data).any(): self.log.debug( "Skipping integration plots due to non-finite data.") self.log.debug("Plotted %s Integration" % integrator) return ys, xs, data def update_dashboard(self, x, y, rho=None, gradient=None, kappa=None, epsilon=None, line=None, figure='live'): """A wrapper to perform dashboard updates.""" line = self.name if line is None else line if figure == 'split': mfig = 'integration' ofig = 'integrationextras' else: mfig, ofig = figure, figure for yi, name in zip( y, ["radius", "luminosity", "pressure", "temperature"]): self.dashboard.update_data(x, yi, figure=mfig, axes=name, line=line, lw=2.0) if rho is not None: self.dashboard.update_data(x, rho, figure=ofig, axes="density", line=line, lw=2.0) if gradient is not None: self.dashboard.update_data(x, gradient, figure=ofig, axes="gradient", line=line, lw=2.0) if kappa is not None: self.dashboard.update_data(x, kappa, figure=ofig, axes="opacity", line=line, lw=2.0) if epsilon is not None: self.dashboard.update_data(x, epsilon, figure=ofig, axes="epsilon", line=line, lw=2.0) def append_dashboard(self, x, y, rho=None, gradient=None, kappa=None, epsilon=None, line=None, figure='live'): """Append data to the dashboard""" line = self.name if line is None else line if figure == 'split': mfig = 'integration' ofig = 'integrationextras' else: mfig, ofig = figure, figure for yi, name in zip( y, ["radius", "luminosity", "pressure", "temperature"]): self.dashboard.append_data(x, yi, figure=mfig, axes=name, line=line) if rho is not None: self.dashboard.append_data(x, rho, figure=ofig, axes="density", line=line) if gradient is not None: self.dashboard.append_data(x, gradient, figure=ofig, axes="gradient", line=line) if kappa is not None: self.dashboard.append_data(x, kappa, figure=ofig, axes="opacity", line=line) if epsilon is not None: self.dashboard.append_data(x, epsilon, figure=ofig, axes="epsilon", line=line) @property def dashboard(self): """Dashboard Thread""" return self._dashboard @property def opacity(self): """Opacity table""" return self._opacity # Compositon Accessor Functions @property def X(self): """Fraction of Hydrogen""" return self._config["Star.Composition.X"] @property def Y(self): """Fraction of Helium""" return self._config["Star.Composition.Y"] @property def dXc(self): """Excess fraction of Carbon""" return self._config["Star.Composition.dXc"] @property def dXo(self): """Excess fraction of Oxygen""" return self._config["Star.Composition.dXo"] @property def Z(self): """Fraction of Heavy Elements""" return 1.0 - (self.X + self.Y + self.dXc + self.dXo) @property def XCNO(self): """Fraction of Carbon, Oxygen and Nitrogen""" return self.config[ "Star.Composition.ZdXCNO"] * self.Z + self.dXc + self.dXo @property def mu(self): """Mean molecular weight of this star.""" return mmw(X=self.X, Y=self.Y) @property def M(self): """Total Mass of the Star""" from astropysics.constants import Ms return self._config["Star.Properties.M"] * Ms # Configuration Functions @property def config(self): """The configuration for this star.""" return self._config def stop(self): """End this star!""" if isinstance(self.opacity, ObjectThread): self.opacity.stop() def kill(self): """Kill this star!""" pass
def __init__(self, fkey, load=True, filename="OPAL.yml", X=None, Y=None, snap=False, error=True, wmax=100, efkey=None): super(OpacityTable, self).__init__() # Initialize our attribute values # Compositional Values self._X = None self._Y = None self._dXc = None self._dXo = None self._interpolator = None # Object for the interpolator self._snap = snap # Snap out-of-bounds objects to the grid. self._error = error # Use python errors, or return np.nan self._warnings = {"NaNs": 0} self._warnings_max = wmax # Logging Values: self.log = logging.getLogger(__name__) # Set up the configuration self.fkey = fkey self.cfg = DottedConfiguration({}) self.cfg.load(filename, silent=False) self.cfg.dn = DottedConfiguration self.table_type = self.cfg[self.fkey].get('type', 'single') self.table_comp = (X, Y) # Load the tables (from cached .npy files if appropriate) self.log.debug("Loading Tables...") if load: try: self.load() except IOError: self.read() self.save() else: self.read() self.log.debug("Tables Loaded: %s", repr(self._tbls.shape)) # Make ourselves a nearest-neighbor composition interpolator self._composition_interpolator() # If we have a composition, we should use it. if X is not None and Y is not None: self.composition(X, Y) if efkey is not None: self.log.debug("Loading extension tables") if self.cfg[efkey].get('type', 'single') == 'single': e_comp, e_tbls = read_table_opal(efkey, cfg=self.cfg) self.log.debug("Read Opacity Tables from %(filename)s" % self.cfg[efkey]) elif self.cfg[efkey].get('type', 'single') == 'split': e_comp, e_tbls = read_standalone_opal(self.X, self.Z, efkey, cfg=self.cfg) self.log.debug("Read Opacity Tables from %(filename)s" % self.cfg[efkey]) self._tbls = merge_tables(self._comp, e_comp, self._tbls, e_tbls) self.log.debug("Read Extension Opacity Tables from %(filename)s" % self.cfg[efkey]) self._temperature_density_interpolator() self.log.debug("Opacity Interpolator Initialzied")
class OpacityTable(object): """This object can interpolate across opacity table information. We use a nearest point interpolator to find the composition table that best matches the requested composition. A linear interpolation is then used to find requested opacity value. """ def __init__(self, fkey, load=True, filename="OPAL.yml", X=None, Y=None, snap=False, error=True, wmax=100, efkey=None): super(OpacityTable, self).__init__() # Initialize our attribute values # Compositional Values self._X = None self._Y = None self._dXc = None self._dXo = None self._interpolator = None # Object for the interpolator self._snap = snap # Snap out-of-bounds objects to the grid. self._error = error # Use python errors, or return np.nan self._warnings = {"NaNs": 0} self._warnings_max = wmax # Logging Values: self.log = logging.getLogger(__name__) # Set up the configuration self.fkey = fkey self.cfg = DottedConfiguration({}) self.cfg.load(filename, silent=False) self.cfg.dn = DottedConfiguration self.table_type = self.cfg[self.fkey].get('type', 'single') self.table_comp = (X, Y) # Load the tables (from cached .npy files if appropriate) self.log.debug("Loading Tables...") if load: try: self.load() except IOError: self.read() self.save() else: self.read() self.log.debug("Tables Loaded: %s", repr(self._tbls.shape)) # Make ourselves a nearest-neighbor composition interpolator self._composition_interpolator() # If we have a composition, we should use it. if X is not None and Y is not None: self.composition(X, Y) if efkey is not None: self.log.debug("Loading extension tables") if self.cfg[efkey].get('type', 'single') == 'single': e_comp, e_tbls = read_table_opal(efkey, cfg=self.cfg) self.log.debug("Read Opacity Tables from %(filename)s" % self.cfg[efkey]) elif self.cfg[efkey].get('type', 'single') == 'split': e_comp, e_tbls = read_standalone_opal(self.X, self.Z, efkey, cfg=self.cfg) self.log.debug("Read Opacity Tables from %(filename)s" % self.cfg[efkey]) self._tbls = merge_tables(self._comp, e_comp, self._tbls, e_tbls) self.log.debug("Read Extension Opacity Tables from %(filename)s" % self.cfg[efkey]) self._temperature_density_interpolator() self.log.debug("Opacity Interpolator Initialzied") def _composition_interpolator(self): """Creates the compositional (X,Y,Z) interpolator""" from scipy.interpolate import NearestNDInterpolator points = self._comp[:, :4] values = self._comp[:, 4] self.log.debug( "Composition Interpolating in %d dimensions on %d points" % (points.shape[1], points.shape[0])) self.log.debug("Input Shapes: [x,y]=%r, [v]=%r" % (repr(points.shape), repr(values.shape))) nans = (np.sum(np.isnan(points)), np.sum(np.isnan(values))) if nans[0] > 0 or nans[1] > 0: self.log.debug("Input NaNs: [x,y]=%g, [v]=%g" % nans) self._table_number = NearestNDInterpolator(points, values) def _temperature_density_interpolator(self): """Creates the temperature-density interpolator""" from scipy.interpolate import LinearNDInterpolator self.log.debug("Initializing Main Interpolator...") points = self.tbl[:, :2] values = self.tbl[:, 2] points = points[np.isfinite(values)] values = values[np.isfinite(values)] self.log.debug("Interpolating Opacity in %d dimensions on %d points" % (points.shape[1], points.shape[0])) self.log.debug(u"Input Shapes: [logR,logT]=%r, [κ]=%r" % (repr(points.shape), repr(values.shape))) nans = (np.sum(np.isnan(points)), np.sum(np.isnan(values))) if nans[0] > 0 or nans[1] > 0: log.debug(u"Input NaNs: [logR,logT]=%g, [κ]=%g" % nans) self._interpolator = LinearNDInterpolator(points, values) self.log.debug("Created Opacity Interpolator") def read(self): """Read the opacity tables from an OPAL file.""" if self.table_type == 'single': self._comp, self._tbls = read_table_opal(self.fkey, cfg=self.cfg) self.log.debug("Read Opacity Tables from %(filename)s" % self.cfg[self.fkey]) elif self.table_type == 'split': X, Y = self.table_comp Z = 1.0 - X - Y self._comp, self._tbls = read_standalone_opal(X, Z, self.fkey, cfg=self.cfg) self.log.debug("Read Opacity Tables from %(filename)s" % self.cfg[self.fkey] % dict(X=X, Z=Z)) def load(self): """Load the interpolator values from a pair of files. Will load from two numpy files the composition table and the opacity tables.""" c = self.cfg[self.fkey] self._comp = np.load("%s.comp.npy" % c["filename"]) self._tbls = np.load("%s.tbls.npy" % c["filename"]) self.log.debug( "Loaded Opacity Tables from Numpy Files: %(filename)s.*.npy" % self.cfg[self.fkey]) def save(self): """Save this interpolator to a numpy file. The composition and tables will be saved to separate tables.""" c = self.cfg[self.fkey] np.save("%s.comp.npy" % c["filename"], self._comp) np.save("%s.tbls.npy" % c["filename"], self._tbls) self.log.debug( "Saved Opacity Tables to Numpy Files: %(filename)s.*.npy" % self.cfg[self.fkey]) @property def X(self): """Fraction of H atoms""" return self._X @property def Y(self): """Fraction of He atoms""" return self._Y @property def Z(self): """Fraction of 'metal' atoms""" return (1.0 - self.X - self.Y - self.dXo - self.dXc) @property def dXo(self): """Fraction of oxygen greater than Z""" return self._dXo @property def dXc(self): """Fraction of carbon greater than Z""" return self._dXc @property def n(self): """Table number currently in use.""" return self._n @property def tbl(self): """The table!""" return np.asarray(self._tbls[int(self.n)]) def properties(self): """Return the properties of this object as a tuple: (n, X, Y, Z, dXc, dXo) """ return (self.n, self.X, self.Y, self.Z, self.dXc, self.dXo) def composition(self, X, Y, dXc=0.0, dXo=0.0): ur"""Set the composition for this sytem. Composition must total to 1.0. Assumes that given X, Y, dXc, dXo, that .. math:: X + Y + dXc + dXo = 1 - Z :param X: Fractional content of hydrogen. :param Y: Fractional content of helium. :param dXc: Fractional content of carbon. :param dXo: Fractional content of oxygen. This function will interpolate to the nearest composition value that it can find. As such, beware that the requested composition will be moved to the nearest acceptable composition in the current opacity tables. """ assert X + Y + dXc + dXo <= 1.0, u"Composition must be less than or equal to 1.0! ∑X=%0.1f" % ( X + Y + dXc + dXo) if X == self.X and Y == self.Y and dXc == self.dXc and dXo == self.dXo: self.log.debug("Values are unchanged") return point = np.atleast_2d(np.array([X, Y, dXc, dXo])) self._n = self._table_number(point)[0] self._X = self._comp[self.n, 0] self._Y = self._comp[self.n, 1] self._dXc = self._comp[self.n, 2] self._dXo = self._comp[self.n, 3] if X != self.X or Y != self.Y or dXc != self.dXc or dXo != self.dXo: self.log.warning( "Interoplation reached a nearby value, not exact requested composition: X=%.4f, Y=%.4f, dXc=%.4f, dXo=%.4f" % (self.X, self.Y, self.dXc, self.dXo)) self._temperature_density_interpolator() @classmethod def invert_points(cls, logR, logT): ur"""Return a log(rho) and log(T) for us. :param logR: An array (or single value) of :math:`\log(R)` (Table Units). :param logT: An array (or single value) of :math:`\log(T)` (Table Units). :returns: An array of [:math:`\log(\rho)`, :math:`\log(T)` ]. """ logR = np.asarray(logR) logT = np.asarray(logT) R = np.power(10, logR) T = np.power(10, logT) T6 = 1e-6 * np.power(10, logT) rho = R * np.power(T6, 3) logrho = np.log10(rho) return np.vstack((logrho, logT)).T @classmethod def make_points(cls, logrho, logT): ur"""Convert the units for a point into proper table units. :param logrho: An array (or single value) of :math:`\log(\rho)`. :param logT: An array (or single value) of :math:`\log(T)`. :returns: An array of [:math:`\log(R)`, :math:`\log(T)`]. """ logrho = np.asarray(logrho) logT = np.asarray(logT) T6 = 1e-6 * (np.power(10, logT)) rho = np.power(10, logrho) R = rho / (np.power(T6, 3)) logR = np.log10(R) return np.vstack((logR, logT)).T def validate(self, points, RMode=False, Describe=False): ur"""Return a boolean if the points guessed are in the table at all. :param np.array points: An array of :math:`\log(\rho)` and :math:`\log(T)` to be checked. :param bool RMode: Whether to assume [logR,logT] (else, assume [logrho,logT]) :returns: True if the points fit within the table. """ if not RMode: points = self.make_points(*points) try: self.__valid__(points) except OpacityTableError as e: if Describe: return False, e.code, e.codes[e.code] else: return False else: if Describe: return True, 0, "No Error" else: return True def bounds(self, logR=None, logT=None): """Return the bounds of the selected opacity table.""" top = np.array([np.max(self.tbl[:, i]) for i in xrange(2)]) bot = np.array([np.min(self.tbl[:, i]) for i in xrange(2)]) return np.vstack((top, bot)).T def snap(self, points): """Take a pair of points and place them back on the valid area.""" maxes = np.array([(points[:, i] <= np.max(self.tbl[:, i])).any() for i in xrange(2)]).all() mines = np.array([(points[:, i] >= np.min(self.tbl[:, i])).any() for i in xrange(2)]).all() if (mines and maxes): return points for point in points: for ind, ele in enumerate(point): vmax, vmin = np.max(self.tbl[:, ind]), np.min(self.tbl[:, ind]) if ele > vmax: point[ind] = vmax elif ele < vmin: point[ind] = vmin return points def __valid__(self, points): ur"""Check the range of this point compared to the opacity table range. :param np.array points: An array of :math:`\log(\rho)` and :math:`\log(T)` to be checked. :raises: :exc:`ValueError` when points are out of bounds. """ maxes = np.array([(points[:, i] <= np.max(self.tbl[:, i])).any() for i in xrange(2)]).all() mines = np.array([(points[:, i] >= np.min(self.tbl[:, i])).any() for i in xrange(2)]).all() if (mines and maxes): return True cols = {0: "logR", 1: "logT"} for point in points: for ind, ele in enumerate(point): vmax, vmin = np.max(self.tbl[:, ind]), np.min(self.tbl[:, ind]) if ele > vmax: raise OpacityTableError(msg="BOUNDS: %s=%g > %g" % (cols[ind], ele, vmax), code=2**(ind + 2), val=vmax) elif ele < vmin: raise OpacityTableError(msg="BOUNDS: %s=%g < %g" % (cols[ind], ele, vmin), code=2**(ind + 2) + 1, val=vmin) raise OpacityTableError(msg="BOUNDS: Error Index Unknown!!!, %r" % points, code=2**4) def lookup(self, points=None, logrho=None, logT=None): ur""" A lookup function for our interpolator. Does the pure lookup. :param points: An array of :math:`\log(\rho)` and :math:`\log(T)` :param logrho: An array (or single value) of :math:`\log(\rho)` used only when `points` is not provided. :param logT: An array (or single value) of :math:`\log(T)` used only when `points` is not provided. :returns: κ, an array of the rosseland mean opacity. """ if self._interpolator is None: raise OpacityTableError(code=2**1) if points is None: assert logrho is not None, u"Must provide a log(ρ)=?" assert logT is not None, u"Must provide a log(T)=?" logrho = np.asarray(logrho) logT = np.asarray(logT) points = self.make_points(logrho=logrho, logT=logT) else: points = self.make_points(logrho=points[:, 0], logT=points[:, 1]) if self._snap: points = self.snap(points) if self._error: self.__valid__(points) kappa = self._interpolator(points) return kappa def kappa(self, logrho=None, logT=None, rho=None, T=None): ur"""Return a rosseland mean opacity at a temperature and density. :param logrho: Logarithmic Density, base 10, :math:`\log(\rho)` :param rho: Density. Accepts `logrho` or `rho`. :param logT: Logarithmic Temperature, :math:`\log(T)` :param T: Temperature. Accepts `logT` or `T`. :returns: κ, the rosseland mean opacity. """ assert (logrho is not None) ^ ( rho is not None), u"Must provide one and only one value for ρ." assert (logT is not None) ^ ( T is not None), u"Must provide one and only one value for T" logT = logT if logT is not None else np.log10(T) logrho = logrho if logrho is not None else np.log10(rho) kappa = np.power(10, self.lookup(logT=logT, logrho=logrho)) knans = np.sum(np.isnan(kappa)) if knans > 0: self._warnings["NaNs"] += 1 if knans > 0 and self._warnings["NaNs"] < self._warnings_max: if len(kappa) == 1: self.log.warning( "Opacity Table Returned NaN: Kappa: %g logT: %g, logrho: %g" % (kappa, logT, logrho)) else: inans = np.sum(np.isnan(logT)) + np.sum(np.isnan(logrho)) inputs = logT.size + logrho.size self.log.warning( "Opacity Table Returned NaNs: Kappas: %d/%d, Inputs: %d/%d" % (knans, kappa.size, inans, inputs)) if T is not None and rho is not None: self.log.debug("T: %s Rho: %s" % (T, rho)) self.log.debug("K: %s" % kappa) elif knans > 0 and self._warnings["NaNs"] == self._warnings_max: self.log.warning( "Caught %d NaN Warnings. Future warnings will be suppressed." % self._warnings["NaNs"]) if np.isnan(kappa).any() and self._error: raise ValueError("BOUNDS: Interpolator returned NaN") return kappa
class Star(object): """A simple configured star object.""" def __init__(self, config=None, optable_args=None, dashboard_args=None): super(Star, self).__init__() self.log = logging.getLogger(__name__) self.data_log = logging.getLogger("data") self.telem_log = logging.getLogger("telemetry") self.telem_log.propagate = False self._call_count = 0 self._warning_count = { "Neg" : 0 } self.name = current_process().name self._config = DottedConfiguration(config) self._config.dn = DottedConfiguration self._integrator_name = self.config.get("System.Integrator.Method","scipy") self._max_warnings = self.config["System.Integrator"].get("Warnings",100) self._integrator = getattr(self,self._integrator_name) self._logmode = self.config.get("System.Integrator.LogScale",True) self._plotting = self.config.get("System.Integrator.LivePlot",True) np.set_printoptions(**self.config["System.Numpy.PrintOptions"]) if optable_args is None: self.log.debug("Starting Opacity Table from Scratch") self._opacity = ObjectThread(OpacityTable, ikwargs=dict(fkey=self._config["Data.Opacity.Config"],X=self.X,Y=self.Y, snap=self._config["System.Opacity.Snap"],error=self._config["System.Opacity.Error"], wmax=self._config["System.Opacity.Warnings"],efkey=self._config.get("System.Opacity.ExtendedKey",None)), locking=True,timeout=self._config["System.Opacity.Timeout"]) self.opacity.start() self.log.debug("Started Opacity Table From Scratch") else: self.log.debug("Starting Opacity Table from Arguments") self._opacity = ObjectManager(**optable_args) self.log.debug("Started Opacity Table from Arguments") if dashboard_args is not None: self._dashboard = ObjectManager(**dashboard_args) from astropysics.constants import Rs, Lsun, Ms self.Pc_Guess = float(self._config["Star.Initial.Pc"]) self.Tc_Guess = float(self._config["Star.Initial.Tc"]) self.R_Guess = float(self._config["Star.Initial.Rs"]) * Rs self.L_Guess = float(self._config["Star.Initial.Ls"]) * Lsun self.dm_Guess = float(self._config["Star.Initial.dm"]) * Ms self.fp = float(self._config["Star.Integration.fp"]) * self.M self._update_frequency = self.config["System.Dashboard.Update"] self._save_frequency = self.config["System.Outputs.Update"] def set_fitting_point(self,point): """Set a new fitting point for this routine""" self.fp = point def set_guesses(self,R,L,Pc,Tc): """Set the initial guess vector""" self.Pc_Guess = Pc self.Tc_Guess = Tc self.R_Guess = R self.L_Guess = L def get_guesses(self): """Return the guess array""" return np.array([self.R_Guess,self.L_Guess,self.Pc_Guess,self.Tc_Guess]) def integral(self,y,x,i): """docstring for integral""" x = np.asarray(x) y = np.asarray(y) self._call_count += 1 dy = self.fprime(xs=x,ys=y,mu=self.mu,optable=self.opacity,X=self.X,XCNO=self.Z,cfg=self.config["Data.Energy"])[:,0] if self._logmode: x = np.power(10,x) update = self._update_frequency != 0 and self._call_count % self._update_frequency == 0 telem = self._save_frequency != 0 and self._call_count % self._save_frequency == 0 if (y < 0).any() or update or telem: rho = density(P=y[2],T=y[3],mu=self.mu) eps = dy[1] if self._logmode: eps /= (x * np.log(10)) self.opacity.kappa(T=y[3],rho=rho) rgrad = radiative_gradient(T=y[3],P=y[2],l=y[1],m=x,rho=rho,optable=self.opacity) agrad = grad(rgrad) self.opacity.kappa(T=y[3],rho=rho) kappa = self.opacity.retrieve() if (y < 0).any(): self._warning_count["Neg"] += 1 if self._warning_count["Neg"] == self._max_warnings: self.log.warning("Future Negative Value Warnings will be suppressed. Passed maximum number of warnings: %d" % self._max_warnings) elif self._warning_count["Neg"] < self._max_warnings: self.log.warning(u"%s y<0 at: \nx=%r, \ny=%r, \ndy=%r, \nρ=%g \nε=%g \n∇=%g \nκ=%g" % (i,x,y,dy,rho,eps,agrad,kappa[0])) if update: if self._plotting: self.append_dashboard(x,y,rho,agrad,kappa,eps,line=i+self.name) self.dashboard.update("live") self.log.info(u"%s %d calls at: \nx=%r, \ny=%r, \ndy=%r, \nρ=%g \nε=%g \n∇=%g \nκ=%g" % (i,self._call_count,x,y,dy,rho,eps,agrad,kappa[0])) if telem: self.telem_log.info(u"%r %r %r %g %g %g %g" % (x,y,dy,rho,eps,agrad,kappa[0])) return dy @property def integrator(self): """The actual integrator function!""" return self._integrator @property def fprime(self): """The correct fprime function for integration""" if self._logmode: return log_derivatives else: return derivatives def show_surface_start(self): """Show a detailed view of the start of integration at the center""" self.fp = (1-1e-3) * self.M self._config["System.Outputs.Size"] = 10 return self.surface() def show_center_start(self): """Show a detailed view of the start of integration at the center""" self.fp = 1e30 self._config["System.Outputs.Size"] = 10 return self.center() def center(self,plotting=True): """Run the center integration.""" self._plotting = plotting self.log.debug("Getting Inner Boundaries") self.log.debug("Initially, star has Tc=%g, Pc=%g, M=%g, m=%g, convective=%s" % (self.Tc_Guess,self.Pc_Guess,self.M,self.dm_Guess,self.config["Star.Initial.Convective"])) center_ic = inner_boundary( Pc=self.Pc_Guess,Tc=self.Tc_Guess,M=self.M,mu=self.mu,m=self.dm_Guess, optable=self.opacity,X=self.X,XCNO=self.XCNO,cfg=self.config["Data.Energy"],convective=self.config["Star.Initial.Convective"]) if self._logmode: integrator = "LogCenter" ms = np.linspace(np.log10(self.dm_Guess),np.log10(self.fp),self._config["System.Outputs.Size"]) else: integrator = "Center" ms = np.logspace(np.log10(self.dm_Guess),np.log10(self.fp),self._config["System.Outputs.Size"]) self.log.debug("Inner Conditions (%s): x=%g, y=%r" % (integrator,self.dm_Guess,np.array(center_ic))) self.log.debug("Starting %s Integration" % integrator) return self.integrator(ms,center_ic,integrator) def surface(self,plotting=True): """Run an integration from the surface to the inner edge""" self._plotting = plotting self.log.debug("Getting Outer Boundaries") outer_ic = outer_boundary(R=self.R_Guess,L=self.L_Guess,M=self.M,mu=self.mu,optable=self.opacity) (r,l,P,T) = outer_ic if self._logmode: integrator = "LogSurface" ms = np.linspace(np.log10(self.fp),np.log10(self.M),self._config["System.Outputs.Size"])[::-1] else: integrator = "Surface" ms = np.logspace(np.log10(self.fp),np.log10(self.M),self._config["System.Outputs.Size"])[::-1] self.log.debug("Surface Conditions (%s): x=%g, y=%r" % (integrator,self.M,np.array(outer_ic))) self.log.debug("Starting %s Integration" % integrator) return self.integrator(ms,outer_ic,integrator) def pystellar(self,xs,ics,integrator): """Run an integration from the central point to the outer edge.""" ys, data = integrate(self.integral,xs,ics,args=(integrator,),**self.config["System.Integrator.PyStellar"][integrator]["Arguments"]) self.log.debug("Finished %s Integration" % integrator) if self._logmode: xs = np.power(10,xs) rho = density(P=ys[:,2],T=ys[:,3],mu=self.mu) eps = dldm(T=ys[:,3],rho=rho,X=self.X,XCNO=self.XCNO,cfg=self.config["Data.Energy"]) self.opacity.kappa(T=ys[:,3],rho=rho) rgrad = radiative_gradient(T=ys[:,3],P=ys[:,2],l=ys[:,1],m=xs,rho=rho,optable=self.opacity) agrad = grad(rgrad) self.opacity.kappa(T=ys[:,3],rho=rho) kappa = self.opacity.retrieve() all_data = np.vstack(map(np.atleast_2d,(xs,ys.T,rho,eps,rgrad,agrad,kappa))).T np.savetxt(self.config["System.Outputs.Data.Integration"] % {'integrator':integrator},all_data) if self._plotting and np.isfinite(all_data).all(): self.update_dashboard(xs,ys.T,rho,agrad,kappa,eps,line=integrator+self.name,figure='split') self.dashboard.update("integration","integrationextras") elif not np.isfinite(all_data).all(): self.log.debug("Skipping integration plots due to non-finite data.") self.log.debug("Plotted %s Integration" % integrator) return ys, xs, data def scipy(self,xs,ics,integrator): """Run an integration from the central point to the outer edge.""" self.log.debug("Calling %s Scipy Integration" % integrator) import scipy.integrate ys, data = scipy.integrate.odeint(self.integral,ics,xs,args=(integrator,), full_output=True,**self.config["System.Integrator.Scipy"][integrator]["Arguments"]) self.log.debug("Finished %s Integration: %d timesteps, %d function calls." % (integrator,data["nst"][-1],data["nfe"][-1])) derivs = self.fprime(xs=xs,ys=ys,mu=self.mu,optable=self.opacity,X=self.X,XCNO=self.Z,cfg=self.config["Data.Energy"]) if self._logmode: xs = np.power(10,xs) rho = density(P=ys[:,2],T=ys[:,3],mu=self.mu) eps = dldm(T=ys[:,3],rho=rho,X=self.X,XCNO=self.XCNO,cfg=self.config["Data.Energy"]) self.opacity.kappa(T=ys[:,3],rho=rho) rgrad = radiative_gradient(T=ys[:,3],P=ys[:,2],l=ys[:,1],m=xs,rho=rho,optable=self.opacity) agrad = grad(rgrad) self.opacity.kappa(T=ys[:,3],rho=rho) kappa = self.opacity.retrieve() all_data = np.vstack(map(np.atleast_2d,(xs,ys.T,rho,eps,rgrad,agrad,kappa))).T np.savetxt(self.config["System.Outputs.Data.Integration"] % {'integrator':integrator},all_data) if self._plotting: self.update_dashboard(xs,ys.T,rho,agrad,kappa,eps,line=integrator+self.name,figure='split') self.dashboard.update("integration","integrationextras") elif np.isnan(all_data).any(): self.log.debug("Skipping integration plots due to non-finite data.") self.log.debug("Plotted %s Integration" % integrator) return ys, xs, data def update_dashboard(self,x,y,rho=None,gradient=None,kappa=None,epsilon=None,line=None,figure='live'): """A wrapper to perform dashboard updates.""" line = self.name if line is None else line if figure == 'split': mfig = 'integration' ofig = 'integrationextras' else: mfig,ofig = figure,figure for yi,name in zip(y,["radius","luminosity","pressure","temperature"]): self.dashboard.update_data(x,yi,figure=mfig,axes=name,line=line,lw=2.0) if rho is not None: self.dashboard.update_data(x,rho,figure=ofig,axes="density",line=line,lw=2.0) if gradient is not None: self.dashboard.update_data(x,gradient,figure=ofig,axes="gradient",line=line,lw=2.0) if kappa is not None: self.dashboard.update_data(x,kappa,figure=ofig,axes="opacity",line=line,lw=2.0) if epsilon is not None: self.dashboard.update_data(x,epsilon,figure=ofig,axes="epsilon",line=line,lw=2.0) def append_dashboard(self,x,y,rho=None,gradient=None,kappa=None,epsilon=None,line=None,figure='live'): """Append data to the dashboard""" line = self.name if line is None else line if figure == 'split': mfig = 'integration' ofig = 'integrationextras' else: mfig,ofig = figure,figure for yi,name in zip(y,["radius","luminosity","pressure","temperature"]): self.dashboard.append_data(x,yi,figure=mfig,axes=name,line=line) if rho is not None: self.dashboard.append_data(x,rho,figure=ofig,axes="density",line=line) if gradient is not None: self.dashboard.append_data(x,gradient,figure=ofig,axes="gradient",line=line) if kappa is not None: self.dashboard.append_data(x,kappa,figure=ofig,axes="opacity",line=line) if epsilon is not None: self.dashboard.append_data(x,epsilon,figure=ofig,axes="epsilon",line=line) @property def dashboard(self): """Dashboard Thread""" return self._dashboard @property def opacity(self): """Opacity table""" return self._opacity # Compositon Accessor Functions @property def X(self): """Fraction of Hydrogen""" return self._config["Star.Composition.X"] @property def Y(self): """Fraction of Helium""" return self._config["Star.Composition.Y"] @property def dXc(self): """Excess fraction of Carbon""" return self._config["Star.Composition.dXc"] @property def dXo(self): """Excess fraction of Oxygen""" return self._config["Star.Composition.dXo"] @property def Z(self): """Fraction of Heavy Elements""" return 1.0 - (self.X + self.Y + self.dXc + self.dXo) @property def XCNO(self): """Fraction of Carbon, Oxygen and Nitrogen""" return self.config["Star.Composition.ZdXCNO"] * self.Z + self.dXc + self.dXo @property def mu(self): """Mean molecular weight of this star.""" return mmw(X=self.X,Y=self.Y) @property def M(self): """Total Mass of the Star""" from astropysics.constants import Ms return self._config["Star.Properties.M"] * Ms # Configuration Functions @property def config(self): """The configuration for this star.""" return self._config def stop(self): """End this star!""" if isinstance(self.opacity,ObjectThread): self.opacity.stop() def kill(self): """Kill this star!""" pass