mirror of
https://github.com/harvard-edge/cs249r_book.git
synced 2026-07-15 21:28:33 -05:00
1383 lines
54 KiB
Python
1383 lines
54 KiB
Python
"""Tests for mlsysim.fmt formatting guards."""
|
||
|
||
import pytest
|
||
|
||
from mlsysim.core.units import ureg
|
||
from mlsysim.core.units import GB, J, K, MB, TB, USD, hour, kg, kWh, second
|
||
import math
|
||
|
||
from mlsysim.fmt import (
|
||
MarkdownStr,
|
||
fmt,
|
||
fmt_arithmetic_intensity,
|
||
fmt_area,
|
||
fmt_count,
|
||
fmt_count_range,
|
||
fmt_compute_efficiency,
|
||
fmt_carbon_intensity,
|
||
fmt_decibel,
|
||
fmt_display_math,
|
||
fmt_emissions,
|
||
fmt_eur,
|
||
fmt_int,
|
||
fmt_energy_per_bit,
|
||
fmt_energy_per_byte,
|
||
fmt_energy_per_flop,
|
||
fmt_energy_per_op,
|
||
fmt_heat_flux,
|
||
fmt_illuminance,
|
||
fmt_multiple,
|
||
fmt_multiple_range,
|
||
fmt_memory_capacity,
|
||
fmt_percent,
|
||
fmt_percent_range,
|
||
fmt_pp,
|
||
fmt_params,
|
||
fmt_power,
|
||
fmt_qty,
|
||
fmt_qty_int,
|
||
fmt_qty_range,
|
||
fmt_magnitude,
|
||
fmt_bandwidth,
|
||
fmt_flop_rate,
|
||
fmt_flops,
|
||
fmt_length,
|
||
fmt_ops_rate,
|
||
fmt_water,
|
||
fmt_water_rate,
|
||
fmt_water_intensity,
|
||
fmt_range,
|
||
fmt_rate,
|
||
fmt_fps,
|
||
fmt_ratio,
|
||
fmt_sci,
|
||
fmt_sci_flops,
|
||
fmt_sci_qty,
|
||
fmt_specific_heat,
|
||
fmt_temperature,
|
||
fmt_temperature_rate,
|
||
fmt_text,
|
||
fmt_time,
|
||
fmt_time_range,
|
||
fmt_tokens,
|
||
fmt_unit,
|
||
fmt_usd,
|
||
fmt_usd_range,
|
||
fmt_val,
|
||
)
|
||
|
||
|
||
class TestFmtComposition:
|
||
def test_fmt_text_preserves_markdown_rendering(self):
|
||
out = fmt_text(
|
||
fmt_usd(400_000),
|
||
" $\\times$ ",
|
||
fmt(0.4, precision=2),
|
||
" = ",
|
||
fmt_usd(160_000),
|
||
)
|
||
|
||
assert out == "\\$400,000 $\\times$ 0.40 = \\$160,000"
|
||
assert isinstance(out, MarkdownStr)
|
||
assert out._repr_markdown_() == str(out)
|
||
|
||
def test_fmt_display_math_wraps_block_math(self):
|
||
out = fmt_display_math(r"x = 1")
|
||
|
||
assert out == r"$$x = 1$$"
|
||
assert isinstance(out, MarkdownStr)
|
||
|
||
|
||
class TestFmtPrecisionGuards:
|
||
def test_precision_zero_accepts_whole_numbers(self):
|
||
assert fmt(989, precision=0, commas=False) == "989"
|
||
assert fmt(10.0, precision=0, commas=False) == "10"
|
||
assert fmt(175.0, precision=0, commas=False) == "175"
|
||
|
||
def test_precision_zero_rejects_small_fractions_that_display_as_zero(self):
|
||
with pytest.raises(ValueError, match="formatted as '0'"):
|
||
fmt(0.1, precision=0, commas=False)
|
||
with pytest.raises(ValueError, match="formatted as '0'"):
|
||
fmt(0.4, precision=0, commas=False)
|
||
|
||
def test_precision_zero_rejects_non_integer_values(self):
|
||
with pytest.raises(ValueError, match="not integer-like"):
|
||
fmt(10.7, precision=0, commas=False)
|
||
with pytest.raises(ValueError, match="not integer-like"):
|
||
fmt(84.7, precision=0, commas=False)
|
||
with pytest.raises(ValueError, match="not integer-like"):
|
||
fmt(4.1, precision=0, commas=False)
|
||
|
||
def test_precision_zero_accepts_explicit_round(self):
|
||
assert fmt(round(10.7), precision=0, commas=False) == "11"
|
||
assert fmt(round(362507.545), precision=0, commas=True) == "362,508"
|
||
|
||
def test_precision_one_preserves_fractions(self):
|
||
assert fmt(10.7, precision=1, commas=False) == "10.7"
|
||
assert fmt(8.5, precision=1, commas=False) == "8.5"
|
||
|
||
def test_fmt_precision_none_auto_trims_integer_like_values(self):
|
||
assert fmt(153.0, precision=None, commas=False) == "153"
|
||
assert fmt(153.016, precision=None, commas=False) == "153"
|
||
assert fmt(2.04, precision=None, commas=False) == "2.04"
|
||
assert fmt(0.2, precision=None, commas=False) == "0.2"
|
||
|
||
def test_auto_precision_preserves_large_fractions(self):
|
||
assert (
|
||
fmt_arithmetic_intensity(
|
||
153.016 * ureg.flop / ureg.byte,
|
||
commas=False,
|
||
)
|
||
== "153 FLOP/byte"
|
||
)
|
||
|
||
def test_auto_precision_keeps_meaningful_small_decimals(self):
|
||
assert fmt_bandwidth(0.2 * MB / second, unit=MB / second, commas=False) == "0.2 MB/s"
|
||
assert fmt_bandwidth(2.04 * TB / second, unit=TB / second, commas=False) == "2.04 TB/s"
|
||
|
||
def test_auto_precision_trim_preserves_commas(self):
|
||
assert (
|
||
fmt_bandwidth(1000.01 * MB / second, unit=MB / second, commas=True)
|
||
== "1,000 MB/s"
|
||
)
|
||
|
||
def test_explicit_precision_preserves_requested_decimal(self):
|
||
assert (
|
||
fmt_arithmetic_intensity(
|
||
153.016 * ureg.flop / ureg.byte,
|
||
precision=1,
|
||
commas=False,
|
||
)
|
||
== "153.0 FLOP/byte"
|
||
)
|
||
|
||
def test_precision_one_rejects_spurious_trailing_zeros_on_integers(self):
|
||
with pytest.raises(ValueError, match="spurious trailing zeros"):
|
||
fmt(512.0, precision=1, commas=False)
|
||
with pytest.raises(ValueError, match="spurious trailing zeros"):
|
||
fmt(989, precision=1, commas=False)
|
||
|
||
def test_returns_markdown_str(self):
|
||
out = fmt(42, precision=0, commas=False)
|
||
assert isinstance(out, MarkdownStr)
|
||
assert out._repr_markdown_() == "42"
|
||
|
||
def test_named_display_markers_replace_raw_prefixes(self):
|
||
assert fmt(100, precision=0, commas=False, approx=True, suffix=" MB/s") == "~100 MB/s"
|
||
assert fmt(1000, precision=0, lower_bound=True, suffix=" MB/s") == "> 1,000 MB/s"
|
||
|
||
def test_rejects_conflicting_display_markers(self):
|
||
with pytest.raises(ValueError, match="only one display marker"):
|
||
fmt(100, precision=0, approx=True, lower_bound=True)
|
||
with pytest.raises(ValueError, match="Use either prefix="):
|
||
fmt(100, precision=0, prefix="~", approx=True)
|
||
|
||
|
||
class TestFmtInt:
|
||
def test_rounds_computed_values_explicitly(self):
|
||
assert fmt_int(120.28, commas=False) == "120"
|
||
assert fmt_int(10.7, commas=False) == "11"
|
||
assert fmt_int(362507.545, commas=True) == "362,508"
|
||
|
||
def test_accepts_prefix_and_suffix(self):
|
||
assert fmt_int(175, commas=False, suffix=" billion") == "175 billion"
|
||
|
||
def test_accepts_named_approx_marker(self):
|
||
assert fmt_int(80, commas=False, approx=True, suffix=" GB") == "~80 GB"
|
||
|
||
|
||
class TestFmtQty:
|
||
def test_mj_over_ms_to_mw(self):
|
||
energy = 66 * ureg.millijoule
|
||
time = 1000 * ureg.millisecond
|
||
power = energy / time
|
||
out = fmt_qty(power, ureg.mW, precision=0, commas=False)
|
||
assert out == "66 mW"
|
||
|
||
def test_gb_display(self):
|
||
mem = 140 * ureg.GB
|
||
out = fmt_qty(mem, ureg.GB, precision=0, commas=False)
|
||
assert out == "140 GB"
|
||
|
||
def test_accepts_unit_keyword(self):
|
||
mem = 140 * ureg.GB
|
||
assert fmt_qty(mem, unit=ureg.GB, precision=0, commas=False) == "140 GB"
|
||
with pytest.raises(TypeError, match="either a positional unit or unit="):
|
||
fmt_qty(mem, ureg.GB, unit=ureg.GB, precision=0)
|
||
|
||
def test_fmt_magnitude_keeps_unit_conversion_checked(self):
|
||
q = 2048 * ureg.MB
|
||
assert fmt_magnitude(q, unit=ureg.GB, precision=3, commas=False) == "2.048"
|
||
|
||
def test_fmt_magnitude_requires_quantity(self):
|
||
with pytest.raises(TypeError, match="requires a Pint Quantity"):
|
||
fmt_magnitude(2048, unit=ureg.GB, precision=0)
|
||
|
||
def test_accepts_named_marker(self):
|
||
mem = 140 * ureg.GB
|
||
out = fmt_qty(mem, ureg.GB, precision=0, commas=False, approx=True)
|
||
assert out == "~140 GB"
|
||
|
||
def test_currency_is_refused(self):
|
||
# Currency must go through fmt_usd, not fmt_qty: fmt_qty cannot emit the
|
||
# Pandoc-safe escaped "\\$" and would leak a literal " USD" suffix.
|
||
price = 2.5 * USD
|
||
with pytest.raises(ValueError, match="fmt_usd"):
|
||
fmt_qty(price, USD, precision=2, commas=False)
|
||
|
||
def test_plain_number_is_refused(self):
|
||
with pytest.raises(TypeError, match="requires a Pint Quantity"):
|
||
fmt_qty(5, ureg.GB, precision=0, commas=False)
|
||
|
||
def test_returns_markdown_str(self):
|
||
out = fmt_qty(5 * ureg.millisecond, ureg.millisecond, precision=0, commas=False)
|
||
assert isinstance(out, MarkdownStr)
|
||
assert out == "5 ms"
|
||
|
||
def test_structured_denominator(self):
|
||
energy = 0.1 * ureg.millijoule
|
||
out = fmt_qty(
|
||
energy,
|
||
ureg.millijoule,
|
||
precision=1,
|
||
commas=False,
|
||
per="inference",
|
||
)
|
||
assert out == "0.1 mJ/inference"
|
||
|
||
def test_unit_label_preserves_dimension_check(self):
|
||
q = 5 * ureg.flop / ureg.byte
|
||
assert (
|
||
fmt_qty(q, ureg.flop / ureg.byte, precision=0, unit_label="FLOP/byte") == "5 FLOP/byte"
|
||
)
|
||
with pytest.raises(Exception):
|
||
fmt_qty(5 * ureg.second, ureg.flop / ureg.byte, unit_label="FLOP/byte")
|
||
|
||
def test_unit_label_rejects_value_kind_glyphs(self):
|
||
with pytest.raises(ValueError, match="currency, percent, or multiplier"):
|
||
fmt_qty(5 * ureg.GB, ureg.GB, unit_label="GB%")
|
||
|
||
def test_rejects_legacy_and_structured_denominator_mix(self):
|
||
with pytest.raises(ValueError, match="extra_suffix="):
|
||
fmt_qty(
|
||
1 * ureg.millijoule,
|
||
ureg.millijoule,
|
||
precision=0,
|
||
extra_suffix="/token",
|
||
per="inference",
|
||
)
|
||
|
||
|
||
class TestFmtQtyInt:
|
||
def test_rounds_quantity_after_unit_conversion(self):
|
||
assert fmt_qty_int(100 * ureg.GiB, ureg.GB, commas=False) == "107 GB"
|
||
|
||
def test_supports_structured_quantity_suffixes(self):
|
||
out = fmt_qty_int(
|
||
1250 * ureg.MB / ureg.second,
|
||
ureg.GB / ureg.second,
|
||
commas=False,
|
||
)
|
||
assert out == "1 GB/s"
|
||
|
||
def test_accepts_unit_keyword(self):
|
||
assert fmt_qty_int(100 * ureg.GB, unit=ureg.GB, commas=False) == "100 GB"
|
||
|
||
def test_plain_number_is_refused(self):
|
||
with pytest.raises(TypeError, match="requires a Pint Quantity"):
|
||
fmt_qty_int(5, ureg.GB, commas=False)
|
||
|
||
def test_returns_markdown_str(self):
|
||
assert isinstance(fmt_qty_int(5 * ureg.GB, ureg.GB), MarkdownStr)
|
||
|
||
|
||
class TestFmtUsd:
|
||
def test_basic_dollar_is_escaped(self):
|
||
# The escaped "\\$" is mandatory so prose never enters math mode.
|
||
assert fmt_usd(15000) == "\\$15,000"
|
||
assert fmt_usd(10, commas=False) == "\\$10"
|
||
|
||
def test_rounds_to_whole_dollars_at_precision_zero(self):
|
||
assert fmt_usd(12345.6) == "\\$12,346"
|
||
assert fmt_usd(999.4) == "\\$999"
|
||
|
||
def test_precision_preserves_cents(self):
|
||
assert fmt_usd(0.09, precision=2, commas=False, suffix="/GB") == "\\$0.09/GB"
|
||
assert fmt_usd(4.6, precision=1, suffix="M") == "\\$4.6M"
|
||
|
||
def test_structured_scale_and_denominator(self):
|
||
assert fmt_usd(4_600_000, precision=1, commas=False, scale="M") == "\\$4.6M"
|
||
assert fmt_usd(4_750_000, precision=2, commas=False, scale="million") == "\\$4.75 million"
|
||
assert fmt_usd(4_750_000, precision=2, commas=False, scale="Million") == "\\$4.75 Million"
|
||
assert fmt_usd(0.09, precision=2, commas=False, per="GB") == "\\$0.09/GB"
|
||
assert fmt_usd(0.50, precision=2, commas=False, per="click") == "\\$0.50/click"
|
||
assert fmt_usd(10_000, per="run") == "\\$10,000/run"
|
||
assert fmt_usd(10, commas=False, per="million queries") == "\\$10/million queries"
|
||
assert (
|
||
fmt_usd(0.06, precision=2, commas=False, per="1K inferences") == "\\$0.06/1K inferences"
|
||
)
|
||
assert fmt_usd(12_000, commas=False, scale="K", per="year") == "\\$12K/year"
|
||
assert fmt_usd(8000, approx=True, marker="*") == "~\\$8,000*"
|
||
|
||
def test_rejects_legacy_suffix_with_structured_parts(self):
|
||
with pytest.raises(ValueError, match="suffix="):
|
||
fmt_usd(1000, scale="K", suffix="K")
|
||
with pytest.raises(ValueError, match="suffix="):
|
||
fmt_usd(1000, marker="*", suffix="*")
|
||
|
||
def test_rejects_bad_denominator(self):
|
||
with pytest.raises(ValueError, match="omit the leading"):
|
||
fmt_usd(0.09, precision=2, per="/GB")
|
||
with pytest.raises(ValueError, match="fmt_usd per must be"):
|
||
fmt_usd(0.09, precision=2, per="widgets")
|
||
|
||
def test_rejects_bad_marker(self):
|
||
with pytest.raises(ValueError, match="fmt_usd marker"):
|
||
fmt_usd(1000, marker="note")
|
||
|
||
def test_approx_prepends_tilde(self):
|
||
assert fmt_usd(1234.7, approx=True, suffix="/year") == "~\\$1,235/year"
|
||
|
||
def test_accepts_pure_dollar_quantity(self):
|
||
assert fmt_usd(2500 * USD) == "\\$2,500"
|
||
|
||
def test_accepts_dollar_rate_quantity_with_physical_denominator(self):
|
||
assert fmt_usd(0.09 * USD / GB, precision=2, commas=False, per="GB") == "\\$0.09/GB"
|
||
assert fmt_usd(5 * USD / hour, commas=False, per="hr") == "\\$5/hr"
|
||
assert (
|
||
fmt_usd(23 * USD / (TB * ureg.month), commas=False, per="TB/month") == "\\$23/TB/month"
|
||
)
|
||
assert fmt_usd(0.12 * USD / kWh, precision=2, commas=False, per="kWh") == "\\$0.12/kWh"
|
||
|
||
def test_rejects_non_currency_quantities(self):
|
||
with pytest.raises(ValueError, match="dollar Quantity"):
|
||
fmt_usd(3 * GB)
|
||
|
||
def test_rejects_rate_quantity_without_denominator(self):
|
||
with pytest.raises(ValueError, match="no per="):
|
||
fmt_usd(0.09 * USD / GB, precision=2)
|
||
|
||
def test_never_emits_literal_usd(self):
|
||
for out in (fmt_usd(5), fmt_usd(5 * USD, suffix="/hr"), fmt_usd(5, approx=True)):
|
||
assert "USD" not in out
|
||
|
||
def test_returns_markdown_str(self):
|
||
assert isinstance(fmt_usd(100), MarkdownStr)
|
||
|
||
|
||
class TestFmtEur:
|
||
def test_basic_eur_code_style(self):
|
||
assert fmt_eur(210_000_000, scale="M", precision=0, commas=False) == "EUR 210M"
|
||
|
||
|
||
class TestFmtCountLegacy:
|
||
def test_accepts_named_marker(self):
|
||
assert fmt_count(1024, suffix=" GPUs", approx=True) == "~1,024 GPUs"
|
||
|
||
|
||
class TestFmtRate:
|
||
def test_formats_allowlisted_service_rates(self):
|
||
assert fmt_rate(2500, "QPS") == "2,500 QPS"
|
||
assert fmt_rate(1200, "tokens/s") == "1,200 tokens/s"
|
||
assert fmt_rate(500_000, "tokens/s", scale="K", commas=False) == "500K tokens/s"
|
||
assert (
|
||
fmt_rate(
|
||
45_200_000,
|
||
"tokens/hour",
|
||
scale="million",
|
||
precision=1,
|
||
commas=False,
|
||
)
|
||
== "45.2 million tokens/hour"
|
||
)
|
||
assert fmt_rate(2500, "QPS", commas=False) == "2500 QPS"
|
||
assert fmt_rate(60, "FPS") == "60 FPS"
|
||
assert fmt_rate(5000, "requests/s") == "5,000 requests/s"
|
||
assert fmt_rate(1000, "samples/hour", commas=False) == "1000 samples/hour"
|
||
|
||
def test_rejects_unknown_rate_unit(self):
|
||
with pytest.raises(ValueError, match="fmt_rate unit must be"):
|
||
fmt_rate(10, "GB/s")
|
||
|
||
def test_rejects_negative_by_default(self):
|
||
with pytest.raises(ValueError, match="non-negative rate"):
|
||
fmt_rate(-1, "QPS")
|
||
assert fmt_rate(-1, "QPS", allow_negative=True) == "-1 QPS"
|
||
|
||
def test_returns_markdown_str(self):
|
||
assert isinstance(fmt_rate(1, "QPS"), MarkdownStr)
|
||
|
||
|
||
class TestFmtFps:
|
||
def test_formats_frame_rate(self):
|
||
assert fmt_fps(30) == "30 FPS"
|
||
assert fmt_fps(30.3) == "30 FPS"
|
||
assert fmt_fps(29.5, precision=1) == "29.5 FPS"
|
||
assert fmt_fps(120, commas=True) == "120 FPS"
|
||
|
||
def test_rejects_negative_by_default(self):
|
||
with pytest.raises(ValueError, match="non-negative rate"):
|
||
fmt_fps(-1)
|
||
assert fmt_fps(-1, allow_negative=True) == "-1 FPS"
|
||
|
||
def test_returns_markdown_str(self):
|
||
assert isinstance(fmt_fps(30), MarkdownStr)
|
||
|
||
|
||
class TestFmtTime:
|
||
def test_symbol_style_accepts_quantities_and_plain_numbers(self):
|
||
assert fmt_time(1500 * ureg.millisecond, ureg.second) == "1.5 s"
|
||
assert fmt_time(1500 * ureg.millisecond, unit=ureg.second) == "1.5 s"
|
||
assert fmt_time(35, ureg.second, precision=0) == "35 s"
|
||
assert fmt_time(35, "second", precision=0) == "35 s"
|
||
assert fmt_time(35, "s", precision=0) == "35 s"
|
||
assert fmt_time(12, "millisecond", precision=0) == "12 ms"
|
||
assert fmt_time(12, "ms", precision=0) == "12 ms"
|
||
assert fmt_time(5, "microsecond", precision=0) == "5 μs"
|
||
assert fmt_time(5, "µs", precision=0) == "5 μs"
|
||
assert fmt_time(5, "μs", precision=0) == "5 μs"
|
||
|
||
def test_word_style_pluralizes(self):
|
||
assert fmt_time(1, ureg.second, precision=0, style="word") == "1 second"
|
||
assert fmt_time(2, ureg.second, precision=0, style="word") == "2 seconds"
|
||
assert fmt_time(2, "year", precision=0, style="word") == "2 years"
|
||
|
||
def test_attributive_word_style_is_hyphenated_singular(self):
|
||
assert fmt_time(1, "hour", precision=0, style="word", attributive=True) == "1-hour"
|
||
assert fmt_time(24, "hour", precision=0, style="word", attributive=True) == "24-hour"
|
||
assert fmt_time(15, "minute", precision=0, style="word", attributive=True) == "15-minute"
|
||
|
||
def test_attributive_rejects_symbol_style_and_per(self):
|
||
with pytest.raises(ValueError, match="attributive"):
|
||
fmt_time(5, "hour", precision=0, attributive=True)
|
||
with pytest.raises(ValueError, match="per="):
|
||
fmt_time(
|
||
5,
|
||
"hour",
|
||
precision=0,
|
||
style="word",
|
||
attributive=True,
|
||
per="day",
|
||
)
|
||
|
||
def test_checked_symbol_marker(self):
|
||
assert fmt_time(100, "millisecond", precision=0, marker="+") == "100 ms+"
|
||
with pytest.raises(ValueError, match="marker"):
|
||
fmt_time(100, "millisecond", precision=0, marker="ish")
|
||
with pytest.raises(ValueError, match="symbol"):
|
||
fmt_time(100, "millisecond", precision=0, style="word", marker="+")
|
||
with pytest.raises(ValueError, match="cannot be combined"):
|
||
fmt_time(100, "millisecond", precision=0, per="step", marker="+")
|
||
|
||
def test_rejects_non_time_unit(self):
|
||
with pytest.raises(ValueError, match="time unit"):
|
||
fmt_time(5, ureg.GB)
|
||
|
||
def test_rejects_negative_by_default(self):
|
||
with pytest.raises(ValueError, match="non-negative duration"):
|
||
fmt_time(-1, ureg.second, precision=0)
|
||
assert fmt_time(-1, ureg.second, precision=0, allow_negative=True) == "-1 s"
|
||
|
||
def test_returns_markdown_str(self):
|
||
assert isinstance(fmt_time(1, ureg.second, precision=0), MarkdownStr)
|
||
|
||
|
||
class TestFmtPercentGuards:
|
||
def test_precision_zero_accepts_whole_percentages(self):
|
||
assert fmt_percent(0.45, precision=0, commas=False) == "45"
|
||
|
||
def test_precision_zero_rejects_fractional_percentages(self):
|
||
with pytest.raises(ValueError, match="not integer-like"):
|
||
fmt_percent(0.456, precision=0, commas=False)
|
||
|
||
def test_default_style_is_bare_number_backward_compatible(self):
|
||
# Default precision=None auto-resolves integer-like percentages.
|
||
assert fmt_percent(0.85) == "85"
|
||
assert fmt_percent(0.123, precision=1) == "12.3"
|
||
|
||
def test_default_precision_auto_resolves_integer_like_percentages(self):
|
||
assert fmt_percent(0.85, style="prose") == "85 percent"
|
||
assert fmt_percent(0.90, style="symbol") == "90%"
|
||
assert fmt_percent_range(0.85, 0.90, style="prose") == "85\u201390 percent"
|
||
assert fmt_pp(7.0) == "7 percentage points"
|
||
|
||
def test_prose_style_owns_the_word_percent(self):
|
||
assert fmt_percent(0.85, precision=0, style="prose") == "85 percent"
|
||
|
||
def test_symbol_style_owns_the_glyph(self):
|
||
assert fmt_percent(0.85, precision=0, style="symbol") == "85%"
|
||
|
||
def test_rejects_already_scaled_value_the_10000_percent_bug(self):
|
||
with pytest.raises(ValueError, match="0-1 ratio"):
|
||
fmt_percent(85)
|
||
with pytest.raises(ValueError, match="0-1 ratio"):
|
||
fmt_percent(45, precision=0)
|
||
|
||
def test_allows_over_100_percent_only_with_explicit_max_ratio(self):
|
||
with pytest.raises(ValueError, match="0-1 ratio"):
|
||
fmt_percent(2.0, precision=0)
|
||
assert fmt_percent(2.0, precision=0, max_ratio=3) == "200"
|
||
|
||
def test_rejects_negative_ratio_by_default(self):
|
||
# a bounded proportion (accuracy, utilization) is never negative
|
||
with pytest.raises(ValueError, match="0-1 ratio"):
|
||
fmt_percent(-0.05, precision=0)
|
||
|
||
def test_allow_negative_widens_domain_for_signed_change(self):
|
||
# ROI / cost-delta: signed, may exceed 100% -> opt in explicitly
|
||
assert fmt_percent(-0.818, precision=1, style="symbol", allow_negative=True) == "-81.8%"
|
||
assert (
|
||
fmt_percent(8.089, precision=1, style="symbol", allow_negative=True, max_ratio=9)
|
||
== "808.9%"
|
||
)
|
||
|
||
def test_allow_negative_still_bounds_magnitude(self):
|
||
# the guard still catches a true 100x blunder even when signed
|
||
with pytest.raises(ValueError, match="0-1 ratio"):
|
||
fmt_percent(-85, allow_negative=True)
|
||
|
||
def test_rejects_unknown_style(self):
|
||
with pytest.raises(ValueError, match="style must be"):
|
||
fmt_percent(0.5, style="pct")
|
||
|
||
def test_returns_markdown_str(self):
|
||
assert isinstance(fmt_percent(0.5, precision=0, style="prose"), MarkdownStr)
|
||
|
||
|
||
class TestFmtPp:
|
||
def test_prose_default(self):
|
||
assert fmt_pp(7.0, precision=0) == "7 percentage points"
|
||
|
||
def test_symbol_style(self):
|
||
assert fmt_pp(7.0, precision=0, style="symbol") == "7 pp"
|
||
|
||
def test_not_multiplied_by_100(self):
|
||
# pp is already on the 0-100 point scale; 7 stays 7, not 700.
|
||
assert fmt_pp(7.0, precision=0).startswith("7 ")
|
||
|
||
def test_returns_markdown_str(self):
|
||
assert isinstance(fmt_pp(3.0, precision=0), MarkdownStr)
|
||
|
||
def test_singular_when_value_is_one(self):
|
||
assert fmt_pp(1.0, precision=0) == "1 percentage point"
|
||
|
||
def test_plural_for_fractional_near_one(self):
|
||
assert fmt_pp(0.9, precision=1) == "0.9 percentage points"
|
||
assert fmt_pp(1.5, precision=1) == "1.5 percentage points"
|
||
|
||
def test_pluralization_follows_rendered_number(self):
|
||
# any integer != 1 is plural
|
||
assert fmt_pp(2.0, precision=0) == "2 percentage points"
|
||
# a fractional value is always plural, even just below 1
|
||
assert fmt_pp(0.5, precision=1) == "0.5 percentage points"
|
||
|
||
def test_attributive_is_hyphenated_singular_word(self):
|
||
assert fmt_pp(5.0, precision=0, attributive=True) == "5 percentage-point"
|
||
# attributive stays singular/hyphenated regardless of magnitude
|
||
assert fmt_pp(12.0, precision=0, attributive=True) == "12 percentage-point"
|
||
|
||
def test_attributive_rejected_for_symbol_style(self):
|
||
with pytest.raises(ValueError, match="attributive"):
|
||
fmt_pp(5.0, style="symbol", attributive=True)
|
||
|
||
|
||
class TestFmtMultiple:
|
||
def test_owns_times_glyph(self):
|
||
assert fmt_multiple(3.2) == "3.2×"
|
||
assert fmt_multiple(10, precision=0) == "10×"
|
||
|
||
def test_owns_display_markers(self):
|
||
assert fmt_multiple(7.8, precision=1, approx=True) == "~7.8×"
|
||
assert fmt_multiple(10, precision=0, lower_bound=True) == "> 10×"
|
||
assert fmt_multiple(2, precision=0, upper_bound=True) == "< 2×"
|
||
with pytest.raises(ValueError, match="only one display marker"):
|
||
fmt_multiple(2, approx=True, upper_bound=True)
|
||
|
||
def test_inherits_fmt_precision_guard(self):
|
||
# An integer-like factor at precision=1 would render "2.0" — the
|
||
# shared fmt() guard rejects that; explicit precision=1 still errors.
|
||
with pytest.raises(ValueError, match="spurious trailing zeros"):
|
||
fmt_multiple(2.0, precision=1)
|
||
assert fmt_multiple(2.0, precision=0) == "2×"
|
||
assert fmt_multiple(2) == "2×"
|
||
assert fmt_multiple(2.0) == "2×"
|
||
|
||
def test_rejects_negative_factor(self):
|
||
with pytest.raises(ValueError, match="non-negative factor"):
|
||
fmt_multiple(-3)
|
||
|
||
def test_rejects_dimensioned_quantity(self):
|
||
with pytest.raises(ValueError, match="dimensionless scalar"):
|
||
fmt_multiple(3 * GB)
|
||
|
||
def test_returns_markdown_str(self):
|
||
assert isinstance(fmt_multiple(2.5), MarkdownStr)
|
||
|
||
|
||
class TestFmtSpecificHeat:
|
||
def test_uses_textbook_unit_label(self):
|
||
assert fmt_specific_heat(1005 * J / (kg * K), precision=0, commas=True) == "1,005 J/kg/K"
|
||
|
||
def test_rejects_wrong_dimension(self):
|
||
with pytest.raises(ValueError, match="energy/mass/temperature"):
|
||
fmt_specific_heat(1 * J, precision=0)
|
||
|
||
|
||
class TestFmtTimeAutoPrecision:
|
||
def test_auto_precision_trims_integer_like_display(self):
|
||
assert fmt_time(1.0 * second, second, precision=None, commas=False) == "1 s"
|
||
assert fmt_time(37.0 * second, second, precision=None, commas=False) == "37 s"
|
||
|
||
def test_auto_precision_preserves_sub_unit_fraction(self):
|
||
assert fmt_time(0.98 * second, second, precision=None, commas=False) == "0.98 s"
|
||
|
||
|
||
class TestFiniteGuard:
|
||
"""The divide-by-zero / non-finite last line of defense, on every helper."""
|
||
|
||
INF = float("inf")
|
||
NAN = float("nan")
|
||
|
||
def test_fmt_rejects_inf_and_nan(self):
|
||
with pytest.raises(ValueError, match="Non-finite"):
|
||
fmt(self.INF, precision=1)
|
||
with pytest.raises(ValueError, match="Non-finite"):
|
||
fmt(self.NAN, precision=1)
|
||
|
||
def test_message_names_divide_by_zero(self):
|
||
with pytest.raises(ValueError, match="divide-by-zero"):
|
||
fmt(self.INF, precision=0)
|
||
|
||
def test_guard_on_every_numeric_helper(self):
|
||
for call in (
|
||
lambda: fmt_int(self.INF),
|
||
lambda: fmt_usd(self.INF),
|
||
lambda: fmt_percent(self.NAN),
|
||
lambda: fmt_multiple(self.INF),
|
||
lambda: fmt_count(self.INF),
|
||
lambda: fmt_ratio(self.INF),
|
||
lambda: fmt_val(self.NAN),
|
||
lambda: fmt_sci(self.INF),
|
||
lambda: fmt_qty(self.INF * ureg.millisecond, ureg.millisecond),
|
||
lambda: fmt_time(self.INF, ureg.second),
|
||
lambda: fmt_rate(self.INF, "QPS"),
|
||
):
|
||
with pytest.raises(ValueError, match="Non-finite"):
|
||
call()
|
||
|
||
def test_finite_values_pass(self):
|
||
assert fmt(3.0, precision=0) == "3"
|
||
assert fmt_ratio(5.3, precision=1) == "5.3"
|
||
|
||
|
||
class TestFmtRatio:
|
||
def test_bare_number_no_decoration(self):
|
||
assert fmt_ratio(5.3, precision=1) == "5.3"
|
||
assert fmt_ratio(3.2) == "3.2"
|
||
assert fmt_ratio(5.0, precision=0) == "5"
|
||
|
||
def test_rejects_negative_by_default(self):
|
||
with pytest.raises(ValueError, match="non-negative ratio"):
|
||
fmt_ratio(-2.0)
|
||
|
||
def test_allows_signed_ratio_with_flag(self):
|
||
assert fmt_ratio(-2.0, precision=0, allow_negative=True) == "-2"
|
||
|
||
def test_rejects_dimensioned_quantity(self):
|
||
with pytest.raises(ValueError, match="dimensionless scalar"):
|
||
fmt_ratio(5 * GB)
|
||
|
||
def test_returns_markdown_str(self):
|
||
assert isinstance(fmt_ratio(1.5), MarkdownStr)
|
||
|
||
|
||
class TestFmtCount:
|
||
def test_no_scale_uses_commas(self):
|
||
assert fmt_count(8192) == "8,192"
|
||
assert fmt_count(1024, suffix=" GPUs") == "1,024 GPUs"
|
||
|
||
def test_structured_label_pluralizes_from_raw_count(self):
|
||
assert fmt_count(1, label="GPU") == "1 GPU"
|
||
assert fmt_count(2, label="GPU") == "2 GPUs"
|
||
assert fmt_count(1024, label="GPU") == "1,024 GPUs"
|
||
assert fmt_count(2, label="query") == "2 queries"
|
||
assert fmt_count(2, label="batch", plural_label="batches") == "2 batches"
|
||
|
||
def test_scale_glyphs(self):
|
||
assert fmt_count(5_000_000, scale="M") == "5M"
|
||
assert fmt_count(5_300_000, scale="M", precision=1) == "5.3M"
|
||
assert fmt_count(70e9, scale="B") == "70B"
|
||
assert fmt_count(70e9, scale="B", label="parameter") == "70B parameters"
|
||
|
||
def test_scale_words(self):
|
||
assert fmt_count(1_000_000, scale="M", scale_style="word") == "1 million"
|
||
assert fmt_count(1_000_000, scale="million") == "1 million"
|
||
assert fmt_count(1_000_000, scale="Million") == "1 Million"
|
||
assert (
|
||
fmt_count(
|
||
60_000_000,
|
||
scale="million",
|
||
label="parameter",
|
||
)
|
||
== "60 million parameters"
|
||
)
|
||
assert (
|
||
fmt_count(
|
||
70_000_000_000,
|
||
scale="billion",
|
||
precision=0,
|
||
commas=False,
|
||
label="parameter",
|
||
)
|
||
== "70 billion parameters"
|
||
)
|
||
|
||
def test_scale_word_attributive_modifier(self):
|
||
assert (
|
||
fmt_count(
|
||
7_000_000_000,
|
||
scale="billion",
|
||
precision=0,
|
||
commas=False,
|
||
attributive=True,
|
||
)
|
||
== "7-billion"
|
||
)
|
||
with pytest.raises(ValueError, match="requires scale"):
|
||
fmt_count(7, attributive=True)
|
||
with pytest.raises(ValueError, match="only the scaled modifier"):
|
||
fmt_count(
|
||
7_000_000_000,
|
||
scale="billion",
|
||
label="parameter",
|
||
attributive=True,
|
||
)
|
||
|
||
def test_scale_inherits_precision_guard(self):
|
||
# 5.3M at precision=0 would silently hide the .3 — guard refuses.
|
||
with pytest.raises(ValueError, match="not integer-like"):
|
||
fmt_count(5_300_000, scale="M", precision=0)
|
||
|
||
def test_rejects_negative_count(self):
|
||
with pytest.raises(ValueError, match="non-negative count"):
|
||
fmt_count(-5)
|
||
|
||
def test_rejects_fractional_count_by_default(self):
|
||
with pytest.raises(ValueError, match="whole-number count"):
|
||
fmt_count(1.5, label="GPU", precision=1)
|
||
assert fmt_count(1.5, label="GPU", precision=1, allow_fractional=True) == "1.5 GPUs"
|
||
|
||
def test_rejects_unknown_scale(self):
|
||
with pytest.raises(ValueError, match="scale must be"):
|
||
fmt_count(1000, scale="G")
|
||
|
||
def test_rejects_unknown_scale_style(self):
|
||
with pytest.raises(ValueError, match="scale_style"):
|
||
fmt_count(1000, scale="K", scale_style="long")
|
||
with pytest.raises(ValueError, match="requires scale"):
|
||
fmt_count(1000, scale_style="word")
|
||
|
||
def test_rejects_label_suffix_conflicts_and_unit_like_labels(self):
|
||
with pytest.raises(ValueError, match="structured label"):
|
||
fmt_count(2, label="GPU", suffix=" GPUs")
|
||
with pytest.raises(ValueError, match="looks like a unit"):
|
||
fmt_count(2, label="QPS")
|
||
|
||
def test_returns_markdown_str(self):
|
||
assert isinstance(fmt_count(1000, scale="K"), MarkdownStr)
|
||
|
||
|
||
class TestFmtRange:
|
||
def test_uses_en_dash_not_hyphen(self):
|
||
out = fmt_range(5, 10, precision=0)
|
||
assert out == "5\u201310"
|
||
assert "-" not in out # never an ASCII hyphen
|
||
|
||
def test_endpoints_written_in_full(self):
|
||
# MIT: 1992-1993, never 1992-93
|
||
assert fmt_range(1992, 1993, precision=0, commas=False) == "1992\u20131993"
|
||
|
||
def test_unit_appended_once(self):
|
||
assert fmt_range(5, 10, precision=0, unit="GB") == "5\u201310 GB"
|
||
assert fmt_range(2, 4, precision=0, unit="percent") == "2\u20134 percent"
|
||
|
||
def test_usd_kind_each_endpoint_carries_dollar(self):
|
||
assert fmt_range(0.10, 0.50, kind="usd", precision=2) == "\\$0.10\u2013\\$0.50"
|
||
|
||
def test_rejects_inverted_range(self):
|
||
with pytest.raises(ValueError, match="hi >= lo"):
|
||
fmt_range(10, 5, precision=0)
|
||
|
||
def test_rejects_non_finite_endpoint(self):
|
||
with pytest.raises(ValueError, match="finite|infinite"):
|
||
fmt_range(float("inf"), 5)
|
||
|
||
def test_rejects_unknown_kind(self):
|
||
with pytest.raises(ValueError, match="kind must be"):
|
||
fmt_range(5, 10, precision=0, kind="percent")
|
||
|
||
def test_returns_markdown_str(self):
|
||
assert isinstance(fmt_range(5, 10, precision=0), MarkdownStr)
|
||
|
||
|
||
class TestTypedRanges:
|
||
def test_quantity_range_appends_unit_once(self):
|
||
out = fmt_qty_range(
|
||
1 * ureg.GB,
|
||
2 * ureg.GB,
|
||
ureg.GB,
|
||
precision=0,
|
||
commas=False,
|
||
)
|
||
assert out == "1\u20132 GB"
|
||
assert (
|
||
fmt_qty_range(1 * ureg.GB, 2 * ureg.GB, unit=ureg.GB, precision=0, commas=False)
|
||
== "1\u20132 GB"
|
||
)
|
||
|
||
def test_quantity_range_accepts_checked_display_label(self):
|
||
out = fmt_qty_range(
|
||
1 * ureg.kilojoule,
|
||
2 * ureg.kilojoule,
|
||
ureg.kilojoule,
|
||
precision=0,
|
||
commas=False,
|
||
unit_label="kJ per hour",
|
||
)
|
||
assert out == "1\u20132 kJ per hour"
|
||
|
||
def test_percent_range_uses_ratio_domain_and_one_suffix(self):
|
||
assert (
|
||
fmt_percent_range(
|
||
0.846,
|
||
0.88,
|
||
precision=(1, 0),
|
||
commas=False,
|
||
style="prose",
|
||
)
|
||
== "84.6\u201388 percent"
|
||
)
|
||
assert (
|
||
fmt_percent_range(0.90, 0.95, precision=0, commas=False, style="symbol")
|
||
== "90\u201395%"
|
||
)
|
||
with pytest.raises(ValueError, match="0-1 ratio"):
|
||
fmt_percent_range(84.6, 88, precision=1)
|
||
|
||
def test_multiple_range_owns_times_glyph_and_is_checked(self):
|
||
assert fmt_multiple_range(3, 7.8, precision=(0, 1), commas=False) == "3\u20137.8×"
|
||
with pytest.raises(ValueError, match="non-negative"):
|
||
fmt_multiple_range(-1, 2)
|
||
with pytest.raises(ValueError, match="hi >= lo"):
|
||
fmt_multiple_range(3, 2)
|
||
|
||
def test_time_range_symbol_and_word_styles(self):
|
||
assert fmt_time_range(5, 20, ureg.millisecond, precision=0, commas=False) == "5\u201320 ms"
|
||
assert fmt_time_range(5, 20, ureg.millisecond, commas=False) == "5\u201320 ms"
|
||
assert fmt_time_range(5, 20, unit=ureg.millisecond, commas=False) == "5\u201320 ms"
|
||
assert (
|
||
fmt_time_range(1, 2, ureg.second, precision=0, style="word", commas=False)
|
||
== "1\u20132 seconds"
|
||
)
|
||
|
||
def test_count_range_pluralizes_from_raw_count(self):
|
||
assert fmt_count_range(1, 2, label="GPU", commas=False) == "1\u20132 GPUs"
|
||
assert (
|
||
fmt_count_range(
|
||
1_000_000,
|
||
2_000_000,
|
||
scale="M",
|
||
label="parameter",
|
||
commas=False,
|
||
)
|
||
== "1M\u20132M parameters"
|
||
)
|
||
assert (
|
||
fmt_count_range(
|
||
1_000_000,
|
||
2_000_000,
|
||
scale="million",
|
||
label="parameter",
|
||
commas=False,
|
||
)
|
||
== "1 million\u20132 million parameters"
|
||
)
|
||
|
||
def test_usd_range_supports_scale_and_denominator(self):
|
||
assert fmt_usd_range(10_000, 30_000, scale="K", commas=False) == "\\$10K\u2013\\$30K"
|
||
assert (
|
||
fmt_usd_range(25000, 30000, approx=True, repeat_symbol=False)
|
||
== "~\\$25,000\u201330,000"
|
||
)
|
||
assert (
|
||
fmt_usd_range(0.10, 0.50, precision=2, commas=False, per="GB")
|
||
== "\\$0.10\u2013\\$0.50/GB"
|
||
)
|
||
|
||
def test_ranges_reject_inverted_endpoints(self):
|
||
with pytest.raises(ValueError, match="hi >= lo"):
|
||
fmt_qty_range(2 * ureg.GB, 1 * ureg.GB, ureg.GB, precision=0)
|
||
with pytest.raises(ValueError, match="hi >= lo"):
|
||
fmt_count_range(2, 1)
|
||
with pytest.raises(ValueError, match="hi >= lo"):
|
||
fmt_usd_range(2, 1)
|
||
|
||
|
||
class TestFmtSciQty:
|
||
def test_scientific_quantity_appends_checked_unit(self):
|
||
out = fmt_sci_qty(
|
||
4.1e17 * ureg.flop,
|
||
ureg.flop,
|
||
precision=2,
|
||
unit_label="FLOPs",
|
||
)
|
||
assert out == "4.10 × 10¹⁷ FLOPs"
|
||
assert isinstance(out, MarkdownStr)
|
||
assert (
|
||
fmt_sci_qty(4.1e17 * ureg.flop, unit=ureg.flop, precision=2, unit_label="FLOPs")
|
||
== "4.10 × 10¹⁷ FLOPs"
|
||
)
|
||
|
||
def test_scientific_quantity_converts_before_formatting(self):
|
||
out = fmt_sci_qty(
|
||
4.1e8 * ureg.GFLOPs,
|
||
ureg.flop,
|
||
precision=2,
|
||
unit_label="FLOPs",
|
||
)
|
||
assert out == "4.10 × 10¹⁷ FLOPs"
|
||
|
||
def test_scientific_quantity_requires_quantity(self):
|
||
with pytest.raises(TypeError, match="requires a Pint Quantity"):
|
||
fmt_sci_qty(4.1e17, ureg.flop)
|
||
|
||
|
||
class TestDomainFormatters:
|
||
def test_fmt_power_scales_to_mw(self):
|
||
from mlsysim.fmt import fmt_power
|
||
from mlsysim.core.units import watt
|
||
|
||
out = fmt_power(17.5e6 * watt, precision=1, commas=False)
|
||
assert out == "17.5 MW"
|
||
|
||
def test_fmt_power_scales_to_gw(self):
|
||
from mlsysim.fmt import fmt_power
|
||
from mlsysim.core.units import GW
|
||
|
||
out = fmt_power(2.6 * GW, precision=1, commas=False)
|
||
assert out == "2.6 GW"
|
||
|
||
def test_fmt_energy_scales_to_kwh(self):
|
||
from mlsysim.fmt import fmt_energy
|
||
from mlsysim.core.units import kWh, watt, hour
|
||
|
||
out = fmt_energy(700 * watt * hour, precision=0, commas=False)
|
||
assert out == "700 Wh"
|
||
|
||
def test_fmt_bandwidth_scales(self):
|
||
from mlsysim.fmt import fmt_bandwidth
|
||
from mlsysim.core.units import GB, TB, second
|
||
|
||
out = fmt_bandwidth(3.35 * TB / second, precision=2, commas=False)
|
||
assert out == "3.35 TB/s"
|
||
|
||
def test_fmt_area_owns_square_unit_label(self):
|
||
assert (
|
||
fmt_area(814 * ureg.millimeter**2, unit=ureg.millimeter**2, commas=False)
|
||
== "814 mm²"
|
||
)
|
||
with pytest.raises(ValueError, match="area unit"):
|
||
fmt_area(814 * ureg.millimeter**2, unit=ureg.watt)
|
||
|
||
def test_fmt_heat_flux_owns_power_density_label(self):
|
||
assert (
|
||
fmt_heat_flux(
|
||
86.0 * ureg.watt / (ureg.centimeter**2),
|
||
unit=ureg.watt / (ureg.centimeter**2),
|
||
commas=False,
|
||
)
|
||
== "86 W/cm²"
|
||
)
|
||
with pytest.raises(ValueError, match="power/area"):
|
||
fmt_heat_flux(86 * ureg.watt / (ureg.centimeter**2), unit=ureg.watt)
|
||
|
||
def test_fmt_params_auto_precision(self):
|
||
from mlsysim.core.units import Bparam, Mparam
|
||
|
||
assert fmt_params(175 * Bparam) == "175B"
|
||
assert fmt_params(1.5 * Bparam) == "1.5B"
|
||
assert fmt_params(150 * Mparam) == "150M"
|
||
assert fmt_params(150 * Mparam, scale="B") == "0.15B"
|
||
|
||
def test_fmt_tokens_scales_checked_counts(self):
|
||
from mlsysim.core.units import count
|
||
|
||
assert fmt_tokens(300e9 * count, scale="B", commas=False) == "300B"
|
||
assert fmt_tokens(1.5e12 * count, scale="T", commas=False) == "1.5T"
|
||
|
||
def test_fmt_flop_rate_scales_to_tflop_per_second(self):
|
||
from mlsysim.core.units import TFLOP, second
|
||
|
||
out = fmt_flop_rate(989 * TFLOP / second, precision=0, commas=False)
|
||
assert out == "989 TFLOP/s"
|
||
|
||
def test_fmt_flop_rate_scales_to_pflop_per_second(self):
|
||
from mlsysim.core.units import PFLOP, TFLOP, second
|
||
|
||
out = fmt_flop_rate(1_979 * TFLOP / second, precision=3, commas=False)
|
||
assert out == "1.979 PFLOP/s"
|
||
assert fmt_flop_rate(2 * PFLOP / second, precision=0, commas=False) == "2 PFLOP/s"
|
||
|
||
def test_fmt_flop_rate_requires_quantity(self):
|
||
with pytest.raises(TypeError, match="requires a Pint Quantity"):
|
||
fmt_flop_rate(989)
|
||
|
||
def test_fmt_flops_scales_to_canonical_singular_units(self):
|
||
from mlsysim.core.units import GFLOP, MFLOP, TFLOP
|
||
|
||
assert fmt_flops(569 * MFLOP, precision=0, commas=False) == "569 MFLOP"
|
||
assert fmt_flops(4.1 * GFLOP, precision=1, commas=False) == "4.1 GFLOP"
|
||
assert fmt_flops(350 * TFLOP, precision=0, commas=False) == "350 TFLOP"
|
||
assert (
|
||
fmt_flops(4.1 * GFLOP, precision=1, commas=False, per="inference")
|
||
== "4.1 GFLOP/inference"
|
||
)
|
||
|
||
def test_fmt_sci_flops_owns_plural_scientific_label(self):
|
||
from mlsysim.core.units import GFLOP, flop
|
||
|
||
assert fmt_sci_flops(4.1e17 * flop, precision=2) == "4.10 × 10¹⁷ FLOPs"
|
||
assert fmt_sci_flops(4.1e8 * GFLOP, precision=2) == "4.10 × 10¹⁷ FLOPs"
|
||
|
||
def test_fmt_flops_requires_quantity(self):
|
||
with pytest.raises(TypeError, match="requires a Pint Quantity"):
|
||
fmt_flops(4.1e9)
|
||
with pytest.raises(TypeError, match="requires a Pint Quantity"):
|
||
fmt_sci_flops(4.1e9)
|
||
|
||
def test_fmt_arithmetic_intensity_owns_flop_per_byte_label(self):
|
||
from mlsysim.core.units import GB, TFLOP, byte, flop
|
||
|
||
assert fmt_arithmetic_intensity(5 * flop / byte, precision=0, commas=False) == "5 FLOP/byte"
|
||
assert (
|
||
fmt_arithmetic_intensity(1 * TFLOP / GB, precision=0, commas=False) == "1000 FLOP/byte"
|
||
)
|
||
|
||
def test_fmt_energy_density_helpers_own_readable_labels(self):
|
||
from mlsysim.core.units import bit, byte, flop, pJ
|
||
|
||
assert fmt_energy_per_byte(160 * pJ / byte, precision=0, commas=False) == "160 pJ/byte"
|
||
assert fmt_energy_per_bit(5 * pJ / bit, precision=0, commas=False) == "5 pJ/bit"
|
||
assert fmt_energy_per_flop(10 * pJ / flop, precision=0, commas=False) == "10 pJ/FLOP"
|
||
assert fmt_energy_per_op(0.9 * pJ / flop, commas=False) == "0.9 pJ/op"
|
||
assert fmt_energy_per_op(0.03 * pJ / flop, commas=False) == "0.03 pJ/op"
|
||
assert fmt_energy_per_op(0.03 * pJ / flop, precision=2, commas=False) == "0.03 pJ/op"
|
||
assert fmt_qty(160 * pJ / byte, pJ / byte, precision=0, commas=False) == "160 pJ/byte"
|
||
|
||
def test_fmt_energy_density_helpers_require_quantities(self):
|
||
with pytest.raises(TypeError, match="requires a Pint Quantity"):
|
||
fmt_energy_per_byte(160)
|
||
with pytest.raises(TypeError, match="requires a Pint Quantity"):
|
||
fmt_energy_per_bit(5)
|
||
with pytest.raises(TypeError, match="requires a Pint Quantity"):
|
||
fmt_energy_per_flop(10)
|
||
with pytest.raises(TypeError, match="requires a Pint Quantity"):
|
||
fmt_energy_per_op(10)
|
||
with pytest.raises(ValueError, match="energy/operation"):
|
||
fmt_energy_per_op(10 * ureg.picojoule, unit=ureg.picojoule)
|
||
with pytest.raises(ValueError, match="non-negative energy"):
|
||
fmt_energy_per_op(-1 * ureg.picojoule / ureg.flop)
|
||
|
||
def test_fmt_ops_rate_scales_integer_ops(self):
|
||
from mlsysim.core.units import TOPS
|
||
|
||
assert fmt_ops_rate(2 * TOPS, precision=0, commas=False) == "2 TOPS"
|
||
assert fmt_ops_rate(0.002 * TOPS, precision=0, commas=False) == "2 GOPS"
|
||
|
||
def test_fmt_rate_allows_iops(self):
|
||
assert fmt_rate(150, "IOPS", precision=0, commas=False) == "150 IOPS"
|
||
assert fmt_rate(6_000, "IOPS", scale="K", precision=0, commas=False) == "6K IOPS"
|
||
|
||
def test_fmt_rate_allows_macs_per_cycle(self):
|
||
assert fmt_rate(16_384, "MACs/cycle", precision=0, commas=True) == "16,384 MACs/cycle"
|
||
|
||
def test_fmt_rate_allows_event_and_inference_rates(self):
|
||
assert fmt_rate(20, "events/s", precision=0, commas=False) == "20 events/s"
|
||
assert fmt_rate(300, "inferences/s", precision=0, commas=False) == "300 inferences/s"
|
||
|
||
def test_fmt_rate_allows_book_service_rates(self):
|
||
assert fmt_rate(12.5, "failures/day", precision=1, commas=False) == "12.5 failures/day"
|
||
assert fmt_rate(90, "queries/hour", precision=0, commas=False) == "90 queries/hour"
|
||
assert fmt_rate(5000, "queries/class", precision=0, commas=True) == "5,000 queries/class"
|
||
assert fmt_rate(4, "deploys/week", precision=0, commas=False) == "4 deploys/week"
|
||
assert (
|
||
fmt_rate(40, "utterances/speaker", precision=0, commas=False) == "40 utterances/speaker"
|
||
)
|
||
assert fmt_rate(12, "cameras/store", precision=0, commas=False) == "12 cameras/store"
|
||
assert fmt_rate(3, "boards/store", precision=0, commas=False) == "3 boards/store"
|
||
assert fmt_rate(1000, "cases/day", precision=0, commas=True) == "1,000 cases/day"
|
||
assert fmt_rate(120, "km/h", precision=0, commas=False) == "120 km/h"
|
||
|
||
def test_fmt_named_markers_include_upper_bound(self):
|
||
from mlsysim.core.units import GB, TOPS, TFLOP, milliwatt, second
|
||
|
||
assert fmt(100, precision=0, lower_bound=True, suffix=" MB/s") == "> 100 MB/s"
|
||
assert fmt(100, precision=0, upper_bound=True, suffix=" MB/s") == "< 100 MB/s"
|
||
assert (
|
||
fmt_flop_rate(
|
||
100 * TFLOP / second,
|
||
unit=TFLOP / second,
|
||
precision=0,
|
||
commas=False,
|
||
lower_bound=True,
|
||
)
|
||
== "> 100 TFLOP/s"
|
||
)
|
||
assert (
|
||
fmt_bandwidth(
|
||
500 * GB / second,
|
||
unit=GB / second,
|
||
precision=0,
|
||
commas=False,
|
||
lower_bound=True,
|
||
)
|
||
== "> 500 GB/s"
|
||
)
|
||
assert (
|
||
fmt_ops_rate(1 * TOPS, unit=TOPS, precision=0, commas=False, upper_bound=True)
|
||
== "< 1 TOPS"
|
||
)
|
||
assert (
|
||
fmt_power(10 * milliwatt, unit=milliwatt, precision=0, commas=False, upper_bound=True)
|
||
== "< 10 mW"
|
||
)
|
||
with pytest.raises(ValueError, match="only one display marker"):
|
||
fmt(100, precision=0, approx=True, upper_bound=True)
|
||
|
||
def test_small_physical_label_helpers(self):
|
||
from mlsysim.core.units import second
|
||
|
||
assert fmt_decibel(ureg.Quantity(20, ureg.decibel), precision=0, commas=False) == "20 dB"
|
||
assert fmt_illuminance(100 * ureg.lux, precision=0, commas=False) == "100 lux"
|
||
assert fmt_temperature(ureg.Quantity(80, ureg.degC), precision=0, commas=False) == "80 °C"
|
||
assert (
|
||
fmt_temperature_rate(
|
||
1 * ureg.delta_degC / second,
|
||
precision=0,
|
||
commas=False,
|
||
)
|
||
== "1 °C/s"
|
||
)
|
||
|
||
def test_fmt_compute_efficiency_renders_full_unit(self):
|
||
from mlsysim.core.units import TFLOP, second, watt
|
||
|
||
out = fmt_compute_efficiency(
|
||
(989 * TFLOP / second) / (700 * watt),
|
||
precision=2,
|
||
commas=False,
|
||
)
|
||
assert out == "1.41 TFLOP/s/W"
|
||
|
||
def test_fmt_memory_scales(self):
|
||
from mlsysim.fmt import fmt_memory
|
||
from mlsysim.core.units import GB, byte
|
||
|
||
out = fmt_memory(14 * GB, precision=0, commas=False)
|
||
assert out == "14 GB"
|
||
assert fmt_memory(4 * byte, unit=byte, precision=0, commas=False) == "4 bytes"
|
||
|
||
def test_fmt_memory_capacity_preserves_binary_magnitude_with_vendor_label(self):
|
||
from mlsysim.core.units import GB, GiB, KiB, MiB
|
||
from mlsysim.fmt import fmt_memory
|
||
|
||
assert fmt_memory_capacity(80 * GiB, unit=GiB, precision=0, commas=False) == "80 GB"
|
||
assert fmt_memory_capacity(33 * MiB, unit=MiB, precision=0, commas=False) == "33 MB"
|
||
assert fmt_memory_capacity(256 * KiB, unit=KiB, precision=0, commas=False) == "256 KB"
|
||
assert fmt_memory(80 * GiB, unit=GB, precision=1, commas=False) == "85.9 GB"
|
||
|
||
def test_fmt_carbon_intensity_defaults_to_grams_per_kwh(self):
|
||
from mlsysim.core.units import gram, kilogram, kWh
|
||
|
||
assert fmt_carbon_intensity(429 * gram / kWh, precision=0, commas=False) == "429 g/kWh"
|
||
assert (
|
||
fmt_carbon_intensity(0.429 * kilogram / kWh, precision=0, commas=False) == "429 g/kWh"
|
||
)
|
||
|
||
def test_fmt_emissions_supports_display_markers(self):
|
||
from mlsysim.core.units import kilogram, metric_ton
|
||
|
||
assert (
|
||
fmt_emissions(
|
||
85_000 * kilogram,
|
||
unit=metric_ton,
|
||
precision=0,
|
||
commas=False,
|
||
approx=True,
|
||
)
|
||
== "~85 t"
|
||
)
|
||
|
||
def test_fmt_water_helpers_use_book_liter_labels(self):
|
||
from mlsysim.core.units import L, day, hour, kWh
|
||
|
||
assert fmt_water(18_000_000 * L, precision=0) == "18,000,000 L"
|
||
assert fmt_water_rate(12_000 * L / hour, unit=L / hour, precision=0) == "12,000 L/h"
|
||
assert fmt_water_rate(12_000 * L / hour, unit=L / day, precision=0) == "288,000 L/day"
|
||
assert fmt_water_intensity(1.8 * L / kWh, precision=1) == "1.8 L/kWh"
|
||
|
||
def test_fmt_water_helpers_reject_wrong_dimensions(self):
|
||
from mlsysim.core.units import L, watt
|
||
|
||
with pytest.raises(ValueError, match="volume unit"):
|
||
fmt_water(1 * L, unit=watt)
|
||
with pytest.raises(ValueError, match="volume/time"):
|
||
fmt_water_rate(1 * L, unit=L)
|
||
with pytest.raises(ValueError, match="volume/energy"):
|
||
fmt_water_intensity(1 * L, unit=L)
|
||
|
||
def test_usd_range_repeat_symbol_false_with_scale(self):
|
||
assert (
|
||
fmt_usd_range(10_000, 30_000, scale="K", commas=False, repeat_symbol=False)
|
||
== "\\$10K\u201330K"
|
||
)
|
||
|
||
|
||
class TestAuditRepairs:
|
||
"""Focused regressions for the 2026-06 verified audit findings.
|
||
|
||
Five findings: fmt_int silent zero + banker's rounding, the auto-scale
|
||
1000-mantissa boundary, fmt_unit/fmt_qty label divergence, fmt_pp
|
||
singular grammar on negatives, and fmt_usd negative sign placement.
|
||
"""
|
||
|
||
# --- Finding 1: fmt_int silent zero + round-half-away-from-zero --------
|
||
|
||
def test_fmt_int_rejects_nonzero_values_that_round_to_zero(self):
|
||
with pytest.raises(ValueError, match="rounds to '0'"):
|
||
fmt_int(0.4)
|
||
with pytest.raises(ValueError, match="rounds to '0'"):
|
||
fmt_int(-0.4)
|
||
with pytest.raises(ValueError, match="rounds to '0'"):
|
||
fmt_int(0.49999)
|
||
|
||
def test_fmt_int_true_zero_still_renders(self):
|
||
assert fmt_int(0) == "0"
|
||
assert fmt_int(0.0) == "0"
|
||
|
||
def test_fmt_int_rounds_half_away_from_zero(self):
|
||
# Python round() is banker's (2.5 -> 2, 3.5 -> 4); fmt_int must round
|
||
# adjacent halves consistently away from zero.
|
||
assert fmt_int(2.5, commas=False) == "3"
|
||
assert fmt_int(3.5, commas=False) == "4"
|
||
assert fmt_int(-2.5, commas=False) == "-3"
|
||
assert fmt_int(-3.5, commas=False) == "-4"
|
||
assert fmt_int(0.5, commas=False) == "1"
|
||
assert fmt_int(-0.5, commas=False) == "-1"
|
||
|
||
def test_fmt_int_still_absorbs_float_noise(self):
|
||
assert fmt_int(7.9999999999, commas=False) == "8"
|
||
assert fmt_int(362507.545, commas=True) == "362,508"
|
||
|
||
def test_fmt_int_keeps_commas_and_markers(self):
|
||
assert fmt_int(1500.5, commas=True) == "1,501"
|
||
assert fmt_int(80.2, commas=False, approx=True, suffix=" GB") == "~80 GB"
|
||
|
||
# --- Finding 2: auto-scale boundary promotion (M -> B -> T) ------------
|
||
|
||
def test_auto_scale_promotes_rounded_mantissa_at_boundary(self):
|
||
# 999,999,999 picks "M" from the raw value but ROUNDS to a 1000.0
|
||
# mantissa at display precision; it must promote to "1B".
|
||
assert fmt_params(999_999_999) == "1B"
|
||
assert fmt_params(999_999_999_999) == "1T"
|
||
|
||
def test_fmt_tokens_auto_scale_boundary(self):
|
||
from mlsysim.core.units import count
|
||
|
||
assert fmt_tokens(999_999_999 * count, scale="auto") == "1B"
|
||
|
||
def test_auto_scale_exact_boundaries_unchanged(self):
|
||
assert fmt_params(1_000_000_000) == "1B"
|
||
assert fmt_params(1_000_000_000_000) == "1T"
|
||
|
||
def test_auto_scale_below_boundary_does_not_promote(self):
|
||
assert fmt_params(999_400_000_000) == "999.4B"
|
||
|
||
def test_auto_scale_top_scale_keeps_value_with_commas(self):
|
||
# No scale above T: keep the T mantissa and group digits instead.
|
||
assert fmt_params(2_000_000_000_000_000) == "2,000T"
|
||
|
||
# --- Finding 3: fmt_unit routed through shared normalization -----------
|
||
|
||
def test_fmt_unit_normalizes_all_flops_rate_labels(self):
|
||
from mlsysim.core.units import Q_
|
||
|
||
assert fmt_unit(Q_(1, "EFLOPs/second")) == "EFLOP/s"
|
||
assert fmt_unit(Q_(1, "KFLOPs/second")) == "KFLOP/s"
|
||
assert fmt_unit(Q_(1, "TFLOPs/second")) == "TFLOP/s"
|
||
|
||
def test_fmt_unit_normalizes_bare_work_units(self):
|
||
from mlsysim.core.units import Q_
|
||
|
||
assert fmt_unit(Q_(1, "TFLOPs")) == "TFLOP"
|
||
assert fmt_unit(Q_(1, "EFLOPs")) == "EFLOP"
|
||
|
||
def test_fmt_unit_matches_fmt_qty_suffix_exactly(self):
|
||
from mlsysim.core.units import Q_
|
||
|
||
q = Q_(2, "EFLOPs/second")
|
||
qty_suffix = str(fmt_qty(q, q.units, precision=0, commas=False)).split(" ", 1)[1]
|
||
assert str(fmt_unit(q)) == qty_suffix
|
||
|
||
def test_fmt_unit_default_and_plain_units(self):
|
||
from mlsysim.core.units import Q_
|
||
|
||
assert fmt_unit(80) == "-"
|
||
assert fmt_unit(Q_(1, "GB")) == "GB"
|
||
assert isinstance(fmt_unit(Q_(1, "GB")), MarkdownStr)
|
||
|
||
# --- Finding 4: fmt_pp singular grammar on negatives -------------------
|
||
|
||
def test_fmt_pp_negative_one_is_singular(self):
|
||
assert fmt_pp(-1) == "-1 percentage point"
|
||
assert fmt_pp(-1.0, precision=0) == "-1 percentage point"
|
||
|
||
def test_fmt_pp_singular_plural_agreement_unchanged(self):
|
||
assert fmt_pp(1.0, precision=0) == "1 percentage point"
|
||
assert fmt_pp(-2.0, precision=0) == "-2 percentage points"
|
||
assert fmt_pp(7.0) == "7 percentage points"
|
||
assert fmt_pp(0.9, precision=1) == "0.9 percentage points"
|
||
|
||
# --- Finding 5: fmt_usd negative sign-first rendering -------------------
|
||
|
||
def test_fmt_usd_negative_renders_sign_first(self):
|
||
assert fmt_usd(-500) == "-\\$500"
|
||
assert fmt_usd(-12345.6) == "-\\$12,346"
|
||
|
||
def test_fmt_usd_negative_with_approx(self):
|
||
assert fmt_usd(-500, approx=True) == "~-\\$500"
|
||
|
||
def test_fmt_usd_negative_with_scale_and_per(self):
|
||
assert fmt_usd(-4_600_000, precision=1, scale="M") == "-\\$4.6M"
|
||
assert fmt_usd(-0.09, precision=2, commas=False, per="GB") == "-\\$0.09/GB"
|
||
assert fmt_usd(-12_000, commas=False, scale="K", per="year") == "-\\$12K/year"
|
||
|
||
def test_fmt_usd_positive_unchanged(self):
|
||
assert fmt_usd(15000) == "\\$15,000"
|
||
assert fmt_usd(8000, approx=True, marker="*") == "~\\$8,000*"
|
||
|
||
|
||
class TestFmtLength:
|
||
def test_fmt_length_meters_small_no_commas(self):
|
||
from mlsysim.core.units import meter
|
||
|
||
assert fmt_length(2.8 * meter, unit=meter, precision=1) == "2.8 m"
|
||
|
||
def test_fmt_length_kilometers_commas(self):
|
||
from mlsysim.core.units import kilometer
|
||
|
||
assert fmt_length(3600 * kilometer, unit=kilometer, precision=0) == "3,600 km"
|
||
|
||
def test_fmt_rate_kmh(self):
|
||
assert fmt_rate(120, "km/h", precision=0, commas=False) == "120 km/h"
|