compute: Dual numbers (forward mode) #83
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Forward-mode automatic differentiation as a dual number:
Dual<Scalar>in a new packagestd/gradient, pure TorbScript, no foreign functions, no dependency onstd/tensor. BecauseDual<Scalar>is itselfNumeric,SignedandReal, every function that is generic overReal(geometry, animation, physics, aTensor<Dual<Scalar>>) can be differentiated without being written for it. Reverse mode (#86) joins the same package later.Source
Dual<Scalar>withReal"Tensor<Dual<Scalar>>must be a tensor;Compareon a dual compares its primal partReal,Signed,Numericinstd/number/src/lib.trb; the model implementor with a generic body isFixedinstd/linear/src/fixed.trbDesign (decided by the loop, see the comment of 2026-10-24)
std/gradient(project.trbwithname = "std/gradient",src/lib.trb,src/dual.trb,tests/dual.test.trb), as COMPUTE.md names it. Reverse mode (#86) lands in the same package later.public type Dual<Scalar: Real = Float>with the two fieldsprimal: Scalarandtangent: Scalar(primalis the word TENSOR.md already uses). It declaresSigned, Real, Show, TryFrom<String, NumberParseError>, andEquals/CompareasNumericrequires.Dual.constant(value)(tangent zero),Dual.variable(value)(tangent one), the plain constructor for both fields, andderivative(of: function, at: value): Scalaras the one-call form (the exact signature is the implementer's, following the closest existing function-argument convention).EqualsandComparelook at the primal part only, so==,<andcompareagree and a branch in a generic body (if x < Scalar.zero) takes the same path as the undifferentiated function. The tangent is read with the field.zero,one,pi,tau,e,epsilon) are constants: the scalar's value with tangent zero.TryFrom<String>andFrom<Scalar>give constants.Scalar.zero/Scalar.one, never a literal):+ - *and/: sum, difference, product and quotient rule;%: tangenta' - b' * trunc(a / b); unary-andabsolute(tangentsign(primal) * tangent, zero at zero).squareRoot,exponential,naturalLogarithm,logarithm(base:), the six trigonometric functions andarcTangentDivided(by:): the textbook derivative. Where the scalar's own function is undefined or infinite (square root at zero,arcSineat +-1, logarithm at zero) the tangent is whatever the scalar's division gives; the scalar panics where the scalar panics (arcSine/arcCosineoutside[-1, 1]).floor,ceiling,round: tangent zero (the derivative almost everywhere).halved,doubled,radiansOfDegrees,degreesOfRadians: scale the tangent like the primal.Power(dual exponent):f ** g, tangentf ** g * (g' * ln f + g * f' / f), but wheng.tangentis zero useg * f ** (g - 1) * f', so a negative base with a constant exponent has no logarithm of a negative number in it.Power<Int64>:n * f ** (n - 1) * f'.Dual" (layout, the rules table above, the==decision, the pitfalls: discontinuities,floor) and its status line stops sayingstd/gradientdoes not exist; ROADMAP item 4 namesDualas built.Acceptance criteria
std/gradientexists as designed above;Dual<Float>andDual<Fixed>both satisfyReal(a generic functionfn f<Scalar: Real>(x: Scalar): Scalaris callable with either).std/gradient/tests/dual.test.trb, passing in the interpreter and in a compiled binary (the test file importsFixedasstd/linear/fixedand the dual by relative path, likestd/geometry/tests):x * x,x / (1 + x),squareRoot,exponential,naturalLogarithm,sine,cosine,tangent,arcTangent,arcSine,arcTangentDivided,** n,** gwith a constant and a variable exponent,absoluteon both sides of zero,floor/roundgiving tangent zero);Real, written without any knowledge ofDual, differentiated byDual<Float>andDual<Fixed>(theFixedcase exact to the part, theFloatcase with a tolerance);==/</compareignore the tangent,ShowandTryFrom<String>round-trip the primal.Vector2<Dual<Float>>.length()has the expected tangent, and nested duals (Dual<Dual<Float>>) give a second derivative ofx ** 3equal to6x. If either is blocked by a checker or monomorphisation gap, drop that test, state the gap in the PR body and file it as its own issue (no workaround in the library).tools/gates.sh agreen in forge CI (andbifCLAUDE.mdasks for it for a new package);torb format --checkclean on touched files; docs changed as above andskills/regenerated.Scope
One PR, one or two Conventional Commits, e.g.
feat(std/gradient): add Dual for forward-mode differentiationanddocs(compute): record the forward-mode Dual. No change tostd/number,std/linear, the compiler or the VM; if the implementer finds that one is needed, stop and report which gap it is.Seed
Touches the seed: no. A new package of ordinary TorbScript: no syntax change, no native, no driver flag, no std name the compiler looks up by string.
Dependencies
None open, verified against
mainon 2026-10-24:Real,Signed,Numeric,PowerandPower<Int64>exist instd/number;FixedcarriesRealinstd/linear; TENSOR.md is merged (#438) and its section 9 is compatible with this design. #82 (std/tensor) is a sibling, not a prerequisite. Blocks #86 (together with #85).Moved from the planning lists of the docs; the design record stays the specification.
std/learning#86[loop:torbscript] intake: in scope (author orion, label loop), source COMPUTE.md section 2, not a duplicate, no collision with a design record. Pure TorbScript beside
std/tensor, no seed impact expected. next: refine, waiting (ready buffer full: #4, #163, #164, #223 are ready) until 2026-10-10T04:00:00Zstd/learning#86std/tensoron the CPU (step 1 of #82) #438std/tensoron the CPU #82std/tensoron the CPU #82[loop:torbscript] claim refine by session:loop-torbscript-20261024 until 2026-10-24T02:15:21Z
[loop:torbscript] decided by the loop: the design of #83 is fixed in the issue body, and nothing in it is an owner question.
std/gradient, because COMPUTE.md sections 1 and 2 put forward mode there ("std/gradient: forward mode asDual<Scalar>withReal") and reverse mode (#86) joins it. Notstd/tensor: #83 has no dependency on it.primalandtangent: TENSOR.md section 9 already says "primal part"; full words per the naming convention.EqualsandCompareon the primal only: TENSOR.md requiresCompareon the primal (minimum/maximum); making==agree keeps==,<andcompareconsistent, and a branch in a generic body takes the same path as the plain function. A structural==would makecompare == equaland== falsepossible together.floor/ceiling/round: tangent zero (derivative almost everywhere);absoluteat zero: tangent zero;Powerwith a constant exponent avoids the logarithm of the base, so a negative base works. These are mathematical conventions, recorded in the design section COMPUTE.md gets.feat(std/gradient),docs(compute)). Not an epic. No performance question, nothing to measure.Vector2<Dual<...>>are acceptance tests with a stated fallback if a checker gap shows up, not a prerequisite.Duplicate search (open and closed) for dual, gradient, forward mode, differentiation: no other issue covers this; #86 depends on it.
[loop:torbscript] refine: body rewritten to goal, design decisions, testable acceptance criteria, scope (
std/gradient/*, COMPUTE.md, ROADMAP), seed statement (no) and dependencies verified againstmain(none open;Real/Signed/Numeric/Powerinstd/number,Fixedinstd/linear, TENSOR.md merged); labelreadyset. Worker usage: about 60k tokens. Claim by session:loop-torbscript-20261024 released. next: implement[loop:torbscript] claim implement by session:loop-torbscript-20261024 until 2026-10-24T02:25:29Z
From<Scalar>on a generic type is refused as overlapping the blanketFrom<Never>#455[loop:torbscript] implement:
std/gradientwithDual<Scalar: Real = Float>andderivative(of:at:)implemented, 28 tests pass in the interpreter locally (nightly torb), format clean; PR #454 (two commits, Closes #83). Two deviations from the design, both forced by the checker and recorded in COMPUTE.md section 4: the field israte(nottangent, whichReal.tangent()already occupies in the member namespace), and there is noFrom<Scalar>(overlap with the blanketFrom<Never>, filed as #455). TheVector2<Dual<Float>>and nested-dual tests work, nothing dropped. Forge CI pending. next: review[loop:torbscript] implement: update 02:52 - the forge runs of PR #454 are still queued (status
waiting, runner backlog;commit messages,what the change touchesandpdiscare green, the gates have not started). Nothing to fix on the branch so far. next: review (a red gate on #454 goes to a fix round)