Reading a Greek you didn't ask for
How .greek(EquityMarket::spot) finds the right number without ever seeing the string "spot", why every market field becomes an input whether the payoff reads it or not, and the Greek that was already sitting in the tape.
p.greek(EquityMarket::spot) is delta. p.greek(EquityMarket::vol) is
vega. Nowhere in that call does the string "spot" or "vol" appear. How
does a method reference on a record component turn into the right entry of
a reverse sweep's gradient?
The whole story
MarketShape.indexOf resolves EquityMarket::spot to an index with no
reflection on the lambda and no bytecode inspection. It builds one "probe"
instance where component i holds the value i + 0.25, then just calls
your accessor on it: EquityMarket::spot applied to EquityMarket(0.25, 1.25, 2.25, 3.25, 4.25) returns 0.25, subtract the offset and round, and
you have index 0. The number the accessor returns is the answer to
which field it reads. It's also why every market-record component is
required to be a double — the trick only works if there's nothing else
the accessor could plausibly do.
Every component becomes an input, whether the payoff reads it or not
in.of(EquityMarket::spot) in 1.3's Products.Sim looks like it's just
fetching a value. What actually happens is eager, and happens once, before
your lambda runs at all:
private Inputs(AadRecorder recorder, MarketShape<M> shape, M defaults) {
this.shape = shape;
this.inputs = new ADouble[shape.size()];
for (int i = 0; i < inputs.length; i++) {
inputs[i] = recorder.input(shape.name(i), shape.value(defaults, i));
}
}
That constructor runs before your payoff lambda ever sees a single
ADouble — it walks every component of EquityMarket — spot, strike,
vol, rate, maturity, all five — and records a named INPUT node for
each, using the record's own component names. in.of(EquityMarket::spot)
afterward just hands back the ADouble that was already made; it doesn't
record anything new.
One reverse sweep, five names, five numbers
AadResult — what a replay actually returns — keeps one adjoint per
named input and looks it up by name, not by node index:
/** Sensitivity of the price to a named input. */
public double gradient(String inputName) {
return outputGradient[0][inputIndexOf(inputName)];
}
Run 1.3's exact europeanCall() example again and read off every name at
once instead of stopping at the four the earlier pages printed:
spot strike vol rate maturity
0.598570 -0.504569 38.591312 50.456860 5.372837
= delta = dV/dK = vega = rho = dV/dT
dV/dT — the option's sensitivity to time to expiry — was sitting in that
same 1,000,000-scenario run all along. VanillaEuropeanGreeks (1.3) and
Benchmarks (2.1) both compute it — GREEKS = 5 counts it — neither one
prints it.
Every one of those five inputs is unconditionally active, per 1.2's
markActive(): an INPUT node is active by definition, regardless of
whether anything downstream ever uses it. Products.Sim happens to read
all five EquityMarket fields, so none of these five sensitivities are
actually zero — but nothing in the mechanism requires that. Swap in a
market record with a field no payoff touches, and .greek(...) on it would
still work, still cost its slot in the reverse sweep, and just come back as
0.0 instead of throwing.
Try it yourself
Add one line to 1.3's example, right after the four line(...) calls:
p.greek(EquityMarket::maturity); // dV/dT — already computed, never printed
No rebuild of the tape, no extra scenario batch — it's the same run, just reading one more name out of a gradient that was already there.
▶️ Run it
Same command as 1.3, since this page changes what you print, not what you build:
mvn -o -q install
mvn -o -q -pl nablatensor-examples exec:java \
-Dexec.mainClass=com.nablatensor.examples.VanillaEuropeanGreeks \
-Dscenarios=1000000
Add the greek(EquityMarket::maturity) line above to VanillaEuropean Greeks.java yourself to see it printed; the number above came from exactly
this command.
⚠️ What this doesn't do
This page is about retrieving a Greek that already exists on the tape,
not computing one you couldn't get before — dV/dT was always there,
because steps and maturity were always inputs to the model. It doesn't
explain how the reverse sweep actually derives each adjoint arithmetically
(that's adjoint-for-dummies.md's job, linked from 1.1), and it doesn't
cover a Greek that isn't a market component — a second-order Greek like
gamma needs a different mechanism entirely, out of scope here.
What's next
→ Deeper: AadResult.java
covers the multi-output case this page skipped — a tape with several named
rec.output(...) calls carries one full gradient row per output.
→ Next: The Asian option, where a payoff needs
the whole path instead of one terminal value — the same Asian call this
benchmark already used, properly explained.