Decidable medical machines, from formulas to Tau
Start with two small medical-style programs, a calorie-deficit calculator and a kidney follow-up workflow, then view each one as math, logic, a decision tree, a finite-state machine, ordinary code, and Tau.
Prerequisites: Tutorial 3 for Tau syntax, Tutorial 6 for MPRD, and Tutorial 22 for the broader medical MPRD context.
This tutorial starts with the small machines that appear inside many medical and wellness programs.
Rather than automating medicine at scale, it shows how a bounded workflow appears in several equivalent forms:
- as a math function,
- as a decision tree,
- as a finite-state machine,
- as ordinary code,
- and as a Tau specification.
These representations are complementary, different lenses on the same computational object.
Assumption hygiene
- Assumption A, calorie lane: the tutorial uses a bounded teaching model of the calorie-deficit formula and treats the published result as an integer floor. The tutorial does not independently establish the physiology claim behind the formula.
- Assumption B, kidney lane: the tutorial uses the adult creatinine-based eGFR equation listed on NIDDK's adult eGFR page, last reviewed in May 2025, for the calculator step, but the follow-up policy shown here is educational, not clinical guidance.
- Assumption C, Tau boundary: some formulas are simple enough to encode directly in Tau, while others are better split into host computes, Tau validates.
Part I: what makes a small medical program decidable
The simplest software object in this tutorial is a total function:
\[f : X \to Y\]It takes an input from X and always returns an output in Y.
If the output is a yes or no verdict, the function becomes a decision procedure:
\[d : X \to \{0,1\}\]A predicate P is decidable exactly when some total Boolean program decides it:
That is the mathematical view.
The same object can also be drawn as a finite-state machine:
\[M = (Q,\Sigma,\delta,q_0,F)\]with:
Q, a finite set of states,Σ, the input alphabet,δ, the transition rule,q0, the start state,F, the set of final states.
For the bounded workflows in this tutorial, decidability means:
\[\forall x \ \exists n \ \mathrm{run}(M,x,n) \in F\]Every input reaches a final state, with no loops and no unresolved cases.
By this definition, both calculators and guideline trees qualify as decidable machines.
Part II: one object, four lenses
For the kinds of examples used here, each system can be read in four ways.
First, as composition:
\[\mathrm{Decision}(x) = \mathrm{Policy}(\mathrm{Classify}(\mathrm{Compute}(x)))\]Second, as a decision tree:
- collect bounded facts,
- test one condition,
- branch,
- reach an action leaf.
Third, as a finite-state machine:
\[\mathrm{Start} \to \mathrm{Read} \to \mathrm{Compute} \to \mathrm{Classify} \to \mathrm{Act} \to \mathrm{Done}\]Fourth, as logic:
\[\mathrm{Allow}(a,s) \Leftrightarrow P_1(s) \land P_2(s) \land \dots \land P_n(s)\]The rest of the tutorial demonstrates each correspondence with concrete examples.
One object, four lenses
Part III: example A, the max-calorie-deficit calculator
The first example is the user’s own calculator:
- public repo: TheDarkLightX/MaxCaloricDeficitCalc
The motivating problem is simple. A person wants a bounded estimate of the largest calorie deficit that still stays inside a particular published formula.
The math view
Let:
w= body weight in pounds,b= body-fat fraction, so20%is0.20,f= fat mass in pounds,m= maximum deficit in kcal/day.
Then the formulas are:
\[f = w \cdot b\] \[m = 31.4 \cdot f\]So the composed function is:
\[m = 31.4 \cdot w \cdot b\]If the program displays an integer result, the bounded calculator can be written as:
\[m_{\lfloor \rfloor} = \lfloor 31.4 \cdot w \cdot b \rfloor\]The logic view
The arithmetic claim can be written as a relation:
\[\mathrm{FatMass}(w,b,f) \Leftrightarrow f = w \cdot b\] \[\mathrm{MaxDeficit}(w,b,m) \Leftrightarrow m = \lfloor 31.4 \cdot w \cdot b \rfloor\]If a program claims some output m, the checker question is:
CorrectClaim is a decidable predicate.
The decision-tree view
Even a calculator has a tiny decision tree:
- Are the inputs present?
- Are the inputs in range?
- If yes, compute the result.
- If no, reject or escalate.
That becomes:
\[\mathrm{Accept}(w,b) \Leftrightarrow \mathrm{Present}(w,b) \land \mathrm{InRange}(w,b)\] \[\mathrm{Accept}(w,b) \Rightarrow \mathrm{Output}(m_{\lfloor \rfloor})\] \[\neg \mathrm{Accept}(w,b) \Rightarrow \mathrm{Output}(\mathrm{error})\]The finite-state-machine view
The core formula is a single expression, but the user-facing program is a state machine:
\[\mathrm{Start} \to \mathrm{ReadWeight} \to \mathrm{ReadBodyFat} \to \mathrm{ComputeFatMass} \to \mathrm{ComputeMaxDeficit} \to \mathrm{Display} \to \mathrm{Done}\]This separation of concerns shapes the design.
The mathematical object is:
\[f(w,b) = \lfloor 31.4 \cdot w \cdot b \rfloor\]The interface object is:
\[M_{\mathrm{ui}} = (Q,\Sigma,\delta,q_0,F)\]with several named phases.
Calorie calculator finite-state machine
The ordinary-code view
In ordinary code, the cleaned core is small:
double fat_mass_lb = bodyweight_lb * bodyfat_fraction;
int max_deficit_kcal = static_cast<int>(std::floor(31.4 * fat_mass_lb));
The same computation appears in spreadsheet form:
\[\mathrm{CellC1} = \mathrm{CellA1} \cdot \mathrm{CellB1}\] \[\mathrm{CellD1} = \lfloor 31.4 \cdot \mathrm{CellC1} \rfloor\]This example reveals a fundamental principle: program synthesis in a spreadsheet, C++, Python, or Tau can preserve the same shape.
The Tau view
The Tau file for this calculator is:
The spec is a checker, not a calculator. It takes three inputs, including a claimed result, and outputs whether that claim matches the floor of the formula. The host or user supplies the claim; Tau validates it.
Because the public teaching lane uses bounded integers, the formula is checked in scaled exact arithmetic.
Body fat is encoded in basis points, so 20.00% becomes 2000.
Then:
To express the floor relation without floating point, the Tau checker uses:
\[100000 \cdot m \le 314 \cdot w \cdot b_{\mathrm{bps}} < 100000 \cdot (m+1)\]That is why this example is especially good for Tau. The arithmetic is exact, finite, and integer-only, with no floating-point approximation.
The stream mapping is:
| Stream | Meaning |
|---|---|
i1 |
bodyweight_lb |
i2 |
bodyfat_bps |
i3 |
claimed_max_deficit_kcal |
o1 |
claimed_formula_matches (0 or 1) |
o2 |
inputs_in_range (0 or 1) |
The core always clause:
always
(((o2[t]:bv[8] = { #x01 }:bv[8]) <->
((i1[t]:bv[32] <= { #x000001F4 }:bv[32]) &&
(i2[t]:bv[32] <= { #x00002710 }:bv[32]) &&
(i3[t]:bv[32] <= { #x00004E20 }:bv[32])))) &&
(((o1[t]:bv[8] = { #x01 }:bv[8]) <->
((o2[t]:bv[8] = { #x01 }:bv[8]) &&
(({ #x000186A0 }:bv[32] * i3[t]:bv[32]) <=
(({ #x0000013A }:bv[32] * i1[t]:bv[32]) * i2[t]:bv[32])) &&
((({ #x0000013A }:bv[32] * i1[t]:bv[32]) * i2[t]:bv[32]) <
({ #x000186A0 }:bv[32] * (i3[t]:bv[32] + { #x00000001 }:bv[32])))))) &&
(((o1[t]:bv[8] = { #x00 }:bv[8]) || (o1[t]:bv[8] = { #x01 }:bv[8])) &&
((o2[t]:bv[8] = { #x00 }:bv[8]) || (o2[t]:bv[8] = { #x01 }:bv[8]))).
Reading guide:
- The
o2biconditional is the range check. The hex constants decode as:#x1F4= 500,#x2710= 10000,#x4E20= 20000. - The
o1biconditional is the floor relation.#x186A0= 100000,#x13A= 314. It checks100000 · m_claim ≤ 314 · w · b_bps < 100000 · (m_claim + 1). - The final two disjunctions restrict both outputs to 0 or 1.
Checker or calculator?
The spec above is a checker: it takes a claimed deficit as input and validates it. But the same floor inequality can also serve as a calculator. If the deficit is moved from an input stream to an output stream, the solver finds the unique value satisfying the constraint:
Input: weight = 180 lb, bodyfat = 2000 bps (20%)
Solve: 100000 * o1 <= 314 * 180 * 2000 < 100000 * (o1 + 1)
Output: o1 = 1130 kcal/day
That is a direct answer, not a validation. The tutorial uses the checker form because it matches the MPRD pattern (host computes, Tau validates) and because the kidney lane cannot work as a direct calculator without leaving Tau's integer arithmetic. Its official equation uses floating-point exponents. Teaching both lanes as checkers establishes the uniform pattern. But for bounded integer formulas like this one, the same constraint works both ways. The direct-calculator version is at medical_max_calorie_deficit_calculator_v1.tau.
Part IV: example B, kidney function to bounded follow-up
The second example is closer to ordinary medicine.
A patient gets a serum creatinine result, for example 1.1 mg/dL.
That result is not yet the same thing as a follow-up decision.
A machine still has to:
- compute eGFR,
- classify the result,
- choose a bounded action class.
The official calculator step
The 2021 CKD-EPI creatinine equation (NIDDK, race-free variant, reviewed May 2025) is:
\[\mathrm{eGFR} = 142 \cdot \min(\mathrm{SCr}/\kappa,1)^{\alpha} \cdot \max(\mathrm{SCr}/\kappa,1)^{-1.200} \cdot 0.9938^{\mathrm{Age}} \cdot 1.012 \ [\text{if female}]\]with:
κ = 0.7for females or0.9for males,α = -0.241for females or-0.302for males,- SCr = serum creatinine in mg/dL.
Source:
Creatinine alone is not enough: the equation depends on age and sex, so the host must supply all three.
A concrete worked example
Assumption K1:
adult female, age 55, serum creatinine 1.1 mg/dL, using the NIDDK adult 2021 CKD-EPI creatinine equation.
For females, κ = 0.7 and α = -0.241.
Since SCr/κ > 1:
Assembling the equation with the female multiplier 1.012:
This step converts serum creatinine into eGFR, the essential computation:
\[\mathrm{KidneyEstimate}(\mathrm{creatinine},\mathrm{age},\mathrm{sex}) = \mathrm{eGFR}\]The National Kidney Foundation’s patient-facing guidance also states that an eGFR below 60 for three months or more, or an eGFR above 60 with kidney damage, can indicate chronic kidney disease. Urine albumin context also matters.
Source:
That is why a real deployment needs more than one lab value and one threshold.
The educational follow-up policy
Because full clinical policy is complex, the teaching example uses a simplified decision tree with three action classes:
watchrepeat_labhuman_review
The host computes the exact eGFR. Then it reduces the result to a bounded flag:
\[\mathrm{Below60}(\mathrm{eGFR}) \Leftrightarrow \mathrm{eGFR} < 60\]Now the policy can be written:
\[\mathrm{HumanReviewRequired}(s) \Leftrightarrow \mathrm{RedFlag}(s) \lor \neg \mathrm{Complete}(s) \lor \neg \mathrm{Fresh}(s)\] \[\mathrm{Allow}(s,a_{\mathrm{watch}}) \Leftrightarrow \mathrm{OneHot}(s) \land \neg \mathrm{HumanReviewRequired}(s) \land \neg \mathrm{Below60}(s)\] \[\mathrm{Allow}(s,a_{\mathrm{repeat}}) \Leftrightarrow \mathrm{OneHot}(s) \land \neg \mathrm{HumanReviewRequired}(s) \land \mathrm{Below60}(s)\] \[\mathrm{Allow}(s,a_{\mathrm{review}}) \Leftrightarrow \mathrm{OneHot}(s) \land \mathrm{HumanReviewRequired}(s)\]Though simplified for teaching, the pipeline structure mirrors real workflows:
\[\mathrm{Decision} = \mathrm{Policy}(\mathrm{Classify}(\mathrm{Compute}(\mathrm{creatinine},\mathrm{age},\mathrm{sex})))\]The finite-state-machine view
The kidney lane is a longer but still finite machine:
\[\mathrm{Start} \to \mathrm{ReadCreatinine} \to \mathrm{ReadAgeSex} \to \mathrm{ComputeEGFR} \to \mathrm{ClassifyBand} \to \mathrm{ChooseAction} \to \mathrm{Done}\]If data are missing or stale, the machine moves to the human-review branch rather than proceeding with incomplete information.
The decision-tree view
The kidney follow-up gate has the same shape as a small decision tree:
- Is the result complete and fresh?
- Is there a red flag?
- Is eGFR below 60?
The leaves are:
\[\neg \mathrm{Complete}(s) \lor \neg \mathrm{Fresh}(s) \Rightarrow a_{\mathrm{review}}\] \[\mathrm{RedFlag}(s) \Rightarrow a_{\mathrm{review}}\] \[\mathrm{Below60}(s) \land \neg \mathrm{RedFlag}(s) \Rightarrow a_{\mathrm{repeat}}\] \[\neg \mathrm{Below60}(s) \land \neg \mathrm{RedFlag}(s) \Rightarrow a_{\mathrm{watch}}\]Here b_bps means body-fat percentage encoded in basis points, a_watch means observe, a_repeat means repeat the lab, and a_review means route to human review.
Every input combination reaches exactly one leaf.
Kidney follow-up decision tree
The ordinary-code view
The same decision tree in ordinary code:
double egfr = ckd_epi_2021(scr, age, sex);
bool below60 = egfr < 60.0;
if (red_flag || !complete || !fresh) return HUMAN_REVIEW;
if (below60) return REPEAT_LAB;
return WATCH;
The code, the state machine, and the logic formulas all model the same decision process.
The Tau view
The public Tau file is:
This file does not recompute the official floating-point equation.
Instead it follows the safer MPRD split:
\[\mathrm{HostComputesEGFR} \land \mathrm{TauValidatesActionClass}\]This split clarifies the architectural boundary between computation and validation.
Host computes, Tau validates
- some bounded arithmetic objects fit fully inside Tau,
- some official equations are better handled by host computation plus Tau validation.
The Tau spec implements this split with the following stream mapping:
| Stream | Meaning |
|---|---|
i1 |
result_complete (0 or 1) |
i2 |
result_fresh (0 or 1) |
i3 |
red_flag_present (0 or 1) |
i4 |
egfr_below_60 (0 or 1) |
i5 |
propose_watch (0 or 1) |
i6 |
propose_repeat_lab (0 or 1) |
i7 |
propose_human_review (0 or 1) |
i8 |
action_one_hot_ok (0 or 1) |
o1 |
selected_action_allowed (0 or 1) |
o2 |
human_review_required (0 or 1) |
The core always clause:
always
(o2[t]:bv[8] =
(i3[t]:bv[8] |
({ #x01 }:bv[8] - i1[t]:bv[8]) |
({ #x01 }:bv[8] - i2[t]:bv[8]))) &&
(o1[t]:bv[8] =
((i8[t]:bv[8] *
i5[t]:bv[8] *
({ #x01 }:bv[8] - i6[t]:bv[8]) *
({ #x01 }:bv[8] - i7[t]:bv[8]) *
i1[t]:bv[8] *
i2[t]:bv[8] *
({ #x01 }:bv[8] - i3[t]:bv[8]) *
({ #x01 }:bv[8] - i4[t]:bv[8])) +
(i8[t]:bv[8] *
({ #x01 }:bv[8] - i5[t]:bv[8]) *
i6[t]:bv[8] *
({ #x01 }:bv[8] - i7[t]:bv[8]) *
i1[t]:bv[8] *
i2[t]:bv[8] *
({ #x01 }:bv[8] - i3[t]:bv[8]) *
i4[t]:bv[8]) +
(i8[t]:bv[8] *
({ #x01 }:bv[8] - i5[t]:bv[8]) *
({ #x01 }:bv[8] - i6[t]:bv[8]) *
i7[t]:bv[8] *
o2[t]:bv[8]))).
Reading guide:
- The first conjunct computes
o2= human_review_required as the bitwise OR of red_flag, NOT complete (1 - i1), and NOT fresh (1 - i2). - The second conjunct computes
o1as the sum of three mutually exclusive products, one for each action class: watch, repeat_lab, and human_review. Because inputs are restricted to 0 or 1, multiplication is conjunction and addition is disjunction over non-overlapping branches. - The watch product includes
(1 - i4), so it goes to zero whenever eGFR is below 60. The repeat_lab product includesi4, so it only fires when eGFR is below 60. The human_review product fires only wheno2= 1.
Part V: why these examples matter
The two examples above already show four recurring shapes. Tutorial 22 develops the full taxonomy; here is the short version with local pointers.
Calculator:f : X → Y, local example: max calorie deficitCalculator + classifier:g(f(x)) ∈ K, local example: creatinine → eGFR →below_60bandClassifier + gate:Allow(a,s) ⇔ P1(s) ∧ ... ∧ Pn(s), local example: watch / repeat_lab / human_reviewTemporal gate:Fresh(x,t) ⇔ t - time(x) ≤ Δ, local example: stale lab → human_review
The same shapes appear across many medical workflows: potassium follow-up trees, HbA1c monitoring and retest windows, CBC or anemia follow-up routing, and refill authorization under explicit freshness and identity rules.
They differ medically but share a common software architecture.
Medical software shapes and how they compose
Part VI: why decidability matters in medicine and wellness software
For a bounded workflow, decidability is not theoretical, it is a practical necessity. It guarantees that every input reaches a final decision without looping indefinitely.
The totality claim is:
\[\forall x \ \exists y \ (f(x)\downarrow = y)\]For a finite-state machine:
\[\forall x \ \exists n \ \mathrm{run}(M,x,n) \in F\]For a gate:
\[\forall s \ \exists a \ (\mathrm{Allow}(s,a) \lor \mathrm{Escalate}(s))\]The practical meanings are:
- every bounded case reaches an explicit conclusion,
- every out-of-scope case routes to a known fallback,
- and the entire decision surface is auditable.
That is why simple medical programs are such good teaching objects.
Part VII: where Tau fits, and where it should stop
Tau is strongest when the object is:
- bounded,
- typed,
- integer-scaled or Boolean,
- and naturally expressed as invariants or action gates.
That makes the calorie example (Shape 1 in Part V: a pure calculator) a direct Tau object.
The kidney example shows the other lane. Shape 2+3 from Part V (calculator followed by classifier followed by gate) is too rich for a single Tau spec because the official eGFR equation uses floating-point exponentiation. The architectural split is:
\[\mathrm{HostComputes} \to \mathrm{TauChecks} \to \mathrm{ExecutorActs}\]This is the same split used in MPRD:
- host code handles the rich arithmetic or parsing,
- Tau validates the bounded decision shape,
- execution only happens through the checked action class.
Tau should not be used for:
- open-ended reasoning or unconstrained search,
- floating-point computation that requires exact numeric fidelity,
- unbounded state spaces where totality cannot be guaranteed,
- or tasks where the action menu is not sharply defined.
Those belong on the host side or in a different tool altogether.
The layers above remain grounded in decidable machines, not hidden complexity. It is just bounded medical machines composed inside a stricter architecture.
Part VIII: downloadable specs and trace evidence
Spec files:
- medical_max_calorie_deficit_formula_v1.tau (checker)
- medical_max_calorie_deficit_calculator_v1.tau (direct calculator)
- medical_egfr_followup_gate_v1.tau
Replay artifacts:
Companion lab:
That means a reader can inspect:
- the formulas,
- the Tau specs,
- the replayable traces,
- and the host-side interactive demo.
Calorie trace walkthrough
Two cases from the recorded traces show the floor check in action:
| case | weight | bf_bps | claimed | 314 * w * bf | 100000 * claimed | floor check | o1 | o2 |
|---|---|---|---|---|---|---|---|---|
| exact_match | 180 | 2000 | 1130 | 113,040,000 | 113,000,000 | pass | 1 | 1 |
| bad_claim | 180 | 2000 | 1200 | 113,040,000 | 120,000,000 | fail | 0 | 1 |
For the exact match:
\[100000 \cdot 1130 = 113{,}000{,}000 \le 113{,}040{,}000 = 314 \cdot 180 \cdot 2000\] \[314 \cdot 180 \cdot 2000 = 113{,}040{,}000 < 113{,}100{,}000 = 100000 \cdot 1131\]Both inequalities hold, so o1 = 1.
For the bad claim:
\[100000 \cdot 1200 = 120{,}000{,}000 > 113{,}040{,}000 = 314 \cdot 180 \cdot 2000\]The lower bound fails, so o1 = 0. The range check still passes (o2 = 1) because all three inputs are within bounds.
Kidney trace walkthrough
Two cases show how the gate blocks a wrong action class and allows the right one:
| flag | bad_watch | repeat_ok |
|---|---|---|
| complete (i1) | 1 | 1 |
| fresh (i2) | 1 | 1 |
| red_flag (i3) | 0 | 0 |
| below_60 (i4) | 1 | 1 |
| propose_watch (i5) | 1 | 0 |
| propose_repeat (i6) | 0 | 1 |
| propose_human (i7) | 0 | 0 |
| one_hot (i8) | 1 | 1 |
| o1 | 0 | 1 |
| o2 | 0 | 0 |
In the bad_watch case, the model proposes watch even though eGFR is below 60. The watch branch of the Tau spec has the factor 1 - i4:
That zero kills the entire watch product, so o1 = 0. The gate denies the action.
In the repeat_ok case, the model instead proposes repeat_lab. The repeat branch has the factor i4:
All other factors in the repeat product are also 1, so o1 = 1. The gate allows the action.
Part IX: interactive lab
Interactive: decidable medical machines lab
Suggested explorations
- In the calorie tab, enter weight 180 lb and body fat 20.0%. Confirm the floor check accepts a claimed deficit of 1130 kcal. Then change the claim to 1200 and watch the formula match fail.
- Switch to the kidney tab. Enter creatinine 1.1 mg/dL, age 55, sex female, and confirm eGFR appears near 59.34.
- With result_complete and result_fresh toggled on and red_flag off, propose
watch. The gate should deny it because eGFR is below 60. Change the proposal torepeat_laband the gate should accept. - Toggle the red_flag on. Any non-human-review proposal should be denied. Propose
human_reviewand the gate should accept. - Toggle result_fresh off. Even without a red flag, the stale-data lane forces
human_review.
Part X: what to take away
Small medical software need not begin with opaque reasoning algorithms.
It can begin with tiny, decidable machines:
- calculators,
- classifiers,
- gates,
- temporal freshness checks,
- and explicit escalation paths.
Once those machines are visible, the same object can be seen as:
- a formula,
- a decision tree,
- a finite-state machine,
- ordinary code,
- a spreadsheet,
- or a Tau specification.
That is the main lesson.
Every object in this tutorial satisfies the totality property from Part I:
\[\forall x \ \exists n \ \mathrm{run}(M,x,n) \in F\]The calorie calculator is a total function: every bounded input reaches a floor result or an out-of-range error. The kidney policy is a finite decision tree over a bounded flag surface: every fact pattern designates one admissible class, and a wrong proposal is denied rather than silently executed. In both cases, every input reaches a definite outcome, with no loops and no unresolved states. That is decidability in practice.
The code may change. The notation may change. The shape does not have to change.
The next layer up composes these decidable machines inside the MPRD architecture from Tutorial 22.