"""Note G, reconstructed and run with exact arithmetic under stated interpretations. Note G (1843) gives a "Diagram for the computation by the Engine of the Numbers of Bernoulli" -- a table of 25 operations that computes a Bernoulli number. Running it is not one question but several, and they have different answers. This module runs each one, over ``fractions.Fraction`` so nothing rounds, and says which is which. **Two printed faults, not one.** The famous one is in operation 4, where the division operands are the wrong way round. Glaschick's reconstruction (references [S7], section 5.2) lists it alongside others, including operation 24: "In line 24 the negative value of V13 must be transferred to V24, i.e. V24 - V13 must be calculated. Also, in the 6th column it must read = - B7". So a run that repairs operation 24 while keeping operation 4's inversion is *not* the table as printed; it is a one-fault variant. **What is supplied, and what is generated.** Note G's table computes one Bernoulli number from the earlier ones, which the Engine would already hold. Supplying them correctly asks "what does this table do?"; letting the faulty table generate them asks "what does this routine do if you run it from the start?". Those are different questions, and the second is where the much-repeated -25621/630 comes from. Every value below is computed here rather than quoted. The corrected run reproducing the documented -1/30 is what earns trust in the reconstruction; the rest then say what each faulty reading actually produces: intended, both faults repaired ................ -1/30 operation 4 as printed, 24 repaired ........... 139/630 both operations as printed .................... -139/630 operation 4 repaired, 24 as printed ........... 1/30 4 as printed, 24 repaired, own earlier values .. -25621/630 An earlier version of this module ran only the middle case and described it as "the as-printed table", which repaired operation 24 without saying so, and concluded that -25621/630 "does not survive recomputation". Both statements were wrong in the same way: they did not state which object was being executed. The history is kept here deliberately -- it is the course's own example of a confident claim that had not said what it was a claim about. Operation sequence and variable roles follow Glaschick, R. (2016), *Ada Lovelace's Calculation of Bernoulli's Numbers* (references.md [S7]); the operation-4 inversion and the correct order ``V4 / V5`` are established there and in the facsimile [P2a]. Standard library only. python3 noteg.py # run every interpretation and print the table """ from __future__ import annotations from fractions import Fraction #: Ada's B1, B3, B5 (modern B2, B4, B6): the values the Engine already holds when #: the table runs for her B7. KNOWN_BERNOULLI = (Fraction(1, 6), Fraction(-1, 30), Fraction(1, 42)) def run( n: int = 4, knowns: tuple[Fraction, ...] | list[Fraction] = KNOWN_BERNOULLI, *, invert_op4: bool = False, negate_op24: bool = True, ) -> Fraction: """Run Note G's 25 operations once, for one value of ``n``. ``invert_op4=True`` keeps the printed division ``V5 / V4`` instead of the corrected ``V4 / V5``. ``negate_op24=False`` keeps operation 24 as printed -- transferring V13 to V24 without the sign inversion the intended computation needs. ``knowns`` supplies the earlier Bernoulli numbers in Ada's labelling. """ V1, V2, V3 = Fraction(1), Fraction(2), Fraction(n) V6 = V4 = V5 = V2 * V3 # 1: 2n V4 = V4 - V1 # 2: 2n - 1 V5 = V5 + V1 # 3: 2n + 1 V11 = (V5 / V4) if invert_op4 else (V4 / V5) # 4: the printed inversion V11 = V11 / V2 # 5: /2 V13 = Fraction(0) - V11 # 6: A0 V10 = V3 - V1 # 7: n - 1 (loop counter) V7 = V2 # 8: j = 2 if V10 != 0: V11 = V6 / V7 # 9: A1 V13 = knowns[0] * V11 + V13 # 10, 11: x = A0 + B1*A1 V10 = V10 - V1 # 12 term = 1 while V10 != 0: V6 = V6 - V1 # 13: 2n - 1 V7 = V1 + V7 # 14: j V8 = V6 / V7 # 15 V11 = V8 * V11 # 16 V6 = V6 - V1 # 17: 2n - 2 V7 = V1 + V7 # 18: j + 1 V9 = V6 / V7 # 19 V11 = V9 * V11 # 20: A(j) V13 = knowns[term] * V11 + V13 # 21, 22: x += B(j)*A(j) V10 = V10 - V1 # 23: decrement; repeat if != 0 term += 1 # 24: the intended computation transfers the *negative* of V13. return (Fraction(0) - V13) if negate_op24 else V13 def run_recursively( up_to: int = 4, *, invert_op4: bool = True, negate_op24: bool = True ) -> tuple[Fraction, list[Fraction]]: """Run the routine repeatedly, letting it generate its own earlier values. Note G's operation 25 increments n so the Engine can go round again, so this is a reading the text invites. With the operation-4 inversion in place the faulty A0 term contaminates every value the routine then feeds back into itself, which is why the result diverges much further from -1/30 than a single run does. Returns ``(result, generated)`` where ``generated`` holds the earlier values the routine produced for itself. """ generated: list[Fraction] = [] for step in range(1, up_to): generated.append( run(step, generated, invert_op4=invert_op4, negate_op24=negate_op24) ) return ( run(up_to, generated, invert_op4=invert_op4, negate_op24=negate_op24), generated, ) #: The intended computation: Ada's B7, the modern B8. CORRECT = run() # -1/30 #: Operation 4 kept as printed, operation 24 repaired, earlier values supplied. OP4_ONLY = run(invert_op4=True) # 139/630 #: Both printed faults kept, earlier values supplied: the literal single table. AS_PRINTED = run(invert_op4=True, negate_op24=False) # -139/630 #: Operation 24 kept as printed, operation 4 repaired. OP24_ONLY = run(negate_op24=False) # 1/30 #: The faulty routine generating its own earlier values. This is the figure #: repeated in encyclopaedia and blog accounts, and it does reproduce -- under #: this interpretation, which those accounts do not state. RECURSIVE = run_recursively()[0] # -25621/630 #: Kept for callers that want the widely repeated value by name. CLAIMED_ONLINE = Fraction(-25621, 630) INTERPRETATIONS = ( ("intended computation (both faults repaired)", CORRECT), ("operation 4 as printed, 24 repaired", OP4_ONLY), ("both operations as printed", AS_PRINTED), ("operation 4 repaired, 24 as printed", OP24_ONLY), ("4 as printed, 24 repaired, own earlier values", RECURSIVE), ) def _main() -> int: print("Note G, reconstructed and run with exact arithmetic") print("-" * 62) for label, value in INTERPRETATIONS: print(f" {label:<50} {str(value):>12}") print() print("The first line reproduces the documented Ada B7 = modern B8 = -1/30,") print("which is what earns trust in the reconstruction.") print() print("There is no single answer to 'what does the faulty table compute',") print("because 'the faulty table' names four different objects. The much-") print(f"repeated {CLAIMED_ONLINE} is the last line: it reproduces exactly, under a") print("reading in which the routine generates its own earlier values. What") print("those accounts leave out is which reading they mean -- which is the") print("thing this course asks you to supply for your own claims.") return 0 if __name__ == "__main__": raise SystemExit(_main())