diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5dd244e..4624c39 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,6 +46,8 @@ A short intro that explains what this page helps the reader do. Explain one concept at a time with concrete examples. + + :::note Use admonitions for warnings, tips, and key details. ::: @@ -53,14 +55,19 @@ Use admonitions for warnings, tips, and key details. ``` +Replace the spark ID with your own, or remove the spark line if your contribution does not include external artifacts. + ## Supported Content - Markdown / MDX pages - Docusaurus admonitions - Inline and block LaTeX math - Images and `.figure` files used by this docs site +- Kothar Sparks for artifacts readers should be able to view and import into their own workspace - Approved docs tags from `docs/tags.yml` +If you want to share source files, runnable examples, generated artifacts, notebooks, datasets, or supplemental project files, prefer a Kothar Spark over pasting large artifacts directly into the page. Small code snippets that explain the text should stay directly in the page. + ## Review Expectations PRs are reviewed by the Kothar team. Clear structure, concise writing, and runnable instructions increase merge speed. diff --git a/README.md b/README.md index 362244d..fccb871 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ This repository hosts a minimal Docusaurus instance used to preview external con - Use approved docs tags from `docs/tags.yml` when adding `tags` front matter. Unknown tags fail the build. - You may use Docusaurus admonitions and inline LaTeX math. - You may include images and `.figure` files produced by the Aleph Plotting module in the workshop. +- You may embed Kothar Sparks for artifacts readers should be able to view and import into their own workspace. ## Docusaurus Basics @@ -26,6 +27,22 @@ If you are new to Docusaurus, start with the official docs: https://docusaurus.i - You can add `tags` front matter, but each tag must exactly match a key in `docs/tags.yml`. - Use relative links for local docs pages and place images in the same folder (or a subfolder) to keep paths simple. +## Kothar Sparks + +Use a Kothar Spark when you want to share artifacts that readers can easily view and import into their own workspace. Good candidates include source files, runnable examples, generated artifacts, notebooks, datasets, and supplemental project files. + +Small code snippets that explain a concept should stay directly in the page. Use a spark when the artifact is something readers should open as a file, inspect as a bundle, or import into their own workspace. + +Embed a spark in any Markdown or MDX contribution page with the `KotharSpark` component: + +```mdx + +``` + +Replace the ID with the spark you want to share. The rendered card links to the hosted spark and shows its description, owner, creation date, file count, total size, and up to 10 file names. + +The production build fetches spark metadata when you run `npm run build`. Invalid, private, or inaccessible spark IDs fail the build, so run the build before opening a pull request. + ## How To Open A Pull Request On GitHub 1. Fork this repository to your GitHub account. diff --git a/docs/contributions/gkls-vec-tutorial/01-single-qubit-validation.aleph b/docs/contributions/gkls-vec-tutorial/01-single-qubit-validation.aleph deleted file mode 100644 index 2fd1de0..0000000 --- a/docs/contributions/gkls-vec-tutorial/01-single-qubit-validation.aleph +++ /dev/null @@ -1,44 +0,0 @@ -/* ============================================================================ - 01-single-qubit-validation.aleph - ---------------------------------------------------------------------------- - First executable validation script for the symbolic Liouville-space GKLS - tutorial. - - This script checks that the reusable symbolic Liouville-space machinery - reproduces the direct GKLS equation on the smallest possible system: one - qubit. - - It validates: - - 1. Population flow for the decay jump |0><1|. - 2. Population flow for the pump jump |1><0|. - 3. Dense-reference agreement for local dissipators. - 4. Dense-reference agreement for coherent Hamiltonian terms. - 5. Dense-reference agreement for a combined one-qubit GKLS model. - 6. One-step trace preservation under symbolic Krylov propagation. - - Dense calculations appear only inside liouville-validation.aleph and are used - as small-system reference checks. The symbolic production path itself remains - in liouville-core.aleph. - ============================================================================ */ - - -include "gkls-vec-tutorial/liouville-validation.aleph" - - -print("") -print("============================================================") -print("Example 01: single-qubit symbolic Liouville validation") -print("============================================================") -print("") -print("This script validates the symbolic GKLS / Liouville-space core") -print("against direct dense GKLS calculations for N = 1.") -print("") -print("No dense Liouvillian matrix is constructed in the production path.") -print("Dense density-matrix calculations are used only as reference tests.") -print("") - -run_core_validation_suite() - -print("") -print("Example 01 finished.") \ No newline at end of file diff --git a/docs/contributions/gkls-vec-tutorial/02-single-qubit-krylov-evolution.aleph b/docs/contributions/gkls-vec-tutorial/02-single-qubit-krylov-evolution.aleph deleted file mode 100644 index cc4d3e6..0000000 --- a/docs/contributions/gkls-vec-tutorial/02-single-qubit-krylov-evolution.aleph +++ /dev/null @@ -1,261 +0,0 @@ -/* ============================================================================ - 02_single_qubit_krylov_evolution.aleph - ---------------------------------------------------------------------------- - First real symbolic Liouville-space propagation example. - - Model: - H = omega * X - - jump L = |0><1| - - GKLS equation: - d rho / dt = - -i [H, rho] - + gamma * ( - L rho L^dag - - 1/2 {L^dag L, rho} - ) - - Numerical method: - 1. Build the Liouvillian symbolically on a doubled lattice. - 2. Construct exp(dt * Liouvillian) using non-Hermitian Krylov - operator_function. - 3. Propagate rho_vec directly in Liouville space. - 4. Measure trace, , and populations without unvectorizing rho. - - This script deliberately does NOT: - - build a dense Liouvillian matrix; - - call direct dense GKLS evolution; - - unvec rho inside the time loop. - ============================================================================ */ - - -include "gkls-vec-tutorial/liouville-core.aleph" - - -// ============================================================================ -// 1) Parameters -// ============================================================================ - -global omega = get_arg("omega", as_real, 1.0) -global gamma = get_arg("gamma", as_real, 0.3) -global dt = get_arg("dt", as_real, 0.02) -global n_steps = get_arg("n_steps", as_integer, 500) -global krylov_dim = get_arg("krylov_dim", as_integer, 4) - -global N = 1 -global dim_vec = liouville_dim(N) -global n_total = n_steps + 1 - - -print("") -print("============================================================") -print("Example 02: single-qubit symbolic Krylov evolution") -print("============================================================") -print("") -print("Model:") -print(" H = omega * X") -print(" L = |0><1|") -print("") -print("Parameters:") -print(" omega = " + omega.string()) -print(" gamma = " + gamma.string()) -print(" dt = " + dt.string()) -print(" n_steps = " + n_steps.string()) -print(" krylov_dim = " + krylov_dim.string()) -print("") - - -// ============================================================================ -// 2) Build symbolic Liouvillian -// ============================================================================ - -/* - * Coherent part: - * - * -i[omega X, rho] - * - * Dissipative part: - * - * gamma D[|0><1|](rho) - * - * Both pieces are symbolic doubled-lattice operators. - */ -print("Building symbolic Liouvillian...") - -var Liouv = make_empty_liouvillian() - -Liouv += coherent_x_term(N, 0, omega) -Liouv += decay_to_zero_dissipator(N, 0, gamma) - -/* - * The Liouvillian contains local operator_dense terms from the dissipator, so - * we use the mixed cleanup path, not simplify_paulis. - */ -Liouv = cleanup_mixed_operator(Liouv, 1.0e-14) - -print("Symbolic Liouvillian built.") - - -// ============================================================================ -// 3) Build symbolic Krylov propagator -// ============================================================================ - -print("Building symbolic Krylov propagator...") - -var U = make_liouville_expm(Liouv, dim_vec, dt, krylov_dim) - -print("Krylov propagator ready.") - - -// ============================================================================ -// 4) Initial state and measurement bras -// ============================================================================ - -/* - * Initial state: - * - * rho(0) = |1><1| - * - * In the two-level convention used here, the dissipator |0><1| relaxes this - * state toward |0><0|, while omega * X drives coherent oscillations. - */ -var rho_vec = make_basis_density_vec(N, 1) - -/* - * Measurement vectors. - * - * For one qubit: - * - * p0 = (Tr rho + ) / 2 - * p1 = (Tr rho - ) / 2 - * - * because Z|0> = +|0> and Z|1> = -|1>. - */ -var trace_bra = make_trace_bra(N) -var z_bra = make_z_observable_bra(N, 0) - - -// ============================================================================ -// 5) Storage -// ============================================================================ - -var times = array(linspaced(n_total, 0.0, n_steps * dt), as_real) - -var trace_values = zeros([n_total], as_array | as_real) -var z_values = zeros([n_total], as_array | as_real) -var p0_values = zeros([n_total], as_array | as_real) -var p1_values = zeros([n_total], as_array | as_real) - - -// ============================================================================ -// 6) Time evolution loop -// ============================================================================ - -print("") -print("Running symbolic Liouville-space evolution...") - -for (var step = 0; step < n_total; ++step) -{ - var tr = real(dot(trace_bra, rho_vec)) - var z = real(dot(z_bra, rho_vec)) - - trace_values[step] = tr - z_values[step] = z - - p0_values[step] = 0.5 * (tr + z) - p1_values[step] = 0.5 * (tr - z) - - if (step % 50 == 0) -{ - var msg = " step " + step.string() + "/" + n_steps.string() - msg += " t = " + times[step].string() - msg += " Tr = " + tr.string() - msg += " p0 = " + p0_values[step].string() - msg += " p1 = " + p1_values[step].string() - - print(msg) -} - if (step < n_steps) - { - rho_vec = apply_liouville_step(U, rho_vec) - } -} - -print("Evolution done.") - - -// ============================================================================ -// 7) Basic final diagnostics -// ============================================================================ - -var final_trace = trace_values[n_steps] -var final_p0 = p0_values[n_steps] -var final_p1 = p1_values[n_steps] - -print("") -print("Final diagnostics:") -print(" Tr[rho(T)] = " + final_trace.string()) -print(" p0(T) = " + final_p0.string()) -print(" p1(T) = " + final_p1.string()) -print(" p0+p1 = " + (final_p0 + final_p1).string()) - - -// ============================================================================ -// 8) Figures -// ============================================================================ - -print("") -print("Generating figures...") - -var label = "omega${omega}_gamma${gamma}_dt${dt}_steps${n_steps}" - -/* - * Figure 1: populations. - */ -var fig_pop = Figure() -fig_pop.tooltip() -fig_pop.line(times, p0_values, ["name": "p0 = <0|rho|0>"]) -fig_pop.line(times, p1_values, ["name": "p1 = <1|rho|1>"]) -fig_pop.title("Driven dissipative qubit: populations") - .x_axis([ - "name": "Time t", - "name_location": "middle", - "name_text_style": ["font_size": 16] - ]) - .y_axis([ - "name": "Population", - "name_location": "middle", - "name_text_style": ["font_size": 16] - ]) - .legend(["top": "bottom", "left": "right"]) - .save("gkls-vec-tutorial/single_qubit_populations_${label}") - - -/* - * Figure 2: trace and . - */ -var fig_obs = Figure() -fig_obs.tooltip() -fig_obs.line(times, trace_values, ["name": "Tr[rho]"]) -fig_obs.line(times, z_values, ["name": ""]) -fig_obs.title("Driven dissipative qubit: trace and magnetization") - .x_axis([ - "name": "Time t", - "name_location": "middle", - "name_text_style": ["font_size": 16] - ]) - .y_axis([ - "name": "Observable", - "name_location": "middle", - "name_text_style": ["font_size": 16] - ]) - .legend(["top": "bottom", "left": "right"]) - .save("gkls-vec-tutorial/single_qubit_trace_z_${label}") - - -print("Figures saved:") -print(" gkls-vec-tutorial/single_qubit_populations_${label}.figure") -print(" gkls-vec-tutorial/single_qubit_trace_z_${label}.figure") -print("") -print("Example 02 finished.") \ No newline at end of file diff --git a/docs/contributions/gkls-vec-tutorial/03-stationary-closed-chain-open-dynamics.aleph b/docs/contributions/gkls-vec-tutorial/03-stationary-closed-chain-open-dynamics.aleph deleted file mode 100644 index b17d936..0000000 --- a/docs/contributions/gkls-vec-tutorial/03-stationary-closed-chain-open-dynamics.aleph +++ /dev/null @@ -1,460 +0,0 @@ -/* ============================================================================ - 03_stationary_closed_chain_open_dynamics.aleph - ---------------------------------------------------------------------------- - First many-body symbolic Liouville-space propagation example. - - Purpose: - Show an open-system many-body evolution where the closed Hamiltonian - baseline is stationary. - - Model: - H = Jz * sum_i Z_i Z_{i+1} + hz * sum_i Z_i - - Initial state: - A computational-basis Neel-like product state. - - Key point: - Since H is diagonal in the computational basis, the initial density - matrix rho(0) = |bitstring> on every site: - - L_j = |0><1|_j. - - Numerical method: - 1. Build the Liouvillian symbolically on a doubled lattice. - 2. Use symbolic non-Hermitian Krylov operator_function. - 3. Propagate the vectorized density matrix directly. - 4. Measure trace, total Z, staggered Z, and final Z profile. - - This script deliberately does NOT: - - build a dense Liouvillian matrix; - - construct full 2^N x 2^N jump matrices; - - unvectorize rho inside the time loop. - ============================================================================ */ - - -include "gkls-vec-tutorial/liouville-core.aleph" - - -// ============================================================================ -// 1) Parameters -// ============================================================================ - -global N = get_arg("N", as_integer, 10) -global Jz = get_arg("Jz", as_real, 1.0) -global hz = get_arg("hz", as_real, 0.3) -global gamma = get_arg("gamma", as_real, 0.2) -global dt = get_arg("dt", as_real, 0.02) -global n_steps = get_arg("n_steps", as_integer, 300) -global krylov_dim = get_arg("krylov_dim", as_integer, 20) - -global dim_vec = liouville_dim(N) -global n_total = n_steps + 1 - - -print("") -print("============================================================") -print("Example 03: stationary closed chain vs open dynamics") -print("============================================================") -print("") -print("Model:") -print(" H = Jz * sum_i Z_i Z_{i+1} + hz * sum_i Z_i") -print(" local jumps L_j = |0><1|_j") -print("") -print("Parameters:") -print(" N = " + N.string()) -print(" dim_vec = 4^N = " + dim_vec.string()) -print(" Jz = " + Jz.string()) -print(" hz = " + hz.string()) -print(" gamma = " + gamma.string()) -print(" dt = " + dt.string()) -print(" n_steps = " + n_steps.string()) -print(" krylov_dim = " + krylov_dim.string()) -print("") - -if (N > 10) -{ - print("WARNING:") - print(" This is exact Liouville-space propagation, so the vector size is 4^N.") - print(" N > 10 may become expensive depending on memory and Krylov dimension.") - print("") -} - - -// ============================================================================ -// 2) Initial product state -// ============================================================================ - -/* - * make_neel_like_basis_index(N) - * - * Builds the integer basis index for a product state with: - * - * site even -> |0> - * site odd -> |1> - * - * Since Aleph site 0 is the least-significant bit, the basis index is: - * - * index = sum_{odd sites j} 2^j. - */ -def make_neel_like_basis_index(integer n) -{ - var idx = 0 - - for (var j = 0; j < n; ++j) - { - if (j % 2 == 1) - { - idx += 2**j - } - } - - return idx -} - - -var initial_basis_index = make_neel_like_basis_index(N) - -print("Initial state:") -print(" Neel-like computational-basis state") -print(" even sites -> |0>, odd sites -> |1>") -print(" basis index = " + initial_basis_index.string()) -print("") - -var rho_init = make_basis_density_vec(N, initial_basis_index) - - -// ============================================================================ -// 3) Build closed and open symbolic Liouvillians -// ============================================================================ - -/* - * Closed Liouvillian: - * - * L_closed rho = -i[H, rho]. - * - * Because rho_init is diagonal in the same basis as H, this should produce - * stationary dynamics for diagonal observables. - */ -print("Building closed symbolic Liouvillian...") - -var L_closed = diagonal_ising_coherent_liouvillian(N, Jz, hz) -L_closed = cleanup_pauli_operator(L_closed, 1.0e-14) - -print("Closed Liouvillian built.") - - -/* - * Open Liouvillian: - * - * L_open = L_closed + sum_j gamma D[|0><1|_j]. - * - * The dissipators are local symbolic operator_dense terms. - */ -print("Building open symbolic Liouvillian...") - -var L_open = diagonal_ising_coherent_liouvillian(N, Jz, hz) - -for (var j = 0; j < N; ++j) -{ - L_open += decay_to_zero_dissipator(N, j, gamma) -} - -L_open = cleanup_mixed_operator(L_open, 1.0e-14) - -print("Open Liouvillian built.") - - -// ============================================================================ -// 4) Build symbolic Krylov propagators -// ============================================================================ - -print("Building closed Krylov propagator...") - -var U_closed = make_liouville_expm(L_closed, dim_vec, dt, krylov_dim) - -print("Closed propagator ready.") - - -print("Building open Krylov propagator...") - -var U_open = make_liouville_expm(L_open, dim_vec, dt, krylov_dim) - -print("Open propagator ready.") - - -// ============================================================================ -// 5) Measurement bras -// ============================================================================ - -print("Preparing Liouville-space measurement vectors...") - -var trace_bra = make_trace_bra(N) -var total_z_bra = make_total_z_observable_bra(N) -var staggered_z_bra = make_staggered_z_observable_bra(N) - -var z_site_bras = [] -z_site_bras.reserve(N) - -for (var j = 0; j < N; ++j) -{ - z_site_bras.push_back(make_z_observable_bra(N, j)) -} - -print("Measurement vectors ready.") - - -// ============================================================================ -// 6) Storage -// ============================================================================ - -var times = array(linspaced(n_total, 0.0, n_steps * dt), as_real) - -var trace_closed = zeros([n_total], as_array | as_real) -var trace_open = zeros([n_total], as_array | as_real) - -var total_z_closed = zeros([n_total], as_array | as_real) -var total_z_open = zeros([n_total], as_array | as_real) - -var stag_z_closed = zeros([n_total], as_array | as_real) -var stag_z_open = zeros([n_total], as_array | as_real) - -var final_profile_closed = zeros([N], as_array | as_real) -var final_profile_open = zeros([N], as_array | as_real) - -var sites = array(linspaced(N, 0, N - 1), as_real) - - -// ============================================================================ -// 7) Time evolution -// ============================================================================ - -var rho_closed = rho_init + zeros_like(rho_init) -var rho_open = rho_init + zeros_like(rho_init) - -print("") -print("Running closed and open symbolic Liouville evolution...") - -for (var step = 0; step < n_total; ++step) -{ - var tr_closed = real(dot(trace_bra, rho_closed)) - var tr_open = real(dot(trace_bra, rho_open)) - - var tz_closed = real(dot(total_z_bra, rho_closed)) - var tz_open = real(dot(total_z_bra, rho_open)) - - var sz_closed = real(dot(staggered_z_bra, rho_closed)) - var sz_open = real(dot(staggered_z_bra, rho_open)) - - trace_closed[step] = tr_closed - trace_open[step] = tr_open - - total_z_closed[step] = tz_closed - total_z_open[step] = tz_open - - stag_z_closed[step] = sz_closed - stag_z_open[step] = sz_open - - if (step == n_steps) - { - for (var j = 0; j < N; ++j) - { - final_profile_closed[j] = real(dot(z_site_bras[j], rho_closed)) - final_profile_open[j] = real(dot(z_site_bras[j], rho_open)) - } - } - - if (step % 50 == 0) -{ - var msg = " step " + step.string() + "/" + n_steps.string() - msg += " t = " + times[step].string() - msg += " totalZ_closed = " + tz_closed.string() - msg += " totalZ_open = " + tz_open.string() - msg += " Tr_open = " + tr_open.string() - - print(msg) -} - - if (step < n_steps) - { - rho_closed = apply_liouville_step(U_closed, rho_closed) - rho_open = apply_liouville_step(U_open, rho_open) - } -} - -print("Evolution done.") - - -// ============================================================================ -// 8) Diagnostics -// ============================================================================ - -def max_abs_trace_error(trace_values, integer len) -{ - var max_err = 0.0 - - for (var i = 0; i < len; ++i) - { - var err = abs(trace_values[i] - 1.0) - - if (err > max_err) - { - max_err = err - } - } - - return max_err -} - - -var trace_err_closed = max_abs_trace_error(trace_closed, n_total) -var trace_err_open = max_abs_trace_error(trace_open, n_total) - -print("") -print("Final diagnostics:") -print(" max |Tr_closed - 1| = " + trace_err_closed.string()) -print(" max |Tr_open - 1| = " + trace_err_open.string()) -print("") -print(" total Z closed at t=0 = " + total_z_closed[0].string()) -print(" total Z closed at T = " + total_z_closed[n_steps].string()) -print(" total Z open at t=0 = " + total_z_open[0].string()) -print(" total Z open at T = " + total_z_open[n_steps].string()) -print("") -print(" staggered Z closed at t=0 = " + stag_z_closed[0].string()) -print(" staggered Z closed at T = " + stag_z_closed[n_steps].string()) -print(" staggered Z open at t=0 = " + stag_z_open[0].string()) -print(" staggered Z open at T = " + stag_z_open[n_steps].string()) - - -// ============================================================================ -// 9) Figures -// ============================================================================ - -print("") -print("Generating figures...") - -var label = "N${N}_Jz${Jz}_hz${hz}_gamma${gamma}_dt${dt}_steps${n_steps}" - - -/* - * Figure 1: total Z. - * - * The closed curve should remain stationary. - * The open curve changes because local decay pushes sites toward |0>, - * for which Z = +1. - */ -var fig_total = Figure() -fig_total.tooltip() -fig_total.line(times, total_z_closed, ["name": "closed"]) -fig_total.line(times, total_z_open, ["name": "open"]) -fig_total.title("Stationary closed chain vs open dynamics: total $Z$") - .x_axis([ - "name": "Time t", - "name_location": "middle", - "name_text_style": ["font_size": 16] - ]) - .y_axis([ - "name": "$\\sum_i \\langle Z_i \\rangle$", - "name_location": "middle", - "name_text_style": ["font_size": 16] - ]) - .legend(["top": "bottom", "left": "right"]) - .save("gkls-vec-tutorial/stationary_chain_total_z_${label}") - - -/* - * Figure 2: staggered Z. - * - * This highlights the relaxation of the Neel-like pattern. - */ -var fig_stag = Figure() -fig_stag.tooltip() -fig_stag.line(times, stag_z_closed, ["name": "closed"]) -fig_stag.line(times, stag_z_open, ["name": "open"]) -fig_stag.title("Stationary closed chain vs open dynamics: staggered $Z$") - .x_axis([ - "name": "Time t", - "name_location": "middle", - "name_text_style": ["font_size": 16] - ]) - .y_axis([ - "name": "$\\sum_i (-1)^i \\langle Z_i \\rangle$", - "name_location": "middle", - "name_text_style": ["font_size": 16] - ]) - .legend(["top": "bottom", "left": "right"]) - .save("gkls-vec-tutorial/stationary_chain_staggered_z_${label}") - - -/* - * Figure 3: final Z profile. - * - * We slightly offset the x positions of the closed/open markers so that - * overlapping values remain visible. This is only for visualization. - */ -var profile_sites_closed = zeros([N], as_array | as_real) -var profile_sites_open = zeros([N], as_array | as_real) - -for (var j = 0; j < N; ++j) -{ - profile_sites_closed[j] = sites[j] - 0.04 - profile_sites_open[j] = sites[j] + 0.04 -} - -var fig_profile = Figure() -fig_profile.tooltip() -fig_profile.scatter(profile_sites_closed, final_profile_closed, ["name": "closed final"]) -fig_profile.scatter(profile_sites_open, final_profile_open, ["name": "open final"]) -fig_profile.title("Final $Z$ profile at $t = ${n_steps * dt}$") - .x_axis([ - "name": "Site i", - "name_location": "middle", - "name_text_style": ["font_size": 16] - ]) - .y_axis([ - "name": "$\\langle Z_i \\rangle$", - "name_location": "middle", - "name_text_style": ["font_size": 16] - ]) - .legend(["top": "bottom", "left": "right"]) - .save("gkls-vec-tutorial/stationary_chain_final_profile_${label}") - - -/* - * Figure 4: trace preservation. - */ -var fig_trace = Figure() -fig_trace.tooltip() -fig_trace.line(times, trace_closed, ["name": "closed trace"]) -fig_trace.line(times, trace_open, ["name": "open trace"]) -fig_trace.title("Trace preservation") - .x_axis([ - "name": "Time t", - "name_location": "middle", - "name_text_style": ["font_size": 16] - ]) - .y_axis([ - "name": "$\\mathrm{Tr}(\\rho)$", - "name_location": "middle", - "name_text_style": ["font_size": 16] - ]) - .legend(["top": "bottom", "left": "right"]) - .save("gkls-vec-tutorial/stationary_chain_trace_${label}") - - -print("Figures saved:") -print(" gkls-vec-tutorial/stationary_chain_total_z_${label}.figure") -print(" gkls-vec-tutorial/stationary_chain_staggered_z_${label}.figure") -print(" gkls-vec-tutorial/stationary_chain_final_profile_${label}.figure") -print(" gkls-vec-tutorial/stationary_chain_trace_${label}.figure") -print("") -print("Example 03 finished.") \ No newline at end of file diff --git a/docs/contributions/gkls-vec-tutorial/index.md b/docs/contributions/gkls-vec-tutorial/index.md index 1391908..df23ad8 100644 --- a/docs/contributions/gkls-vec-tutorial/index.md +++ b/docs/contributions/gkls-vec-tutorial/index.md @@ -177,13 +177,9 @@ The previous section gave us the conceptual move: once the density matrix is vec For that reason, the code is organized in layers. The reusable machinery lives in a core file, and the actual examples are kept as separate executable scripts. -The main files used in this tutorial are: +The main files used in this tutorial are in the following spark: -- [`liouville-core.aleph`](./liouville-core.aleph) -- [`liouville-validation.aleph`](./liouville-validation.aleph) -- [`01-single-qubit-validation.aleph`](./01-single-qubit-validation.aleph) -- [`02-single-qubit-krylov-evolution.aleph`](./02-single-qubit-krylov-evolution.aleph) -- [`03-stationary-closed-chain-open-dynamics.aleph`](./03-stationary-closed-chain-open-dynamics.aleph) + The idea here is simple: first build and validate the Liouville-space machinery on small systems, then use the same machinery in the propagation examples. @@ -212,13 +208,13 @@ liouville-validation.aleph 01-single-qubit-validation.aleph ``` -The role of these files is not to be large or physically impressive, but only make sure the convention is correct before we trust the many-body examples. They do that by comparing the symbolic Liouville-space construction against direct dense GKLS calculations in one-qubit cases. +The role of these files is not to be large or physically impressive, but to make sure the convention is correct before we trust the many-body examples. They do that by comparing the symbolic Liouville-space construction against direct dense GKLS calculations in one-qubit cases. After that, the first propagation examples are: ```text -02_single_qubit_krylov_evolution.aleph -03_stationary_closed_chain_open_dynamics.aleph +02-single-qubit-krylov-evolution.aleph +03-stationary-closed-chain-open-dynamics.aleph ``` While the single-qubit file is the clean pedagogical example, the many-body file is where the same vectorization strategy is used on a larger open spin-chain problem. @@ -504,7 +500,7 @@ A successful run should report `PASS` throughout the validation suite. Once that Once the Liouville-space construction has been validated, we can use it for actual time evolution. The first propagation example is kept deliberately small: ```text -02_single_qubit_krylov_evolution.aleph +02-single-qubit-krylov-evolution.aleph ``` It describes a single driven dissipative qubit with $H=\omega X$ and a relaxation channel $L=|0\rangle\langle 1|$. @@ -619,7 +615,7 @@ In this section, we move to a spin chain with exact Liouville-space propagation. The corresponding file is: ```text -03_stationary_closed_chain_open_dynamics.aleph +03-stationary-closed-chain-open-dynamics.aleph ``` ### Model diff --git a/docs/contributions/gkls-vec-tutorial/liouville-core.aleph b/docs/contributions/gkls-vec-tutorial/liouville-core.aleph deleted file mode 100644 index e3684b1..0000000 --- a/docs/contributions/gkls-vec-tutorial/liouville-core.aleph +++ /dev/null @@ -1,778 +0,0 @@ -/* ============================================================================ - Symbolic-first Liouville-space utilities for GKLS dynamics. - - This file is intended to be included by tutorial examples. - - It contains: - - doubled-lattice bookkeeping; - - **row-stacking** Liouville vector conventions; - - symbolic coherent Liouvillian terms; - - local one-site dissipator builders using operator_dense; - - symbolic non-Hermitian Krylov propagation helpers; - - simple trace / Z-observable measurement helpers. - - It deliberately does NOT contain: - - dense Liouvillian construction; - - plotting; - - benchmark loops; - - top-level execution. - - Main convention: - For N physical spin-1/2 sites, the Liouville-space vector has length 4^N. - The symbolic Liouvillian acts on 2N spin-1/2 sites: - - forward copy : 0, 1, ..., N-1 - backward copy : N, N+1, ..., 2N-1 - - Aleph site ordering corresponds here to row-stacking: - - vec_row(rho)[row * d + col] = rho[row, col], - - where d = 2^N. - ============================================================================ */ - - -// ============================================================================ -// 1) Basic dimensions and doubled-lattice bookkeeping -// ============================================================================ -/** -* convert site index to the corresponding forward site of the doubled lattice. -*/ -def fwd(integer site) { return site } - -/** -* convert site index to the corresponding backward site of the doubled lattice. -*/ -def bwd(integer site, integer N) { return N + site } - - /** - * size of the wavefunction's Hilbert space. - */ -def hilbert_dim(integer N) { return 2**N } - -/** -* Size of the vector space of density matrices. -*/ -def liouville_dim(integer N) { return 4**N } - -/** -* convert a matrix element position to a row-stacked vector element position. -*/ -def rho_vec_index(integer row, integer col, integer d) { return row * d + col } - -/** -* context specialized factory to initialize an empty liouvillian. -*/ -def make_empty_liouvillian() { return operator_sum() } - - -// ============================================================================ -// 2) Row-stacking vectorization helpers -// ============================================================================ - -/* - * vec_dm_row(rho, d) - * - * Row-stacking vectorization: - * - * vec_row(rho)[row * d + col] = rho[row, col]. - * - * This is the convention compatible with the doubled-lattice symbolic action - * used in this tutorial. - */ -def vec_dm_row(rho, integer d) -{ - var v = zeros([d * d], as_matrix) - - for (var row = 0; row < d; ++row) - { - for (var col = 0; col < d; ++col) - { - v[rho_vec_index(row, col, d)] = rho[row, col] - } - } - - return v -} - -/* - * unvec_dm_row(v, d) - * - * Inverse of vec_dm_row. - * This is mainly useful in small validation tests, not in production loops. - */ -def unvec_dm_row(v, integer d) -{ - var rho = zeros([d, d], as_matrix) - - for (var row = 0; row < d; ++row) - { - for (var col = 0; col < d; ++col) - { - rho[row, col] = v[rho_vec_index(row, col, d)] - } - } - - return rho -} - -/* - * make_basis_density_vec(N, basis_index) - * - * Builds vec_row(|basis_index> -i H_fwd + i H_bwd^T. - * - * The helpers below only implement terms whose transpose behavior is explicit. - * For X, Z, ZZ, and XX+YY, the whole term is transpose-invariant. - */ - -def coherent_x_term(integer N, integer site, real coeff) -{ - var L = operator_sum() - - L += ( 1i * coeff) * X(fwd(site)) - L += (-1i * coeff) * X(bwd(site, N)) - - return L -} - -def coherent_z_term(integer N, integer site, real coeff) -{ - var L = operator_sum() - - L += ( 1i * coeff) * Z(fwd(site)) - L += (-1i * coeff) * Z(bwd(site, N)) - - return L -} - -def coherent_zz_term(integer N, integer site0, integer site1, real coeff) -{ - var L = operator_sum() - - L += ( 1i * coeff) * ZZ(fwd(site0), fwd(site1)) - L += (-1i * coeff) * ZZ(bwd(site0, N), bwd(site1, N)) - - return L -} - - -/* - * XXPYY(i,j) = X_i X_j + Y_i Y_j. - * - * Although Y is antisymmetric under transpose, the two-Y product is - * transpose-invariant as a whole. - */ -def coherent_xxpyy_term(integer N, integer site0, integer site1, real coeff) -{ - var L = operator_sum() - - L += ( 1i * coeff) * XXPYY(fwd(site0), fwd(site1)) - L += (-1i * coeff) * XXPYY(bwd(site0, N), bwd(site1, N)) - - return L -} - - -// ============================================================================ -// 5) Local one-site matrices for jump operators -// ============================================================================ - -/* - * Aleph matrix literals are column-major: each inner list is one column. - * - * In the {|0>, |1>} basis: - * - * |0><1| = [[0, 1], - * [0, 0]] - * - * is written in Aleph as: - * - * matrix([[0, 0], [1, 0]], as_complex) - * - * because column 0 is [0, 0]^T and column 1 is [1, 0]^T. - * - * Physics naming warning: - * In Aleph's Qbit spin convention, |0> is spin-up and |1> is spin-down. - * Therefore |0><1| corresponds to S+ in spin language. - * - * In a quantum-optics two-level convention where |0> is the ground state and - * |1> is the excited state, this same operator is the usual decay jump - * |0><1|. - */ -def ket0_bra1_matrix() -{ - return matrix([[0, 0], [1, 0]], as_complex) -} - -/* - * |1><0|, written with Aleph's column-major literal convention. - */ -def ket1_bra0_matrix() -{ - return matrix([[0, 1], [0, 0]], as_complex) -} - -/* - * Pauli Z as a one-site local matrix. - */ -def pauli_z_matrix() -{ - return matrix([[1, 0], [0, -1]], as_complex) -} - - -// ============================================================================ -// 6) Local symbolic dissipators -// ============================================================================ - -/* - * local_jump_dissipator(N, site, jump_local, gamma) - * - * Builds the symbolic doubled-lattice representation of - * - * gamma * ( L rho L^\dagger - 1/2 {L^\dagger L, rho} ) - * - * for a one-site jump matrix jump_local acting on physical site "site". - * - * This does NOT construct the full 2^N x 2^N jump matrix. - * It embeds only the one-site 2x2 matrix into the symbolic operator. - * - * Row-stacking identity: - * - * vec_row(A rho B) = (A \otimes B^T) vec_row(rho). - * - * Therefore: - * - * L rho L^\dagger -> L_fwd and (L^\dagger)^T = L^* on backward copy - * L^\dagger L rho -> L^\dagger L on forward copy - * rho L^\dagger L -> (L^\dagger L)^T on backward copy - */ -def local_jump_dissipator(integer N, integer site, jump_local, real gamma) -{ - var D = operator_sum() - - var jump_dag = jump_local.adjointed() - var jump_conj = jump_dag.transposed() - - var jump_dag_jump = jump_dag * jump_local - var jump_dag_jump_T = jump_dag_jump.transposed() - - var L_fwd = operator_dense(jump_local, fwd(site)) - var L_conj_bwd = operator_dense(jump_conj, bwd(site, N)) - - var LdL_fwd = operator_dense(jump_dag_jump, fwd(site)) - var LdL_T_bwd = operator_dense(jump_dag_jump_T, bwd(site, N)) - - D += gamma * L_conj_bwd * L_fwd - D += (-0.5 * gamma) * LdL_fwd - D += (-0.5 * gamma) * LdL_T_bwd - - return D -} - -/* - * decay_to_zero_dissipator - * - * Jump operator: - * - * L = |0><1|. - * - * In quantum-optics language with |0> as ground and |1> as excited, this is - * amplitude damping / relaxation to |0>. - * - * In Aleph spin language, this is S+. - */ -def decay_to_zero_dissipator(integer N, integer site, real gamma) -{ - return local_jump_dissipator(N, site, ket0_bra1_matrix(), gamma) -} - -/* - * pump_to_one_dissipator - * - * Jump operator: - * - * L = |1><0|. - * - * This pumps population from |0> to |1>. - * - * In Aleph spin language, this is S-. - */ -def pump_to_one_dissipator(integer N, integer site, real gamma) -{ - return local_jump_dissipator(N, site, ket1_bra0_matrix(), gamma) -} - -/* - * z_dephasing_dissipator - * - * Jump operator: - * - * L = Z. - * - * With this normalization: - * - * D[Z](rho) = Z rho Z - rho. - */ -def z_dephasing_dissipator(integer N, integer site, real gamma) -{ - return local_jump_dissipator(N, site, pauli_z_matrix(), gamma) -} - - -// ============================================================================ -// 7) Simple model-building helpers -// ============================================================================ - -/* - * diagonal_ising_coherent_liouvillian(N, Jz, hz) - * - * H = Jz sum_i Z_i Z_{i+1} + hz sum_i Z_i - * - * Open boundary conditions. - * - * This is useful pedagogically because computational-basis product states are - * eigenstates of H. Therefore, for diagonal observables, closed dynamics gives - * a clean stationary baseline. - */ -def diagonal_ising_coherent_liouvillian(integer N, real Jz, real hz) -{ - var L = make_empty_liouvillian() - - for (var i = 0; i < N - 1; ++i) - { - L += coherent_zz_term(N, i, i + 1, Jz) - } - - for (var i = 0; i < N; ++i) - { - L += coherent_z_term(N, i, hz) - } - - return L -} - -/* - * xxz_coherent_liouvillian(N, J, Delta) - * - * H = J/2 sum_i (X_i X_{i+1} + Y_i Y_{i+1}) - * + J Delta sum_i Z_i Z_{i+1} - * - * Open boundary conditions. - */ -def contiguous_sites(integer start_site, integer stop_site) -{ - var sites = [] - - for (var s = start_site; s < stop_site; ++s) - { - sites.push_back(s) - } - - return sites -} - - -def xxz_coherent_liouvillian(integer N, real J, real Delta) -{ - var L = make_empty_liouvillian() - - /* - * XX + YY part: - * - * Keep this as nearest-neighbor XXPYY terms. - */ - for (var i = 0; i < N - 1; ++i) - { - L += coherent_xxpyy_term(N, i, i + 1, J / 2.0) - } - - /* - * ZZ part: - * - * Use ZZNN instead of manually summing ZZ(i, i+1). - * - * Forward copy: sites 0, ..., N-1 - * Backward copy: sites N, ..., 2N-1 - */ - var fwd_sites = contiguous_sites(0, N) - var bwd_sites = contiguous_sites(N, 2 * N) - - L += ( 1i * J * Delta) * ZZNN(fwd_sites) - L += (-1i * J * Delta) * ZZNN(bwd_sites) - - return L -} - -/* - * boundary_driven_xxz_liouvillian(N, J, Delta, gamma_left, gamma_right) - * - * A reusable symbolic exact-Liouville model: - * - * H = J/2 sum_i (XX + YY) + J Delta sum_i ZZ - * - * with boundary driving: - * - * left jump: |1><0| pumps site 0 into |1> - * right jump: |0><1| relaxes site N-1 into |0> - * - * This convention is chosen to be explicit in the computational basis. - */ -def boundary_driven_xxz_liouvillian( - integer N, - real J, - real Delta, - real gamma_left, - real gamma_right -) -{ - var L = xxz_coherent_liouvillian(N, J, Delta) - - L += pump_to_one_dissipator(N, 0, gamma_left) - L += decay_to_zero_dissipator(N, N - 1, gamma_right) - - return L -} - -// ============================================================================ -// 7b) Fully symbolic Pauli-expanded dissipators -// ============================================================================ - -/* - * These functions implement the same local channels as: - * - * decay_to_zero_dissipator(...) - * pump_to_one_dissipator(...) - * z_dephasing_dissipator(...) - * - * but without operator_dense. - * - * They are intended for benchmarking a fully symbolic Pauli-expanded backend. - * - * Conventions: - * - * |0><1| = S+ = (X + iY)/2 - * |1><0| = S- = (X - iY)/2 - * - * P0 = |0><0| = (I + Z)/2 - * P1 = |1><1| = (I - Z)/2 - * - * For row-stacking on the doubled lattice, the jump term is represented by - * placing the same real jump operator on the forward and backward copies. - */ - - -def decay_to_zero_dissipator_pauli(integer N, integer site, real gamma) -{ - var D = operator_sum() - - var jf = fwd(site) - var jb = bwd(site, N) - - /* - * L = |0><1| = S+ = (X + iY)/2. - * - * Jump term: - * - * S+_bwd S+_fwd - * = 1/4 (X_b X_f + i X_b Y_f + i Y_b X_f - Y_b Y_f) - * - * Anti-commutator: - * - * L^dag L = |1><1| = P1 = (I - Z)/2. - */ - D += (0.25 * gamma) * X(jb) * X(jf) - D += (1i * 0.25 * gamma) * X(jb) * Y(jf) - D += (1i * 0.25 * gamma) * Y(jb) * X(jf) - D += (-0.25 * gamma) * Y(jb) * Y(jf) - - D += (-0.5 * gamma) * ID() - D += (0.25 * gamma) * Z(jf) - D += (0.25 * gamma) * Z(jb) - - return D -} - - -def pump_to_one_dissipator_pauli(integer N, integer site, real gamma) -{ - var D = operator_sum() - - var jf = fwd(site) - var jb = bwd(site, N) - - /* - * L = |1><0| = S- = (X - iY)/2. - * - * Jump term: - * - * S-_bwd S-_fwd - * = 1/4 (X_b X_f - i X_b Y_f - i Y_b X_f - Y_b Y_f) - * - * Anti-commutator: - * - * L^dag L = |0><0| = P0 = (I + Z)/2. - */ - D += (0.25 * gamma) * X(jb) * X(jf) - D += (-1i * 0.25 * gamma) * X(jb) * Y(jf) - D += (-1i * 0.25 * gamma) * Y(jb) * X(jf) - D += (-0.25 * gamma) * Y(jb) * Y(jf) - - D += (-0.5 * gamma) * ID() - D += (-0.25 * gamma) * Z(jf) - D += (-0.25 * gamma) * Z(jb) - - return D -} - - -def z_dephasing_dissipator_pauli(integer N, integer site, real gamma) -{ - var D = operator_sum() - - var jf = fwd(site) - var jb = bwd(site, N) - - /* - * L = Z. - * - * D[Z](rho) = Z rho Z - rho. - */ - D += gamma * Z(jb) * Z(jf) - D += (-gamma) * ID() - - return D -} - - -/* - * boundary_driven_xxz_liouvillian_pauli(...) - * - * Same physical model as boundary_driven_xxz_liouvillian(...), but using - * Pauli-expanded symbolic dissipators instead of local operator_dense terms. - */ -def boundary_driven_xxz_liouvillian_pauli( - integer N, - real J, - real Delta, - real gamma_left, - real gamma_right -) -{ - var L = xxz_coherent_liouvillian(N, J, Delta) - - L += pump_to_one_dissipator_pauli(N, 0, gamma_left) - L += decay_to_zero_dissipator_pauli(N, N - 1, gamma_right) - - return L -} - - - -// ============================================================================ -// 8) Symbolic non-Hermitian Krylov propagation -// ============================================================================ - -/* - * make_liouville_expm(Liouv, dim_vec, dt, krylov_dim) - * - * Builds a Krylov representation of - * - * exp(dt * Liouv) - * - * for a generally non-Hermitian symbolic Liouvillian. - * - * Verified pattern: - * - * operator_function(op, dim_vec, callback, opts) - * - * The callback must be derivative-aware: - * - * f(z) = exp(dt z) - * f^(n)(z) = dt^n exp(dt z). - */ -def make_liouville_expm(Liouv, integer dim_vec, real dt, integer krylov_dim) -{ - var kdim = krylov_dim - - if (kdim > dim_vec) - { - kdim = dim_vec - } - - var exp_dt_L = fun[dt](complex z, integer deriv_order) { - return (dt ** deriv_order) * exp(dt * z) - } - - return operator_function(Liouv, dim_vec, exp_dt_L, ["krylov_dimension": kdim]) -} - -/* - * apply_liouville_step(U, rho_vec) - * - * One out-of-place Krylov propagation step. - */ -def apply_liouville_step(U, rho_vec) -{ - return U * rho_vec -} - - -// ============================================================================ -// 9) Liouville-space measurement helpers -// ============================================================================ - -/* - * make_trace_bra(N) - * - * Returns vec_row(I). Since I is diagonal, row-stacking and column-stacking - * coincide for this particular object. - * - * Tr(rho) = dot(vec_row(I), vec_row(rho)). - */ -def make_trace_bra(integer N) -{ - var d = hilbert_dim(N) - var eye = identity([d, d], as_matrix) - return eye.flattened() -} - -/* - * make_z_observable_bra(N, site) - * - * Builds a measurement vector for . - * - * For diagonal Z observables, applying Z on the forward copy to vec(I) gives - * the correct trace functional. - */ -def make_z_observable_bra(integer N, integer site) -{ - var id_bra = make_trace_bra(N) - return Z(fwd(site)) * id_bra -} - -/* - * make_total_z_observable_bra(N) - * - * Builds a measurement vector for sum_i . - */ -def make_total_z_observable_bra(integer N) -{ - var id_bra = make_trace_bra(N) - return ZN([0..N]) * id_bra -} - -/* - * make_staggered_z_observable_bra(N) - * - * Builds a measurement vector for - * - * sum_i (-1)^i . - */ -def make_staggered_z_observable_bra(integer N) -{ - var obs = operator_sum() - - for (var i = 0; i < N; ++i) - { - var sign = (i % 2 == 0) ? 1.0 : -1.0 - obs += sign * Z(fwd(i)) - } - - var id_bra = make_trace_bra(N) - return obs * id_bra -} \ No newline at end of file diff --git a/docs/contributions/gkls-vec-tutorial/liouville-validation.aleph b/docs/contributions/gkls-vec-tutorial/liouville-validation.aleph deleted file mode 100644 index 06c6a46..0000000 --- a/docs/contributions/gkls-vec-tutorial/liouville-validation.aleph +++ /dev/null @@ -1,788 +0,0 @@ -/* ============================================================================ - liouville-validation.aleph - ---------------------------------------------------------------------------- - Small-system validation helpers for symbolic Liouville-space GKLS dynamics. - - This file is intended to be included by validation examples. - - It deliberately contains dense density-matrix calculations, but only for - small correctness tests. These routines are NOT the production propagation - path of the tutorial. - - Main purpose: - Compare two ways of computing d rho / dt: - - 1. Direct dense GKLS formula: - d rho / dt = -i[H,rho] - + sum_k gamma_k ( - L_k rho L_k^dag - - 1/2 {L_k^dag L_k, rho} - ) - - 2. Symbolic Liouville-space action: - d|rho>>/dt = Liouv |rho>> - - The comparison uses the row-stacking convention from liouville-core.aleph: - - vec_row(rho)[row * d + col] = rho[row, col]. - ============================================================================ */ - - -include "gkls-vec-tutorial/liouville-core.aleph" - - -// ============================================================================ -// 1) Small dense density matrices -// ============================================================================ - -/* - * make_basis_density_matrix(N, basis_index) - * - * Builds the dense matrix - * - * rho = |basis_index><1| - * - * and apply the dissipator with jump: - * - * L = |0><1|. - * - * We expect: - * - * d rho_00 / dt > 0, - * d rho_11 / dt < 0, - * d Tr(rho) / dt = 0. - * - * This test does NOT construct a dense Liouvillian matrix. - */ -def check_decay_to_zero_population_flow(real gamma, real tol) -{ - var N = 1 - var d = hilbert_dim(N) - - var D = decay_to_zero_dissipator(N, 0, gamma) - - // rho = |1><1|. In row-stacking, rho_11 is index 1*d + 1. - var rho_vec = make_basis_density_vec(N, 1) - - var drho_vec = D * rho_vec - - var idx_00 = rho_vec_index(0, 0, d) - var idx_11 = rho_vec_index(1, 1, d) - - var drho_00 = real(drho_vec[idx_00]) - var drho_11 = real(drho_vec[idx_11]) - - var trace_bra = make_trace_bra(N) - var dtrace = real(dot(trace_bra, drho_vec)) - - print("") - print("=== Population-flow check: decay_to_zero_dissipator ===") - print("d rho_00 / dt = " + drho_00.string()) - print("d rho_11 / dt = " + drho_11.string()) - print("d trace / dt = " + dtrace.string()) - - var pass_pop_in = drho_00 > 0.0 - var pass_pop_out = drho_11 < 0.0 - var pass_trace = abs(dtrace) < tol - - if (pass_pop_in) - { - print("PASS: d rho_00 / dt > 0") - } - else - { - print("FAIL: d rho_00 / dt should be > 0") - } - - if (pass_pop_out) - { - print("PASS: d rho_11 / dt < 0") - } - else - { - print("FAIL: d rho_11 / dt should be < 0") - } - - if (pass_trace) - { - print("PASS: trace is preserved to tolerance") - } - else - { - print("FAIL: trace derivative is not close to zero") - } - - return pass_pop_in && pass_pop_out && pass_trace -} - -/* - * check_pump_to_one_population_flow(gamma, tol) - * - * Symbolic-only dissipator sanity check for N = 1. - * - * We start from: - * - * rho = |0><0| - * - * and apply the dissipator with jump: - * - * L = |1><0|. - * - * We expect: - * - * d rho_00 / dt < 0, - * d rho_11 / dt > 0, - * d Tr(rho) / dt = 0. - * - * This test does NOT construct a dense Liouvillian matrix. - */ -def check_pump_to_one_population_flow(real gamma, real tol) -{ - var N = 1 - var d = hilbert_dim(N) - - var D = pump_to_one_dissipator(N, 0, gamma) - - // rho = |0><0|. - var rho_vec = make_basis_density_vec(N, 0) - - var drho_vec = D * rho_vec - - var idx_00 = rho_vec_index(0, 0, d) - var idx_11 = rho_vec_index(1, 1, d) - - var drho_00 = real(drho_vec[idx_00]) - var drho_11 = real(drho_vec[idx_11]) - - var trace_bra = make_trace_bra(N) - var dtrace = real(dot(trace_bra, drho_vec)) - - print("") - print("=== Population-flow check: pump_to_one_dissipator ===") - print("d rho_00 / dt = " + drho_00.string()) - print("d rho_11 / dt = " + drho_11.string()) - print("d trace / dt = " + dtrace.string()) - - var pass_pop_out = drho_00 < 0.0 - var pass_pop_in = drho_11 > 0.0 - var pass_trace = abs(dtrace) < tol - - if (pass_pop_out) - { - print("PASS: d rho_00 / dt < 0") - } - else - { - print("FAIL: d rho_00 / dt should be < 0") - } - - if (pass_pop_in) - { - print("PASS: d rho_11 / dt > 0") - } - else - { - print("FAIL: d rho_11 / dt should be > 0") - } - - if (pass_trace) - { - print("PASS: trace is preserved to tolerance") - } - else - { - print("FAIL: trace derivative is not close to zero") - } - - return pass_pop_out && pass_pop_in && pass_trace -} - - -// ============================================================================ -// 5) Dense-reference validation tests for the core dissipators -// ============================================================================ - -/* - * validate_decay_to_zero_against_dense(gamma, tol) - * - * Compares the symbolic decay_to_zero_dissipator against the direct dense - * GKLS right-hand side for N = 1 and rho = |1><1|. - */ -def validate_decay_to_zero_against_dense(real gamma, real tol) -{ - var N = 1 - var d = hilbert_dim(N) - - var Liouv = decay_to_zero_dissipator(N, 0, gamma) - - var rho = make_basis_density_matrix(N, 1) - var H = make_zero_hamiltonian(N) - - var jump_ops = [ket0_bra1_matrix()] - var gammas = [gamma] - - var err = compare_direct_and_symbolic_rhs(Liouv, rho, H, jump_ops, gammas, d) - - print_validation_result("Dense reference: decay_to_zero_dissipator", err, tol) - - return err -} - -/* - * validate_pump_to_one_against_dense(gamma, tol) - * - * Compares the symbolic pump_to_one_dissipator against the direct dense - * GKLS right-hand side for N = 1 and rho = |0><0|. - */ -def validate_pump_to_one_against_dense(real gamma, real tol) -{ - var N = 1 - var d = hilbert_dim(N) - - var Liouv = pump_to_one_dissipator(N, 0, gamma) - - var rho = make_basis_density_matrix(N, 0) - var H = make_zero_hamiltonian(N) - - var jump_ops = [ket1_bra0_matrix()] - var gammas = [gamma] - - var err = compare_direct_and_symbolic_rhs(Liouv, rho, H, jump_ops, gammas, d) - - print_validation_result("Dense reference: pump_to_one_dissipator", err, tol) - - return err -} - -/* - * validate_z_dephasing_against_dense(gamma, tol) - * - * Compares the symbolic z_dephasing_dissipator against the direct dense - * GKLS right-hand side for N = 1 and a coherent test state. - */ -def validate_z_dephasing_against_dense(real gamma, real tol) -{ - var N = 1 - var d = hilbert_dim(N) - - var Liouv = z_dephasing_dissipator(N, 0, gamma) - - // rho = |+><+| = 1/2 [[1, 1], [1, 1]] - // Aleph matrix literal is column-major; both columns are [1/2, 1/2]^T. - var rho = matrix([[0.5, 0.5], [0.5, 0.5]], as_complex) - - var H = make_zero_hamiltonian(N) - - var jump_ops = [pauli_z_matrix()] - var gammas = [gamma] - - var err = compare_direct_and_symbolic_rhs(Liouv, rho, H, jump_ops, gammas, d) - - print_validation_result("Dense reference: z_dephasing_dissipator", err, tol) - - return err -} - -// ============================================================================ -// 5b) Dense-reference validation tests for Pauli-expanded dissipators -// ============================================================================ - -/* - * validate_decay_to_zero_pauli_against_dense(gamma, tol) - * - * Compares the fully symbolic Pauli-expanded decay dissipator against the - * direct dense GKLS right-hand side for N = 1 and rho = |1><1|. - */ -def validate_decay_to_zero_pauli_against_dense(real gamma, real tol) -{ - var N = 1 - var d = hilbert_dim(N) - - var Liouv = decay_to_zero_dissipator_pauli(N, 0, gamma) - Liouv = cleanup_pauli_operator(Liouv, 1.0e-14) - - var rho = make_basis_density_matrix(N, 1) - var H = make_zero_hamiltonian(N) - - var jump_ops = [ket0_bra1_matrix()] - var gammas = [gamma] - - var err = compare_direct_and_symbolic_rhs(Liouv, rho, H, jump_ops, gammas, d) - - print_validation_result("Dense reference: decay_to_zero_dissipator_pauli", err, tol) - - return err -} - - -/* - * validate_pump_to_one_pauli_against_dense(gamma, tol) - * - * Compares the fully symbolic Pauli-expanded pump dissipator against the - * direct dense GKLS right-hand side for N = 1 and rho = |0><0|. - */ -def validate_pump_to_one_pauli_against_dense(real gamma, real tol) -{ - var N = 1 - var d = hilbert_dim(N) - - var Liouv = pump_to_one_dissipator_pauli(N, 0, gamma) - Liouv = cleanup_pauli_operator(Liouv, 1.0e-14) - - var rho = make_basis_density_matrix(N, 0) - var H = make_zero_hamiltonian(N) - - var jump_ops = [ket1_bra0_matrix()] - var gammas = [gamma] - - var err = compare_direct_and_symbolic_rhs(Liouv, rho, H, jump_ops, gammas, d) - - print_validation_result("Dense reference: pump_to_one_dissipator_pauli", err, tol) - - return err -} - - -/* - * validate_z_dephasing_pauli_against_dense(gamma, tol) - * - * Compares the fully symbolic Pauli-expanded dephasing dissipator against the - * direct dense GKLS right-hand side for N = 1 and rho = |+><+|. - */ -def validate_z_dephasing_pauli_against_dense(real gamma, real tol) -{ - var N = 1 - var d = hilbert_dim(N) - - var Liouv = z_dephasing_dissipator_pauli(N, 0, gamma) - Liouv = cleanup_pauli_operator(Liouv, 1.0e-14) - - // rho = |+><+| = 1/2 [[1, 1], [1, 1]] - var rho = matrix([[0.5, 0.5], [0.5, 0.5]], as_complex) - - var H = make_zero_hamiltonian(N) - - var jump_ops = [pauli_z_matrix()] - var gammas = [gamma] - - var err = compare_direct_and_symbolic_rhs(Liouv, rho, H, jump_ops, gammas, d) - - print_validation_result("Dense reference: z_dephasing_dissipator_pauli", err, tol) - - return err -} - - -/* - * validate_one_qubit_gkls_pauli_against_dense(omega, gamma, tol) - * - * Combined one-qubit test using the Pauli-expanded decay dissipator. - * - * H = omega X, - * L = |0><1|. - * - * We choose rho = |1><1| so that both the Hamiltonian and dissipator - * contribute nontrivially. - */ -def validate_one_qubit_gkls_pauli_against_dense(real omega, real gamma, real tol) -{ - var N = 1 - var d = hilbert_dim(N) - - var Liouv = coherent_x_term(N, 0, omega) - Liouv += decay_to_zero_dissipator_pauli(N, 0, gamma) - Liouv = cleanup_pauli_operator(Liouv, 1.0e-14) - - var rho = make_basis_density_matrix(N, 1) - - var H = omega * make_single_qubit_x_matrix() - - var jump_ops = [ket0_bra1_matrix()] - var gammas = [gamma] - - var err = compare_direct_and_symbolic_rhs(Liouv, rho, H, jump_ops, gammas, d) - - print_validation_result("Dense reference: one-qubit GKLS Pauli backend", err, tol) - - return err -} - - -// ============================================================================ -// 6) Dense-reference validation for coherent terms -// ============================================================================ - -/* - * validate_single_qubit_x_hamiltonian_against_dense(omega, tol) - * - * Checks the symbolic coherent X Liouvillian against the direct dense - * commutator for N = 1. - */ -def validate_single_qubit_x_hamiltonian_against_dense(real omega, real tol) -{ - var N = 1 - var d = hilbert_dim(N) - - var Liouv = coherent_x_term(N, 0, omega) - - // A non-stationary test state: rho = |0><0|. - var rho = make_basis_density_matrix(N, 0) - - var H = omega * make_single_qubit_x_matrix() - - var jump_ops = [] - var gammas = [] - - var err = compare_direct_and_symbolic_rhs(Liouv, rho, H, jump_ops, gammas, d) - - print_validation_result("Dense reference: coherent X Hamiltonian", err, tol) - - return err -} - -/* - * validate_single_qubit_z_hamiltonian_against_dense(omega, tol) - * - * Checks the symbolic coherent Z Liouvillian against the direct dense - * commutator for N = 1. - * - * We choose rho = |+><+| so the commutator is nonzero. - */ -def validate_single_qubit_z_hamiltonian_against_dense(real omega, real tol) -{ - var N = 1 - var d = hilbert_dim(N) - - var Liouv = coherent_z_term(N, 0, omega) - - // rho = |+><+| - var rho = matrix([[0.5, 0.5], [0.5, 0.5]], as_complex) - - var H = omega * make_single_qubit_z_matrix() - - var jump_ops = [] - var gammas = [] - - var err = compare_direct_and_symbolic_rhs(Liouv, rho, H, jump_ops, gammas, d) - - print_validation_result("Dense reference: coherent Z Hamiltonian", err, tol) - - return err -} - - -// ============================================================================ -// 7) Combined one-qubit GKLS validation -// ============================================================================ - -/* - * validate_one_qubit_gkls_against_dense(omega, gamma, tol) - * - * Checks a small GKLS model: - * - * H = omega X, - * L = |0><1|. - * - * This validates that coherent and dissipative symbolic pieces combine - * correctly. - */ -def validate_one_qubit_gkls_against_dense(real omega, real gamma, real tol) -{ - var N = 1 - var d = hilbert_dim(N) - - var Liouv = coherent_x_term(N, 0, omega) - Liouv += decay_to_zero_dissipator(N, 0, gamma) - - // Use a coherent initial state to test both Hamiltonian and dissipator. - var rho = matrix([[0.5, 0.5], [0.5, 0.5]], as_complex) - - var H = omega * make_single_qubit_x_matrix() - - var jump_ops = [ket0_bra1_matrix()] - var gammas = [gamma] - - var err = compare_direct_and_symbolic_rhs(Liouv, rho, H, jump_ops, gammas, d) - - print_validation_result("Dense reference: one-qubit GKLS model", err, tol) - - return err -} - - -// ============================================================================ -// 8) Symbolic Krylov one-step sanity check -// ============================================================================ - -/* - * validate_one_step_trace_preservation(gamma, dt, krylov_dim, tol) - * - * Builds a one-qubit decay Liouvillian, constructs the symbolic Krylov - * propagator exp(dt * L), applies one time step, and checks trace preservation. - * - * This does not construct a dense Liouvillian matrix. - */ -def validate_one_step_trace_preservation( - real gamma, - real dt, - integer krylov_dim, - real tol -) -{ - var N = 1 - var dim_vec = liouville_dim(N) - - var Liouv = decay_to_zero_dissipator(N, 0, gamma) - var U = make_liouville_expm(Liouv, dim_vec, dt, krylov_dim) - - var rho_vec = make_basis_density_vec(N, 1) - - var trace_bra = make_trace_bra(N) - var trace_before = dot(trace_bra, rho_vec) - - rho_vec = U * rho_vec - - var trace_after = dot(trace_bra, rho_vec) - var trace_err = abs(real(trace_after - trace_before)) - - print("") - print("=== Symbolic Krylov one-step trace-preservation check ===") - print("trace before = " + trace_before.string()) - print("trace after = " + trace_after.string()) - print("trace error = " + trace_err.string()) - print("tolerance = " + tol.string()) - - if (trace_err < tol) - { - print("PASS") - } - else - { - print("FAIL") - } - - return trace_err -} - - -// ============================================================================ -// 9) Run a compact validation suite -// ============================================================================ - -/* - * run_core_validation_suite() - * - * Convenience wrapper used by the first example script. - */ -def run_core_validation_suite() -{ - var tol = 1.0e-10 - - var gamma = 1.0 - var omega = 0.7 - var dt = 0.01 - var krylov_dim = 4 - - print("") - print("============================================================") - print("Running symbolic Liouville-space core validation suite") - print("============================================================") - - check_decay_to_zero_population_flow(gamma, tol) - check_pump_to_one_population_flow(gamma, tol) - - validate_decay_to_zero_against_dense(gamma, tol) - validate_pump_to_one_against_dense(gamma, tol) - validate_z_dephasing_against_dense(gamma, tol) - - validate_decay_to_zero_pauli_against_dense(gamma, tol) - validate_pump_to_one_pauli_against_dense(gamma, tol) - validate_z_dephasing_pauli_against_dense(gamma, tol) - validate_one_qubit_gkls_pauli_against_dense(omega, gamma, tol) - - validate_single_qubit_x_hamiltonian_against_dense(omega, tol) - validate_single_qubit_z_hamiltonian_against_dense(omega, tol) - - validate_one_qubit_gkls_against_dense(omega, gamma, tol) - - validate_one_step_trace_preservation(gamma, dt, krylov_dim, tol) - - print("") - print("============================================================") - print("Validation suite finished") - print("============================================================") -} \ No newline at end of file diff --git a/docusaurus.config.ts b/docusaurus.config.ts index d3c09e7..8b30c55 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -5,6 +5,7 @@ import remarkMath from 'remark-math'; import rehypeKatex from 'rehype-katex'; import { remarkAutolinker } from '@kasisoft/remark-autolinker'; import remarkFigure from './src/remark/figure'; +import kotharSparksPlugin from './src/plugins/kothar-sparks'; const htmlEntityMap: Record = { '<': '<', @@ -149,6 +150,7 @@ const config: Config = { ], plugins: [ + kotharSparksPlugin, 'docusaurus-lunr-search', 'docusaurus-plugin-image-zoom', function figureAssetsPlugin() { diff --git a/src/components/KotharSpark/KotharSpark.css b/src/components/KotharSpark/KotharSpark.css new file mode 100644 index 0000000..a2469b1 --- /dev/null +++ b/src/components/KotharSpark/KotharSpark.css @@ -0,0 +1,143 @@ +.kothar-spark { + display: block; + width: 100%; + margin: 1.25rem 0; + padding: 1rem; + border: 1px solid rgba(107, 196, 255, 0.18); + border-radius: 8px; + background: + linear-gradient(180deg, rgba(9, 28, 42, 0.92), rgba(5, 20, 31, 0.96)), + var(--ifm-background-surface-color); + color: var(--ifm-font-color-base); + text-decoration: none; + box-shadow: 0 12px 36px rgba(0, 0, 0, 0.18); + transition: + border-color 150ms ease, + background-color 150ms ease, + transform 150ms ease; +} + +.kothar-spark:hover, +.kothar-spark:focus, +.kothar-spark:active { + color: var(--ifm-font-color-base); + text-decoration: none; +} + +.kothar-spark:hover { + border-color: rgba(107, 196, 255, 0.42); + transform: translateY(-1px); +} + +.kothar-spark:focus-visible { + outline: 2px solid var(--ifm-color-primary-light); + outline-offset: 3px; +} + +.kothar-spark-header { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.kothar-spark-title-group { + display: grid; + min-width: 0; + gap: 0.05rem; +} + +.kothar-spark-kicker { + font-size: 0.75rem; + line-height: 1rem; + color: var(--ifm-font-color-secondary); +} + +.kothar-spark-title { + overflow-wrap: anywhere; + font-size: 0.72rem; + line-height: 1rem; + font-family: var(--ifm-font-family-monospace); + font-weight: 500; + color: var(--ifm-font-color-secondary); +} + +.kothar-spark-external-icon { + margin-left: auto; + flex: 0 0 auto; + color: var(--ifm-font-color-secondary); +} + +.kothar-spark-description { + margin: 0.85rem 0 0; + color: #d9dde3; + font-size: 0.94rem; + line-height: 1.55; +} + +.kothar-spark-meta { + display: flex; + flex-wrap: wrap; + gap: 0.45rem 1rem; + margin-top: 0.9rem; + color: var(--ifm-font-color-secondary); + font-size: 0.78rem; + line-height: 1.2rem; +} + +.kothar-spark-meta span:not(:last-child)::after { + content: ''; +} + +.kothar-spark-files { + display: grid; + gap: 0.4rem; + margin-top: 0.95rem; + padding-top: 0.95rem; + border-top: 1px solid rgba(255, 255, 255, 0.08); +} + +.kothar-spark-file { + display: flex; + min-width: 0; + align-items: center; + gap: 0.45rem; + color: #c6ccd4; + font-family: var(--ifm-font-family-monospace); + font-size: 0.78rem; + line-height: 1.25rem; +} + +.kothar-spark-file svg { + flex: 0 0 auto; + color: var(--ifm-color-primary-lighter); +} + +.kothar-spark-file span { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.kothar-spark-file-overflow { + font-family: var(--ifm-font-family-base); + color: var(--ifm-font-color-secondary); +} + +.kothar-spark-action { + display: block; + margin-top: 0.9rem; + color: #c7eeff; + font-size: 0.8rem; + line-height: 1.25rem; +} + +@media (max-width: 540px) { + .kothar-spark { + padding: 0.9rem; + } + + .kothar-spark-meta { + gap: 0.35rem 0.7rem; + } +} diff --git a/src/components/KotharSpark/KotharSpark.jsx b/src/components/KotharSpark/KotharSpark.jsx new file mode 100644 index 0000000..6a00010 --- /dev/null +++ b/src/components/KotharSpark/KotharSpark.jsx @@ -0,0 +1,129 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { ExternalLink, FileText } from 'lucide-react'; +import { usePluginData } from '@docusaurus/useGlobalData'; +import './KotharSpark.css'; + +const KOTHAR_SPARK_API_BASE = 'https://api.kotharcomputing.com/v1/sparks'; +const KOTHAR_SPARK_URL_BASE = 'https://sparks.kotharcomputing.com/s'; + +export default function KotharSpark({ id }) { + if (!id) { + throw new Error('KotharSpark requires an `id` prop.'); + } + + const pluginData = usePluginData('kothar-sparks'); + const buildSpark = pluginData?.sparksById?.[id]; + const [clientSpark, setClientSpark] = useState(null); + const [clientError, setClientError] = useState(false); + const spark = buildSpark ?? clientSpark; + const href = spark?.url ?? `${KOTHAR_SPARK_URL_BASE}/${encodeURIComponent(id)}`; + + useEffect(() => { + if (buildSpark || typeof window === 'undefined') { + return undefined; + } + + let cancelled = false; + + fetch(`${KOTHAR_SPARK_API_BASE}/${encodeURIComponent(id)}`) + .then((response) => { + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + return response.json(); + }) + .then((data) => { + if (!cancelled) { + setClientSpark(data); + } + }) + .catch(() => { + if (!cancelled) { + setClientError(true); + } + }); + + return () => { + cancelled = true; + }; + }, [buildSpark, id]); + + const description = useMemo(() => { + if (!spark?.description) { + return clientError + ? 'Spark details are unavailable right now.' + : 'Loading Spark details...'; + } + + return spark.description + .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1') + .replace(/`([^`]+)`/g, '$1') + .replace(/\*\*([^*]+)\*\*/g, '$1') + .replace(/\*([^*]+)\*/g, '$1'); + }, [clientError, spark]); + + return ( + +
+
+ Kothar Spark + {id} +
+
+ +

{description}

+ +
+ {spark?.ownerDisplayName ?? 'Kothar'} + {spark?.createdAt ? ( + + {new Intl.DateTimeFormat('en', { + month: 'short', + day: 'numeric', + year: 'numeric', + timeZone: 'UTC', + }).format(new Date(spark.createdAt))} + + ) : null} + {typeof spark?.fileCount === 'number' ? ( + + {spark.fileCount} {spark.fileCount === 1 ? 'file' : 'files'} + + ) : null} + {typeof spark?.totalBytes === 'number' ? ( + + {spark.totalBytes >= 1024 * 1024 + ? `${(spark.totalBytes / (1024 * 1024)).toFixed(1)} MB` + : `${Math.max(1, Math.round(spark.totalBytes / 1024))} KB`} + + ) : null} +
+ + {spark?.files?.length ? ( +
+ {spark.files.slice(0, 10).map((file) => ( + + + ))} + {spark.files.length > 10 ? ( + + +{spark.files.length - 10} more + + ) : null} +
+ ) : null} + + + Open to preview files and import them into your workspace. + +
+ ); +} diff --git a/src/plugins/kothar-sparks/index.ts b/src/plugins/kothar-sparks/index.ts new file mode 100644 index 0000000..577ec8e --- /dev/null +++ b/src/plugins/kothar-sparks/index.ts @@ -0,0 +1,132 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import type { LoadContext, Plugin } from '@docusaurus/types'; + +const KOTHAR_SPARK_API_BASE = 'https://api.kotharcomputing.com/v1/sparks'; + +type SparkFile = { + path: string; + sizeBytes: number; +}; + +type Spark = { + id: string; + url: string; + ownerDisplayName: string; + description: string; + createdAt: string; + fileCount: number; + totalBytes: number; + files: SparkFile[]; +}; + +export default function kotharSparksPlugin( + context: LoadContext, +): Plugin { + return { + name: 'kothar-sparks', + async loadContent() { + const sparkIds = new Set(); + const roots = ['docs', 'changelog', 'src/pages']; + const sparkPattern = + /]*\bid\s*=\s*(?:"([^"]+)"|'([^']+)'|\{`([^`]+)`\}|\{"([^"]+)"\}|\{'([^']+)'\})/g; + + for (const root of roots) { + const rootPath = path.join(context.siteDir, root); + const pending = [rootPath]; + + while (pending.length > 0) { + const currentPath = pending.pop(); + if (!currentPath) continue; + + let entries; + try { + entries = await fs.readdir(currentPath, { withFileTypes: true }); + } catch (error) { + if ( + error && + typeof error === 'object' && + 'code' in error && + error.code === 'ENOENT' + ) { + continue; + } + throw error; + } + + for (const entry of entries) { + const entryPath = path.join(currentPath, entry.name); + if (entry.isDirectory()) { + pending.push(entryPath); + continue; + } + + if (!/\.(?:md|mdx|jsx?|tsx?)$/.test(entry.name)) { + continue; + } + + const content = await fs.readFile(entryPath, 'utf8'); + sparkPattern.lastIndex = 0; + for ( + let match = sparkPattern.exec(content); + match; + match = sparkPattern.exec(content) + ) { + const id = match[1] ?? match[2] ?? match[3] ?? match[4] ?? match[5]; + if (id) { + sparkIds.add(id); + } + } + } + } + } + + const sparksById: Record = {}; + + for (const id of sparkIds) { + const response = await fetch( + `${KOTHAR_SPARK_API_BASE}/${encodeURIComponent(id)}`, + ); + + if (!response.ok) { + throw new Error( + `Failed to fetch Kothar spark "${id}": HTTP ${response.status} ${response.statusText}`, + ); + } + + const spark = await response.json(); + if ( + !spark || + typeof spark !== 'object' || + spark.id !== id || + typeof spark.url !== 'string' || + typeof spark.ownerDisplayName !== 'string' || + typeof spark.description !== 'string' || + typeof spark.createdAt !== 'string' || + typeof spark.fileCount !== 'number' || + typeof spark.totalBytes !== 'number' || + !Array.isArray(spark.files) || + spark.files.some((file: unknown) => { + if (!file || typeof file !== 'object') { + return true; + } + const sparkFile = file as Partial; + return ( + typeof sparkFile.path !== 'string' || + typeof sparkFile.sizeBytes !== 'number' + ); + }) + ) { + throw new Error(`Invalid Kothar spark API response for "${id}".`); + } + + sparksById[id] = spark as Spark; + } + + return { sparksById }; + }, + async contentLoaded({ content, actions }) { + actions.setGlobalData(content); + }, + }; +} diff --git a/src/theme/MDXComponents.js b/src/theme/MDXComponents.js index 608bc73..e23ece6 100644 --- a/src/theme/MDXComponents.js +++ b/src/theme/MDXComponents.js @@ -10,6 +10,7 @@ import { AccordionContent, } from '@site/src/components/Accordion/Accordion.jsx'; import Figure from '@site/src/components/Figure/Figure.jsx'; +import KotharSpark from '@site/src/components/KotharSpark/KotharSpark.jsx'; const StateHighlight = ({ children }) => ( {children} @@ -37,4 +38,5 @@ export default { AccordionTrigger, AccordionContent, Figure, + KotharSpark, };