Exemplo n.º 1
0
    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"]
Exemplo n.º 2
0
 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"]
Exemplo n.º 3
0
    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")
Exemplo n.º 4
0
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
Exemplo n.º 5
0
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
Exemplo n.º 6
0
    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")
Exemplo n.º 7
0
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
Exemplo n.º 8
0
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