Spaces:
Running
Running
feat(cholesky): improve layout and controls for Cholesky decomposition and solving linear systems
Browse files- pages/linear_algebra/02_cholesky.py +131 -119
- pages/linear_algebra/03_cholesky_solve.py +140 -157
pages/linear_algebra/02_cholesky.py
CHANGED
|
@@ -4,20 +4,20 @@ import numpy as np
|
|
| 4 |
|
| 5 |
st.title("Cholesky Decomposition")
|
| 6 |
|
| 7 |
-
st.markdown(
|
| 8 |
-
|
| 9 |
-
For a **symmetric positive definite** matrix $A$, Cholesky gives $A = LL^\top$
|
| 10 |
-
where $L$ is lower triangular. The algorithm computes $L$ element by element.
|
| 11 |
-
"""
|
| 12 |
-
)
|
| 13 |
-
|
| 14 |
-
#
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
|
|
|
| 18 |
n = st.radio("Matrix size", [3, 4], horizontal=True)
|
| 19 |
|
| 20 |
-
with col_preset:
|
| 21 |
preset = st.selectbox(
|
| 22 |
"Example matrix",
|
| 23 |
["Simple diagonal-dominant", "Correlation matrix", "Random SPD"],
|
|
@@ -111,30 +111,6 @@ def cholesky_steps(A):
|
|
| 111 |
steps = cholesky_steps(A)
|
| 112 |
total_steps = len(steps)
|
| 113 |
|
| 114 |
-
# Buttons
|
| 115 |
-
col_prev, col_next, col_reset = st.columns([1, 1, 1])
|
| 116 |
-
|
| 117 |
-
with col_prev:
|
| 118 |
-
if st.button("β Previous", disabled=st.session_state.chol_step == 0):
|
| 119 |
-
st.session_state.chol_step -= 1
|
| 120 |
-
st.rerun()
|
| 121 |
-
|
| 122 |
-
with col_next:
|
| 123 |
-
if st.button("Next β", disabled=st.session_state.chol_step >= total_steps):
|
| 124 |
-
st.session_state.chol_step += 1
|
| 125 |
-
st.rerun()
|
| 126 |
-
|
| 127 |
-
with col_reset:
|
| 128 |
-
if st.button("Reset"):
|
| 129 |
-
st.session_state.chol_step = 0
|
| 130 |
-
st.rerun()
|
| 131 |
-
|
| 132 |
-
current_step = st.session_state.chol_step
|
| 133 |
-
|
| 134 |
-
# Progress
|
| 135 |
-
st.progress(current_step / total_steps if total_steps > 0 else 0)
|
| 136 |
-
st.caption(f"Step {current_step} of {total_steps}")
|
| 137 |
-
|
| 138 |
|
| 139 |
# Helper to format matrix as LaTeX
|
| 140 |
def matrix_to_latex(M, highlight=None, computed_mask=None):
|
|
@@ -160,93 +136,129 @@ def matrix_to_latex(M, highlight=None, computed_mask=None):
|
|
| 160 |
return r"\begin{pmatrix} " + r" \\ ".join(rows) + r" \end{pmatrix}"
|
| 161 |
|
| 162 |
|
| 163 |
-
#
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
col_A, col_eq, col_L, col_Lt = st.columns([2, 0.5, 2, 2])
|
| 167 |
-
|
| 168 |
-
# Get current L state
|
| 169 |
-
if current_step == 0:
|
| 170 |
-
L_current = np.zeros_like(A)
|
| 171 |
-
computed = np.zeros((n, n), dtype=bool)
|
| 172 |
-
highlight = None
|
| 173 |
-
else:
|
| 174 |
-
i, j, formula, val, L_current = steps[current_step - 1]
|
| 175 |
-
computed = np.abs(L_current) > 1e-10
|
| 176 |
-
# Upper triangle is always "computed" as 0
|
| 177 |
-
for ii in range(n):
|
| 178 |
-
for jj in range(ii + 1, n):
|
| 179 |
-
computed[ii, jj] = True
|
| 180 |
-
highlight = (i, j)
|
| 181 |
-
|
| 182 |
-
with col_A:
|
| 183 |
-
st.markdown("**Matrix $A$:**")
|
| 184 |
-
st.latex(f"A = {matrix_to_latex(A)}")
|
| 185 |
-
|
| 186 |
-
with col_eq:
|
| 187 |
-
st.markdown(" ")
|
| 188 |
-
st.latex("=")
|
| 189 |
-
|
| 190 |
-
with col_L:
|
| 191 |
-
st.markdown("**Cholesky $L$:**")
|
| 192 |
-
# Create mask for computed entries
|
| 193 |
-
mask = np.zeros((n, n), dtype=bool)
|
| 194 |
-
for ii in range(n):
|
| 195 |
-
for jj in range(ii + 1, n):
|
| 196 |
-
mask[ii, jj] = True # Upper triangle always shown as 0
|
| 197 |
-
if current_step > 0:
|
| 198 |
-
for step_idx in range(current_step):
|
| 199 |
-
si, sj, _, _, _ = steps[step_idx]
|
| 200 |
-
mask[si, sj] = True
|
| 201 |
-
st.latex(
|
| 202 |
-
f"L = {matrix_to_latex(L_current, highlight=highlight, computed_mask=mask)}"
|
| 203 |
-
)
|
| 204 |
|
| 205 |
-
|
| 206 |
-
st.
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
|
| 214 |
-
|
| 215 |
-
st.markdown("---")
|
| 216 |
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
r"L_{jj} = \sqrt{A_{jj} - \sum_{k=1}^{j-1} L_{jk}^2} \qquad L_{ij} = \frac{A_{ij} - \sum_{k=1}^{j-1} L_{ik} L_{jk}}{L_{jj}} \text{ for } i > j"
|
| 223 |
-
)
|
| 224 |
-
st.info("Click **Next β** to start computing elements of $L$")
|
| 225 |
-
elif current_step <= total_steps:
|
| 226 |
-
i, j, formula, val, _ = steps[current_step - 1]
|
| 227 |
-
if i == j:
|
| 228 |
-
st.markdown(f"**Computing diagonal element $L_{{{i + 1}{j + 1}}}$:**")
|
| 229 |
else:
|
| 230 |
-
|
| 231 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
st.markdown("---")
|
| 236 |
-
st.success("**β Cholesky decomposition complete!**")
|
| 237 |
-
L_final = steps[-1][4]
|
| 238 |
-
LLT = L_final @ L_final.T
|
| 239 |
-
st.markdown("**Verification:** $LL^\\top = A$")
|
| 240 |
-
|
| 241 |
-
col_v1, col_v2, col_v3 = st.columns([2, 0.5, 2])
|
| 242 |
-
with col_v1:
|
| 243 |
-
st.latex(f"LL^\\top = {matrix_to_latex(LLT)}")
|
| 244 |
-
with col_v2:
|
| 245 |
st.latex("=")
|
| 246 |
-
with col_v3:
|
| 247 |
-
st.latex(f"A = {matrix_to_latex(A)}")
|
| 248 |
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
st.title("Cholesky Decomposition")
|
| 6 |
|
| 7 |
+
# st.markdown(
|
| 8 |
+
# r"""
|
| 9 |
+
# For a **symmetric positive definite** matrix $A$, Cholesky gives $A = LL^\top$
|
| 10 |
+
# where $L$ is lower triangular. The algorithm computes $L$ element by element.
|
| 11 |
+
# """
|
| 12 |
+
# )
|
| 13 |
+
|
| 14 |
+
# Controls and visualizations layout
|
| 15 |
+
col_controls, col_content = st.columns([1, 3])
|
| 16 |
+
|
| 17 |
+
# LEFT COLUMN: Controls
|
| 18 |
+
with col_controls:
|
| 19 |
n = st.radio("Matrix size", [3, 4], horizontal=True)
|
| 20 |
|
|
|
|
| 21 |
preset = st.selectbox(
|
| 22 |
"Example matrix",
|
| 23 |
["Simple diagonal-dominant", "Correlation matrix", "Random SPD"],
|
|
|
|
| 111 |
steps = cholesky_steps(A)
|
| 112 |
total_steps = len(steps)
|
| 113 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
|
| 115 |
# Helper to format matrix as LaTeX
|
| 116 |
def matrix_to_latex(M, highlight=None, computed_mask=None):
|
|
|
|
| 136 |
return r"\begin{pmatrix} " + r" \\ ".join(rows) + r" \end{pmatrix}"
|
| 137 |
|
| 138 |
|
| 139 |
+
# Add buttons and progress to left column
|
| 140 |
+
with col_controls:
|
| 141 |
+
st.markdown("---")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
|
| 143 |
+
# Buttons
|
| 144 |
+
col_prev, col_next, col_reset = st.columns([1, 1, 1])
|
| 145 |
+
|
| 146 |
+
with col_prev:
|
| 147 |
+
if st.button(
|
| 148 |
+
"β Prev", disabled=st.session_state.chol_step == 0, use_container_width=True
|
| 149 |
+
):
|
| 150 |
+
st.session_state.chol_step -= 1
|
| 151 |
+
st.rerun()
|
| 152 |
+
|
| 153 |
+
with col_next:
|
| 154 |
+
if st.button(
|
| 155 |
+
"Next β",
|
| 156 |
+
disabled=st.session_state.chol_step >= total_steps,
|
| 157 |
+
use_container_width=True,
|
| 158 |
+
):
|
| 159 |
+
st.session_state.chol_step += 1
|
| 160 |
+
st.rerun()
|
| 161 |
+
|
| 162 |
+
with col_reset:
|
| 163 |
+
if st.button("Reset", use_container_width=True):
|
| 164 |
+
st.session_state.chol_step = 0
|
| 165 |
+
st.rerun()
|
| 166 |
+
|
| 167 |
+
# Progress
|
| 168 |
+
st.progress(st.session_state.chol_step / total_steps if total_steps > 0 else 0)
|
| 169 |
+
st.caption(f"Step {st.session_state.chol_step} of {total_steps}")
|
| 170 |
+
|
| 171 |
+
# RIGHT COLUMN: Content
|
| 172 |
+
with col_content:
|
| 173 |
+
current_step = st.session_state.chol_step
|
| 174 |
+
|
| 175 |
+
# Display matrices side by side
|
| 176 |
+
st.markdown("---")
|
| 177 |
|
| 178 |
+
col_A, col_eq, col_L, col_Lt = st.columns([2, 0.5, 2, 2])
|
|
|
|
| 179 |
|
| 180 |
+
# Get current L state
|
| 181 |
+
if current_step == 0:
|
| 182 |
+
L_current = np.zeros_like(A)
|
| 183 |
+
computed = np.zeros((n, n), dtype=bool)
|
| 184 |
+
highlight = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
else:
|
| 186 |
+
i, j, formula, val, L_current = steps[current_step - 1]
|
| 187 |
+
computed = np.abs(L_current) > 1e-10
|
| 188 |
+
# Upper triangle is always "computed" as 0
|
| 189 |
+
for ii in range(n):
|
| 190 |
+
for jj in range(ii + 1, n):
|
| 191 |
+
computed[ii, jj] = True
|
| 192 |
+
highlight = (i, j)
|
| 193 |
+
|
| 194 |
+
with col_A:
|
| 195 |
+
st.markdown("**Matrix $A$:**")
|
| 196 |
+
st.latex(f"A = {matrix_to_latex(A)}")
|
| 197 |
|
| 198 |
+
with col_eq:
|
| 199 |
+
st.markdown(" ")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
st.latex("=")
|
|
|
|
|
|
|
| 201 |
|
| 202 |
+
with col_L:
|
| 203 |
+
st.markdown("**Cholesky $L$:**")
|
| 204 |
+
# Create mask for computed entries
|
| 205 |
+
mask = np.zeros((n, n), dtype=bool)
|
| 206 |
+
for ii in range(n):
|
| 207 |
+
for jj in range(ii + 1, n):
|
| 208 |
+
mask[ii, jj] = True # Upper triangle always shown as 0
|
| 209 |
+
if current_step > 0:
|
| 210 |
+
for step_idx in range(current_step):
|
| 211 |
+
si, sj, _, _, _ = steps[step_idx]
|
| 212 |
+
mask[si, sj] = True
|
| 213 |
+
st.latex(
|
| 214 |
+
f"L = {matrix_to_latex(L_current, highlight=highlight, computed_mask=mask)}"
|
| 215 |
+
)
|
| 216 |
+
|
| 217 |
+
with col_Lt:
|
| 218 |
+
st.markdown("**Transpose $L^\\top$:**")
|
| 219 |
+
L_T = L_current.T
|
| 220 |
+
mask_T = mask.T
|
| 221 |
+
highlight_T = (highlight[1], highlight[0]) if highlight else None
|
| 222 |
+
st.latex(
|
| 223 |
+
f"L^\\top = {matrix_to_latex(L_T, highlight=highlight_T, computed_mask=mask_T)}"
|
| 224 |
+
)
|
| 225 |
+
|
| 226 |
+
# Show current step formula
|
| 227 |
+
st.markdown("---")
|
| 228 |
+
|
| 229 |
+
if current_step == 0:
|
| 230 |
+
st.markdown(
|
| 231 |
+
"**Cholesky Algorithm:** For each column $j$, compute elements from top to bottom:"
|
| 232 |
+
)
|
| 233 |
+
st.latex(
|
| 234 |
+
r"L_{jj} = \sqrt{A_{jj} - \sum_{k=1}^{j-1} L_{jk}^2} \qquad L_{ij} = \frac{A_{ij} - \sum_{k=1}^{j-1} L_{ik} L_{jk}}{L_{jj}} \text{ for } i > j"
|
| 235 |
+
)
|
| 236 |
+
st.info("Click **Next β** to start computing elements of $L$")
|
| 237 |
+
elif current_step <= total_steps:
|
| 238 |
+
i, j, formula, val, _ = steps[current_step - 1]
|
| 239 |
+
if i == j:
|
| 240 |
+
st.markdown(f"**Computing diagonal element $L_{{{i + 1}{j + 1}}}$:**")
|
| 241 |
+
else:
|
| 242 |
+
st.markdown(f"**Computing off-diagonal element $L_{{{i + 1}{j + 1}}}$:**")
|
| 243 |
+
st.latex(formula + f" = {val:.4f}")
|
| 244 |
+
|
| 245 |
+
# Verification at the end
|
| 246 |
+
if current_step == total_steps:
|
| 247 |
+
st.markdown("---")
|
| 248 |
+
st.success("**β Cholesky decomposition complete!**")
|
| 249 |
+
# L_final = steps[-1][4]
|
| 250 |
+
# LLT = L_final @ L_final.T
|
| 251 |
+
# st.markdown("**Verification:** $LL^\\top = A$")
|
| 252 |
+
|
| 253 |
+
# col_v1, col_v2, col_v3 = st.columns([2, 0.5, 2])
|
| 254 |
+
# with col_v1:
|
| 255 |
+
# st.latex(f"LL^\\top = {matrix_to_latex(LLT)}")
|
| 256 |
+
# with col_v2:
|
| 257 |
+
# st.latex("=")
|
| 258 |
+
# with col_v3:
|
| 259 |
+
# st.latex(f"A = {matrix_to_latex(A)}")
|
| 260 |
+
|
| 261 |
+
# # Check numerical accuracy
|
| 262 |
+
# error = np.max(np.abs(LLT - A))
|
| 263 |
+
# if error < 1e-10:
|
| 264 |
+
# st.markdown(f"Maximum error: ${error:.2e}$ β")
|
pages/linear_algebra/03_cholesky_solve.py
CHANGED
|
@@ -4,22 +4,12 @@ import numpy as np
|
|
| 4 |
|
| 5 |
st.title("Solving Linear Systems with Cholesky")
|
| 6 |
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
To solve $A\mathbf{x} = \mathbf{b}$ where $A$ is symmetric positive definite:
|
| 10 |
-
1. Compute Cholesky: $A = LL^\top$
|
| 11 |
-
2. Solve $L\mathbf{y} = \mathbf{b}$ (forward substitution)
|
| 12 |
-
3. Solve $L^\top\mathbf{x} = \mathbf{y}$ (backward substitution)
|
| 13 |
-
"""
|
| 14 |
-
)
|
| 15 |
-
|
| 16 |
-
# Size selection
|
| 17 |
-
col_size, col_preset = st.columns(2)
|
| 18 |
-
|
| 19 |
-
with col_size:
|
| 20 |
-
n = st.radio("System size", [3, 4], horizontal=True)
|
| 21 |
|
| 22 |
-
|
|
|
|
|
|
|
| 23 |
preset = st.selectbox(
|
| 24 |
"Example",
|
| 25 |
["Simple system", "Unit RHS", "Random"],
|
|
@@ -29,8 +19,8 @@ with col_preset:
|
|
| 29 |
np.random.seed(123)
|
| 30 |
if preset == "Simple system":
|
| 31 |
if n == 3:
|
| 32 |
-
A = np.array([[
|
| 33 |
-
b = np.array([
|
| 34 |
else:
|
| 35 |
A = np.array(
|
| 36 |
[
|
|
@@ -138,46 +128,58 @@ if st.session_state.solve_n != n or st.session_state.solve_preset != preset:
|
|
| 138 |
st.session_state.solve_n = n
|
| 139 |
st.session_state.solve_preset = preset
|
| 140 |
|
| 141 |
-
#
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
with col_prev:
|
| 145 |
-
if st.button("β Previous", disabled=st.session_state.solve_step == 0):
|
| 146 |
-
st.session_state.solve_step -= 1
|
| 147 |
-
st.rerun()
|
| 148 |
-
|
| 149 |
-
with col_next:
|
| 150 |
-
if st.button("Next β", disabled=st.session_state.solve_step >= total_steps):
|
| 151 |
-
st.session_state.solve_step += 1
|
| 152 |
-
st.rerun()
|
| 153 |
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
|
| 159 |
current_step = st.session_state.solve_step
|
| 160 |
|
| 161 |
-
# Progress
|
| 162 |
-
st.progress(current_step / total_steps if total_steps > 0 else 0)
|
| 163 |
-
|
| 164 |
-
# Determine phase
|
| 165 |
-
if current_step == 0:
|
| 166 |
-
phase = "setup"
|
| 167 |
-
elif current_step <= total_forward:
|
| 168 |
-
phase = "forward"
|
| 169 |
-
else:
|
| 170 |
-
phase = "backward"
|
| 171 |
-
|
| 172 |
-
if phase == "setup":
|
| 173 |
-
st.caption("Step 0: Setup")
|
| 174 |
-
elif phase == "forward":
|
| 175 |
-
st.caption(f"Forward substitution: Step {current_step} of {total_forward}")
|
| 176 |
-
else:
|
| 177 |
-
st.caption(
|
| 178 |
-
f"Backward substitution: Step {current_step - total_forward} of {total_backward}"
|
| 179 |
-
)
|
| 180 |
-
|
| 181 |
|
| 182 |
# Helper to format vector as LaTeX
|
| 183 |
def vector_to_latex(v, highlight=None, computed_mask=None):
|
|
@@ -204,115 +206,96 @@ def matrix_to_latex(M):
|
|
| 204 |
return r"\begin{pmatrix} " + r" \\ ".join(rows) + r" \end{pmatrix}"
|
| 205 |
|
| 206 |
|
| 207 |
-
#
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
col1, col2, col3 = st.columns([3, 3, 3])
|
| 212 |
-
|
| 213 |
-
with col1:
|
| 214 |
-
st.markdown("**System $A\\mathbf{x} = \\mathbf{b}$:**")
|
| 215 |
-
st.latex(f"A = {matrix_to_latex(A)}")
|
| 216 |
-
st.latex(f"\\mathbf{{b}} = {vector_to_latex(b)}")
|
| 217 |
-
|
| 218 |
-
with col2:
|
| 219 |
-
st.markdown("**Cholesky factor $L$:**")
|
| 220 |
-
st.latex(f"L = {matrix_to_latex(L)}")
|
| 221 |
-
|
| 222 |
-
# Compute current state of y and x
|
| 223 |
-
if current_step == 0:
|
| 224 |
-
y_current = np.zeros(n)
|
| 225 |
-
x_current = np.zeros(n)
|
| 226 |
-
y_mask = np.zeros(n, dtype=bool)
|
| 227 |
-
x_mask = np.zeros(n, dtype=bool)
|
| 228 |
-
y_highlight = None
|
| 229 |
-
x_highlight = None
|
| 230 |
-
elif current_step <= total_forward:
|
| 231 |
-
# In forward substitution
|
| 232 |
-
_, _, _, y_current = forward_steps[current_step - 1]
|
| 233 |
-
x_current = np.zeros(n)
|
| 234 |
-
y_mask = np.zeros(n, dtype=bool)
|
| 235 |
-
for s in range(current_step):
|
| 236 |
-
idx = forward_steps[s][0]
|
| 237 |
-
y_mask[idx] = True
|
| 238 |
-
x_mask = np.zeros(n, dtype=bool)
|
| 239 |
-
y_highlight = forward_steps[current_step - 1][0]
|
| 240 |
-
x_highlight = None
|
| 241 |
-
else:
|
| 242 |
-
# In backward substitution
|
| 243 |
-
y_current = y_final.copy()
|
| 244 |
-
backward_idx = current_step - total_forward - 1
|
| 245 |
-
_, _, _, x_current = backward_steps[backward_idx]
|
| 246 |
-
y_mask = np.ones(n, dtype=bool)
|
| 247 |
-
x_mask = np.zeros(n, dtype=bool)
|
| 248 |
-
for s in range(backward_idx + 1):
|
| 249 |
-
idx = backward_steps[s][0]
|
| 250 |
-
x_mask[idx] = True
|
| 251 |
-
y_highlight = None
|
| 252 |
-
x_highlight = backward_steps[backward_idx][0]
|
| 253 |
-
|
| 254 |
-
with col3:
|
| 255 |
-
st.markdown("**Intermediate $\\mathbf{y}$ and solution $\\mathbf{x}$:**")
|
| 256 |
-
col_y, col_x = st.columns(2)
|
| 257 |
-
with col_y:
|
| 258 |
-
st.latex(
|
| 259 |
-
f"\\mathbf{{y}} = {vector_to_latex(y_current, highlight=y_highlight, computed_mask=y_mask)}"
|
| 260 |
-
)
|
| 261 |
-
with col_x:
|
| 262 |
-
st.latex(
|
| 263 |
-
f"\\mathbf{{x}} = {vector_to_latex(x_current, highlight=x_highlight, computed_mask=x_mask)}"
|
| 264 |
-
)
|
| 265 |
|
| 266 |
-
|
| 267 |
-
st.markdown("
|
|
|
|
|
|
|
| 268 |
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
i, formula, val, _ = forward_steps[step_idx]
|
| 280 |
-
st.markdown(f"**Forward substitution:** Computing $y_{{{i + 1}}}$")
|
| 281 |
-
st.latex(formula + f" = {val:.4f}")
|
| 282 |
|
| 283 |
-
if
|
| 284 |
-
st.success("β Forward substitution complete!")
|
| 285 |
st.markdown(
|
| 286 |
-
"**
|
| 287 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
|
| 289 |
-
else: # backward
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
# Final verification
|
| 296 |
-
if current_step == total_steps:
|
| 297 |
-
st.markdown("---")
|
| 298 |
-
st.success("**β System solved!**")
|
| 299 |
-
|
| 300 |
-
x_final = backward_steps[-1][3]
|
| 301 |
-
Ax = A @ x_final
|
| 302 |
-
|
| 303 |
-
st.markdown("**Verification:** $A\\mathbf{x} = \\mathbf{b}$")
|
| 304 |
-
|
| 305 |
-
col_v1, col_v2, col_v3, col_v4, col_v5 = st.columns([2, 0.3, 1.5, 0.3, 1.5])
|
| 306 |
-
with col_v1:
|
| 307 |
-
st.latex(f"A\\mathbf{{x}} = {matrix_to_latex(A)} {vector_to_latex(x_final)}")
|
| 308 |
-
with col_v2:
|
| 309 |
-
st.latex("=")
|
| 310 |
-
with col_v3:
|
| 311 |
-
st.latex(f"{vector_to_latex(Ax)}")
|
| 312 |
-
with col_v4:
|
| 313 |
-
st.latex("=")
|
| 314 |
-
with col_v5:
|
| 315 |
-
st.latex(f"\\mathbf{{b}} = {vector_to_latex(b)}")
|
| 316 |
|
| 317 |
-
|
| 318 |
-
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
st.title("Solving Linear Systems with Cholesky")
|
| 6 |
|
| 7 |
+
# Create layout
|
| 8 |
+
col_controls, col_content = st.columns([1, 3])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
+
# LEFT COLUMN: Controls
|
| 11 |
+
with col_controls:
|
| 12 |
+
n = st.radio("System size", [3, 4], horizontal=True)
|
| 13 |
preset = st.selectbox(
|
| 14 |
"Example",
|
| 15 |
["Simple system", "Unit RHS", "Random"],
|
|
|
|
| 19 |
np.random.seed(123)
|
| 20 |
if preset == "Simple system":
|
| 21 |
if n == 3:
|
| 22 |
+
A = np.array([[1.0, 1.0, 1.0], [1.0, 2.0, 2.0], [1.0, 2.0, 3.0]])
|
| 23 |
+
b = np.array([3.0, 5.0, 6.0])
|
| 24 |
else:
|
| 25 |
A = np.array(
|
| 26 |
[
|
|
|
|
| 128 |
st.session_state.solve_n = n
|
| 129 |
st.session_state.solve_preset = preset
|
| 130 |
|
| 131 |
+
# Continue LEFT COLUMN: Add buttons and progress
|
| 132 |
+
with col_controls:
|
| 133 |
+
st.markdown("---")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
+
# Buttons
|
| 136 |
+
col_prev, col_next, col_reset = st.columns([1, 1, 1])
|
| 137 |
+
|
| 138 |
+
with col_prev:
|
| 139 |
+
if st.button(
|
| 140 |
+
"β Prev",
|
| 141 |
+
disabled=st.session_state.solve_step == 0,
|
| 142 |
+
use_container_width=True,
|
| 143 |
+
):
|
| 144 |
+
st.session_state.solve_step -= 1
|
| 145 |
+
st.rerun()
|
| 146 |
+
|
| 147 |
+
with col_next:
|
| 148 |
+
if st.button(
|
| 149 |
+
"Next β",
|
| 150 |
+
disabled=st.session_state.solve_step >= total_steps,
|
| 151 |
+
use_container_width=True,
|
| 152 |
+
):
|
| 153 |
+
st.session_state.solve_step += 1
|
| 154 |
+
st.rerun()
|
| 155 |
+
|
| 156 |
+
with col_reset:
|
| 157 |
+
if st.button("Reset", use_container_width=True):
|
| 158 |
+
st.session_state.solve_step = 0
|
| 159 |
+
st.rerun()
|
| 160 |
+
|
| 161 |
+
# Progress
|
| 162 |
+
st.progress(st.session_state.solve_step / total_steps if total_steps > 0 else 0)
|
| 163 |
+
|
| 164 |
+
# Caption
|
| 165 |
+
phase = (
|
| 166 |
+
"setup"
|
| 167 |
+
if st.session_state.solve_step == 0
|
| 168 |
+
else ("forward" if st.session_state.solve_step <= total_forward else "backward")
|
| 169 |
+
)
|
| 170 |
+
if phase == "setup":
|
| 171 |
+
st.caption("Step 0: Setup")
|
| 172 |
+
elif phase == "forward":
|
| 173 |
+
st.caption(
|
| 174 |
+
f"Forward substitution: Step {st.session_state.solve_step} of {total_forward}"
|
| 175 |
+
)
|
| 176 |
+
else:
|
| 177 |
+
st.caption(
|
| 178 |
+
f"Backward substitution: Step {st.session_state.solve_step - total_forward} of {total_backward}"
|
| 179 |
+
)
|
| 180 |
|
| 181 |
current_step = st.session_state.solve_step
|
| 182 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
|
| 184 |
# Helper to format vector as LaTeX
|
| 185 |
def vector_to_latex(v, highlight=None, computed_mask=None):
|
|
|
|
| 206 |
return r"\begin{pmatrix} " + r" \\ ".join(rows) + r" \end{pmatrix}"
|
| 207 |
|
| 208 |
|
| 209 |
+
# RIGHT COLUMN: Content
|
| 210 |
+
with col_content:
|
| 211 |
+
# Show the problem setup
|
| 212 |
+
col1, col2, col3 = st.columns([3, 3, 3])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
|
| 214 |
+
with col1:
|
| 215 |
+
st.markdown("**System $A\\mathbf{x} = \\mathbf{b}$:**")
|
| 216 |
+
st.latex(f"A = {matrix_to_latex(A)}")
|
| 217 |
+
st.latex(f"\\mathbf{{b}} = {vector_to_latex(b)}")
|
| 218 |
|
| 219 |
+
with col2:
|
| 220 |
+
st.markdown("**Cholesky factor $L$:**")
|
| 221 |
+
st.latex(f"L = {matrix_to_latex(L)}")
|
| 222 |
+
|
| 223 |
+
# Compute current state of y and x
|
| 224 |
+
if current_step == 0:
|
| 225 |
+
y_current = np.zeros(n)
|
| 226 |
+
x_current = np.zeros(n)
|
| 227 |
+
y_mask = np.zeros(n, dtype=bool)
|
| 228 |
+
x_mask = np.zeros(n, dtype=bool)
|
| 229 |
+
y_highlight = None
|
| 230 |
+
x_highlight = None
|
| 231 |
+
elif current_step <= total_forward:
|
| 232 |
+
# In forward substitution
|
| 233 |
+
_, _, _, y_current = forward_steps[current_step - 1]
|
| 234 |
+
x_current = np.zeros(n)
|
| 235 |
+
y_mask = np.zeros(n, dtype=bool)
|
| 236 |
+
for s in range(current_step):
|
| 237 |
+
idx = forward_steps[s][0]
|
| 238 |
+
y_mask[idx] = True
|
| 239 |
+
x_mask = np.zeros(n, dtype=bool)
|
| 240 |
+
y_highlight = forward_steps[current_step - 1][0]
|
| 241 |
+
x_highlight = None
|
| 242 |
+
else:
|
| 243 |
+
# In backward substitution
|
| 244 |
+
y_current = y_final.copy()
|
| 245 |
+
backward_idx = current_step - total_forward - 1
|
| 246 |
+
_, _, _, x_current = backward_steps[backward_idx]
|
| 247 |
+
y_mask = np.ones(n, dtype=bool)
|
| 248 |
+
x_mask = np.zeros(n, dtype=bool)
|
| 249 |
+
for s in range(backward_idx + 1):
|
| 250 |
+
idx = backward_steps[s][0]
|
| 251 |
+
x_mask[idx] = True
|
| 252 |
+
y_highlight = None
|
| 253 |
+
x_highlight = backward_steps[backward_idx][0]
|
| 254 |
+
|
| 255 |
+
with col3:
|
| 256 |
+
st.markdown("**Intermediate $\\mathbf{y}$ and solution $\\mathbf{x}$:**")
|
| 257 |
+
col_y, col_x = st.columns(2)
|
| 258 |
+
with col_y:
|
| 259 |
+
st.latex(
|
| 260 |
+
f"\\mathbf{{y}} = {vector_to_latex(y_current, highlight=y_highlight, computed_mask=y_mask)}"
|
| 261 |
+
)
|
| 262 |
+
with col_x:
|
| 263 |
+
st.latex(
|
| 264 |
+
f"\\mathbf{{x}} = {vector_to_latex(x_current, highlight=x_highlight, computed_mask=x_mask)}"
|
| 265 |
+
)
|
| 266 |
|
| 267 |
+
# Show current computation
|
| 268 |
+
st.markdown("---")
|
|
|
|
|
|
|
|
|
|
| 269 |
|
| 270 |
+
if phase == "setup":
|
|
|
|
| 271 |
st.markdown(
|
| 272 |
+
"**Step 1: Forward Substitution** β Solve $L\\mathbf{y} = \\mathbf{b}$"
|
| 273 |
)
|
| 274 |
+
st.latex(f"{matrix_to_latex(L)} \\mathbf{{y}} = {vector_to_latex(b)}")
|
| 275 |
+
st.markdown(
|
| 276 |
+
"Start from the top row and work down. Each $y_i$ depends only on previously computed values."
|
| 277 |
+
)
|
| 278 |
+
st.info("Click **Next β** to begin forward substitution")
|
| 279 |
+
|
| 280 |
+
elif phase == "forward":
|
| 281 |
+
step_idx = current_step - 1
|
| 282 |
+
i, formula, val, _ = forward_steps[step_idx]
|
| 283 |
+
st.markdown(f"**Forward substitution:** Computing $y_{{{i + 1}}}$")
|
| 284 |
+
st.latex(formula + f" = {val:.4f}")
|
| 285 |
+
|
| 286 |
+
if current_step == total_forward:
|
| 287 |
+
st.success("β Forward substitution complete!")
|
| 288 |
+
st.markdown(
|
| 289 |
+
"**Next:** Backward substitution to solve $L^\\top \\mathbf{x} = \\mathbf{y}$"
|
| 290 |
+
)
|
| 291 |
|
| 292 |
+
else: # backward
|
| 293 |
+
step_idx = current_step - total_forward - 1
|
| 294 |
+
i, formula, val, _ = backward_steps[step_idx]
|
| 295 |
+
st.markdown(f"**Backward substitution:** Computing $x_{{{i + 1}}}$")
|
| 296 |
+
st.latex(formula + f" = {val:.4f}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
|
| 298 |
+
# Final verification
|
| 299 |
+
if current_step == total_steps:
|
| 300 |
+
st.markdown("---")
|
| 301 |
+
st.success("**β System solved!**")
|