class Nucleon(Baryon): __upinit = Baryon.__init__ def __init__(self, u, d, name, mass, doc, **what): what['constituents'] = {Quark.item.up: u, Quark.item.down: d} self.__upinit(name, mass, doc, **what) mass = Quantity(1 / mol / mol.Avogadro, gram, doc="""Atomic Mass Unit, AMU. This is the nominal mass of a nucleon. In reality, both proton and neutron are a fraction of a percent heavier. The Mole is so defined that a Mole of carbon-12 weighs exactly 12 grams. The carbon-12 nucleus comprises six protons and six neutrons. Thus dividing one gram by the number of items in a Mole thereof (Avogadro's constant) yields one twelfth of the mass of a carbon-12 atom, nominally (half the mass of an electron plus) the average of the masses of neutron and proton, albeit the binding energy of the nucleus reduces this value (by more than the electron mass). """) amuk = Quantity(mass / Thermal.k, doc="""AMU scaled down by Boltzmann's constant. If we look at the ideal gas law, P*V = N*k*T, for N items of a gas with mass m per item, we get density = N*m/V = m*P/k/T. Since m is the relative molecular (or atomic) mass, M, times the atomic mass unit, we can write it as M * AMU and obtain density = M*amuk*P/T with M a pure number (and, typically, very close to an integer). Thus, at standard temperature (zero Celsius) and pressure (one Atmosphere), density is just M times 44.618 grams per cubic metre.\n""")
def KLfamily(nm, lnom, lsym, lm, lme, lrate, mnom, mm, mme, pnom, pm, pme, mev=mega*eV.mass, Hz=Hertz): """Deciphering Kaye&Laby p449. Positional arguments are as follows: neutrino mass -- upper bound, measured in MeV lepton name -- string lepton symbol -- string lepton mass -- in MeV lepton mass error -- half-width of error bar on previous lepton decay rate -- fraction of the given lepton species which decay per second -ve quark name -- name of the quark with -ve charge e/3 -ve quark mass -- mass estimate, in GeV, for the -ve quark -ve quark mass error -- half-width of error-bar on previous +ve quark name -- name of the quark with +ve charge 2*e/3 +ve quark mass -- mass estimate, in GeV, for the +ve quark +ve quark mass error -- half-width of error bar on previous\n""" return Family(Neutrino(lnom, mass=Quantity.below(nm, mev)), Lepton(lnom, mass=Quantity.within(lm, lme, mev), symbol=lsym, decay=lrate * Hz), dQuark(mnom, mass=Quantity.within(mm, mme, kilo*mev)), uQuark(pnom, mass=Quantity.within(pm, pme, kilo*mev)))
def waterviscosity(T, A=2.414e-5 * Pascal * second, K=Kelvin, ten=Quantity(10)): """Variation of water's dynamic viscosity with temperature. Takes one argument, an absolute temperature (e.g. a return from study.value.archaea's Centigrade or Fahrenheit). Result is probably only valid if this is a temperature at which water is a liquid !\n""" return A * ten**(247.8 / (T / K - 140)) # adapted from Wikipedia
def KLfamily(nm, lnom, lsym, lm, lme, lrate, mnom, mm, mme, pnom, pm, pme, mev=mega * eV.mass, Hz=Hertz): """Deciphering Kaye&Laby p449. Positional arguments are as follows: neutrino mass -- upper bound, measured in MeV lepton name -- string lepton symbol -- string lepton mass -- in MeV lepton mass error -- half-width of error bar on previous lepton decay rate -- fraction of the given lepton species which decay per second -ve quark name -- name of the quark with -ve charge e/3 -ve quark mass -- mass estimate, in GeV, for the -ve quark -ve quark mass error -- half-width of error-bar on previous +ve quark name -- name of the quark with +ve charge 2*e/3 +ve quark mass -- mass estimate, in GeV, for the +ve quark +ve quark mass error -- half-width of error bar on previous\n""" return Family( Neutrino(lnom, mass=Quantity.below(nm, mev)), Lepton(lnom, mass=Quantity.within(lm, lme, mev), symbol=lsym, decay=lrate * Hz), dQuark(mnom, mass=Quantity.within(mm, mme, kilo * mev)), uQuark(pnom, mass=Quantity.within(pm, pme, kilo * mev)))
self.__p *= self.__x # a power of x k = 2 # sentinel: if self.__k gets to 2, we've converged ! # Newton-Raphson to find a zero of f: f = self.__p - (self.__p - 1) / (self.__x - 1) g = f.derivative while k != self.__k: k = self.__k self.__k -= f(k) * 1. / g(k) return k from study.value.quantity import Quantity golden = Quantity((1 + 5.**.5) / 2, doc="""The golden ratio. This is the positive solution to the quadratic equation x*x = x+1; divide -1 by it to get the negative solution. One can re-write the equation as (2*x-1)**2 = 4*x*x -4*x +1 = 4*(x*x-x) + 1 = 5, whence the solutions are (1 +/- 5**.5)/2. A rectangle whose long side is this times its short side has the property that, if your cut from it a square on a short side, the remainder rectangle, though smaller, has the same shape, i.e. ratio of long side (which was the original's short side) to short side (the difference between the original's sides). """) assert golden**2 == golden + 1 del Quantity
debt -- description of debts and mortgages job -- description of a job See study.LICENSE for copyright and license information. """ from study.value.quantity import Quantity quid = Quantity.base_unit( "£", "Pound Sterling", """The base unit of British currency. Used to be 20 shillings (21 shillings made a Guinea); each shilling was 12 pence, each penny was four farthings. A florin was two shillings; a crown was five. Apparently a pound was also called a sovereign. HTML supports character entity &sterling; for the Pound Sterling. To properly handle money within a system of units, I need support for variation in time and space (conversion factors between different currencies vary with time; and you'll get different exchange rates from different trading partners). Then again, conversion factors between systems of units also show similar variation - contrast the different nations' archaic units of length, and notice how units of volume got re-defined by assorted legislative acts over the years. """, ) # It's clearly inadequate to treat money units as approximate multiples of one # another: each is an exact unit in its place, it's only the conversion between # them that's approximate. krone = quid / Quantity.within(10, 2) del Quantity
def between(lo, hi, units, *args, **what): return Quantity.flat(lo, hi, None, units, *args, **what)
violet=photon(380, 420, 'violet')) # purple del photon visible.also(spectrum=(visible.red, visible.orange, visible.yellow, visible.green, visible.cyan, visible.blue, visible.indigo, visible.violet), rainbow=Quantity.within( 138.7, .7, arc.degree, """The angle through which a rainbow turns visible light. This varies with the colour of the light, red being turned least and blue most. Since the angle exceeds a quarter turn, the arc of a (pure water) rainbow appears centred on the direction opposite to the light source (generally the sun), at an angle ranging from 40.6 (violet) to 42 (red) degrees from that direction. The spray-bow resulting from sea-spray is tighter - sea water droplets turn light through a larger angle than pure water droplets. """, secondary=between( 127, 130, arc.degree, """The angle through which a secondary rainbow turns visible light. Compare visible.rainbow, the primary angle: for the secondary rainbow, red (130 degrees) is turned more than violet (127 degrees); since this range is less than that of the primary rainbow (but still more than a quarter turn), the secondary bow appears outside the primary. """))) radio = Photon(name="radio", frequency = Quantity.below(3, giga * Hertz)) microwave = Photon(name="microwave", wavelength = between(1, 100, milli * metre), frequency = between(1, 100, 3 * giga * Hertz)) infrared = Photon(name="infra-red",
composed of the first column: indeed, most matter is hydrogen, comprising a proton and an electron; the proton is made of two up quarks and one down. See also: elements.py See study.LICENSE for copyright and license information. """ from study.snake.lazy import Lazy from study.value.quantity import Quantity, Object from study.value.units import pi, arc, bykind, \ harpo, femto, pico, nano, micro, milli, kilo, mega, giga, tera, peta, exa, \ gram, metre, mol, second, year, Volt, Angstrom, Hertz, Joule, Tesla from physics import Quantum, Vacuum, Thermal from decay import Decay eV = Quantity( Quantum.Millikan, Volt, doc='The electron-Volt: a standard unit of energy in particle physics.') def between(lo, hi, units, *args, **what): return Quantity.flat(lo, hi, None, units, *args, **what) class Particle(Object): # needs merged in with units-related toys etc. __obinit = Object.__init__ def __init__(self, name, *args, **what): """Describe a particle's rest-state. Required argument, name, is the name of the particle. Other arguments
class Bodalizer (Lazy): """The auto-Bodalizer. An instance of this class tries to automatically select three constants zero, unit and base for which a family of orbits' radii have values close to zero + unit * base**i for various i. It allows i to change in steps other than 1, but should (though it presently does not) try to ensure that such exceptions are rare. Constructor takes a sequence of space.body.Object instances, selects a sub-set of them (the instances of the earliest of Planet, Planetoid, Body or Object to have several instances in the sequence) and records the orbital radii of this sub-set. When it comes to need values for zero, unit and base it does the necessary computation to match this sequence of radii to the above pattern. Public attributes: * zero, unit, base -- the defining attributes mentioned above. Public methods: * use(zero, unit, base) -- over-ride the given attributes * index(radius) -- i = log((radius - zero) / unit) / log(base) """ def __init__(self, seq): # First identfy your demos ... plenty, k = len(seq), 3 while k * k < plenty: k = 1 + k plenty = k # max(3, sqrt(len(seq)) rounded up) self.__seq = self.__enough(seq, plenty) def __len__(self): return len(self.__seq) def __getitem__(self, key): return self.zero + self.unit * self.base**key def __repr__(self): return 'lambda i: %s + %s * %s ** i' % (self.zero, self.unit, self.base) def index(self, radius): r = (radius - self.zero) / self.unit if r.low < .0001: return 0 # avoid domain errors and hugely -ve answers ... # (while incidentally being polite to Mercury) return r.log / self.__base # Auto-detection of zero, unit and base: from study.value.quantity import Quantity, tera # NB: Any scalar Quantity has attributes exp and log (among others) we can exploit ;^) def median(seq, span=1, Q=Quantity.flat): # tool func seq.sort() i, b = divmod(len(seq), 2) if b: best = seq[i].best else: best = .5 * (seq[i-1].best + seq[i].best) i, b = divmod(len(seq) - span, 2) if b: lo, hi = seq[i].best, seq[-1-i].best else: lo, hi = seq[i].low, seq[-1-i].high return Q(lo, hi, best) def __enough(self, seq, plenty, mid=median, cache=[]): try: bodytypes = cache[0] except IndexError: from body import Planet, Planetoid, Body, Object bodytypes = ( Planet, Planetoid, Body, Object ) cache.append(bodytypes) for k in bodytypes: row = [x for x in seq if isinstance(x, k)] if len(row) < plenty: continue # try to eliminate any initial or final arithmetic sequences row = [ x.orbit.radius for x in row ] gap = [ y - x for x, y in zip(row[:-1], row[1:]) ] rat = [ (y / x).log for x, y in zip(gap[:-1], gap[1:]) ] cut = mid(rat) / 5 # Could perhaps do better by considering every ratio of differences # among entries; these are all base**j * (base**i - 1)/(base**k - 1) # if we do things in the right order; and every difference between # entries with adjacent indices yields, where it's used as # denominator, a simple k = 1 so these ratios are base**j * # (base**(i-1) + ... + base**2 + base + 1) score = rat[0] while score < cut: row, rat = row[1:], rat[1:] score = score + rat[0] score = rat[-1] while score < cut: row, rat = row[:-1], rat[:-1] score = score + rat[-1] if len(row) >= plenty: return row return [ x.orbit.radius for x in seq ] from study.value.SI import metre Unit = Quantity(tera * metre / 7) # Arbitrary Unit of length (approximates the AU) del tera, metre def use(self, zero=None, unit=None, base=None, Q=Quantity): if zero is None: try: del self.zero except AttributeError: pass else: self.zero = zero if unit is None: try: del self.unit except AttributeError: pass else: self.unit = unit if base is None: try: del self.base except AttributeError: pass try: del self.__base except AttributeError: pass else: self.unit # Force lazy evaluation so we can blot out its __base ... self.base = Q(base) self.__base = self.base.log def _lazy_get_unit_(self, ig, mid=median, AU=Unit): row = [x - self.zero for x in self.__seq if x > self.zero] # That forced computation of zero, making base available ... offs = [ ((x / AU).log / self.__base) % 1 for x in row ] frac = offs[:] frac.sort() gaps = [ frac[0] + 1 - frac[-1] ] + [y - x for x, y in zip(frac[:-1], frac[1:])] ind = gaps.index(max(gaps)) # frac[ind-1] and frac[ind] differ by max(gaps) dim = (frac[ind-1] + frac[ind]) * .5 # the anti-middle # Now index row as nicely as we can hope for: i, ind = len(offs), [] while i > 0: i = i - 1 r = offs[i] if r > dim: r = r - 1 n = (row[i] / AU).log / self.__base - r ind.insert(0, int(n.best)) # but that leaves an arbitrary offset in ind. offs = [n - i - 1 for i, n in enumerate(ind)] # print 'offsets:', offs offs.sort() n = offs[len(offs) / 3] return mid([ r / self.base**(i - n) for r, i in zip(row, ind) ]) del median def _lazy_get_base_(self, ignored): self.zero # force evaluation so we compute __base return self.__base.exp import math def __spread(self, z, e=math.exp, AU=Unit): # icky complex-to-complex used in hunting good values for zero and base zero, b = e(z.imag) * AU, e(z.real) # it may be prudent to frob oz ... oz = (zero / self.__seq[0] / 10).evaluate(lambda x: x + 1/x) row = [x for x in self.__seq if x > zero] if b > 0 and len(row) > 1: row = [ ((x / AU).log / b) % 1 for x in row ] row.sort() gap = max([ row[0] + 1 - row[-1] ] + [ y - x for x, y in zip(row[:-1], row[1:]) ]) span, oz = ((1 - gap) / len(row)).best, (oz / len(row)).best else: count = len(self.__seq) + 1 - len(row) try: span, oz = (e(b) + e(-b)) * count, (oz * count).best except OverflowError: raise OverflowError(z, b, oz) return float(span) + 1j * float(oz) from study.maths.search import Search def _lazy_get_zero_(self, ignored, S=Search, e=math.exp, AU=Unit, Q=Quantity): # Mercury's orbit as zero: z = self.__seq[0] # Saturn vs. Venus gives median log(ratio)/(difference in index) try: b = ((self.__seq[5] - z) / (self.__seq[1] - z)).log * .2 except IndexError: b = Q(2).log # fall back on 2 guess = float(b.log.best) + 1j * float((z / AU).log.best) hunt = S(self.__spread, guess, stride=.1) hunt.rummage() z = hunt.best[0] self.__base = Q(e(z.real)) return e(z.imag) * AU del Quantity, Search, Unit, math
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 !\n""" try: if temperature > 0: T = temperature * Kelvin else: T = Centigrade(temperature) except TypeError: # temperature has units, so > 0 check bombed. if temperature / Kelvin > 0: T = temperature # TypeError if wrong units else: T = temperature + Centigrade(0) if T / Kelvin < 0: raise ValueError, 'Negative temperature, even after Centigrade coercion' return Radiator(T, *args, **what) from study.value.quantity import Quantity Human = Radiator(Quantity.fromDecimal(309.5, 1, None, Kelvin), oral=Centigrade(Quantity.gaussian(36.8, .5)), axillary=Centigrade(Quantity.flat(36, 36.9, 36.6)), __doc__="""Human body as a radiator. The human body maintains a roughly constant temperature, so naturally radiates as a body of that temperature, in so far as it's exposed. Skin temperature is doubtless less than one would measure in an arm-pit (an 'axillary' measurement, commonly used in Russia and Poland), which is about .2 K lower than oral measurement (common in the anglophone world); which, in turn, is about .5 K below anal measurements; while core temperatures are presumed to be higher yet than this. """) del Quantity, Centigrade
class Particle(Object): # needs merged in with units-related toys etc. __obinit = Object.__init__ def __init__(self, name, *args, **what): """Describe a particle's rest-state. Required argument, name, is the name of the particle. Other arguments are handled as for Object (q.v.) except for these keywords: constituents -- should be a mapping { particle: number } with self comprising the given number of instances of each particle. Shalln't appear as an attribute: see the eponymous method. The following keyword arguments, if supplied, should match the interpretation of them used in this class: decay -- probability per unit time of decaying lifetime -- expected time to decay, inverse of decay halflife -- half-life of particle decays -- a decay.Decays (q.v.) object. You can use an Object(particle, kinetic=energy) to specify a particle of the given type with some specified kinetic energy.\n""" try: self.__bits = what['constituents'] except KeyError: pass # self will be deemed primitive else: del what['constituents'] self.__obinit(*args, **what) self._store_as_(name, self.__class__) self.__name = name _unborrowable_attributes_ = Object._unborrowable_attributes_ + ( 'name', 'symbol', 'charge', 'anti') def constituents(self, *primitives): """Returns self's composition. Takes any number of classes (irrelevant unless derived from Particle) and particles (irrelevant unless (possibly indirect) instances of Particle) to be deemed primitive and returns a dictionary mapping primitive particles to their multiplicities within self. Regardless of any classes supplied as arguments, any particle whose composition wasn't specified when constructing it is deemed primitive. To get the composition specified when self was constructed, pass Particle as the sole argument; pass no arguments to get the particle reduced to its most primitive constituents; pass Nucleon to get a nucleus reduced to its nucleons; etc.\n""" try: bits = self.__bits except AttributeError: return {self: 1} bits, ans = bits.copy(), {} def carve(obj, m=[x for x in primitives if issubclass(x, Particle)], p=[x for x in primitives if isinstance(x, Particle)]): """Returns None if obj is primitive, else its constituents. """ try: bok = obj.__bits except AttributeError: return None if obj in p: return None for k in m: if isinstance(obj, k): return None return bok while bits: for k, v in bits.items(): del bits[k] b = carve(k) if b: for q, r in b.items(): assert q is not k bits[q] = bits.get(q, 0) + v * r else: ans[k] = ans.get(k, 0) + v return ans def __bindener(self, bok): sum = -self.energy for k, v in bok.items(): sum = sum + k.energy * abs(v) return sum def bindingenergy(self, *primitives): return self.__bindener(self.constituents(*primitives)) def bindingfraction(self, *primitives): return self.bindingenergy(*primitives) / self.energy def bindingenergyper(self, *primitives): bok = self.constituents(*primitives) return self.__bindener(bok) / sum(abs(v) for v in bok.values()) class __ItemCarrier(Lazy): __upinit = Lazy.__init__ def __init__(self, *args, **what): ali = what.get('lazy_aliases', {}) # Only relevant to Quark and its bases: ali.update({'top': 'truth', 'bottom': 'beauty'}) what['lazy_aliases'] = ali self.__upinit(*args, **what) # Only relevant to Lepton and its bases: def _lazy_get_positron_(self, ignored): return self.electron.anti class _lazy_get_anti_(Lazy): def __init__(self, source, ignored): self.__source = source def _lazy_lookup_(self, key): return getattr(self.__source, key).anti def _store_as_(self, name, cls, root=None, ItemCarrier=__ItemCarrier): """Each sub-class of Particle carries a namespace full of its instances. That includes indirect instances but only applies to strict sub-classes, not to Particle itself. Since Neutrino and Photon use anomalous naming, I let sub-classes over-ride _store_as_, but this base-class implementation should be good enough for most classes - it chases back up the __bases__ graph towards Particle doing the work. The namespace carrying the instances of the class is the .item attribute of the class, which is created the first time a particle of the class is stored. The .item of a class should not be set otherwise: this method provides a special class for .item objects, which handles particle aliasing and other issues. The .item object of a class also has a .anti sub-object for ease of reading; .item.anti.electron is effectively a synonym for .item.electron.anti, and likewise for other particles. """ if root is None: root = Particle # can't set as default; not defined yet todo, done = [cls], [Particle] while todo: k, todo = todo[0], todo[1:] try: i = k.item except AttributeError: i = k.item = ItemCarrier() done.append(k) try: getattr(i, name) except AttributeError: setattr(i, name, self) for b in k.__bases__: if b not in done and issubclass(b, root): todo.append(b) del __ItemCarrier def _lazy_get_name_(self, ignored): return self.__name __oblook = Object._lazy_lookup_ def _lazy_lookup_(self, key): ans = self.__oblook(key) try: ans.document('The %s of the %s %s.' % (key, self.name, self.__class__.__name__)) except (AttributeError, TypeError): pass return ans def _lazy_get_magneton_(self, ignored): return self.charge * self.spin / self.mass log2 = Quantity(2).log def _lazy_get_decay_(self, ignored, zero=0 / second, ln2=log2): """Fractional decay rate. This is defined by: the probability density for decay of the particle at time t is r*exp(-t*r) with r as the .decay attribute. Unless otherwise specified, this is presumed to be zero; however, it may be specified when you initialise - either directly, e.g. Fermion(decay=32/second), or indirectly via attribute halflife or (better) decays; see constructor documentation. The defining formula implies that the probability of decay before some specified time T is 1-exp(-T*r), making the half-life log(2)/r, and the mean time until decay is 1/r.\n""" try: halflife = self.__dict__['halflife'] except AttributeError: pass else: return ln2 / halflife try: lifetime = self.__dict__['lifetime'] except AttributeError: pass else: return 1. / halflife try: decays = self.decays except AttributeError: pass else: return decays.rate return zero def _lazy_get_halflife_(self, ignored, ln2=log2): """Time taken for the probability of having decayed to reach half""" return ln2 / self.decay del log2 def _lazy_get_lifetime_(self, ignored): """Expected time to decay, for a particle of the given type""" return 1 / self.decay def _lazy_get_anti_(self, ignored): """Returns self's anti-particle.""" # the anti-electron is anomalously named :^o try: nom = { 'electron': 'positron' # any other anomalies ? }[self.name] except KeyError: nom = 'anti-%s' % self.name try: bits = {} for k, v in self.__bits.items(): bits[k] = -v except AttributeError: bits = {self: -1} ans = self.__class__(nom, self, _charge=-self._charge, constituents=bits) ans.anti = self # NB cyclic reference; ans is about to become self.anti return ans def _lazy_get__charge_(self, ignored): """Charge in units of on third the positron's charge. This is an exact integer value, far more suitable for working with than the actual charge, whose error bar grows with each arithmetic operation.\n""" try: bits = self.__bits except AttributeError: return 0 q = 0 for k, v in bits.items(): q = q + v * k._charge return q def _lazy_get_charge_(self, ignored, unit=Quantum.Millikan / 3): return self._charge * unit def _lazy_get_period_(self, ignored, k=Quantum.h / Vacuum.c**2): """de Broglie wave period along world-line: h/c/c/mass""" return k / self.restmass def _lazy_get_energy_(self, ignored, h=Quantum.h, P=Quantum.Planck): try: m = self.__dict__['mass'] except KeyError: pass else: return m.energy try: t = self.__dict__['period'] except KeyError: pass else: return h / t try: f = self.__dict__['frequency'] except KeyError: pass else: return h * f try: f = self.__dict__['nu'] except KeyError: pass else: return Planck * f raise AttributeError('energy', 'mass', 'period', 'frequency', 'nu') def _lazy_get_mass_(self, ignored): return self.energy.mass def _lazy_get_qperm_(self, ignored): """Charge-to-mass ratio""" return self.charge / self.mass def _lazy_get_frequency_(self, ignored, h=Quantum.h): return self.energy / h def _lazy_get_nu_(self, ignored, h=Quantum.Planck): try: return self.frequency / turn except AttributeError: pass return self.energy / h def _lazy_get_momentum_(self, ignored, hbar=Quantum.hbar, h=Quantum.h): try: k = self.__dict__['wavevector'] except KeyError: pass else: return hbar * k try: d = self.__dict__['wavelength'] except KeyError: pass else: return h / d raise AttributeError('momentum', 'wavevector', 'wavelength') def _lazy_get_wavevector_(self, ignored, hbar=Quantum.hbar): return self.momentum / hbar def _lazy_get_wavelength_(self, ignored, h=Quantum.h): return h / self.momentum def resolve(self, aperture): """Resolving power of an aperture. Returns the angle subtended, at an aperture of given diameter, by the gap between a pair of objects that can just be resolved by an aparatus observing these objects through that aperture using particles whose wavelength is equal to that of self.\n""" return (self.wavelength / aperture).arcSin def _lazy_get_restmass_(self, ignored, csqr=Vacuum.c**2): return (self.mass**2 - abs(self.momentum)**2 / csqr)**.5 def __str__(self): return self.__name def __repr__(self): return '%s.%s' % (self._namespace, self.__name) def _lazy_get__namespace_(self, ignored): """Namespace in which to look for self `normally'. This should usually be self's class; however, where a class has sub-classes to make distinctions (e.g. that between bosonic and fermionic nuclei, below) one orthodoxly ignores, self may prefer to be sought in the base-class with the nice familiar name rather than in the pedantically more apt derived class.\n""" return '%s.item' % self.__class__.__name__ def __hash__(self): return hash(self.__name)
Fragments: debt -- description of debts and mortgages job -- description of a job See study.LICENSE for copyright and license information. """ from study.value.quantity import Quantity quid = Quantity.base_unit( '£', 'Pound Sterling', """The base unit of British currency. Used to be 20 shillings (21 shillings made a Guinea); each shilling was 12 pence, each penny was four farthings. A florin was two shillings; a crown was five. Apparently a pound was also called a sovereign. HTML supports character entity &sterling; for the Pound Sterling. To properly handle money within a system of units, I need support for variation in time and space (conversion factors between different currencies vary with time; and you'll get different exchange rates from different trading partners). Then again, conversion factors between systems of units also show similar variation - contrast the different nations' archaic units of length, and notice how units of volume got re-defined by assorted legislative acts over the years. """) # It's clearly inadequate to treat money units as approximate multiples of one # another: each is an exact unit in its place, it's only the conversion between # them that's approximate. krone = quid / Quantity.within(10, 2) del Quantity
def waterviscosity(T, A=2.414e-5 * Pascal * second, K=Kelvin, ten=Quantity(10)): """Variation of water's dynamic viscosity with temperature. Takes one argument, an absolute temperature (e.g. a return from study.value.archaea's Centigrade or Fahrenheit). Result is probably only valid if this is a temperature at which water is a liquid !\n""" return A * ten**(247.8 / (T / K - 140)) # adapted from Wikipedia water = Substance( density=Quantity.fromDecimal( 1 - 27e-6, 6, None, kilogramme / litre, """Density of water. at 277.13K, when density is maximal. The definition of the UK gallon used to make the density of water 10 pound / gallon at some specific temperature; but now both pound and gallon are defined in terms of SI.\n"""), viscosity=waterviscosity, heat=Heats(capacity=Quantity( 1, calorie / gram / Kelvin, """The specific heat capacity of water. The definition of the (short) calorie is as the energy it takes to heat one gram of water by one degree Celsius. Naturally, this varies with temperature; see calorie's documentation for consequences. """)), temperature=Temperatures( triple=Quantity( 273.16, Kelvin, "Triple point of water (by definition of the Kelvin)."), melt=Quantity(273.150, Kelvin,
class BlackHole(Object): # should be based on chemy.thermal.Radiator """A simple Schwarzschild black hole. Unlike other species of particle, black holes can have pretty much any mass. Their mass, radius, apparent temperature and surface gravity are all simply related to one another. Any one of these four quantities suffices for our initializer. See the documentation of Thermal.Hawking and Cosmos.Schwarzschild for details; and http://casa.colorado.edu/~ajsh/hawk.html\n""" __upinit = Object.__init__ def __init__(self, *args, **what): """Set up a description of a black hole. Any positional arguments that are instances of Quantity shall be used as keyword arguments using the kind of quantity as name, treating a length as radius. Pass at least one of mass, radius, (surface) gravity and temperature, either as keyword or positional.\n""" row = [] for arg in args: if not isinstance(arg, Quantity): row.append(arg) else: bykind(arg, what, {'length': 'radius'}) self.__upinit(*row, **what) def __repr__(self): return 'BlackHole(mass=%s)' % ` self.mass ` def _lazy_get_mass_(self, ig, tm=Thermal.Hawking): return tm / self.temperature def _lazy_get_radius_(self, ig, mr=Cosmos.Schwarzschild): return mr * self.mass def _lazy_get_gravity_(self, ig, rg=Vacuum.c**2 * .5): return rg / self.radius def _lazy_get_temperature_(self, ig, gt=Quantum.hbar * .5 / pi / Vacuum.c / Thermal.k): return gt * self.gravity def _lazy_get_tidal_(self, ig): return 2 * self.gravity / self.radius def _lazy_get_diameter_(self, ignored): return 2 * self.radius def _lazy_get_circumference_(self, ignored, tp=2 * pi): return tp * self.radius def _lazy_get_area_(self, ignored, fp=4 * pi): return fp * self.radius**2 def _lazy_get_volume_(self, ignored, ftp=pi / .75): return ftp * self.radius**3 def _lazy_get_luminosity_(self, ig, q=Quantum.h / 30, k=Cosmos.kappa): """Rate of energy loss due to Hawking radiation. A black hole emits a photon of wavelength comparable to its size about once per the time light takes to travel that far. So big black holes are very stable and little ones blow up really fast. The power output of the thermal radiation - the Hawking luminosity - is given by the usual Stefan-Boltzmann law, L = A sigma T**4 where A is the surface area, 4 pi r**2, sigma is the Stefan-Boltzmann constant and T is the apparent temperature of the black hole. These yield L = 4 pi sigma (hbar c / 4 / pi / k)**4 / r**2 = 4*(pi**3/60/c**2/hbar**3) * (hbar*c/(4*pi))**4 * (c**2/(2*G*m))**2 = hbar * c**6 / 15 / pi / 1024 / G**2 / m**2 = h / 480 / kappa**2 / m**2 However, L rises by a factor N/2 where N is the number of particle types having mass less than k.T/c/c, counting two helicity types of photons separately so that N is at least 2 for any positive T. (But presumably this is actually true for the Stefan-Boltzmann law generally, at least in so far as the thermal energy is accessible to some mechanism capable of interacting with the particle type. I must also guess that particle types don't turn on step-wise; as k.T/c/c nears the mass of a particle type, some of the particle must surely show up before they reach full power.) Note that k.T/c/c = hbar.c/8/pi/G/m is the mass limit for particle types; this is the square of the Planck mass divided by 4.pi.m. Neutrinos are the least massive particles type, so should provide the lowest temperature deviation from the above; which, once observed, would give us a datum on the mass of neutrinos. However, maybe the thermal energy of a material is normally fairly well de-coupled from any thermal energy inside its nuclei, hence there's no channel for normal thermal energy to couple with a process capable of producing neutrinos. The black hole should be free of this issue, so we can expect its evaporation to be affected by neutrinos first. The known upper bound of 82e-36 kg (see below) implies a temperature of around half a million Kelvin below which we should notice neutrinos complicating this law. """ return q / (k * 4 * self.mass)**2 MassCubedRate = Quantity( -.1, Quantum.h / (4 * Vacuum.c * Cosmos.kappa)**2, """Rate of decrease of the cube of a black hole's mass. ... the evaporation time is shorter for smaller black holes (evaporation time t is proportional to M**3), and black holes with masses less than about 1e11 kg (the mass of a small mountain) can evaporate in less than the age of the Universe. Observe that dm/dt = -L/c/c (see BlackHole._lazy_get_luminosity_.__doc__) gives d(m**3)/dt = 3 m**2 dm/dt = -h * N / 320 / c**2 / kappa**2 constant, except for N's variation, confirming that time to evaporate is proportional to cube of mass: cube of mass decreases at a constant rate, 11.897 peta kg**3 / s, while N = 2. This (a dozen cubic tonnes per microsecond) also deserves to be imortalized as someone's constant ;^) The above-mentioned 1e11 kg black hole that would evaporate within the present age of the universe has an initial temperature of over a tera Kelvin. Which is at least tiny compared to the Planck temperature ... to evaporate within a century, a black hole would need to have mass at most a third of a million tonnes; with a radius less than half an atto-metre, this would be a very good approximation to a point mass. """) def _lazy_get_lifespan_(self, ig, rate=-MassCubedRate): """How long it'll take this black hole to evaporate. Assumes the black hole is isolated in a universe whose cosmic microwave background, if any, has an effective temperature significantly lower than that of the black hole. This condition is not met by most known black holes; it would only be met, in the known universe, by a black hole whose mass is significantly smaller than that of the Moon. A black hole, with temperature equal to that of our observed cosmic microwave background, would have mass .6127 times that of the Moon. It would be in thermal equilibrium, if otherwise isolated, so would not be shrinking at all. However, the cosmic microwave background cools adiabatically as the universe expands, so its temperature will eventually fall below that of the black hole, causing it to evaporate (albeit slowly), so warm, hence evaporate faster and duly decay; so it would not last for ever - but, even if fully isolated as assumed here, its .lifespan is more than 17e33 times the present age of our universe; which is a respectably good approximation to 'for ever' !\n""" return self.mass**3 / rate VolumeRate = Quantity( -.1 / 768 / pi**2, Quantum.h * Cosmos.kappa * Vacuum.c, """Rate of decrease of volume of a black hole. Given that r = 2.G.m/c/c is proportional to m, we can infer (from MassCubedRate, q.v.) that r**3 also decreases at the constant rate h kappa c N / 20480 / pi**3, or about 39e-66 cubic metres per second when N = 2, whence the nominal volume of the black hole decreases at 4.pi/3 times this, i.e. h kappa c N / 15360 / pi**2 or about 0.16e-63 cubic metres per second when N = 2. With k.T inversely proportional to mass, we can infer that (k.T)**-3 decreases at rate pi**3 kappa N / (h c)**2 / 40, so 1/T**3 decreases at 6.433e-54 / second / Kelvin**3 when N = 2. Note that a black hole hot enough to boil Osmium at its surface (i.e. above 5300 Kelvin) would have radius about 32 nano metres, mass 23 peta tonnes (about 3e-4 of the Moon's mass) yet would still take over 28e33 years to boil away ... Of course, all these evaporation times are over-estimates; they ignore the fact that N increases as T grows. At half a peta Kelvin, k.T/c/c exceeds the mass of the heaviest quark, Truth; at 2.6 peta Kelvin it exceeds the mass of a Uranium atom. If N only counts fundamental particle types, it's at least 14 at the first of these and I can't say for sure that it ever gets above that; othewise, it's functionally infinite (every isotope, including all the unstable ones, of every known element; plus quite a lot of stranger things) by around the second, which is only five times as big. A half peta Kelvin black hole has mass just over a quarter of a million tonnes and radius under 0.4e-18 metres; with N=14 this will take about 7 years to evaporate. It seems reasonable to guess that nothing but neutrinos (and photons) have less mass than the electron. On that assumption, N is at most 5 for temperatures below 5.9 giga Kelvin, radii above 31 femto metres (of order the size of nuclei), masses above 21 giga tonne. Even if we (conservatively) suppose N jumps to infinity at this threshold, we only eliminate the last 23 peta years of the time a black hole takes to evaporate; and at least 40% of the remainder survives the N-scaling; for just the age of the universe to remain requires only 22 million tonnes of extra mass, about one part in a thousand. Thus any black hole bigger or colder than the threshold just described will last essentially for ever; and even an asteroid a couple of kilometres across has more mass than that. """)
del Nucleon def waterviscosity(T, A=2.414e-5 * Pascal * second, K=Kelvin, ten=Quantity(10)): """Variation of water's dynamic viscosity with temperature. Takes one argument, an absolute temperature (e.g. a return from study.value.archaea's Centigrade or Fahrenheit). Result is probably only valid if this is a temperature at which water is a liquid !\n""" return A * ten**(247.8/(T/K -140)) # adapted from Wikipedia water = Substance( density = Quantity.fromDecimal(1 -27e-6, 6, None, kilogramme / litre, """Density of water. at 277.13K, when density is maximal. The definition of the UK gallon used to make the density of water 10 pound / gallon at some specific temperature; but now both pound and gallon are defined in terms of SI.\n"""), viscosity = waterviscosity, heat = Heats( capacity = Quantity(1, calorie / gram / Kelvin, """The specific heat capacity of water. The definition of the (short) calorie is as the energy it takes to heat one gram of water by one degree Celsius. Naturally, this varies with temperature; see calorie's documentation for consequences. """)), temperature = Temperatures( triple = Quantity(273.16, Kelvin, "Triple point of water (by definition of the Kelvin)."),