use crate::ledger::{Ledger, Prices};
#[derive(Clone, Copy, Debug)]
pub struct Machine {
pub idle_watts: f64,
pub marginal_watts: f64,
pub rate: f64,
}
#[derive(Clone, Debug, PartialEq)]
pub enum DutyError {
NotPhysical(&'static str),
Empty(&'static str),
CannotSustain {
needs_s: f64,
period_s: f64,
},
PricesUnstated,
StandbyUnpublished,
DeviceCannotSustain {
reflash_s: f64,
period_s: f64,
},
}
impl core::fmt::Display for DutyError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
DutyError::NotPhysical(s) | DutyError::Empty(s) => write!(f, "{s}"),
DutyError::CannotSustain { needs_s, period_s } => write!(
f,
"that work needs {needs_s:.6} s of computation and the cadence allows {period_s:.6} s, \
so this machine cannot sustain it -- a joules figure here would price a run that \
could not have happened"
),
DutyError::PricesUnstated => write!(
f,
"that device states no per-operation energy, so its computation has no price here. \
Borrowing another device's prices is what produces a figure that looks exactly \
like a real one"
),
DutyError::StandbyUnpublished => write!(
f,
"that device states no standby power, and at anything below full duty cycle standby \
IS the comparison -- so this cannot be answered rather than answered with a guess. \
No thermodynamic vendor publishes the number; supply it and the arithmetic runs"
),
DutyError::DeviceCannotSustain { reflash_s, period_s } => write!(
f,
"reprogramming this device for one period takes at least {reflash_s:.6} s and the \
cadence allows {period_s:.6} s, so it cannot run this loop at ANY energy price. \
The feasibility verdict arrives before the joules"
),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct Verdict {
pub incumbent_joules: f64,
pub challenger_joules: f64,
pub standby_budget: f64,
pub challenger_wins: bool,
pub standby_was_bounded: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Outcome {
ChallengerWins,
IncumbentWins,
Inconclusive,
}
#[derive(Clone, Copy, Debug)]
pub struct DeviceRun {
pub prices: Prices,
pub per_period: Ledger,
pub graph_nodes: u64,
pub standby_watts: Option<f64>,
pub standby_is_upper_bound: bool,
}
impl DeviceRun {
#[must_use]
pub fn with_standby_at_most(mut self, active_watts: f64) -> DeviceRun {
self.standby_watts = Some(active_watts);
self.standby_is_upper_bound = true;
self
}
pub fn compute_joules(&self) -> Result<f64, DutyError> {
self.per_period.joules(&self.prices).ok_or(DutyError::PricesUnstated)
}
pub fn check_sustains(&self, period_s: f64) -> Result<(), DutyError> {
if !period_s.is_finite() || period_s <= 0.0 {
return Err(DutyError::Empty("the period must be a finite positive number of seconds"));
}
match self.per_period.reflash_seconds(&self.prices, self.graph_nodes) {
Some(reflash_s) if reflash_s > period_s => {
Err(DutyError::DeviceCannotSustain { reflash_s, period_s })
}
_ => Ok(()),
}
}
pub fn joules_per_period(&self, period_s: f64) -> Result<f64, DutyError> {
self.check_sustains(period_s)?;
let standby = self.standby_watts.ok_or(DutyError::StandbyUnpublished)?;
if !standby.is_finite() || standby < 0.0 {
return Err(DutyError::NotPhysical("standby power must be finite and non-negative"));
}
Ok(self.compute_joules()? + standby * period_s)
}
}
impl Verdict {
#[must_use]
pub fn outcome(&self) -> Outcome {
match (self.challenger_wins, self.standby_was_bounded) {
(true, _) => Outcome::ChallengerWins,
(false, false) => Outcome::IncumbentWins,
(false, true) => Outcome::Inconclusive,
}
}
}
impl Machine {
pub fn new(idle_watts: f64, marginal_watts: f64, rate: f64) -> Result<Machine, DutyError> {
if !idle_watts.is_finite() || idle_watts < 0.0 {
return Err(DutyError::NotPhysical("idle power must be finite and non-negative"));
}
if !marginal_watts.is_finite() || marginal_watts < 0.0 {
return Err(DutyError::NotPhysical(
"marginal power must be finite and non-negative; a negative delta is the meter's \
noise, not a machine that generates power while it works",
));
}
if !rate.is_finite() || rate <= 0.0 {
return Err(DutyError::NotPhysical("rate must be finite and positive"));
}
Ok(Machine { idle_watts, marginal_watts, rate })
}
pub fn run_seconds(&self, work: u64) -> f64 {
work as f64 / self.rate
}
#[must_use]
pub fn idle_dominant_below(&self) -> f64 {
if self.marginal_watts == 0.0 {
return f64::INFINITY;
}
self.idle_watts / self.marginal_watts
}
pub fn duty(&self, work: u64, period_s: f64) -> Result<f64, DutyError> {
if work == 0 {
return Err(DutyError::Empty("no work was done, so there is no duty cycle"));
}
if !period_s.is_finite() || period_s <= 0.0 {
return Err(DutyError::Empty("the period must be a finite positive number of seconds"));
}
let needs_s = self.run_seconds(work);
if needs_s > period_s {
return Err(DutyError::CannotSustain { needs_s, period_s });
}
Ok(needs_s / period_s)
}
pub fn joules_per_period(&self, work: u64, period_s: f64) -> Result<f64, DutyError> {
self.duty(work, period_s)?;
Ok(self.marginal_watts * self.run_seconds(work) + self.idle_watts * period_s)
}
pub fn joules_per_unit(&self, work: u64, period_s: f64) -> Result<f64, DutyError> {
Ok(self.joules_per_period(work, period_s)? / work as f64)
}
pub fn standby_budget(&self, work: u64, period_s: f64) -> Result<f64, DutyError> {
let d = self.duty(work, period_s)?;
Ok(self.idle_watts + self.marginal_watts * d)
}
pub fn beaten_by(
&self,
challenger_standby_watts: f64,
challenger_compute_joules: f64,
work: u64,
period_s: f64,
) -> Result<Verdict, DutyError> {
if !challenger_standby_watts.is_finite() || challenger_standby_watts < 0.0 {
return Err(DutyError::NotPhysical(
"the challenger's standby power must be finite and non-negative",
));
}
if !challenger_compute_joules.is_finite() || challenger_compute_joules < 0.0 {
return Err(DutyError::NotPhysical(
"the challenger's compute energy must be finite and non-negative",
));
}
let incumbent = self.joules_per_period(work, period_s)?;
let challenger = challenger_compute_joules + challenger_standby_watts * period_s;
Ok(Verdict {
incumbent_joules: incumbent,
challenger_joules: challenger,
standby_budget: self.standby_budget(work, period_s)?,
challenger_wins: challenger < incumbent,
standby_was_bounded: false,
})
}
pub fn beaten_by_device(
&self,
device: &DeviceRun,
work: u64,
period_s: f64,
) -> Result<Verdict, DutyError> {
let incumbent = self.joules_per_period(work, period_s)?;
let challenger = device.joules_per_period(period_s)?;
Ok(Verdict {
incumbent_joules: incumbent,
challenger_joules: challenger,
standby_budget: self.standby_budget(work, period_s)?,
challenger_wins: challenger < incumbent,
standby_was_bounded: device.standby_is_upper_bound,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn gpu() -> Machine {
Machine::new(20.0, 100.0, 1e9).unwrap()
}
#[test]
fn idle_dominates_below_the_ratio_of_the_two_powers() {
let m = gpu();
let d_star = m.idle_dominant_below();
assert!((d_star - 0.2).abs() < 1e-12, "{d_star}");
let work = 1_000_000u64; let t_run = m.run_seconds(work);
for (duty, idle_should_win) in [(0.02, true), (0.5, false)] {
let period = t_run / duty;
let total = m.joules_per_period(work, period).unwrap();
let idle_part = m.idle_watts * period;
assert_eq!(
idle_part > total / 2.0,
idle_should_win,
"at duty {duty} idle share was {:.3}",
idle_part / total
);
}
}
#[test]
fn the_standby_budget_collapses_to_the_incumbents_idle_draw() {
let m = gpu();
let work = 1_000_000u64;
let busy = m.standby_budget(work, m.run_seconds(work)).unwrap(); let rare = m.standby_budget(work, 60.0).unwrap();
assert!((busy - 120.0).abs() < 1e-9, "at full duty the whole draw is the budget: {busy}");
let residual = (rare - m.idle_watts).abs() / m.idle_watts;
assert!(
residual < 1e-4,
"once a minute the budget is the idle draw to within {:.4}%: {rare} vs {}",
100.0 * residual,
m.idle_watts
);
assert!(
rare < busy / 5.0,
"the two regimes have to be far apart or there is nothing to report"
);
}
#[test]
fn free_computation_does_not_win_on_its_own() {
let m = gpu();
let work = 1_000_000u64;
let period = 1.0;
let budget = m.standby_budget(work, period).unwrap();
let incumbent = m.joules_per_period(work, period).unwrap();
let challenger = 0.0 + (2.0 * budget) * period;
assert!(
challenger > incumbent,
"free sampling at {:.1} W standby still costs {challenger:.1} J against {incumbent:.1} J",
2.0 * budget
);
}
#[test]
fn a_cadence_the_machine_cannot_meet_is_refused_rather_than_priced() {
let m = gpu();
let err = m.joules_per_period(10_000_000_000, 1.0).unwrap_err();
match err {
DutyError::CannotSustain { needs_s, period_s } => {
assert!((needs_s - 10.0).abs() < 1e-9 && (period_s - 1.0).abs() < 1e-9);
}
other => panic!("expected a sustainability refusal, got {other:?}"),
}
assert!(m.joules_per_period(10_000_000_000, 11.0).is_ok(), "11 s is enough for 10 s of work");
}
#[test]
fn effective_cost_per_unit_diverges_as_the_cadence_slackens() {
let m = gpu();
let work = 1_000_000u64;
let above_idle_per_unit = m.marginal_watts * m.run_seconds(work) / work as f64;
let busy = m.joules_per_unit(work, m.run_seconds(work)).unwrap();
let rare = m.joules_per_unit(work, 60.0).unwrap();
assert!(rare > busy * 1000.0, "busy {busy:.3e} vs rare {rare:.3e}");
assert!(
above_idle_per_unit < busy,
"subtracting idle always reports less than the machine actually spent"
);
}
#[test]
fn a_challenger_is_judged_on_standby_once_the_cadence_slackens() {
let m = gpu();
let work = 1_000_000u64;
let period = 60.0;
let good = m.beaten_by(m.idle_watts / 4.0, 0.0, work, period).unwrap();
let bad = m.beaten_by(m.idle_watts * 2.0, 0.0, work, period).unwrap();
assert!(good.challenger_wins, "a quarter of the idle draw has to win: {good:?}");
assert!(!bad.challenger_wins, "twice the idle draw cannot win on free compute: {bad:?}");
assert!(m.idle_watts / 4.0 < good.standby_budget);
assert!(m.idle_watts * 2.0 > bad.standby_budget);
}
#[test]
fn free_sampling_cannot_rescue_a_device_that_must_stay_powered() {
let m = gpu();
let (work, period) = (1_000_000u64, 60.0);
let budget = m.standby_budget(work, period).unwrap();
let v = m.beaten_by(budget * 1.001, 0.0, work, period).unwrap();
assert!(
!v.challenger_wins,
"0.1% over the budget with FREE computation still loses: {:.1} J vs {:.1} J",
v.challenger_joules, v.incumbent_joules
);
let v2 = m.beaten_by(budget * 0.999, 0.0, work, period).unwrap();
assert!(v2.challenger_wins, "0.1% under the budget wins: {v2:?}");
}
fn z1_control_loop(nodes: u64) -> DeviceRun {
DeviceRun {
prices: crate::ledger::Z1_SPICE,
per_period: Ledger { samples: nodes * 64, reads: 16, writes: nodes },
graph_nodes: nodes,
standby_watts: None,
standby_is_upper_bound: false,
}
}
#[test]
fn the_real_published_device_model_cannot_be_compared_at_all() {
let m = gpu();
let mut dev = z1_control_loop(1_000);
dev.per_period.writes = 0; assert!(dev.check_sustains(60.0).is_ok(), "no writes means no reflash floor");
assert!(dev.compute_joules().is_ok(), "its computation IS priced");
let err = m.beaten_by_device(&dev, 1_000_000, 60.0).unwrap_err();
assert_eq!(
err,
DutyError::StandbyUnpublished,
"everything about the computation is published and the comparison still cannot run"
);
assert!(format!("{err}").contains("standby"));
}
#[test]
fn feasibility_is_decided_before_energy() {
let m = gpu();
let dev = z1_control_loop(1_000);
match m.beaten_by_device(&dev, 1_000_000, 0.01) {
Err(DutyError::DeviceCannotSustain { reflash_s, period_s }) => {
assert!((reflash_s - 1.0).abs() < 1e-9, "one full reflash at 1 Hz: {reflash_s}");
assert!((period_s - 0.01).abs() < 1e-12);
}
other => panic!("feasibility must be decided first, got {other:?}"),
}
let mut priced = dev;
priced.standby_watts = Some(0.0);
assert!(matches!(
m.beaten_by_device(&priced, 1_000_000, 0.01),
Err(DutyError::DeviceCannotSustain { .. })
));
}
#[test]
fn an_unpriced_device_gets_no_number() {
let m = gpu();
let dev = DeviceRun {
prices: crate::ledger::Prices::UNSTATED,
per_period: Ledger { samples: 1_000, reads: 1, writes: 0 },
graph_nodes: 1_000,
standby_watts: Some(1.0),
standby_is_upper_bound: false,
};
assert_eq!(dev.compute_joules().unwrap_err(), DutyError::PricesUnstated);
assert_eq!(m.beaten_by_device(&dev, 1_000_000, 1.0).unwrap_err(), DutyError::PricesUnstated);
}
#[test]
fn supplying_the_missing_number_makes_the_arithmetic_run() {
let m = gpu();
let mut dev = z1_control_loop(1_000);
dev.per_period.writes = 0;
dev.standby_watts = Some(0.001);
let (work, period) = (1_000_000u64, 60.0);
let v = m.beaten_by_device(&dev, work, period).unwrap();
let loose = m.beaten_by(0.001, dev.compute_joules().unwrap(), work, period).unwrap();
assert!((v.challenger_joules - loose.challenger_joules).abs() < 1e-12);
assert_eq!(v.challenger_wins, loose.challenger_wins);
assert!(v.challenger_wins, "a milliwatt against a {} W idle draw wins", m.idle_watts);
}
#[test]
fn an_active_power_figure_can_stand_in_for_the_standby_nobody_publishes() {
let m = gpu(); let (work, period) = (1_000_000u64, 60.0);
let mut dev = z1_control_loop(1_000);
dev.per_period.writes = 0;
let v = m.beaten_by_device(&dev.with_standby_at_most(1.0), work, period).unwrap();
assert_eq!(
v.outcome(),
Outcome::ChallengerWins,
"1 W against a 20 W idle draw: {:.2} J vs {:.2} J",
v.challenger_joules,
v.incumbent_joules
);
assert!(v.standby_was_bounded);
}
#[test]
fn losing_under_a_pessimistic_assumption_proves_nothing() {
let m = gpu();
let (work, period) = (1_000_000u64, 60.0);
let mut dev = z1_control_loop(1_000);
dev.per_period.writes = 0;
let bounded = m.beaten_by_device(&dev.with_standby_at_most(500.0), work, period).unwrap();
assert!(!bounded.challenger_wins);
assert_eq!(bounded.outcome(), Outcome::Inconclusive);
let mut stated = z1_control_loop(1_000);
stated.per_period.writes = 0;
stated.standby_watts = Some(500.0);
let v = m.beaten_by_device(&stated, work, period).unwrap();
assert_eq!(v.outcome(), Outcome::IncumbentWins);
assert!(!v.standby_was_bounded);
}
#[test]
fn a_challenger_stated_in_nonsense_is_refused() {
let m = gpu();
assert!(matches!(m.beaten_by(f64::NAN, 0.0, 10, 1.0), Err(DutyError::NotPhysical(_))));
assert!(matches!(m.beaten_by(-1.0, 0.0, 10, 1.0), Err(DutyError::NotPhysical(_))));
assert!(matches!(m.beaten_by(1.0, f64::NAN, 10, 1.0), Err(DutyError::NotPhysical(_))));
assert!(matches!(m.beaten_by(1.0, -1.0, 10, 1.0), Err(DutyError::NotPhysical(_))));
assert!(matches!(
m.beaten_by(1.0, 0.0, 10_000_000_000, 1.0),
Err(DutyError::CannotSustain { .. })
));
}
#[test]
fn unphysical_characterisations_are_refused() {
assert!(matches!(Machine::new(f64::NAN, 1.0, 1.0), Err(DutyError::NotPhysical(_))));
assert!(matches!(Machine::new(-1.0, 1.0, 1.0), Err(DutyError::NotPhysical(_))));
assert!(matches!(Machine::new(1.0, -0.5, 1.0), Err(DutyError::NotPhysical(_))));
assert!(matches!(Machine::new(1.0, 1.0, 0.0), Err(DutyError::NotPhysical(_))));
assert!(matches!(Machine::new(1.0, 1.0, f64::INFINITY), Err(DutyError::NotPhysical(_))));
let flat = Machine::new(20.0, 0.0, 1e9).unwrap();
assert_eq!(flat.idle_dominant_below(), f64::INFINITY);
}
#[test]
fn zero_work_and_impossible_periods_get_errors_not_numbers() {
let m = gpu();
assert!(matches!(m.duty(0, 1.0), Err(DutyError::Empty(_))));
assert!(matches!(m.duty(10, 0.0), Err(DutyError::Empty(_))));
assert!(matches!(m.duty(10, f64::NAN), Err(DutyError::Empty(_))));
}
}