You are viewing a plain text version of this content. The canonical link for it is here.
Posted to c-commits@axis.apache.org by ro...@apache.org on 2026/09/20 15:58:15 UTC
(axis-axis2-c-core) 07/07: Budget the correlated book in asset-steps and fix the overflow sentinel
This is an automated email from the ASF dual-hosted git repository.
robertlazarski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/axis-axis2-c-core.git
commit f3f88d32c0719b0b154d4ee37f801523543d6968
Author: Robert Lazarski <ro...@gmail.com>
AuthorDate: Sun Sep 20 05:38:21 2026 -1000
Budget the correlated book in asset-steps and fix the overflow sentinel
The correlated monteCarlo budget was n_simulations * n_periods * n_assets^2,
which refused 20 assets at the defaults while the docs promised 100. It is
now asset-steps, n_simulations * n_periods * n_assets, which admits 100 at
the defaults and about 39 at 100,000 paths; the refusal states the count
the budget allows. The terminal value for an overflowing path is 1e150
instead of 1e308, which overflowed when squared for the variance or when
two such paths were summed. services.xml's monteCarlo schema now lists the
same properties as the MCP catalog.
Co-Authored-By: Claude Fable 5.1 <no...@anthropic.com>
---
.../financial-benchmark-service/README.md | 5 +++--
.../financial-benchmark-service/services.xml | 9 +++++++-
.../src/financial_benchmark_service.c | 19 +++++++++-------
.../src/financial_benchmark_service.h | 26 +++++++++++++++++-----
4 files changed, 42 insertions(+), 17 deletions(-)
diff --git a/samples/user_guide/financial-benchmark-service/README.md b/samples/user_guide/financial-benchmark-service/README.md
index c1f063bb7..d65d3f004 100644
--- a/samples/user_guide/financial-benchmark-service/README.md
+++ b/samples/user_guide/financial-benchmark-service/README.md
@@ -108,8 +108,9 @@ systemic jump (one Poisson draw and one log-jump per step for the whole book).
The response adds `simulation_mode: "correlated"`, `n_assets`, the `weights`
used and `portfolio_volatility` = √(w'Σw). A single asset with Σ = [σ²]
reproduces the scalar run bit-for-bit for the same seed. Cost is
-n_simulations × n_periods × n_assets² against the same work budget; max 100
-assets.
+n_simulations × n_periods × n_assets asset-steps against the same work
+budget (10⁹), so the defaults allow all 100 assets and 100,000 paths allow
+about 39; max 100 assets.
```json
{
diff --git a/samples/user_guide/financial-benchmark-service/services.xml b/samples/user_guide/financial-benchmark-service/services.xml
index 8ab22c63d..95eb028dd 100644
--- a/samples/user_guide/financial-benchmark-service/services.xml
+++ b/samples/user_guide/financial-benchmark-service/services.xml
@@ -208,7 +208,7 @@
Run Monte Carlo simulation for Value at Risk (VaR) calculation.
Compute-intensive: n_simulations × n_periods iterations, times
- n_assets² for a correlated book (covariance_matrix + weights).
+ n_assets for a correlated book (covariance_matrix + weights).
Key benchmark: 10,000 sims × 252 days = 2.52M iterations
Target: <500ms on old Android phone
@@ -232,7 +232,14 @@
"expected_return": {"type": "number", "description": "Annualised expected return (e.g. 0.08 for 8%)"},
"volatility": {"type": "number", "description": "Annualised volatility (e.g. 0.20 for 20%)"},
"random_seed": {"type": "integer", "description": "Seed for reproducible results. When omitted, seeded from time() — results differ across calls."},
+ "n_periods_per_year": {"type": "integer", "default": 252,
+ "description": "Trading periods per year; dt = 1/n_periods_per_year. Must match the basis of expected_return and volatility."},
+ "percentiles": {"type": "array", "items": {"type": "number"}, "maxItems": 8,
+ "description": "Tail levels for VaR, e.g. [0.01, 0.05] for 99% and 95%. Default [0.01, 0.05]."},
"model": {"type": "string", "enum": ["gbm", "merton"], "default": "gbm"},
+ "jump_intensity": {"type": "number", "default": 1.0, "description": "Merton: jumps per year (lambda*dt must be at most 0.1)."},
+ "jump_mean": {"type": "number", "default": -0.03, "description": "Merton: mean log-jump."},
+ "jump_vol": {"type": "number", "default": 0.05, "description": "Merton: log-jump standard deviation."},
"covariance_matrix": {"type": "array", "items": {"type": "number"},
"description": "Correlated book: n_assets×n_assets annualized covariance matrix (flat row-major or nested), as composeCovariance returns it. Switches the run to a buy-and-hold multi-asset book; volatility is then ignored. Must be positive definite."},
"weights": {"type": "array", "items": {"type": "number"},
diff --git a/samples/user_guide/financial-benchmark-service/src/financial_benchmark_service.c b/samples/user_guide/financial-benchmark-service/src/financial_benchmark_service.c
index 4564817e6..428f5d6fd 100644
--- a/samples/user_guide/financial-benchmark-service/src/financial_benchmark_service.c
+++ b/samples/user_guide/financial-benchmark-service/src/financial_benchmark_service.c
@@ -1819,8 +1819,8 @@ finbench_run_monte_carlo_correlated(
"supply n_assets or a weights array alongside it.");
}
if (n > FINBENCH_MAX_MC_ASSETS) {
- MC_FAIL("n_assets=%d exceeds the correlated-simulation maximum of %d "
- "(each step costs O(n_assets^2)).", n, FINBENCH_MAX_MC_ASSETS);
+ MC_FAIL("n_assets=%d exceeds the correlated-simulation maximum of %d.",
+ n, FINBENCH_MAX_MC_ASSETS);
}
if (!request->weights || !request->covariance_matrix || !request->expected_returns) {
MC_FAIL("Internal error: correlated request arrays missing.");
@@ -1843,9 +1843,12 @@ finbench_run_monte_carlo_correlated(
MC_FAIL("expected_returns array length %d != n_assets %d.",
request->expected_returns_provided, n);
}
- if ((int64_t)request->n_simulations * request->n_periods * (int64_t)n * n > FINBENCH_MAX_WORK) {
- MC_FAIL("n_simulations * n_periods * n_assets^2 exceeds the computation budget (%ld).",
- (long)FINBENCH_MAX_WORK);
+ if ((int64_t)request->n_simulations * request->n_periods * (int64_t)n > FINBENCH_MAX_WORK) {
+ MC_FAIL("n_simulations * n_periods * n_assets = %lld exceeds the computation budget (%ld). "
+ "At n_simulations=%d and n_periods=%d the budget allows %d assets.",
+ (long long)request->n_simulations * request->n_periods * n, (long)FINBENCH_MAX_WORK,
+ request->n_simulations, request->n_periods,
+ (int)(FINBENCH_MAX_WORK / ((int64_t)request->n_simulations * request->n_periods)));
}
for (i = 0; i < n; i++) {
double w = request->weights[i];
@@ -1993,7 +1996,7 @@ finbench_run_monte_carlo_correlated(
value += s_vals[i];
}
if (terminal) {
- value = 1e308;
+ value = FINBENCH_EXTREME_VALUE;
break;
}
@@ -2254,12 +2257,12 @@ finbench_run_monte_carlo(
* multiply the cap and overflow to +Inf, which propagates NaN into
* the whole response. Treat an extreme path as terminal instead. */
if (exponent > 709.0) {
- value = 1e308; /* extreme outcome */
+ value = FINBENCH_EXTREME_VALUE; /* extreme outcome */
break;
}
value *= exp(exponent);
if (!isfinite(value)) {
- value = 1e308;
+ value = FINBENCH_EXTREME_VALUE;
break;
}
diff --git a/samples/user_guide/financial-benchmark-service/src/financial_benchmark_service.h b/samples/user_guide/financial-benchmark-service/src/financial_benchmark_service.h
index e0eada4ee..66907d706 100644
--- a/samples/user_guide/financial-benchmark-service/src/financial_benchmark_service.h
+++ b/samples/user_guide/financial-benchmark-service/src/financial_benchmark_service.h
@@ -76,11 +76,22 @@ extern "C"
* response amplification. 500 assets: ~4e7 Cholesky steps, ~10 MB reply. */
#define FINBENCH_MAX_COV_ASSETS 500
-/** Maximum assets in a correlated (multi-asset) monteCarlo request. Each
- * time step costs O(n_assets²) for the Cholesky product, so the work cap
- * below is applied as n_simulations × n_periods × n_assets². */
+/** Maximum assets in a correlated (multi-asset) monteCarlo request. The work
+ * cap for a book is n_simulations × n_periods × n_assets ≤ FINBENCH_MAX_WORK:
+ * one asset-step (a normal draw and an exp()) is the unit, so the defaults
+ * (10,000 × 252) allow all 100 assets and 100,000 paths allow about 39. The
+ * O(n_assets²) Cholesky product is cheap next to the transcendentals and is
+ * bounded by this cap, adding at most a few multiples of wall time at n=100. */
#define FINBENCH_MAX_MC_ASSETS 100
+/** Terminal value assigned to a path whose GBM step overflowed (see the
+ * numerical-edge-cases note). Chosen so that it survives everything the
+ * statistics do to it: squared it is 1e300, and a million of them summed
+ * (or a million squares summed) stay below DBL_MAX, so no +Inf or NaN can
+ * enter the mean, the variance or the sort. 1e308 was used before and did
+ * overflow when squared. */
+#define FINBENCH_EXTREME_VALUE 1e150
+
/** Monte Carlo model selection constants */
#define FINBENCH_MODEL_GBM 0 /* Geometric Brownian Motion (default) */
#define FINBENCH_MODEL_MERTON 1 /* Merton (1976) jump-diffusion */
@@ -427,7 +438,8 @@ typedef struct finbench_compose_covariance_response
* consumes the PRNG in the same order, so a single-asset correlated run
* reproduces the scalar run bit-for-bit for the same seed (provided
* sqrt(Σ₁₁) == σ exactly, e.g. σ = 0.25).
- * Work cap: n_simulations × n_periods × n_assets² ≤ FINBENCH_MAX_WORK.
+ * Work cap: n_simulations × n_periods × n_assets ≤ FINBENCH_MAX_WORK
+ * (asset-steps), plus n_assets ≤ FINBENCH_MAX_MC_ASSETS.
*
* VaR sign convention: var_95, var_99, and cvar_95 are returned as
* POSITIVE LOSS MAGNITUDES in base-currency units. So var_95 = 252000
@@ -459,8 +471,10 @@ typedef struct finbench_compose_covariance_response
* - exp() overflow: extreme volatility × long horizon can push the
* GBM exponent past ~709 (log DBL_MAX). A step whose exponent exceeds
* 709, or whose product is no longer finite, ends the path: it is
- * recorded as a terminal extreme outcome (1e308) and simulated no
- * further, so +Inf never reaches the sort or the sums. Capping the
+ * recorded as a terminal extreme outcome (FINBENCH_EXTREME_VALUE,
+ * 1e150) and simulated no further, so +Inf never reaches the sort or
+ * the sums — the sentinel is sized so that squaring it and summing a
+ * million of them both stay finite. Capping the
* exponent and continuing would only defer the overflow to the next
* step. NOTE: this diverges from Axis2/Java, which deliberately does
* NOT guard — Java prefers NaN propagation as an alarm. Both choices