Download distinct_server/comparators.py from User1342/distinct: direct link, hf CLI and curl.
- Browser
- Download file 16.7 kB
-
https://huggingface.co/spaces/User1342/distinct/resolve/main/distinct_server/comparators.py
- Command line
-
hf download hf://spaces/User1342/distinct/distinct_server/comparators.py
-
curl -L -o comparators.py https://huggingface.co/spaces/User1342/distinct/resolve/main/distinct_server/comparators.py
16.7 kB
| """What a number of joules actually amounts to, in things people have done. | |
| WHY A SECOND TABLE, WHEN render.COMPARATORS ALREADY EXISTS | |
| That one answers "how long would this run an appliance": *twelve seconds of a | |
| 10 W bulb*. It is exact and it is nearly useless at the small end, because | |
| nobody has an intuition for twelve seconds of a light. This table answers the | |
| other question: "how much of a whole, familiar thing is this": *a tenth of a | |
| cup of tea*. Those are the units people already own. | |
| HOW THE NUMBERS WERE GOT | |
| Every entry is either derived here from physics, or is a rated figure for a | |
| common appliance run for a stated time. The working is in the note beside it so | |
| a reader can check it rather than trust it, which is the same standard the rest | |
| of this project applies to the figures it republishes. | |
| Water is the recurring one, so it is worth stating once. Heating water takes | |
| ``mass x 4186 J/kg/K x temperature rise``. A 250 ml mug from a 20 C tap to | |
| 100 C is ``0.25 x 4186 x 80 = 83.7 kJ`` of heat into the water. A domestic | |
| kettle puts roughly 80% of what it draws into the water, the rest going into | |
| the element, the body and the steam, so the wall figure is about 105 kJ. Where | |
| an entry says "in a kettle" it is the wall figure, because that is the | |
| electricity somebody actually paid for. | |
| Anything expressed in kWh is converted at 3.6 MJ per kWh. | |
| WHAT IS DELIBERATELY NOT HERE | |
| No CO2, no water footprint, no land, no money. Those are conversions with | |
| assumptions of their own -- grid mix, time of day, jurisdiction -- and this | |
| project does not make assessor-created estimates. A joule compared to another | |
| joule needs no assumption at all, which is why every entry on this list is an | |
| energy and nothing else. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| KWH = 3_600_000.0 | |
| #: Specific heat capacity of water, J/(kg K). | |
| WATER_SPECIFIC_HEAT = 4186.0 | |
| #: A domestic kettle delivers roughly this share of its draw into the water. | |
| KETTLE_EFFICIENCY = 0.80 | |
| #: Standard gravity, m/s^2. | |
| G = 9.81 | |
| def _heat_water(litres: float, rise_c: float, *, efficiency: float = 1.0) -> float: | |
| return litres * WATER_SPECIFIC_HEAT * rise_c / efficiency | |
| def _appliance(watts: float, seconds: float) -> float: | |
| return watts * seconds | |
| def _lift(kilograms: float, metres: float) -> float: | |
| return kilograms * G * metres | |
| class Comparator: | |
| """One familiar thing, and what it costs.""" | |
| #: Reads after a number: "about 2.5 <label>". | |
| label: str | |
| #: Energy for exactly one of them, in joules. | |
| joules: float | |
| #: How that figure was arrived at. | |
| note: str | |
| #: Plural form where "s" will not do. | |
| plural: str = "" | |
| def name(self, count: float) -> str: | |
| """Singular only when the number in front of it will print as "1". | |
| Matched to :func:`_round_count` rather than to the raw float, because | |
| "about 0.98 minute" is what a tolerance around 1.0 produces and it | |
| reads as a mistake. | |
| """ | |
| return self.label if _round_count(count) == "1" else (self.plural or self.label + "s") | |
| #: Ordered by size. Roughly one entry every quarter-decade, so a picker can | |
| #: nearly always find one that puts the figure between 0.1 and 100 of itself. | |
| COMPARATORS: tuple[Comparator, ...] = ( | |
| # ---- sub-joule to 100 J: the scale of a short CPU-only run ----------- | |
| Comparator("heartbeat", 1.0, "about 1 J of mechanical work per beat at rest"), | |
| Comparator("apple lifted one metre", 0.98, "0.1 kg x 9.81 m/s^2 x 1 m", "apples lifted one metre"), | |
| Comparator("second of a smoke alarm", 1.0, "1 W standby draw for 1 s", "seconds of a smoke alarm"), | |
| Comparator("phone screen-second", 2.0, "about 2 W for the panel, for 1 s", "phone screen-seconds"), | |
| Comparator("teaspoon of water warmed by 1 C", 20.9, "0.005 kg x 4186 x 1", "teaspoons of water warmed by 1 C"), | |
| Comparator("second of an LED bulb", 10.0, "10 W for 1 s", "seconds of an LED bulb"), | |
| Comparator("second of a wifi router", 6.0, "6 W for 1 s", "seconds of a wifi router"), | |
| Comparator("AA battery's worth of torch light", 15_120.0, "2.8 Ah x 1.5 V alkaline = 4.2 Wh"), | |
| Comparator("stair step climbed", 132.0, "75 kg x 9.81 x 0.18 m riser", "stair steps climbed"), | |
| Comparator("second of a laptop", 50.0, "50 W for 1 s", "seconds of a laptop"), | |
| Comparator("press-up", 200.0, "about 200 J of work for an adult", "press-ups"), | |
| Comparator("second of a desktop PC", 150.0, "150 W for 1 s", "seconds of a desktop PC"), | |
| Comparator("minute of an LED bulb", 600.0, "10 W for 60 s", "minutes of an LED bulb"), | |
| Comparator("second of a gaming GPU at full load", 350.0, "350 W for 1 s", "seconds of a gaming GPU at full load"), | |
| Comparator("smartphone photo taken", 500.0, "about 0.5 kJ including capture and write", "smartphone photos taken"), | |
| # ---- 1 kJ to 100 kJ: a normal LLM run -------------------------------- | |
| Comparator("second of a kettle", 3000.0, "3 kW element for 1 s", "seconds of a kettle"), | |
| Comparator("minute of a wifi router", 360.0, "6 W for 60 s", "minutes of a wifi router"), | |
| Comparator("minute of a laptop", 3000.0, "50 W for 60 s", "minutes of a laptop"), | |
| Comparator("Google search", 1080.0, "0.3 Wh, Google's own published figure", "Google searches"), | |
| Comparator("minute of a desktop PC", 9000.0, "150 W for 60 s", "minutes of a desktop PC"), | |
| Comparator("litre of water warmed by 1 C", 4186.0, "1 kg x 4186 x 1", "litres of water warmed by 1 C"), | |
| Comparator("minute of a 55-inch TV", 6000.0, "100 W for 60 s", "minutes of a 55-inch TV"), | |
| Comparator("phone charged by 1%", 623.0, "4500 mAh x 3.85 V = 17.3 Wh, one hundredth", "% of a phone charge"), | |
| Comparator("minute of a gaming GPU at full load", 21_000.0, "350 W for 60 s", "minutes of a gaming GPU at full load"), | |
| Comparator("slice of toast", 96_000.0, "800 W toaster for 2 minutes", "slices of toast"), | |
| Comparator("mug of tea boiled", 104_650.0, "0.25 l from 20 C to 100 C in a kettle at 80%", "mugs of tea boiled"), | |
| Comparator("minute in a microwave", 60_000.0, "1 kW draw for 60 s", "minutes in a microwave"), | |
| Comparator("egg boiled", 130_000.0, "hob heating 0.4 l to 100 C and holding it", "eggs boiled"), | |
| Comparator("hour of a wifi router", 21_600.0, "6 W for 1 h", "hours of a wifi router"), | |
| Comparator("minute of an electric shower", 510_000.0, "8.5 kW for 60 s", "minutes of an electric shower"), | |
| Comparator("hour of an LED bulb", 36_000.0, "10 W for 1 h", "hours of an LED bulb"), | |
| Comparator("hour of a laptop", 180_000.0, "50 W for 1 h", "hours of a laptop"), | |
| Comparator("phone charged from flat", 62_300.0, "4500 mAh x 3.85 V = 17.3 Wh", "phones charged from flat"), | |
| Comparator("pint of water boiled", 237_800.0, "0.568 l from 20 C to 100 C in a kettle at 80%", "pints of water boiled"), | |
| Comparator("hour of a 55-inch TV", 360_000.0, "100 W for 1 h", "hours of a 55-inch TV"), | |
| Comparator("piece of bread baked", 250_000.0, "oven share for one loaf's worth of a slice", "pieces of bread baked"), | |
| # ---- 1 MJ to 100 MJ: a long session, or a machine left running ------- | |
| Comparator("hour of a desktop PC", 540_000.0, "150 W for 1 h", "hours of a desktop PC"), | |
| Comparator("hour of a gaming GPU at full load", 1_260_000.0, "350 W for 1 h", "hours of a gaming GPU at full load"), | |
| Comparator("hour of a fridge", 144_000.0, "40 W average duty for 1 h", "hours of a fridge"), | |
| Comparator("load of washing", 2_520_000.0, "0.7 kWh, a 40 C cotton cycle", "loads of washing"), | |
| Comparator("dishwasher cycle", 3_600_000.0, "1.0 kWh eco programme", "dishwasher cycles"), | |
| Comparator("shower taken", 4_080_000.0, "8.5 kW electric shower for 8 minutes", "showers taken"), | |
| Comparator("hour of an oven", 7_200_000.0, "2 kW for 1 h", "hours of an oven"), | |
| Comparator("tumble dryer cycle", 9_000_000.0, "2.5 kWh, a condenser dryer", "tumble dryer cycles"), | |
| Comparator("kilometre driven in an electric car", 648_000.0, "0.18 kWh/km", "kilometres driven in an electric car"), | |
| Comparator("day of a fridge", 3_456_000.0, "40 W average duty for 24 h", "days of a fridge"), | |
| Comparator("day of a laptop in constant use", 4_320_000.0, "50 W for 24 h", "days of a laptop in constant use"), | |
| Comparator("bath run", 7_060_000.0, "150 l from 10 C to 40 C at 90% immersion efficiency", "baths run"), | |
| Comparator("mile driven in an electric car", 1_043_000.0, "0.18 kWh/km over 1.609 km", "miles driven in an electric car"), | |
| # ---- filling the band a run actually lands in ------------------------ | |
| Comparator("second of a kettle at half power", 1500.0, "1.5 kW for 1 s", "seconds of a kettle at half power"), | |
| Comparator("second of a hair dryer", 1800.0, "1.8 kW for 1 s", "seconds of a hair dryer"), | |
| Comparator("second of a microwave", 1000.0, "1 kW for 1 s", "seconds of a microwave"), | |
| Comparator("second of a vacuum cleaner", 900.0, "900 W for 1 s", "seconds of a vacuum cleaner"), | |
| Comparator("second of a 55-inch TV", 100.0, "100 W for 1 s", "seconds of a 55-inch TV"), | |
| Comparator("second of a games console", 200.0, "200 W for 1 s", "seconds of a games console"), | |
| Comparator("second of a fridge compressor", 120.0, "120 W while running, for 1 s", "seconds of a fridge compressor"), | |
| Comparator("minute of a phone charging", 1038.0, "17.3 Wh over about 100 minutes", "minutes of a phone charging"), | |
| Comparator("minute of a games console", 12_000.0, "200 W for 60 s", "minutes of a games console"), | |
| Comparator("minute of a fridge compressor", 7200.0, "120 W for 60 s", "minutes of a fridge compressor"), | |
| Comparator("minute of a hair dryer", 108_000.0, "1.8 kW for 60 s", "minutes of a hair dryer"), | |
| Comparator("minute of a vacuum cleaner", 54_000.0, "900 W for 60 s", "minutes of a vacuum cleaner"), | |
| Comparator("minute of an oven", 120_000.0, "2 kW for 60 s", "minutes of an oven"), | |
| Comparator("minute of a tumble dryer", 150_000.0, "2.5 kW for 60 s", "minutes of a tumble dryer"), | |
| Comparator("minute of a fan heater", 120_000.0, "2 kW for 60 s", "minutes of a fan heater"), | |
| Comparator("espresso pulled", 45_000.0, "1.4 kW machine for about 32 s including pre-heat share", "espressos pulled"), | |
| Comparator("cafetiere of coffee", 167_400.0, "0.4 l from 20 C to 100 C in a kettle at 80%", "cafetieres of coffee"), | |
| Comparator("hot water bottle filled", 313_950.0, "0.75 l from 20 C to 100 C in a kettle at 80%", "hot water bottles filled"), | |
| Comparator("pan of pasta water boiled", 837_200.0, "2 l from 20 C to 100 C in a kettle at 80%", "pans of pasta water boiled"), | |
| Comparator("slice of pizza reheated", 90_000.0, "1 kW microwave for 90 s", "slices of pizza reheated"), | |
| Comparator("piece of bread toasted lightly", 48_000.0, "800 W toaster for 60 s", "pieces of bread toasted lightly"), | |
| Comparator("kettle boiled full", 627_900.0, "1.5 l from 20 C to 100 C in a kettle at 80%", "kettles boiled full"), | |
| Comparator("laptop charged from flat", 216_000.0, "60 Wh battery", "laptops charged from flat"), | |
| Comparator("tablet charged from flat", 118_800.0, "33 Wh battery", "tablets charged from flat"), | |
| Comparator("e-bike battery charged", 1_800_000.0, "500 Wh battery", "e-bike batteries charged"), | |
| Comparator("cordless drill battery charged", 194_400.0, "54 Wh pack", "cordless drill batteries charged"), | |
| Comparator("hour of a games console", 720_000.0, "200 W for 1 h", "hours of a games console"), | |
| Comparator("hour of a set-top box", 28_800.0, "8 W for 1 h", "hours of a set-top box"), | |
| Comparator("hour of a phone charger left plugged in", 1800.0, "0.5 W vampire draw for 1 h", "hours of a phone charger left plugged in"), | |
| Comparator("hour of an electric blanket", 360_000.0, "100 W for 1 h", "hours of an electric blanket"), | |
| Comparator("hour of a dehumidifier", 1_080_000.0, "300 W for 1 h", "hours of a dehumidifier"), | |
| Comparator("hour of a fan heater", 7_200_000.0, "2 kW for 1 h", "hours of a fan heater"), | |
| Comparator("hour of a heat pump", 3_600_000.0, "1 kW electrical input for 1 h", "hours of a heat pump"), | |
| Comparator("hour of a server in a rack", 1_800_000.0, "500 W for 1 h", "hours of a server in a rack"), | |
| Comparator("day of a wifi router", 518_400.0, "6 W for 24 h", "days of a wifi router"), | |
| Comparator("day of a smart speaker", 259_200.0, "3 W for 24 h", "days of a smart speaker"), | |
| Comparator("day of a games console left on", 17_280_000.0, "200 W for 24 h", "days of a games console left on"), | |
| Comparator("week of a fridge", 24_192_000.0, "40 W average duty for 7 days", "weeks of a fridge"), | |
| Comparator("month of a wifi router", 15_768_000.0, "6 W for 30.4 days", "months of a wifi router"), | |
| Comparator("10 km drive in an electric car", 6_480_000.0, "0.18 kWh/km x 10", "10 km drives in an electric car"), | |
| Comparator("100 km drive in an electric car", 64_800_000.0, "0.18 kWh/km x 100", "100 km drives in an electric car"), | |
| Comparator("litre of petrol burnt", 34_200_000.0, "9.5 kWh of chemical energy per litre", "litres of petrol burnt"), | |
| Comparator("bag of coal burnt", 700_000_000.0, "25 kg at about 28 MJ/kg", "bags of coal burnt"), | |
| Comparator("month of a UK home", 821_000_000.0, "2739 kWh a year, divided by twelve", "months of a UK home"), | |
| Comparator("hour of a domestic solar array at noon", 14_400_000.0, "4 kWp array for 1 h", "hours of a domestic solar array at noon"), | |
| Comparator("day of a domestic solar array", 43_200_000.0, "about 12 kWh on a good summer day", "days of a domestic solar array"), | |
| Comparator("year of a fridge", 1_261_440_000.0, "40 W average duty for a year", "years of a fridge"), | |
| Comparator("lifetime of a AAA battery", 5400.0, "1000 mAh x 1.5 V", "lifetimes of a AAA battery"), | |
| Comparator("kilogram lifted to the top of Big Ben", 957.0, "1 kg x 9.81 x 96 m", "kilograms lifted to the top of Big Ben"), | |
| # ---- 100 MJ upwards: fleets, and the honest top of the scale --------- | |
| Comparator("day of a UK home", 27_000_000.0, "7.5 kWh, Ofgem typical domestic consumption", "days of a UK home"), | |
| Comparator("week of a UK home", 189_000_000.0, "7.5 kWh x 7", "weeks of a UK home"), | |
| Comparator("full charge of an electric car", 216_000_000.0, "60 kWh battery", "full charges of an electric car"), | |
| Comparator("year of a UK home", 9_860_000_000.0, "2739 kWh, Ofgem typical annual electricity", "years of a UK home"), | |
| ) | |
| #: The comparator picker aims to put the figure inside this band, so the number | |
| #: in front of the label is one a person can picture. Below 0.1 the answer is | |
| #: "hardly any of one", above 100 it stops being a comparison. | |
| NICE_LOW = 0.1 | |
| NICE_HIGH = 100.0 | |
| def choose(joules: float, *, table: tuple[Comparator, ...] = COMPARATORS) -> tuple[Comparator, float] | None: | |
| """The comparator that puts this figure closest to one whole unit. | |
| Closest in log space, because 0.5 of a thing and 2 of a thing are equally | |
| easy to picture and a linear distance would prefer the wrong one of them. | |
| """ | |
| if not isinstance(joules, (int, float)) or isinstance(joules, bool): | |
| return None | |
| value = float(joules) | |
| if value <= 0 or value != value or value in (float("inf"), float("-inf")): | |
| return None | |
| import math | |
| best: tuple[Comparator, float] | None = None | |
| best_distance = float("inf") | |
| for entry in table: | |
| if entry.joules <= 0: | |
| continue | |
| count = value / entry.joules | |
| distance = abs(math.log10(count)) | |
| if distance < best_distance: | |
| best, best_distance = (entry, count), distance | |
| return best | |
| def _round_count(count: float) -> str: | |
| if count >= 100: | |
| return f"{count:,.0f}" | |
| if count >= 10: | |
| return f"{count:.0f}" | |
| if count >= 1: | |
| return f"{count:.1f}".rstrip("0").rstrip(".") | |
| if count >= 0.1: | |
| return f"{count:.2f}".rstrip("0").rstrip(".") | |
| return f"{count:.3f}".rstrip("0").rstrip(".") | |
| def describe(joules: float) -> str: | |
| """"about 1.4 mugs of tea boiled", or an empty string if there is nothing to say.""" | |
| chosen = choose(joules) | |
| if chosen is None: | |
| return "" | |
| entry, count = chosen | |
| if count < 0.01: | |
| return f"a tiny fraction of {_article(entry.label)}" | |
| return f"about {_round_count(count)} {entry.name(count)}" | |
| def _article(label: str) -> str: | |
| return ("an " if label[:1].lower() in "aeiou" else "a ") + label | |
| def basis(joules: float) -> str: | |
| """The working behind whichever comparator was shown.""" | |
| chosen = choose(joules) | |
| return "" if chosen is None else chosen[0].note | |