"""Description of thermal radiation.

$Id: thermal.py,v 1.4 2007/03/24 22:42:21 eddy Exp $
"""
from physics import Thermal, Quantum, Vacuum
from study.value.object import Object

import math # will del later

# Experiment - due to move once a better home for it materialises
class Radiator (Object):
    """Descriptor for a black-body radiator. """
    def __init__(self, temperature, *args, **what):
        apply(Object.__init__, (self,) + args, what)
        self.__temperature = temperature

    def _lazy_get_total_(self, ignored, S=Thermal.Stefan):
        """Computes the power output per unit area of exposed surface.

        To compute total power output, allowing (if the body isn't convex) for
        re-absorption of some of what it emits, I suspect you should multiply
        radiance by the surface area of the body's convex hull, rather than its
        actual surface area.  The product is then the rate of loss of energy,
        due to thermal radiation, of the body. """

        # Stefan-Boltzmann law:
        return self.__temperature**4 * S

    def _lazy_get__hoverkT_(self, ignored, hoverk=Quantum.h / Thermal.k):
        """Constant needed by spectral: h / (k.T)."""

        return hoverk / self.__temperature

    def spectral(self, nu=None, frequency=None, wavelength=None,
                 exp=math.exp, h=Quantum.h, c=Vacuum.c, pi=math.pi):
        """Returns spectral radiance.

        Takes any one of three arguments, in the order nu, frequency,
        wavelength, the former being the `angular' frequency of the radiation,
        frequency/(2*pi).  If more than one is given, the first in the above
        ordering (which is their positional order) is used, any others are
        ignored.  Returns the radiance (power output per unit area) of the body,
        per unit variation in the given argument, produced in a narrow portion
        of the spectrum about the given position in the spectrum.

        Since this is `per unit', the answer depends on which argument you
        supplied not only for how the argument is read but also in how the power
        density varies with that argument. """

        if nu: return self.spectral(frequency = nu * 2 * pi) / 2 / pi
        elif frequency: f = frequency
        elif wavelength: f = c / wavelength
        else: raise ValueError, 'Either zero input or none given: unphysical.'

        base = 2 * h / (exp(self._hoverkT * f) - 1)
        if frequency: return base * f**3 / c**2
        else: return base * c**2 / wavelength**5

del math, Object, Quantum
from study.value.units import Kelvin

def radiator(temperature, *args, **what):
    """Wrap Radiator with provision for -ve temperatures (in Centigrade).

    This isn't the right way to wrap it, but if any wrapping's to be done, it
    should be done here, not by bodging Radiator's __init__ method ! """

    try: T = temperature + 273.15
    except TypeError: # temperature has units
        if temperature / Kelvin > 0: T = temperature
        else:
            T = temperature + 273.15 * Kelvin
            if T / Kelvin < 0:
                raise ValueError, 'Negative temperature, even after coercion by 273.15 K'
    else:
        if temperature > 0: T = temperature * Kelvin
        elif T < 0:
            raise ValueError, 'Negative temperature, even after coercion by 273.15 K'
        else: T = T * Kelvin

    return apply(Radiator, (T,) + args, what)
