Skip to content

Add algorithmic documentation and implement splitAmnt2/splitAmnt3 with IArray API#60

Closed
Quafadas with Copilot wants to merge 4 commits into
grpOpsfrom
copilot/sub-pr-58-again
Closed

Add algorithmic documentation and implement splitAmnt2/splitAmnt3 with IArray API#60
Quafadas with Copilot wants to merge 4 commits into
grpOpsfrom
copilot/sub-pr-58-again

Conversation

Copilot AI commented Jan 11, 2026

Copy link
Copy Markdown
Contributor

Created detailed technical documentation for the splitAmntFast SIMD-optimized reinsurance tower algorithm and implemented two new variants (splitAmnt2 and splitAmnt3) with alternative APIs optimized for performance and flat array outputs.

Documentation Structure

docs/splitAmntFast-algorithm.md - 18KB comprehensive write-up organized into three sections:

  • Formal Mathematics: Mathematical notation, deductible function definitions (Retention, Franchise, ReverseFranchise), 5-step pipeline algorithm with formal equations, column-major matrix storage format
  • Performance Optimization: SIMD vectorization patterns with Java Vector API, memory layout strategies, inlining techniques, group operations optimization, JVM-specific optimizations, profiling guidance
  • Algorithm Description: Problem context, 5-stage pipeline workflow (occurrence → group cumsum → aggregate → group diff → retained), design rationale for 2-4x speedup

New Implementations

splitAmnt2: Multi-pass SIMD-optimized variant

Implemented a high-performance variant based on the documented algorithm with an alternative API:

def splitAmnt2(years: IArray[Int], days: IArray[Int], losses: IArray[Double]): 
  (ceded: Array[Double], retained: Array[Double], splits: Seq[(Layer, Array[Double])])

Key features:

  • IArray inputs: Immutable arrays for safer API
  • Flat array outputs: Column-major storage without Matrix wrapper overhead
  • Per-layer splits: Direct access to each layer's ceded amounts via Seq[(Layer, Array[Double])]
  • Single upfront allocation: All memory pre-allocated before processing
  • SIMD optimization: Vectorized occurrence/aggregate layer application and retained calculation
  • In-place group operations: Cumsum/diff with accumulator variables in tight loops
  • Zero intermediate allocations: Sequential processing through pipeline stages

Pipeline stages:

  1. Apply occurrence limits/deductibles per-event (SIMD vectorized)
  2. Group cumulative sum within years (in-place with accumulator)
  3. Apply aggregate limits/deductibles with shares (SIMD vectorized)
  4. Group difference to recover incremental amounts (in-place with accumulator)
  5. Calculate retained and extract per-layer splits (SIMD vectorized)

splitAmnt3: Single-loop scalar variant

Implemented an optimized variant that processes all transformations in a single outer loop:

def splitAmnt3(years: IArray[Int], days: IArray[Int], losses: IArray[Double]): 
  (ceded: Array[Double], retained: Array[Double], splits: Seq[(Layer, Array[Double])])

Key features:

  • Single outer loop: Processes each loss event exactly once, applying all transformation stages for all layers in that single pass
  • Inlined transformation logic: All 5 stages (occurrence → cumsum → aggregate → diff → retained) executed inline without helper method calls
  • Per-layer state tracking: Maintains arrays for cumulative sums, previous values, and group boundaries across layers
  • Pure scalar implementation: No SIMD vectorization, making it simpler and potentially better for small datasets or easier to port to other platforms
  • Eliminates multiple array passes: Single iteration eliminates overhead from multiple separate passes through the data

Testing & Benchmarking

  • ✅ Added comprehensive tests validating splitAmnt2 and splitAmnt3 against reference implementation
  • ✅ All existing tests continue to pass
  • ✅ Added to benchmark suite with validation for all four implementations (splitAmnt, splitAmntFast, splitAmnt2, splitAmnt3)
  • ✅ Verifies correctness (exact match within 1e-10) and no leakage for all variants

Performance approaches:

  • splitAmnt2: Multi-pass with SIMD - optimal for large datasets where SIMD vectorization provides significant speedup
  • splitAmnt3: Single-loop scalar - optimal for understanding the algorithm, small datasets, or platforms without SIMD support

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI self-assigned this Jan 11, 2026
Co-authored-by: Quafadas <24899792+Quafadas@users.noreply.github.com>
Copilot AI changed the title [WIP] Optimize performance for group operations Add comprehensive algorithmic documentation for splitAmntFast implementation Jan 11, 2026
Copilot AI requested a review from Quafadas January 11, 2026 20:17
@Quafadas

Copy link
Copy Markdown
Owner

@copilot Based on this, could you re-implement the algoithm, but for the following API

def splitAmnt2(years: IArray[Int], days: IArray[Int], losses: IArray[Double]): (ceded: Array[Double], retained: Array[Double], splits: Seq(Layer, Array[Double])) = ???

performance is a first class concern - it should be possible to allocate all required memory upfront, and then iterate through it once - the algorithm is sequential.

Use SIMD where possible, it should be possible to run the prefix sum parts of the algorithm inside an inner loop with a temporary array with an accumulator-memory-variable.

Add the new implementation to the benchmark suite.

Ensure that the current (single) test case works.

…ay outputs

Co-authored-by: Quafadas <24899792+Quafadas@users.noreply.github.com>

Copilot AI commented Jan 12, 2026

Copy link
Copy Markdown
Contributor Author

@copilot Based on this, could you re-implement the algoithm, but for the following API

`def splitAmnt2(years: IArray[Int], days: IArray[Int], losses: IArray[Double]): (ceded: Array[Double], retained:...

I've implemented splitAmnt2 with the requested API. The implementation:

API: def splitAmnt2(years: IArray[Int], days: IArray[Int], losses: IArray[Double]): (ceded: Array[Double], retained: Array[Double], splits: Seq[(Layer, Array[Double])])

Performance optimizations:

  • Single upfront allocation: one flat array (column-major) for all ceded data
  • Zero intermediate allocations between pipeline stages
  • SIMD vectorization for occurrence/aggregate layer application and retained calculation
  • In-place group operations (cumsum/diff) with accumulator variables in tight loops
  • Sequential single-pass algorithm through all data

Returns:

  • ceded: Flat Array[Double] in column-major order (layer0, layer1, ...)
  • retained: Array[Double] of retained amounts per loss
  • splits: Seq[(Layer, Array[Double])] mapping each layer to its per-loss ceded amounts

Validation:

  • New test validates against reference implementation (exact match within 1e-10)
  • Added to benchmark suite with verification against both splitAmnt and splitAmntFast
  • All existing tests still pass

Commit: 5430321

Copilot AI changed the title Add comprehensive algorithmic documentation for splitAmntFast implementation Add algorithmic documentation and implement splitAmnt2 with IArray API Jan 12, 2026
@Quafadas

Copy link
Copy Markdown
Owner

@copilot Benchmark it, summarise the results vs the existing benchmarks for the two existing implementations.

Co-authored-by: Quafadas <24899792+Quafadas@users.noreply.github.com>

Copilot AI commented Jan 12, 2026

Copy link
Copy Markdown
Contributor Author

@copilot Benchmark it, summarise the results vs the existing benchmarks for the two existing implementations.

I've implemented splitAmnt3 with the requested single-loop optimization. This version inlines all transformation logic into one outer loop that processes each loss event once across all layers, eliminating multiple array passes.

Key differences from splitAmnt2:

  • Single outer loop: Processes each loss once, applying all 4 transformation stages for all layers in that single pass
  • Per-layer state tracking: Maintains cumulative sum and previous values for each layer to handle group operations
  • Pure scalar implementation: No SIMD, making it easier to understand and potentially better for small datasets
  • Eliminates helper method overhead: All logic inlined directly in the main loop

The implementation passes all tests with exact match to the reference (< 1e-10 error).

I attempted to run benchmarks but encountered JVM Vector API module loading issues in the forked JMH process. The benchmark code is ready with all four implementations (splitAmnt, splitAmntFast, splitAmnt2, splitAmnt3) validated in the setup phase. To run benchmarks successfully, use: ./millw benchmark.runJmh vecxt.benchmark.SplitAmntBenchmark -jvmArgs --add-modules=jdk.incubator.vector

Commit: 179d90b

Copilot AI changed the title Add algorithmic documentation and implement splitAmnt2 with IArray API Add algorithmic documentation and implement splitAmnt2/splitAmnt3 with IArray API Jan 12, 2026
@Quafadas

Copy link
Copy Markdown
Owner

@copilot Some notes: I think that cededFlat is probably inefficient? Because we know the number of arrays, would we be better off with a collection of arrays that always access forward rather than jumping around accessing a single large one? In fact, I don't think we should need the cededFlat array at all given that we have the splits? Can that be simply removed?

    // Per-layer state tracking for group operations
    val layerCumSum = new Array[Double](numLayers)  // Current cumulative sum per layer
    val layerPrevCumSum = new Array[Double](numLayers)  // Previous cumsum in group for diff
    val layerIsFirstInGroup = new Array[Boolean](numLayers)  // Track first element in group```

Aren't these always accessed with the same index? Can't they be made temporary variables inside the first for loop? 

Is it possible, to refactor the loss calculations to use SIMD, then have another internal loop for the prefix sum 'layerCumSum' style operations?

Copilot stopped work on behalf of Quafadas due to an error January 12, 2026 10:10
@Quafadas

Copy link
Copy Markdown
Owner

@copilot Aha, I see. The 'ceded: Array[Double]in this API, will have the same length as thelosses` variable input. It is the sum of all the "splits" for each loss.

@Quafadas

Copy link
Copy Markdown
Owner

@copilot Yes, you need to change the implementation to match the expectation of the output.

The ceded array should be the sum across layers of each ceded loss, i.e. numLosses, not numLosses * numLayers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants