<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

 <title>Alessandro Carraro</title>
 <link href="https://alecarraro.github.io/atom.xml" rel="self"/>
 <link href="https://alecarraro.github.io/"/>
 <updated>2026-08-22T21:33:50+00:00</updated>
 <id>https://alecarraro.github.io</id>
 <author>
   <name>Alessandro Carraro</name>
   <email>your-email@example.com</email>
 </author>

 
 <entry>
   <title>Tile-Low-Rank Matrices in NextLA.jl</title>
   <link href="https://alecarraro.github.io/2026/08/20/gsoc-project-with-nextla/"/>
   <updated>2026-08-20T00:00:00+00:00</updated>
   <id>https://alecarraro.github.io/2026/08/20/gsoc-project-with-nextla</id>
   <content type="html">&lt;div class=&quot;message&quot;&gt;
  This post summarizes my Google Summer of Code 2026 work on
  &lt;a href=&quot;https://github.com/NextLinearAlgebra/NextLA.jl&quot;&gt;NextLA.jl&lt;/a&gt;,
  mentored by &lt;a href=&quot;https://github.com/rabab53&quot;&gt;Rabab Alomairy&lt;/a&gt; and
  &lt;a href=&quot;https://github.com/QingleiCao&quot;&gt;Qinglei Cao&lt;/a&gt;. The project introduced
  the Tile-Low-Rank (TLR) matrix format and GPU-accelerated operations, including
  matrix multiplication with both dense and compressed output.
&lt;/div&gt;

&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;#introduction&quot;&gt;Introduction&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#my-contribution&quot;&gt;My contribution&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#two-tlr-variants&quot;&gt;Two TLR variants&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#packing-variable-rank-tiles&quot;&gt;Packing variable-rank tiles&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#compression-with-ara&quot;&gt;Compression with ARA&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#gemm-on-compressed-operands&quot;&gt;GEMM on compressed operands&lt;/a&gt;
    &lt;ol&gt;
      &lt;li&gt;&lt;a href=&quot;#workspace-is-part-of-the-schedule&quot;&gt;Workspace is part of the schedule&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#returning-a-compressed-result&quot;&gt;Returning a compressed result&lt;/a&gt;&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#results&quot;&gt;Results&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#using-the-implementation&quot;&gt;Using the implementation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#references&quot;&gt;References&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;A dense \(32{,}768 \times 32{,}768\) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Float64&lt;/code&gt; matrix costs 8.6 GB to
store, and operations such as factorization still scale cubically in the
matrix dimension. But many matrices that appear in scientific computing do
not contain that much independent information. Boundary element
discretizations, Gaussian process covariance matrices, and kernel matrices
from radial basis functions often have smooth interactions between
well-separated groups of points. Smooth interactions are numerically low
rank, so after blocking the matrix, most off-diagonal tiles have singular
values that decay after only a small number of terms.&lt;/p&gt;

&lt;p&gt;The storage arithmetic is already enough to motivate the format. Tile the
\(32{,}768 \times 32{,}768\) matrix into \(1024 \times 1024\) blocks. The 32
diagonal tiles stay dense, while the remaining 992 off-diagonal tiles are
stored as rank-16 factor pairs. The dense diagonal costs 268 MB, and the
off-diagonal factors cost about 260 MB. The result is 528 MB instead of
8.6 GB, about a 16x reduction.&lt;/p&gt;

&lt;p&gt;Tile-Low-Rank (TLR) uses that observation directly. Cut the matrix into a flat
grid of tiles. Keep the diagonal dense, since it represents the near field and
is usually genuinely full rank. Replace each off-diagonal tile with two skinny
factors,&lt;/p&gt;

\[A_{ij} \approx U_{ij}V_{ij}^{T},
\qquad
U_{ij}\in\mathbb{R}^{b_i\times r_{ij}},
\quad
V_{ij}\in\mathbb{R}^{b_j\times r_{ij}},\]

&lt;p&gt;where \(r_{ij}\) is much smaller than the tile size. Unlike a global low-rank
factorization, each tile gets its own rank. Unlike \(\mathcal{H}\)-matrices or
HODLR, there is no tree and no recursion: one level, one tile size, and one
regular grid. That gives up some compressibility, but it produces a layout
that is far easier to schedule on a GPU, which is fundamental for performance
in real applications.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/images/gsoc-2026/tlr_matrix-transparent.png&quot; alt=&quot;A Tile-Low-Rank matrix whose tiles have different ranks&quot; width=&quot;900&quot; /&gt;
  &lt;figcaption&gt;Each tile has its own low-rank factorization and rank.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;During the Google Summer of Code I implemented the TLR module in
&lt;a href=&quot;https://github.com/NextLinearAlgebra/NextLA.jl&quot;&gt;NextLA.jl&lt;/a&gt;, a
Julia library aiming to provide next-generation dense linear algebra: mixed
precision, low-rank formats, and multiple hardware backends behind one API, on
CPU and GPU without hardware-specific code paths. This post describes the TLR
module we added.&lt;/p&gt;

&lt;h2 id=&quot;my-contribution&quot;&gt;My contribution&lt;/h2&gt;

&lt;p&gt;The code is available in
&lt;a href=&quot;https://github.com/NextLinearAlgebra/NextLA.jl&quot;&gt;NextLA.jl&lt;/a&gt; and was
introduced in pull request
&lt;a href=&quot;https://github.com/NextLinearAlgebra/NextLA.jl/pull/20&quot;&gt;#20&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The main result is an end-to-end public pipeline for working with TLR
matrices. Starting from ordinary dense matrices, NextLA can now build
compressed representations,&lt;/p&gt;

\[A \longrightarrow A_{\mathrm{tlr}},
\qquad
B \longrightarrow B_{\mathrm{tlr}},\]

&lt;p&gt;where each object stores the matrix by tiles rather than as one dense array.
The API exposes two variants: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TLRMatrix&lt;/code&gt;, which keeps the diagonal tiles
dense, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CompressedFTLRMatrix&lt;/code&gt;, where every tile is stored in compressed
form.&lt;/p&gt;

&lt;p&gt;Once the matrices are compressed, they can be used directly in BLAS-like
products,&lt;/p&gt;

\[C \leftarrow
\alpha\,\operatorname{op}(A_{\mathrm{tlr}})
        \operatorname{op}(B_{\mathrm{tlr}})
+ \beta C,\]

&lt;p&gt;with support for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TLR × TLR&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TLR × dense&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dense × TLR&lt;/code&gt;. There is also
an allocation-returning path where the output remains compressed,&lt;/p&gt;

\[C_{\mathrm{tlr}}
\leftarrow
\operatorname{gemm}(A_{\mathrm{tlr}}, B_{\mathrm{tlr}}),\]

&lt;p&gt;so the product does not have to be materialized as a dense matrix. The API also exposes the controls needed for real workloads: transpose combinations, mixed-precision compute modes, workspace limits, and workspace reuse.&lt;/p&gt;

&lt;p&gt;Below we illustrate some implementation details and provide a small tutorial to show how to construct the TLR matrices, multiply them, and choose how much precision and memory to spend.&lt;/p&gt;

&lt;h2 id=&quot;two-tlr-variants&quot;&gt;Two TLR variants&lt;/h2&gt;

&lt;p&gt;We implemented two versions of the format. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TLRMatrix&lt;/code&gt; follows the usual
scientific-computing layout [1]: diagonal tiles are dense and off-diagonal
tiles are compressed. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CompressedFTLRMatrix&lt;/code&gt; stores every tile, including the
diagonal, as a factor pair. The second format is useful when there is no clear
dense near field, which is common for blockwise-compressed machine-learning
matrices [4].&lt;/p&gt;

&lt;h2 id=&quot;packing-variable-rank-tiles&quot;&gt;Packing variable-rank tiles&lt;/h2&gt;

&lt;p&gt;Storing every factor at an operand-wide maximum rank would make the arrays
regular, but it would also bring back arithmetic and memory that compression
was supposed to remove. NextLA instead stores each tile using its discovered
logical rank.&lt;/p&gt;

&lt;p&gt;The two factors use complementary traversal orders. The \(U\) factors are
packed by tile row, while the \(V\) factors are packed by tile column. This
makes a row panel of \(U\) and a column panel of \(V\) available as contiguous
views at the same time. Transposition only exchanges the two roles, so the same
storage works for every &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;N&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T&lt;/code&gt; operand combination without keeping another
copy.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/images/gsoc-2026/data_structure-transparent.png&quot; alt=&quot;Complementary packed storage for U and V factors&quot; width=&quot;900&quot; /&gt;
  &lt;figcaption&gt;
    Logical ranks and factor offsets describe two complementary packed arrays.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Some low-precision kernels, including grouped GEMM, require aligned factor
addresses and dimensions. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rank_multiple&lt;/code&gt; option meets this requirement by
padding each factor’s stored capacity without changing its logical rank, error
estimate, or numerical result. On current CUDA hardware, use
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rank_multiple=8&lt;/code&gt; for FP16 and BF16 factors.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/images/gsoc-2026/paddinh-transparent.png&quot; alt=&quot;A logical low-rank factor padded to an aligned stored capacity&quot; width=&quot;850&quot; /&gt;
  &lt;figcaption&gt;
    Alignment padding is local to each factor instead of using one maximum
    rank for the whole matrix.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;compression-with-ara&quot;&gt;Compression with ARA&lt;/h2&gt;

&lt;p&gt;The tile ranks are outputs of the compression, not inputs. We use Adaptive
Randomized Approximation (ARA) [2] to sample a batch of tiles and grow an
orthogonal basis until each tile meets the requested tolerance or reaches
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;maxrank&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Tiles in one batch do not necessarily converge together. The implementation
therefore keeps separate progress and stopping state for every tile. On each
pass it projects a fresh sample against the existing basis, performs two-pass
Cholesky QR, and checks the new projected columns. Finished tiles retire while
the others continue. A final small SVD chooses the stored rank and records the
remaining error.&lt;/p&gt;

&lt;p&gt;From the user side, this is just a constructor:&lt;/p&gt;

&lt;div class=&quot;language-julia highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;A_tlr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TLRMatrix&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;256&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;;&lt;/span&gt;
                  &lt;span class=&quot;n&quot;&gt;maxrank&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;
                  &lt;span class=&quot;n&quot;&gt;tol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1f-5&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;
                  &lt;span class=&quot;n&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;
                  &lt;span class=&quot;n&quot;&gt;r_required&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;r_required&lt;/code&gt; controls how many consecutive negligible samples ARA needs
before declaring a tile converged. Compression workspaces can also be reused
when several matrices have the same shape and tiling.&lt;/p&gt;

&lt;h2 id=&quot;gemm-on-compressed-operands&quot;&gt;GEMM on compressed operands&lt;/h2&gt;

&lt;p&gt;The dense-output interface follows BLAS:&lt;/p&gt;

\[C \leftarrow \alpha\,\operatorname{op}(A)\operatorname{op}(B)+\beta C,
\qquad \operatorname{op}(X)\in\{X,X^T\}.\]

&lt;p&gt;The API covers two compressed operands as well as mixed dense/compressed
products. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TLRMatrix&lt;/code&gt; also carries dense diagonal tiles and smaller boundary
tiles, so its GEMM combines the compressed off-diagonal product with the two
dense-diagonal cross terms and the diagonal product.&lt;/p&gt;

&lt;p&gt;The interesting part is the product of two compressed tiles. Write&lt;/p&gt;

\[A_{i\ell}=U_{i\ell}V_{i\ell}^{T},
\qquad
B_{\ell j}=W_{\ell j}Z_{\ell j}^{T}.\]

&lt;p&gt;Then one output tile is&lt;/p&gt;

\[C_{ij}=\sum_{\ell}
U_{i\ell}\underbrace{\left(V_{i\ell}^{T}W_{\ell j}\right)}_{S_{i\ell j}}
Z_{\ell j}^{T}.\]

&lt;p&gt;Computing that expression tile by tile gives many tiny GEMMs and repeatedly
loads and stores \(C_{ij}\). Most of the GEMM work went into avoiding that. The
packed factors let us fuse adjacent tile rows and columns into larger views,
and the product is lowered in three stages:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;form the small coupling matrices \(S_{i\ell j}\);&lt;/li&gt;
  &lt;li&gt;multiply \(S\) by one side’s factors;&lt;/li&gt;
  &lt;li&gt;fold the sum over \(\ell\) into the reduction dimension of a final GEMM.&lt;/li&gt;
&lt;/ol&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/images/gsoc-2026/tlr_stage1-transparent.png&quot; alt=&quot;Stage 1 forms the coupling matrices and fuses the output-column axis&quot; width=&quot;700&quot; /&gt;
  &lt;figcaption&gt;
    Stage 1 forms the coupling matrices. Output columns are fused into each
    GEMM, while the contraction tiles are submitted as a batch.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/images/gsoc-2026/tlr_stage2-transparent.png&quot; alt=&quot;Stage 2 multiplies the coupling matrices by the inner factors of B&quot; width=&quot;800&quot; /&gt;
  &lt;figcaption&gt;
    Stage 2 builds the intermediate factors with grouped GEMMs over the
    contraction and output-column indices.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/images/gsoc-2026/tlr_stage3-transparent.png&quot; alt=&quot;Stage 3 folds the contraction axis into one GEMM for an output row&quot; width=&quot;750&quot; /&gt;
  &lt;figcaption&gt;
    Stage 3 folds the complete sum over ℓ into the GEMM reduction
    dimension, producing an output row with one final GEMM.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;There are two valid contraction orders. FoldRight computes
\(U(SZ^T)\); FoldLeft computes \((US)Z^T\). Their temporary sizes and operation
counts depend on the ranks in the current rows and columns. The scheduler
looks at that rank metadata and chooses the cheaper feasible direction instead
of imposing one order on the entire matrix.&lt;/p&gt;

&lt;h3 id=&quot;workspace-is-part-of-the-schedule&quot;&gt;Workspace is part of the schedule&lt;/h3&gt;

&lt;p&gt;The temporary workspace determines how many output tiles can be fused in one
run. With more memory, the scheduler builds wider operations. With less, it
splits the output into smaller row and column ranges while staying inside the
requested byte limit.&lt;/p&gt;

&lt;p&gt;NextLA exposes minimum and maximum workspace queries. For fully compressed
operands, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gemm_workspace_bytes(A, B; runs=n)&lt;/code&gt; chooses the smallest workspace
that meets a target number of runs. The symbolic schedule and prepared
grouped-GEMM descriptors can be cached and reused when the matrix dimensions
and rank distribution stay the same.&lt;/p&gt;

&lt;p&gt;This makes the memory/performance tradeoff explicit. A caller can use the
fastest schedule when memory is available or deliberately trade some fusion
for a smaller peak allocation.&lt;/p&gt;

&lt;h3 id=&quot;returning-a-compressed-result&quot;&gt;Returning a compressed result&lt;/h3&gt;

&lt;p&gt;The allocation-returning &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gemm(A_tlr, B_tlr; ...)&lt;/code&gt; keeps the result compressed.
This is a different problem from the dense-output path: the rank of each output
tile is unknown until the product has been sampled. We cannot allocate its
final packed offsets in advance, and forming a dense tile just to compress it
would lose most of the benefit.&lt;/p&gt;

&lt;p&gt;For one output tile, define&lt;/p&gt;

\[X_{ij}=\alpha\sum_{\ell} A_{i\ell}B_{\ell j}
=\alpha\sum_{\ell}
U^A_{i\ell}S_{i\ell j}(V^B_{\ell j})^T,
\qquad
S_{i\ell j}=(V^A_{i\ell})^TU^B_{\ell j}.\]

&lt;p&gt;The coupling matrices \(S_{i\ell j}\) depend on the input factors but not on
the random samples, so they are computed once for a run and reused. A right sample
of the complete tile is then&lt;/p&gt;

\[X_{ij}\Omega
=\alpha\sum_{\ell}U^A_{i\ell}S_{i\ell j}
\left((V^B_{\ell j})^T\Omega\right).\]

&lt;p&gt;This is evaluated as three GEMM contractions: project the random block through
\(V^B\), apply the small coupling matrices, and reduce all \(\ell\)
contributions through the packed \(U^A\) row. The wide \(b_m\times b_n\) tile
\(X_{ij}\) never exists in memory. If left sampling is cheaper, the
implementation applies the transpose expression instead. Complementary packing
provides the contiguous row or column stack needed by either direction and by
all four transpose combinations.&lt;/p&gt;

&lt;p&gt;ARA repeats this implicit apply with fresh random blocks and grows a basis \(Q\)
until the tile converges. It then applies the complementary operator once,&lt;/p&gt;

\[Z_{ij}=X_{ij}^TQ_{ij},
\qquad
X_{ij}\approx Q_{ij}Z_{ij}^T,\]

&lt;p&gt;and performs a small final SVD to select the output rank and split the result
into its two stored factors. In contrast to sequential recompression, the
whole sum over \(\ell\) is compressed once rather than after every contraction
tile.&lt;/p&gt;

&lt;p&gt;The output grid is processed with a rolling workspace. A byte budget determines
how many ARA slots are available. A fixed-row or fixed-column run fills those
slots; when some tiles converge, they are moved to a retired suffix, truncated,
and copied to output staging. Pending tiles immediately reuse the freed slots.
Workspace therefore scales with the chosen concurrency rather than with the
number of output tiles.&lt;/p&gt;

&lt;p&gt;During this pass, staging factors have a uniform width of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;maxrank&lt;/code&gt;. Once every
logical rank is known, NextLA allocates the final exact-rank offsets, applies
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rank_multiple&lt;/code&gt; if alignment is requested, and copies only the active factor
columns. This is why compressed-output GEMM returns a newly allocated matrix
instead of accepting a finalized packed destination through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gemm!&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The main controls are:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;Keyword&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;Role in compressed-output GEMM&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;maxrank&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Upper bound for every discovered output rank and the temporary staging width&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tol&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rel&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Absolute or relative error target used by final truncation&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eps_rel&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Sampling tolerance for the adaptive range finder; defaults from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tol&lt;/code&gt; and the numerical stopping floor&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Number of random columns drawn in one ARA pass&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;r_required&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Consecutive negligible samples required before a tile retires&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;workspace&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Optional byte budget; larger values allow more simultaneous ARA slots&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rank_multiple&lt;/code&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Alignment quantum for the final stored capacities, without changing logical ranks&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The current compressed-output entry point accepts two &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CompressedFTLRMatrix&lt;/code&gt;
operands on a regular tile grid. It supports &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NN&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NT&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TN&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TT&lt;/code&gt;, plus
the same mixed-precision compute modes as the dense-output implementation.&lt;/p&gt;

&lt;h2 id=&quot;results&quot;&gt;Results&lt;/h2&gt;

&lt;p&gt;We evaluated the dense-output GEMM on an NVIDIA H100 with both uniform and
skewed tile-rank distributions. A direct comparison with KBLAS is necessarily
limited: KBLAS supports FP32 and FP64 and uses one common rank for every tile
in an operand. NextLA additionally supports BF16, FP16, and TF32 on CUDA, and
executes each tile at its own rank.&lt;/p&gt;

&lt;p&gt;For constant-rank FP32 problems, NextLA reached 92.1% of the ceiling obtained
by scaling dense-GEMM time by the executed-FLOP ratio. On the configurations
shared with KBLAS, it was 1.44 times faster in geometric mean and up to 2.58
times faster. The advantage is larger for variable ranks because NextLA does
not pad every tile to one operand-wide rank.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/images/gsoc-2026/figure_4_constant_rank_nextla_vs_kblas_b_n16_r_b16-transparent.png&quot; alt=&quot;NextLA and KBLAS constant-rank FP32 TLR GEMM runtime on an NVIDIA H100&quot; width=&quot;650&quot; /&gt;
  &lt;figcaption&gt;
    Constant-rank FP32 GEMM with tile size $$b=N/16$$ and tile rank $$r=b/16$$.
    The arithmetic ceiling scales dense-GEMM time by the executed-FLOP ratio.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The constant-rank experiment above provides the like-for-like KBLAS
comparison. The next experiment uses the feature that the padded baseline
cannot represent directly: different ranks for different tiles. Each matrix
has eight tiles per axis, hence 64 tiles in total, with tile size \(b=N/8\)
and ranks varying between \(b/16\) and \(b/8\). The bars report speedup over
the corresponding dense GEMM for several matrix sizes and compute modes.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/images/gsoc-2026/figure_5_precision_speedup_bars_skewed_b_n8-transparent.png&quot; alt=&quot;TLR GEMM speedups across BF16, FP16, TF32, and FP32 for matrices with skewed tile ranks&quot; width=&quot;850&quot; /&gt;
  &lt;figcaption&gt;
    Speedup over dense GEMM for matrices with an \(8\times8\) tile grid,
    \(b=N/8\), and per-tile ranks in \([b/16,b/8]\). NextLA uses the exact
    rank of each tile. KBLAS pads the varying ranks to one common rank and is
    shown only for its supported FP32 comparison.
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The missing KBLAS bars in the BF16, FP16, and TF32 panels are therefore not
omitted measurements; those execution modes are not supported by KBLAS. The
NextLA speedup increases with matrix size as the fused compressed operations
become large enough to use the GPU efficiently.&lt;/p&gt;

&lt;p&gt;The workspace experiments show the other side of the design. One FP16
configuration was 6.34 times faster than dense GEMM while using 17.8% of the
dense-input memory. In the largest reported FP16 case, \(N=65{,}536\) completed
in 81.8 ms, 9.57 times faster than the dense baseline while using 10.7% of its
operand memory.&lt;/p&gt;

&lt;!-- TODO(plot): Runtime versus normalized operand-and-workspace memory. --&gt;

&lt;h2 id=&quot;using-the-implementation&quot;&gt;Using the implementation&lt;/h2&gt;

&lt;p&gt;Assume &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B&lt;/code&gt; are dense &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Matrix{Float32}&lt;/code&gt; inputs. The same compressed
operands can then be used with both GEMM output modes:&lt;/p&gt;

&lt;div class=&quot;language-julia highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NextLA&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# A and B are dense matrices with compatible dimensions. For compressed-output&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# GEMM, each dimension must also be a multiple of the tile size.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maxrank&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;256&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;A_tlr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CompressedFTLRMatrix&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maxrank&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1f-5&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;B_tlr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CompressedFTLRMatrix&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maxrank&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1f-5&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Dense accumulation: C_dense is an ordinary dense matrix.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;C_dense&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;zeros&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Float32&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;dense_workspace&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gemm_maximum_workspace_bytes&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;A_tlr&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;B_tlr&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gemm!&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;C_dense&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;A_tlr&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;B_tlr&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;workspace&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dense_workspace&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;alpha&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;beta&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Compressed accumulation: ranks are discovered before C_tlr is packed.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;C_tlr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gemm&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;A_tlr&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;B_tlr&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;;&lt;/span&gt;
             &lt;span class=&quot;n&quot;&gt;maxrank&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;
             &lt;span class=&quot;n&quot;&gt;tol&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1f-5&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;
             &lt;span class=&quot;n&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;
             &lt;span class=&quot;n&quot;&gt;r_required&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Expand a compressed matrix whenever a conventional dense array is needed.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;C_uncompressed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;similar&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;C_dense&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;uncompress!&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;C_uncompressed&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;C_tlr&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C_dense&lt;/code&gt; stores the product directly in dense form. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C_tlr&lt;/code&gt; keeps each output
tile compressed, while &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C_uncompressed&lt;/code&gt; is its dense reconstruction.&lt;/p&gt;

&lt;p&gt;On CUDA, FP16 and BF16 storage can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rank_multiple=8&lt;/code&gt;; FP32 can select
Tensor Core execution with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compute=TF32()&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;p&gt;[1] K. Akbudak, H. Ltaief, A. Mikhalev, and D. Keyes. &lt;em&gt;Tile Low Rank Cholesky
Factorization for Climate/Weather Modeling Applications on Manycore
Architectures.&lt;/em&gt; ISC High Performance, 2017.&lt;/p&gt;

&lt;p&gt;[2] W. Boukaram, G. Turkiyyah, and D. Keyes. &lt;em&gt;Hierarchical Matrix Operations
on GPUs.&lt;/em&gt; SIAM Journal on Scientific Computing 41(4), 2019.&lt;/p&gt;

&lt;p&gt;[3] A. Charara, D. Keyes, and H. Ltaief. &lt;em&gt;Tile Low-Rank GEMM Using Batched
Operations on GPUs.&lt;/em&gt; Euro-Par, 2018.&lt;/p&gt;

&lt;p&gt;[4] P.-H. Chen, S. Si, Y. Li, C. Chelba, and C.-J. Hsieh. &lt;em&gt;GroupReduce:
Block-Wise Low-Rank Approximation for Neural Language Model Shrinking.&lt;/em&gt;
NeurIPS, 2018.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>TLR Matrix Multiplication</title>
   <link href="https://alecarraro.github.io/2026/07/03/tlr-matrix-multiplication/"/>
   <updated>2026-07-03T00:00:00+00:00</updated>
   <id>https://alecarraro.github.io/2026/07/03/tlr-matrix-multiplication</id>
   <content type="html">&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;#introduction-tlr-gemm&quot;&gt;Introduction: TLR &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GEMM&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#fixed-rank-dense-tlr-tlr&quot;&gt;Fixed Rank &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GEMM&lt;/code&gt;: dense ← TLR × TLR&lt;/a&gt;
    &lt;ol&gt;
      &lt;li&gt;&lt;a href=&quot;#regular-tlr-gemm&quot;&gt;Regular TLR &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GEMM&lt;/code&gt;&lt;/a&gt;
        &lt;ol&gt;
          &lt;li&gt;&lt;a href=&quot;#the-easy-terms&quot;&gt;The easy terms&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#oa-ob-terms&quot;&gt;The \(O_A O_B\) terms&lt;/a&gt;
            &lt;ol&gt;
              &lt;li&gt;&lt;a href=&quot;#the-stride-1-axis&quot;&gt;The stride-1 axis&lt;/a&gt;&lt;/li&gt;
              &lt;li&gt;&lt;a href=&quot;#lever-1-a-layout-k-sum&quot;&gt;Lever 1: \(A\)’s layout determines how the \(k\)-sum is performed&lt;/a&gt;&lt;/li&gt;
              &lt;li&gt;&lt;a href=&quot;#lever-2-b-layout-stage-1&quot;&gt;Lever 2: \(B\)’s layout determines whether Stage 1 fuses over \(j\)&lt;/a&gt;&lt;/li&gt;
              &lt;li&gt;&lt;a href=&quot;#output-traversal-workspace-budget&quot;&gt;Output traversal and workspace budget&lt;/a&gt;&lt;/li&gt;
              &lt;li&gt;&lt;a href=&quot;#pseudocode&quot;&gt;Pseudocode&lt;/a&gt;&lt;/li&gt;
              &lt;li&gt;&lt;a href=&quot;#benchmark&quot;&gt;Benchmark&lt;/a&gt;&lt;/li&gt;
            &lt;/ol&gt;
          &lt;/li&gt;
        &lt;/ol&gt;
      &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;introduction-tlr-gemm&quot;&gt;Introduction: TLR &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GEMM&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;Let \(A \in \mathbb{R}^{N \times N}\) be a square matrix stored in tile low-rank (TLR) format. We  partition \(A\) into an \(n \times n\) grid of tiles,&lt;/p&gt;

\[A =
\begin{bmatrix}
A_{11} &amp;amp; \widetilde A_{12} &amp;amp; \cdots &amp;amp; \widetilde A_{1n} \\
\widetilde A_{21} &amp;amp; A_{22} &amp;amp; \cdots &amp;amp; \widetilde A_{2n} \\
\vdots &amp;amp; \vdots &amp;amp; \ddots &amp;amp; \vdots \\
\widetilde A_{n1} &amp;amp; \widetilde A_{n2} &amp;amp; \cdots &amp;amp; A_{nn}
\end{bmatrix}.\]

&lt;p&gt;The diagonal tiles \(A_{ii}\) are stored densely; each off-diagonal tile (\(i \neq j\)) is stored in compressed low-rank form&lt;/p&gt;

\[\widetilde A_{ij} = U_{ij} V_{ij}^{T}, \qquad U_{ij} \in \mathbb{R}^{b_i \times r_{ij}}, \quad V_{ij} \in \mathbb{R}^{b_j \times r_{ij}},\]

&lt;p&gt;where \(r_{ij}\) is the tile rank. Interior tiles are \(b \times b\); boundary tiles are smaller when \(N\) is not a multiple of \(b\).&lt;/p&gt;

&lt;p&gt;We want the generalized product&lt;/p&gt;

\[C \leftarrow \alpha \operatorname{op}(A)\operatorname{op}(B) + \beta C, \qquad \operatorname{op}(X) \in \{X, X^{T}\},\]

&lt;p&gt;where each of \(A\), \(B\), \(C\) may be stored densely or in TLR format. The tile factors can be stored padded to occupy \(b\times r_{\max}\) even if the efefctive rank \(r_{ij} &amp;lt; r_{\max}\) or with variable storage such that each tile only takes \(r_{ij}\). The second one requires more complex scheduling. We develop the general GEMM \(\mathrm{TLR}\times \mathrm{TLR}\rightarrow \mathrm{TLR}\) in three steps, from the easiest to the most complex, building on each other.&lt;/p&gt;

\[\begin{aligned}
&amp;amp;\text{1. fixed rank:} \qquad &amp;amp;&amp;amp;\text{dense} \leftarrow \text{TLR} \times \text{TLR}, \\
&amp;amp;\text{2. fixed rank:} \qquad &amp;amp;&amp;amp;\text{TLR} \leftarrow \text{TLR} \times \text{TLR}, \\
&amp;amp;\text{3. variable rank:} \qquad &amp;amp;&amp;amp;\text{TLR} \leftarrow \text{TLR} \times \text{TLR}.
\end{aligned}\]

&lt;p&gt;This post describes stage 1, the fixed-rank dense-output case; the TLR-output cases extend it.&lt;/p&gt;

&lt;h2 id=&quot;fixed-rank-dense-tlr-tlr&quot;&gt;Fixed Rank &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GEMM&lt;/code&gt;:  \(\mathrm{ dense} \leftarrow \mathrm{TLR}\times \mathrm{TLR}\)&lt;/h2&gt;

&lt;p&gt;The goal of the first implementation stage is to compute&lt;/p&gt;

\[C \leftarrow AB,\]

&lt;p&gt;where both \(A\) and \(B\) are stored in fixed-rank TLR format and \(C\) is stored densely. The main implementation objective is to express the computation in terms of vendor-optimized BLAS primitives, in particular &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GEMM&lt;/code&gt; and batched &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GEMM&lt;/code&gt; variants.&lt;/p&gt;

&lt;p&gt;To expose regular operations, we need to analyse the memory storage. TLR matrix can be stored using three buffers, one for the dense diagonal tile, represented by a 3d array of size \(b\times b \times n\) such that each tile is written in memory in the (Julia standard) column major storage. Similarly the factors \(U\) and \(V\) can also be written in memory in two distinct buffers, where each factor is laid out column major and the factors are stored linearized according to either a tile row major or tile col major ordering. In the case where the tiles have the same blocksize \(b\) and rank \(r_{\max}\), there is a constant stride among consecutive factors and can be written as a 3d array as well.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/images/tlr-gemm/tlr-int-storage.png&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;

&lt;p&gt;In the more general case, where we store the arrays with variable ranks, we also hold an array with the ranks \(r\) and compute its starting location in the buffer by prefix sum.&lt;/p&gt;

&lt;p&gt;For the case the global matrix dimension \(N\) is not an exact multiple of the nominal tile size \(b\). Let&lt;/p&gt;

\[N = mb + b_{\mathrm{rem}},\]

&lt;p&gt;where \(m = \lfloor N/b \rfloor\) and \(0 \leq b_{\mathrm{rem}} &amp;lt; b\). If \(b_{\mathrm{rem}} = 0\), all tiles have size \(b \times b\). Otherwise, the last block row and last block column contain boundary tiles with smaller dimensions. In that case, the interior part of the matrix has dimension \(mb \times mb\), while the boundary block has size \(b_{\mathrm{rem}}\).&lt;/p&gt;

&lt;p&gt;For this reason, it is convenient to decompose the matrix into a regular interior TLR (sub)matrix and then three boundary panels. When \(b_{\mathrm{rem}} &amp;gt; 0\), we write&lt;/p&gt;

\[A =
\begin{bmatrix}
A_{\mathrm{int}} &amp;amp; u_A \\
v_A^{T} &amp;amp; \gamma_A
\end{bmatrix},\]

&lt;p&gt;where&lt;/p&gt;

\[A_{\mathrm{int}} \in \mathbb{R}^{mb \times mb}, 
\qquad
u_A \in \mathbb{R}^{mb \times b_{\mathrm{rem}}},
\qquad
v_A \in \mathbb{R}^{mb \times b_{\mathrm{rem}}},
\qquad
\gamma_A \in \mathbb{R}^{b_{\mathrm{rem}} \times b_{\mathrm{rem}}}.\]

&lt;p&gt;Here, \(A_{\mathrm{int}}\) denotes the regular TLR sub-matrix formed by the full-size tiles, while \(u_A\), \(v_A^{T}\), and \(\gamma_A\) collect the boundary column, boundary row, and bottom-right corner block, respectively. So the storage becomes as in the figure below&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/images/tlr-gemm/tlr-bnd-storage.png&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;

&lt;p&gt;The right operand \(B\) in the TLR &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GEMM&lt;/code&gt; can have the same structure. Then the product \(C = AB\) can be written as&lt;/p&gt;

\[\begin{aligned}
C &amp;amp;=
\begin{bmatrix}
A_{\mathrm{int}} &amp;amp; u_A \\
v_A^{T} &amp;amp; \gamma_A
\end{bmatrix}
\begin{bmatrix}
B_{\mathrm{int}} &amp;amp; u_B \\
v_B^{T} &amp;amp; \gamma_B
\end{bmatrix} \\
&amp;amp;=
\begin{bmatrix}
A_{\mathrm{int}} B_{\mathrm{int}} &amp;amp; A_{\mathrm{int}} u_B \\
v_A^{T} B_{\mathrm{int}} &amp;amp; v_A^{T} u_B
\end{bmatrix}
+
\begin{bmatrix}
u_A v_B^{T} &amp;amp; u_A \gamma_B \\
\gamma_A v_B^{T} &amp;amp; \gamma_A \gamma_B
\end{bmatrix}.
\end{aligned}\]

&lt;p&gt;This decomposition separates the computation into four output regions:&lt;/p&gt;

\[C =
\begin{bmatrix}
C_{\mathrm{int}} &amp;amp; C_{\mathrm{right}} \\
C_{\mathrm{bottom}} &amp;amp; C_{\mathrm{corner}}
\end{bmatrix}.\]

&lt;p&gt;This structure is allows to split the matrix-multiplication across terms with a regular structure, which maps well to (batched) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GEMM&lt;/code&gt;s. The bulk of the work is the update of the interior block \(A_{\mathrm{int}} B_{\mathrm{int}}\). Moreover since, the the four ouput regions are dijsoint they can be updated concurrently on separate streams.&lt;/p&gt;

&lt;p&gt;We explain the machinery developed to compute the interior product \(A_{\mathrm{int}} B_{\mathrm{int}}\), as this is the most computationally intensive operation and the most general. The same ideas are reused for the other products&lt;/p&gt;

&lt;h3 id=&quot;regular-tlr-gemm&quot;&gt;Regular TLR &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GEMM&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;From here on we drop the \(\mathrm{int}\) subscript and work with two square interior TLR sub-matrices. The storage naturally splits each operand into its dense diagonal and its low-rank off-diagonal part, \(A = D_A + O_A\) and \(B = D_B + O_B\), so that&lt;/p&gt;

\[C \leftarrow \beta C + \alpha (D_A + O_A)(D_B + O_B)
      = \beta C + \alpha\big(D_A D_B + O_A D_B + D_A O_B + O_A O_B\big).\]

&lt;p&gt;The four products touch different parts of \(C_{\mathrm{int}}\):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;\(D_A D_B\) writes only the &lt;strong&gt;diagonal&lt;/strong&gt; tiles of \(C\);&lt;/li&gt;
  &lt;li&gt;\(O_A D_B\) and \(D_A O_B\) write only the &lt;strong&gt;off-diagonal&lt;/strong&gt; tiles;&lt;/li&gt;
  &lt;li&gt;\(O_A O_B\) writes &lt;strong&gt;both&lt;/strong&gt;, and usually accounts for the dominant part of the work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the diagonal group and the off-diagonal group touch disjoint tiles, they can run concurrently on separate streams&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/images/tlr-gemm/streams-diags.png&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;

&lt;p&gt;The scaling by \(\beta\) is fused into the first GEMM that writes each tile of \(C\). For a diagonal tile \(C_{ii}\), the first writer is \(D_A D_B\), so that GEMM performs&lt;/p&gt;

\[C_{ii} \leftarrow \beta C_{ii} + \alpha A_{ii}B_{ii}.\]

&lt;p&gt;For an off-diagonal tile \(C_{ij}\), the first writer is either the \(O_A D_B\) update or the \(D_A O_B\) update. That first off-diagonal GEMM uses the original \(\beta\). Every later contribution to the same tile uses accumulation mode, i.e. it is launched with \(\beta = 1\).&lt;/p&gt;

&lt;h4 id=&quot;the-easy-terms&quot;&gt;The easy terms&lt;/h4&gt;

&lt;p&gt;The first three products are mapped to strided batched GEMMs. The diagonal product is a batch of independent dense tile GEMMs:&lt;/p&gt;

\[(D_A D_B)_{ii} = A_{ii}B_{ii},
\qquad i = 1,\dots,m.\]

&lt;p&gt;Thus it is implemented as one strided batched GEMM with matrix size \(b \times b\) and batch size \(m\).&lt;/p&gt;

&lt;p&gt;The product \(O_A D_B\) can be written as&lt;/p&gt;

\[(O_A D_B)_{ij} = U_{ij}\big(V_{ij}^{T}D_{jj}\big),
\qquad i\neq j,\]

&lt;p&gt;and is therefore evaluated in two batched GEMM stages. Consider first the products \(V_{ij}^{T}D_{jj}\) over all off-diagonal tiles. The GEMM grouping depends on the layout of the low-rank factors. If the factors of \(A\) are stored tile-column major, then for each fixed \(j\) the matrices \(V_{ij}^{T}\), \(i\neq j\), are contiguous and can be stacked into one tall matrix. Thus the \(m-1\) products associated with column \(j\) are computed by one GEMM of size \(((m-1)r)\times b\) times \(b\times b\). Repeating this for all \(j\) gives \(m\) such GEMMs, which can be launched as one strided batched GEMM. If the factors of \(A\) are stored row-wise instead, the factors needed for a fixed \(j\) are not contiguous, so this aggregation is not available; the stage is then implemented as one small GEMM per off-diagonal tile, for a total of \(m(m-1)\) GEMMs of size \(r\times b\) times \(b\times b\).&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/images/tlr-gemm/offdiag_x_diag.png&quot; width=&quot;800&quot; /&gt;
&lt;/figure&gt;

&lt;p&gt;By symmetry, the opposite holds for the product \(D_A * O_B\).&lt;/p&gt;

&lt;h4 id=&quot;oa-ob-terms&quot;&gt;The \(O_A O_B\) terms&lt;/h4&gt;
&lt;p&gt;Here both operands are low-rank. With \(\widetilde A_{ik} = U_{ik} V_{ik}^{T}\) and \(\widetilde B_{kj} = W_{kj} Z_{kj}^{T}\), the \((i,j)\) output block is&lt;/p&gt;

\[(O_A O_B)_{ij}
=
\sum_{k\neq i,j}
U_{ik}\,\bigl(V_{ik}^{T}W_{kj}\bigr)\,Z_{kj}^{T}.\]

&lt;p&gt;We first compute the common intermediate&lt;/p&gt;

\[\text{Stage 1:}\qquad
S_{ikj}=V_{ik}^{T}W_{kj}
\in\mathbb{R}^{r_A\times r_B}.\]

&lt;p&gt;The remaining contractions are ordered to minimize the size of the intermediate matrix. Specifically,&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If \(r_A\le r_B\),&lt;/li&gt;
&lt;/ul&gt;

\[\begin{aligned}
\text{Stage 2:}\qquad
&amp;amp;T_{ikj}=S_{ikj}Z_{kj}^{T}
&amp;amp;&amp;amp;\in\mathbb{R}^{r_A\times b},\\
\text{Stage 3:}\qquad
&amp;amp;C_{ij}=\sum_k U_{ik}T_{ikj}
&amp;amp;&amp;amp;\in\mathbb{R}^{b\times b}.
\end{aligned}\]

&lt;ul&gt;
  &lt;li&gt;If \(r_B&amp;lt;r_A\),&lt;/li&gt;
&lt;/ul&gt;

\[\begin{aligned}
\text{Stage 2:}\qquad
&amp;amp;T_{ikj}=U_{ik}S_{ikj}
&amp;amp;&amp;amp;\in\mathbb{R}^{b\times r_B},\\
\text{Stage 3:}\qquad
&amp;amp;C_{ij}=\sum_k T_{ikj}Z_{kj}^{T}
&amp;amp;&amp;amp;\in\mathbb{R}^{b\times b}.
\end{aligned}\]

&lt;p&gt;Thus, after forming \(S_{ikj}\), the multiplication order is chosen according to \(\min(r_A,r_B)\), ensuring that the intermediate matrix \(T_{ikj}\) is as small as possible.&lt;/p&gt;

&lt;p&gt;Each stage is a collection of small GEMMs indexed by three tile indices: the output row \(i\), the output column \(j\), and the contraction tile \(k\). As in the previous section, the number and size of the GEMM depends on the layout of the factors. Every index can be handled in three cases:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Fused&lt;/strong&gt; into a GEMM dimension (M, N, or K): several tiles are glued into one larger matrix operand and handled by a single large GEMM. This is fastest, but requires the tiles along a given axis to be &lt;strong&gt;contiguous in memory&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Batched&lt;/strong&gt;: many independent, equal-shaped GEMMs issued in one batched-GEMM call. This works for any layout, but each GEMM stays small.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Looped&lt;/strong&gt; serially, one GEMM per value of the index.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Fusing is best (biggest GEMMs, fewest launches), batching is the fallback for scattered data, and looping is reserved for a case we will see is unavoidable. The axes which can be fused depends on the ordering of the factors: tile column or tile major order.&lt;/p&gt;

&lt;h5 id=&quot;the-stride-1-axis&quot;&gt;The stride-1 axis&lt;/h5&gt;

&lt;p&gt;The factors are stored as \([b, r, n_{\text{off}}]\) arrays. Tiles are contiguous in memory only along the stride-1 axis, which depends on the tile layout.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;Operand&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Tile-column-major&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Tile-row-major&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;\(A_{ik}\)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;\(i\)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;\(k\)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;\(B_{kj}\)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;\(k\)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;\(j\)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;This gives four possible layout combinations, which determines the optimization lever.&lt;/p&gt;

&lt;h5 id=&quot;lever-1-a-layout-k-sum&quot;&gt;Lever 1: \(A\)’s layout determines how the \(k\)-sum is performed&lt;/h5&gt;

&lt;p&gt;First consider one fixed output tile \(C_{ij}\). Its low-rank update has the form&lt;/p&gt;

\[C_{ij}
\mathrel{+}=
\sum_{k\neq i,j}
U_{ik}T_{ikj},\]

&lt;p&gt;where \(T_{ikj}\) is the intermediate produced by the previous stage. The important point is that all values of \(k\) contribute to the same output tile \(C_{ij}\). There are two ways to handle this sum:&lt;/p&gt;

&lt;p&gt;If the factors \(U_{ik}\) are contiguous in \(k\), we can fold the \(k\)-sum into the GEMM contraction dimension. Namely, for fixed \((i,j)\) we form the block products as&lt;/p&gt;

\[C_{ij}
\mathrel{+}=
\underbrace{
\begin{bmatrix}
U_{i1} &amp;amp; U_{i2} &amp;amp; \cdots
\end{bmatrix}
}_{b\times K_r}
\underbrace{
\begin{bmatrix}
T_{i1j}\\
T_{i2j}\\
\vdots
\end{bmatrix}
}_{K_r\times b},\]

&lt;p&gt;where \(K_r\) is the sum of the ranks over the contributing \(k\) indices. In this case one GEMM computes the full sum over \(k\) and writes \(C_{ij}\) once. In this case the GEMM has the schematic form. This is the &lt;strong&gt;write-once&lt;/strong&gt; case for \(C_{ij}\).&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/images/tlr-gemm/stage3.png&quot; width=&quot;600&quot; /&gt;
&lt;/figure&gt;

&lt;p&gt;If the factors \(U_{ik}\) are not contiguous in \(k\), folding the reduction into one GEMM would require packing or copying the \(U\) panels. Without such packing, the implementation loops over \(k\):&lt;/p&gt;

\[\begin{cases}
C_{ij} \leftarrow U_{ik}T_{ikj} + \beta C_{ij} &amp;amp;&amp;amp; k=1 \\
C_{ij} +=   U_{ik}T_{ikj} &amp;amp;&amp;amp; k=2, \dots, n-1
\end{cases}\]

&lt;p&gt;Now each contribution is a separate GEMM, and the same tile \(C_{ij}\) is updated repeatedly. This is the &lt;strong&gt;accumulate&lt;/strong&gt; case.&lt;/p&gt;

&lt;p&gt;Thus:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If \(A\) is stride-1 in \(k\), then the \(U_{ik}\) panels needed for one \(C_{ij}\) are contiguous. The \(k\)-sum can be folded into the GEMM contraction dimension, and each output tile \(C_{ij}\) is written once.&lt;/li&gt;
  &lt;li&gt;If \(A\) is stride-1 in \(i\), then the \(U_{ik}\) panels needed for one \(C_{ij}\) are scattered as \(k\) varies. The implementation loops over \(k\) and accumulates into \(C_{ij}\) multiple times.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This statement concerns one fixed output tile. Across many output tiles, we still batch or loop over the remaining free indices. For example, if the \(k\)-sum has been folded for each \((i,j)\), then different output tiles \(C_{ij}\) are independent and can be handled as a batch of write-once GEMMs. If the \(k\)-sum is not folded, then the implementation typically loops over \(k\), and for each fixed \(k\) updates a batch of output tiles \(C_{ij}\) with accumulation.&lt;/p&gt;

&lt;p&gt;This is why the two scheduling families differ in their traffic to \(C\). In the write-once family, each \(C_{ij}\) tile is formed by a GEMM that already includes the full \(k\)-sum. In the accumulate family, each \(C_{ij}\) tile is read and written once per contributing \(k\). Since Stage 3 operates on full \(b\times b\) tiles, reducing the number of updates to \(C\) is usually the dominant consideration.&lt;/p&gt;

&lt;h5 id=&quot;lever-2-b-layout-stage-1&quot;&gt;Lever 2: \(B\)’s layout determines whether Stage 1 fuses over \(j\)&lt;/h5&gt;

&lt;p&gt;Stage 1 computes \(S_{ikj}=V_{ik}^{T}W_{kj}\). For fixed \((i,k)\), the left operand \(V_{ik}^{T}\) is reused for all output columns \(j\), while the right operand \(W_{kj}\) changes with \(j\). Therefore, if the \(W_{kj}\) factors are contiguous as \(j\) varies, the products over \(j\) can be fused into the \(N\) dimension of one larger GEMM.&lt;/p&gt;

&lt;p&gt;If \(B\) is stride-1 in \(j\), then the row-\(k\) panel&lt;/p&gt;

\[\begin{bmatrix}
W_{k j_1} &amp;amp; W_{k j_2} &amp;amp; \cdots
\end{bmatrix}\]

&lt;p&gt;is contiguous, and Stage 1 can compute&lt;/p&gt;

\[\begin{bmatrix}
S_{ikj_1} &amp;amp; S_{ikj_2} &amp;amp; \cdots
\end{bmatrix}
=

V_{ik}^{T}
\begin{bmatrix}
W_{k j_1} &amp;amp; W_{k j_2} &amp;amp; \cdots
\end{bmatrix}.\]

&lt;p&gt;Thus \(j\) is fused into the GEMM \(N\) dimension. This replaces many small \(r_A\times r_B\) products by one wider GEMM.&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&quot;/assets/images/tlr-gemm/stage1_fuse.png&quot; width=&quot;600&quot; /&gt;
&lt;/figure&gt;

&lt;p&gt;If \(B\) is stride-1 in \(k\), then the factors \(W_{kj}\) needed for fixed \(k\) and varying \(j\) are scattered. The fusion over \(j\) is not available without packing, so Stage 1 remains tilewise and is implemented as a batched GEMM.&lt;/p&gt;

&lt;p&gt;The same idea applies to the \(i\) direction. For fixed \(k\) the factors \(V_{ik}^{T}\) are contiguous as \(i\) varies, then stage 1 can then fuse \(i\) into the GEMM \(M\) dimension.&lt;/p&gt;

&lt;p&gt;Combining the two levers gives the four layouts:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;\(A\) stride-1 axis&lt;/th&gt;
      &lt;th&gt;\(B\) stride-1 axis&lt;/th&gt;
      &lt;th&gt;\(k\)-reduction&lt;/th&gt;
      &lt;th&gt;Stage-1 fusion&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;# (batched) GEMMs&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;\(k\)&lt;/td&gt;
      &lt;td&gt;\(j\)&lt;/td&gt;
      &lt;td&gt;folded into \(K\); write-once&lt;/td&gt;
      &lt;td&gt;\(j\to N\)&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;\(3\)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;\(k\)&lt;/td&gt;
      &lt;td&gt;\(k\)&lt;/td&gt;
      &lt;td&gt;folded into \(K\); write-once&lt;/td&gt;
      &lt;td&gt;none; tilewise&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;\(3\)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;\(i\)&lt;/td&gt;
      &lt;td&gt;\(k\)&lt;/td&gt;
      &lt;td&gt;looped over \(k\); accumulate&lt;/td&gt;
      &lt;td&gt;\(i\to M\)&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;\(2+n\)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;\(i\)&lt;/td&gt;
      &lt;td&gt;\(j\)&lt;/td&gt;
      &lt;td&gt;looped over \(k\); accumulate&lt;/td&gt;
      &lt;td&gt;\(i\to M,\ j\to N\)&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;\(2+n\)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Here \(n_k\) is the number of contraction tiles. The count refers to batched-GEMM launches for the \(O_AO_B\) term. In the write-once family, the computation uses three batched GEMM stages: one for Stage 1, one for Stage 2, and one for the final update to \(C\). In the accumulate family, the first two stages can still be grouped, but the final update must be performed once per contraction tile \(k\), giving \(2+n_k\) GEMM launches.&lt;/p&gt;

&lt;h5 id=&quot;output-traversal-workspace-budget&quot;&gt;Output traversal and workspace budget&lt;/h5&gt;

&lt;p&gt;The previous discussion determines how the computation should be traversed. For each output tile \(C_{ij}\), the intermediates \(S_{ikj}\) and \(T_{ikj}\) must be stored, or at least produced in a workspace large enough to feed the next GEMM stage. With a limited workspace budget for \(S\) and \(T\), the goal is to choose the largest panel of output tiles that can be processed without spilling or excessive packing.&lt;/p&gt;

&lt;p&gt;The best traversal depends on whether the \(k\)-reduction can be folded into the final GEMM.&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;write-once family&lt;/strong&gt;, \(A\) is stride-1 in \(k\). For fixed output row \(i\), the factors \(U_{ik}\) are contiguous as \(k\) varies, so the whole \(k\)-sum can be folded into the GEMM contraction dimension. Thus, for a fixed row \(i\) and a block of output columns \(J\), Stage 3 has the form&lt;/p&gt;

\[C_{iJ}
\mathrel{+}=
\underbrace{
\begin{bmatrix}
U_{ik_1} &amp;amp; U_{ik_2} &amp;amp; \cdots
\end{bmatrix}
}_{b\times K_r}
\underbrace{
\begin{bmatrix}
T_{ik_1J}\\
T_{ik_2J}\\
\vdots
\end{bmatrix}
}_{K_r\times |J|b},\]

&lt;p&gt;where \(K_r\) is the total rank over the contributing \(k\) indices. Increasing \(J\) increases the \(N\) dimension of this GEMM and improves throughput. Thus the natural traversal order for \(C\) is row by row. If the workspace is large enough, then several output rows \(i\in I\) can be processed at once, giving a batched GEMM over the row block \(I\).&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;accumulate family&lt;/strong&gt;, \(A\) is stride-1 in \(i\). The axis $k$ is fused in Stage 1 and to increase the size of the GEMM we can compute \(S_{Ikj}\) for a set of rows \(I\), so the computation proceeds column-by-column. Here the key workspace decision is different. Since the final update is serial in \(k\), a deeper block of contraction indices increases the number of times the same \(C\) tile is read and written before moving on. Consequently, under a limited workspace budget, it is more effective to keep the active \(k\)-block small and allocate the remaining workspace to enlarging the free output dimension \(J\). In practice, this means fixing a single \(k\) at a time and partially updating as many \(C_{ij}\) tiles as possible, processing tile columns first&lt;/p&gt;

&lt;h5 id=&quot;pseudocode&quot;&gt;Pseudocode&lt;/h5&gt;

&lt;p&gt;The write-once (row) family, at full budget:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# A stride-1 in k  ⇒  U_ik, V_ik contiguous over k for a fixed row i
# scratch:  S[r, r, n, |J|, |I|]     T[r, n, b, |J|, |I|]

for run (block of rows I × columns J) sized to the budget:

    # Stage 1 — S_ikj = V_ik^T W_kj,  batched over (i, k, j)
    #   B stride-1 j: j fused into N   |   B stride-1 k: tilewise
    batched_gemm(&apos;T&apos;,&apos;N&apos;,  1, V[i,k], W[k,j],  0, S[i,k,j])

    # Stage 2 — T_ikj = S_ikj Z_kj^T,  batched over (i, k, j)
    batched_gemm(&apos;N&apos;,&apos;T&apos;,  1, S[i,k,j], Z[k,j],  0, T[i,k,j])

    # Stage 3 — fold k into K: one write per output row i, batched over i
    for i in I:                                 # collected into ONE batched call
        Ustack_i = [ U_i1 | U_i2 | … | U_in ]   #  b × n·r   (contiguous view)
        Tstack_i = reshape(T[i, :, :, J])       #  n·r × |J|·b
    batched_gemm(&apos;N&apos;,&apos;N&apos;,  α, Ustack, Tstack,  β=1, C[I, J])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The accumulate (column) family, at full budget:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# A stride-1 in i  ⇒  V_ik contiguous over i  (i fuses into M)
# k cannot fold into K, so it is a serial loop in Stage 3

for run (block of contraction tiles K × columns J) sized to the budget:

    # Stage 1 — batched over (k, j); i fused into M (and j into N if B stride-1 j)
    batched_gemm(&apos;T&apos;,&apos;N&apos;,  1, Vpanel[k], W[k,j],  0, S[k,j])

    # Stage 2 — batched over (k, j)
    batched_gemm(&apos;N&apos;,&apos;T&apos;,  1, S[k,j], Z[k,j],  0, T[k,j])

    # Stage 3 — the reduction: loop k, accumulate (β=1); batch the free axes (i,j)
    for k in K:
        batched_gemm(&apos;N&apos;,&apos;N&apos;,  α, U[:,k], T[k],  β=1, C[:, :])   # distinct (i,j) tiles
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h5 id=&quot;benchmark&quot;&gt;Benchmark&lt;/h5&gt;
&lt;p&gt;The benchmark below (not exhaustive) shows indeed that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt; tile-row major and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B&lt;/code&gt; tile column major (the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ij&lt;/code&gt; Stride-1 axis combination) results in the largest speedup&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Matrix size&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Tile size \(b\)&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Rank \(r\)&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Dense (ms)&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;kj (ms)&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;kk (ms)&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;ik (ms)&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;ij (ms)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;\(1024 \times 1024\)&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;64&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;16&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;21.878&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;15.892&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;30.210&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;13.131&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;9.101&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;\(2048 \times 2048\)&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;64&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;16&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;147.426&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;119.235&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;238.657&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;96.963&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;66.572&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;\(3072 \times 3072\)&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;64&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;24&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;493.884&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;585.493&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;939.301&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;484.963&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;364.823&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;\(2048 \times 2048\)&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;128&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;32&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;148.391&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;82.159&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;100.250&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;74.737&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;64.758&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;\(4096 \times 4096\)&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;256&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;64&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1181.113&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;488.826&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;489.062&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;479.816&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;480.066&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;\(2048 \times 2048\)&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;32&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;8&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;149.807&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;405.434&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;585.653&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;323.768&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;226.776&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
</content>
 </entry>
 
 <entry>
   <title>Parametric Reachability Analysis in Julia</title>
   <link href="https://alecarraro.github.io/2025/08/21/gsoc-project-with-juliareach/"/>
   <updated>2025-08-21T00:00:00+00:00</updated>
   <id>https://alecarraro.github.io/2025/08/21/gsoc-project-with-juliareach</id>
   <content type="html">&lt;div class=&quot;message&quot;&gt;
  Hey! This post summarizes my GSoC 2025 work with the 
  &lt;a href=&quot;https://juliareach.github.io/&quot;&gt;JuliaReach&lt;/a&gt;
  organization on ReachabilityAnalysis.jl under the guidance of mentors 
  &lt;a href=&quot;https://github.com/schillic&quot;&gt;Christian Schilling&lt;/a&gt; and 
  &lt;a href=&quot;https://github.com/mforets&quot;&gt;Marcelo Forets&lt;/a&gt;.
&lt;/div&gt;

&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;#outline-and-motivation&quot;&gt;Outline and Motivation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#example&quot;&gt;Example&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#contributions-overview&quot;&gt;Contributions Overview&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#references&quot;&gt;References&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#appendix&quot;&gt;Appendix&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;outline-and-motivation&quot;&gt;Outline and Motivation&lt;/h2&gt;

&lt;p&gt;In many safety-critical systems, it is often impossible to predict exactly how the system will behave due to uncertainties in initial states, inputs, or dynamics. Reachability analysis addresses this challenge by computing &lt;em&gt;flowpipes&lt;/em&gt; that enclose all possible behaviors over time, allowing engineers to rigorously verify safety.&lt;/p&gt;

&lt;p&gt;For example, imagine an autonomous car faced with a pedestrian suddenly stepping onto the road. Depending on its exact speed, distance to the pedestrian, and reaction delay, the car may need to brake immediately or steer around the obstacle, while also accounting for sensor errors. One could try to sample a range of initial speeds and positions and simulate the resulting trajectories to guide the decision. However, because the car’s dynamics are nonlinear, even small variations in initial conditions can lead to drastically different behaviors. Simulation alone therefore cannot provide the rigorous guarantees required for safety.&lt;/p&gt;

&lt;p&gt;In the &lt;a href=&quot;https://github.com/JuliaReach/ReachabilityAnalysis.jl&quot;&gt;ReachabilityAnalysis.jl&lt;/a&gt; package, several algorithms are available to compute these enclosures for different types of dynamical systems with uncertain inputs and initial states, including hybrid systems.&lt;/p&gt;

&lt;p&gt;During my project, I focused on a fundamental class of models: linear time-invariant (LTI) systems with uncertain parameters.&lt;br /&gt;
An LTI system describes the evolution of the state vector \(x(t)\) under a fixed matrix \(A\):&lt;/p&gt;

\[x&apos; = A x\]

&lt;p&gt;where \(x \in \mathbb{R}^n\).&lt;/p&gt;

&lt;p&gt;Such systems are widely used because they arise naturally when linearizing nonlinear models. In practice, however, the dynamics are rarely known exactly. Parameters in \(A\) may be uncertain due to modeling errors, noisy measurements, and other sources of uncertainty. To capture this, we consider a parametric LTI system of the form:&lt;/p&gt;

\[x&apos; = A x, \quad x(0) \in X_0, \quad A \in \mathcal{A}\]

&lt;p&gt;Here:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;\(X_0\) is the set of possible initial states, reflecting uncertainty at \(t=t_0\).&lt;/li&gt;
  &lt;li&gt;\(\mathcal{A}\) is a set of possible system matrices, representing parameter uncertainty.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is to compute a tight over-approximation of all trajectories that can arise from &lt;em&gt;any&lt;/em&gt; choice of initial state in \(X_0\) and &lt;em&gt;any&lt;/em&gt; admissible dynamics in \(\mathcal{A}\). This is significantly more challenging than the classical case with a fixed \(A\), since the solution space must account for both state uncertainty and dynamics uncertainty simultaneously.&lt;/p&gt;

&lt;p&gt;Huang et al. [1] introduced a dedicated algorithm for these parametric LTI systems with uncertain dynamics, which combines two modern set representations: matrix zonotopes for modeling uncertainty in the system matrix and sparse polynomial zonotopes (SPZs) for representing the reachable states.&lt;br /&gt;
The motivation for this choice becomes clear when we compare possible uncertainty models for \(A\) and discuss how to best enclose the evolving states.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;why-matrix-zonotopes-instead-of-interval-matrices&quot;&gt;Why Matrix Zonotopes Instead of Interval Matrices?&lt;/h3&gt;

&lt;p&gt;A classical way to represent uncertainty in \(A\) is through an interval matrix:&lt;/p&gt;

\[\mathcal{A} = \{ A \mid a_{ij} \in [\underline{a}_{ij}, \overline{a}_{ij}] \}\]

&lt;p&gt;This representation assumes that each entry of the matrix can vary independently within its interval. While simple and well studied, this independence assumption can be overly conservative: it includes combinations of parameters that may never actually occur in practice, often resulting in loose over-approximations of the reachable set.&lt;/p&gt;

&lt;p&gt;To address this, Althoff [2] introduced matrix zonotopes, which generalize standard zonotopes to matrices:&lt;/p&gt;

\[\mathcal{Z} = \{ A_0 + \sum_{i=1}^p A_i \alpha_i \;\mid\; \alpha_i \in [-1,1] \}\]

&lt;p&gt;Here, \(A_0\) is the center matrix, and the \(A_i\) are generator matrices. Matrix zonotopes preserve correlations between matrix entries, unlike interval matrices that treat them as independent. This leads to significantly tighter enclosures for uncertain dynamics, making them a more accurate and efficient choice in reachability analysis.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;why-sparse-polynomial-zonotopes&quot;&gt;Why Sparse Polynomial Zonotopes?&lt;/h3&gt;

&lt;p&gt;Once the dynamics are modeled with matrix zonotopes, the reachable states need a representation that can handle the resulting complexity without becoming overly conservative.&lt;br /&gt;
Huang et al. use sparse polynomial zonotopes for this task.&lt;/p&gt;

&lt;p&gt;SPZs extend zonotopes with polynomial terms, allowing them to capture non-convex sets while remaining closed under Minkowski sums and linear maps: two core operations in many reachability algorithms.&lt;/p&gt;

&lt;p&gt;For a more detailed description of SPZs, see [3]. For a friendly introduction, see Luca’s previous GSoC &lt;a href=&quot;https://www.lucaferranti.com/posts/2022/09/gsoc22/&quot;&gt;blog post&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;example&quot;&gt;Example&lt;/h2&gt;

&lt;p&gt;Now let’s test the algorithm with a simple 2D dynamical system:&lt;/p&gt;

\[x&apos; = A x, \quad x(0) \in X_0, \quad 
A = \begin{bmatrix} -1 &amp;amp; -5 \\ -1 &amp;amp; -1 \end{bmatrix}.\]

&lt;p&gt;In this case the origin is a &lt;em&gt;stable focus&lt;/em&gt;, since the eigenvalues are&lt;/p&gt;

\[\lambda = -1 \pm i\sqrt{5},\]

&lt;p&gt;so given an initial set \(X_0\) the trajectories should rotate and shrink toward the origin.&lt;/p&gt;

&lt;p&gt;We then compare two cases:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;the dynamics are known exactly,&lt;/li&gt;
  &lt;li&gt;the dynamics are uncertain.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For the uncertain case, assume that we are unsure about one of the entries of \(A\):&lt;/p&gt;

\[A \in \begin{bmatrix} [-1.1, -0.9]  &amp;amp; -5 \\ 1 &amp;amp; -1 \end{bmatrix}.\]

&lt;p&gt;This can be expressed as a matrix zonotope:&lt;/p&gt;

\[\mathcal{A} = 
\begin{bmatrix} -1 &amp;amp; -5 \\ 1 &amp;amp; -1 \end{bmatrix}
+ c \begin{bmatrix} 0.1 &amp;amp; 0 \\ 0 &amp;amp; 0 \end{bmatrix},
\quad c \in [-1,1].\]

&lt;p&gt;Now we will set up the reachability problem using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LazySets&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReachabilityAnalysis&lt;/code&gt;:&lt;/p&gt;

&lt;h3 id=&quot;step-1-import-the-packages&quot;&gt;Step 1: Import the packages&lt;/h3&gt;

&lt;div class=&quot;language-julia highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ReachabilityAnalysis&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IntervalMatrices&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;# required for internal calculations&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Plots&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;step-2-define-the-system-matrices&quot;&gt;Step 2: Define the system matrices&lt;/h3&gt;

&lt;div class=&quot;language-julia highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Nominal matrix&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;A0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;x&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;5.0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;;&lt;/span&gt;
       &lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Matrix zonotope with one generator&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MatrixZonotope&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;A0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;x&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.1&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;]])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;step-3-define-the-initial-set&quot;&gt;Step 3: Define the initial set&lt;/h3&gt;

&lt;p&gt;We initialize the initial conditions as a sparse polynomial zonotope:&lt;/p&gt;

&lt;div class=&quot;language-julia highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;X0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SparsePolynomialZonotope&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;x&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# center &lt;/span&gt;
    &lt;span class=&quot;mf&quot;&gt;0.1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;x&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.0&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;2.0&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# independent generators&lt;/span&gt;
    &lt;span class=&quot;mf&quot;&gt;0.1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reshape&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# dependent generators&lt;/span&gt;
    &lt;span class=&quot;x&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# exponent&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;step-4-define-the-ivp&quot;&gt;Step 4: Define the IVP&lt;/h3&gt;
&lt;p&gt;We can use the convenient macro &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@ivp&lt;/code&gt; to specify the initial states and uncertain dynamics:&lt;/p&gt;

&lt;div class=&quot;language-julia highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;prob1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;@ivp&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;A&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;∈&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;X0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;A&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;∈&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AS&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;step-5-configure-the-reachability-algorithm&quot;&gt;Step 5: Configure the reachability algorithm&lt;/h3&gt;

&lt;p&gt;We use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HLBS25&lt;/code&gt; algorithm for systems with uncertain parameters:&lt;/p&gt;

&lt;div class=&quot;language-julia highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Time step and horizon&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;δ&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;3.0&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Algorithm setup&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;alg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HLBS25&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;δ&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;δ&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;approx_model&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CorrectionHullMatrixZonotope&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;max_order&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;taylor_order&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;reduction_method&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LazySets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GIR05&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;recursive&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;message&quot;&gt;
  &lt;p&gt;&lt;strong&gt;Main parameters of &lt;code&gt;HLBS25&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;δ&lt;/strong&gt; – time step&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;approx_model&lt;/strong&gt; – discretization model (default: &lt;code&gt;CorrectionHullMatrixZonotope&lt;/code&gt;)&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;max_order&lt;/strong&gt; – maximum order of the SPZ (trade-off between accuracy and complexity)&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;taylor_order&lt;/strong&gt; – truncation order of the Taylor expansion of the exponential&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;reduction_method&lt;/strong&gt; – strategy to reduce the complexity of matrix zonotopes&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;recursive&lt;/strong&gt; – whether to compute the exponential recursively or directly&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;
    For a full description of these options, refer to the
    &lt;a href=&quot;https://github.com/JuliaReach/ReachabilityAnalysis.jl&quot;&gt;documentation&lt;/a&gt;.
  &lt;/p&gt;
&lt;/div&gt;

&lt;h3 id=&quot;step-6-solve-and-visualize&quot;&gt;Step 6: Solve and visualize&lt;/h3&gt;

&lt;div class=&quot;language-julia highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Solve&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;sol1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;solve&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prob1&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alg&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Plot&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Reachable Set&quot;&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;X&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sol1&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;plot!&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This produces the flowpipe, showing how trajectories evolve while accounting for both the initial uncertainty and the uncertainty in the dynamics.&lt;/p&gt;

&lt;p&gt;To compare with the exact case, we can repeat the experiment with a matrix zonotope that has no generators, which corresponds to a fixed matrix \(A\). The fully worked-out code can be found at the end of this page in the Appendix.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/images/gsoc-2025/hlbs25.png&quot; alt=&quot;Reachability plot&quot; width=&quot;500&quot; /&gt;
  &lt;figcaption&gt;Figure 1: Reachable set over time for exact and uncertain dynamics. Every third reach set is plotted.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;a-not-so-successful-example&quot;&gt;A Not-So-Successful Example&lt;/h2&gt;
&lt;p&gt;For small uncertainty the algorithm behaves well, but with larger uncertainty the over-approximation deteriorates. For example, if we choose a matrix zonotope with large uncertainty such as:&lt;/p&gt;

\[\mathcal{A} = 
\begin{bmatrix} -1 &amp;amp; -5 \\ -1 &amp;amp; -1 \end{bmatrix}
+ c \begin{bmatrix} 0.1 &amp;amp; 0.1 \\ 0.1 &amp;amp; 0.1 \end{bmatrix},
\quad c \in [-1,1].\]

&lt;p&gt;The results look quite bad:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/images/gsoc-2025/all-generators-comparison.png&quot; alt=&quot;Reachability plot&quot; width=&quot;500&quot; /&gt;
  &lt;figcaption&gt;Figure 2: Reachable set over time for exact and uncertain dynamics. Every third reach set is plotted.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;In this case we observe a strange behavior: the reach sets quickly bloat, growing faster than they contract around the fixed point. Interestingly, increasing the maximum order parameter seems to mitigate the bloating somewhat.&lt;/p&gt;

&lt;h3 id=&quot;what-is-going-wrong&quot;&gt;What is going wrong?&lt;/h3&gt;
&lt;p&gt;Together with my mentors, I investigated this issue for several weeks. The problem appears to stem from the repeated use of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reduce_order&lt;/code&gt; method. Keeping it brief, at each step the algorithm computes and applies the matrix zonotope exponential \(e^{\mathcal{A}}\) to propagate the state set forward in time. This increases the number of generators in the SPZ, forcing order reduction to keep the complexity manageable. However, repeated reduction seems to discard generators that capture important information, while the remaining ones grow excessively under successive exponentials.&lt;/p&gt;

&lt;p&gt;This is quite puzzling, since &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reduce_order&lt;/code&gt; works well in other parts of the library. At this point it is still unclear to us whether this instability reflects a deeper stability issue, perhaps some relationship between the amount of uncertainty and the step size, similar to time-stepping constraints in ODE solvers, or whether it comes from a subtle bug in the implementation of one of the many building blocks. What is certain is that the issue seems specific to this algorithm, which is still very new and, before our work, had only been implemented in the library &lt;a href=&quot;https://tumcps.github.io/CORA/&quot;&gt;CORA&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;whats-next&quot;&gt;What’s next?&lt;/h3&gt;
&lt;p&gt;After GSoC, we made a plan to further investigate the problem and eventually turn the algorithm into a stable release.
I have also implemented the non-homogeneous case \(x&apos; = \mathcal{A}x + \mathcal{B}u\). However, it is not ready for release yet, since some implementation details still require further discussion with the mentors.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;contributions-overview&quot;&gt;Contributions Overview&lt;/h2&gt;
&lt;p&gt;In the tables below I summarize my contributions to the different packages in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JuliaReach&lt;/code&gt; ecosystem to implement the reachability algorithm described above.&lt;/p&gt;

&lt;h3 id=&quot;1-lazysetsjl-matrix-zonotopes&quot;&gt;1. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LazySets.jl&lt;/code&gt;: Matrix Zonotopes&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;PR Title&lt;/th&gt;
      &lt;th&gt;PR #&lt;/th&gt;
      &lt;th&gt;Category&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;overapproximate&lt;/code&gt; for matrix zonotope exponential&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/JuliaReach/LazySets.jl/pull/4000&quot;&gt;#4000&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Feature&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Add operations on matrix zonotopes&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/JuliaReach/LazySets.jl/pull/3999&quot;&gt;#3999&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Feature&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;overapproximate&lt;/code&gt; for matrix zonotope multiplication&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/JuliaReach/LazySets.jl/pull/3996&quot;&gt;#3996&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Feature&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Preserve &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;indexvector&lt;/code&gt; in linear map for MZ&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/JuliaReach/LazySets.jl/pull/3985&quot;&gt;#3985&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Fix&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Extend &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ExponentialMap&lt;/code&gt; to support &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MatrixZonotope&lt;/code&gt;s&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/JuliaReach/LazySets.jl/pull/3970&quot;&gt;#3970&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Feature&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;linear_map&lt;/code&gt; between MatrixZonotope and SPZ&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/JuliaReach/LazySets.jl/pull/3969&quot;&gt;#3969&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Feature&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Fix in-place scaling for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MatrixZonotope&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/JuliaReach/LazySets.jl/pull/3967&quot;&gt;#3967&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Fix&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Add methods and type for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MatrixSets&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/JuliaReach/LazySets.jl/pull/3966&quot;&gt;#3966&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Feature&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;norm&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;overapproximate_norm&lt;/code&gt; for matrix zonotope&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/JuliaReach/LazySets.jl/pull/3941&quot;&gt;#3941&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Feature&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Refactor and extend &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MatrixSets&lt;/code&gt; module&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/JuliaReach/LazySets.jl/pull/3933&quot;&gt;#3933&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Refactor&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;2-lazysetsjl-general-improvements-and-fixes&quot;&gt;2. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LazySets.jl&lt;/code&gt;: General improvements and fixes&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;PR Title&lt;/th&gt;
      &lt;th&gt;PR #&lt;/th&gt;
      &lt;th&gt;Category&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Optimize &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;remove_redundant_generators&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/JuliaReach/LazySets.jl/pull/3998&quot;&gt;#3998&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Feature&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Improve &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;remove_redundant_generators&lt;/code&gt; for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SPZ&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/JuliaReach/LazySets.jl/pull/3986&quot;&gt;#3986&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Improvement&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;overapproximate&lt;/code&gt; for exponential map for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SPZ&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Zonotope&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/JuliaReach/LazySets.jl/pull/3979&quot;&gt;#3979&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Feature&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Fix &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;isuniversal&lt;/code&gt; and constructor for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Polygon&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/JuliaReach/LazySets.jl/pull/3978&quot;&gt;#3978&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Fix&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Preserve ID in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;minkowski_sum&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cartesian_product&lt;/code&gt; between &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SPZ&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Zonotope&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/JuliaReach/LazySets.jl/pull/3977&quot;&gt;#3977&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Improvement&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Make &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;overapproximate&lt;/code&gt; of ASPZ with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UnionSetArray{Zonotope}&lt;/code&gt; more robust&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/JuliaReach/LazySets.jl/pull/3972&quot;&gt;#3972&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Improvement&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Add overapproximation of the l1 norm of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Zonotope&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/JuliaReach/LazySets.jl/pull/3925&quot;&gt;#3925&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Feature&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Faster l1 norm for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AbstractZonotope&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/JuliaReach/LazySets.jl/pull/3924&quot;&gt;#3924&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Improvement&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scale&lt;/code&gt; for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SPZ&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/JuliaReach/LazySets.jl/pull/3908&quot;&gt;#3908&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Feature&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;merge_id&lt;/code&gt; and generalize &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exact_sum&lt;/code&gt; for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SPZ&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/JuliaReach/LazySets.jl/pull/3905&quot;&gt;#3905&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Feature&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Add sampler for sparse polynomial zonotopes&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/JuliaReach/LazySets.jl/pull/3847&quot;&gt;#3847&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Feature&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;3-mathematicalsystemsjl-and-reachabilityanalysisjl&quot;&gt;3. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MathematicalSystems.jl&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReachabilityAnalysis.jl&lt;/code&gt;&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;PR Title&lt;/th&gt;
      &lt;th&gt;PR #&lt;/th&gt;
      &lt;th&gt;Notes&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parametric&lt;/code&gt; systems&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/JuliaReach/MathematicalSystems.jl/pull/332&quot;&gt;#332&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Feature&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Add HLBS25 algorithm for linear parametric systems&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/JuliaReach/ReachabilityAnalysis.jl/pull/931&quot;&gt;#931&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Feature&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;p&gt;[1] Yushen Huang, Ertai Luo, Stanley Bak, Yifan Sun, &lt;em&gt;Reachability analysis for linear systems with uncertain parameters using polynomial zonotopes&lt;/em&gt;, Nonlinear Analysis: Hybrid Systems, Volume 56, 2025, 101571, ISSN 1751-570X. &lt;a href=&quot;https://doi.org/10.1016/j.nahs.2024.101571&quot;&gt;DOI&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[2] Matthias Althoff, &lt;em&gt;Reachability Analysis and its Application to the Safety Assessment of Autonomous Cars&lt;/em&gt;, PhD thesis, Technische Universität München, July 2010.&lt;/p&gt;

&lt;p&gt;[3] Niklas Kochdumper, Matthias Althoff, &lt;em&gt;Sparse Polynomial Zonotopes: A Novel Set Representation for Reachability Analysis&lt;/em&gt;, IEEE Transactions on Automatic Control, Vol. 66, No. 9, Sept. 2021, pp. 4043–4058. &lt;a href=&quot;https://doi.org/10.1109/TAC.2020.3024348&quot;&gt;DOI&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;appendix&quot;&gt;Appendix&lt;/h2&gt;
&lt;div class=&quot;language-julia highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ReachabilityAnalysis&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IntervalMatrices&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Plots&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;N&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Float64&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;A0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MatrixZonotope&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;A0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;x&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;zeros&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)])&lt;/span&gt; 

&lt;span class=&quot;c&quot;&gt;# Initial set&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;X0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SparsePolynomialZonotope&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.1&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.0&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;2.0&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.1&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reshape&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;x&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Two problems: no variation vs. small generator variation&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;prob1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;@ivp&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;A&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;∈&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;X0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;A&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;∈&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AS&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MatrixZonotope&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;A0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;x&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.1&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.1&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.1&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.1&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;]])&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;prob2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;@ivp&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;A&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;∈&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;X0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;A&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;∈&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AS&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;δ&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;3.0&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;alg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HLBS25&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;δ&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;δ&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;approx_model&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CorrectionHullMatrixZonotope&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;max_order&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;taylor_order&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;reduction_method&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LazySets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GIR05&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;recursive&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;sol1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;solve&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prob1&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alg&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;sol2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;solve&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prob2&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alg&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# plotting function&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt; plot_flowpipe&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sets&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;X0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kwargs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enumerate&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sets&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;plot!&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vars&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alpha&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.3&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lw&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;p1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plot_flowpipe&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sol1&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;X0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Flowpipe: exact A&quot;&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;xlabel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;x₁&quot;&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ylabel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;x₂&quot;&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;legend&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;p2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plot_flowpipe&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sol2&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;X0&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; 
    &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Flowpipe: uncertain A &quot;&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;xlabel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;x₁&quot;&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ylabel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;x₂&quot;&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;legend&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# common limits for better comparison&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;xlims_common&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xlims&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p1&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xlims&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p2&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xlims&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p1&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xlims&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p2&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ylims_common&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ylims&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p1&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ylims&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p2&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ylims&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p1&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ylims&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p2&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;plot!&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p1&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xlims&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xlims_common&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ylims&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ylims_common&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;plot!&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p2&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xlims&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xlims_common&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ylims&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ylims_common&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p1&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p2&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;420&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;savefig&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;reach_comp.png&quot;&lt;/span&gt;&lt;span class=&quot;x&quot;&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 

</feed>
