diff --git a/.github/workflows/julia-test-version.yaml b/.github/workflows/julia-test-version.yaml index 3d4be823a..8a243e815 100644 --- a/.github/workflows/julia-test-version.yaml +++ b/.github/workflows/julia-test-version.yaml @@ -213,7 +213,7 @@ jobs: strategy: fail-fast: false matrix: - group: [core, api, errorfields] + group: [core, api, errorfields, plots] env: GPEC_LOCAL_CACHE_DIR: ${{ vars.GPEC_LOCAL_CACHE_DIR }} JULIA_CPU_TARGET: 'generic' @@ -293,11 +293,30 @@ jobs: env: CACHE_KEY: ${{ needs.precompile.outputs.gpec-key }} + - name: Update the package registry + if: matrix.group != 'plots' + run: julia -e 'using Pkg; Pkg.Registry.update()' + # Level 1 can reuse the higher-optimization production package images. - name: Run tests + if: matrix.group != 'plots' uses: julia-actions/julia-runtest@v1 with: coverage: false check_bounds: 'yes' optimize: '1' allow_reresolve: false + + # The plots draw with Makie through GPEC's package extension, so Makie stays out of the package's own + # dependencies; this group loads CairoMakie from a separate environment stacked onto LOAD_PATH. + - name: Add the plotting backend + if: matrix.group == 'plots' + run: | + set -euo pipefail + backend="$RUNNER_TEMP/plots-backend" + julia --project="$backend" -e 'using Pkg; Pkg.add("CairoMakie")' + echo "JULIA_LOAD_PATH=@:$backend:@stdlib" >> "$GITHUB_ENV" + + - name: Run plot tests + if: matrix.group == 'plots' + run: julia --project=. --check-bounds=yes -O1 test/runtests.jl diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index ee2a1eff1..a3029d6ba 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -61,7 +61,7 @@ jobs: # trigger a run, so new untested Julia (benchmarks, docs scripts, a # future tools/ directory) is excluded by default rather than by # remembering to exclude it. - tested='(^src/|^test/|^examples/|^ci/manifests/|^ci/local-julia-cache\.sh$|^Project\.toml$|^\.github/workflows/(test|julia-test-version)\.yaml$)' + tested='(^src/|^ext/|^test/|^examples/|^ci/manifests/|^ci/local-julia-cache\.sh$|^Project\.toml$|^\.github/workflows/(test|julia-test-version)\.yaml$)' relevant=$(printf '%s\n' "$files" | grep -E "$tested" || true) diff --git a/CLAUDE.md b/CLAUDE.md index 206b6e39b..c7383c62c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -121,13 +121,7 @@ Roster, the recommended review pipeline (physics fidelity → readability → pe - **Default output**: `gpec.h5` (previously `euler.h5` in older versions) ### Plotting -Spectrum-plot (`seriestype=:steppre` / `step_series`) and figure-saving conventions are in **[`docs/development/plotting.md`](docs/development/plotting.md)** — read before writing any plotting code. - -**Adding a plot.** Every plot in `src/Analysis/` takes a result in memory or a `gpec.h5` path and is registered in `src/Analysis/Registry.jl`, which `available_plots` and the REPL summaries read: -- Read the data through one `_x_data(src)` method per source (the result's fields, and the same datasets from the file), and draw from the returned NamedTuple only, so both sources go through one plotting body. -- Start the public `plot_x(src; ...)` with `_require(plot_x, src)`, which throws the registry's reason when the source lacks the data; never draw a "No data" placeholder or return `nothing`. Composite summaries skip the panels they cannot draw (`_can`) and throw only when none can. -- Call `register!(plot_x, sources, needs, description)` after the definition. `needs` names capabilities; a new one gets a `capabilities` predicate for each result type that can hold it, a dataset in `_H5_CAPABILITIES`, and a `REASONS` sentence saying what to change in the run. -- `test/runtests_analysis_plots.jl` checks every registered plot from both sources automatically; add an extractor to its list when the plot reads new data. +Figures are drawn with Makie through the package extension `ext/GPECMakieExt/`. Read **[`docs/development/plotting.md`](docs/development/plotting.md)** before writing any plotting code: it has the figure conventions and the guide to adding an Analysis plot. In short, Analysis plots are added sparingly (frequent everyday-workflow figures only, approved by a human), and each is registered, follows the shared theme, carries little text, and throws an `ArgumentError` when its data are absent. Never offer to add an Analysis plot unprompted. **REPL summaries.** `src/Display.jl` gives every result type a one-line `show` and a short `text/plain` summary. A new result type gets both, plus an entry in `_DISPLAY_FIELDS`; a new field on a summarized type is classified there as shown or omitted, which `test/display_check.jl` enforces. diff --git a/Project.toml b/Project.toml index f2658611c..01d61596c 100644 --- a/Project.toml +++ b/Project.toml @@ -25,8 +25,6 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" -PlotlyJS = "f0f68f2c-4968-5e81-91da-67840de0976a" -Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" @@ -39,6 +37,12 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +[weakdeps] +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" + +[extensions] +GPECMakieExt = "Makie" + [compat] AdaptiveArrayPools = "0.3.5" CommonSolve = "0.2" @@ -57,10 +61,9 @@ JLD2 = "0.6.3" LaTeXStrings = "1.4.0" LinearAlgebra = "1" Logging = "1" +Makie = "0.24" OrdinaryDiffEq = "6.102.0" Pkg = "1" -PlotlyJS = "0.18.17" -Plots = "1.40.15" PrecompileTools = "1.2" Printf = "1" QuadGK = "2.11.3" diff --git a/ci/manifests/Manifest-v1.11.toml b/ci/manifests/Manifest-v1.11.toml index 2101791ed..1f7510f31 100644 --- a/ci/manifests/Manifest-v1.11.toml +++ b/ci/manifests/Manifest-v1.11.toml @@ -2,7 +2,7 @@ julia_version = "1.11.9" manifest_format = "2.0" -project_hash = "0ae359086b0249d78fa4ef29b4162952ca8c0475" +project_hash = "88bbe078ff4b2f6063ba6bf3eb3bebca8a1fda5a" [[deps.ADTypes]] deps = ["PrecompileTools"] @@ -52,9 +52,9 @@ version = "0.4.5" [[deps.Accessors]] deps = ["CompositionsBase", "ConstructionBase", "Dates", "InverseFunctions", "MacroTools"] -git-tree-sha1 = "7063ad1083578215c7c4bf410368150abe8d5524" +git-tree-sha1 = "8c0bc12397dabf5d2dfc4b5763ec463854cc44d3" uuid = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697" -version = "0.1.45" +version = "0.1.46" [deps.Accessors.extensions] AxisKeysExt = "AxisKeys" @@ -76,9 +76,9 @@ version = "0.1.45" [[deps.Adapt]] deps = ["LinearAlgebra"] -git-tree-sha1 = "dee8388a9bec89c8713ad176f96960ae2f98598a" +git-tree-sha1 = "eec8a0ad4e4b673afba213931504c360fffb8f97" uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" -version = "4.7.4" +version = "4.8.0" weakdeps = ["SparseArrays", "StaticArrays"] [deps.Adapt.extensions] @@ -87,9 +87,9 @@ weakdeps = ["SparseArrays", "StaticArrays"] [[deps.AdaptiveArrayPools]] deps = ["Preferences", "Printf", "Random"] -git-tree-sha1 = "5feaa5055572e809b69b62f06826b1c1c8d3ebf3" +git-tree-sha1 = "86d0ae34bc46373170064ef33c12a303994ab736" uuid = "4f381ef7-9af0-4cbe-99d4-cf36d7b0f233" -version = "0.3.11" +version = "0.3.12" [deps.AdaptiveArrayPools.extensions] AdaptiveArrayPoolsCUDAExt = "CUDA" @@ -104,12 +104,6 @@ git-tree-sha1 = "7e651ea8d262d2d74ce75fdf47c4d63c07dba7a6" uuid = "35492f91-a3bd-45ad-95db-fcad7dcfedb7" version = "1.2.0" -[[deps.AliasTables]] -deps = ["PtrArrays", "Random"] -git-tree-sha1 = "9876e1e164b144ca45e9e3198d0b689cadfed9ff" -uuid = "66dad0bd-aa9a-41b7-9441-69ab47430ed8" -version = "1.1.3" - [[deps.ArgTools]] uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" version = "1.1.2" @@ -155,9 +149,9 @@ version = "7.30.2" [[deps.ArrayLayouts]] deps = ["FillArrays", "LinearAlgebra", "StaticArrays"] -git-tree-sha1 = "b4a2d2dfcaedf8f0cb93a970c4ccba6f632f14cc" +git-tree-sha1 = "8fde25d92e917350570f1203e47c933d16b77dce" uuid = "4c555306-a7a7-4459-81d9-ec55ddd5c99a" -version = "1.15.1" +version = "1.16.0" weakdeps = ["SparseArrays"] [deps.ArrayLayouts.extensions] @@ -167,33 +161,16 @@ weakdeps = ["SparseArrays"] uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" version = "1.11.0" -[[deps.AssetRegistry]] -deps = ["JSON", "Pidfile", "SHA"] -git-tree-sha1 = "902b85010203830d4bc02259f5450d2e16b316d8" -uuid = "bf4720bc-e11a-5d0c-854e-bdca1663c893" -version = "0.1.1" - [[deps.Base64]] uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" version = "1.11.0" -[[deps.BitFlags]] -git-tree-sha1 = "bbe1079eecf9c9fbb52765193ad2bae27ae09bc8" -uuid = "d1d4a3ce-64b1-5f1a-9ba4-7e7e69966f35" -version = "0.1.10" - [[deps.BitTwiddlingConvenienceFunctions]] deps = ["Static"] git-tree-sha1 = "f21cfd4950cb9f0587d5067e69405ad2acd27b87" uuid = "62783981-4cbd-42fc-bca8-16325de8dc4b" version = "0.1.6" -[[deps.Blink]] -deps = ["Base64", "Distributed", "HTTP", "JSExpr", "JSON", "Lazy", "Logging", "MacroTools", "Mustache", "Mux", "Pkg", "Reexport", "Sockets", "WebIO"] -git-tree-sha1 = "bc93511973d1f949d45b0ea17878e6cb0ad484a1" -uuid = "ad839575-38b3-5650-b840-f874b8c74a25" -version = "0.12.9" - [[deps.BracketingNonlinearSolve]] deps = ["CommonSolve", "ConcreteStructs", "NonlinearSolveBase", "PrecompileTools", "Reexport", "SciMLBase"] git-tree-sha1 = "7ad7171d693ae5552ac43862e7f6b61df4471c2b" @@ -208,12 +185,6 @@ version = "1.12.1" ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" -[[deps.Bzip2_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "1b96ea4a01afe0ea4090c5c8039690672dd13f2e" -uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" -version = "1.0.9+0" - [[deps.CPUSummary]] deps = ["CpuId", "IfElse", "PrecompileTools", "Preferences", "Static"] git-tree-sha1 = "f3a21d7fc84ba618a779d1ed2fcca2e682865bab" @@ -222,9 +193,9 @@ version = "0.2.7" [[deps.CRlibm]] deps = ["CRlibm_jll"] -git-tree-sha1 = "66188d9d103b92b6cd705214242e27f5737a1e5e" +git-tree-sha1 = "0db159493e83ecb2d122f9c40138d89b70d43963" uuid = "96374032-68de-5a5b-8d9e-752f78720389" -version = "1.0.2" +version = "1.1.1" [[deps.CRlibm_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] @@ -232,12 +203,6 @@ git-tree-sha1 = "e329286945d0cfc04456972ea732551869af1cfc" uuid = "4e9b3aee-d8a1-5a3d-ad8b-7d824db253f0" version = "1.0.1+0" -[[deps.Cairo_jll]] -deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "Libdl", "Pixman_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"] -git-tree-sha1 = "7b841680738c19948120f6e4cf8d0200518bd564" -uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a" -version = "1.18.8+0" - [[deps.ChunkCodecCore]] git-tree-sha1 = "3496e2b359c27793d12bec54ec94be7983a6ceb2" uuid = "0b6fb165-00bc-4d37-ab8b-79f91016dbe1" @@ -267,38 +232,6 @@ git-tree-sha1 = "970758a3d591a2a5c2a907c53f2e2f8c1b1d3537" uuid = "944b1d66-785c-5afd-91f1-9de20f533193" version = "0.7.9" -[[deps.ColorSchemes]] -deps = ["ColorTypes", "ColorVectorSpace", "Colors", "FixedPointNumbers", "PrecompileTools", "Random"] -git-tree-sha1 = "b0fd3f56fa442f81e0a47815c92245acfaaa4e34" -uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4" -version = "3.31.0" - -[[deps.ColorTypes]] -deps = ["FixedPointNumbers", "Random"] -git-tree-sha1 = "a7cf827a79b378a381e31e5c7feca853ec19cb94" -uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" -version = "0.12.4" -weakdeps = ["StyledStrings"] - - [deps.ColorTypes.extensions] - StyledStringsExt = "StyledStrings" - -[[deps.ColorVectorSpace]] -deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "Requires", "Statistics", "TensorCore"] -git-tree-sha1 = "8b3b6f87ce8f65a2b4f857528fd8d70086cd72b1" -uuid = "c3611d14-8923-5661-9e6a-0046d554d3a4" -version = "0.11.0" -weakdeps = ["SpecialFunctions"] - - [deps.ColorVectorSpace.extensions] - SpecialFunctionsExt = "SpecialFunctions" - -[[deps.Colors]] -deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "Reexport"] -git-tree-sha1 = "a633156e74096f90b3b5e833529292b651b5671a" -uuid = "5ae59095-9a9b-59fe-a467-6f913c188581" -version = "0.13.3" - [[deps.CommonSolve]] deps = ["PrecompileTools"] git-tree-sha1 = "6c389fa857f6ca5a95474b52a52023fd77f24cb7" @@ -347,12 +280,6 @@ git-tree-sha1 = "a72c3b5ce6d2a477f55b5c9b8756e91e695c67f6" uuid = "2569d6c7-a4a2-43d3-a901-331e8e4be471" version = "0.2.8" -[[deps.ConcurrentUtilities]] -deps = ["Serialization", "Sockets"] -git-tree-sha1 = "d5e5fa1f0d6ca517edbbcd734efa93c6639405b0" -uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb" -version = "2.7.1" - [[deps.ConstructionBase]] git-tree-sha1 = "b4b092499347b18a015186eae3042f72267106cb" uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9" @@ -406,12 +333,6 @@ git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe" uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" version = "1.16.0" -[[deps.DataFrames]] -deps = ["Compat", "DataAPI", "DataStructures", "Future", "InlineStrings", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrecompileTools", "PrettyTables", "Printf", "Random", "Reexport", "SentinelArrays", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"] -git-tree-sha1 = "5fab31e2e01e70ad66e3e24c968c264d1cf166d6" -uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" -version = "1.8.2" - [[deps.DataInterpolations]] deps = ["EnumX", "FindFirstFunctions", "ForwardDiff", "LinearAlgebra", "PrettyTables", "RecipesBase", "Reexport"] git-tree-sha1 = "4d50e83772222ce023efb7d30632198828ea56a6" @@ -454,17 +375,11 @@ deps = ["Printf"] uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" version = "1.11.0" -[[deps.Dbus_jll]] -deps = ["Artifacts", "Expat_jll", "JLLWrappers", "Libdl"] -git-tree-sha1 = "473e9afc9cf30814eb67ffa5f2db7df82c3ad9fd" -uuid = "ee1fde0b-3d02-5ea6-8484-8dfef6360eab" -version = "1.16.2+0" - [[deps.DelaunayTriangulation]] deps = ["AdaptivePredicates", "EnumX", "ExactPredicates", "Random"] -git-tree-sha1 = "4ac548adcad90c1d5d677af13568a748af4c952b" +git-tree-sha1 = "e1e28f37cbdca4b594ad86ecfe2ee8f25796ebff" uuid = "927a84f5-c5f4-47a5-9785-b46e178433df" -version = "1.6.7" +version = "1.7.0" [[deps.DelimitedFiles]] deps = ["Mmap"] @@ -610,9 +525,9 @@ version = "1.19.0" [[deps.DoubleFloats]] deps = ["GenericLinearAlgebra", "GenericSchur", "LinearAlgebra", "Printf", "Quadmath", "Random", "SpecialFunctions"] -git-tree-sha1 = "e9350afd366d2deaaa9b4ed13fd537e2b4dea6e5" +git-tree-sha1 = "00720e467f998dc28d6bd3ec2fe33b1d04155f6c" uuid = "497a8b3b-efae-58df-a0af-a86822472b78" -version = "1.11.2" +version = "1.12.0" [[deps.Downloads]] deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] @@ -625,9 +540,9 @@ uuid = "4e289a0a-7415-4d19-859d-a7e5c4648b56" version = "1.0.7" [[deps.EnzymeCore]] -git-tree-sha1 = "156ede9181d50b3b21b74d98a7edb08bc7bbdbf1" +git-tree-sha1 = "d88c803e26799193db42e47f6e1e28eeb0322e4d" uuid = "f151be2c-9106-41f4-ab19-57ee4f262869" -version = "0.8.23" +version = "0.8.27" [deps.EnzymeCore.extensions] AdaptExt = "Adapt" @@ -637,29 +552,17 @@ version = "0.8.23" Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" -[[deps.EpollShim_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "8a4be429317c42cfae6a7fc03c31bad1970c310d" -uuid = "2702e6a9-849d-5ed8-8c21-79e8b8f9ee43" -version = "0.0.20230411+1" - [[deps.ExactPredicates]] deps = ["IntervalArithmetic", "Random", "StaticArrays"] git-tree-sha1 = "83231673ea4d3d6008ac74dc5079e77ab2209d8f" uuid = "429591f6-91af-11e9-00e2-59fbe8cec110" version = "2.2.9" -[[deps.ExceptionUnwrapping]] -deps = ["Test"] -git-tree-sha1 = "4e468f521e1f9f86891cb07186de5df90360a666" -uuid = "460bff9d-24e4-43bc-9d9f-a8973cb893f4" -version = "0.1.12" - [[deps.Expat_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "2bfb1e047e2ad0a5ca94365340bde8005d637568" +git-tree-sha1 = "f2659a735f9360fedfa29925812380f322679e93" uuid = "2e619515-83b5-522b-bb60-26c02a35a201" -version = "2.8.4+0" +version = "2.10.0+0" [[deps.ExponentialUtilities]] deps = ["Adapt", "ArrayInterface", "GPUArraysCore", "GenericSchur", "LinearAlgebra", "PrecompileTools", "Printf", "SparseArrays", "libblastrampoline_jll"] @@ -677,21 +580,9 @@ uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04" version = "0.1.11" [[deps.ExproniconLite]] -git-tree-sha1 = "c13f0b150373771b0fdc1713c97860f8df12e6c2" +git-tree-sha1 = "250aa1562e840b38c72b44a9ebef0f39f6b6e950" uuid = "55351af7-c7e9-48d6-89ff-24e801d99491" -version = "0.10.14" - -[[deps.FFMPEG]] -deps = ["FFMPEG_jll"] -git-tree-sha1 = "7bd13840b4148949e290748071bbe6826b3eabcd" -uuid = "c87230d0-a227-11e9-1b43-d7ebe4e7570a" -version = "0.4.6" - -[[deps.FFMPEG_jll]] -deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "PCRE2_jll", "Zlib_jll", "libaom_jll", "libass_jll", "libfdk_aac_jll", "libva_jll", "libvorbis_jll", "x264_jll", "x265_jll"] -git-tree-sha1 = "d9d3cd382f2c999f684e6bfa4039fcc16c153b6f" -uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5" -version = "9.0.2+0" +version = "0.10.16" [[deps.FFTW]] deps = ["AbstractFFTs", "FFTW_jll", "Libdl", "LinearAlgebra", "MKL_jll", "Preferences", "Reexport"] @@ -707,14 +598,19 @@ version = "3.3.12+0" [[deps.FastBroadcast]] deps = ["ArrayInterface", "LinearAlgebra", "PrecompileTools"] -git-tree-sha1 = "c8f8eefadaa330d982bbf5e395c34e53a13ab668" +git-tree-sha1 = "093bf5e9ea63b36032240033ec2f149088fc0926" uuid = "7034ab61-46d4-4ed7-9d0f-46aef9175898" -version = "1.4.0" -weakdeps = ["Polyester", "Static"] +version = "1.5.1" [deps.FastBroadcast.extensions] FastBroadcastPolyesterExt = "Polyester" FastBroadcastStaticExt = "Static" + FixedSizeArraysExt = "FixedSizeArrays" + + [deps.FastBroadcast.weakdeps] + FixedSizeArrays = "3821ddf9-e5b5-40d5-8e25-6813ab96b5e2" + Polyester = "f517fe37-dbe3-4b94-8317-1923a5111588" + Static = "aedffcd0-7271-4cad-89d0-dc628f76c6d3" [[deps.FastClosures]] git-tree-sha1 = "acebe244d53ee1b461970f8910c235b259e772ef" @@ -753,9 +649,9 @@ version = "0.4.20" [[deps.FastPower]] deps = ["PrecompileTools"] -git-tree-sha1 = "3c7269c236978d434a16ebe99f9743b9d706270a" +git-tree-sha1 = "a6bcbd090fc6b6f022fc0ad96f77ba31b9577282" uuid = "a4df4552-cc26-4903-aec0-212e50a0e84b" -version = "1.5.0" +version = "1.5.1" [deps.FastPower.extensions] FastPowerEnzymeExt = "Enzyme" @@ -780,20 +676,22 @@ deps = ["Pkg", "Requires", "UUIDs"] git-tree-sha1 = "6621fef488e496356c9c9625d0562c12a6070819" uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" version = "1.20.0" -weakdeps = ["HTTP"] [deps.FileIO.extensions] HTTPExt = "HTTP" + [deps.FileIO.weakdeps] + HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" + [[deps.FileWatching]] uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" version = "1.11.0" [[deps.FillArrays]] deps = ["LinearAlgebra"] -git-tree-sha1 = "417289ac840d201005d9ed16789a46defccbe36d" +git-tree-sha1 = "831f86fde78ab7f6470d6ba4d1161a11fb6b4ec0" uuid = "1a297f60-69ca-5386-bcde-b61e274b549b" -version = "1.18.1" +version = "1.18.2" [deps.FillArrays.extensions] FillArraysPDMatsExt = "PDMats" @@ -831,45 +729,16 @@ version = "2.33.0" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" -[[deps.FixedPointNumbers]] -deps = ["Random", "Statistics"] -git-tree-sha1 = "59af96b98217c6ef4ae0dfe065ac7c20831d1a84" -uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" -version = "0.8.6" - -[[deps.Fontconfig_jll]] -deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Zlib_jll"] -git-tree-sha1 = "f85dac9a96a01087df6e3a749840015a0ca3817d" -uuid = "a3f928ae-7b40-5064-980b-68af3947d34b" -version = "2.17.1+0" - -[[deps.Format]] -git-tree-sha1 = "9c68794ef81b08086aeb32eeaf33531668d5f5fc" -uuid = "1fa38f19-a742-5d3f-a2b9-30dd87b9d5f8" -version = "1.3.7" - [[deps.ForwardDiff]] deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "LogExpFunctions", "NaNMath", "Preferences", "Printf", "Random", "SpecialFunctions"] -git-tree-sha1 = "3b0f72e2ffef1a139ac450725c9ecd01c0a3c050" +git-tree-sha1 = "a55669aa77ae8bbdc7310c4027112500ff5ec874" uuid = "f6369f11-7733-5829-9624-2563aa707210" -version = "1.4.6" +version = "1.4.7" weakdeps = ["StaticArrays"] [deps.ForwardDiff.extensions] ForwardDiffStaticArraysExt = "StaticArrays" -[[deps.FreeType2_jll]] -deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Zlib_jll"] -git-tree-sha1 = "70329abc09b886fd2c5d94ad2d9527639c421e3e" -uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7" -version = "2.14.3+1" - -[[deps.FriBidi_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "7a214fdac5ed5f59a22c2d9a885a16da1c74bbc7" -uuid = "559328eb-81f9-559d-9380-de523a88c83c" -version = "1.0.17+0" - [[deps.FunctionWrappers]] git-tree-sha1 = "d62485945ce5ae9c0c48f124a84998d755bae00e" uuid = "069b7b12-0de2-55c6-9aab-29f3d0a68a2e" @@ -890,52 +759,22 @@ version = "1.13.0" EnzymeCore = "f151be2c-9106-41f4-ab19-57ee4f262869" Mooncake = "da2b9cff-9c12-43a0-ae48-6db2b0edb7d6" -[[deps.FunctionalCollections]] -deps = ["Test"] -git-tree-sha1 = "04cb9cfaa6ba5311973994fe3496ddec19b6292a" -uuid = "de31a74c-ac4f-5751-b3fd-e18cd04993ca" -version = "0.5.0" - [[deps.Future]] deps = ["Random"] uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820" version = "1.11.0" -[[deps.GLFW_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Libglvnd_jll", "Xorg_libXcursor_jll", "Xorg_libXi_jll", "Xorg_libXinerama_jll", "Xorg_libXrandr_jll", "libdecor_jll", "xkbcommon_jll"] -git-tree-sha1 = "64bbbb7d1499297751b536dd39c58b20750ab1db" -uuid = "0656b61e-2033-5cc2-a64a-77c0f6c09b89" -version = "3.5.1+0" - [[deps.GPUArraysCore]] deps = ["Adapt"] -git-tree-sha1 = "0b2b3e8b8fb7c1f5ddbf498be2eb2b3b37c9c0ed" +git-tree-sha1 = "f60436514e6d73f8ee069ae4ee0ff038fe96b69d" uuid = "46192b85-c4d5-4398-a991-12ede77f4527" -version = "0.2.1" - -[[deps.GR]] -deps = ["Artifacts", "Base64", "DelimitedFiles", "Downloads", "GR_jll", "JSON", "Libdl", "LinearAlgebra", "Preferences", "Printf", "Qt6Wayland_jll", "Random", "Serialization", "Sockets", "TOML", "Tar", "Test", "p7zip_jll"] -git-tree-sha1 = "4d777f73c46b46b8b5276206059cf8a195499314" -uuid = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71" -version = "0.73.27" - - [deps.GR.extensions] - IJuliaExt = "IJulia" - - [deps.GR.weakdeps] - IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" - -[[deps.GR_jll]] -deps = ["Artifacts", "Bzip2_jll", "Cairo_jll", "FFMPEG_jll", "Fontconfig_jll", "FreeType2_jll", "GLFW_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pixman_jll", "Qt6Base_jll", "Zlib_jll", "libpng_jll"] -git-tree-sha1 = "f8eb8f7ba13ea75083531647fc8faeda8d541f07" -uuid = "d2c73de3-f751-5644-a686-071e5b155ba9" -version = "0.73.27+0" +version = "0.2.2" [[deps.GenericLinearAlgebra]] deps = ["LinearAlgebra", "Printf", "Random", "libblastrampoline_jll"] -git-tree-sha1 = "961f8a5d33f043e02cdf2b8020e3a7b0aae78474" +git-tree-sha1 = "3b0e97a0de86e54fa897c6e431c179ad4cd1dea0" uuid = "14197337-ba66-59df-a3e3-ca00e7dcff7a" -version = "0.4.1" +version = "0.4.2" [[deps.GenericSchur]] deps = ["LinearAlgebra", "MatrixFactorizations", "Preferences", "Printf", "UUIDs"] @@ -943,18 +782,6 @@ git-tree-sha1 = "e3d1202ab9ae72edd4c46134ecbb6746ec420486" uuid = "c145ed77-6b09-5dd9-b285-bf645a82121e" version = "0.5.8" -[[deps.GettextRuntime_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll"] -git-tree-sha1 = "45288942190db7c5f760f59c04495064eedf9340" -uuid = "b0724c58-0f36-5564-988d-3bb0596ebc4a" -version = "0.22.4+0" - -[[deps.Ghostscript_jll]] -deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Zlib_jll"] -git-tree-sha1 = "38044a04637976140074d0b0621c1edf0eb531fd" -uuid = "61579ee1-b43e-5ca0-a5da-69d92c66a64b" -version = "9.55.1+0" - [[deps.Git]] deps = ["Git_LFS_jll", "Git_jll", "JLLWrappers", "OpenSSH_jll"] git-tree-sha1 = "824a1890086880696fc908fe12a17bcf61738bd8" @@ -963,9 +790,9 @@ version = "1.5.0" [[deps.Git_LFS_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "8c66e385d631bb934ff05e76d4a566c640c8df69" +git-tree-sha1 = "d310b22511a51569401e975194cc76077bcd3786" uuid = "020c3dae-16b3-5ae5-87b3-4cb189e250b2" -version = "3.7.1+0" +version = "3.8.1+0" [[deps.Git_jll]] deps = ["Artifacts", "Expat_jll", "JLLWrappers", "LibCURL_jll", "Libdl", "Libiconv_jll", "OpenSSL_jll", "PCRE2_jll", "Zlib_jll"] @@ -973,18 +800,6 @@ git-tree-sha1 = "3842d761162baf8efce819cdfb5ea070fe44ac40" uuid = "f8c6e375-362e-5223-8a59-34ff63f689eb" version = "2.56.0+0" -[[deps.Glib_jll]] -deps = ["Artifacts", "GettextRuntime_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE2_jll", "Zlib_jll"] -git-tree-sha1 = "090526e65de8f69648ac156daae153de8b56df62" -uuid = "7746bdde-850d-59dc-9ae8-88ece973131d" -version = "2.88.3+0" - -[[deps.Graphite2_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "69ffb934a5c5b7e086a0b4fee3427db2556fba6e" -uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472" -version = "1.3.16+0" - [[deps.HDF5]] deps = ["Compat", "HDF5_jll", "Libdl", "MPIPreferences", "Mmap", "Preferences", "Printf", "Random", "Requires", "UUIDs"] git-tree-sha1 = "26e37af34e8ccb7a8358bd8de1619c7bcb526738" @@ -999,21 +814,9 @@ version = "0.17.4" [[deps.HDF5_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LibCURL_jll", "Libdl", "MPIABI_jll", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "OpenSSL_jll", "TOML", "Zlib_jll", "aws_c_s3_jll", "dlfcn_win32_jll", "libaec_jll", "mpif_jll"] -git-tree-sha1 = "58d79d3a61d2fdeda667fead57546ae22a20b27a" +git-tree-sha1 = "d791a1c4c3c8f383b248a799be31105d00310204" uuid = "0234f1f7-429e-5d53-9886-15a909be8d59" -version = "2.2.3+0" - -[[deps.HTTP]] -deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "PrecompileTools", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"] -git-tree-sha1 = "51059d23c8bb67911a2e6fd5130229113735fc7e" -uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3" -version = "1.11.0" - -[[deps.HarfBuzz_jll]] -deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll"] -git-tree-sha1 = "9d9531a9cb63a9edc33836414e82a07e81710de2" -uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566" -version = "100.14004.0+0" +version = "2.2.4+0" [[deps.HashArrayMappedTries]] git-tree-sha1 = "2eaa69a7cab70a52b9687c8bf950a5a93ec895ae" @@ -1021,19 +824,15 @@ uuid = "076d061b-32b6-4027-95e0-9a2c6f6d7e74" version = "0.2.0" [[deps.HelpPlots]] -git-tree-sha1 = "07d389d81ca31e42d68bd88cfa1e900a63b7d378" +git-tree-sha1 = "4b09f61ba5ab9242250855d44cf8747d0d43b981" uuid = "5880b2e8-ad0a-48c9-bacd-30dc9c1357e0" -version = "1.1.0" -weakdeps = ["Plots"] +version = "1.1.1" [deps.HelpPlots.extensions] HelpPlotsExt = "Plots" -[[deps.Hiccup]] -deps = ["MacroTools", "Test"] -git-tree-sha1 = "6187bb2d5fcbb2007c39e7ac53308b0d371124bd" -uuid = "9fb69e20-1954-56bb-a84f-559cc56a8ff7" -version = "0.2.2" + [deps.HelpPlots.weakdeps] + Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" [[deps.Hwloc_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "XML2_jll", "Xorg_libpciaccess_jll"] @@ -1043,9 +842,9 @@ version = "2.15.0+0" [[deps.IMASdd]] deps = ["AbstractTrees", "AdaptiveArrayPools", "CoordinateConventions", "DataInterpolations", "Dates", "FastInterpolations", "HDF5", "InteractiveUtils", "JSON", "Memoization", "OrderedCollections", "PrecompileTools", "Preferences", "Printf", "StaticArraysCore", "ThreadSafeDicts", "TimerOutputs"] -git-tree-sha1 = "2284c64e11d8fb6225971bcc367c67bae51923f5" +git-tree-sha1 = "34f3a6338f95ee5df3ca455a96f416b0a2044eb0" uuid = "c5a45a97-b3f9-491c-b9a7-aa88c3bc0067" -version = "8.6.2" +version = "8.6.3" [[deps.IOCapture]] deps = ["Logging", "Random"] @@ -1058,19 +857,6 @@ git-tree-sha1 = "debdd00ffef04665ccbb3e150747a77560e8fad1" uuid = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173" version = "0.1.1" -[[deps.InlineStrings]] -git-tree-sha1 = "06b65886c7577a3784d616e29f1302c2e36e389d" -uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48" -version = "1.4.6" - - [deps.InlineStrings.extensions] - ArrowTypesExt = "ArrowTypes" - ParsersExt = "Parsers" - - [deps.InlineStrings.weakdeps] - ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd" - Parsers = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" - [[deps.IntelOpenMP_jll]] deps = ["Artifacts", "JLLWrappers", "LazyArtifacts", "Libdl"] git-tree-sha1 = "ec1debd61c300961f98064cfb21287613ad7f303" @@ -1111,24 +897,19 @@ version = "1.0.12" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" [[deps.InverseFunctions]] -git-tree-sha1 = "a779299d77cd080bf77b97535acecd73e1c5e5cb" +git-tree-sha1 = "5cc6ad3e2bfc75a11eed92632ee341d37f4a4536" uuid = "3587e190-3f89-42d0-90ee-14403ec27112" -version = "0.1.17" +version = "0.1.18" weakdeps = ["Dates", "Test"] [deps.InverseFunctions.extensions] InverseFunctionsDatesExt = "Dates" InverseFunctionsTestExt = "Test" -[[deps.InvertedIndices]] -git-tree-sha1 = "6da3c4316095de0f5ee2ebd875df8721e7e0bdbe" -uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f" -version = "1.3.1" - [[deps.IrrationalConstants]] -git-tree-sha1 = "b2d91fe939cae05960e760110b328288867b5758" +git-tree-sha1 = "d22e8bdb3f03c300a103ff23c0dd5a654c6701de" uuid = "92d709cd-6900-40b7-9082-c6be49f344b6" -version = "0.2.6" +version = "0.2.7" [[deps.IteratorInterfaceExtensions]] git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856" @@ -1140,16 +921,12 @@ deps = ["ChunkCodecCore", "ChunkCodecLibZlib", "ChunkCodecLibZstd", "FileIO", "M git-tree-sha1 = "9ce2e7c49ae4a7035b7d60db3a553b1f6c16875d" uuid = "033835bb-8acc-5ee8-8aae-3f567f8a3819" version = "0.6.7" -weakdeps = ["UnPack"] [deps.JLD2.extensions] UnPackExt = "UnPack" -[[deps.JLFzf]] -deps = ["REPL", "Random", "fzf_jll"] -git-tree-sha1 = "82f7acdc599b65e0f8ccd270ffa1467c21cb647b" -uuid = "1019f520-868f-41f5-a6de-eb00f4b6a39c" -version = "0.1.11" + [deps.JLD2.weakdeps] + UnPack = "3a884ed6-31ef-47d7-9d2a-63182c4928ed" [[deps.JLLWrappers]] deps = ["Artifacts", "Preferences"] @@ -1157,17 +934,19 @@ git-tree-sha1 = "7204148362dafe5fe6a273f855b8ccbe4df8173e" uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" version = "1.8.0" -[[deps.JSExpr]] -deps = ["JSON", "MacroTools", "Observables", "WebIO"] -git-tree-sha1 = "b413a73785b98474d8af24fd4c8a975e31df3658" -uuid = "97c1335a-c9c5-57fe-bc5d-ec35cebe8660" -version = "0.5.4" - [[deps.JSON]] -deps = ["Dates", "Mmap", "Parsers", "Unicode"] -git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" +deps = ["Dates", "Logging", "Parsers", "PrecompileTools", "StructUtils", "UUIDs", "Unicode"] +git-tree-sha1 = "06d90f699285d055e34f0b014636a75a17ff2252" uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" -version = "0.21.4" +version = "1.13.4" + + [deps.JSON.extensions] + JSONArrowExt = ["ArrowTypes"] + JSONSerializationExt = ["Serialization"] + + [deps.JSON.weakdeps] + ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd" + Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" [[deps.Jieko]] deps = ["ExproniconLite"] @@ -1175,65 +954,17 @@ git-tree-sha1 = "2f05ed29618da60c06a87e9c033982d4f71d0b6c" uuid = "ae98c720-c025-4a4a-838c-29b094483192" version = "0.2.1" -[[deps.JpegTurbo_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "037babc10853eeb8e585418922246cb97b8e5b74" -uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8" -version = "3.2.0+1" - -[[deps.Kaleido_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "43032da5832754f58d14a91ffbe86d5f176acda9" -uuid = "f7e6163d-2fa5-5f23-b69c-1db539e41963" -version = "0.2.1+0" - [[deps.Krylov]] deps = ["LinearAlgebra", "Printf", "SparseArrays"] -git-tree-sha1 = "e0574e7e2cd5c40d9a6ae444511fbadd51afbf30" +git-tree-sha1 = "b13076e4fff6ea0d0a5ddd9b939a11ca2027755a" uuid = "ba0b0d4f-ebba-5204-a429-3ac8c609bfb7" -version = "0.10.11" - -[[deps.LAME_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "059aabebaa7c82ccb853dd4a0ee9d17796f7e1bc" -uuid = "c1c5ebd0-6772-5130-a774-d5fcae4a789d" -version = "3.100.3+0" - -[[deps.LERC_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "39bca05343661c347aae0bca57a5994a0bf4f08d" -uuid = "88015f11-f218-50d7-93a8-a6af411a945d" -version = "4.2.0+0" - -[[deps.LLVMOpenMP_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "e5b100780d4d30d63b4618d7930d48af409c1772" -uuid = "1d63c593-3942-5779-bab2-d838dc0a180e" -version = "23.1.1+0" +version = "0.10.12" [[deps.LaTeXStrings]] git-tree-sha1 = "f88f3ccef05a6a72a0cf0ed417c8fd68530f4ab2" uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" version = "1.4.1" -[[deps.Latexify]] -deps = ["Format", "Ghostscript_jll", "InteractiveUtils", "LaTeXStrings", "MacroTools", "Markdown", "OrderedCollections", "Requires"] -git-tree-sha1 = "df7566479bd64f20bd16b09960145e70160ffb3b" -uuid = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" -version = "0.16.12" - - [deps.Latexify.extensions] - DataFramesExt = "DataFrames" - SparseArraysExt = "SparseArrays" - SymEngineExt = "SymEngine" - TectonicExt = "tectonic_jll" - - [deps.Latexify.weakdeps] - DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" - SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - SymEngine = "123dc426-2d89-5057-bbad-38513e3affd8" - tectonic_jll = "d7dd28d6-a5e6-559c-9131-7eb760cdacc5" - [[deps.LayoutPointers]] deps = ["ArrayInterface", "LinearAlgebra", "ManualMemory", "SIMDTypes", "Static", "StaticArrayInterface"] git-tree-sha1 = "a9eaadb366f5493a5654e843864c13d8b107548c" @@ -1245,12 +976,6 @@ git-tree-sha1 = "0f2da712350b020bc3957f269c9caad516383ee0" uuid = "0e77f7df-68c5-4e49-93ce-4cd80f5598bf" version = "1.3.0" -[[deps.Lazy]] -deps = ["MacroTools"] -git-tree-sha1 = "1370f8202dac30758f3c345f9909b97f53d87d3f" -uuid = "50d2b5c4-7a5e-59d5-8109-a42b560f39c0" -version = "0.15.1" - [[deps.LazyArtifacts]] deps = ["Artifacts", "Pkg"] uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3" @@ -1285,42 +1010,12 @@ version = "1.11.0+1" uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" version = "1.11.0" -[[deps.Libffi_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "c8da7e6a91781c41a863611c7e966098d783c57a" -uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490" -version = "3.4.7+0" - -[[deps.Libglvnd_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll", "Xorg_libXext_jll"] -git-tree-sha1 = "d36c21b9e7c172a44a10484125024495e2625ac0" -uuid = "7e76a0d4-f3c7-5321-8279-8d96eeed0f29" -version = "1.7.1+1" - [[deps.Libiconv_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] git-tree-sha1 = "be484f5c92fad0bd8acfef35fe017900b0b73809" uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" version = "1.18.0+0" -[[deps.Libmount_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "e2b48382276415b5a5d5282a5dd75a0f0bf4ac84" -uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9" -version = "2.42.2+0" - -[[deps.Libtiff_jll]] -deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"] -git-tree-sha1 = "aebd334d06cee9f24cea70bd19a39749daf73881" -uuid = "89763e89-9b03-5906-acba-b20f662cd828" -version = "4.7.3+0" - -[[deps.Libuuid_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "d620582b1f0cbe2c72dd1d5bd195a9ce73370ab1" -uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700" -version = "2.42.0+0" - [[deps.LineSearch]] deps = ["ADTypes", "CommonSolve", "ConcreteStructs", "FastClosures", "LinearAlgebra", "MaybeInplace", "PrecompileTools", "SciMLBase", "SciMLJacobianOperators", "StaticArraysCore"] git-tree-sha1 = "2e05027f5a68891d997fcad60d11ce48d09208d0" @@ -1524,22 +1219,11 @@ weakdeps = ["SparseArrays"] [deps.MaybeInplace.extensions] MaybeInplaceSparseArraysExt = "SparseArrays" -[[deps.MbedTLS]] -deps = ["Dates", "MbedTLS_jll", "MozillaCACerts_jll", "NetworkOptions", "Random", "Sockets"] -git-tree-sha1 = "8785729fa736197687541f7053f6d8ab7fc44f92" -uuid = "739be429-bea8-5141-9913-cc70e7f3736d" -version = "1.1.10" - [[deps.MbedTLS_jll]] deps = ["Artifacts", "Libdl"] uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" version = "2.28.6+0" -[[deps.Measures]] -git-tree-sha1 = "b513cedd20d9c914783d8ad83d08120702bf2c77" -uuid = "442fdcdd-2543-5da2-b0f3-8c86c306513e" -version = "0.3.3" - [[deps.Memoization]] deps = ["MacroTools"] git-tree-sha1 = "7dbf904fa6c4447bd1f1d316886bfbe29feacf45" @@ -1547,16 +1231,10 @@ uuid = "6fafb56a-5788-4b4e-91ca-c0cea6611c73" version = "0.2.2" [[deps.MicrosoftMPI_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "bc95bf4149bf535c09602e3acdf950d9b4376227" +deps = ["Artifacts", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] +git-tree-sha1 = "3350d35d0ebcf7ccee0c66290b8e95d82419ef6f" uuid = "9237b28f-5490-5468-be7b-bb81f5f5e6cf" -version = "10.1.4+3" - -[[deps.Missings]] -deps = ["DataAPI"] -git-tree-sha1 = "ec4f7fbeab05d7747bdf98eb74d130a2a2ed298d" -uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28" -version = "1.2.0" +version = "10.1.5+0" [[deps.Mmap]] uuid = "a63ad114-7e13-5084-954f-fe012c677804" @@ -1578,18 +1256,6 @@ git-tree-sha1 = "283bf85d4a767481dd924dff0eee1735e95f449e" uuid = "46d2c3a1-f734-5fdb-9937-b9b9aeba4221" version = "0.2.7" -[[deps.Mustache]] -deps = ["DataFrames", "Printf", "Tables"] -git-tree-sha1 = "7437c911e62f50e7a043a774ebdc0dbd780d9fe5" -uuid = "ffc61752-8dc7-55ee-8c37-f3e9cdd09e70" -version = "1.1.0" - -[[deps.Mux]] -deps = ["AssetRegistry", "Base64", "HTTP", "Hiccup", "MbedTLS", "Pkg", "Sockets"] -git-tree-sha1 = "7295d849103ac4fcbe3b2e439f229c5cc77b9b69" -uuid = "a975b10e-0019-58db-a62f-e48ff68538c9" -version = "1.0.2" - [[deps.NLSolversBase]] deps = ["ADTypes", "DifferentiationInterface", "FiniteDiff", "LinearAlgebra"] git-tree-sha1 = "f96d38936d92d610318dec4d3b5ef37b0373c20f" @@ -1598,9 +1264,9 @@ version = "8.0.1" [[deps.NaNMath]] deps = ["OpenLibm_jll"] -git-tree-sha1 = "dbd2e8cd2c1c27f0b584f6661b4309609c5a685e" +git-tree-sha1 = "676c0f1c1c34224d6869675047a54b42fdc7b8c5" uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" -version = "1.1.4" +version = "1.1.5" [[deps.NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" @@ -1697,17 +1363,6 @@ weakdeps = ["ForwardDiff"] [deps.NonlinearSolveSpectralMethods.extensions] NonlinearSolveSpectralMethodsForwardDiffExt = "ForwardDiff" -[[deps.Observables]] -git-tree-sha1 = "7438a59546cf62428fc9d1bc94729146d37a7225" -uuid = "510215fc-4207-5dde-b226-833fc4488ee2" -version = "0.5.5" - -[[deps.Ogg_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "b6aa4566bb7ae78498a5e68943863fa8b5231b59" -uuid = "e7412a2a-1a6e-54c0-be00-318e2571c051" -version = "1.3.6+0" - [[deps.OpenBLASConsistentFPCSR_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"] git-tree-sha1 = "38a93f17e431141c6470bb67a88952a7c4f0e928" @@ -1736,12 +1391,6 @@ git-tree-sha1 = "1f640b82a627df3dbbaaff756a201a9ceb10dd1f" uuid = "9bd350c2-7e96-507f-8002-3f2e150b4e1b" version = "10.6.1+0" -[[deps.OpenSSL]] -deps = ["BitFlags", "Dates", "MozillaCACerts_jll", "NetworkOptions", "OpenSSL_jll", "Sockets"] -git-tree-sha1 = "5caab1056e0734b87f62ae3a3b520d47d3e994e8" -uuid = "4d8831e6-92b7-49fb-bdf8-b643e874388c" -version = "1.7.0" - [[deps.OpenSSL_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] git-tree-sha1 = "20dd6a885a6fe8b07584d3eb8073133ad04c72e3" @@ -1754,16 +1403,10 @@ git-tree-sha1 = "1346c9208249809840c91b26703912dff463d335" uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e" version = "0.5.6+0" -[[deps.Opus_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "e2bb57a313a74b8104064b7efd01406c0a50d2ff" -uuid = "91d4177d-7536-5919-b921-800302f37372" -version = "1.6.1+0" - [[deps.OrderedCollections]] -git-tree-sha1 = "94ba93778373a53bfd5a0caaf7d809c445292ff4" +git-tree-sha1 = "b079a6affebde167dad8ffe0d670b53917fbcb68" uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" -version = "1.8.2" +version = "2.0.3" [[deps.OrdinaryDiffEq]] deps = ["ADTypes", "Adapt", "ArrayInterface", "CommonSolve", "DataStructures", "DiffEqBase", "DocStringExtensions", "EnumX", "ExponentialUtilities", "FastBroadcast", "FastClosures", "FillArrays", "FiniteDiff", "ForwardDiff", "FunctionWrappersWrappers", "InteractiveUtils", "LineSearches", "LinearAlgebra", "LinearSolve", "Logging", "MacroTools", "MuladdMacro", "NonlinearSolve", "OrdinaryDiffEqAdamsBashforthMoulton", "OrdinaryDiffEqBDF", "OrdinaryDiffEqCore", "OrdinaryDiffEqDefault", "OrdinaryDiffEqDifferentiation", "OrdinaryDiffEqExplicitRK", "OrdinaryDiffEqExponentialRK", "OrdinaryDiffEqExtrapolation", "OrdinaryDiffEqFIRK", "OrdinaryDiffEqFeagin", "OrdinaryDiffEqFunctionMap", "OrdinaryDiffEqHighOrderRK", "OrdinaryDiffEqIMEXMultistep", "OrdinaryDiffEqLinear", "OrdinaryDiffEqLowOrderRK", "OrdinaryDiffEqLowStorageRK", "OrdinaryDiffEqNonlinearSolve", "OrdinaryDiffEqNordsieck", "OrdinaryDiffEqPDIRK", "OrdinaryDiffEqPRK", "OrdinaryDiffEqQPRK", "OrdinaryDiffEqRKN", "OrdinaryDiffEqRosenbrock", "OrdinaryDiffEqSDIRK", "OrdinaryDiffEqSSPRK", "OrdinaryDiffEqStabilizedIRK", "OrdinaryDiffEqStabilizedRK", "OrdinaryDiffEqSymplecticRK", "OrdinaryDiffEqTsit5", "OrdinaryDiffEqVerner", "Polyester", "PreallocationTools", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "SciMLOperators", "SciMLStructures", "SimpleNonlinearSolve", "SparseArrays", "Static", "StaticArrayInterface", "StaticArrays", "TruncatedStacktraces"] @@ -1968,35 +1611,11 @@ deps = ["Artifacts", "Libdl"] uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" version = "10.42.0+1" -[[deps.Pango_jll]] -deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "FriBidi_jll", "Glib_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl"] -git-tree-sha1 = "1912a9f1b9ca55005b03ba075f8e19993583e237" -uuid = "36c8627f-9965-5494-a995-c6b170f724f3" -version = "1.58.2+0" - -[[deps.Parameters]] -deps = ["OrderedCollections", "UnPack"] -git-tree-sha1 = "34c0e9ad262e5f7fc75b10a9952ca7692cfc5fbe" -uuid = "d96e819e-fc66-5662-9728-84c9c7592b0a" -version = "0.12.3" - [[deps.Parsers]] -deps = ["Dates", "PrecompileTools", "UUIDs"] -git-tree-sha1 = "ba0dc8a8a67cacac4842631f960c046e4e563675" +deps = ["Dates", "PrecompileTools"] +git-tree-sha1 = "e5ae52d1f4f19a9591d615cb0f1ef8d415b40933" uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "2.8.8" - -[[deps.Pidfile]] -deps = ["FileWatching", "Test"] -git-tree-sha1 = "2d8aaf8ee10df53d0dfb9b8ee44ae7c04ced2b03" -uuid = "fa939f87-e72e-5be4-a000-7fc836dbe307" -version = "1.3.0" - -[[deps.Pixman_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "Libdl"] -git-tree-sha1 = "e4a6721aa89e62e5d4217c0b21bd714263779dda" -uuid = "30392449-352a-5448-841d-b1acce4e97dc" -version = "0.46.4+0" +version = "3.0.6" [[deps.Pkg]] deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"] @@ -2007,80 +1626,6 @@ weakdeps = ["REPL"] [deps.Pkg.extensions] REPLExt = "REPL" -[[deps.PlotThemes]] -deps = ["PlotUtils", "Statistics"] -git-tree-sha1 = "41031ef3a1be6f5bbbf3e8073f210556daeae5ca" -uuid = "ccf2f8ad-2431-5c83-bf29-c5338b663b6a" -version = "3.3.0" - -[[deps.PlotUtils]] -deps = ["ColorSchemes", "Colors", "Dates", "PrecompileTools", "Printf", "Reexport", "Statistics"] -git-tree-sha1 = "ddcc36fd83fb4cd459a53c891981697bde0d3de6" -uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043" -version = "1.5.1" - -[[deps.PlotlyBase]] -deps = ["ColorSchemes", "Colors", "Dates", "DelimitedFiles", "DocStringExtensions", "JSON", "LaTeXStrings", "Logging", "Parameters", "Pkg", "REPL", "Requires", "Statistics", "UUIDs"] -git-tree-sha1 = "6256ab3ee24ef079b3afa310593817e069925eeb" -uuid = "a03496cd-edff-5a9b-9e67-9cda94a718b5" -version = "0.8.23" - - [deps.PlotlyBase.extensions] - DataFramesExt = "DataFrames" - DistributionsExt = "Distributions" - IJuliaExt = "IJulia" - JSON3Ext = "JSON3" - - [deps.PlotlyBase.weakdeps] - DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" - Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" - IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" - JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" - -[[deps.PlotlyJS]] -deps = ["Base64", "Blink", "DelimitedFiles", "JSExpr", "JSON", "Kaleido_jll", "Markdown", "Pkg", "PlotlyBase", "PlotlyKaleido", "REPL", "Reexport", "Requires", "WebIO"] -git-tree-sha1 = "4bf6c08295346efba58bb2d5f8c52984efed7f27" -uuid = "f0f68f2c-4968-5e81-91da-67840de0976a" -version = "0.18.18" - - [deps.PlotlyJS.extensions] - CSVExt = "CSV" - DataFramesExt = ["DataFrames", "CSV"] - IJuliaExt = "IJulia" - JSON3Ext = "JSON3" - - [deps.PlotlyJS.weakdeps] - CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" - DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" - IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" - JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" - -[[deps.PlotlyKaleido]] -deps = ["Artifacts", "Base64", "JSON", "Kaleido_jll"] -git-tree-sha1 = "c7a270d11881c0709052f8e3def3d60d551e99f3" -uuid = "f2990250-8cf9-495f-b13a-cce12b45703c" -version = "2.3.1" - -[[deps.Plots]] -deps = ["Base64", "Contour", "Dates", "Downloads", "FFMPEG", "FixedPointNumbers", "GR", "JLFzf", "JSON", "LaTeXStrings", "Latexify", "LinearAlgebra", "Measures", "NaNMath", "Pkg", "PlotThemes", "PlotUtils", "PrecompileTools", "Printf", "REPL", "Random", "RecipesBase", "RecipesPipeline", "Reexport", "RelocatableFolders", "Requires", "Scratch", "Showoff", "SparseArrays", "Statistics", "StatsBase", "TOML", "UUIDs", "UnicodeFun", "Unzip"] -git-tree-sha1 = "83bd514e8ff16b5858ac54c53fa0bcf6002a3b00" -uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" -version = "1.41.7" - - [deps.Plots.extensions] - FileIOExt = "FileIO" - GeometryBasicsExt = "GeometryBasics" - IJuliaExt = "IJulia" - ImageInTerminalExt = "ImageInTerminal" - UnitfulExt = "Unitful" - - [deps.Plots.weakdeps] - FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" - GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326" - IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" - ImageInTerminal = "d8c32880-2388-543b-8c61-d9f865259254" - Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" - [[deps.Polyester]] deps = ["ArrayInterface", "BitTwiddlingConvenienceFunctions", "CPUSummary", "IfElse", "ManualMemory", "PolyesterWeave", "Static", "StaticArrayInterface", "StrideArraysCore", "ThreadingUtilities"] git-tree-sha1 = "16bbc30b5ebea91e9ce1671adc03de2832cff552" @@ -2093,12 +1638,6 @@ git-tree-sha1 = "645bed98cd47f72f67316fd42fc47dee771aefcd" uuid = "1d0040c9-8b98-4ee7-8388-3f51789ca0ad" version = "0.2.2" -[[deps.PooledArrays]] -deps = ["DataAPI", "Future"] -git-tree-sha1 = "36d8b4b899628fb92c2749eb488d884a926614d3" -uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720" -version = "1.4.3" - [[deps.PreallocationTools]] deps = ["Adapt", "ArrayInterface", "PrecompileTools", "SciMLPublic"] git-tree-sha1 = "cd00e28071a75c98664663f87c2ae447d25c0503" @@ -2123,15 +1662,15 @@ version = "1.2.1" [[deps.Preferences]] deps = ["TOML"] -git-tree-sha1 = "5005266de4bfe50e53ff44a5cb5c540b6e47a254" +git-tree-sha1 = "aef1987d90dcc2e2cbd919ad10b2d25bcce4f418" uuid = "21216c6a-2e73-6563-6e65-726566657250" -version = "1.6.0" +version = "1.6.1" [[deps.PrettyTables]] deps = ["Crayons", "LaTeXStrings", "Markdown", "PrecompileTools", "Printf", "REPL", "Reexport", "StringManipulation", "StyledStrings", "Tables"] -git-tree-sha1 = "99fd8f32ab6728ff205cddd24e2d9ea6cbb498d3" +git-tree-sha1 = "eeac4c9337f876c3c32b8da732141953567f57c9" uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" -version = "3.5.0" +version = "3.5.1" [deps.PrettyTables.extensions] PrettyTablesExcelExt = "XLSX" @@ -2148,11 +1687,6 @@ deps = ["Unicode"] uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" version = "1.11.0" -[[deps.PtrArrays]] -git-tree-sha1 = "4fbbafbc6251b883f4d2705356f3641f3652a7fe" -uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d" -version = "1.4.0" - [[deps.PureKLU]] deps = ["LinearAlgebra", "PrecompileTools", "SparseArrays"] git-tree-sha1 = "ad0e84647fe7b3c2ae02aaeace1522139aaf6a27" @@ -2163,36 +1697,6 @@ weakdeps = ["ForwardDiff"] [deps.PureKLU.extensions] PureKLUForwardDiffExt = "ForwardDiff" -[[deps.Qt6Base_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "Fontconfig_jll", "Glib_jll", "JLLWrappers", "Libdl", "Libglvnd_jll", "OpenSSL_jll", "Vulkan_Loader_jll", "Xorg_libSM_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Xorg_libxcb_jll", "Xorg_xcb_util_cursor_jll", "Xorg_xcb_util_image_jll", "Xorg_xcb_util_keysyms_jll", "Xorg_xcb_util_renderutil_jll", "Xorg_xcb_util_wm_jll", "Zlib_jll", "libinput_jll", "xkbcommon_jll"] -git-tree-sha1 = "144895f6166994730ee7ff8113b981fc360638f1" -uuid = "c0090381-4147-56d7-9ebc-da0b1113ec56" -version = "6.10.2+2" - -[[deps.Qt6Declarative_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Qt6Base_jll", "Qt6ShaderTools_jll", "Qt6Svg_jll"] -git-tree-sha1 = "159d253ab126d5b29230cf53521899bea4ef4648" -uuid = "629bc702-f1f5-5709-abd5-49b8460ea067" -version = "6.10.2+2" - -[[deps.Qt6ShaderTools_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Qt6Base_jll"] -git-tree-sha1 = "4d85eedf69d875982c46643f6b4f66919d7e157b" -uuid = "ce943373-25bb-56aa-8eca-768745ed7b5a" -version = "6.10.2+1" - -[[deps.Qt6Svg_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Qt6Base_jll"] -git-tree-sha1 = "81587ff5ff25a4e1115ce191e36285ede0334c9d" -uuid = "6de9746b-f93d-5813-b365-ba18ad4a9cf3" -version = "6.10.2+0" - -[[deps.Qt6Wayland_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Qt6Base_jll", "Qt6Declarative_jll"] -git-tree-sha1 = "672c938b4b4e3e0169a07a5f227029d4905456f2" -uuid = "e99dba38-086e-5de3-a5b1-6e4c66e897c3" -version = "6.10.2+1" - [[deps.QuadGK]] deps = ["DataStructures", "LinearAlgebra"] git-tree-sha1 = "5e8e8b0ab68215d7a2b14b9921a946fee794749e" @@ -2231,12 +1735,6 @@ git-tree-sha1 = "5c3d09cc4f31f5fc6af001c250bf1278733100ff" uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" version = "1.3.4" -[[deps.RecipesPipeline]] -deps = ["Dates", "NaNMath", "PlotUtils", "PrecompileTools", "RecipesBase"] -git-tree-sha1 = "45cf9fd0ca5839d06ef333c8201714e888486342" -uuid = "01d81517-befc-4cb6-b9ec-a95719d0359c" -version = "0.6.12" - [[deps.RecursiveArrayTools]] deps = ["Adapt", "ArrayInterface", "DocStringExtensions", "GPUArraysCore", "LinearAlgebra", "PrecompileTools", "RecipesBase", "StaticArraysCore", "SymbolicIndexingInterface"] git-tree-sha1 = "d0282d612f22dcad7b81cf487b746e63aa2a6709" @@ -2284,12 +1782,6 @@ git-tree-sha1 = "ffd19052caf598b8653b99404058fce14828be51" uuid = "2792f1a3-b283-48e8-9a74-f99dce5104f3" version = "0.1.0" -[[deps.RelocatableFolders]] -deps = ["SHA", "Scratch"] -git-tree-sha1 = "ffdaf70d81cf6ff22c2b6e733c900c3321cab864" -uuid = "05181044-ff0b-4ac5-8273-598c1e38db00" -version = "1.0.1" - [[deps.Requires]] deps = ["UUIDs"] git-tree-sha1 = "62389eeff14780bfe55195b7204c0d8738436d64" @@ -2403,9 +1895,9 @@ version = "1.10.1" [[deps.SciMLOperators]] deps = ["Accessors", "Adapt", "ArrayInterface", "DocStringExtensions", "LinearAlgebra", "PrecompileTools", "SciMLPublic"] -git-tree-sha1 = "2712b6eeef282b5cd8c38660e68c001a4b9b24e2" +git-tree-sha1 = "3feb8a7bce88bb3ef051a4eda8106004ad040b6f" uuid = "c0aeaf25-5076-4817-a8d5-81caf7dfa961" -version = "1.30.3" +version = "1.30.4" [deps.SciMLOperators.extensions] SciMLOperatorsLoopVectorizationExt = "LoopVectorization" @@ -2433,37 +1925,14 @@ git-tree-sha1 = "67a144433c4ce877ee6d1ada69a124d6b1ecf7be" uuid = "7e506255-f358-4e82-b7e4-beb19740aa63" version = "1.6.2" -[[deps.Scratch]] -deps = ["Dates"] -git-tree-sha1 = "9b81b8393e50b7d4e6d0a9f14e192294d3b7c109" -uuid = "6c6a2e73-6563-6170-7368-637461726353" -version = "1.3.0" - -[[deps.SentinelArrays]] -deps = ["Dates", "Random"] -git-tree-sha1 = "084c47c7c5ce5cfecefa0a98dff69eb3646b5a80" -uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c" -version = "1.4.10" - [[deps.Serialization]] uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" version = "1.11.0" [[deps.Setfield]] deps = ["ConstructionBase", "Future", "MacroTools", "StaticArraysCore"] -git-tree-sha1 = "c5391c6ace3bc430ca630251d02ea9687169ca68" +git-tree-sha1 = "89880a2cca5cabc68c9023f58e544beda1d2fc1f" uuid = "efcf1570-3423-57d1-acb7-fd33fddbac46" -version = "1.1.2" - -[[deps.Showoff]] -deps = ["Dates"] -git-tree-sha1 = "8238217340ad0aaabe11afe39c1098b5bc9f4c8e" -uuid = "992d4aef-0814-514b-bc4d-f2e9a6c4116f" -version = "1.1.1" - -[[deps.SimpleBufferStream]] -git-tree-sha1 = "f305871d2f381d21527c770d4788c06c097c9bc1" -uuid = "777ac1f9-54b0-4bf8-805c-2214025038e7" version = "1.2.0" [[deps.SimpleNonlinearSolve]] @@ -2486,12 +1955,6 @@ version = "2.12.0" uuid = "6462fe0b-24de-5631-8697-dd941f90decc" version = "1.11.0" -[[deps.SortingAlgorithms]] -deps = ["DataStructures"] -git-tree-sha1 = "13cd91cc9be159e3f4d95b857fa2aa383b53772a" -uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c" -version = "1.2.3" - [[deps.SparseArrays]] deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" @@ -2533,9 +1996,9 @@ version = "0.4.29" [[deps.SpecialFunctions]] deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"] -git-tree-sha1 = "429071b23f4c9a13fb6582f807cc2ef454082408" +git-tree-sha1 = "88ef0d98204515bc53dd4aaea5f7788882d1b66c" uuid = "276daf66-3868-5448-9aa4-cd146d93841b" -version = "2.9.0" +version = "2.9.1" [deps.SpecialFunctions.extensions] SpecialFunctionsChainRulesCoreExt = "ChainRulesCore" @@ -2565,9 +2028,9 @@ version = "1.10.0" [[deps.StaticArrays]] deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] -git-tree-sha1 = "de349d5585559b692f518b6d3fc7c179d64b2364" +git-tree-sha1 = "8a8a52f887f38d44014e24ee46b8a7311c625f6d" uuid = "90137ffa-7385-5640-81b9-e52037218182" -version = "1.9.23" +version = "1.9.24" [deps.StaticArrays.extensions] StaticArraysChainRulesCoreExt = "ChainRulesCore" @@ -2592,18 +2055,6 @@ weakdeps = ["SparseArrays"] [deps.Statistics.extensions] SparseArraysExt = ["SparseArrays"] -[[deps.StatsAPI]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "178ed29fd5b2a2cfc3bd31c13375ae925623ff36" -uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0" -version = "1.8.0" - -[[deps.StatsBase]] -deps = ["AliasTables", "DataAPI", "DataStructures", "IrrationalConstants", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"] -git-tree-sha1 = "adb9da019510162e67a4493fc235c23203d8b09e" -uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" -version = "0.34.13" - [[deps.StrideArraysCore]] deps = ["ArrayInterface", "CloseOpenIntervals", "IfElse", "LayoutPointers", "LinearAlgebra", "ManualMemory", "SIMDTypes", "Static", "StaticArrayInterface", "ThreadingUtilities"] git-tree-sha1 = "5316097111523c9a970596a5b33cfea5f92e8581" @@ -2616,6 +2067,28 @@ git-tree-sha1 = "9e3a02d73a8f0a9be04e677dbef758aab40f8bea" uuid = "892a3eda-7b42-436c-8928-eab12a02cf0e" version = "0.6.1" +[[deps.StructUtils]] +deps = ["Dates", "UUIDs"] +git-tree-sha1 = "b04bfab37484defe300b2f9ed64469abab2431ff" +uuid = "ec057cc2-7a8d-4b58-b3b3-92acb9f63b42" +version = "2.12.0" + + [deps.StructUtils.extensions] + StructUtilsDBInterfaceExt = ["DBInterface", "Tables"] + StructUtilsDataFramesExt = ["DataFrames"] + StructUtilsLazilyInitializedFieldsExt = ["LazilyInitializedFields"] + StructUtilsMeasurementsExt = ["Measurements"] + StructUtilsStaticArraysCoreExt = ["StaticArraysCore"] + StructUtilsTablesExt = ["Tables"] + + [deps.StructUtils.weakdeps] + DBInterface = "a10d1c49-ce27-4219-8d33-6db1a4562965" + DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" + LazilyInitializedFields = "0e77f7df-68c5-4e49-93ce-4cd80f5598bf" + Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7" + StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" + Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" + [[deps.StyledStrings]] uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b" version = "1.11.0" @@ -2627,9 +2100,9 @@ version = "7.7.0+0" [[deps.SymbolicIndexingInterface]] deps = ["Accessors", "ArrayInterface", "PrecompileTools", "RuntimeGeneratedFunctions", "StaticArraysCore"] -git-tree-sha1 = "7eb6da9656581ac9fbffaef3ef7950a090a5002a" +git-tree-sha1 = "42dd4c193a1bacea5b6adf78b6224de907b06351" uuid = "2efcf032-c050-4f8e-a9bb-153293bab1f5" -version = "0.3.55" +version = "0.3.56" weakdeps = ["PrettyTables"] [deps.SymbolicIndexingInterface.extensions] @@ -2657,12 +2130,6 @@ deps = ["ArgTools", "SHA"] uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" version = "1.10.0" -[[deps.TensorCore]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "1feb45f88d133a655e001435632f019a9a1bcdb6" -uuid = "62fd8b95-f654-4bbd-a8a5-9c27f68ccd50" -version = "0.1.1" - [[deps.Test]] deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" @@ -2702,223 +2169,27 @@ git-tree-sha1 = "ea3e54c2bdde39062abf5a9758a23735558705e1" uuid = "781d530d-4396-4725-bb49-402e4bee1e77" version = "1.4.0" -[[deps.URIs]] -deps = ["Serialization"] -git-tree-sha1 = "0392e5e3c5327b90a487b0ff3e818c4aecd3660b" -uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" -version = "1.7.1" - [[deps.UUIDs]] deps = ["Random", "SHA"] uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" version = "1.11.0" -[[deps.UnPack]] -git-tree-sha1 = "387c1f73762231e86e0c9c5443ce3b4a0a9a0c2b" -uuid = "3a884ed6-31ef-47d7-9d2a-63182c4928ed" -version = "1.0.2" - [[deps.Unicode]] uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" version = "1.11.0" -[[deps.UnicodeFun]] -deps = ["REPL"] -git-tree-sha1 = "53915e50200959667e78a92a418594b428dffddf" -uuid = "1cfade01-22cf-5700-b092-accc4b62d6e1" -version = "0.4.1" - -[[deps.Unzip]] -git-tree-sha1 = "ca0969166a028236229f63514992fc073799bb78" -uuid = "41fe7b60-77ed-43a1-b4f0-825fd5a5650d" -version = "0.2.0" - -[[deps.Vulkan_Loader_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Wayland_jll", "Xorg_libX11_jll", "Xorg_libXrandr_jll", "xkbcommon_jll"] -git-tree-sha1 = "2f0486047a07670caad3a81a075d2e518acc5c59" -uuid = "a44049a8-05dd-5a78-86c9-5fde0876e88c" -version = "1.3.243+0" - -[[deps.Wayland_jll]] -deps = ["Artifacts", "EpollShim_jll", "Expat_jll", "JLLWrappers", "Libdl", "Libffi_jll"] -git-tree-sha1 = "96478df35bbc2f3e1e791bc7a3d0eeee559e60e9" -uuid = "a2964d1f-97da-50d4-b82a-358c7fce9d89" -version = "1.24.0+0" - -[[deps.WebIO]] -deps = ["AssetRegistry", "Base64", "Distributed", "FunctionalCollections", "JSON", "Logging", "Observables", "Pkg", "Random", "Requires", "Sockets", "UUIDs", "WebSockets", "Widgets"] -git-tree-sha1 = "0eef0765186f7452e52236fa42ca8c9b3c11c6e3" -uuid = "0f1e0344-ec1d-5b48-a673-e5cf874b6c29" -version = "0.8.21" - -[[deps.WebSockets]] -deps = ["Base64", "Dates", "HTTP", "Logging", "Sockets"] -git-tree-sha1 = "4162e95e05e79922e44b9952ccbc262832e4ad07" -uuid = "104b5d7c-a370-577a-8038-80a2059c5097" -version = "1.6.0" - -[[deps.Widgets]] -deps = ["Colors", "Dates", "Observables", "OrderedCollections"] -git-tree-sha1 = "cab29ed70d9355a28b95b1798007ff3a00eefc91" -uuid = "cc8bc4a8-27d6-5769-a93b-9d913e69aa62" -version = "0.6.8" - [[deps.XML2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] git-tree-sha1 = "b826aedca5fce2e3c91904e338c279e5c1a2da74" uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" version = "2.15.4+0" -[[deps.XZ_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "e52eca002a11c30a858185efdfb15311e1c7a6bf" -uuid = "ffd25f8a-64ca-5728-b0f7-c24cf3aae800" -version = "5.8.4+0" - -[[deps.Xorg_libICE_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "a3ea76ee3f4facd7a64684f9af25310825ee3668" -uuid = "f67eecfb-183a-506d-b269-f58e52b52d7c" -version = "1.1.2+0" - -[[deps.Xorg_libSM_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libICE_jll"] -git-tree-sha1 = "9c7ad99c629a44f81e7799eb05ec2746abb5d588" -uuid = "c834827a-8449-5923-a945-d239c165b7dd" -version = "1.2.6+0" - -[[deps.Xorg_libX11_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xtrans_jll"] -git-tree-sha1 = "808090ede1d41644447dd5cbafced4731c56bd2f" -uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc" -version = "1.8.13+0" - -[[deps.Xorg_libXau_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "aa1261ebbac3ccc8d16558ae6799524c450ed16b" -uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec" -version = "1.0.13+0" - -[[deps.Xorg_libXcursor_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXfixes_jll", "Xorg_libXrender_jll"] -git-tree-sha1 = "6c74ca84bbabc18c4547014765d194ff0b4dc9da" -uuid = "935fb764-8cf2-53bf-bb30-45bb1f8bf724" -version = "1.2.4+0" - -[[deps.Xorg_libXdmcp_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "52858d64353db33a56e13c341d7bf44cd0d7b309" -uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05" -version = "1.1.6+0" - -[[deps.Xorg_libXext_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] -git-tree-sha1 = "1a4a26870bf1e5d26cd585e38038d399d7e65706" -uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3" -version = "1.3.8+0" - -[[deps.Xorg_libXfixes_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] -git-tree-sha1 = "75e00946e43621e09d431d9b95818ee751e6b2ef" -uuid = "d091e8ba-531a-589c-9de9-94069b037ed8" -version = "6.0.2+0" - -[[deps.Xorg_libXi_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXext_jll", "Xorg_libXfixes_jll"] -git-tree-sha1 = "dcb316b3ce0941f195537dda56bea4517fcd3ff5" -uuid = "a51aa0fd-4e3c-5386-b890-e753decda492" -version = "1.8.4+0" - -[[deps.Xorg_libXinerama_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXext_jll"] -git-tree-sha1 = "0ba01bc7396896a4ace8aab67db31403c71628f4" -uuid = "d1454406-59df-5ea1-beac-c340f2130bc3" -version = "1.1.7+0" - -[[deps.Xorg_libXrandr_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXext_jll", "Xorg_libXrender_jll"] -git-tree-sha1 = "6c174ef70c96c76f4c3f4d3cfbe09d018bcd1b53" -uuid = "ec84b674-ba8e-5d96-8ba1-2a689ba10484" -version = "1.5.6+0" - -[[deps.Xorg_libXrender_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] -git-tree-sha1 = "7ed9347888fac59a618302ee38216dd0379c480d" -uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa" -version = "0.9.12+0" - [[deps.Xorg_libpciaccess_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] git-tree-sha1 = "58972370b81423fc546c56a60ed1a009450177c3" uuid = "a65dc6b1-eb27-53a1-bb3e-dea574b5389e" version = "0.19.0+0" -[[deps.Xorg_libxcb_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXau_jll", "Xorg_libXdmcp_jll"] -git-tree-sha1 = "bfcaf7ec088eaba362093393fe11aa141fa15422" -uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b" -version = "1.17.1+0" - -[[deps.Xorg_libxkbfile_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] -git-tree-sha1 = "ed756a03e95fff88d8f738ebc2849431bdd4fd1a" -uuid = "cc61e674-0454-545c-8b26-ed2c68acab7a" -version = "1.2.0+0" - -[[deps.Xorg_xcb_util_cursor_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_image_jll", "Xorg_xcb_util_jll", "Xorg_xcb_util_renderutil_jll"] -git-tree-sha1 = "9750dc53819eba4e9a20be42349a6d3b86c7cdf8" -uuid = "e920d4aa-a673-5f3a-b3d7-f755a4d47c43" -version = "0.1.6+0" - -[[deps.Xorg_xcb_util_image_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] -git-tree-sha1 = "f4fc02e384b74418679983a97385644b67e1263b" -uuid = "12413925-8142-5f55-bb0e-6d7ca50bb09b" -version = "0.4.1+0" - -[[deps.Xorg_xcb_util_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll"] -git-tree-sha1 = "68da27247e7d8d8dafd1fcf0c3654ad6506f5f97" -uuid = "2def613f-5ad1-5310-b15b-b15d46f528f5" -version = "0.4.1+0" - -[[deps.Xorg_xcb_util_keysyms_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] -git-tree-sha1 = "44ec54b0e2acd408b0fb361e1e9244c60c9c3dd4" -uuid = "975044d2-76e6-5fbe-bf08-97ce7c6574c7" -version = "0.4.1+0" - -[[deps.Xorg_xcb_util_renderutil_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] -git-tree-sha1 = "5b0263b6d080716a02544c55fdff2c8d7f9a16a0" -uuid = "0d47668e-0667-5a69-a72c-f761630bfb7e" -version = "0.3.10+0" - -[[deps.Xorg_xcb_util_wm_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] -git-tree-sha1 = "f233c83cad1fa0e70b7771e0e21b061a116f2763" -uuid = "c22f9ab0-d5fe-5066-847c-f4bb1cd4e361" -version = "0.4.2+0" - -[[deps.Xorg_xkbcomp_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxkbfile_jll"] -git-tree-sha1 = "9a5e6e8f1a7c40941465c04840145e527f0d6b4b" -uuid = "35661453-b289-5fab-8a00-3d9160c6a3a4" -version = "1.5.0+0" - -[[deps.Xorg_xkeyboard_config_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xkbcomp_jll"] -git-tree-sha1 = "df77b22eb77051b3d0ef30aab979684f7482c66f" -uuid = "33bec58e-1273-512f-9401-5d533626f822" -version = "2.48.0+0" - -[[deps.Xorg_xtrans_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "a63799ff68005991f9d9491b6e95bd3478d783cb" -uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10" -version = "1.6.0+0" - [[deps.Zlib_jll]] deps = ["Libdl"] uuid = "83775a58-1f1d-513f-b197-d71354ab007a" @@ -2932,57 +2203,57 @@ version = "1.5.7+1" [[deps.aws_c_auth_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_cal_jll", "aws_c_http_jll", "aws_c_sdkutils_jll"] -git-tree-sha1 = "8cab83c96af80a1be968251ce1a0548a7545484d" +git-tree-sha1 = "7945f94b714d20b55aee2510b511afee0ee17ad2" uuid = "2b3700d1-4306-52e2-a478-c162f0c514be" -version = "0.9.6+0" +version = "1.0.1+0" [[deps.aws_c_cal_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_common_jll"] -git-tree-sha1 = "22c0f42f4a1f0dc5dcfa8fd267c4ac407c455e7a" +git-tree-sha1 = "60a8d3fafed6f7ed8b3114793885a03330aa8e51" uuid = "70f11efc-bab2-57f1-b0f3-22aad4e67c4b" -version = "0.9.13+0" +version = "1.0.0+0" [[deps.aws_c_common_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "a759cb9bf456ad792cc7898a81ae333cce9ef02a" +git-tree-sha1 = "f5ce8191ad02a5a4127d37ab5f474aee5cf91ad4" uuid = "73048d1d-b8c4-5092-a58d-866c5e8d1e50" -version = "0.12.6+0" +version = "1.0.3+1" [[deps.aws_c_compression_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_common_jll"] -git-tree-sha1 = "7910c72f45f44afd297c39fe43b99c56d5ed22ec" +git-tree-sha1 = "6efd591b9fae7919266d3b342a18a6e6280850f9" uuid = "73a04cd5-f3d7-5bac-9290-e8adb709f224" -version = "0.3.2+0" +version = "1.0.0+1" [[deps.aws_c_http_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_compression_jll", "aws_c_io_jll"] -git-tree-sha1 = "3fb8685778068de502c72fec5dd8075e037cee15" +git-tree-sha1 = "e4108b4a2201cb9987ddd36714fdcfd572d6bb24" uuid = "3254fc65-9028-534d-aa9d-d76d128babc6" -version = "0.10.15+0" +version = "1.0.0+0" [[deps.aws_c_io_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_cal_jll", "aws_c_common_jll", "s2n_tls_jll"] -git-tree-sha1 = "7e481d474b2087ee8bbf55b81bf9119f21e396d9" +git-tree-sha1 = "b816756403a8db80e32fc23a4a1aae240d2168ca" uuid = "13c41daa-f319-5298-b5eb-5754e0170d52" -version = "0.26.3+0" +version = "1.0.0+0" [[deps.aws_c_s3_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_auth_jll", "aws_c_common_jll", "aws_c_http_jll", "aws_checksums_jll", "s2n_tls_jll"] -git-tree-sha1 = "3e9917ab25114feba657e71be41cad068b9f6595" +git-tree-sha1 = "1ebf5d4fa9c5248c3c1066124ef41d06de05a03a" uuid = "bd1f34fb-993f-5903-a121-aaf302eed6d4" -version = "0.11.5+0" +version = "1.3.1+0" [[deps.aws_c_sdkutils_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_common_jll"] -git-tree-sha1 = "c43dfba2c1ab9ea9f02f2c80e86fa16f6460244e" +git-tree-sha1 = "0162699e5aa99420f04a9606a9d7b38a872a81ee" uuid = "1282aa60-004d-510b-9f52-12498d409daa" -version = "0.2.4+1" +version = "1.0.0+0" [[deps.aws_checksums_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_common_jll"] -git-tree-sha1 = "2570c8e23f4771a087b12a47edcaaa670ac05a01" +git-tree-sha1 = "ee1f5427815c98355b0b0454b5fe79806ffdf935" uuid = "b2a88e68-78e7-5e94-8c20-c02986ec140e" -version = "0.2.10+0" +version = "1.0.1+1" [[deps.dlfcn_win32_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] @@ -2990,100 +2261,22 @@ git-tree-sha1 = "e141d67ffe550eadfb5af1bdbdaf138031e4805f" uuid = "c4b69c83-5512-53e3-94e6-de98773c479f" version = "1.4.2+0" -[[deps.eudev_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "c3b0e6196d50eab0c5ed34021aaa0bb463489510" -uuid = "35ca27e7-8b34-5b7f-bca9-bdc33f59eb06" -version = "3.2.14+0" - -[[deps.fzf_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "b6a34e0e0960190ac2a4363a1bd003504772d631" -uuid = "214eeab7-80f7-51ab-84ad-2988db7cef09" -version = "0.61.1+0" - [[deps.libaec_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "60f4792734488db6f42e2c7699f1d4594780bd03" +git-tree-sha1 = "31e4c16405815484827f75a38fb7eb17432e72e3" uuid = "477f73a3-ac25-53e9-8cc3-50b2fa2566f0" -version = "1.1.7+0" - -[[deps.libaom_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "1210ba774d3427387d307bf1f416d699b7c39417" -uuid = "a4ae2306-e953-59d6-aa16-d00cac43593b" -version = "3.15.1+0" - -[[deps.libass_jll]] -deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Zlib_jll"] -git-tree-sha1 = "cb007192783c56d8249db4cf0e3495001edfe414" -uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0" -version = "0.17.5+0" +version = "1.1.8+0" [[deps.libblastrampoline_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" version = "5.11.0+0" -[[deps.libdecor_jll]] -deps = ["Artifacts", "Dbus_jll", "JLLWrappers", "Libdl", "Libglvnd_jll", "Pango_jll", "Wayland_jll", "xkbcommon_jll"] -git-tree-sha1 = "9bf7903af251d2050b467f76bdbe57ce541f7f4f" -uuid = "1183f4f0-6f2a-5f1a-908b-139f9cdfea6f" -version = "0.2.2+0" - -[[deps.libdrm_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libpciaccess_jll"] -git-tree-sha1 = "28e57478e8a160d346a19c28b3fffb9273bcc9c2" -uuid = "8e53e030-5e6c-5a89-a30b-be5b7263a166" -version = "2.4.134+0" - -[[deps.libevdev_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "56d643b57b188d30cccc25e331d416d3d358e557" -uuid = "2db6ffa8-e38f-5e21-84af-90c45d0032cc" -version = "1.13.4+0" - -[[deps.libfdk_aac_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "646634dd19587a56ee2f1199563ec056c5f228df" -uuid = "f638f0a6-7fb0-5443-88ba-1cc74229b280" -version = "2.0.4+0" - -[[deps.libinput_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "eudev_jll", "libevdev_jll", "mtdev_jll"] -git-tree-sha1 = "91d05d7f4a9f67205bd6cf395e488009fe85b499" -uuid = "36db933b-70db-51c0-b978-0f229ee0e533" -version = "1.28.1+0" - -[[deps.libpng_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] -git-tree-sha1 = "1fcd6cf05d23adab0b5ffc496d020537215751dc" -uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f" -version = "1.6.59+1" - -[[deps.libva_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll", "Xorg_libXext_jll", "Xorg_libXfixes_jll", "libdrm_jll"] -git-tree-sha1 = "7dbf96baae3310fe2fa0df0ccbb3c6288d5816c9" -uuid = "9a156e7d-b971-5f62-b2c9-67348b8fb97c" -version = "2.23.0+0" - -[[deps.libvorbis_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll"] -git-tree-sha1 = "11e1772e7f3cc987e9d3de991dd4f6b2602663a5" -uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a" -version = "1.3.8+0" - [[deps.mpif_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIABI_jll", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "TOML"] -git-tree-sha1 = "a06fcd368cfe6fe2c0eb7b63320d4d27ddcd010d" +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "MPIABI_jll", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "TOML"] +git-tree-sha1 = "52e322f7a6d418576cdeb043dd2a7f7ad9a8abd0" uuid = "9aeb927a-4695-514f-a259-621a69f20ec0" -version = "1.0.0+0" - -[[deps.mtdev_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "b4d631fd51f2e9cdd93724ae25b2efc198b059b1" -uuid = "009596ad-96f7-51b1-9f1b-5ce2d5e8a71e" -version = "1.1.7+0" +version = "1.0.1+0" [[deps.nghttp2_jll]] deps = ["Artifacts", "Libdl"] @@ -3103,24 +2296,6 @@ version = "17.4.0+2" [[deps.s2n_tls_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "d6cfe0874f96962fe4b20ed8bdb71c467e09c2c6" +git-tree-sha1 = "6f9611b1b99beb089154f19a4031579f3a818cbd" uuid = "cddc5d3d-934d-5d3a-9747-62fc12ea3f48" -version = "1.7.11+0" - -[[deps.x264_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "14cc7083fc6dff3cc44f2bc435ee96d06ed79aa7" -uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a" -version = "10164.0.1+0" - -[[deps.x265_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "e7b67590c14d487e734dcb925924c5dc43ec85f3" -uuid = "dfaa095f-4041-5dcd-9319-2fabd8486b76" -version = "4.1.0+0" - -[[deps.xkbcommon_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xkeyboard_config_jll"] -git-tree-sha1 = "a1fc6507a40bf504527d0d4067d718f8e179b2b8" -uuid = "d8fb68d0-12a3-5cfd-a85a-d49703b185fd" -version = "1.13.0+0" +version = "1.7.12+0" diff --git a/ci/manifests/Manifest-v1.12.toml b/ci/manifests/Manifest-v1.12.toml index d570e4976..e1702e10d 100644 --- a/ci/manifests/Manifest-v1.12.toml +++ b/ci/manifests/Manifest-v1.12.toml @@ -2,7 +2,7 @@ julia_version = "1.12.7" manifest_format = "2.0" -project_hash = "46cc8ae34afcb7a4b038db845711846a1cfd9266" +project_hash = "cb302878a288b9dbaff045ec0f6d5c726498ce68" [[deps.ADTypes]] deps = ["PrecompileTools"] @@ -52,9 +52,9 @@ version = "0.4.5" [[deps.Accessors]] deps = ["CompositionsBase", "ConstructionBase", "Dates", "InverseFunctions", "MacroTools"] -git-tree-sha1 = "7063ad1083578215c7c4bf410368150abe8d5524" +git-tree-sha1 = "8c0bc12397dabf5d2dfc4b5763ec463854cc44d3" uuid = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697" -version = "0.1.45" +version = "0.1.46" [deps.Accessors.extensions] AxisKeysExt = "AxisKeys" @@ -76,9 +76,9 @@ version = "0.1.45" [[deps.Adapt]] deps = ["LinearAlgebra"] -git-tree-sha1 = "dee8388a9bec89c8713ad176f96960ae2f98598a" +git-tree-sha1 = "eec8a0ad4e4b673afba213931504c360fffb8f97" uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" -version = "4.7.4" +version = "4.8.0" weakdeps = ["SparseArrays", "StaticArrays"] [deps.Adapt.extensions] @@ -87,9 +87,9 @@ weakdeps = ["SparseArrays", "StaticArrays"] [[deps.AdaptiveArrayPools]] deps = ["Preferences", "Printf", "Random"] -git-tree-sha1 = "5feaa5055572e809b69b62f06826b1c1c8d3ebf3" +git-tree-sha1 = "86d0ae34bc46373170064ef33c12a303994ab736" uuid = "4f381ef7-9af0-4cbe-99d4-cf36d7b0f233" -version = "0.3.11" +version = "0.3.12" [deps.AdaptiveArrayPools.extensions] AdaptiveArrayPoolsCUDAExt = "CUDA" @@ -104,12 +104,6 @@ git-tree-sha1 = "7e651ea8d262d2d74ce75fdf47c4d63c07dba7a6" uuid = "35492f91-a3bd-45ad-95db-fcad7dcfedb7" version = "1.2.0" -[[deps.AliasTables]] -deps = ["PtrArrays", "Random"] -git-tree-sha1 = "9876e1e164b144ca45e9e3198d0b689cadfed9ff" -uuid = "66dad0bd-aa9a-41b7-9441-69ab47430ed8" -version = "1.1.3" - [[deps.ArgTools]] uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" version = "1.1.2" @@ -155,9 +149,9 @@ version = "7.30.2" [[deps.ArrayLayouts]] deps = ["FillArrays", "LinearAlgebra", "StaticArrays"] -git-tree-sha1 = "b4a2d2dfcaedf8f0cb93a970c4ccba6f632f14cc" +git-tree-sha1 = "8fde25d92e917350570f1203e47c933d16b77dce" uuid = "4c555306-a7a7-4459-81d9-ec55ddd5c99a" -version = "1.15.1" +version = "1.16.0" weakdeps = ["SparseArrays"] [deps.ArrayLayouts.extensions] @@ -167,33 +161,16 @@ weakdeps = ["SparseArrays"] uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" version = "1.11.0" -[[deps.AssetRegistry]] -deps = ["JSON", "Pidfile", "SHA"] -git-tree-sha1 = "902b85010203830d4bc02259f5450d2e16b316d8" -uuid = "bf4720bc-e11a-5d0c-854e-bdca1663c893" -version = "0.1.1" - [[deps.Base64]] uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" version = "1.11.0" -[[deps.BitFlags]] -git-tree-sha1 = "bbe1079eecf9c9fbb52765193ad2bae27ae09bc8" -uuid = "d1d4a3ce-64b1-5f1a-9ba4-7e7e69966f35" -version = "0.1.10" - [[deps.BitTwiddlingConvenienceFunctions]] deps = ["Static"] git-tree-sha1 = "f21cfd4950cb9f0587d5067e69405ad2acd27b87" uuid = "62783981-4cbd-42fc-bca8-16325de8dc4b" version = "0.1.6" -[[deps.Blink]] -deps = ["Base64", "Distributed", "HTTP", "JSExpr", "JSON", "Lazy", "Logging", "MacroTools", "Mustache", "Mux", "Pkg", "Reexport", "Sockets", "WebIO"] -git-tree-sha1 = "bc93511973d1f949d45b0ea17878e6cb0ad484a1" -uuid = "ad839575-38b3-5650-b840-f874b8c74a25" -version = "0.12.9" - [[deps.BracketingNonlinearSolve]] deps = ["CommonSolve", "ConcreteStructs", "NonlinearSolveBase", "PrecompileTools", "Reexport", "SciMLBase"] git-tree-sha1 = "7ad7171d693ae5552ac43862e7f6b61df4471c2b" @@ -208,12 +185,6 @@ version = "1.12.1" ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" -[[deps.Bzip2_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "1b96ea4a01afe0ea4090c5c8039690672dd13f2e" -uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" -version = "1.0.9+0" - [[deps.CPUSummary]] deps = ["CpuId", "IfElse", "PrecompileTools", "Preferences", "Static"] git-tree-sha1 = "f3a21d7fc84ba618a779d1ed2fcca2e682865bab" @@ -222,9 +193,9 @@ version = "0.2.7" [[deps.CRlibm]] deps = ["CRlibm_jll"] -git-tree-sha1 = "66188d9d103b92b6cd705214242e27f5737a1e5e" +git-tree-sha1 = "0db159493e83ecb2d122f9c40138d89b70d43963" uuid = "96374032-68de-5a5b-8d9e-752f78720389" -version = "1.0.2" +version = "1.1.1" [[deps.CRlibm_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] @@ -232,12 +203,6 @@ git-tree-sha1 = "e329286945d0cfc04456972ea732551869af1cfc" uuid = "4e9b3aee-d8a1-5a3d-ad8b-7d824db253f0" version = "1.0.1+0" -[[deps.Cairo_jll]] -deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "Libdl", "Pixman_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"] -git-tree-sha1 = "7b841680738c19948120f6e4cf8d0200518bd564" -uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a" -version = "1.18.8+0" - [[deps.ChunkCodecCore]] git-tree-sha1 = "3496e2b359c27793d12bec54ec94be7983a6ceb2" uuid = "0b6fb165-00bc-4d37-ab8b-79f91016dbe1" @@ -267,38 +232,6 @@ git-tree-sha1 = "970758a3d591a2a5c2a907c53f2e2f8c1b1d3537" uuid = "944b1d66-785c-5afd-91f1-9de20f533193" version = "0.7.9" -[[deps.ColorSchemes]] -deps = ["ColorTypes", "ColorVectorSpace", "Colors", "FixedPointNumbers", "PrecompileTools", "Random"] -git-tree-sha1 = "b0fd3f56fa442f81e0a47815c92245acfaaa4e34" -uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4" -version = "3.31.0" - -[[deps.ColorTypes]] -deps = ["FixedPointNumbers", "Random"] -git-tree-sha1 = "a7cf827a79b378a381e31e5c7feca853ec19cb94" -uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" -version = "0.12.4" -weakdeps = ["StyledStrings"] - - [deps.ColorTypes.extensions] - StyledStringsExt = "StyledStrings" - -[[deps.ColorVectorSpace]] -deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "Requires", "Statistics", "TensorCore"] -git-tree-sha1 = "8b3b6f87ce8f65a2b4f857528fd8d70086cd72b1" -uuid = "c3611d14-8923-5661-9e6a-0046d554d3a4" -version = "0.11.0" -weakdeps = ["SpecialFunctions"] - - [deps.ColorVectorSpace.extensions] - SpecialFunctionsExt = "SpecialFunctions" - -[[deps.Colors]] -deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "Reexport"] -git-tree-sha1 = "a633156e74096f90b3b5e833529292b651b5671a" -uuid = "5ae59095-9a9b-59fe-a467-6f913c188581" -version = "0.13.3" - [[deps.CommonSolve]] deps = ["PrecompileTools"] git-tree-sha1 = "6c389fa857f6ca5a95474b52a52023fd77f24cb7" @@ -347,12 +280,6 @@ git-tree-sha1 = "a72c3b5ce6d2a477f55b5c9b8756e91e695c67f6" uuid = "2569d6c7-a4a2-43d3-a901-331e8e4be471" version = "0.2.8" -[[deps.ConcurrentUtilities]] -deps = ["Serialization", "Sockets"] -git-tree-sha1 = "d5e5fa1f0d6ca517edbbcd734efa93c6639405b0" -uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb" -version = "2.7.1" - [[deps.ConstructionBase]] git-tree-sha1 = "b4b092499347b18a015186eae3042f72267106cb" uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9" @@ -406,12 +333,6 @@ git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe" uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" version = "1.16.0" -[[deps.DataFrames]] -deps = ["Compat", "DataAPI", "DataStructures", "Future", "InlineStrings", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrecompileTools", "PrettyTables", "Printf", "Random", "Reexport", "SentinelArrays", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"] -git-tree-sha1 = "5fab31e2e01e70ad66e3e24c968c264d1cf166d6" -uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" -version = "1.8.2" - [[deps.DataInterpolations]] deps = ["EnumX", "FindFirstFunctions", "ForwardDiff", "LinearAlgebra", "PrettyTables", "RecipesBase", "Reexport"] git-tree-sha1 = "4d50e83772222ce023efb7d30632198828ea56a6" @@ -454,17 +375,11 @@ deps = ["Printf"] uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" version = "1.11.0" -[[deps.Dbus_jll]] -deps = ["Artifacts", "Expat_jll", "JLLWrappers", "Libdl"] -git-tree-sha1 = "473e9afc9cf30814eb67ffa5f2db7df82c3ad9fd" -uuid = "ee1fde0b-3d02-5ea6-8484-8dfef6360eab" -version = "1.16.2+0" - [[deps.DelaunayTriangulation]] deps = ["AdaptivePredicates", "EnumX", "ExactPredicates", "Random"] -git-tree-sha1 = "4ac548adcad90c1d5d677af13568a748af4c952b" +git-tree-sha1 = "e1e28f37cbdca4b594ad86ecfe2ee8f25796ebff" uuid = "927a84f5-c5f4-47a5-9785-b46e178433df" -version = "1.6.7" +version = "1.7.0" [[deps.DelimitedFiles]] deps = ["Mmap"] @@ -610,9 +525,9 @@ version = "1.19.0" [[deps.DoubleFloats]] deps = ["GenericLinearAlgebra", "GenericSchur", "LinearAlgebra", "Printf", "Quadmath", "Random", "SpecialFunctions"] -git-tree-sha1 = "e9350afd366d2deaaa9b4ed13fd537e2b4dea6e5" +git-tree-sha1 = "00720e467f998dc28d6bd3ec2fe33b1d04155f6c" uuid = "497a8b3b-efae-58df-a0af-a86822472b78" -version = "1.11.2" +version = "1.12.0" [[deps.Downloads]] deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] @@ -625,9 +540,9 @@ uuid = "4e289a0a-7415-4d19-859d-a7e5c4648b56" version = "1.0.7" [[deps.EnzymeCore]] -git-tree-sha1 = "156ede9181d50b3b21b74d98a7edb08bc7bbdbf1" +git-tree-sha1 = "d88c803e26799193db42e47f6e1e28eeb0322e4d" uuid = "f151be2c-9106-41f4-ab19-57ee4f262869" -version = "0.8.23" +version = "0.8.27" [deps.EnzymeCore.extensions] AdaptExt = "Adapt" @@ -637,29 +552,17 @@ version = "0.8.23" Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" -[[deps.EpollShim_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "8a4be429317c42cfae6a7fc03c31bad1970c310d" -uuid = "2702e6a9-849d-5ed8-8c21-79e8b8f9ee43" -version = "0.0.20230411+1" - [[deps.ExactPredicates]] deps = ["IntervalArithmetic", "Random", "StaticArrays"] git-tree-sha1 = "83231673ea4d3d6008ac74dc5079e77ab2209d8f" uuid = "429591f6-91af-11e9-00e2-59fbe8cec110" version = "2.2.9" -[[deps.ExceptionUnwrapping]] -deps = ["Test"] -git-tree-sha1 = "4e468f521e1f9f86891cb07186de5df90360a666" -uuid = "460bff9d-24e4-43bc-9d9f-a8973cb893f4" -version = "0.1.12" - [[deps.Expat_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "2bfb1e047e2ad0a5ca94365340bde8005d637568" +git-tree-sha1 = "f2659a735f9360fedfa29925812380f322679e93" uuid = "2e619515-83b5-522b-bb60-26c02a35a201" -version = "2.8.4+0" +version = "2.10.0+0" [[deps.ExponentialUtilities]] deps = ["Adapt", "ArrayInterface", "GPUArraysCore", "GenericSchur", "LinearAlgebra", "PrecompileTools", "Printf", "SparseArrays", "libblastrampoline_jll"] @@ -677,21 +580,9 @@ uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04" version = "0.1.11" [[deps.ExproniconLite]] -git-tree-sha1 = "c13f0b150373771b0fdc1713c97860f8df12e6c2" +git-tree-sha1 = "250aa1562e840b38c72b44a9ebef0f39f6b6e950" uuid = "55351af7-c7e9-48d6-89ff-24e801d99491" -version = "0.10.14" - -[[deps.FFMPEG]] -deps = ["FFMPEG_jll"] -git-tree-sha1 = "7bd13840b4148949e290748071bbe6826b3eabcd" -uuid = "c87230d0-a227-11e9-1b43-d7ebe4e7570a" -version = "0.4.6" - -[[deps.FFMPEG_jll]] -deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "PCRE2_jll", "Zlib_jll", "libaom_jll", "libass_jll", "libfdk_aac_jll", "libva_jll", "libvorbis_jll", "x264_jll", "x265_jll"] -git-tree-sha1 = "d9d3cd382f2c999f684e6bfa4039fcc16c153b6f" -uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5" -version = "9.0.2+0" +version = "0.10.16" [[deps.FFTW]] deps = ["AbstractFFTs", "FFTW_jll", "Libdl", "LinearAlgebra", "MKL_jll", "Preferences", "Reexport"] @@ -707,14 +598,19 @@ version = "3.3.12+0" [[deps.FastBroadcast]] deps = ["ArrayInterface", "LinearAlgebra", "PrecompileTools"] -git-tree-sha1 = "c8f8eefadaa330d982bbf5e395c34e53a13ab668" +git-tree-sha1 = "093bf5e9ea63b36032240033ec2f149088fc0926" uuid = "7034ab61-46d4-4ed7-9d0f-46aef9175898" -version = "1.4.0" -weakdeps = ["Polyester", "Static"] +version = "1.5.1" [deps.FastBroadcast.extensions] FastBroadcastPolyesterExt = "Polyester" FastBroadcastStaticExt = "Static" + FixedSizeArraysExt = "FixedSizeArrays" + + [deps.FastBroadcast.weakdeps] + FixedSizeArrays = "3821ddf9-e5b5-40d5-8e25-6813ab96b5e2" + Polyester = "f517fe37-dbe3-4b94-8317-1923a5111588" + Static = "aedffcd0-7271-4cad-89d0-dc628f76c6d3" [[deps.FastClosures]] git-tree-sha1 = "acebe244d53ee1b461970f8910c235b259e772ef" @@ -753,9 +649,9 @@ version = "0.4.20" [[deps.FastPower]] deps = ["PrecompileTools"] -git-tree-sha1 = "3c7269c236978d434a16ebe99f9743b9d706270a" +git-tree-sha1 = "a6bcbd090fc6b6f022fc0ad96f77ba31b9577282" uuid = "a4df4552-cc26-4903-aec0-212e50a0e84b" -version = "1.5.0" +version = "1.5.1" [deps.FastPower.extensions] FastPowerEnzymeExt = "Enzyme" @@ -780,20 +676,22 @@ deps = ["Pkg", "Requires", "UUIDs"] git-tree-sha1 = "6621fef488e496356c9c9625d0562c12a6070819" uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" version = "1.20.0" -weakdeps = ["HTTP"] [deps.FileIO.extensions] HTTPExt = "HTTP" + [deps.FileIO.weakdeps] + HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" + [[deps.FileWatching]] uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" version = "1.11.0" [[deps.FillArrays]] deps = ["LinearAlgebra"] -git-tree-sha1 = "417289ac840d201005d9ed16789a46defccbe36d" +git-tree-sha1 = "831f86fde78ab7f6470d6ba4d1161a11fb6b4ec0" uuid = "1a297f60-69ca-5386-bcde-b61e274b549b" -version = "1.18.1" +version = "1.18.2" [deps.FillArrays.extensions] FillArraysPDMatsExt = "PDMats" @@ -831,45 +729,16 @@ version = "2.33.0" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" -[[deps.FixedPointNumbers]] -deps = ["Random", "Statistics"] -git-tree-sha1 = "59af96b98217c6ef4ae0dfe065ac7c20831d1a84" -uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" -version = "0.8.6" - -[[deps.Fontconfig_jll]] -deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Zlib_jll"] -git-tree-sha1 = "f85dac9a96a01087df6e3a749840015a0ca3817d" -uuid = "a3f928ae-7b40-5064-980b-68af3947d34b" -version = "2.17.1+0" - -[[deps.Format]] -git-tree-sha1 = "9c68794ef81b08086aeb32eeaf33531668d5f5fc" -uuid = "1fa38f19-a742-5d3f-a2b9-30dd87b9d5f8" -version = "1.3.7" - [[deps.ForwardDiff]] deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "LogExpFunctions", "NaNMath", "Preferences", "Printf", "Random", "SpecialFunctions"] -git-tree-sha1 = "3b0f72e2ffef1a139ac450725c9ecd01c0a3c050" +git-tree-sha1 = "a55669aa77ae8bbdc7310c4027112500ff5ec874" uuid = "f6369f11-7733-5829-9624-2563aa707210" -version = "1.4.6" +version = "1.4.7" weakdeps = ["StaticArrays"] [deps.ForwardDiff.extensions] ForwardDiffStaticArraysExt = "StaticArrays" -[[deps.FreeType2_jll]] -deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Zlib_jll"] -git-tree-sha1 = "70329abc09b886fd2c5d94ad2d9527639c421e3e" -uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7" -version = "2.14.3+1" - -[[deps.FriBidi_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "7a214fdac5ed5f59a22c2d9a885a16da1c74bbc7" -uuid = "559328eb-81f9-559d-9380-de523a88c83c" -version = "1.0.17+0" - [[deps.FunctionWrappers]] git-tree-sha1 = "d62485945ce5ae9c0c48f124a84998d755bae00e" uuid = "069b7b12-0de2-55c6-9aab-29f3d0a68a2e" @@ -890,58 +759,34 @@ version = "1.13.0" EnzymeCore = "f151be2c-9106-41f4-ab19-57ee4f262869" Mooncake = "da2b9cff-9c12-43a0-ae48-6db2b0edb7d6" -[[deps.FunctionalCollections]] -deps = ["Test"] -git-tree-sha1 = "04cb9cfaa6ba5311973994fe3496ddec19b6292a" -uuid = "de31a74c-ac4f-5751-b3fd-e18cd04993ca" -version = "0.5.0" - [[deps.Future]] deps = ["Random"] uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820" version = "1.11.0" -[[deps.GLFW_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Libglvnd_jll", "Xorg_libXcursor_jll", "Xorg_libXi_jll", "Xorg_libXinerama_jll", "Xorg_libXrandr_jll", "libdecor_jll", "xkbcommon_jll"] -git-tree-sha1 = "64bbbb7d1499297751b536dd39c58b20750ab1db" -uuid = "0656b61e-2033-5cc2-a64a-77c0f6c09b89" -version = "3.5.1+0" - [[deps.GPUArraysCore]] deps = ["Adapt"] -git-tree-sha1 = "0b2b3e8b8fb7c1f5ddbf498be2eb2b3b37c9c0ed" +git-tree-sha1 = "f60436514e6d73f8ee069ae4ee0ff038fe96b69d" uuid = "46192b85-c4d5-4398-a991-12ede77f4527" -version = "0.2.1" - -[[deps.GR]] -deps = ["Artifacts", "Base64", "DelimitedFiles", "Downloads", "GR_jll", "JSON", "Libdl", "LinearAlgebra", "Preferences", "Printf", "Qt6Wayland_jll", "Random", "Serialization", "Sockets", "TOML", "Tar", "Test", "p7zip_jll"] -git-tree-sha1 = "4d777f73c46b46b8b5276206059cf8a195499314" -uuid = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71" -version = "0.73.27" - - [deps.GR.extensions] - IJuliaExt = "IJulia" - - [deps.GR.weakdeps] - IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" - -[[deps.GR_jll]] -deps = ["Artifacts", "Bzip2_jll", "Cairo_jll", "FFMPEG_jll", "Fontconfig_jll", "FreeType2_jll", "GLFW_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pixman_jll", "Qt6Base_jll", "Zlib_jll", "libpng_jll"] -git-tree-sha1 = "f8eb8f7ba13ea75083531647fc8faeda8d541f07" -uuid = "d2c73de3-f751-5644-a686-071e5b155ba9" -version = "0.73.27+0" +version = "0.2.2" [[deps.GeneralizedPerturbedEquilibrium]] -deps = ["AdaptiveArrayPools", "CommonSolve", "Contour", "Dates", "DelaunayTriangulation", "DelimitedFiles", "DiffEqCallbacks", "Documenter", "DoubleFloats", "FFTW", "FastGaussQuadrature", "FastInterpolations", "HDF5", "IMASdd", "JLD2", "LaTeXStrings", "LinearAlgebra", "Logging", "OrdinaryDiffEq", "Pkg", "PlotlyJS", "Plots", "PrecompileTools", "Printf", "QuadGK", "Random", "Roots", "SparseArrays", "SpecialFunctions", "StaticArrays", "Statistics", "TOML", "Test"] +deps = ["AdaptiveArrayPools", "CommonSolve", "Contour", "Dates", "DelaunayTriangulation", "DelimitedFiles", "DiffEqCallbacks", "Documenter", "DoubleFloats", "FFTW", "FastGaussQuadrature", "FastInterpolations", "HDF5", "IMASdd", "JLD2", "LaTeXStrings", "LinearAlgebra", "Logging", "OrdinaryDiffEq", "Pkg", "PrecompileTools", "Printf", "QuadGK", "Random", "Roots", "SparseArrays", "SpecialFunctions", "StaticArrays", "Statistics", "TOML", "Test"] path = "." uuid = "462872dd-e066-4d2e-b993-6468b5239634" -version = "0.1.0" +version = "2.0.1" + + [deps.GeneralizedPerturbedEquilibrium.extensions] + GPECMakieExt = "Makie" + + [deps.GeneralizedPerturbedEquilibrium.weakdeps] + Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" [[deps.GenericLinearAlgebra]] deps = ["LinearAlgebra", "Printf", "Random", "libblastrampoline_jll"] -git-tree-sha1 = "961f8a5d33f043e02cdf2b8020e3a7b0aae78474" +git-tree-sha1 = "3b0e97a0de86e54fa897c6e431c179ad4cd1dea0" uuid = "14197337-ba66-59df-a3e3-ca00e7dcff7a" -version = "0.4.1" +version = "0.4.2" [[deps.GenericSchur]] deps = ["LinearAlgebra", "MatrixFactorizations", "Preferences", "Printf", "UUIDs"] @@ -949,18 +794,6 @@ git-tree-sha1 = "e3d1202ab9ae72edd4c46134ecbb6746ec420486" uuid = "c145ed77-6b09-5dd9-b285-bf645a82121e" version = "0.5.8" -[[deps.GettextRuntime_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll"] -git-tree-sha1 = "45288942190db7c5f760f59c04495064eedf9340" -uuid = "b0724c58-0f36-5564-988d-3bb0596ebc4a" -version = "0.22.4+0" - -[[deps.Ghostscript_jll]] -deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Zlib_jll"] -git-tree-sha1 = "38044a04637976140074d0b0621c1edf0eb531fd" -uuid = "61579ee1-b43e-5ca0-a5da-69d92c66a64b" -version = "9.55.1+0" - [[deps.Git]] deps = ["Git_LFS_jll", "Git_jll", "JLLWrappers", "OpenSSH_jll"] git-tree-sha1 = "824a1890086880696fc908fe12a17bcf61738bd8" @@ -969,9 +802,9 @@ version = "1.5.0" [[deps.Git_LFS_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "8c66e385d631bb934ff05e76d4a566c640c8df69" +git-tree-sha1 = "d310b22511a51569401e975194cc76077bcd3786" uuid = "020c3dae-16b3-5ae5-87b3-4cb189e250b2" -version = "3.7.1+0" +version = "3.8.1+0" [[deps.Git_jll]] deps = ["Artifacts", "Expat_jll", "JLLWrappers", "LibCURL_jll", "Libdl", "Libiconv_jll", "OpenSSL_jll", "PCRE2_jll", "Zlib_jll"] @@ -979,18 +812,6 @@ git-tree-sha1 = "3842d761162baf8efce819cdfb5ea070fe44ac40" uuid = "f8c6e375-362e-5223-8a59-34ff63f689eb" version = "2.56.0+0" -[[deps.Glib_jll]] -deps = ["Artifacts", "GettextRuntime_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE2_jll", "Zlib_jll"] -git-tree-sha1 = "090526e65de8f69648ac156daae153de8b56df62" -uuid = "7746bdde-850d-59dc-9ae8-88ece973131d" -version = "2.88.3+0" - -[[deps.Graphite2_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "69ffb934a5c5b7e086a0b4fee3427db2556fba6e" -uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472" -version = "1.3.16+0" - [[deps.HDF5]] deps = ["Compat", "HDF5_jll", "Libdl", "MPIPreferences", "Mmap", "Preferences", "Printf", "Random", "Requires", "UUIDs"] git-tree-sha1 = "26e37af34e8ccb7a8358bd8de1619c7bcb526738" @@ -1005,21 +826,9 @@ version = "0.17.4" [[deps.HDF5_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LibCURL_jll", "Libdl", "MPIABI_jll", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "OpenSSL_jll", "TOML", "Zlib_jll", "aws_c_s3_jll", "dlfcn_win32_jll", "libaec_jll", "mpif_jll"] -git-tree-sha1 = "58d79d3a61d2fdeda667fead57546ae22a20b27a" +git-tree-sha1 = "d791a1c4c3c8f383b248a799be31105d00310204" uuid = "0234f1f7-429e-5d53-9886-15a909be8d59" -version = "2.2.3+0" - -[[deps.HTTP]] -deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "PrecompileTools", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"] -git-tree-sha1 = "51059d23c8bb67911a2e6fd5130229113735fc7e" -uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3" -version = "1.11.0" - -[[deps.HarfBuzz_jll]] -deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll"] -git-tree-sha1 = "9d9531a9cb63a9edc33836414e82a07e81710de2" -uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566" -version = "100.14004.0+0" +version = "2.2.4+0" [[deps.HashArrayMappedTries]] git-tree-sha1 = "2eaa69a7cab70a52b9687c8bf950a5a93ec895ae" @@ -1027,19 +836,15 @@ uuid = "076d061b-32b6-4027-95e0-9a2c6f6d7e74" version = "0.2.0" [[deps.HelpPlots]] -git-tree-sha1 = "07d389d81ca31e42d68bd88cfa1e900a63b7d378" +git-tree-sha1 = "4b09f61ba5ab9242250855d44cf8747d0d43b981" uuid = "5880b2e8-ad0a-48c9-bacd-30dc9c1357e0" -version = "1.1.0" -weakdeps = ["Plots"] +version = "1.1.1" [deps.HelpPlots.extensions] HelpPlotsExt = "Plots" -[[deps.Hiccup]] -deps = ["MacroTools", "Test"] -git-tree-sha1 = "6187bb2d5fcbb2007c39e7ac53308b0d371124bd" -uuid = "9fb69e20-1954-56bb-a84f-559cc56a8ff7" -version = "0.2.2" + [deps.HelpPlots.weakdeps] + Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" [[deps.Hwloc_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "XML2_jll", "Xorg_libpciaccess_jll"] @@ -1049,9 +854,9 @@ version = "2.15.0+0" [[deps.IMASdd]] deps = ["AbstractTrees", "AdaptiveArrayPools", "CoordinateConventions", "DataInterpolations", "Dates", "FastInterpolations", "HDF5", "InteractiveUtils", "JSON", "Memoization", "OrderedCollections", "PrecompileTools", "Preferences", "Printf", "StaticArraysCore", "ThreadSafeDicts", "TimerOutputs"] -git-tree-sha1 = "2284c64e11d8fb6225971bcc367c67bae51923f5" +git-tree-sha1 = "34f3a6338f95ee5df3ca455a96f416b0a2044eb0" uuid = "c5a45a97-b3f9-491c-b9a7-aa88c3bc0067" -version = "8.6.2" +version = "8.6.3" [[deps.IOCapture]] deps = ["Logging", "Random"] @@ -1064,19 +869,6 @@ git-tree-sha1 = "debdd00ffef04665ccbb3e150747a77560e8fad1" uuid = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173" version = "0.1.1" -[[deps.InlineStrings]] -git-tree-sha1 = "06b65886c7577a3784d616e29f1302c2e36e389d" -uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48" -version = "1.4.6" - - [deps.InlineStrings.extensions] - ArrowTypesExt = "ArrowTypes" - ParsersExt = "Parsers" - - [deps.InlineStrings.weakdeps] - ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd" - Parsers = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" - [[deps.IntelOpenMP_jll]] deps = ["Artifacts", "JLLWrappers", "LazyArtifacts", "Libdl"] git-tree-sha1 = "ec1debd61c300961f98064cfb21287613ad7f303" @@ -1117,24 +909,19 @@ version = "1.0.12" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" [[deps.InverseFunctions]] -git-tree-sha1 = "a779299d77cd080bf77b97535acecd73e1c5e5cb" +git-tree-sha1 = "5cc6ad3e2bfc75a11eed92632ee341d37f4a4536" uuid = "3587e190-3f89-42d0-90ee-14403ec27112" -version = "0.1.17" +version = "0.1.18" weakdeps = ["Dates", "Test"] [deps.InverseFunctions.extensions] InverseFunctionsDatesExt = "Dates" InverseFunctionsTestExt = "Test" -[[deps.InvertedIndices]] -git-tree-sha1 = "6da3c4316095de0f5ee2ebd875df8721e7e0bdbe" -uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f" -version = "1.3.1" - [[deps.IrrationalConstants]] -git-tree-sha1 = "b2d91fe939cae05960e760110b328288867b5758" +git-tree-sha1 = "d22e8bdb3f03c300a103ff23c0dd5a654c6701de" uuid = "92d709cd-6900-40b7-9082-c6be49f344b6" -version = "0.2.6" +version = "0.2.7" [[deps.IteratorInterfaceExtensions]] git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856" @@ -1146,16 +933,12 @@ deps = ["ChunkCodecCore", "ChunkCodecLibZlib", "ChunkCodecLibZstd", "FileIO", "M git-tree-sha1 = "9ce2e7c49ae4a7035b7d60db3a553b1f6c16875d" uuid = "033835bb-8acc-5ee8-8aae-3f567f8a3819" version = "0.6.7" -weakdeps = ["UnPack"] [deps.JLD2.extensions] UnPackExt = "UnPack" -[[deps.JLFzf]] -deps = ["REPL", "Random", "fzf_jll"] -git-tree-sha1 = "82f7acdc599b65e0f8ccd270ffa1467c21cb647b" -uuid = "1019f520-868f-41f5-a6de-eb00f4b6a39c" -version = "0.1.11" + [deps.JLD2.weakdeps] + UnPack = "3a884ed6-31ef-47d7-9d2a-63182c4928ed" [[deps.JLLWrappers]] deps = ["Artifacts", "Preferences"] @@ -1163,17 +946,19 @@ git-tree-sha1 = "7204148362dafe5fe6a273f855b8ccbe4df8173e" uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" version = "1.8.0" -[[deps.JSExpr]] -deps = ["JSON", "MacroTools", "Observables", "WebIO"] -git-tree-sha1 = "b413a73785b98474d8af24fd4c8a975e31df3658" -uuid = "97c1335a-c9c5-57fe-bc5d-ec35cebe8660" -version = "0.5.4" - [[deps.JSON]] -deps = ["Dates", "Mmap", "Parsers", "Unicode"] -git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" +deps = ["Dates", "Logging", "Parsers", "PrecompileTools", "StructUtils", "UUIDs", "Unicode"] +git-tree-sha1 = "06d90f699285d055e34f0b014636a75a17ff2252" uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" -version = "0.21.4" +version = "1.13.4" + + [deps.JSON.extensions] + JSONArrowExt = ["ArrowTypes"] + JSONSerializationExt = ["Serialization"] + + [deps.JSON.weakdeps] + ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd" + Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" [[deps.Jieko]] deps = ["ExproniconLite"] @@ -1181,70 +966,22 @@ git-tree-sha1 = "2f05ed29618da60c06a87e9c033982d4f71d0b6c" uuid = "ae98c720-c025-4a4a-838c-29b094483192" version = "0.2.1" -[[deps.JpegTurbo_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "037babc10853eeb8e585418922246cb97b8e5b74" -uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8" -version = "3.2.0+1" - [[deps.JuliaSyntaxHighlighting]] deps = ["StyledStrings"] uuid = "ac6e5ff7-fb65-4e79-a425-ec3bc9c03011" version = "1.12.0" -[[deps.Kaleido_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "43032da5832754f58d14a91ffbe86d5f176acda9" -uuid = "f7e6163d-2fa5-5f23-b69c-1db539e41963" -version = "0.2.1+0" - [[deps.Krylov]] deps = ["LinearAlgebra", "Printf", "SparseArrays"] -git-tree-sha1 = "e0574e7e2cd5c40d9a6ae444511fbadd51afbf30" +git-tree-sha1 = "b13076e4fff6ea0d0a5ddd9b939a11ca2027755a" uuid = "ba0b0d4f-ebba-5204-a429-3ac8c609bfb7" -version = "0.10.11" - -[[deps.LAME_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "059aabebaa7c82ccb853dd4a0ee9d17796f7e1bc" -uuid = "c1c5ebd0-6772-5130-a774-d5fcae4a789d" -version = "3.100.3+0" - -[[deps.LERC_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "39bca05343661c347aae0bca57a5994a0bf4f08d" -uuid = "88015f11-f218-50d7-93a8-a6af411a945d" -version = "4.2.0+0" - -[[deps.LLVMOpenMP_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "e5b100780d4d30d63b4618d7930d48af409c1772" -uuid = "1d63c593-3942-5779-bab2-d838dc0a180e" -version = "23.1.1+0" +version = "0.10.12" [[deps.LaTeXStrings]] git-tree-sha1 = "f88f3ccef05a6a72a0cf0ed417c8fd68530f4ab2" uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" version = "1.4.1" -[[deps.Latexify]] -deps = ["Format", "Ghostscript_jll", "InteractiveUtils", "LaTeXStrings", "MacroTools", "Markdown", "OrderedCollections", "Requires"] -git-tree-sha1 = "df7566479bd64f20bd16b09960145e70160ffb3b" -uuid = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" -version = "0.16.12" - - [deps.Latexify.extensions] - DataFramesExt = "DataFrames" - SparseArraysExt = "SparseArrays" - SymEngineExt = "SymEngine" - TectonicExt = "tectonic_jll" - - [deps.Latexify.weakdeps] - DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" - SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - SymEngine = "123dc426-2d89-5057-bbad-38513e3affd8" - tectonic_jll = "d7dd28d6-a5e6-559c-9131-7eb760cdacc5" - [[deps.LayoutPointers]] deps = ["ArrayInterface", "LinearAlgebra", "ManualMemory", "SIMDTypes", "Static", "StaticArrayInterface"] git-tree-sha1 = "a9eaadb366f5493a5654e843864c13d8b107548c" @@ -1256,12 +993,6 @@ git-tree-sha1 = "0f2da712350b020bc3957f269c9caad516383ee0" uuid = "0e77f7df-68c5-4e49-93ce-4cd80f5598bf" version = "1.3.0" -[[deps.Lazy]] -deps = ["MacroTools"] -git-tree-sha1 = "1370f8202dac30758f3c345f9909b97f53d87d3f" -uuid = "50d2b5c4-7a5e-59d5-8109-a42b560f39c0" -version = "0.15.1" - [[deps.LazyArtifacts]] deps = ["Artifacts", "Pkg"] uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3" @@ -1296,42 +1027,12 @@ version = "1.11.3+1" uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" version = "1.11.0" -[[deps.Libffi_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "c8da7e6a91781c41a863611c7e966098d783c57a" -uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490" -version = "3.4.7+0" - -[[deps.Libglvnd_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll", "Xorg_libXext_jll"] -git-tree-sha1 = "d36c21b9e7c172a44a10484125024495e2625ac0" -uuid = "7e76a0d4-f3c7-5321-8279-8d96eeed0f29" -version = "1.7.1+1" - [[deps.Libiconv_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] git-tree-sha1 = "be484f5c92fad0bd8acfef35fe017900b0b73809" uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" version = "1.18.0+0" -[[deps.Libmount_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "e2b48382276415b5a5d5282a5dd75a0f0bf4ac84" -uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9" -version = "2.42.2+0" - -[[deps.Libtiff_jll]] -deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"] -git-tree-sha1 = "aebd334d06cee9f24cea70bd19a39749daf73881" -uuid = "89763e89-9b03-5906-acba-b20f662cd828" -version = "4.7.3+0" - -[[deps.Libuuid_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "d620582b1f0cbe2c72dd1d5bd195a9ce73370ab1" -uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700" -version = "2.42.0+0" - [[deps.LineSearch]] deps = ["ADTypes", "CommonSolve", "ConcreteStructs", "FastClosures", "LinearAlgebra", "MaybeInplace", "PrecompileTools", "SciMLBase", "SciMLJacobianOperators", "StaticArraysCore"] git-tree-sha1 = "2e05027f5a68891d997fcad60d11ce48d09208d0" @@ -1535,23 +1236,6 @@ weakdeps = ["SparseArrays"] [deps.MaybeInplace.extensions] MaybeInplaceSparseArraysExt = "SparseArrays" -[[deps.MbedTLS]] -deps = ["Dates", "MbedTLS_jll", "MozillaCACerts_jll", "NetworkOptions", "Random", "Sockets"] -git-tree-sha1 = "8785729fa736197687541f7053f6d8ab7fc44f92" -uuid = "739be429-bea8-5141-9913-cc70e7f3736d" -version = "1.1.10" - -[[deps.MbedTLS_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "ff69a2b1330bcb730b9ac1ab7dd680176f5896b8" -uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" -version = "2.28.1010+0" - -[[deps.Measures]] -git-tree-sha1 = "b513cedd20d9c914783d8ad83d08120702bf2c77" -uuid = "442fdcdd-2543-5da2-b0f3-8c86c306513e" -version = "0.3.3" - [[deps.Memoization]] deps = ["MacroTools"] git-tree-sha1 = "7dbf904fa6c4447bd1f1d316886bfbe29feacf45" @@ -1559,16 +1243,10 @@ uuid = "6fafb56a-5788-4b4e-91ca-c0cea6611c73" version = "0.2.2" [[deps.MicrosoftMPI_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "bc95bf4149bf535c09602e3acdf950d9b4376227" +deps = ["Artifacts", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] +git-tree-sha1 = "3350d35d0ebcf7ccee0c66290b8e95d82419ef6f" uuid = "9237b28f-5490-5468-be7b-bb81f5f5e6cf" -version = "10.1.4+3" - -[[deps.Missings]] -deps = ["DataAPI"] -git-tree-sha1 = "ec4f7fbeab05d7747bdf98eb74d130a2a2ed298d" -uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28" -version = "1.2.0" +version = "10.1.5+0" [[deps.Mmap]] uuid = "a63ad114-7e13-5084-954f-fe012c677804" @@ -1590,18 +1268,6 @@ git-tree-sha1 = "283bf85d4a767481dd924dff0eee1735e95f449e" uuid = "46d2c3a1-f734-5fdb-9937-b9b9aeba4221" version = "0.2.7" -[[deps.Mustache]] -deps = ["DataFrames", "Printf", "Tables"] -git-tree-sha1 = "7437c911e62f50e7a043a774ebdc0dbd780d9fe5" -uuid = "ffc61752-8dc7-55ee-8c37-f3e9cdd09e70" -version = "1.1.0" - -[[deps.Mux]] -deps = ["AssetRegistry", "Base64", "HTTP", "Hiccup", "MbedTLS", "Pkg", "Sockets"] -git-tree-sha1 = "7295d849103ac4fcbe3b2e439f229c5cc77b9b69" -uuid = "a975b10e-0019-58db-a62f-e48ff68538c9" -version = "1.0.2" - [[deps.NLSolversBase]] deps = ["ADTypes", "DifferentiationInterface", "FiniteDiff", "LinearAlgebra"] git-tree-sha1 = "f96d38936d92d610318dec4d3b5ef37b0373c20f" @@ -1610,9 +1276,9 @@ version = "8.0.1" [[deps.NaNMath]] deps = ["OpenLibm_jll"] -git-tree-sha1 = "dbd2e8cd2c1c27f0b584f6661b4309609c5a685e" +git-tree-sha1 = "676c0f1c1c34224d6869675047a54b42fdc7b8c5" uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" -version = "1.1.4" +version = "1.1.5" [[deps.NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" @@ -1709,17 +1375,6 @@ weakdeps = ["ForwardDiff"] [deps.NonlinearSolveSpectralMethods.extensions] NonlinearSolveSpectralMethodsForwardDiffExt = "ForwardDiff" -[[deps.Observables]] -git-tree-sha1 = "7438a59546cf62428fc9d1bc94729146d37a7225" -uuid = "510215fc-4207-5dde-b226-833fc4488ee2" -version = "0.5.5" - -[[deps.Ogg_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "b6aa4566bb7ae78498a5e68943863fa8b5231b59" -uuid = "e7412a2a-1a6e-54c0-be00-318e2571c051" -version = "1.3.6+0" - [[deps.OpenBLASConsistentFPCSR_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"] git-tree-sha1 = "38a93f17e431141c6470bb67a88952a7c4f0e928" @@ -1748,12 +1403,6 @@ git-tree-sha1 = "1f640b82a627df3dbbaaff756a201a9ceb10dd1f" uuid = "9bd350c2-7e96-507f-8002-3f2e150b4e1b" version = "10.6.1+0" -[[deps.OpenSSL]] -deps = ["BitFlags", "Dates", "MozillaCACerts_jll", "NetworkOptions", "OpenSSL_jll", "Sockets"] -git-tree-sha1 = "5caab1056e0734b87f62ae3a3b520d47d3e994e8" -uuid = "4d8831e6-92b7-49fb-bdf8-b643e874388c" -version = "1.7.0" - [[deps.OpenSSL_jll]] deps = ["Artifacts", "Libdl"] uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" @@ -1765,16 +1414,10 @@ git-tree-sha1 = "1346c9208249809840c91b26703912dff463d335" uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e" version = "0.5.6+0" -[[deps.Opus_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "e2bb57a313a74b8104064b7efd01406c0a50d2ff" -uuid = "91d4177d-7536-5919-b921-800302f37372" -version = "1.6.1+0" - [[deps.OrderedCollections]] -git-tree-sha1 = "94ba93778373a53bfd5a0caaf7d809c445292ff4" +git-tree-sha1 = "b079a6affebde167dad8ffe0d670b53917fbcb68" uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" -version = "1.8.2" +version = "2.0.3" [[deps.OrdinaryDiffEq]] deps = ["ADTypes", "Adapt", "ArrayInterface", "CommonSolve", "DataStructures", "DiffEqBase", "DocStringExtensions", "EnumX", "ExponentialUtilities", "FastBroadcast", "FastClosures", "FillArrays", "FiniteDiff", "ForwardDiff", "FunctionWrappersWrappers", "InteractiveUtils", "LineSearches", "LinearAlgebra", "LinearSolve", "Logging", "MacroTools", "MuladdMacro", "NonlinearSolve", "OrdinaryDiffEqAdamsBashforthMoulton", "OrdinaryDiffEqBDF", "OrdinaryDiffEqCore", "OrdinaryDiffEqDefault", "OrdinaryDiffEqDifferentiation", "OrdinaryDiffEqExplicitRK", "OrdinaryDiffEqExponentialRK", "OrdinaryDiffEqExtrapolation", "OrdinaryDiffEqFIRK", "OrdinaryDiffEqFeagin", "OrdinaryDiffEqFunctionMap", "OrdinaryDiffEqHighOrderRK", "OrdinaryDiffEqIMEXMultistep", "OrdinaryDiffEqLinear", "OrdinaryDiffEqLowOrderRK", "OrdinaryDiffEqLowStorageRK", "OrdinaryDiffEqNonlinearSolve", "OrdinaryDiffEqNordsieck", "OrdinaryDiffEqPDIRK", "OrdinaryDiffEqPRK", "OrdinaryDiffEqQPRK", "OrdinaryDiffEqRKN", "OrdinaryDiffEqRosenbrock", "OrdinaryDiffEqSDIRK", "OrdinaryDiffEqSSPRK", "OrdinaryDiffEqStabilizedIRK", "OrdinaryDiffEqStabilizedRK", "OrdinaryDiffEqSymplecticRK", "OrdinaryDiffEqTsit5", "OrdinaryDiffEqVerner", "Polyester", "PreallocationTools", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "SciMLOperators", "SciMLStructures", "SimpleNonlinearSolve", "SparseArrays", "Static", "StaticArrayInterface", "StaticArrays", "TruncatedStacktraces"] @@ -1979,35 +1622,11 @@ deps = ["Artifacts", "Libdl"] uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" version = "10.44.0+1" -[[deps.Pango_jll]] -deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "FriBidi_jll", "Glib_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl"] -git-tree-sha1 = "1912a9f1b9ca55005b03ba075f8e19993583e237" -uuid = "36c8627f-9965-5494-a995-c6b170f724f3" -version = "1.58.2+0" - -[[deps.Parameters]] -deps = ["OrderedCollections", "UnPack"] -git-tree-sha1 = "34c0e9ad262e5f7fc75b10a9952ca7692cfc5fbe" -uuid = "d96e819e-fc66-5662-9728-84c9c7592b0a" -version = "0.12.3" - [[deps.Parsers]] -deps = ["Dates", "PrecompileTools", "UUIDs"] -git-tree-sha1 = "ba0dc8a8a67cacac4842631f960c046e4e563675" +deps = ["Dates", "PrecompileTools"] +git-tree-sha1 = "e5ae52d1f4f19a9591d615cb0f1ef8d415b40933" uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "2.8.8" - -[[deps.Pidfile]] -deps = ["FileWatching", "Test"] -git-tree-sha1 = "2d8aaf8ee10df53d0dfb9b8ee44ae7c04ced2b03" -uuid = "fa939f87-e72e-5be4-a000-7fc836dbe307" -version = "1.3.0" - -[[deps.Pixman_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "Libdl"] -git-tree-sha1 = "e4a6721aa89e62e5d4217c0b21bd714263779dda" -uuid = "30392449-352a-5448-841d-b1acce4e97dc" -version = "0.46.4+0" +version = "3.0.6" [[deps.Pkg]] deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"] @@ -2018,80 +1637,6 @@ weakdeps = ["REPL"] [deps.Pkg.extensions] REPLExt = "REPL" -[[deps.PlotThemes]] -deps = ["PlotUtils", "Statistics"] -git-tree-sha1 = "41031ef3a1be6f5bbbf3e8073f210556daeae5ca" -uuid = "ccf2f8ad-2431-5c83-bf29-c5338b663b6a" -version = "3.3.0" - -[[deps.PlotUtils]] -deps = ["ColorSchemes", "Colors", "Dates", "PrecompileTools", "Printf", "Reexport", "Statistics"] -git-tree-sha1 = "ddcc36fd83fb4cd459a53c891981697bde0d3de6" -uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043" -version = "1.5.1" - -[[deps.PlotlyBase]] -deps = ["ColorSchemes", "Colors", "Dates", "DelimitedFiles", "DocStringExtensions", "JSON", "LaTeXStrings", "Logging", "Parameters", "Pkg", "REPL", "Requires", "Statistics", "UUIDs"] -git-tree-sha1 = "6256ab3ee24ef079b3afa310593817e069925eeb" -uuid = "a03496cd-edff-5a9b-9e67-9cda94a718b5" -version = "0.8.23" - - [deps.PlotlyBase.extensions] - DataFramesExt = "DataFrames" - DistributionsExt = "Distributions" - IJuliaExt = "IJulia" - JSON3Ext = "JSON3" - - [deps.PlotlyBase.weakdeps] - DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" - Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" - IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" - JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" - -[[deps.PlotlyJS]] -deps = ["Base64", "Blink", "DelimitedFiles", "JSExpr", "JSON", "Kaleido_jll", "Markdown", "Pkg", "PlotlyBase", "PlotlyKaleido", "REPL", "Reexport", "Requires", "WebIO"] -git-tree-sha1 = "4bf6c08295346efba58bb2d5f8c52984efed7f27" -uuid = "f0f68f2c-4968-5e81-91da-67840de0976a" -version = "0.18.18" - - [deps.PlotlyJS.extensions] - CSVExt = "CSV" - DataFramesExt = ["DataFrames", "CSV"] - IJuliaExt = "IJulia" - JSON3Ext = "JSON3" - - [deps.PlotlyJS.weakdeps] - CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" - DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" - IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" - JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" - -[[deps.PlotlyKaleido]] -deps = ["Artifacts", "Base64", "JSON", "Kaleido_jll"] -git-tree-sha1 = "c7a270d11881c0709052f8e3def3d60d551e99f3" -uuid = "f2990250-8cf9-495f-b13a-cce12b45703c" -version = "2.3.1" - -[[deps.Plots]] -deps = ["Base64", "Contour", "Dates", "Downloads", "FFMPEG", "FixedPointNumbers", "GR", "JLFzf", "JSON", "LaTeXStrings", "Latexify", "LinearAlgebra", "Measures", "NaNMath", "Pkg", "PlotThemes", "PlotUtils", "PrecompileTools", "Printf", "REPL", "Random", "RecipesBase", "RecipesPipeline", "Reexport", "RelocatableFolders", "Requires", "Scratch", "Showoff", "SparseArrays", "Statistics", "StatsBase", "TOML", "UUIDs", "UnicodeFun", "Unzip"] -git-tree-sha1 = "83bd514e8ff16b5858ac54c53fa0bcf6002a3b00" -uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" -version = "1.41.7" - - [deps.Plots.extensions] - FileIOExt = "FileIO" - GeometryBasicsExt = "GeometryBasics" - IJuliaExt = "IJulia" - ImageInTerminalExt = "ImageInTerminal" - UnitfulExt = "Unitful" - - [deps.Plots.weakdeps] - FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" - GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326" - IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" - ImageInTerminal = "d8c32880-2388-543b-8c61-d9f865259254" - Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" - [[deps.Polyester]] deps = ["ArrayInterface", "BitTwiddlingConvenienceFunctions", "CPUSummary", "IfElse", "ManualMemory", "PolyesterWeave", "Static", "StaticArrayInterface", "StrideArraysCore", "ThreadingUtilities"] git-tree-sha1 = "16bbc30b5ebea91e9ce1671adc03de2832cff552" @@ -2104,12 +1649,6 @@ git-tree-sha1 = "645bed98cd47f72f67316fd42fc47dee771aefcd" uuid = "1d0040c9-8b98-4ee7-8388-3f51789ca0ad" version = "0.2.2" -[[deps.PooledArrays]] -deps = ["DataAPI", "Future"] -git-tree-sha1 = "36d8b4b899628fb92c2749eb488d884a926614d3" -uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720" -version = "1.4.3" - [[deps.PreallocationTools]] deps = ["Adapt", "ArrayInterface", "PrecompileTools", "SciMLPublic"] git-tree-sha1 = "cd00e28071a75c98664663f87c2ae447d25c0503" @@ -2134,15 +1673,15 @@ version = "1.3.4" [[deps.Preferences]] deps = ["TOML"] -git-tree-sha1 = "5005266de4bfe50e53ff44a5cb5c540b6e47a254" +git-tree-sha1 = "aef1987d90dcc2e2cbd919ad10b2d25bcce4f418" uuid = "21216c6a-2e73-6563-6e65-726566657250" -version = "1.6.0" +version = "1.6.1" [[deps.PrettyTables]] deps = ["Crayons", "LaTeXStrings", "Markdown", "PrecompileTools", "Printf", "REPL", "Reexport", "StringManipulation", "StyledStrings", "Tables"] -git-tree-sha1 = "99fd8f32ab6728ff205cddd24e2d9ea6cbb498d3" +git-tree-sha1 = "eeac4c9337f876c3c32b8da732141953567f57c9" uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" -version = "3.5.0" +version = "3.5.1" [deps.PrettyTables.extensions] PrettyTablesExcelExt = "XLSX" @@ -2159,11 +1698,6 @@ deps = ["Unicode"] uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" version = "1.11.0" -[[deps.PtrArrays]] -git-tree-sha1 = "4fbbafbc6251b883f4d2705356f3641f3652a7fe" -uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d" -version = "1.4.0" - [[deps.PureKLU]] deps = ["LinearAlgebra", "PrecompileTools", "SparseArrays"] git-tree-sha1 = "ad0e84647fe7b3c2ae02aaeace1522139aaf6a27" @@ -2174,36 +1708,6 @@ weakdeps = ["ForwardDiff"] [deps.PureKLU.extensions] PureKLUForwardDiffExt = "ForwardDiff" -[[deps.Qt6Base_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "Fontconfig_jll", "Glib_jll", "JLLWrappers", "Libdl", "Libglvnd_jll", "OpenSSL_jll", "Vulkan_Loader_jll", "Xorg_libSM_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Xorg_libxcb_jll", "Xorg_xcb_util_cursor_jll", "Xorg_xcb_util_image_jll", "Xorg_xcb_util_keysyms_jll", "Xorg_xcb_util_renderutil_jll", "Xorg_xcb_util_wm_jll", "Zlib_jll", "libinput_jll", "xkbcommon_jll"] -git-tree-sha1 = "144895f6166994730ee7ff8113b981fc360638f1" -uuid = "c0090381-4147-56d7-9ebc-da0b1113ec56" -version = "6.10.2+2" - -[[deps.Qt6Declarative_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Qt6Base_jll", "Qt6ShaderTools_jll", "Qt6Svg_jll"] -git-tree-sha1 = "159d253ab126d5b29230cf53521899bea4ef4648" -uuid = "629bc702-f1f5-5709-abd5-49b8460ea067" -version = "6.10.2+2" - -[[deps.Qt6ShaderTools_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Qt6Base_jll"] -git-tree-sha1 = "4d85eedf69d875982c46643f6b4f66919d7e157b" -uuid = "ce943373-25bb-56aa-8eca-768745ed7b5a" -version = "6.10.2+1" - -[[deps.Qt6Svg_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Qt6Base_jll"] -git-tree-sha1 = "81587ff5ff25a4e1115ce191e36285ede0334c9d" -uuid = "6de9746b-f93d-5813-b365-ba18ad4a9cf3" -version = "6.10.2+0" - -[[deps.Qt6Wayland_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Qt6Base_jll", "Qt6Declarative_jll"] -git-tree-sha1 = "672c938b4b4e3e0169a07a5f227029d4905456f2" -uuid = "e99dba38-086e-5de3-a5b1-6e4c66e897c3" -version = "6.10.2+1" - [[deps.QuadGK]] deps = ["DataStructures", "LinearAlgebra"] git-tree-sha1 = "5e8e8b0ab68215d7a2b14b9921a946fee794749e" @@ -2242,12 +1746,6 @@ git-tree-sha1 = "0c0dcffc464cda2ff33fc8ffc1dd1bdd53cd98be" uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" version = "1.4.0" -[[deps.RecipesPipeline]] -deps = ["Dates", "NaNMath", "PlotUtils", "PrecompileTools", "RecipesBase"] -git-tree-sha1 = "45cf9fd0ca5839d06ef333c8201714e888486342" -uuid = "01d81517-befc-4cb6-b9ec-a95719d0359c" -version = "0.6.12" - [[deps.RecursiveArrayTools]] deps = ["Adapt", "ArrayInterface", "DocStringExtensions", "GPUArraysCore", "LinearAlgebra", "PrecompileTools", "RecipesBase", "StaticArraysCore", "SymbolicIndexingInterface"] git-tree-sha1 = "d0282d612f22dcad7b81cf487b746e63aa2a6709" @@ -2295,12 +1793,6 @@ git-tree-sha1 = "ffd19052caf598b8653b99404058fce14828be51" uuid = "2792f1a3-b283-48e8-9a74-f99dce5104f3" version = "0.1.0" -[[deps.RelocatableFolders]] -deps = ["SHA", "Scratch"] -git-tree-sha1 = "ffdaf70d81cf6ff22c2b6e733c900c3321cab864" -uuid = "05181044-ff0b-4ac5-8273-598c1e38db00" -version = "1.0.1" - [[deps.Requires]] deps = ["UUIDs"] git-tree-sha1 = "62389eeff14780bfe55195b7204c0d8738436d64" @@ -2414,9 +1906,9 @@ version = "1.10.1" [[deps.SciMLOperators]] deps = ["Accessors", "Adapt", "ArrayInterface", "DocStringExtensions", "LinearAlgebra", "PrecompileTools", "SciMLPublic"] -git-tree-sha1 = "2712b6eeef282b5cd8c38660e68c001a4b9b24e2" +git-tree-sha1 = "3feb8a7bce88bb3ef051a4eda8106004ad040b6f" uuid = "c0aeaf25-5076-4817-a8d5-81caf7dfa961" -version = "1.30.3" +version = "1.30.4" [deps.SciMLOperators.extensions] SciMLOperatorsLoopVectorizationExt = "LoopVectorization" @@ -2444,37 +1936,14 @@ git-tree-sha1 = "67a144433c4ce877ee6d1ada69a124d6b1ecf7be" uuid = "7e506255-f358-4e82-b7e4-beb19740aa63" version = "1.6.2" -[[deps.Scratch]] -deps = ["Dates"] -git-tree-sha1 = "9b81b8393e50b7d4e6d0a9f14e192294d3b7c109" -uuid = "6c6a2e73-6563-6170-7368-637461726353" -version = "1.3.0" - -[[deps.SentinelArrays]] -deps = ["Dates", "Random"] -git-tree-sha1 = "084c47c7c5ce5cfecefa0a98dff69eb3646b5a80" -uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c" -version = "1.4.10" - [[deps.Serialization]] uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" version = "1.11.0" [[deps.Setfield]] deps = ["ConstructionBase", "Future", "MacroTools", "StaticArraysCore"] -git-tree-sha1 = "c5391c6ace3bc430ca630251d02ea9687169ca68" +git-tree-sha1 = "89880a2cca5cabc68c9023f58e544beda1d2fc1f" uuid = "efcf1570-3423-57d1-acb7-fd33fddbac46" -version = "1.1.2" - -[[deps.Showoff]] -deps = ["Dates"] -git-tree-sha1 = "8238217340ad0aaabe11afe39c1098b5bc9f4c8e" -uuid = "992d4aef-0814-514b-bc4d-f2e9a6c4116f" -version = "1.1.1" - -[[deps.SimpleBufferStream]] -git-tree-sha1 = "f305871d2f381d21527c770d4788c06c097c9bc1" -uuid = "777ac1f9-54b0-4bf8-805c-2214025038e7" version = "1.2.0" [[deps.SimpleNonlinearSolve]] @@ -2497,12 +1966,6 @@ version = "2.12.0" uuid = "6462fe0b-24de-5631-8697-dd941f90decc" version = "1.11.0" -[[deps.SortingAlgorithms]] -deps = ["DataStructures"] -git-tree-sha1 = "13cd91cc9be159e3f4d95b857fa2aa383b53772a" -uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c" -version = "1.2.3" - [[deps.SparseArrays]] deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" @@ -2544,9 +2007,9 @@ version = "0.4.29" [[deps.SpecialFunctions]] deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"] -git-tree-sha1 = "429071b23f4c9a13fb6582f807cc2ef454082408" +git-tree-sha1 = "88ef0d98204515bc53dd4aaea5f7788882d1b66c" uuid = "276daf66-3868-5448-9aa4-cd146d93841b" -version = "2.9.0" +version = "2.9.1" [deps.SpecialFunctions.extensions] SpecialFunctionsChainRulesCoreExt = "ChainRulesCore" @@ -2576,9 +2039,9 @@ version = "1.10.0" [[deps.StaticArrays]] deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] -git-tree-sha1 = "de349d5585559b692f518b6d3fc7c179d64b2364" +git-tree-sha1 = "8a8a52f887f38d44014e24ee46b8a7311c625f6d" uuid = "90137ffa-7385-5640-81b9-e52037218182" -version = "1.9.23" +version = "1.9.24" [deps.StaticArrays.extensions] StaticArraysChainRulesCoreExt = "ChainRulesCore" @@ -2603,18 +2066,6 @@ weakdeps = ["SparseArrays"] [deps.Statistics.extensions] SparseArraysExt = ["SparseArrays"] -[[deps.StatsAPI]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "178ed29fd5b2a2cfc3bd31c13375ae925623ff36" -uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0" -version = "1.8.0" - -[[deps.StatsBase]] -deps = ["AliasTables", "DataAPI", "DataStructures", "IrrationalConstants", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"] -git-tree-sha1 = "adb9da019510162e67a4493fc235c23203d8b09e" -uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" -version = "0.34.13" - [[deps.StrideArraysCore]] deps = ["ArrayInterface", "CloseOpenIntervals", "IfElse", "LayoutPointers", "LinearAlgebra", "ManualMemory", "SIMDTypes", "Static", "StaticArrayInterface", "ThreadingUtilities"] git-tree-sha1 = "5316097111523c9a970596a5b33cfea5f92e8581" @@ -2627,6 +2078,28 @@ git-tree-sha1 = "9e3a02d73a8f0a9be04e677dbef758aab40f8bea" uuid = "892a3eda-7b42-436c-8928-eab12a02cf0e" version = "0.6.1" +[[deps.StructUtils]] +deps = ["Dates", "UUIDs"] +git-tree-sha1 = "b04bfab37484defe300b2f9ed64469abab2431ff" +uuid = "ec057cc2-7a8d-4b58-b3b3-92acb9f63b42" +version = "2.12.0" + + [deps.StructUtils.extensions] + StructUtilsDBInterfaceExt = ["DBInterface", "Tables"] + StructUtilsDataFramesExt = ["DataFrames"] + StructUtilsLazilyInitializedFieldsExt = ["LazilyInitializedFields"] + StructUtilsMeasurementsExt = ["Measurements"] + StructUtilsStaticArraysCoreExt = ["StaticArraysCore"] + StructUtilsTablesExt = ["Tables"] + + [deps.StructUtils.weakdeps] + DBInterface = "a10d1c49-ce27-4219-8d33-6db1a4562965" + DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" + LazilyInitializedFields = "0e77f7df-68c5-4e49-93ce-4cd80f5598bf" + Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7" + StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" + Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" + [[deps.StyledStrings]] uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b" version = "1.11.0" @@ -2638,9 +2111,9 @@ version = "7.8.3+2" [[deps.SymbolicIndexingInterface]] deps = ["Accessors", "ArrayInterface", "PrecompileTools", "RuntimeGeneratedFunctions", "StaticArraysCore"] -git-tree-sha1 = "7eb6da9656581ac9fbffaef3ef7950a090a5002a" +git-tree-sha1 = "42dd4c193a1bacea5b6adf78b6224de907b06351" uuid = "2efcf032-c050-4f8e-a9bb-153293bab1f5" -version = "0.3.55" +version = "0.3.56" weakdeps = ["PrettyTables"] [deps.SymbolicIndexingInterface.extensions] @@ -2668,12 +2141,6 @@ deps = ["ArgTools", "SHA"] uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" version = "1.10.0" -[[deps.TensorCore]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "1feb45f88d133a655e001435632f019a9a1bcdb6" -uuid = "62fd8b95-f654-4bbd-a8a5-9c27f68ccd50" -version = "0.1.1" - [[deps.Test]] deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" @@ -2713,223 +2180,27 @@ git-tree-sha1 = "ea3e54c2bdde39062abf5a9758a23735558705e1" uuid = "781d530d-4396-4725-bb49-402e4bee1e77" version = "1.4.0" -[[deps.URIs]] -deps = ["Serialization"] -git-tree-sha1 = "0392e5e3c5327b90a487b0ff3e818c4aecd3660b" -uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" -version = "1.7.1" - [[deps.UUIDs]] deps = ["Random", "SHA"] uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" version = "1.11.0" -[[deps.UnPack]] -git-tree-sha1 = "387c1f73762231e86e0c9c5443ce3b4a0a9a0c2b" -uuid = "3a884ed6-31ef-47d7-9d2a-63182c4928ed" -version = "1.0.2" - [[deps.Unicode]] uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" version = "1.11.0" -[[deps.UnicodeFun]] -deps = ["REPL"] -git-tree-sha1 = "53915e50200959667e78a92a418594b428dffddf" -uuid = "1cfade01-22cf-5700-b092-accc4b62d6e1" -version = "0.4.1" - -[[deps.Unzip]] -git-tree-sha1 = "ca0969166a028236229f63514992fc073799bb78" -uuid = "41fe7b60-77ed-43a1-b4f0-825fd5a5650d" -version = "0.2.0" - -[[deps.Vulkan_Loader_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Wayland_jll", "Xorg_libX11_jll", "Xorg_libXrandr_jll", "xkbcommon_jll"] -git-tree-sha1 = "2f0486047a07670caad3a81a075d2e518acc5c59" -uuid = "a44049a8-05dd-5a78-86c9-5fde0876e88c" -version = "1.3.243+0" - -[[deps.Wayland_jll]] -deps = ["Artifacts", "EpollShim_jll", "Expat_jll", "JLLWrappers", "Libdl", "Libffi_jll"] -git-tree-sha1 = "96478df35bbc2f3e1e791bc7a3d0eeee559e60e9" -uuid = "a2964d1f-97da-50d4-b82a-358c7fce9d89" -version = "1.24.0+0" - -[[deps.WebIO]] -deps = ["AssetRegistry", "Base64", "Distributed", "FunctionalCollections", "JSON", "Logging", "Observables", "Pkg", "Random", "Requires", "Sockets", "UUIDs", "WebSockets", "Widgets"] -git-tree-sha1 = "0eef0765186f7452e52236fa42ca8c9b3c11c6e3" -uuid = "0f1e0344-ec1d-5b48-a673-e5cf874b6c29" -version = "0.8.21" - -[[deps.WebSockets]] -deps = ["Base64", "Dates", "HTTP", "Logging", "Sockets"] -git-tree-sha1 = "4162e95e05e79922e44b9952ccbc262832e4ad07" -uuid = "104b5d7c-a370-577a-8038-80a2059c5097" -version = "1.6.0" - -[[deps.Widgets]] -deps = ["Colors", "Dates", "Observables", "OrderedCollections"] -git-tree-sha1 = "cab29ed70d9355a28b95b1798007ff3a00eefc91" -uuid = "cc8bc4a8-27d6-5769-a93b-9d913e69aa62" -version = "0.6.8" - [[deps.XML2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] git-tree-sha1 = "b826aedca5fce2e3c91904e338c279e5c1a2da74" uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" version = "2.15.4+0" -[[deps.XZ_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "e52eca002a11c30a858185efdfb15311e1c7a6bf" -uuid = "ffd25f8a-64ca-5728-b0f7-c24cf3aae800" -version = "5.8.4+0" - -[[deps.Xorg_libICE_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "a3ea76ee3f4facd7a64684f9af25310825ee3668" -uuid = "f67eecfb-183a-506d-b269-f58e52b52d7c" -version = "1.1.2+0" - -[[deps.Xorg_libSM_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libICE_jll"] -git-tree-sha1 = "9c7ad99c629a44f81e7799eb05ec2746abb5d588" -uuid = "c834827a-8449-5923-a945-d239c165b7dd" -version = "1.2.6+0" - -[[deps.Xorg_libX11_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xtrans_jll"] -git-tree-sha1 = "808090ede1d41644447dd5cbafced4731c56bd2f" -uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc" -version = "1.8.13+0" - -[[deps.Xorg_libXau_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "aa1261ebbac3ccc8d16558ae6799524c450ed16b" -uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec" -version = "1.0.13+0" - -[[deps.Xorg_libXcursor_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXfixes_jll", "Xorg_libXrender_jll"] -git-tree-sha1 = "6c74ca84bbabc18c4547014765d194ff0b4dc9da" -uuid = "935fb764-8cf2-53bf-bb30-45bb1f8bf724" -version = "1.2.4+0" - -[[deps.Xorg_libXdmcp_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "52858d64353db33a56e13c341d7bf44cd0d7b309" -uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05" -version = "1.1.6+0" - -[[deps.Xorg_libXext_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] -git-tree-sha1 = "1a4a26870bf1e5d26cd585e38038d399d7e65706" -uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3" -version = "1.3.8+0" - -[[deps.Xorg_libXfixes_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] -git-tree-sha1 = "75e00946e43621e09d431d9b95818ee751e6b2ef" -uuid = "d091e8ba-531a-589c-9de9-94069b037ed8" -version = "6.0.2+0" - -[[deps.Xorg_libXi_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXext_jll", "Xorg_libXfixes_jll"] -git-tree-sha1 = "dcb316b3ce0941f195537dda56bea4517fcd3ff5" -uuid = "a51aa0fd-4e3c-5386-b890-e753decda492" -version = "1.8.4+0" - -[[deps.Xorg_libXinerama_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXext_jll"] -git-tree-sha1 = "0ba01bc7396896a4ace8aab67db31403c71628f4" -uuid = "d1454406-59df-5ea1-beac-c340f2130bc3" -version = "1.1.7+0" - -[[deps.Xorg_libXrandr_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXext_jll", "Xorg_libXrender_jll"] -git-tree-sha1 = "6c174ef70c96c76f4c3f4d3cfbe09d018bcd1b53" -uuid = "ec84b674-ba8e-5d96-8ba1-2a689ba10484" -version = "1.5.6+0" - -[[deps.Xorg_libXrender_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] -git-tree-sha1 = "7ed9347888fac59a618302ee38216dd0379c480d" -uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa" -version = "0.9.12+0" - [[deps.Xorg_libpciaccess_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] git-tree-sha1 = "58972370b81423fc546c56a60ed1a009450177c3" uuid = "a65dc6b1-eb27-53a1-bb3e-dea574b5389e" version = "0.19.0+0" -[[deps.Xorg_libxcb_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXau_jll", "Xorg_libXdmcp_jll"] -git-tree-sha1 = "bfcaf7ec088eaba362093393fe11aa141fa15422" -uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b" -version = "1.17.1+0" - -[[deps.Xorg_libxkbfile_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] -git-tree-sha1 = "ed756a03e95fff88d8f738ebc2849431bdd4fd1a" -uuid = "cc61e674-0454-545c-8b26-ed2c68acab7a" -version = "1.2.0+0" - -[[deps.Xorg_xcb_util_cursor_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_image_jll", "Xorg_xcb_util_jll", "Xorg_xcb_util_renderutil_jll"] -git-tree-sha1 = "9750dc53819eba4e9a20be42349a6d3b86c7cdf8" -uuid = "e920d4aa-a673-5f3a-b3d7-f755a4d47c43" -version = "0.1.6+0" - -[[deps.Xorg_xcb_util_image_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] -git-tree-sha1 = "f4fc02e384b74418679983a97385644b67e1263b" -uuid = "12413925-8142-5f55-bb0e-6d7ca50bb09b" -version = "0.4.1+0" - -[[deps.Xorg_xcb_util_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll"] -git-tree-sha1 = "68da27247e7d8d8dafd1fcf0c3654ad6506f5f97" -uuid = "2def613f-5ad1-5310-b15b-b15d46f528f5" -version = "0.4.1+0" - -[[deps.Xorg_xcb_util_keysyms_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] -git-tree-sha1 = "44ec54b0e2acd408b0fb361e1e9244c60c9c3dd4" -uuid = "975044d2-76e6-5fbe-bf08-97ce7c6574c7" -version = "0.4.1+0" - -[[deps.Xorg_xcb_util_renderutil_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] -git-tree-sha1 = "5b0263b6d080716a02544c55fdff2c8d7f9a16a0" -uuid = "0d47668e-0667-5a69-a72c-f761630bfb7e" -version = "0.3.10+0" - -[[deps.Xorg_xcb_util_wm_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] -git-tree-sha1 = "f233c83cad1fa0e70b7771e0e21b061a116f2763" -uuid = "c22f9ab0-d5fe-5066-847c-f4bb1cd4e361" -version = "0.4.2+0" - -[[deps.Xorg_xkbcomp_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxkbfile_jll"] -git-tree-sha1 = "9a5e6e8f1a7c40941465c04840145e527f0d6b4b" -uuid = "35661453-b289-5fab-8a00-3d9160c6a3a4" -version = "1.5.0+0" - -[[deps.Xorg_xkeyboard_config_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xkbcomp_jll"] -git-tree-sha1 = "df77b22eb77051b3d0ef30aab979684f7482c66f" -uuid = "33bec58e-1273-512f-9401-5d533626f822" -version = "2.48.0+0" - -[[deps.Xorg_xtrans_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "a63799ff68005991f9d9491b6e95bd3478d783cb" -uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10" -version = "1.6.0+0" - [[deps.Zlib_jll]] deps = ["Libdl"] uuid = "83775a58-1f1d-513f-b197-d71354ab007a" @@ -2943,57 +2214,57 @@ version = "1.5.7+1" [[deps.aws_c_auth_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_cal_jll", "aws_c_http_jll", "aws_c_sdkutils_jll"] -git-tree-sha1 = "8cab83c96af80a1be968251ce1a0548a7545484d" +git-tree-sha1 = "7945f94b714d20b55aee2510b511afee0ee17ad2" uuid = "2b3700d1-4306-52e2-a478-c162f0c514be" -version = "0.9.6+0" +version = "1.0.1+0" [[deps.aws_c_cal_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_common_jll"] -git-tree-sha1 = "22c0f42f4a1f0dc5dcfa8fd267c4ac407c455e7a" +git-tree-sha1 = "60a8d3fafed6f7ed8b3114793885a03330aa8e51" uuid = "70f11efc-bab2-57f1-b0f3-22aad4e67c4b" -version = "0.9.13+0" +version = "1.0.0+0" [[deps.aws_c_common_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "a759cb9bf456ad792cc7898a81ae333cce9ef02a" +git-tree-sha1 = "f5ce8191ad02a5a4127d37ab5f474aee5cf91ad4" uuid = "73048d1d-b8c4-5092-a58d-866c5e8d1e50" -version = "0.12.6+0" +version = "1.0.3+1" [[deps.aws_c_compression_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_common_jll"] -git-tree-sha1 = "7910c72f45f44afd297c39fe43b99c56d5ed22ec" +git-tree-sha1 = "6efd591b9fae7919266d3b342a18a6e6280850f9" uuid = "73a04cd5-f3d7-5bac-9290-e8adb709f224" -version = "0.3.2+0" +version = "1.0.0+1" [[deps.aws_c_http_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_compression_jll", "aws_c_io_jll"] -git-tree-sha1 = "3fb8685778068de502c72fec5dd8075e037cee15" +git-tree-sha1 = "e4108b4a2201cb9987ddd36714fdcfd572d6bb24" uuid = "3254fc65-9028-534d-aa9d-d76d128babc6" -version = "0.10.15+0" +version = "1.0.0+0" [[deps.aws_c_io_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_cal_jll", "aws_c_common_jll", "s2n_tls_jll"] -git-tree-sha1 = "7e481d474b2087ee8bbf55b81bf9119f21e396d9" +git-tree-sha1 = "b816756403a8db80e32fc23a4a1aae240d2168ca" uuid = "13c41daa-f319-5298-b5eb-5754e0170d52" -version = "0.26.3+0" +version = "1.0.0+0" [[deps.aws_c_s3_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_auth_jll", "aws_c_common_jll", "aws_c_http_jll", "aws_checksums_jll", "s2n_tls_jll"] -git-tree-sha1 = "3e9917ab25114feba657e71be41cad068b9f6595" +git-tree-sha1 = "1ebf5d4fa9c5248c3c1066124ef41d06de05a03a" uuid = "bd1f34fb-993f-5903-a121-aaf302eed6d4" -version = "0.11.5+0" +version = "1.3.1+0" [[deps.aws_c_sdkutils_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_common_jll"] -git-tree-sha1 = "c43dfba2c1ab9ea9f02f2c80e86fa16f6460244e" +git-tree-sha1 = "0162699e5aa99420f04a9606a9d7b38a872a81ee" uuid = "1282aa60-004d-510b-9f52-12498d409daa" -version = "0.2.4+1" +version = "1.0.0+0" [[deps.aws_checksums_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_common_jll"] -git-tree-sha1 = "2570c8e23f4771a087b12a47edcaaa670ac05a01" +git-tree-sha1 = "ee1f5427815c98355b0b0454b5fe79806ffdf935" uuid = "b2a88e68-78e7-5e94-8c20-c02986ec140e" -version = "0.2.10+0" +version = "1.0.1+1" [[deps.dlfcn_win32_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] @@ -3001,100 +2272,22 @@ git-tree-sha1 = "e141d67ffe550eadfb5af1bdbdaf138031e4805f" uuid = "c4b69c83-5512-53e3-94e6-de98773c479f" version = "1.4.2+0" -[[deps.eudev_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "c3b0e6196d50eab0c5ed34021aaa0bb463489510" -uuid = "35ca27e7-8b34-5b7f-bca9-bdc33f59eb06" -version = "3.2.14+0" - -[[deps.fzf_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "b6a34e0e0960190ac2a4363a1bd003504772d631" -uuid = "214eeab7-80f7-51ab-84ad-2988db7cef09" -version = "0.61.1+0" - [[deps.libaec_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "60f4792734488db6f42e2c7699f1d4594780bd03" +git-tree-sha1 = "31e4c16405815484827f75a38fb7eb17432e72e3" uuid = "477f73a3-ac25-53e9-8cc3-50b2fa2566f0" -version = "1.1.7+0" - -[[deps.libaom_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "1210ba774d3427387d307bf1f416d699b7c39417" -uuid = "a4ae2306-e953-59d6-aa16-d00cac43593b" -version = "3.15.1+0" - -[[deps.libass_jll]] -deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Zlib_jll"] -git-tree-sha1 = "cb007192783c56d8249db4cf0e3495001edfe414" -uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0" -version = "0.17.5+0" +version = "1.1.8+0" [[deps.libblastrampoline_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" version = "5.15.0+0" -[[deps.libdecor_jll]] -deps = ["Artifacts", "Dbus_jll", "JLLWrappers", "Libdl", "Libglvnd_jll", "Pango_jll", "Wayland_jll", "xkbcommon_jll"] -git-tree-sha1 = "9bf7903af251d2050b467f76bdbe57ce541f7f4f" -uuid = "1183f4f0-6f2a-5f1a-908b-139f9cdfea6f" -version = "0.2.2+0" - -[[deps.libdrm_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libpciaccess_jll"] -git-tree-sha1 = "28e57478e8a160d346a19c28b3fffb9273bcc9c2" -uuid = "8e53e030-5e6c-5a89-a30b-be5b7263a166" -version = "2.4.134+0" - -[[deps.libevdev_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "56d643b57b188d30cccc25e331d416d3d358e557" -uuid = "2db6ffa8-e38f-5e21-84af-90c45d0032cc" -version = "1.13.4+0" - -[[deps.libfdk_aac_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "646634dd19587a56ee2f1199563ec056c5f228df" -uuid = "f638f0a6-7fb0-5443-88ba-1cc74229b280" -version = "2.0.4+0" - -[[deps.libinput_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "eudev_jll", "libevdev_jll", "mtdev_jll"] -git-tree-sha1 = "91d05d7f4a9f67205bd6cf395e488009fe85b499" -uuid = "36db933b-70db-51c0-b978-0f229ee0e533" -version = "1.28.1+0" - -[[deps.libpng_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] -git-tree-sha1 = "1fcd6cf05d23adab0b5ffc496d020537215751dc" -uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f" -version = "1.6.59+1" - -[[deps.libva_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll", "Xorg_libXext_jll", "Xorg_libXfixes_jll", "libdrm_jll"] -git-tree-sha1 = "7dbf96baae3310fe2fa0df0ccbb3c6288d5816c9" -uuid = "9a156e7d-b971-5f62-b2c9-67348b8fb97c" -version = "2.23.0+0" - -[[deps.libvorbis_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll"] -git-tree-sha1 = "11e1772e7f3cc987e9d3de991dd4f6b2602663a5" -uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a" -version = "1.3.8+0" - [[deps.mpif_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIABI_jll", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "TOML"] -git-tree-sha1 = "a06fcd368cfe6fe2c0eb7b63320d4d27ddcd010d" +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "MPIABI_jll", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "TOML"] +git-tree-sha1 = "52e322f7a6d418576cdeb043dd2a7f7ad9a8abd0" uuid = "9aeb927a-4695-514f-a259-621a69f20ec0" -version = "1.0.0+0" - -[[deps.mtdev_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "b4d631fd51f2e9cdd93724ae25b2efc198b059b1" -uuid = "009596ad-96f7-51b1-9f1b-5ce2d5e8a71e" -version = "1.1.7+0" +version = "1.0.1+0" [[deps.nghttp2_jll]] deps = ["Artifacts", "Libdl"] @@ -3114,24 +2307,6 @@ version = "17.7.0+0" [[deps.s2n_tls_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "d6cfe0874f96962fe4b20ed8bdb71c467e09c2c6" +git-tree-sha1 = "6f9611b1b99beb089154f19a4031579f3a818cbd" uuid = "cddc5d3d-934d-5d3a-9747-62fc12ea3f48" -version = "1.7.11+0" - -[[deps.x264_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "14cc7083fc6dff3cc44f2bc435ee96d06ed79aa7" -uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a" -version = "10164.0.1+0" - -[[deps.x265_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "e7b67590c14d487e734dcb925924c5dc43ec85f3" -uuid = "dfaa095f-4041-5dcd-9319-2fabd8486b76" -version = "4.1.0+0" - -[[deps.xkbcommon_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xkeyboard_config_jll"] -git-tree-sha1 = "a1fc6507a40bf504527d0d4067d718f8e179b2b8" -uuid = "d8fb68d0-12a3-5cfd-a85a-d49703b185fd" -version = "1.13.0+0" +version = "1.7.12+0" diff --git a/ci/manifests/Manifest-v1.13.toml b/ci/manifests/Manifest-v1.13.toml index 459066e96..11fb356d8 100644 --- a/ci/manifests/Manifest-v1.13.toml +++ b/ci/manifests/Manifest-v1.13.toml @@ -2,7 +2,7 @@ julia_version = "1.13.3" manifest_format = "2.1" -project_hash = "46cc8ae34afcb7a4b038db845711846a1cfd9266" +project_hash = "cb302878a288b9dbaff045ec0f6d5c726498ce68" [[deps.ADTypes]] deps = ["PrecompileTools"] @@ -82,10 +82,10 @@ version = "0.1.46" [[deps.Adapt]] deps = ["LinearAlgebra"] -git-tree-sha1 = "dee8388a9bec89c8713ad176f96960ae2f98598a" +git-tree-sha1 = "eec8a0ad4e4b673afba213931504c360fffb8f97" registries = "General" uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" -version = "4.7.4" +version = "4.8.0" weakdeps = ["SparseArrays", "StaticArrays"] [deps.Adapt.extensions] @@ -94,10 +94,10 @@ weakdeps = ["SparseArrays", "StaticArrays"] [[deps.AdaptiveArrayPools]] deps = ["Preferences", "Printf", "Random"] -git-tree-sha1 = "5feaa5055572e809b69b62f06826b1c1c8d3ebf3" +git-tree-sha1 = "86d0ae34bc46373170064ef33c12a303994ab736" registries = "General" uuid = "4f381ef7-9af0-4cbe-99d4-cf36d7b0f233" -version = "0.3.11" +version = "0.3.12" [deps.AdaptiveArrayPools.extensions] AdaptiveArrayPoolsCUDAExt = "CUDA" @@ -113,13 +113,6 @@ registries = "General" uuid = "35492f91-a3bd-45ad-95db-fcad7dcfedb7" version = "1.2.0" -[[deps.AliasTables]] -deps = ["PtrArrays", "Random"] -git-tree-sha1 = "9876e1e164b144ca45e9e3198d0b689cadfed9ff" -registries = "General" -uuid = "66dad0bd-aa9a-41b7-9441-69ab47430ed8" -version = "1.1.3" - [[deps.ArgTools]] uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" version = "1.1.2" @@ -179,23 +172,10 @@ weakdeps = ["SparseArrays"] uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" version = "1.11.0" -[[deps.AssetRegistry]] -deps = ["JSON", "Pidfile", "SHA"] -git-tree-sha1 = "902b85010203830d4bc02259f5450d2e16b316d8" -registries = "General" -uuid = "bf4720bc-e11a-5d0c-854e-bdca1663c893" -version = "0.1.1" - [[deps.Base64]] uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" version = "1.11.0" -[[deps.BitFlags]] -git-tree-sha1 = "bbe1079eecf9c9fbb52765193ad2bae27ae09bc8" -registries = "General" -uuid = "d1d4a3ce-64b1-5f1a-9ba4-7e7e69966f35" -version = "0.1.10" - [[deps.BitTwiddlingConvenienceFunctions]] deps = ["Static"] git-tree-sha1 = "f21cfd4950cb9f0587d5067e69405ad2acd27b87" @@ -203,13 +183,6 @@ registries = "General" uuid = "62783981-4cbd-42fc-bca8-16325de8dc4b" version = "0.1.6" -[[deps.Blink]] -deps = ["Base64", "Distributed", "HTTP", "JSExpr", "JSON", "Lazy", "Logging", "MacroTools", "Mustache", "Mux", "Pkg", "Reexport", "Sockets", "WebIO"] -git-tree-sha1 = "bc93511973d1f949d45b0ea17878e6cb0ad484a1" -registries = "General" -uuid = "ad839575-38b3-5650-b840-f874b8c74a25" -version = "0.12.9" - [[deps.BracketingNonlinearSolve]] deps = ["CommonSolve", "ConcreteStructs", "NonlinearSolveBase", "PrecompileTools", "Reexport", "SciMLBase"] git-tree-sha1 = "7ad7171d693ae5552ac43862e7f6b61df4471c2b" @@ -225,13 +198,6 @@ version = "1.12.1" ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" -[[deps.Bzip2_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "1b96ea4a01afe0ea4090c5c8039690672dd13f2e" -registries = "General" -uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" -version = "1.0.9+0" - [[deps.CPUSummary]] deps = ["CpuId", "IfElse", "PrecompileTools", "Preferences", "Static"] git-tree-sha1 = "f3a21d7fc84ba618a779d1ed2fcca2e682865bab" @@ -241,10 +207,10 @@ version = "0.2.7" [[deps.CRlibm]] deps = ["CRlibm_jll"] -git-tree-sha1 = "66188d9d103b92b6cd705214242e27f5737a1e5e" +git-tree-sha1 = "0db159493e83ecb2d122f9c40138d89b70d43963" registries = "General" uuid = "96374032-68de-5a5b-8d9e-752f78720389" -version = "1.0.2" +version = "1.1.1" [[deps.CRlibm_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] @@ -253,13 +219,6 @@ registries = "General" uuid = "4e9b3aee-d8a1-5a3d-ad8b-7d824db253f0" version = "1.0.1+0" -[[deps.Cairo_jll]] -deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "Libdl", "Pixman_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"] -git-tree-sha1 = "0cfb779e24fdced77a05df1dbe99200821a9f563" -registries = "General" -uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a" -version = "1.18.8+1" - [[deps.ChunkCodecCore]] git-tree-sha1 = "3496e2b359c27793d12bec54ec94be7983a6ceb2" registries = "General" @@ -294,42 +253,6 @@ registries = "General" uuid = "944b1d66-785c-5afd-91f1-9de20f533193" version = "0.7.9" -[[deps.ColorSchemes]] -deps = ["ColorTypes", "ColorVectorSpace", "Colors", "FixedPointNumbers", "PrecompileTools", "Random"] -git-tree-sha1 = "b0fd3f56fa442f81e0a47815c92245acfaaa4e34" -registries = "General" -uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4" -version = "3.31.0" - -[[deps.ColorTypes]] -deps = ["FixedPointNumbers", "Random"] -git-tree-sha1 = "a7cf827a79b378a381e31e5c7feca853ec19cb94" -registries = "General" -uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" -version = "0.12.4" -weakdeps = ["StyledStrings"] - - [deps.ColorTypes.extensions] - StyledStringsExt = "StyledStrings" - -[[deps.ColorVectorSpace]] -deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "Requires", "Statistics", "TensorCore"] -git-tree-sha1 = "a4e53eb7a1779254f92c1cc1302c2bc6acdba560" -registries = "General" -uuid = "c3611d14-8923-5661-9e6a-0046d554d3a4" -version = "0.11.1" -weakdeps = ["SpecialFunctions"] - - [deps.ColorVectorSpace.extensions] - SpecialFunctionsExt = "SpecialFunctions" - -[[deps.Colors]] -deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "Reexport"] -git-tree-sha1 = "a633156e74096f90b3b5e833529292b651b5671a" -registries = "General" -uuid = "5ae59095-9a9b-59fe-a467-6f913c188581" -version = "0.13.3" - [[deps.CommonSolve]] deps = ["PrecompileTools"] git-tree-sha1 = "6c389fa857f6ca5a95474b52a52023fd77f24cb7" @@ -384,13 +307,6 @@ registries = "General" uuid = "2569d6c7-a4a2-43d3-a901-331e8e4be471" version = "0.2.8" -[[deps.ConcurrentUtilities]] -deps = ["Serialization", "Sockets"] -git-tree-sha1 = "d5e5fa1f0d6ca517edbbcd734efa93c6639405b0" -registries = "General" -uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb" -version = "2.7.1" - [[deps.ConstructionBase]] git-tree-sha1 = "b4b092499347b18a015186eae3042f72267106cb" registries = "General" @@ -452,13 +368,6 @@ registries = "General" uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" version = "1.16.0" -[[deps.DataFrames]] -deps = ["Compat", "DataAPI", "DataStructures", "Future", "InlineStrings", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrecompileTools", "PrettyTables", "Printf", "Random", "Reexport", "SentinelArrays", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"] -git-tree-sha1 = "5fab31e2e01e70ad66e3e24c968c264d1cf166d6" -registries = "General" -uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" -version = "1.8.2" - [[deps.DataInterpolations]] deps = ["EnumX", "FindFirstFunctions", "ForwardDiff", "LinearAlgebra", "PrettyTables", "RecipesBase", "Reexport"] git-tree-sha1 = "4d50e83772222ce023efb7d30632198828ea56a6" @@ -504,13 +413,6 @@ deps = ["Printf"] uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" version = "1.11.0" -[[deps.Dbus_jll]] -deps = ["Artifacts", "Expat_jll", "JLLWrappers", "Libdl"] -git-tree-sha1 = "473e9afc9cf30814eb67ffa5f2db7df82c3ad9fd" -registries = "General" -uuid = "ee1fde0b-3d02-5ea6-8484-8dfef6360eab" -version = "1.16.2+0" - [[deps.DelaunayTriangulation]] deps = ["AdaptivePredicates", "EnumX", "ExactPredicates", "Random"] git-tree-sha1 = "e1e28f37cbdca4b594ad86ecfe2ee8f25796ebff" @@ -687,10 +589,10 @@ uuid = "4e289a0a-7415-4d19-859d-a7e5c4648b56" version = "1.0.7" [[deps.EnzymeCore]] -git-tree-sha1 = "04c4269956f90d8f5c9acc73b774c5293481f4d7" +git-tree-sha1 = "d88c803e26799193db42e47f6e1e28eeb0322e4d" registries = "General" uuid = "f151be2c-9106-41f4-ab19-57ee4f262869" -version = "0.8.26" +version = "0.8.27" [deps.EnzymeCore.extensions] AdaptExt = "Adapt" @@ -700,13 +602,6 @@ version = "0.8.26" Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" -[[deps.EpollShim_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "8a4be429317c42cfae6a7fc03c31bad1970c310d" -registries = "General" -uuid = "2702e6a9-849d-5ed8-8c21-79e8b8f9ee43" -version = "0.0.20230411+1" - [[deps.ExactPredicates]] deps = ["IntervalArithmetic", "Random", "StaticArrays"] git-tree-sha1 = "83231673ea4d3d6008ac74dc5079e77ab2209d8f" @@ -714,19 +609,12 @@ registries = "General" uuid = "429591f6-91af-11e9-00e2-59fbe8cec110" version = "2.2.9" -[[deps.ExceptionUnwrapping]] -deps = ["Test"] -git-tree-sha1 = "4e468f521e1f9f86891cb07186de5df90360a666" -registries = "General" -uuid = "460bff9d-24e4-43bc-9d9f-a8973cb893f4" -version = "0.1.12" - [[deps.Expat_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "d0f666deaf8099a7d7ac229a74402ada88898be1" +git-tree-sha1 = "f2659a735f9360fedfa29925812380f322679e93" registries = "General" uuid = "2e619515-83b5-522b-bb60-26c02a35a201" -version = "2.9.0+0" +version = "2.10.0+0" [[deps.ExponentialUtilities]] deps = ["Adapt", "ArrayInterface", "GPUArraysCore", "GenericSchur", "LinearAlgebra", "PrecompileTools", "Printf", "SparseArrays", "libblastrampoline_jll"] @@ -746,24 +634,10 @@ uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04" version = "0.1.11" [[deps.ExproniconLite]] -git-tree-sha1 = "c13f0b150373771b0fdc1713c97860f8df12e6c2" +git-tree-sha1 = "250aa1562e840b38c72b44a9ebef0f39f6b6e950" registries = "General" uuid = "55351af7-c7e9-48d6-89ff-24e801d99491" -version = "0.10.14" - -[[deps.FFMPEG]] -deps = ["FFMPEG_jll"] -git-tree-sha1 = "7bd13840b4148949e290748071bbe6826b3eabcd" -registries = "General" -uuid = "c87230d0-a227-11e9-1b43-d7ebe4e7570a" -version = "0.4.6" - -[[deps.FFMPEG_jll]] -deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "PCRE2_jll", "Zlib_jll", "libaom_jll", "libass_jll", "libfdk_aac_jll", "libva_jll", "libvorbis_jll", "x264_jll", "x265_jll"] -git-tree-sha1 = "d9d3cd382f2c999f684e6bfa4039fcc16c153b6f" -registries = "General" -uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5" -version = "9.0.2+0" +version = "0.10.16" [[deps.FFTW]] deps = ["AbstractFFTs", "FFTW_jll", "Libdl", "LinearAlgebra", "MKL_jll", "Preferences", "Reexport"] @@ -781,10 +655,10 @@ version = "3.3.12+0" [[deps.FastBroadcast]] deps = ["ArrayInterface", "LinearAlgebra", "PrecompileTools"] -git-tree-sha1 = "b90aee4f191474eeda3dfa0cb588635f80820cf1" +git-tree-sha1 = "093bf5e9ea63b36032240033ec2f149088fc0926" registries = "General" uuid = "7034ab61-46d4-4ed7-9d0f-46aef9175898" -version = "1.5.0" +version = "1.5.1" [deps.FastBroadcast.extensions] FastBroadcastPolyesterExt = "Polyester" @@ -865,11 +739,13 @@ git-tree-sha1 = "6621fef488e496356c9c9625d0562c12a6070819" registries = "General" uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" version = "1.20.0" -weakdeps = ["HTTP"] [deps.FileIO.extensions] HTTPExt = "HTTP" + [deps.FileIO.weakdeps] + HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" + [[deps.FileWatching]] uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" version = "1.11.0" @@ -919,51 +795,17 @@ version = "2.33.0" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" -[[deps.FixedPointNumbers]] -deps = ["Random", "Statistics"] -git-tree-sha1 = "59af96b98217c6ef4ae0dfe065ac7c20831d1a84" -registries = "General" -uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" -version = "0.8.6" - -[[deps.Fontconfig_jll]] -deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Zlib_jll"] -git-tree-sha1 = "e1e9ba3a8f5d71e6ae698f138a1362aeabb42e9f" -registries = "General" -uuid = "a3f928ae-7b40-5064-980b-68af3947d34b" -version = "2.18.3+0" - -[[deps.Format]] -git-tree-sha1 = "9c68794ef81b08086aeb32eeaf33531668d5f5fc" -registries = "General" -uuid = "1fa38f19-a742-5d3f-a2b9-30dd87b9d5f8" -version = "1.3.7" - [[deps.ForwardDiff]] deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "LogExpFunctions", "NaNMath", "Preferences", "Printf", "Random", "SpecialFunctions"] -git-tree-sha1 = "3b0f72e2ffef1a139ac450725c9ecd01c0a3c050" +git-tree-sha1 = "a55669aa77ae8bbdc7310c4027112500ff5ec874" registries = "General" uuid = "f6369f11-7733-5829-9624-2563aa707210" -version = "1.4.6" +version = "1.4.7" weakdeps = ["StaticArrays"] [deps.ForwardDiff.extensions] ForwardDiffStaticArraysExt = "StaticArrays" -[[deps.FreeType2_jll]] -deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Zlib_jll"] -git-tree-sha1 = "d95a7a3a7e0f2fffc6291bd33035830aab01310b" -registries = "General" -uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7" -version = "2.15.0+0" - -[[deps.FriBidi_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "f3f50c8001bdfa84f312234fa86715b7c9243771" -registries = "General" -uuid = "559328eb-81f9-559d-9380-de523a88c83c" -version = "1.0.18+0" - [[deps.FunctionWrappers]] git-tree-sha1 = "d62485945ce5ae9c0c48f124a84998d755bae00e" registries = "General" @@ -986,64 +828,36 @@ version = "1.13.0" EnzymeCore = "f151be2c-9106-41f4-ab19-57ee4f262869" Mooncake = "da2b9cff-9c12-43a0-ae48-6db2b0edb7d6" -[[deps.FunctionalCollections]] -deps = ["Test"] -git-tree-sha1 = "04cb9cfaa6ba5311973994fe3496ddec19b6292a" -registries = "General" -uuid = "de31a74c-ac4f-5751-b3fd-e18cd04993ca" -version = "0.5.0" - [[deps.Future]] deps = ["Random"] uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820" version = "1.11.0" -[[deps.GLFW_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Libglvnd_jll", "Xorg_libXcursor_jll", "Xorg_libXi_jll", "Xorg_libXinerama_jll", "Xorg_libXrandr_jll", "libdecor_jll", "xkbcommon_jll"] -git-tree-sha1 = "64bbbb7d1499297751b536dd39c58b20750ab1db" -registries = "General" -uuid = "0656b61e-2033-5cc2-a64a-77c0f6c09b89" -version = "3.5.1+0" - [[deps.GPUArraysCore]] deps = ["Adapt"] -git-tree-sha1 = "0b2b3e8b8fb7c1f5ddbf498be2eb2b3b37c9c0ed" +git-tree-sha1 = "f60436514e6d73f8ee069ae4ee0ff038fe96b69d" registries = "General" uuid = "46192b85-c4d5-4398-a991-12ede77f4527" -version = "0.2.1" - -[[deps.GR]] -deps = ["Artifacts", "Base64", "DelimitedFiles", "Downloads", "GR_jll", "JSON", "Libdl", "LinearAlgebra", "Preferences", "Printf", "Qt6Wayland_jll", "Random", "Serialization", "Sockets", "TOML", "Tar", "Test", "p7zip_jll"] -git-tree-sha1 = "4d777f73c46b46b8b5276206059cf8a195499314" -registries = "General" -uuid = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71" -version = "0.73.27" - - [deps.GR.extensions] - IJuliaExt = "IJulia" - - [deps.GR.weakdeps] - IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" - -[[deps.GR_jll]] -deps = ["Artifacts", "Bzip2_jll", "Cairo_jll", "FFMPEG_jll", "Fontconfig_jll", "FreeType2_jll", "GLFW_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pixman_jll", "Qt6Base_jll", "Zlib_jll", "libpng_jll"] -git-tree-sha1 = "f8eb8f7ba13ea75083531647fc8faeda8d541f07" -registries = "General" -uuid = "d2c73de3-f751-5644-a686-071e5b155ba9" -version = "0.73.27+0" +version = "0.2.2" [[deps.GeneralizedPerturbedEquilibrium]] -deps = ["AdaptiveArrayPools", "CommonSolve", "Contour", "Dates", "DelaunayTriangulation", "DelimitedFiles", "DiffEqCallbacks", "Documenter", "DoubleFloats", "FFTW", "FastGaussQuadrature", "FastInterpolations", "HDF5", "IMASdd", "JLD2", "LaTeXStrings", "LinearAlgebra", "Logging", "OrdinaryDiffEq", "Pkg", "PlotlyJS", "Plots", "PrecompileTools", "Printf", "QuadGK", "Random", "Roots", "SparseArrays", "SpecialFunctions", "StaticArrays", "Statistics", "TOML", "Test"] +deps = ["AdaptiveArrayPools", "CommonSolve", "Contour", "Dates", "DelaunayTriangulation", "DelimitedFiles", "DiffEqCallbacks", "Documenter", "DoubleFloats", "FFTW", "FastGaussQuadrature", "FastInterpolations", "HDF5", "IMASdd", "JLD2", "LaTeXStrings", "LinearAlgebra", "Logging", "OrdinaryDiffEq", "Pkg", "PrecompileTools", "Printf", "QuadGK", "Random", "Roots", "SparseArrays", "SpecialFunctions", "StaticArrays", "Statistics", "TOML", "Test"] path = "." uuid = "462872dd-e066-4d2e-b993-6468b5239634" -version = "0.1.0" +version = "2.0.1" + + [deps.GeneralizedPerturbedEquilibrium.extensions] + GPECMakieExt = "Makie" + + [deps.GeneralizedPerturbedEquilibrium.weakdeps] + Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" [[deps.GenericLinearAlgebra]] deps = ["LinearAlgebra", "Printf", "Random", "libblastrampoline_jll"] -git-tree-sha1 = "961f8a5d33f043e02cdf2b8020e3a7b0aae78474" +git-tree-sha1 = "3b0e97a0de86e54fa897c6e431c179ad4cd1dea0" registries = "General" uuid = "14197337-ba66-59df-a3e3-ca00e7dcff7a" -version = "0.4.1" +version = "0.4.2" [[deps.GenericSchur]] deps = ["LinearAlgebra", "MatrixFactorizations", "Preferences", "Printf", "UUIDs"] @@ -1052,20 +866,6 @@ registries = "General" uuid = "c145ed77-6b09-5dd9-b285-bf645a82121e" version = "0.5.8" -[[deps.GettextRuntime_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll"] -git-tree-sha1 = "45288942190db7c5f760f59c04495064eedf9340" -registries = "General" -uuid = "b0724c58-0f36-5564-988d-3bb0596ebc4a" -version = "0.22.4+0" - -[[deps.Ghostscript_jll]] -deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Zlib_jll"] -git-tree-sha1 = "38044a04637976140074d0b0621c1edf0eb531fd" -registries = "General" -uuid = "61579ee1-b43e-5ca0-a5da-69d92c66a64b" -version = "9.55.1+0" - [[deps.Git]] deps = ["Git_LFS_jll", "Git_jll", "JLLWrappers", "OpenSSH_jll"] git-tree-sha1 = "824a1890086880696fc908fe12a17bcf61738bd8" @@ -1087,20 +887,6 @@ registries = "General" uuid = "f8c6e375-362e-5223-8a59-34ff63f689eb" version = "2.56.0+0" -[[deps.Glib_jll]] -deps = ["Artifacts", "GettextRuntime_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE2_jll", "Zlib_jll"] -git-tree-sha1 = "ea9f1fb37ff10597353b56f3ba0ebd525acef6e0" -registries = "General" -uuid = "7746bdde-850d-59dc-9ae8-88ece973131d" -version = "2.90.1+0" - -[[deps.Graphite2_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "69ffb934a5c5b7e086a0b4fee3427db2556fba6e" -registries = "General" -uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472" -version = "1.3.16+0" - [[deps.HDF5]] deps = ["Compat", "HDF5_jll", "Libdl", "MPIPreferences", "Mmap", "Preferences", "Printf", "Random", "Requires", "UUIDs"] git-tree-sha1 = "26e37af34e8ccb7a8358bd8de1619c7bcb526738" @@ -1116,24 +902,10 @@ version = "0.17.4" [[deps.HDF5_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LibCURL_jll", "Libdl", "MPIABI_jll", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "OpenSSL_jll", "TOML", "Zlib_jll", "aws_c_s3_jll", "dlfcn_win32_jll", "libaec_jll", "mpif_jll"] -git-tree-sha1 = "58d79d3a61d2fdeda667fead57546ae22a20b27a" +git-tree-sha1 = "d791a1c4c3c8f383b248a799be31105d00310204" registries = "General" uuid = "0234f1f7-429e-5d53-9886-15a909be8d59" -version = "2.2.3+0" - -[[deps.HTTP]] -deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "PrecompileTools", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"] -git-tree-sha1 = "49a7885f0e1c0ecabd51b25f2fd14f26357777e0" -registries = "General" -uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3" -version = "1.11.1" - -[[deps.HarfBuzz_jll]] -deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll"] -git-tree-sha1 = "f700c32c1e9e11b576592043f270e6b33868a56f" -registries = "General" -uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566" -version = "100.15000.0+0" +version = "2.2.4+0" [[deps.HashArrayMappedTries]] git-tree-sha1 = "2eaa69a7cab70a52b9687c8bf950a5a93ec895ae" @@ -1142,21 +914,16 @@ uuid = "076d061b-32b6-4027-95e0-9a2c6f6d7e74" version = "0.2.0" [[deps.HelpPlots]] -git-tree-sha1 = "07d389d81ca31e42d68bd88cfa1e900a63b7d378" +git-tree-sha1 = "4b09f61ba5ab9242250855d44cf8747d0d43b981" registries = "General" uuid = "5880b2e8-ad0a-48c9-bacd-30dc9c1357e0" -version = "1.1.0" -weakdeps = ["Plots"] +version = "1.1.1" [deps.HelpPlots.extensions] HelpPlotsExt = "Plots" -[[deps.Hiccup]] -deps = ["MacroTools", "Test"] -git-tree-sha1 = "6187bb2d5fcbb2007c39e7ac53308b0d371124bd" -registries = "General" -uuid = "9fb69e20-1954-56bb-a84f-559cc56a8ff7" -version = "0.2.2" + [deps.HelpPlots.weakdeps] + Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" [[deps.Hwloc_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "XML2_jll", "Xorg_libpciaccess_jll"] @@ -1167,10 +934,10 @@ version = "2.15.0+0" [[deps.IMASdd]] deps = ["AbstractTrees", "AdaptiveArrayPools", "CoordinateConventions", "DataInterpolations", "Dates", "FastInterpolations", "HDF5", "InteractiveUtils", "JSON", "Memoization", "OrderedCollections", "PrecompileTools", "Preferences", "Printf", "StaticArraysCore", "ThreadSafeDicts", "TimerOutputs"] -git-tree-sha1 = "2284c64e11d8fb6225971bcc367c67bae51923f5" +git-tree-sha1 = "34f3a6338f95ee5df3ca455a96f416b0a2044eb0" registries = "General" uuid = "c5a45a97-b3f9-491c-b9a7-aa88c3bc0067" -version = "8.6.2" +version = "8.6.3" [[deps.IOCapture]] deps = ["Logging", "Random"] @@ -1185,20 +952,6 @@ registries = "General" uuid = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173" version = "0.1.1" -[[deps.InlineStrings]] -git-tree-sha1 = "06b65886c7577a3784d616e29f1302c2e36e389d" -registries = "General" -uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48" -version = "1.4.6" - - [deps.InlineStrings.extensions] - ArrowTypesExt = "ArrowTypes" - ParsersExt = "Parsers" - - [deps.InlineStrings.weakdeps] - ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd" - Parsers = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" - [[deps.IntelOpenMP_jll]] deps = ["Artifacts", "JLLWrappers", "LazyArtifacts", "Libdl"] git-tree-sha1 = "ec1debd61c300961f98064cfb21287613ad7f303" @@ -1251,12 +1004,6 @@ weakdeps = ["Dates", "Test"] InverseFunctionsDatesExt = "Dates" InverseFunctionsTestExt = "Test" -[[deps.InvertedIndices]] -git-tree-sha1 = "6da3c4316095de0f5ee2ebd875df8721e7e0bdbe" -registries = "General" -uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f" -version = "1.3.1" - [[deps.IrrationalConstants]] git-tree-sha1 = "d22e8bdb3f03c300a103ff23c0dd5a654c6701de" registries = "General" @@ -1275,17 +1022,12 @@ git-tree-sha1 = "9ce2e7c49ae4a7035b7d60db3a553b1f6c16875d" registries = "General" uuid = "033835bb-8acc-5ee8-8aae-3f567f8a3819" version = "0.6.7" -weakdeps = ["UnPack"] [deps.JLD2.extensions] UnPackExt = "UnPack" -[[deps.JLFzf]] -deps = ["REPL", "Random", "fzf_jll"] -git-tree-sha1 = "82f7acdc599b65e0f8ccd270ffa1467c21cb647b" -registries = "General" -uuid = "1019f520-868f-41f5-a6de-eb00f4b6a39c" -version = "0.1.11" + [deps.JLD2.weakdeps] + UnPack = "3a884ed6-31ef-47d7-9d2a-63182c4928ed" [[deps.JLLWrappers]] deps = ["Artifacts", "Preferences"] @@ -1294,19 +1036,20 @@ registries = "General" uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" version = "1.8.0" -[[deps.JSExpr]] -deps = ["JSON", "MacroTools", "Observables", "WebIO"] -git-tree-sha1 = "b413a73785b98474d8af24fd4c8a975e31df3658" -registries = "General" -uuid = "97c1335a-c9c5-57fe-bc5d-ec35cebe8660" -version = "0.5.4" - [[deps.JSON]] -deps = ["Dates", "Mmap", "Parsers", "Unicode"] -git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" +deps = ["Dates", "Logging", "Parsers", "PrecompileTools", "StructUtils", "UUIDs", "Unicode"] +git-tree-sha1 = "06d90f699285d055e34f0b014636a75a17ff2252" registries = "General" uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" -version = "0.21.4" +version = "1.13.4" + + [deps.JSON.extensions] + JSONArrowExt = ["ArrowTypes"] + JSONSerializationExt = ["Serialization"] + + [deps.JSON.weakdeps] + ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd" + Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" [[deps.Jieko]] deps = ["ExproniconLite"] @@ -1315,25 +1058,11 @@ registries = "General" uuid = "ae98c720-c025-4a4a-838c-29b094483192" version = "0.2.1" -[[deps.JpegTurbo_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "037babc10853eeb8e585418922246cb97b8e5b74" -registries = "General" -uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8" -version = "3.2.0+1" - [[deps.JuliaSyntaxHighlighting]] deps = ["StyledStrings"] uuid = "ac6e5ff7-fb65-4e79-a425-ec3bc9c03011" version = "1.12.0" -[[deps.Kaleido_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "43032da5832754f58d14a91ffbe86d5f176acda9" -registries = "General" -uuid = "f7e6163d-2fa5-5f23-b69c-1db539e41963" -version = "0.2.1+0" - [[deps.Krylov]] deps = ["LinearAlgebra", "Printf", "SparseArrays"] git-tree-sha1 = "b13076e4fff6ea0d0a5ddd9b939a11ca2027755a" @@ -1341,52 +1070,12 @@ registries = "General" uuid = "ba0b0d4f-ebba-5204-a429-3ac8c609bfb7" version = "0.10.12" -[[deps.LAME_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "059aabebaa7c82ccb853dd4a0ee9d17796f7e1bc" -registries = "General" -uuid = "c1c5ebd0-6772-5130-a774-d5fcae4a789d" -version = "3.100.3+0" - -[[deps.LERC_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "39bca05343661c347aae0bca57a5994a0bf4f08d" -registries = "General" -uuid = "88015f11-f218-50d7-93a8-a6af411a945d" -version = "4.2.0+0" - -[[deps.LLVMOpenMP_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "e5b100780d4d30d63b4618d7930d48af409c1772" -registries = "General" -uuid = "1d63c593-3942-5779-bab2-d838dc0a180e" -version = "23.1.1+0" - [[deps.LaTeXStrings]] git-tree-sha1 = "f88f3ccef05a6a72a0cf0ed417c8fd68530f4ab2" registries = "General" uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" version = "1.4.1" -[[deps.Latexify]] -deps = ["Format", "Ghostscript_jll", "InteractiveUtils", "LaTeXStrings", "MacroTools", "Markdown", "OrderedCollections", "Requires"] -git-tree-sha1 = "df7566479bd64f20bd16b09960145e70160ffb3b" -registries = "General" -uuid = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" -version = "0.16.12" - - [deps.Latexify.extensions] - DataFramesExt = "DataFrames" - SparseArraysExt = "SparseArrays" - SymEngineExt = "SymEngine" - TectonicExt = "tectonic_jll" - - [deps.Latexify.weakdeps] - DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" - SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - SymEngine = "123dc426-2d89-5057-bbad-38513e3affd8" - tectonic_jll = "d7dd28d6-a5e6-559c-9131-7eb760cdacc5" - [[deps.LayoutPointers]] deps = ["ArrayInterface", "LinearAlgebra", "ManualMemory", "SIMDTypes", "Static", "StaticArrayInterface"] git-tree-sha1 = "a9eaadb366f5493a5654e843864c13d8b107548c" @@ -1400,13 +1089,6 @@ registries = "General" uuid = "0e77f7df-68c5-4e49-93ce-4cd80f5598bf" version = "1.3.0" -[[deps.Lazy]] -deps = ["MacroTools"] -git-tree-sha1 = "1370f8202dac30758f3c345f9909b97f53d87d3f" -registries = "General" -uuid = "50d2b5c4-7a5e-59d5-8109-a42b560f39c0" -version = "0.15.1" - [[deps.LazyArtifacts]] deps = ["Artifacts", "Pkg"] uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3" @@ -1441,20 +1123,6 @@ version = "1.11.104+0" uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" version = "1.11.0" -[[deps.Libffi_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "c8da7e6a91781c41a863611c7e966098d783c57a" -registries = "General" -uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490" -version = "3.4.7+0" - -[[deps.Libglvnd_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll", "Xorg_libXext_jll"] -git-tree-sha1 = "d36c21b9e7c172a44a10484125024495e2625ac0" -registries = "General" -uuid = "7e76a0d4-f3c7-5321-8279-8d96eeed0f29" -version = "1.7.1+1" - [[deps.Libiconv_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] git-tree-sha1 = "be484f5c92fad0bd8acfef35fe017900b0b73809" @@ -1462,27 +1130,6 @@ registries = "General" uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" version = "1.18.0+0" -[[deps.Libmount_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "b8a91037379e1fd661e9b5ff3d7a770424b1f9f2" -registries = "General" -uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9" -version = "2.42.4+0" - -[[deps.Libtiff_jll]] -deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"] -git-tree-sha1 = "aebd334d06cee9f24cea70bd19a39749daf73881" -registries = "General" -uuid = "89763e89-9b03-5906-acba-b20f662cd828" -version = "4.7.3+0" - -[[deps.Libuuid_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "9e887184f306b28dbfcff88cefbb0ec80f1aa9e0" -registries = "General" -uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700" -version = "2.42.4+0" - [[deps.LineSearch]] deps = ["ADTypes", "CommonSolve", "ConcreteStructs", "FastClosures", "LinearAlgebra", "MaybeInplace", "PrecompileTools", "SciMLBase", "SciMLJacobianOperators", "StaticArraysCore"] git-tree-sha1 = "2e05027f5a68891d997fcad60d11ce48d09208d0" @@ -1701,26 +1348,6 @@ weakdeps = ["SparseArrays"] [deps.MaybeInplace.extensions] MaybeInplaceSparseArraysExt = "SparseArrays" -[[deps.MbedTLS]] -deps = ["Dates", "MbedTLS_jll", "MozillaCACerts_jll", "NetworkOptions", "Random", "Sockets"] -git-tree-sha1 = "0e4b139a1f0f0b8c054fcdfd3be5f610cfb8c787" -registries = "General" -uuid = "739be429-bea8-5141-9913-cc70e7f3736d" -version = "1.2.0" - -[[deps.MbedTLS_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "ff69a2b1330bcb730b9ac1ab7dd680176f5896b8" -registries = "General" -uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" -version = "2.28.1010+0" - -[[deps.Measures]] -git-tree-sha1 = "b513cedd20d9c914783d8ad83d08120702bf2c77" -registries = "General" -uuid = "442fdcdd-2543-5da2-b0f3-8c86c306513e" -version = "0.3.3" - [[deps.Memoization]] deps = ["MacroTools"] git-tree-sha1 = "7dbf904fa6c4447bd1f1d316886bfbe29feacf45" @@ -1729,18 +1356,11 @@ uuid = "6fafb56a-5788-4b4e-91ca-c0cea6611c73" version = "0.2.2" [[deps.MicrosoftMPI_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "bc95bf4149bf535c09602e3acdf950d9b4376227" +deps = ["Artifacts", "JLLWrappers", "LazyArtifacts", "Libdl", "MPIPreferences", "TOML"] +git-tree-sha1 = "3350d35d0ebcf7ccee0c66290b8e95d82419ef6f" registries = "General" uuid = "9237b28f-5490-5468-be7b-bb81f5f5e6cf" -version = "10.1.4+3" - -[[deps.Missings]] -deps = ["DataAPI"] -git-tree-sha1 = "ec4f7fbeab05d7747bdf98eb74d130a2a2ed298d" -registries = "General" -uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28" -version = "1.2.0" +version = "10.1.5+0" [[deps.Mmap]] uuid = "a63ad114-7e13-5084-954f-fe012c677804" @@ -1764,20 +1384,6 @@ registries = "General" uuid = "46d2c3a1-f734-5fdb-9937-b9b9aeba4221" version = "0.2.7" -[[deps.Mustache]] -deps = ["DataFrames", "Printf", "Tables"] -git-tree-sha1 = "7437c911e62f50e7a043a774ebdc0dbd780d9fe5" -registries = "General" -uuid = "ffc61752-8dc7-55ee-8c37-f3e9cdd09e70" -version = "1.1.0" - -[[deps.Mux]] -deps = ["AssetRegistry", "Base64", "HTTP", "Hiccup", "MbedTLS", "Pkg", "Sockets"] -git-tree-sha1 = "7295d849103ac4fcbe3b2e439f229c5cc77b9b69" -registries = "General" -uuid = "a975b10e-0019-58db-a62f-e48ff68538c9" -version = "1.0.2" - [[deps.NLSolversBase]] deps = ["ADTypes", "DifferentiationInterface", "FiniteDiff", "LinearAlgebra"] git-tree-sha1 = "f96d38936d92d610318dec4d3b5ef37b0373c20f" @@ -1892,19 +1498,6 @@ weakdeps = ["ForwardDiff"] [deps.NonlinearSolveSpectralMethods.extensions] NonlinearSolveSpectralMethodsForwardDiffExt = "ForwardDiff" -[[deps.Observables]] -git-tree-sha1 = "7438a59546cf62428fc9d1bc94729146d37a7225" -registries = "General" -uuid = "510215fc-4207-5dde-b226-833fc4488ee2" -version = "0.5.5" - -[[deps.Ogg_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "b6aa4566bb7ae78498a5e68943863fa8b5231b59" -registries = "General" -uuid = "e7412a2a-1a6e-54c0-be00-318e2571c051" -version = "1.3.6+0" - [[deps.OpenBLASConsistentFPCSR_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"] git-tree-sha1 = "38a93f17e431141c6470bb67a88952a7c4f0e928" @@ -1936,13 +1529,6 @@ registries = "General" uuid = "9bd350c2-7e96-507f-8002-3f2e150b4e1b" version = "10.6.1+0" -[[deps.OpenSSL]] -deps = ["BitFlags", "Dates", "MozillaCACerts_jll", "NetworkOptions", "OpenSSL_jll", "Sockets"] -git-tree-sha1 = "f1cffefcdc73bbf17875e7c1cb062ecb000c1552" -registries = "General" -uuid = "4d8831e6-92b7-49fb-bdf8-b643e874388c" -version = "1.7.1" - [[deps.OpenSSL_jll]] deps = ["Artifacts", "Libdl"] uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" @@ -1955,18 +1541,11 @@ registries = "General" uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e" version = "0.5.6+0" -[[deps.Opus_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "e2bb57a313a74b8104064b7efd01406c0a50d2ff" -registries = "General" -uuid = "91d4177d-7536-5919-b921-800302f37372" -version = "1.6.1+0" - [[deps.OrderedCollections]] -git-tree-sha1 = "94ba93778373a53bfd5a0caaf7d809c445292ff4" +git-tree-sha1 = "b079a6affebde167dad8ffe0d670b53917fbcb68" registries = "General" uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" -version = "1.8.2" +version = "2.0.3" [[deps.OrdinaryDiffEq]] deps = ["ADTypes", "Adapt", "ArrayInterface", "CommonSolve", "DataStructures", "DiffEqBase", "DocStringExtensions", "EnumX", "ExponentialUtilities", "FastBroadcast", "FastClosures", "FillArrays", "FiniteDiff", "ForwardDiff", "FunctionWrappersWrappers", "InteractiveUtils", "LineSearches", "LinearAlgebra", "LinearSolve", "Logging", "MacroTools", "MuladdMacro", "NonlinearSolve", "OrdinaryDiffEqAdamsBashforthMoulton", "OrdinaryDiffEqBDF", "OrdinaryDiffEqCore", "OrdinaryDiffEqDefault", "OrdinaryDiffEqDifferentiation", "OrdinaryDiffEqExplicitRK", "OrdinaryDiffEqExponentialRK", "OrdinaryDiffEqExtrapolation", "OrdinaryDiffEqFIRK", "OrdinaryDiffEqFeagin", "OrdinaryDiffEqFunctionMap", "OrdinaryDiffEqHighOrderRK", "OrdinaryDiffEqIMEXMultistep", "OrdinaryDiffEqLinear", "OrdinaryDiffEqLowOrderRK", "OrdinaryDiffEqLowStorageRK", "OrdinaryDiffEqNonlinearSolve", "OrdinaryDiffEqNordsieck", "OrdinaryDiffEqPDIRK", "OrdinaryDiffEqPRK", "OrdinaryDiffEqQPRK", "OrdinaryDiffEqRKN", "OrdinaryDiffEqRosenbrock", "OrdinaryDiffEqSDIRK", "OrdinaryDiffEqSSPRK", "OrdinaryDiffEqStabilizedIRK", "OrdinaryDiffEqStabilizedRK", "OrdinaryDiffEqSymplecticRK", "OrdinaryDiffEqTsit5", "OrdinaryDiffEqVerner", "Polyester", "PreallocationTools", "PrecompileTools", "Preferences", "RecursiveArrayTools", "Reexport", "SciMLBase", "SciMLOperators", "SciMLStructures", "SimpleNonlinearSolve", "SparseArrays", "Static", "StaticArrayInterface", "StaticArrays", "TruncatedStacktraces"] @@ -2202,40 +1781,12 @@ deps = ["Artifacts", "Libdl"] uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" version = "10.47.0+0" -[[deps.Pango_jll]] -deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "FriBidi_jll", "Glib_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl"] -git-tree-sha1 = "1912a9f1b9ca55005b03ba075f8e19993583e237" -registries = "General" -uuid = "36c8627f-9965-5494-a995-c6b170f724f3" -version = "1.58.2+0" - -[[deps.Parameters]] -deps = ["OrderedCollections", "UnPack"] -git-tree-sha1 = "34c0e9ad262e5f7fc75b10a9952ca7692cfc5fbe" -registries = "General" -uuid = "d96e819e-fc66-5662-9728-84c9c7592b0a" -version = "0.12.3" - [[deps.Parsers]] -deps = ["Dates", "PrecompileTools", "UUIDs"] -git-tree-sha1 = "ba0dc8a8a67cacac4842631f960c046e4e563675" +deps = ["Dates", "PrecompileTools"] +git-tree-sha1 = "e5ae52d1f4f19a9591d615cb0f1ef8d415b40933" registries = "General" uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "2.8.8" - -[[deps.Pidfile]] -deps = ["FileWatching", "Test"] -git-tree-sha1 = "2d8aaf8ee10df53d0dfb9b8ee44ae7c04ced2b03" -registries = "General" -uuid = "fa939f87-e72e-5be4-a000-7fc836dbe307" -version = "1.3.0" - -[[deps.Pixman_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "Libdl"] -git-tree-sha1 = "e4a6721aa89e62e5d4217c0b21bd714263779dda" -registries = "General" -uuid = "30392449-352a-5448-841d-b1acce4e97dc" -version = "0.46.4+0" +version = "3.0.6" [[deps.Pkg]] deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "Zstd_jll", "p7zip_jll"] @@ -2246,86 +1797,6 @@ weakdeps = ["REPL"] [deps.Pkg.extensions] REPLExt = "REPL" -[[deps.PlotThemes]] -deps = ["PlotUtils", "Statistics"] -git-tree-sha1 = "41031ef3a1be6f5bbbf3e8073f210556daeae5ca" -registries = "General" -uuid = "ccf2f8ad-2431-5c83-bf29-c5338b663b6a" -version = "3.3.0" - -[[deps.PlotUtils]] -deps = ["ColorSchemes", "Colors", "Dates", "PrecompileTools", "Printf", "Reexport", "Statistics"] -git-tree-sha1 = "ddcc36fd83fb4cd459a53c891981697bde0d3de6" -registries = "General" -uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043" -version = "1.5.1" - -[[deps.PlotlyBase]] -deps = ["ColorSchemes", "Colors", "Dates", "DelimitedFiles", "DocStringExtensions", "JSON", "LaTeXStrings", "Logging", "Parameters", "Pkg", "REPL", "Requires", "Statistics", "UUIDs"] -git-tree-sha1 = "6256ab3ee24ef079b3afa310593817e069925eeb" -registries = "General" -uuid = "a03496cd-edff-5a9b-9e67-9cda94a718b5" -version = "0.8.23" - - [deps.PlotlyBase.extensions] - DataFramesExt = "DataFrames" - DistributionsExt = "Distributions" - IJuliaExt = "IJulia" - JSON3Ext = "JSON3" - - [deps.PlotlyBase.weakdeps] - DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" - Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" - IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" - JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" - -[[deps.PlotlyJS]] -deps = ["Base64", "Blink", "DelimitedFiles", "JSExpr", "JSON", "Kaleido_jll", "Markdown", "Pkg", "PlotlyBase", "PlotlyKaleido", "REPL", "Reexport", "Requires", "WebIO"] -git-tree-sha1 = "4bf6c08295346efba58bb2d5f8c52984efed7f27" -registries = "General" -uuid = "f0f68f2c-4968-5e81-91da-67840de0976a" -version = "0.18.18" - - [deps.PlotlyJS.extensions] - CSVExt = "CSV" - DataFramesExt = ["DataFrames", "CSV"] - IJuliaExt = "IJulia" - JSON3Ext = "JSON3" - - [deps.PlotlyJS.weakdeps] - CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" - DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" - IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" - JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" - -[[deps.PlotlyKaleido]] -deps = ["Artifacts", "Base64", "JSON", "Kaleido_jll"] -git-tree-sha1 = "c7a270d11881c0709052f8e3def3d60d551e99f3" -registries = "General" -uuid = "f2990250-8cf9-495f-b13a-cce12b45703c" -version = "2.3.1" - -[[deps.Plots]] -deps = ["Base64", "Contour", "Dates", "Downloads", "FFMPEG", "FixedPointNumbers", "GR", "JLFzf", "JSON", "LaTeXStrings", "Latexify", "LinearAlgebra", "Measures", "NaNMath", "Pkg", "PlotThemes", "PlotUtils", "PrecompileTools", "Printf", "REPL", "Random", "RecipesBase", "RecipesPipeline", "Reexport", "RelocatableFolders", "Requires", "Scratch", "Showoff", "SparseArrays", "Statistics", "StatsBase", "TOML", "UUIDs", "UnicodeFun", "Unzip"] -git-tree-sha1 = "83bd514e8ff16b5858ac54c53fa0bcf6002a3b00" -registries = "General" -uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" -version = "1.41.7" - - [deps.Plots.extensions] - FileIOExt = "FileIO" - GeometryBasicsExt = "GeometryBasics" - IJuliaExt = "IJulia" - ImageInTerminalExt = "ImageInTerminal" - UnitfulExt = "Unitful" - - [deps.Plots.weakdeps] - FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" - GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326" - IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" - ImageInTerminal = "d8c32880-2388-543b-8c61-d9f865259254" - Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" - [[deps.Polyester]] deps = ["ArrayInterface", "BitTwiddlingConvenienceFunctions", "CPUSummary", "IfElse", "ManualMemory", "PolyesterWeave", "Static", "StaticArrayInterface", "StrideArraysCore", "ThreadingUtilities"] git-tree-sha1 = "16bbc30b5ebea91e9ce1671adc03de2832cff552" @@ -2340,13 +1811,6 @@ registries = "General" uuid = "1d0040c9-8b98-4ee7-8388-3f51789ca0ad" version = "0.2.2" -[[deps.PooledArrays]] -deps = ["DataAPI", "Future"] -git-tree-sha1 = "36d8b4b899628fb92c2749eb488d884a926614d3" -registries = "General" -uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720" -version = "1.4.3" - [[deps.PreallocationTools]] deps = ["Adapt", "ArrayInterface", "PrecompileTools", "SciMLPublic"] git-tree-sha1 = "cd00e28071a75c98664663f87c2ae447d25c0503" @@ -2380,10 +1844,10 @@ version = "1.6.1" [[deps.PrettyTables]] deps = ["Crayons", "LaTeXStrings", "Markdown", "PrecompileTools", "Printf", "REPL", "Reexport", "StringManipulation", "StyledStrings", "Tables"] -git-tree-sha1 = "99fd8f32ab6728ff205cddd24e2d9ea6cbb498d3" +git-tree-sha1 = "eeac4c9337f876c3c32b8da732141953567f57c9" registries = "General" uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" -version = "3.5.0" +version = "3.5.1" [deps.PrettyTables.extensions] PrettyTablesExcelExt = "XLSX" @@ -2400,12 +1864,6 @@ deps = ["Unicode"] uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" version = "1.11.0" -[[deps.PtrArrays]] -git-tree-sha1 = "4fbbafbc6251b883f4d2705356f3641f3652a7fe" -registries = "General" -uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d" -version = "1.4.0" - [[deps.PureKLU]] deps = ["LinearAlgebra", "PrecompileTools", "SparseArrays"] git-tree-sha1 = "ad0e84647fe7b3c2ae02aaeace1522139aaf6a27" @@ -2417,41 +1875,6 @@ weakdeps = ["ForwardDiff"] [deps.PureKLU.extensions] PureKLUForwardDiffExt = "ForwardDiff" -[[deps.Qt6Base_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "Fontconfig_jll", "Glib_jll", "JLLWrappers", "Libdl", "Libglvnd_jll", "OpenSSL_jll", "Vulkan_Loader_jll", "Xorg_libSM_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Xorg_libxcb_jll", "Xorg_xcb_util_cursor_jll", "Xorg_xcb_util_image_jll", "Xorg_xcb_util_keysyms_jll", "Xorg_xcb_util_renderutil_jll", "Xorg_xcb_util_wm_jll", "Zlib_jll", "libinput_jll", "xkbcommon_jll"] -git-tree-sha1 = "144895f6166994730ee7ff8113b981fc360638f1" -registries = "General" -uuid = "c0090381-4147-56d7-9ebc-da0b1113ec56" -version = "6.10.2+2" - -[[deps.Qt6Declarative_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Qt6Base_jll", "Qt6ShaderTools_jll", "Qt6Svg_jll"] -git-tree-sha1 = "159d253ab126d5b29230cf53521899bea4ef4648" -registries = "General" -uuid = "629bc702-f1f5-5709-abd5-49b8460ea067" -version = "6.10.2+2" - -[[deps.Qt6ShaderTools_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Qt6Base_jll"] -git-tree-sha1 = "4d85eedf69d875982c46643f6b4f66919d7e157b" -registries = "General" -uuid = "ce943373-25bb-56aa-8eca-768745ed7b5a" -version = "6.10.2+1" - -[[deps.Qt6Svg_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Qt6Base_jll"] -git-tree-sha1 = "81587ff5ff25a4e1115ce191e36285ede0334c9d" -registries = "General" -uuid = "6de9746b-f93d-5813-b365-ba18ad4a9cf3" -version = "6.10.2+0" - -[[deps.Qt6Wayland_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Qt6Base_jll", "Qt6Declarative_jll"] -git-tree-sha1 = "672c938b4b4e3e0169a07a5f227029d4905456f2" -registries = "General" -uuid = "e99dba38-086e-5de3-a5b1-6e4c66e897c3" -version = "6.10.2+1" - [[deps.QuadGK]] deps = ["DataStructures", "LinearAlgebra"] git-tree-sha1 = "5e8e8b0ab68215d7a2b14b9921a946fee794749e" @@ -2493,13 +1916,6 @@ registries = "General" uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" version = "1.4.0" -[[deps.RecipesPipeline]] -deps = ["Dates", "NaNMath", "PlotUtils", "PrecompileTools", "RecipesBase"] -git-tree-sha1 = "45cf9fd0ca5839d06ef333c8201714e888486342" -registries = "General" -uuid = "01d81517-befc-4cb6-b9ec-a95719d0359c" -version = "0.6.12" - [[deps.RecursiveArrayTools]] deps = ["Adapt", "ArrayInterface", "DocStringExtensions", "GPUArraysCore", "LinearAlgebra", "PrecompileTools", "RecipesBase", "StaticArraysCore", "SymbolicIndexingInterface"] git-tree-sha1 = "d0282d612f22dcad7b81cf487b746e63aa2a6709" @@ -2550,13 +1966,6 @@ registries = "General" uuid = "2792f1a3-b283-48e8-9a74-f99dce5104f3" version = "0.1.0" -[[deps.RelocatableFolders]] -deps = ["SHA", "Scratch"] -git-tree-sha1 = "ffdaf70d81cf6ff22c2b6e733c900c3321cab864" -registries = "General" -uuid = "05181044-ff0b-4ac5-8273-598c1e38db00" -version = "1.0.1" - [[deps.Requires]] deps = ["UUIDs"] git-tree-sha1 = "62389eeff14780bfe55195b7204c0d8738436d64" @@ -2678,10 +2087,10 @@ version = "1.10.1" [[deps.SciMLOperators]] deps = ["Accessors", "Adapt", "ArrayInterface", "DocStringExtensions", "LinearAlgebra", "PrecompileTools", "SciMLPublic"] -git-tree-sha1 = "2712b6eeef282b5cd8c38660e68c001a4b9b24e2" +git-tree-sha1 = "3feb8a7bce88bb3ef051a4eda8106004ad040b6f" registries = "General" uuid = "c0aeaf25-5076-4817-a8d5-81caf7dfa961" -version = "1.30.3" +version = "1.30.4" [deps.SciMLOperators.extensions] SciMLOperatorsLoopVectorizationExt = "LoopVectorization" @@ -2712,42 +2121,15 @@ registries = "General" uuid = "7e506255-f358-4e82-b7e4-beb19740aa63" version = "1.6.2" -[[deps.Scratch]] -deps = ["Dates"] -git-tree-sha1 = "9b81b8393e50b7d4e6d0a9f14e192294d3b7c109" -registries = "General" -uuid = "6c6a2e73-6563-6170-7368-637461726353" -version = "1.3.0" - -[[deps.SentinelArrays]] -deps = ["Dates", "Random"] -git-tree-sha1 = "084c47c7c5ce5cfecefa0a98dff69eb3646b5a80" -registries = "General" -uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c" -version = "1.4.10" - [[deps.Serialization]] uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" version = "1.11.0" [[deps.Setfield]] deps = ["ConstructionBase", "Future", "MacroTools", "StaticArraysCore"] -git-tree-sha1 = "1a17a79e9dffc0d188c81c7ea58e8cc79752df7e" +git-tree-sha1 = "89880a2cca5cabc68c9023f58e544beda1d2fc1f" registries = "General" uuid = "efcf1570-3423-57d1-acb7-fd33fddbac46" -version = "1.1.3" - -[[deps.Showoff]] -deps = ["Dates"] -git-tree-sha1 = "8238217340ad0aaabe11afe39c1098b5bc9f4c8e" -registries = "General" -uuid = "992d4aef-0814-514b-bc4d-f2e9a6c4116f" -version = "1.1.1" - -[[deps.SimpleBufferStream]] -git-tree-sha1 = "f305871d2f381d21527c770d4788c06c097c9bc1" -registries = "General" -uuid = "777ac1f9-54b0-4bf8-805c-2214025038e7" version = "1.2.0" [[deps.SimpleNonlinearSolve]] @@ -2771,13 +2153,6 @@ version = "2.12.0" uuid = "6462fe0b-24de-5631-8697-dd941f90decc" version = "1.11.0" -[[deps.SortingAlgorithms]] -deps = ["DataStructures"] -git-tree-sha1 = "13cd91cc9be159e3f4d95b857fa2aa383b53772a" -registries = "General" -uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c" -version = "1.2.3" - [[deps.SparseArrays]] deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" @@ -2886,20 +2261,6 @@ weakdeps = ["SparseArrays"] [deps.Statistics.extensions] SparseArraysExt = ["SparseArrays"] -[[deps.StatsAPI]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "178ed29fd5b2a2cfc3bd31c13375ae925623ff36" -registries = "General" -uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0" -version = "1.8.0" - -[[deps.StatsBase]] -deps = ["AliasTables", "DataAPI", "DataStructures", "IrrationalConstants", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"] -git-tree-sha1 = "adb9da019510162e67a4493fc235c23203d8b09e" -registries = "General" -uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" -version = "0.34.13" - [[deps.StrideArraysCore]] deps = ["ArrayInterface", "CloseOpenIntervals", "IfElse", "LayoutPointers", "LinearAlgebra", "ManualMemory", "SIMDTypes", "Static", "StaticArrayInterface", "ThreadingUtilities"] git-tree-sha1 = "5316097111523c9a970596a5b33cfea5f92e8581" @@ -2914,6 +2275,29 @@ registries = "General" uuid = "892a3eda-7b42-436c-8928-eab12a02cf0e" version = "0.6.1" +[[deps.StructUtils]] +deps = ["Dates", "UUIDs"] +git-tree-sha1 = "b04bfab37484defe300b2f9ed64469abab2431ff" +registries = "General" +uuid = "ec057cc2-7a8d-4b58-b3b3-92acb9f63b42" +version = "2.12.0" + + [deps.StructUtils.extensions] + StructUtilsDBInterfaceExt = ["DBInterface", "Tables"] + StructUtilsDataFramesExt = ["DataFrames"] + StructUtilsLazilyInitializedFieldsExt = ["LazilyInitializedFields"] + StructUtilsMeasurementsExt = ["Measurements"] + StructUtilsStaticArraysCoreExt = ["StaticArraysCore"] + StructUtilsTablesExt = ["Tables"] + + [deps.StructUtils.weakdeps] + DBInterface = "a10d1c49-ce27-4219-8d33-6db1a4562965" + DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" + LazilyInitializedFields = "0e77f7df-68c5-4e49-93ce-4cd80f5598bf" + Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7" + StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" + Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" + [[deps.StyledStrings]] uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b" version = "1.11.0" @@ -2925,10 +2309,10 @@ version = "7.10.1+0" [[deps.SymbolicIndexingInterface]] deps = ["Accessors", "ArrayInterface", "PrecompileTools", "RuntimeGeneratedFunctions", "StaticArraysCore"] -git-tree-sha1 = "7eb6da9656581ac9fbffaef3ef7950a090a5002a" +git-tree-sha1 = "42dd4c193a1bacea5b6adf78b6224de907b06351" registries = "General" uuid = "2efcf032-c050-4f8e-a9bb-153293bab1f5" -version = "0.3.55" +version = "0.3.56" weakdeps = ["PrettyTables"] [deps.SymbolicIndexingInterface.extensions] @@ -2958,13 +2342,6 @@ deps = ["ArgTools", "SHA"] uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" version = "1.10.0" -[[deps.TensorCore]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "1feb45f88d133a655e001435632f019a9a1bcdb6" -registries = "General" -uuid = "62fd8b95-f654-4bbd-a8a5-9c27f68ccd50" -version = "0.1.1" - [[deps.Test]] deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" @@ -3009,76 +2386,15 @@ registries = "General" uuid = "781d530d-4396-4725-bb49-402e4bee1e77" version = "1.4.0" -[[deps.URIs]] -deps = ["Serialization"] -git-tree-sha1 = "50eda8011f098869a76da69a524ff59e0298711a" -registries = "General" -uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" -version = "1.7.2" - [[deps.UUIDs]] deps = ["Random", "SHA"] uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" version = "1.11.0" -[[deps.UnPack]] -git-tree-sha1 = "387c1f73762231e86e0c9c5443ce3b4a0a9a0c2b" -registries = "General" -uuid = "3a884ed6-31ef-47d7-9d2a-63182c4928ed" -version = "1.0.2" - [[deps.Unicode]] uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" version = "1.11.0" -[[deps.UnicodeFun]] -deps = ["REPL"] -git-tree-sha1 = "53915e50200959667e78a92a418594b428dffddf" -registries = "General" -uuid = "1cfade01-22cf-5700-b092-accc4b62d6e1" -version = "0.4.1" - -[[deps.Unzip]] -git-tree-sha1 = "ca0969166a028236229f63514992fc073799bb78" -registries = "General" -uuid = "41fe7b60-77ed-43a1-b4f0-825fd5a5650d" -version = "0.2.0" - -[[deps.Vulkan_Loader_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Wayland_jll", "Xorg_libX11_jll", "Xorg_libXrandr_jll", "xkbcommon_jll"] -git-tree-sha1 = "2f0486047a07670caad3a81a075d2e518acc5c59" -registries = "General" -uuid = "a44049a8-05dd-5a78-86c9-5fde0876e88c" -version = "1.3.243+0" - -[[deps.Wayland_jll]] -deps = ["Artifacts", "EpollShim_jll", "Expat_jll", "JLLWrappers", "Libdl", "Libffi_jll"] -git-tree-sha1 = "96478df35bbc2f3e1e791bc7a3d0eeee559e60e9" -registries = "General" -uuid = "a2964d1f-97da-50d4-b82a-358c7fce9d89" -version = "1.24.0+0" - -[[deps.WebIO]] -deps = ["AssetRegistry", "Base64", "Distributed", "FunctionalCollections", "JSON", "Logging", "Observables", "Pkg", "Random", "Requires", "Sockets", "UUIDs", "WebSockets", "Widgets"] -git-tree-sha1 = "0eef0765186f7452e52236fa42ca8c9b3c11c6e3" -registries = "General" -uuid = "0f1e0344-ec1d-5b48-a673-e5cf874b6c29" -version = "0.8.21" - -[[deps.WebSockets]] -deps = ["Base64", "Dates", "HTTP", "Logging", "Sockets"] -git-tree-sha1 = "4162e95e05e79922e44b9952ccbc262832e4ad07" -registries = "General" -uuid = "104b5d7c-a370-577a-8038-80a2059c5097" -version = "1.6.0" - -[[deps.Widgets]] -deps = ["Colors", "Dates", "Observables", "OrderedCollections"] -git-tree-sha1 = "cab29ed70d9355a28b95b1798007ff3a00eefc91" -registries = "General" -uuid = "cc8bc4a8-27d6-5769-a93b-9d913e69aa62" -version = "0.6.8" - [[deps.XML2_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] git-tree-sha1 = "b826aedca5fce2e3c91904e338c279e5c1a2da74" @@ -3086,97 +2402,6 @@ registries = "General" uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" version = "2.15.4+0" -[[deps.XZ_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "e52eca002a11c30a858185efdfb15311e1c7a6bf" -registries = "General" -uuid = "ffd25f8a-64ca-5728-b0f7-c24cf3aae800" -version = "5.8.4+0" - -[[deps.Xorg_libICE_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "a3ea76ee3f4facd7a64684f9af25310825ee3668" -registries = "General" -uuid = "f67eecfb-183a-506d-b269-f58e52b52d7c" -version = "1.1.2+0" - -[[deps.Xorg_libSM_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libICE_jll"] -git-tree-sha1 = "9c7ad99c629a44f81e7799eb05ec2746abb5d588" -registries = "General" -uuid = "c834827a-8449-5923-a945-d239c165b7dd" -version = "1.2.6+0" - -[[deps.Xorg_libX11_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xtrans_jll"] -git-tree-sha1 = "ce91b54a5f90c4f27df6950706234b34e49cf79e" -registries = "General" -uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc" -version = "1.8.14+0" - -[[deps.Xorg_libXau_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "aa1261ebbac3ccc8d16558ae6799524c450ed16b" -registries = "General" -uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec" -version = "1.0.13+0" - -[[deps.Xorg_libXcursor_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXfixes_jll", "Xorg_libXrender_jll"] -git-tree-sha1 = "6c74ca84bbabc18c4547014765d194ff0b4dc9da" -registries = "General" -uuid = "935fb764-8cf2-53bf-bb30-45bb1f8bf724" -version = "1.2.4+0" - -[[deps.Xorg_libXdmcp_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "52858d64353db33a56e13c341d7bf44cd0d7b309" -registries = "General" -uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05" -version = "1.1.6+0" - -[[deps.Xorg_libXext_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] -git-tree-sha1 = "1a4a26870bf1e5d26cd585e38038d399d7e65706" -registries = "General" -uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3" -version = "1.3.8+0" - -[[deps.Xorg_libXfixes_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] -git-tree-sha1 = "75e00946e43621e09d431d9b95818ee751e6b2ef" -registries = "General" -uuid = "d091e8ba-531a-589c-9de9-94069b037ed8" -version = "6.0.2+0" - -[[deps.Xorg_libXi_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXext_jll", "Xorg_libXfixes_jll"] -git-tree-sha1 = "704274ca960c42049fd9bda089fc4be32af5cdca" -registries = "General" -uuid = "a51aa0fd-4e3c-5386-b890-e753decda492" -version = "1.8.5+0" - -[[deps.Xorg_libXinerama_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXext_jll"] -git-tree-sha1 = "0ba01bc7396896a4ace8aab67db31403c71628f4" -registries = "General" -uuid = "d1454406-59df-5ea1-beac-c340f2130bc3" -version = "1.1.7+0" - -[[deps.Xorg_libXrandr_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXext_jll", "Xorg_libXrender_jll"] -git-tree-sha1 = "6c174ef70c96c76f4c3f4d3cfbe09d018bcd1b53" -registries = "General" -uuid = "ec84b674-ba8e-5d96-8ba1-2a689ba10484" -version = "1.5.6+0" - -[[deps.Xorg_libXrender_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] -git-tree-sha1 = "a54cf54677c9f6eac0d97e9d757c233b56bc21b9" -registries = "General" -uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa" -version = "0.9.13+0" - [[deps.Xorg_libpciaccess_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] git-tree-sha1 = "58972370b81423fc546c56a60ed1a009450177c3" @@ -3184,83 +2409,6 @@ registries = "General" uuid = "a65dc6b1-eb27-53a1-bb3e-dea574b5389e" version = "0.19.0+0" -[[deps.Xorg_libxcb_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXau_jll", "Xorg_libXdmcp_jll"] -git-tree-sha1 = "bfcaf7ec088eaba362093393fe11aa141fa15422" -registries = "General" -uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b" -version = "1.17.1+0" - -[[deps.Xorg_libxkbfile_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] -git-tree-sha1 = "ed756a03e95fff88d8f738ebc2849431bdd4fd1a" -registries = "General" -uuid = "cc61e674-0454-545c-8b26-ed2c68acab7a" -version = "1.2.0+0" - -[[deps.Xorg_xcb_util_cursor_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_image_jll", "Xorg_xcb_util_jll", "Xorg_xcb_util_renderutil_jll"] -git-tree-sha1 = "9750dc53819eba4e9a20be42349a6d3b86c7cdf8" -registries = "General" -uuid = "e920d4aa-a673-5f3a-b3d7-f755a4d47c43" -version = "0.1.6+0" - -[[deps.Xorg_xcb_util_image_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] -git-tree-sha1 = "f4fc02e384b74418679983a97385644b67e1263b" -registries = "General" -uuid = "12413925-8142-5f55-bb0e-6d7ca50bb09b" -version = "0.4.1+0" - -[[deps.Xorg_xcb_util_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll"] -git-tree-sha1 = "68da27247e7d8d8dafd1fcf0c3654ad6506f5f97" -registries = "General" -uuid = "2def613f-5ad1-5310-b15b-b15d46f528f5" -version = "0.4.1+0" - -[[deps.Xorg_xcb_util_keysyms_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] -git-tree-sha1 = "44ec54b0e2acd408b0fb361e1e9244c60c9c3dd4" -registries = "General" -uuid = "975044d2-76e6-5fbe-bf08-97ce7c6574c7" -version = "0.4.1+0" - -[[deps.Xorg_xcb_util_renderutil_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] -git-tree-sha1 = "5b0263b6d080716a02544c55fdff2c8d7f9a16a0" -registries = "General" -uuid = "0d47668e-0667-5a69-a72c-f761630bfb7e" -version = "0.3.10+0" - -[[deps.Xorg_xcb_util_wm_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] -git-tree-sha1 = "f233c83cad1fa0e70b7771e0e21b061a116f2763" -registries = "General" -uuid = "c22f9ab0-d5fe-5066-847c-f4bb1cd4e361" -version = "0.4.2+0" - -[[deps.Xorg_xkbcomp_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxkbfile_jll"] -git-tree-sha1 = "9a5e6e8f1a7c40941465c04840145e527f0d6b4b" -registries = "General" -uuid = "35661453-b289-5fab-8a00-3d9160c6a3a4" -version = "1.5.0+0" - -[[deps.Xorg_xkeyboard_config_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xkbcomp_jll"] -git-tree-sha1 = "df77b22eb77051b3d0ef30aab979684f7482c66f" -registries = "General" -uuid = "33bec58e-1273-512f-9401-5d533626f822" -version = "2.48.0+0" - -[[deps.Xorg_xtrans_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "a63799ff68005991f9d9491b6e95bd3478d783cb" -registries = "General" -uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10" -version = "1.6.0+0" - [[deps.Zlib_jll]] deps = ["Libdl"] uuid = "83775a58-1f1d-513f-b197-d71354ab007a" @@ -3273,66 +2421,66 @@ version = "1.5.7+1" [[deps.aws_c_auth_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_cal_jll", "aws_c_http_jll", "aws_c_sdkutils_jll"] -git-tree-sha1 = "8cab83c96af80a1be968251ce1a0548a7545484d" +git-tree-sha1 = "7945f94b714d20b55aee2510b511afee0ee17ad2" registries = "General" uuid = "2b3700d1-4306-52e2-a478-c162f0c514be" -version = "0.9.6+0" +version = "1.0.1+0" [[deps.aws_c_cal_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_common_jll"] -git-tree-sha1 = "22c0f42f4a1f0dc5dcfa8fd267c4ac407c455e7a" +git-tree-sha1 = "60a8d3fafed6f7ed8b3114793885a03330aa8e51" registries = "General" uuid = "70f11efc-bab2-57f1-b0f3-22aad4e67c4b" -version = "0.9.13+0" +version = "1.0.0+0" [[deps.aws_c_common_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "a759cb9bf456ad792cc7898a81ae333cce9ef02a" +git-tree-sha1 = "f5ce8191ad02a5a4127d37ab5f474aee5cf91ad4" registries = "General" uuid = "73048d1d-b8c4-5092-a58d-866c5e8d1e50" -version = "0.12.6+0" +version = "1.0.3+1" [[deps.aws_c_compression_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_common_jll"] -git-tree-sha1 = "7910c72f45f44afd297c39fe43b99c56d5ed22ec" +git-tree-sha1 = "6efd591b9fae7919266d3b342a18a6e6280850f9" registries = "General" uuid = "73a04cd5-f3d7-5bac-9290-e8adb709f224" -version = "0.3.2+0" +version = "1.0.0+1" [[deps.aws_c_http_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_compression_jll", "aws_c_io_jll"] -git-tree-sha1 = "3fb8685778068de502c72fec5dd8075e037cee15" +git-tree-sha1 = "e4108b4a2201cb9987ddd36714fdcfd572d6bb24" registries = "General" uuid = "3254fc65-9028-534d-aa9d-d76d128babc6" -version = "0.10.15+0" +version = "1.0.0+0" [[deps.aws_c_io_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_cal_jll", "aws_c_common_jll", "s2n_tls_jll"] -git-tree-sha1 = "7e481d474b2087ee8bbf55b81bf9119f21e396d9" +git-tree-sha1 = "b816756403a8db80e32fc23a4a1aae240d2168ca" registries = "General" uuid = "13c41daa-f319-5298-b5eb-5754e0170d52" -version = "0.26.3+0" +version = "1.0.0+0" [[deps.aws_c_s3_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_auth_jll", "aws_c_common_jll", "aws_c_http_jll", "aws_checksums_jll", "s2n_tls_jll"] -git-tree-sha1 = "3e9917ab25114feba657e71be41cad068b9f6595" +git-tree-sha1 = "1ebf5d4fa9c5248c3c1066124ef41d06de05a03a" registries = "General" uuid = "bd1f34fb-993f-5903-a121-aaf302eed6d4" -version = "0.11.5+0" +version = "1.3.1+0" [[deps.aws_c_sdkutils_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_common_jll"] -git-tree-sha1 = "c43dfba2c1ab9ea9f02f2c80e86fa16f6460244e" +git-tree-sha1 = "0162699e5aa99420f04a9606a9d7b38a872a81ee" registries = "General" uuid = "1282aa60-004d-510b-9f52-12498d409daa" -version = "0.2.4+1" +version = "1.0.0+0" [[deps.aws_checksums_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "aws_c_common_jll"] -git-tree-sha1 = "2570c8e23f4771a087b12a47edcaaa670ac05a01" +git-tree-sha1 = "ee1f5427815c98355b0b0454b5fe79806ffdf935" registries = "General" uuid = "b2a88e68-78e7-5e94-8c20-c02986ec140e" -version = "0.2.10+0" +version = "1.0.1+1" [[deps.dlfcn_win32_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] @@ -3341,102 +2489,18 @@ registries = "General" uuid = "c4b69c83-5512-53e3-94e6-de98773c479f" version = "1.4.2+0" -[[deps.eudev_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "c3b0e6196d50eab0c5ed34021aaa0bb463489510" -registries = "General" -uuid = "35ca27e7-8b34-5b7f-bca9-bdc33f59eb06" -version = "3.2.14+0" - -[[deps.fzf_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "b6a34e0e0960190ac2a4363a1bd003504772d631" -registries = "General" -uuid = "214eeab7-80f7-51ab-84ad-2988db7cef09" -version = "0.61.1+0" - [[deps.libaec_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "60f4792734488db6f42e2c7699f1d4594780bd03" +git-tree-sha1 = "31e4c16405815484827f75a38fb7eb17432e72e3" registries = "General" uuid = "477f73a3-ac25-53e9-8cc3-50b2fa2566f0" -version = "1.1.7+0" - -[[deps.libaom_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "0343a4dc329ca4dd71bf5c5b3fc1a8c2f6e0692d" -registries = "General" -uuid = "a4ae2306-e953-59d6-aa16-d00cac43593b" -version = "3.15.2+0" - -[[deps.libass_jll]] -deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Zlib_jll"] -git-tree-sha1 = "cb007192783c56d8249db4cf0e3495001edfe414" -registries = "General" -uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0" -version = "0.17.5+0" +version = "1.1.8+0" [[deps.libblastrampoline_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" version = "5.15.0+0" -[[deps.libdecor_jll]] -deps = ["Artifacts", "Dbus_jll", "JLLWrappers", "Libdl", "Libglvnd_jll", "Pango_jll", "Wayland_jll", "xkbcommon_jll"] -git-tree-sha1 = "9bf7903af251d2050b467f76bdbe57ce541f7f4f" -registries = "General" -uuid = "1183f4f0-6f2a-5f1a-908b-139f9cdfea6f" -version = "0.2.2+0" - -[[deps.libdrm_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libpciaccess_jll"] -git-tree-sha1 = "28e57478e8a160d346a19c28b3fffb9273bcc9c2" -registries = "General" -uuid = "8e53e030-5e6c-5a89-a30b-be5b7263a166" -version = "2.4.134+0" - -[[deps.libevdev_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "56d643b57b188d30cccc25e331d416d3d358e557" -registries = "General" -uuid = "2db6ffa8-e38f-5e21-84af-90c45d0032cc" -version = "1.13.4+0" - -[[deps.libfdk_aac_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "646634dd19587a56ee2f1199563ec056c5f228df" -registries = "General" -uuid = "f638f0a6-7fb0-5443-88ba-1cc74229b280" -version = "2.0.4+0" - -[[deps.libinput_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "eudev_jll", "libevdev_jll", "mtdev_jll"] -git-tree-sha1 = "91d05d7f4a9f67205bd6cf395e488009fe85b499" -registries = "General" -uuid = "36db933b-70db-51c0-b978-0f229ee0e533" -version = "1.28.1+0" - -[[deps.libpng_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] -git-tree-sha1 = "1fcd6cf05d23adab0b5ffc496d020537215751dc" -registries = "General" -uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f" -version = "1.6.59+1" - -[[deps.libva_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll", "Xorg_libXext_jll", "Xorg_libXfixes_jll", "libdrm_jll"] -git-tree-sha1 = "21cbc8fc2ac0bb182ed461e556d9c062ae6a3164" -registries = "General" -uuid = "9a156e7d-b971-5f62-b2c9-67348b8fb97c" -version = "2.24.1+0" - -[[deps.libvorbis_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll"] -git-tree-sha1 = "11e1772e7f3cc987e9d3de991dd4f6b2602663a5" -registries = "General" -uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a" -version = "1.3.8+0" - [[deps.mpif_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "MPIABI_jll", "MPICH_jll", "MPIPreferences", "MPItrampoline_jll", "MicrosoftMPI_jll", "OpenMPI_jll", "TOML"] git-tree-sha1 = "52e322f7a6d418576cdeb043dd2a7f7ad9a8abd0" @@ -3444,13 +2508,6 @@ registries = "General" uuid = "9aeb927a-4695-514f-a259-621a69f20ec0" version = "1.0.1+0" -[[deps.mtdev_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "b4d631fd51f2e9cdd93724ae25b2efc198b059b1" -registries = "General" -uuid = "009596ad-96f7-51b1-9f1b-5ce2d5e8a71e" -version = "1.1.7+0" - [[deps.nghttp2_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" @@ -3475,27 +2532,6 @@ registries = "General" uuid = "cddc5d3d-934d-5d3a-9747-62fc12ea3f48" version = "1.7.12+0" -[[deps.x264_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "14cc7083fc6dff3cc44f2bc435ee96d06ed79aa7" -registries = "General" -uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a" -version = "10164.0.1+0" - -[[deps.x265_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "e7b67590c14d487e734dcb925924c5dc43ec85f3" -registries = "General" -uuid = "dfaa095f-4041-5dcd-9319-2fabd8486b76" -version = "4.1.0+0" - -[[deps.xkbcommon_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xkeyboard_config_jll"] -git-tree-sha1 = "a1fc6507a40bf504527d0d4067d718f8e179b2b8" -registries = "General" -uuid = "d8fb68d0-12a3-5cfd-a85a-d49703b185fd" -version = "1.13.0+0" - [registries.General] url = "https://github.com/JuliaRegistries/General.git" uuid = "23338594-aafe-5451-b93e-139f81909106" diff --git a/ci/manifests/project.sha256 b/ci/manifests/project.sha256 index 677b6b55e..a6f94311a 100644 --- a/ci/manifests/project.sha256 +++ b/ci/manifests/project.sha256 @@ -1 +1 @@ -f7a12df0a57f6dc6e828822e093b997e7359cd7145772f96a308d6bc9beb8d67 +c4c572447d732cf52e7a9bd20d4b260d6d581250583218634a09dee27fe9fe87 diff --git a/docs/DOC_STANDARD.md b/docs/DOC_STANDARD.md index bafe45902..c7518853d 100644 --- a/docs/DOC_STANDARD.md +++ b/docs/DOC_STANDARD.md @@ -65,14 +65,15 @@ page. ### Figures do not build with the docs -Figure scripts run **manually**, in the root project: +Figure scripts run **manually**, in the root project, with CairoMakie in your global +environment (`figure_tools.jl` loads it): ```bash julia --project=. docs/src/figures//make_.jl ``` They are **never** run at Documenter build time. The build only embeds the -committed PNGs, so `docs/Project.toml` stays minimal (no `Plots`) and the build +committed PNGs, so `docs/Project.toml` stays minimal (no plotting package) and the build stays fast. This is also why we do not regenerate every figure on every docs change — see the regeneration policy below. diff --git a/docs/development/plotting.md b/docs/development/plotting.md index ef7becc43..11b0fbdd8 100644 --- a/docs/development/plotting.md +++ b/docs/development/plotting.md @@ -1,21 +1,81 @@ # Plotting Conventions -## Spectrum plots +GPEC draws figures with [Makie](https://docs.makie.org). Makie is a weak dependency: the drawing code lives in the +package extension `ext/GPECMakieExt/`, which loads when a session loads any Makie backend (`using CairoMakie` for +files, `using GLMakie` for interactive windows). `src/Analysis/` holds the data extraction, the registry and the +public `plot_*` functions, and calls the extension's drawing functions through `Analysis._draw`. -Any plot with discrete mode numbers m or n on the x-axis must use `seriestype=:steppre` with a `step_series` helper that pads zeros on both ends. Pattern from `benchmarks/benchmark_coil_ForcingTerms_against_fortran.jl`: +## Adding an Analysis plot + +**Sparingly.** Analysis plots are polished figures that users of a module want often in their everyday workflow: +flagship results, diagnostics and applications of the code. Never add one for a one-off benchmark or test, a +diagnostic of a scan, a comparison against Fortran, or anything of that kind. + +Before adding one, ask: *will I look at this often when I use this workflow?* Add it only if the answer is yes, and +would be yes for other users too. Otherwise draw it yourself in a script: the results and the `_x_data` extractors +make simple and one-off plots easy to make by hand. + +AI agents must not offer to add Analysis plots unprompted, and a human must approve any plot an agent adds as meeting +these criteria before it lands. A new feature does not by itself warrant a new Analysis plot. + +Every plot: + +- is registered in the plot registry (`src/Analysis/Registry.jl`) with the capabilities it needs; +- follows the theme and the figure conventions below; +- carries little text: a short title that states the run's result, terse legends, and no explanatory notes on the + figure; +- throws an `ArgumentError` that names what to change in the run when the data are absent, never a text panel or + `nothing`. + +### Mechanics + +- **Data.** Read the data through one `_x_data(src)` method per source (the result's fields, and the same datasets + from a `gpec.h5` path), returning a NamedTuple, so both sources reach one drawing function. +- **Public function.** `plot_x(src; ..., save_path=nothing)` in `src/Analysis/` starts with `_require(plot_x, src)`, + which throws the registry's reason when the source lacks the data, and ends with + `_draw(_plot_x, data; save_path, kwargs...)`. Declare the drawing function in the module with `function _plot_x end`. + Composite summaries skip the panels they cannot draw (`_can`) and throw only when none can. +- **Registry.** Call `register!(plot_x, sources, needs, description)` after the definition. A new capability in + `needs` gets a `capabilities` predicate for each result type that can hold it, a dataset in `_H5_CAPABILITIES`, and + a `REASONS` sentence saying what to change in the run. +- **Drawing.** Add the `A.._plot_x` method to the matching file in `ext/GPECMakieExt/`. Build the figure inside + `themed() do ... end`, and reuse the shared renderers before writing new ones: `_surface_slice!` (a field on the + control surface in φ and θ), `_poloidal_slice!` (a field on the (R, Z) flux-surface grid), `_coil_traces!`, + `_surfaces_on_top!` (rational surfaces along the top of a ψ_N axis), and the label helpers in `theme.jl` (`_num`, + `_power`, `_unit`, `sub`, `sup`, `PSIN`). +- **Tests.** `test/runtests_analysis_plots.jl` draws every registered plot from both sources and checks the refusals; + add an extractor to its list when the plot reads new data. Plot tests run in the `plots` test group, which stacks a + CairoMakie environment onto the load path; the other groups never load Makie. + +## Figure conventions + +- **One theme.** `GPEC_THEME` in `ext/GPECMakieExt/theme.jl`: 15 pt labels, 13 pt ticks, a 16 pt regular-weight + title, faint grid, full box, the colour-blind-safe Wong palette, and legends on a light translucent backing placed in + empty space or outside the data. Figure size follows the panel count, with no empty grid cells. +- **Self-describing.** The title and axis labels say what is plotted, its normalization and its units. A run label + appears only when several runs are overlaid. +- **Typography.** Real glyphs and rich text, never ASCII `_` or `^`: ψ_N, ξ^ψ_m, |b̄_res|, Δ′. Units in parentheses. + Numbers to three significant figures, with ×10ⁿ outside [1e-2, 1e3); an axis whose magnitudes sit outside that range + is scaled by a power of ten in its label, as in "|b̄_res| (10⁻⁴ T)". Tick labels never repeat one value: + near-constant data is reported as such. +- **Axes.** Per-rational-surface quantities go on an evenly spaced categorical `q = m/n` axis, as stems and dots, + unless where in ψ they sit is the point. Use a log scale when the data span more than about two decades, with ticks + at whole decades, and a diverging, symmetric colour range for signed data. Values that are roundoff against the + run's scale are left out and counted in the title. +- **3D.** Equal data aspect, and solid surfaces occlude what is behind them (CairoMakie has no depth buffer, so draw + in back-to-front order). + +### Spectrum plots + +Any plot with discrete mode numbers m or n on the x axis draws centred steps padded with a zero at each end: ```julia -function step_series(m_vals, amps) - m_ext = [m_vals[1] - 1; m_vals; m_vals[end] + 1] - amp_ext = [0.0; amps; 0.0] - return m_ext, amp_ext -end -# Usage: -m_ext, a_ext = step_series(m_vals, amplitudes) -plot!(p, m_ext, a_ext; seriestype=:steppre, lw=2, label="...") +step_series(m, a) = (vcat(m[1] - 1, m, m[end] + 1), vcat(0.0, a, 0.0)) +stairs!(ax, step_series(m, amplitudes)...; step=:center, linewidth=2, label="...") ``` -## Figures and plots +## Saving figures -- Always print the full absolute path of any figure or plot file you save, so the user can open it directly without searching the filesystem. -- Always check that axis labels are not clipped. In Plots.jl there is no `tight_layout()` equivalent; use explicit margins instead: `left_margin=12Plots.mm`, `bottom_margin=4Plots.mm`, etc. When in doubt, add a generous `left_margin` to prevent y-axis label cutoff. +- `save_path` saves through `Makie.save` with whichever backend is loaded. CairoMakie writes PNG, PDF and SVG; + GLMakie writes PNG only and throws an `ArgumentError` for vector formats. +- Always print the full absolute path of any figure you save, so it can be opened directly. diff --git a/docs/figure_tools.jl b/docs/figure_tools.jl index 13b91d08f..dad7db38e 100644 --- a/docs/figure_tools.jl +++ b/docs/figure_tools.jl @@ -11,9 +11,10 @@ # # Figure scripts run MANUALLY in the root project, never at docs-build time: # julia --project=. docs/src/figures//make_.jl -# so the Documenter build stays fast and Plots-free. +# with CairoMakie in the global environment, so the Documenter build stays fast +# and free of plotting packages. -using Plots +using CairoMakie using Dates using TOML @@ -46,50 +47,41 @@ function figure_provenance() end """ - stamp!(p; commit, date, subplot=length(p.subplots)) + stamp!(fig; commit, date) -Annotate a small `GPEC · ` provenance mark in the bottom-right -corner of subplot `subplot` (default: the last panel), so the figure's age is -visible when browsing the rendered docs. Works on linear or log axes -(positions in data coordinates). Never throws: a plotting hiccup warns and -leaves the figure unstamped rather than losing the plot. +Add a small `GPEC · ` provenance mark in a row below the figure's +layout, right-aligned, so the figure's age is visible when browsing the rendered +docs. Never throws: a layout hiccup warns and leaves the figure unstamped rather +than losing the plot. """ -function stamp!(p::Plots.Plot; commit::AbstractString, date::AbstractString, - subplot::Int=length(p.subplots)) +function stamp!(fig::Figure; commit::AbstractString, date::AbstractString) try - sp = p[subplot] - xl = Plots.xlims(sp) - yl = Plots.ylims(sp) - xpos = xl[2] # right edge (data coords) - ypos = yl[1] # bottom edge - txt = Plots.text("GPEC $(commit) · $(date)", 6, RGBA(0.5, 0.5, 0.5, 0.9), - :right, :bottom) - annotate!(sp, xpos, ypos, txt) + Label(fig[end+1, :], "GPEC $(commit) · $(date)"; fontsize=9, color=(:gray50, 0.9), halign=:right, tellwidth=false) catch err @warn "stamp! failed; saving figure without provenance mark" exception = err end - return p + return fig end """ - save_doc_figure(p, mod, name; script, depends, npx...) -> String + save_doc_figure(fig, mod, name; script, depends) -> String -Stamp `p` with the current git provenance, save it to +Stamp `fig` with the current git provenance, save it to `docs/src/figures//.png`, and upsert its `manifest.toml` entry (`script`, `commit`, `date`, `depends`). `depends` lists the repo-relative source files whose numbers the figure visualizes — the regeneration policy in `docs/DOC_STANDARD.md` keys off them. Returns the absolute PNG path (printed -so it can be opened directly). Extra keyword args are forwarded to `savefig`. +so it can be opened directly). """ -function save_doc_figure(p::Plots.Plot, mod::AbstractString, name::AbstractString; +function save_doc_figure(fig::Figure, mod::AbstractString, name::AbstractString; script::AbstractString, depends::AbstractVector{<:AbstractString}=String[]) prov = figure_provenance() - stamp!(p; commit=prov.commit, date=prov.date) + stamp!(fig; commit=prov.commit, date=prov.date) outdir = joinpath(FIGURES_ROOT, mod) mkpath(outdir) outpath = joinpath(outdir, name * ".png") - savefig(p, outpath) + save(outpath, fig) _update_manifest!(mod, name; script=String(script), commit=prov.commit, date=prov.date, depends=String.(depends)) diff --git a/docs/src/analysis.md b/docs/src/analysis.md index 9ca4ceb88..14fb812f8 100644 --- a/docs/src/analysis.md +++ b/docs/src/analysis.md @@ -12,7 +12,28 @@ - `ErrorFields`: coil sensitivities, tolerance distributions, locking risk and correction limits (see [Error Fields](error_fields.md)) `examples/DIIID-like_ideal_example/analyze_example.jl`, `examples/Solovev_ideal_example/analyze_example.jl` and `examples/DIIID-like_error_field_example/analyze_example.jl` are worked examples of each; the s–α maps of local stability use `salpha_reference`, `compute_ballooning_stability!` and `scan_delta_prime_map`. -Figure conventions for spectrum plots are in `docs/development/plotting.md` of the repository, and `write_imas` on the [Utilities](utilities.md) page exports results to an IMAS data dictionary. +`write_imas` on the [Utilities](utilities.md) page exports results to an IMAS data dictionary. + +## Drawing + +The plots are drawn with [Makie](https://docs.makie.org), which GPEC loads only when a session loads a Makie backend. Load one alongside GPEC, from your own or the global environment: + +```julia +using GeneralizedPerturbedEquilibrium, CairoMakie # files: PNG, PDF, SVG +using GeneralizedPerturbedEquilibrium, GLMakie # interactive windows that zoom, pan and rotate +``` + +Every plot returns a Makie `Figure`, and `save_path` saves it with the loaded backend (GLMakie writes PNG only). Without a backend a plot throws an `ArgumentError` saying which to load. + +The summary figures of a DIII-D-like run (`examples/DIIID-like_ideal_example`): + +![Equilibrium summary: flux surfaces and the q, μ₀p and 2πF profiles, with the n = 1 rational surfaces](figures/analysis/equilibrium_summary.png) + +![Stability summary: critical determinant, free-boundary energies and the least stable eigenmodes](figures/analysis/ffs_summary.png) + +![Perturbed-equilibrium summary: resonant field, edge spectrum, plasma displacement and b_n on the poloidal plane](figures/analysis/pe_summary.png) + +![The C-coil set around the plasma boundary](figures/analysis/coils_3d.png) ```@docs GeneralizedPerturbedEquilibrium.Analysis diff --git a/docs/src/api.md b/docs/src/api.md index d12110902..4cb8938dd 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -53,29 +53,31 @@ ForceFreeStatesResult Riccati, ideal closure Δ′_ii (ψ_N) 2/1: 0.1781, 3/1: -215.7 Solution not computed (Riccati stores no ξ profiles) Wall ConformalWall, standoff = 0.2415 a - Plots 11 of 13 available (see available_plots) + Plots 13 of 15 available (see available_plots) ``` The fields themselves are documented in each type's docstring. ## Plotting a result -Every plot in [`Analysis`](analysis.md) takes the result in memory or the path of the `gpec.h5` it was saved to. `available_plots` lists the plots for a result, a path, or the named tuple `main` returns, and for each one the run cannot draw, what to change: +Every plot in [`Analysis`](analysis.md) takes the result in memory or the path of the `gpec.h5` it was saved to, and draws with the Makie backend the session has loaded (`using CairoMakie` or `using GLMakie`). `available_plots` lists the plots for a result, a path, or the named tuple `main` returns, and for each one the run cannot draw, what to change: ```julia-repl julia> available_plots(ffs) -Plots for ForceFreeStatesResult (riccati): 11 of 13 available - ✓ ForceFreeStates.plot_ffs_summary the stability panels the run supplies - ✗ ForceFreeStates.plot_mode_displacement needs stored ξ profiles: use the Forward integrator or a matched Galerkin solve (Riccati and unmatched Galerkin store none) - ✓ ForceFreeStates.plot_fixed_boundary_stability_criterion critical determinant against ψ_N +Plots for ForceFreeStatesResult (riccati): 13 of 15 available + +ForceFreeStates + ✓ plot_ffs_summary the stability panels the run supplies + ✗ plot_mode_displacement needs stored ξ profiles: use the Forward integrator or a matched Galerkin solve (Riccati and unmatched Galerkin store none) + ✓ plot_critical_determinant critical determinant against ψ_N ⋮ ``` A plot the source cannot draw throws an `ArgumentError` with the same reason, and a summary leaves out the panels it cannot draw: ```julia -Analysis.ForceFreeStates.plot_ffs_summary(ffs) # the result in memory -Analysis.ForceFreeStates.plot_ffs_summary("gpec.h5") # or the file to which it was saved +plot_ffs_summary(ffs) # the result in memory +plot_ffs_summary("gpec.h5") # or the file to which it was saved ``` ## Saving and rerunning diff --git a/docs/src/figures/analysis/coils_3d.png b/docs/src/figures/analysis/coils_3d.png new file mode 100644 index 000000000..a7175fcaa Binary files /dev/null and b/docs/src/figures/analysis/coils_3d.png differ diff --git a/docs/src/figures/analysis/equilibrium_summary.png b/docs/src/figures/analysis/equilibrium_summary.png new file mode 100644 index 000000000..1ecc94c1d Binary files /dev/null and b/docs/src/figures/analysis/equilibrium_summary.png differ diff --git a/docs/src/figures/analysis/ffs_summary.png b/docs/src/figures/analysis/ffs_summary.png new file mode 100644 index 000000000..1364c3f22 Binary files /dev/null and b/docs/src/figures/analysis/ffs_summary.png differ diff --git a/docs/src/figures/analysis/make_highlights.jl b/docs/src/figures/analysis/make_highlights.jl new file mode 100644 index 000000000..2012209b2 --- /dev/null +++ b/docs/src/figures/analysis/make_highlights.jl @@ -0,0 +1,37 @@ +# make_highlights.jl +# +# Figures: the highlight Analysis figures on the Analysis docs page, from the +# DIII-D-like ideal example run with the Forward integrator and the C-coil +# perturbed equilibrium (its kinetic and error-field stages left out). About two +# minutes. +# +# Run manually, with CairoMakie in the global environment: +# julia --project=. docs/src/figures/analysis/make_highlights.jl + +include(joinpath(@__DIR__, "..", "..", "..", "figure_tools.jl")) + +using GeneralizedPerturbedEquilibrium + +const G = GeneralizedPerturbedEquilibrium +const A = G.Analysis + +ret = mktempdir() do dir + src = joinpath(pkgdir(G), "examples", "DIIID-like_ideal_example") + for name in readdir(src) + endswith(name, ".jl") || name == "benchmark_inputs.jld2" || cp(joinpath(src, name), joinpath(dir, name)) + end + deck = TOML.parsefile(joinpath(dir, "gpec.toml")) + delete!(deck, "KineticForces") + delete!(deck, "ErrorFields") + open(io -> TOML.print(io, deck), joinpath(dir, "gpec.toml"), "w") + G.main([dir]) +end + +depends = ["src/Analysis/Equilibrium.jl", "src/Analysis/ForceFreeStates.jl", "src/Analysis/PerturbedEquilibrium.jl", "src/Analysis/CoilForcing.jl"] +for (fig, name) in ( + (A.Equilibrium.plot_equilibrium_summary(ret.equil), "equilibrium_summary"), + (A.ForceFreeStates.plot_ffs_summary(ret.ffs), "ffs_summary"), + (A.PerturbedEquilibrium.plot_perturbed_equilibrium_summary(ret.pe), "pe_summary"), + (A.CoilForcing.plot_coil_geometry_3d(ret.pe), "coils_3d")) + save_doc_figure(fig, "analysis", name; script=basename(@__FILE__), depends) +end diff --git a/docs/src/figures/analysis/pe_summary.png b/docs/src/figures/analysis/pe_summary.png new file mode 100644 index 000000000..e1349503a Binary files /dev/null and b/docs/src/figures/analysis/pe_summary.png differ diff --git a/docs/src/figures/inner_layer/make_backend_regime_map.jl b/docs/src/figures/inner_layer/make_backend_regime_map.jl index f0978ab30..7fbcfbce3 100644 --- a/docs/src/figures/inner_layer/make_backend_regime_map.jl +++ b/docs/src/figures/inner_layer/make_backend_regime_map.jl @@ -8,7 +8,8 @@ # The crossovers of each curve with the 1% line are the practical reach of each # method — this is what motivates the :ray backend. A few tens of solves; ~1 min. # -# Run manually: julia --project=. docs/src/figures/inner_layer/make_backend_regime_map.jl +# Run manually, with CairoMakie in the global environment: +# julia --project=. docs/src/figures/inner_layer/make_backend_regime_map.jl include(joinpath(@__DIR__, "..", "..", "..", "figure_tools.jl")) @@ -23,7 +24,8 @@ p = IL.GGJ.q4_surface_benchmark() γ(aq) = im * aq * GGJ.q0(p) # imaginary axis: Q = i|Q| # Worst-case relative error over the two parity components vs the :ray reference. -relerr(Δ, Δref) = maximum(abs.(Δ .- Δref) ./ abs.(Δref)) +parities(Δ) = [Δ.tearing, Δ.interchange] +relerr(Δ, Δref) = maximum(abs.(parities(Δ) .- parities(Δref)) ./ abs.(parities(Δref))) absQ = 10 .^ range(-1, log10(500); length=22) ref = [IL.solve_inner(IL.GGJModel(; solver=:ray), p, γ(aq)) for aq in absQ] @@ -33,13 +35,13 @@ shoot_x, shoot_e = Float64[], Float64[] for (k, aq) in enumerate(absQ) try Δ = IL.solve_inner(IL.GGJModel(; solver=:galerkin), p, γ(aq)) - all(isfinite, Δ) && (push!(gal_x, aq); push!(gal_e, max(relerr(Δ, ref[k]), 1e-16))) + all(isfinite, parities(Δ)) && (push!(gal_x, aq); push!(gal_e, max(relerr(Δ, ref[k]), 1e-16))) catch end aq > 10 && continue # :shooting is meaningless past its regime try Δ = IL.solve_inner(IL.GGJModel(; solver=:shooting), p, γ(aq)) - all(isfinite, Δ) && (push!(shoot_x, aq); push!(shoot_e, max(relerr(Δ, ref[k]), 1e-16))) + all(isfinite, parities(Δ)) && (push!(shoot_x, aq); push!(shoot_e, max(relerr(Δ, ref[k]), 1e-16))) catch end end @@ -47,19 +49,20 @@ end @printf("computed %d :ray refs, %d :galerkin, %d :shooting points\n", length(absQ), length(gal_x), length(shoot_x)) -plt = plot(; size=(820, 560), legend=:bottomright, xscale=:log10, yscale=:log10, +fig = Figure(; size=(820, 560)) +ax = Axis(fig[1, 1]; xscale=log10, yscale=log10, limits=(nothing, (1e-7, 3e0)), yminorticksvisible=false, + yticks=(exp10.(-6:2:0), [rich("10", superscript(string(k))) for k in -6:2:0]), xlabel="|Q| (imaginary axis, Q = i|Q|)", ylabel="relative error vs :ray reference", - title="Backend accuracy along the imaginary-Q axis (q=4 surface)", - ylims=(1e-7, 3e0), left_margin=9Plots.mm, bottom_margin=5Plots.mm) + title="Backend accuracy along the imaginary-Q axis (q=4 surface)") -plot!(plt, shoot_x, shoot_e; lw=2, marker=:diamond, ms=5, color=:seagreen, label=":shooting") -plot!(plt, gal_x, gal_e; lw=2, marker=:circle, ms=5, color=:orange, label=":galerkin") -hline!(plt, [1e-2]; color=:red, ls=:dash, lw=1.5, label="1% accuracy") -annotate!(plt, 8.0, 3e-6, - Plots.text(":ray reference\n(error < 1e-5 to |Q| = 500)", 8, :center, RGB(0.2, 0.3, 0.6))) +scatterlines!(ax, shoot_x, shoot_e; linewidth=2, marker=:diamond, markersize=10, color=:seagreen, label=":shooting") +scatterlines!(ax, gal_x, gal_e; linewidth=2, marker=:circle, markersize=10, color=:orange, label=":galerkin") +hlines!(ax, 1e-2; color=:red, linestyle=:dash, linewidth=1.5, label="1% accuracy") +text!(ax, 8.0, 3e-6; text=":ray reference\n(error < 1e-5 to |Q| = 500)", fontsize=11, align=(:center, :center), color=RGBf(0.2, 0.3, 0.6)) +axislegend(ax; position=:rb) -save_doc_figure(plt, "inner_layer", "backend_regime_map"; +save_doc_figure(fig, "inner_layer", "backend_regime_map"; script=basename(@__FILE__), depends=["src/InnerLayer/GGJ/Ray.jl", "src/InnerLayer/GGJ/Galerkin.jl", "src/InnerLayer/GGJ/Shooting.jl"]) diff --git a/docs/src/figures/manifest.toml b/docs/src/figures/manifest.toml index fd45fad1e..aea34a4a8 100644 --- a/docs/src/figures/manifest.toml +++ b/docs/src/figures/manifest.toml @@ -4,6 +4,30 @@ # list changes the numbers it shows — see docs/DOC_STANDARD.md. +[analysis.coils_3d] +commit = "316828b2e" +date = "2026-11-07" +depends = ["src/Analysis/Equilibrium.jl", "src/Analysis/ForceFreeStates.jl", "src/Analysis/PerturbedEquilibrium.jl", "src/Analysis/CoilForcing.jl"] +script = "make_highlights.jl" + +[analysis.equilibrium_summary] +commit = "316828b2e" +date = "2026-11-07" +depends = ["src/Analysis/Equilibrium.jl", "src/Analysis/ForceFreeStates.jl", "src/Analysis/PerturbedEquilibrium.jl", "src/Analysis/CoilForcing.jl"] +script = "make_highlights.jl" + +[analysis.ffs_summary] +commit = "4f1274edc" +date = "2026-11-09" +depends = ["src/Analysis/Equilibrium.jl", "src/Analysis/ForceFreeStates.jl", "src/Analysis/PerturbedEquilibrium.jl", "src/Analysis/CoilForcing.jl"] +script = "make_highlights.jl" + +[analysis.pe_summary] +commit = "ba4d5453b" +date = "2026-11-09" +depends = ["src/Analysis/Equilibrium.jl", "src/Analysis/ForceFreeStates.jl", "src/Analysis/PerturbedEquilibrium.jl", "src/Analysis/CoilForcing.jl"] +script = "make_highlights.jl" + [ballooning.delta_prime_matching] commit = "legacy" date = "unknown" diff --git a/examples/DIIID-like_error_field_example/analyze_example.jl b/examples/DIIID-like_error_field_example/analyze_example.jl index 20e2ec2b9..53f0f9973 100644 --- a/examples/DIIID-like_error_field_example/analyze_example.jl +++ b/examples/DIIID-like_error_field_example/analyze_example.jl @@ -1,10 +1,19 @@ using Pkg; Pkg.activate(joinpath(@__DIR__, "../..")) -using GeneralizedPerturbedEquilibrium, Plots, Printf, HDF5 +# The figures need a Makie backend from your global environment: CairoMakie saves files, GLMakie opens windows. +using GeneralizedPerturbedEquilibrium, CairoMakie, Printf, HDF5 using GeneralizedPerturbedEquilibrium: PerturbedEquilibrium, ErrorFields -isinteractive() ? plotlyjs() : gr() +AEF = GeneralizedPerturbedEquilibrium.Analysis.ErrorFields h5path = joinpath(@__DIR__, "gpec.h5") +# Show a figure and save it beside this script. +function show_and_save(fig, name) + display(fig) + path = joinpath(@__DIR__, name * ".png") + save(path, fig) + println("Saved: ", path) + return fig +end # The run stored every coil set's spectrum linearization. Project it onto the dominant # resonant-coupling mode over the core window (ψ_N ≤ 0.9, the default) to see which coils matter. @@ -24,17 +33,8 @@ end # Error field per millimetre of in-plane shift and per millimetre of rim displacement from tilt, # F coils only, both in the units mechanical tolerances arrive in. Axisymmetric hoops have no # nominal n=1 drive, so the sensitivities are their whole error-field story. -p_shift = bar(names[fcoils], full.abs_delta_shift_per_mm[fcoils]; label="core window ψ_N ≤ 0.9", alpha=0.75, - ylabel="|δ| per mm", title="Dominant-mode error field per mm of F-coil shift", xrotation=45, xticks=(1:length(fcoils), names[fcoils]), - left_margin=12Plots.mm, bottom_margin=8Plots.mm, legend=:topright) -p_tilt = bar(names[fcoils], full.abs_delta_rim_per_mm[fcoils]; label="core window ψ_N ≤ 0.9", alpha=0.75, - ylabel="|δ| per mm of rim", title="Dominant-mode error field per mm of F-coil rim displacement", xrotation=45, - xticks=(1:length(fcoils), names[fcoils]), left_margin=12Plots.mm, bottom_margin=8Plots.mm, legend=:topright) -p_sens = plot(p_shift, p_tilt; layout=(2, 1), size=(900, 700)) -display(p_sens) -sens_path = joinpath(@__DIR__, "fcoil_sensitivities.png") -Plots.savefig(p_sens, sens_path) -println("Saved: ", abspath(sens_path)) +show_and_save(AEF.plot_coil_sensitivities(full; quantity=:shift, coils=names[fcoils]), "fcoil_shift_sensitivity") +show_and_save(AEF.plot_coil_sensitivities(full; quantity=:rim, coils=names[fcoils]), "fcoil_rim_sensitivity") # Where the sensitivity lives in mode space: the spectrum derivative of the most and least # sensitive F coils under a 1 mm shift along x, against the C-coil's nominal spectrum. @@ -42,78 +42,49 @@ step_series(m, a) = (vcat(m[1] - 1, m, m[end] + 1), vcat(0.0, a, 0.0)) m = sens.m_modes order = sortperm(full.abs_delta_shift_per_mm[fcoils]; rev=true) picks = [fcoils[order[1]], fcoils[order[end]]] -p_spec = plot(; xlabel="poloidal mode m", ylabel="|b̃| [T]", yscale=:log10, legend=:topleft, - title="Root-area-weighted spectra: C-coil as built vs F coils shifted 1 mm in x", - left_margin=12Plots.mm, bottom_margin=6Plots.mm, size=(900, 420)) c = findfirst(==("d3d_c"), names) # file-based sets are named machine_name -me, ae = step_series(m, max.(abs.(sens.field_as_designed[:, c]), 1e-30)) -plot!(p_spec, me, ae; seriestype=:steppre, lw=2, label="C-coil nominal (20 A)") -for j in picks - me, ae = step_series(m, max.(1e-3 .* abs.(sens.shift_sensitivity_per_m[:, 1, j]), 1e-30)) - plot!(p_spec, me, ae; seriestype=:steppre, lw=2, label="$(names[j]) ∂b̃/∂Δx · 1 mm") +spectra = vcat([("C-coil nominal (20 A)", abs.(sens.field_as_designed[:, c]))], + [("$(names[j]) ∂b̃/∂Δx · 1 mm", 1e-3 .* abs.(sens.shift_sensitivity_per_m[:, 1, j])) for j in picks]) +floor = minimum(minimum(filter(>(0), a)) for (_, a) in spectra) / 3 +top = maximum(maximum(a) for (_, a) in spectra) +fig = Figure(; size=(1100, 420)) +ax = Axis(fig[1, 1]; xlabel="poloidal mode m", ylabel="|b̃| (T)", yscale=log10, + title="Root-area-weighted spectra: C-coil as built vs F coils shifted 1 mm in x") +for (label, a) in spectra + stairs!(ax, step_series(m, max.(a, floor))...; step=:center, linewidth=2, label) end -display(p_spec) -spec_path = joinpath(@__DIR__, "fcoil_spectra.png") -Plots.savefig(p_spec, spec_path) -println("Saved: ", abspath(spec_path)) +ylims!(ax, floor, 2top) +Legend(fig[1, 2], ax; framevisible=false) +show_and_save(fig, "fcoil_spectra") # The tolerance Monte Carlo: the intrinsic and corrected |δ| distributions the run wrote (full # window, dominant mode), and a post-hoc re-run over the edge window with the same tolerances. mc = ErrorFields.MonteCarloResult(h5path) mc_edge = ErrorFields.run_monte_carlo(h5path; psi_low=0.7, nsample=200_000, nbatch=4, seed=1) +show_and_save(AEF.plot_tolerance_pdf(["all rational surfaces" => mc, "ψ_N ≥ 0.7" => mc_edge]), "tolerance_pdf") centers(m) = (m.abs_delta_bin_edges[1:end-1] .+ m.abs_delta_bin_edges[2:end]) ./ 2 -p_pdf = plot(; xlabel="dominant-mode overlap |δ|", ylabel="probability density", legend=:topright, - title="Tolerance Monte Carlo: |δ| over sampled misalignments", left_margin=12Plots.mm, bottom_margin=6Plots.mm, size=(900, 420)) -plot!(p_pdf, centers(mc), mc.abs_delta_pdf; lw=2, label="intrinsic, all rational surfaces") -plot!(p_pdf, centers(mc), mc.abs_delta_efc_pdf; lw=2, label="corrected (efc_factor = 2)") -plot!(p_pdf, centers(mc_edge), mc_edge.abs_delta_pdf; lw=2, ls=:dash, label="intrinsic, ψ_N ≥ 0.7") -vline!(p_pdf, [mc.abs_delta_total_as_designed]; ls=:dot, c=:black, label="as designed") -display(p_pdf) -pdf_path = joinpath(@__DIR__, "tolerance_pdf.png") -Plots.savefig(p_pdf, pdf_path) -println("Saved: ", abspath(pdf_path)) @printf("⟨|δ|⟩ = %.3e intrinsic, %.3e corrected; as designed %.3e; batch spread of ⟨|δ|⟩ ≈ %.1e\n", mc.abs_delta_sampled_mean, mc.abs_delta_efc_sampled_mean, mc.abs_delta_total_as_designed, maximum(abs.(vec(sum(mc.abs_delta_pdf_batches .* centers(mc) .* diff(mc.abs_delta_bin_edges); dims=1)) .- mc.abs_delta_sampled_mean))) -# Locking risk: the threshold distribution against the overlap distribution, and the risk against +# Locking risk: the overlap distribution against the threshold's distribution, and the risk against # tolerance scale with the allowable tolerance for a 1 % target read off the scan. scan = ErrorFields.ToleranceScan(h5path) -risk_nominal = h5open( +locking_probability_percent, locking_probability_efc_percent, thr_nom = h5open( f -> (read(f["ErrorFields/Risk/locking_probability_percent"]), read(f["ErrorFields/Risk/locking_probability_efc_percent"]), - read(f["ErrorFields/Risk/threshold_fit"]), read(f["ErrorFields/Risk/threshold_pdf"]), read(f["ErrorFields/Risk/locking_probability_given_delta"])), h5path, "r") -locking_probability_percent, locking_probability_efc_percent, thr_nom, thr_pdf, p_given = risk_nominal -p_thr = plot(; xlabel="dominant-mode overlap |δ|", ylabel="probability density", legend=:topright, xscale=:log10, - title="Overlap distribution vs ITPA penetration threshold", left_margin=12Plots.mm, bottom_margin=6Plots.mm, size=(900, 420)) -c = centers(mc) -keep = c .> 0 -plot!(p_thr, c[keep], mc.abs_delta_pdf[keep] ./ maximum(mc.abs_delta_pdf); lw=2, label="intrinsic |δ| (normalized)") -plot!(p_thr, c[keep], mc.abs_delta_efc_pdf[keep] ./ maximum(mc.abs_delta_efc_pdf); lw=2, label="corrected |δ| (normalized)") -plot!(p_thr, c[keep], thr_pdf[keep] ./ maximum(thr_pdf); lw=2, c=:black, label="threshold density (normalized)") -plot!(p_thr, mc.abs_delta_bin_edges[2:end], p_given[2:end]; lw=1.5, ls=:dash, c=:red, label="P(lock | δ)") -vline!(p_thr, [thr_nom]; ls=:dot, c=:black, label="nominal threshold") -p_scan = plot(; xlabel="tolerance scale (× tolerances.toml)", ylabel="locking probability [%]", xscale=:log10, yscale=:log10, - legend=:topleft, title="Risk vs tolerance scale", left_margin=12Plots.mm, bottom_margin=6Plots.mm) -plot!(p_scan, scan.tolerance_scale, max.(scan.locking_probability_percent, 1e-4); marker=:circle, lw=2, label="intrinsic") -plot!(p_scan, scan.tolerance_scale, max.(scan.locking_probability_efc_percent, 1e-4); marker=:square, lw=2, label="corrected") -hline!(p_scan, [1.0]; ls=:dash, c=:gray, label="1 % target") -p_risk = plot(p_thr, p_scan; layout=(2, 1), size=(900, 760)) -display(p_risk) -risk_path = joinpath(@__DIR__, "locking_risk.png") -Plots.savefig(p_risk, risk_path) -println("Saved: ", abspath(risk_path)) + read(f["ErrorFields/Risk/threshold_fit"])), h5path, "r") +show_and_save(AEF.plot_threshold_scaling(h5path), "threshold_scaling") +show_and_save(AEF.plot_locking_risk(h5path; target_percent=1.0), "locking_risk") @printf("P_lock = %.2f %% intrinsic, %.2f %% corrected at the design tolerances; allowable scale for 1 %%: %.2f (intrinsic), %.2f (corrected)\n", locking_probability_percent, locking_probability_efc_percent, ErrorFields.allowable_tolerance(scan, 1.0), ErrorFields.allowable_tolerance(scan, 1.0; corrected=true)) -# The same figures through the Analysis module, which overplots design revisions when given -# several `label => gpec.h5` pairs. -p_summary = GeneralizedPerturbedEquilibrium.Analysis.ErrorFields.plot_error_field_summary(h5path; save_path=joinpath(@__DIR__, "error_field_summary.png")) -display(p_summary) +# The run's assessment on one page. The single-run figures above overplot design revisions when +# given several `label => gpec.h5` pairs. +show_and_save(AEF.plot_error_field_summary(h5path), "error_field_summary") # How much intrinsic error field the C-coil could correct before its own NTV torque, at a # 4 N·m budget, costs the rotation that holds the penetration threshold up. -p_ntv = GeneralizedPerturbedEquilibrium.Analysis.ErrorFields.plot_efc_ntv_limits(h5path; torque_budget=4.0, save_path=joinpath(@__DIR__, "efc_ntv_limits.png")) -display(p_ntv) +show_and_save(AEF.plot_efc_ntv_limits(h5path; torque_budget=4.0), "efc_ntv_limits") for c in ErrorFields.read_efc_couplings(h5path) lim = ErrorFields.max_correctable_overlap(c; delta_threshold=thr_nom, torque_budget=4.0) @printf("%s: |δ| = %.3e per kAt, resonant fraction %.1f %%, torque %.3e (full) / %.3e (residual) N·m per kAt²; correctable up to %.2f × threshold\n", @@ -126,7 +97,6 @@ end # The context holds everything that does not depend on which coils are being judged, so any # geometry can be swept against it. Here the example's own coils, shifted a millimetre, stand in # for a second revision. -AEF = GeneralizedPerturbedEquilibrium.Analysis.ErrorFields FT = GeneralizedPerturbedEquilibrium.ForcingTerms ctx = ErrorFields.ResonantDriveContext(h5path) @@ -150,6 +120,5 @@ pair = ErrorFields.combine_overlaps(as_built, as_built[1].coil_name => 1.0, as_b # against the dominant mode. for (name, fn) in (("applied_spectra", AEF.plot_applied_spectra), ("overlap_contributions", AEF.plot_overlap_contributions), ("surface_overlay", AEF.plot_surface_overlay)) - path = joinpath(@__DIR__, "$name.png") - display(fn(ctx, as_built; save_path=path)) + show_and_save(fn(ctx, as_built), name) end diff --git a/examples/DIIID-like_ideal_example/analyze_example.jl b/examples/DIIID-like_ideal_example/analyze_example.jl index 1a3db2eb1..38134e94b 100644 --- a/examples/DIIID-like_ideal_example/analyze_example.jl +++ b/examples/DIIID-like_ideal_example/analyze_example.jl @@ -1,30 +1,27 @@ using Pkg; Pkg.activate(joinpath(@__DIR__, "../..")) -using GeneralizedPerturbedEquilibrium, Plots +# The figures need a Makie backend from your global environment: CairoMakie saves files, GLMakie opens windows. +using GeneralizedPerturbedEquilibrium, CairoMakie using GeneralizedPerturbedEquilibrium: Analysis using GeneralizedPerturbedEquilibrium: Equilibrium, ForceFreeStates, LocalStability -using LaTeXStrings, TOML -isinteractive() ? plotlyjs() : gr() +using TOML h5path = joinpath(@__DIR__, "gpec.h5") +saved = String[] +# Show a figure and save it beside this script. +function show_and_save(fig, name) + display(fig) + path = joinpath(@__DIR__, name * ".png") + save(path, fig) + push!(saved, path) + return fig +end -# Summary plots -p_eq = Analysis.Equilibrium.plot_equilibrium_summary(h5path) -p_ffs = Analysis.ForceFreeStates.plot_ffs_summary(h5path) -p_pe = Analysis.PerturbedEquilibrium.plot_perturbed_equilibrium_summary(h5path) - -display(p_eq); -Plots.savefig(p_eq, joinpath(@__DIR__, "equilibrium_summary.png")) -display(p_ffs); -Plots.savefig(p_ffs, joinpath(@__DIR__, "ffs_summary.png")) -display(p_pe); -Plots.savefig(p_pe, joinpath(@__DIR__, "pe_summary.png")) - -# BALOO-style first ballooning stability boundary: experimental α vs critical α -p_baloo = Analysis.ForceFreeStates.plot_ballooning_alpha_boundary(h5path) -display(p_baloo) -baloo_path = joinpath(@__DIR__, "ballooning_alpha_boundary.png") -Plots.savefig(p_baloo, baloo_path) +# Summary plots, and the BALOO-style first ballooning stability boundary: experimental α against critical α +show_and_save(Analysis.Equilibrium.plot_equilibrium_summary(h5path), "equilibrium_summary") +show_and_save(Analysis.ForceFreeStates.plot_ffs_summary(h5path), "ffs_summary") +show_and_save(Analysis.PerturbedEquilibrium.plot_perturbed_equilibrium_summary(h5path), "pe_summary") +show_and_save(Analysis.ForceFreeStates.plot_ballooning_alpha_boundary(h5path), "ballooning_alpha_boundary") # ---------------------------------------------------------------------- # Local stability: s-alpha profiles, D_I / ballooning Δ', and 2D s-alpha @@ -33,11 +30,22 @@ Plots.savefig(p_baloo, baloo_path) # rather than reading gpec.h5, so the s-alpha scan can perturb (p', q'). # ---------------------------------------------------------------------- -function symmetric_clims(z) - finite_vals = vec(z)[isfinite.(vec(z))] - maxabs = isempty(finite_vals) ? 1.0 : maximum(abs.(finite_vals)) - maxabs = max(maxabs, eps(Float64)) - return (-maxabs, maxabs) +# Colour limits symmetric about zero over the finite values. +function symmetric_limits(z) + m = max(maximum(abs, filter(isfinite, z); init=0.0), eps()) + return (-m, m) +end + +# Two profiles side by side against normalized poloidal flux, NaNs dropped. +function profile_pair(psi, left, right; zero_line=false) + fig = Figure(; size=(1100, 420)) + for (col, (y, label, title)) in enumerate((left, right)) + ax = Axis(fig[1, col]; xlabel="normalized poloidal flux ψ_N", ylabel=label, title) + ok = isfinite.(y) + lines!(ax, psi[ok], y[ok]; linewidth=2) + zero_line && col == 2 && hlines!(ax, 0.0; color=:black, linestyle=:dash) + end + return fig end geqdsk_cases = [ @@ -46,11 +54,6 @@ geqdsk_cases = [ base_toml = TOML.parsefile(joinpath(@__DIR__, "gpec.toml")) -println("Saved figures:") -for p in (joinpath(@__DIR__, "equilibrium_summary.png"), joinpath(@__DIR__, "ffs_summary.png"), joinpath(@__DIR__, "pe_summary.png"), baloo_path) - println(" ", p) -end - for geqdsk_file in geqdsk_cases case_label = splitext(basename(geqdsk_file))[1] println("\n=== Local stability: $case_label ===") @@ -73,78 +76,16 @@ for geqdsk_file in geqdsk_cases @warn "s-alpha reference failed" i psi = psi_norm[i] exception = (err, catch_backtrace()) end end - - s_mask = isfinite.(s_profile) - alpha_mask = isfinite.(alpha_profile) - p_s = plot( - psi_norm[s_mask], - s_profile[s_mask]; - xlabel="Norm. Poloidal Flux", - ylabel="s", - title="Magnetic shear s", - label="s", - lw=2, - framestyle=:box, - left_margin=10Plots.mm, - bottom_margin=5Plots.mm - ) - p_alpha = plot( - psi_norm[alpha_mask], - alpha_profile[alpha_mask]; - xlabel="Norm. Poloidal Flux", - ylabel=L"\alpha", - title="Pressure gradient α", - label=L"\alpha", - lw=2, - framestyle=:box, - left_margin=10Plots.mm, - bottom_margin=5Plots.mm - ) - p_salpha = plot(p_s, p_alpha; layout=(1, 2), size=(1100, 420)) - display(p_salpha) - salpha_path = joinpath(@__DIR__, "salpha_profiles_$(case_label).png") - Plots.savefig(p_salpha, salpha_path) + show_and_save(profile_pair(psi_norm, (s_profile, "s", "Magnetic shear s"), (alpha_profile, "α", "Pressure gradient α")), "salpha_profiles_$(case_label)") # D_I (Mercier) and ballooning Δ' profiles locstab_fs = zeros(length(psi_norm), 5) LocalStability.compute_ballooning_stability!(locstab_fs, equil; compute_delta_prime=true) - delta_prime = Vector(locstab_fs[:, 4]) di_profile = fill(NaN, length(psi_norm)) psi_mask = abs.(psi_norm) .> eps(Float64) di_profile[psi_mask] .= locstab_fs[psi_mask, 1] ./ psi_norm[psi_mask] - - delta_mask = isfinite.(delta_prime) - di_mask = isfinite.(di_profile) - p_delta = plot( - psi_norm[delta_mask], - delta_prime[delta_mask]; - xlabel="Norm. Poloidal Flux", - ylabel=L"\Delta'", - title="Ballooning Δ'", - label=L"\Delta'", - lw=2, - framestyle=:box, - left_margin=10Plots.mm, - bottom_margin=5Plots.mm - ) - p_di = plot( - psi_norm[di_mask], - di_profile[di_mask]; - xlabel="Norm. Poloidal Flux", - ylabel=L"D_I", - title="Mercier D_I", - label=L"D_I", - lw=2, - framestyle=:box, - left_margin=10Plots.mm, - bottom_margin=5Plots.mm - ) - hline!(p_di, [0.0]; linestyle=:dash, color=:black, label=nothing) - p_localstab = plot(p_delta, p_di; layout=(1, 2), size=(1100, 420)) - display(p_localstab) - localstab_path = joinpath(@__DIR__, "delta_di_profiles_$(case_label).png") - Plots.savefig(p_localstab, localstab_path) + show_and_save(profile_pair(psi_norm, (delta_prime, "Δ′", "Ballooning Δ′"), (di_profile, "D_I", "Mercier D_I"); zero_line=true), "delta_di_profiles_$(case_label)") # 2D s-alpha maps of Δ' and D_I at a near-edge surface, with zero contours psi_target = 0.974 @@ -159,66 +100,36 @@ for geqdsk_file in geqdsk_cases porder = sortperm(scan_pprime) scan_qprime = scan_qprime[qorder] scan_pprime = scan_pprime[porder] - scan_delta = scan.delta_prime[qorder, porder] - scan_di = scan.di_values[qorder, porder] - - p_map_delta = contourf( - scan_pprime, - scan_qprime, - scan_delta; - c=cgrad([:blue, :white, :red]), - clims=symmetric_clims(scan_delta), - xlabel=L"p_{\psi}", - ylabel=L"q_{\psi}", - title=L"\Delta'", - levels=41, - framestyle=:box, - left_margin=10Plots.mm, - bottom_margin=5Plots.mm - ) - contour!(p_map_delta, scan_pprime, scan_qprime, scan_delta; levels=[0.0], color=:black, linewidth=2) - scatter!(p_map_delta, [scan.reference.pprime_ref], [scan.reference.qprime_ref]; color=:green, marker=:star5, ms=7, label="equilibrium") - p_map_di = contourf( - scan_pprime, - scan_qprime, - scan_di; - c=cgrad([:blue, :white, :red]), - clims=symmetric_clims(scan_di), - xlabel=L"p_{\psi}", - ylabel=L"q_{\psi}", - title=L"D_I", - levels=41, - framestyle=:box, - left_margin=10Plots.mm, - bottom_margin=5Plots.mm - ) - contour!(p_map_di, scan_pprime, scan_qprime, scan_di; levels=[0.0], color=:black, linewidth=2) - scatter!(p_map_di, [scan.reference.pprime_ref], [scan.reference.qprime_ref]; color=:green, marker=:star5, ms=7, label="equilibrium") - p_maps = plot(p_map_delta, p_map_di; layout=(1, 2), size=(1150, 460)) - display(p_maps) - maps_path = joinpath(@__DIR__, "salpha_maps_$(case_label).png") - Plots.savefig(p_maps, maps_path) + # Rows of the scan follow q′ and columns p′; Makie takes z[x, y], so transpose to put p′ on x. + scan_delta = permutedims(scan.delta_prime[qorder, porder]) + scan_di = permutedims(scan.di_values[qorder, porder]) + eq_point = ([scan.reference.pprime_ref], [scan.reference.qprime_ref]) + + fig = Figure(; size=(1150, 460)) + for (col, (z, title)) in enumerate(((scan_delta, "Δ′"), (scan_di, "D_I"))) + ax = Axis(fig[1, 2col-1]; xlabel="p_ψ", ylabel="q_ψ", title) + cf = contourf!(ax, scan_pprime, scan_qprime, z; levels=range(symmetric_limits(z)...; length=41), colormap=Reverse(:RdBu)) + contour!(ax, scan_pprime, scan_qprime, z; levels=[0.0], color=:black, linewidth=2) + scatter!(ax, eq_point...; color=:green, marker=:star5, markersize=14) + Colorbar(fig[1, 2col], cf) + end + show_and_save(fig, "salpha_maps_$(case_label)") # Overlay of Δ'=0 (ballooning) and D_I=0 (Mercier) stability boundaries - p_zero = plot(; xlabel=L"p_{\psi}", ylabel=L"q_{\psi}", title="Ballooning / Mercier stability boundaries", framestyle=:box, left_margin=10Plots.mm, bottom_margin=5Plots.mm) - contour!(p_zero, scan_pprime, scan_qprime, scan_delta; levels=[0.0], color=:blue, linewidth=3, colorbar=false) - contour!(p_zero, scan_pprime, scan_qprime, scan_di; levels=[0.0], color=:red, linewidth=3, linestyle=:dash, colorbar=false) - scatter!(p_zero, [scan.reference.pprime_ref], [scan.reference.qprime_ref]; color=:green, marker=:star5, ms=8, label="equilibrium") - display(p_zero) - zero_path = joinpath(@__DIR__, "salpha_zero_contours_$(case_label).png") - Plots.savefig(p_zero, zero_path) + fig = Figure(; size=(700, 520)) + ax = Axis(fig[1, 1]; xlabel="p_ψ", ylabel="q_ψ", title="Ballooning / Mercier stability boundaries") + contour!(ax, scan_pprime, scan_qprime, scan_delta; levels=[0.0], color=:blue, linewidth=3) + contour!(ax, scan_pprime, scan_qprime, scan_di; levels=[0.0], color=:red, linewidth=3, linestyle=:dash) + scatter!(ax, eq_point...; color=:green, marker=:star5, markersize=16) + axislegend(ax, [LineElement(; color=:blue, linewidth=3), LineElement(; color=:red, linewidth=3, linestyle=:dash), + MarkerElement(; color=:green, marker=:star5, markersize=16)], ["Δ′ = 0", "D_I = 0", "equilibrium"]) + show_and_save(fig, "salpha_zero_contours_$(case_label)") # BALOO-style α vs ψ stability boundaries (1st and 2nd) over the Δ'(ψ, α) map bnd = LocalStability.ballooning_alpha_boundaries(equil) dpmap = LocalStability.ballooning_delta_prime_map(equil) - p_alpha_bnd = Analysis.ForceFreeStates.plot_ballooning_alpha_boundaries(bnd, dpmap) - display(p_alpha_bnd) - alpha_bnd_path = joinpath(@__DIR__, "alpha_stability_boundary_$(case_label).png") - Plots.savefig(p_alpha_bnd, alpha_bnd_path) - - println(" $salpha_path") - println(" $localstab_path") - println(" $maps_path") - println(" $zero_path") - println(" $alpha_bnd_path") + show_and_save(Analysis.ForceFreeStates.plot_ballooning_alpha_boundaries(bnd, dpmap), "alpha_stability_boundary_$(case_label)") end + +println("Saved figures:") +foreach(p -> println(" ", p), saved) diff --git a/examples/Solovev_ideal_example/analyze_example.jl b/examples/Solovev_ideal_example/analyze_example.jl index 91400a204..eae4a4bb4 100644 --- a/examples/Solovev_ideal_example/analyze_example.jl +++ b/examples/Solovev_ideal_example/analyze_example.jl @@ -1,17 +1,12 @@ using Pkg; Pkg.activate(joinpath(@__DIR__, "../..")) -using GeneralizedPerturbedEquilibrium, Plots +# The figures need a Makie backend from your global environment: CairoMakie saves files, GLMakie opens windows. +using GeneralizedPerturbedEquilibrium, CairoMakie using GeneralizedPerturbedEquilibrium: Analysis -isinteractive() ? plotlyjs() : gr() # Run the deck and plot its results in memory; every plot also takes the path of the gpec.h5 the run wrote. ret = GeneralizedPerturbedEquilibrium.main([@__DIR__]) display(available_plots(ret)) -p_eq = Analysis.Equilibrium.plot_equilibrium_summary(ret.equil) -p_ffs = Analysis.ForceFreeStates.plot_ffs_summary(ret.ffs) - -display(p_eq); -Plots.savefig(p_eq, joinpath(@__DIR__, "equilibrium_summary.png")); -display(p_ffs); -Plots.savefig(p_ffs, joinpath(@__DIR__, "ffs_summary.png")); +display(Analysis.Equilibrium.plot_equilibrium_summary(ret.equil; save_path=joinpath(@__DIR__, "equilibrium_summary.png"))) +display(Analysis.ForceFreeStates.plot_ffs_summary(ret.ffs; save_path=joinpath(@__DIR__, "ffs_summary.png"))) diff --git a/examples/Solovev_ideal_example_multi_n/analyze_example.jl b/examples/Solovev_ideal_example_multi_n/analyze_example.jl index 10edafd6e..ebd8d91ec 100644 --- a/examples/Solovev_ideal_example_multi_n/analyze_example.jl +++ b/examples/Solovev_ideal_example_multi_n/analyze_example.jl @@ -1,30 +1,17 @@ using Pkg; Pkg.activate(joinpath(@__DIR__, "../..")) -using GeneralizedPerturbedEquilibrium, Plots +# The figures need a Makie backend from your global environment: CairoMakie saves files, GLMakie opens windows. +using GeneralizedPerturbedEquilibrium, CairoMakie using GeneralizedPerturbedEquilibrium: Analysis -isinteractive() ? plotlyjs() : gr() # Analyze the multi-n run h5path = joinpath(@__DIR__, "gpec.h5") -# Summary plots -p_eq = Analysis.Equilibrium.plot_equilibrium_summary(h5path) -p_ffs = Analysis.ForceFreeStates.plot_ffs_summary(h5path) -p_pe = Analysis.PerturbedEquilibrium.plot_perturbed_equilibrium_summary(h5path) - -display(p_eq); -Plots.savefig(p_eq, joinpath(@__DIR__, "equilibrium_summary.png")); -display(p_ffs); -Plots.savefig(p_ffs, joinpath(@__DIR__, "ffs_summary.png")); -display(p_pe); -Plots.savefig(p_pe, joinpath(@__DIR__, "pe_summary.png")); +display(Analysis.Equilibrium.plot_equilibrium_summary(h5path; save_path=joinpath(@__DIR__, "equilibrium_summary.png"))) +display(Analysis.ForceFreeStates.plot_ffs_summary(h5path; save_path=joinpath(@__DIR__, "ffs_summary.png"))) +display(Analysis.PerturbedEquilibrium.plot_perturbed_equilibrium_summary(h5path; save_path=joinpath(@__DIR__, "pe_summary.png"))) # Analyze the single-n runs -h5path_n1 = joinpath(@__DIR__, "single_n_1", "euler_n1.h5") -h5path_n2 = joinpath(@__DIR__, "single_n_2", "euler_n2.h5") -p_n1 = Analysis.ForceFreeStates.plot_ffs_summary(h5path_n1) -p_n2 = Analysis.ForceFreeStates.plot_ffs_summary(h5path_n2) - -display(p_n1); -Plots.savefig(p_n1, joinpath(@__DIR__, "single_n_1", "ffs_summary.png")); -display(p_n2); -Plots.savefig(p_n2, joinpath(@__DIR__, "single_n_2", "ffs_summary.png")); +for n in 1:2 + dir = joinpath(@__DIR__, "single_n_$n") + display(Analysis.ForceFreeStates.plot_ffs_summary(joinpath(dir, "euler_n$n.h5"); save_path=joinpath(dir, "ffs_summary.png"))) +end diff --git a/ext/GPECMakieExt/GPECMakieExt.jl b/ext/GPECMakieExt/GPECMakieExt.jl new file mode 100644 index 000000000..0ae4fc3f1 --- /dev/null +++ b/ext/GPECMakieExt/GPECMakieExt.jl @@ -0,0 +1,38 @@ +""" + GPECMakieExt + +Makie drawing for `GeneralizedPerturbedEquilibrium.Analysis`. Loads with any Makie backend: `using CairoMakie` for +files, `using GLMakie` for interactive windows. +""" +module GPECMakieExt + +using Makie +import GeneralizedPerturbedEquilibrium.Analysis as A + +include("theme.jl") +include("fields.jl") +include("equilibrium.jl") +include("forcefreestates.jl") +include("perturbedequilibrium.jl") +include("coilforcing.jl") +include("errorfields.jl") + +# Save `fig` to `path` with the loaded backend; GLMakie writes raster images only. +function A._save(fig::Figure, path::AbstractString) + ext = lowercase(splitext(path)[2]) + backend = nameof(Makie.current_backend()) + backend === :GLMakie && ext in (".pdf", ".svg", ".eps") && + throw(ArgumentError("GLMakie writes PNG only; load CairoMakie (`using CairoMakie`) to save $ext")) + save(path, fig) + return nothing +end + +# Math is set in the theme's sans face, so accented symbols such as F̄ match the text; this applies to the session's +# LaTeX strings once GPEC's figures are loaded. +function __init__() + Makie.MathTeXEngine.set_texfont_family!(Makie.MathTeXEngine.FontFamily("TeXGyreHeros")) + A._BACKEND[] = true + return nothing +end + +end # module GPECMakieExt diff --git a/ext/GPECMakieExt/coilforcing.jl b/ext/GPECMakieExt/coilforcing.jl new file mode 100644 index 000000000..3c930bdbd --- /dev/null +++ b/ext/GPECMakieExt/coilforcing.jl @@ -0,0 +1,144 @@ +# Coil forcing figures: coils in 3D and (R, Z), the forcing spectrum, control-surface maps and the 3D boundary. + +const CF = A.CoilForcing + +const _SURFACE_GREY = RGBf(0.93, 0.94, 0.96) + +# Index runs of a conductor on the camera's side (`near = true`) or the far side of the vertical plane through the axis, +# each run extended by one point so the halves join. +function _coil_runs(x, y, azimuth, near) + side = [(xi * cos(azimuth) + yi * sin(azimuth) >= 0) == near for (xi, yi) in zip(x, y)] + runs = UnitRange{Int}[] + i = 1 + while i <= length(side) + if side[i] + j = i + while j < length(side) && side[j+1] + j += 1 + end + push!(runs, max(i - 1, 1):min(j + 1, length(side))) + i = j + 1 + else + i += 1 + end + end + return runs +end + +function CF._plot_coils_3d(coil_sets, surface, field; title=nothing, by_set=false) + return themed() do + fig = Figure(; size=(900, 560), figure_padding=(30, 10, 10, 10)) + title = something(title, surface === nothing ? "Coils" : "Plasma boundary and coils") + azimuth = 1.15π + ax = Axis3(fig[1, 1]; aspect=:data, title, titlefont=:regular, xlabel="X (m)", ylabel="Y (m)", zlabel="Z (m)", azimuth, elevation=0.32, + protrusions=(60, 20, 40, 30)) + # Same map as b_n: a positive current drives positive b_n beneath the coil. + ilim = isempty(coil_sets) ? 1.0 : max(maximum(abs, (I / 1e3 for cs in coil_sets for I in cs.currents)), floatmin()) + # CairoMakie draws in order with no depth test, so coil runs behind the plasma go before the surface and the rest + # after it; GLMakie depth-tests either way. + function coils!(near) + for (i, cs) in enumerate(coil_sets), j in 1:cs.ncoil, k in 1:cs.s + x, y, z = cs.x[j, k, :], cs.y[j, k, :], cs.z[j, k, :] + for r in _coil_runs(x, y, azimuth, near) + if by_set + lines!(ax, x[r], y[r], z[r]; color=WONG[mod1(i, length(WONG))], linewidth=3) + else + lines!(ax, x[r], y[r], z[r]; color=fill(cs.currents[j] / 1e3, length(r)), colormap=DIVERGING, colorrange=(-ilim, ilim), + linewidth=3) + end + end + end + end + coils!(false) + col = 2 + if surface !== nothing + X, Y, Z = surface + if field === nothing + # Reversed θ order turns the mesh normals outward, so the lit surface is light rather than in shadow. + surface!(ax, reverse(X; dims=1), reverse(Y; dims=1), reverse(Z; dims=1); color=fill(0.0, size(X)), colormap=[_SURFACE_GREY, _SURFACE_GREY]) + else + factor, scale = _power(field.values) + lim = something(field.clim, max(maximum(abs, field.values), floatmin())) / factor + surface!(ax, X, Y, Z; color=field.values ./ factor, colormap=DIVERGING, colorrange=(-lim, lim), shading=NoShading) + label = isempty(scale) ? field.label : rich(field.label, " ×", scale) + Colorbar(fig[1, col]; colormap=DIVERGING, limits=(-lim, lim), label, height=Relative(0.6)) + col += 1 + end + end + coils!(true) + if by_set + Legend(fig[1, col], [LineElement(; color=WONG[mod1(i, length(WONG))], linewidth=3) for i in eachindex(coil_sets)], [cs.name for cs in coil_sets]; + framevisible=false) + elseif !isempty(coil_sets) + Colorbar(fig[1, col]; colormap=DIVERGING, limits=(-ilim, ilim), label="coil current (kA)", height=Relative(0.6)) + end + fig + end +end + +function CF._plot_coils_rz(coil_sets, boundary) + return themed() do + Rs = [vec(hypot.(cs.x, cs.y)) for cs in coil_sets] + Zs = [vec(cs.z) for cs in coil_sets] + Rall = reduce(vcat, Rs; init=boundary === nothing ? Float64[] : boundary[1]) + Zall = reduce(vcat, Zs; init=boundary === nothing ? Float64[] : boundary[2]) + # Room on the right for the set names; the axis width follows the data's aspect so no band is left empty. + xlo, xhi = minimum(Rall) - 0.15, maximum(Rall) + 0.45 + ylo, yhi = minimum(Zall) - 0.1, maximum(Zall) + 0.1 + height = 620 + fig = Figure() + ax = Axis(fig[1, 1]; aspect=DataAspect(), height, width=round(Int, height * (xhi - xlo) / (yhi - ylo)), limits=((xlo, xhi), (ylo, yhi)), + xlabel="R (m)", ylabel="Z (m)", title=boundary === nothing ? "Coils" : "Coils and plasma boundary") + boundary === nothing || lines!(ax, [boundary[1]; boundary[1][1]], [boundary[2]; boundary[2][1]]; color=:black, linewidth=2) + for (i, (cs, R, Z)) in enumerate(zip(coil_sets, Rs, Zs)) + color = WONG[mod1(i, length(WONG))] + # A single toroidal loop is a point in (R, Z): draw it as a marker. + if maximum(R) - minimum(R) < 0.01 && maximum(Z) - minimum(Z) < 0.01 + scatter!(ax, [sum(R) / length(R)], [sum(Z) / length(Z)]; marker=:rect, markersize=9, color) + else + for j in 1:cs.ncoil, k in 1:cs.s + lines!(ax, hypot.(cs.x[j, k, :], cs.y[j, k, :]), cs.z[j, k, :]; color, linewidth=2) + end + end + text!(ax, maximum(R) + 0.05, sum(Z) / length(Z); text=cs.name, fontsize=11, align=(:left, :center), color) + end + resize_to_layout!(fig) + fig + end +end + +function CF._plot_forcing_spectrum(forcing_modes, mlow, mhigh, rational_m) + return themed() do + ns = sort(unique(f.n for f in forcing_modes)) + factor, scale = _power([abs(f.amplitude) for f in forcing_modes]) + ms = [f.m for f in forcing_modes] + lo, hi = something(mlow, minimum(ms)), something(mhigh, maximum(ms)) + fig = Figure(; size=(820, 440)) + ax = Axis(fig[1, 1]; xlabel="poloidal harmonic m", ylabel=rich("|Φ", subscript("x"), "(m, n)|", _unit(scale, "")), + title="Coil forcing spectrum on the control surface", limits=((lo - 1.5, hi + 1.5), (0, nothing))) + isempty(rational_m) || vlines!(ax, rational_m; color=(:black, 0.15), linewidth=1) + for (i, n) in enumerate(ns) + sel = sort([f for f in forcing_modes if f.n == n]; by=f -> f.m) + m = [f.m for f in sel] + a = [abs(f.amplitude) / factor for f in sel] + stairs!(ax, vcat(m[1] - 1, m, m[end] + 1), vcat(0.0, a, 0.0); step=:center, color=WONG[i], linewidth=2, label="n = $n") + end + length(ns) > 1 && axislegend(ax; position=:rt) + fig + end +end + +function CF._plot_surface_map(d; title, arb=false) + return themed() do + values, scale = _decade(d.field) + fig = Figure(; size=(900, 500)) + ax = Axis(fig[1, 1]; title) + hm = _surface_slice!(ax, d.phi, d.theta, values) + d.coils === nothing || _coil_traces!(ax, d.coils) + xlims!(ax, 0, 360) + ylims!(ax, -180, 180) + label = arb ? rich("b", subscript("n"), " (arb.)") : rich("b", subscript("n"), _unit(scale, "T")) + Colorbar(fig[1, 2], hm; label) + fig + end +end diff --git a/ext/GPECMakieExt/equilibrium.jl b/ext/GPECMakieExt/equilibrium.jl new file mode 100644 index 000000000..348c1e3e5 --- /dev/null +++ b/ext/GPECMakieExt/equilibrium.jl @@ -0,0 +1,145 @@ +# Equilibrium figures: profiles against ψ_N, flux surfaces in (R, Z), the summary and the Grad-Shafranov residual. + +const EQ = A.Equilibrium + +# A rational q as "m/n", e.g. 2 → "2/1" and 1.5 → "3/2". +function _rational_label(q) + r = rationalize(q; tol=1e-6) + return "$(numerator(r))/$(denominator(r))" +end + +# One profile against ψ_N, scaled by a power of ten when needed. +function _profile!(ax, psi, y, label) + yv, scale = _decade(y) + ax.ylabel = isempty(scale) ? label : rich(label, " (", scale, ")") + lines!(ax, psi, yv; color=WONG[1], linewidth=2) + xlims!(ax, 0, 1) + return ax +end + +# Rational surfaces labelled as m/n along the top of `ax`. +function _rationals_on_top!(pos, ax, d) + isempty(d.sing_psi) && return nothing + return _surfaces_on_top!(pos, ax, d.sing_psi, _rational_label.(d.sing_q)) +end + +function _single_profile(d, y, label) + fig = Figure(; size=(760, 420)) + ax = Axis(fig[1, 1]; xlabel=PSIN) + _profile!(ax, d.psi, y, label) + _rationals_on_top!(fig[1, 1], ax, d) + return fig +end + +function EQ._plot_qprofile(d; show_singular=true) + return themed() do + fig = Figure(; size=(760, 420)) + ax = Axis(fig[1, 1]; xlabel=PSIN) + _profile!(ax, d.psi, d.q, "q") + # q₀ labelled at the edge and q₉₅ at the axis, where q is far from each. + for (v, name, x, align) in ((d.q0, "0", 0.98, (:right, :bottom)), (d.q95, "95", 0.02, (:left, :bottom))) + hlines!(ax, v; color=(:black, 0.35), linestyle=:dot, linewidth=1) + text!(ax, x, v; text=rich("q", subscript(name), " = ", _num(v)), align, fontsize=12, color=(:black, 0.6)) + end + show_singular && _rationals_on_top!(fig[1, 1], ax, d) + fig + end +end + +EQ._plot_pressure_profile(d) = themed(() -> _single_profile(d, d.mu0p, rich("μ", subscript("0"), "p (T", superscript("2"), ")"))) +EQ._plot_f_profile(d) = themed(() -> _single_profile(d, d.twopif, rich("2πF = RB", subscript("φ"), " (T m)"))) + +_closed(v) = [v; v[1]] + +# Flux surfaces in (R, Z): `n_psi` surfaces, `n_theta` constant-θ spokes, the rational surfaces in colour, the axis. +function _flux_surfaces!(ax, d; n_psi=11, n_theta=0) + R, Z = EQ._rz_grid(d) + ax.aspect = DataAspect() + ax.xlabel = "R (m)" + ax.ylabel = "Z (m)" + if n_theta > 0 + for j in round.(Int, range(1, length(d.theta); length=n_theta + 1))[1:(end-1)] + lines!(ax, R[:, j], Z[:, j]; color=(:black, 0.15), linewidth=0.7) + end + end + for i in round.(Int, range(2, length(d.psi); length=n_psi)) + lines!(ax, _closed(R[i, :]), _closed(Z[i, :]); color=(:black, 0.45), linewidth=0.8) + end + colors = Makie.resample_cmap(:viridis, length(d.sing_psi) + 2) + for (k, (ps, q)) in enumerate(zip(d.sing_psi, d.sing_q)) + i = argmin(abs.(d.psi .- ps)) + lines!(ax, _closed(R[i, :]), _closed(Z[i, :]); color=colors[k+1], linewidth=2, label="q = $(_rational_label(q))") + end + scatter!(ax, [d.ro], [d.zo]; marker=:cross, color=:black, markersize=10) + return ax +end + +# The rational-surface legend below a flux-surface axis. +function _surface_legend!(fig, slot, ax, d) + isempty(d.sing_psi) && return nothing + return Legend(fig[slot...], ax; orientation=:horizontal, nbanks=cld(length(d.sing_psi), 4), tellheight=true, framevisible=false) +end + +function EQ._plot_flux_surfaces(d; n_psi=11, n_theta=18) + return themed() do + fig = Figure(; size=(560, 760)) + ax = Axis(fig[1, 1]; title="Flux surfaces") + _flux_surfaces!(ax, d; n_psi, n_theta) + _surface_legend!(fig, (2, 1), ax, d) + fig + end +end + +function EQ._plot_equilibrium_summary(g, prof, geo) + return themed() do + fig = Figure(; size=(1150, 760)) + head = Any[] + for (sym, v, unit) in ((sub("q", "0"), g.q0, ""), (sub("q", "95"), g.q95, ""), (sub("β", "N"), g.betan, ""), ("κ", g.kappa, ""), + (sub("I", "p"), g.ip, " MA"), (sub("B", "T"), g.bt, " T")) + isnothing(v) && continue + push!(head, sym, " = ", _num(v), unit, " ") + end + Label(fig[0, 1:2], rich(head...); fontsize=15, tellwidth=false, halign=:left) + ax = Axis(fig[1:3, 1]; title="Flux surfaces") + _flux_surfaces!(ax, geo) + _surface_legend!(fig, (4, 1), ax, geo) + rows = ((prof.q, "q"), (prof.mu0p, rich("μ", subscript("0"), "p (T", superscript("2"), ")")), (prof.twopif, rich("2πF = RB", subscript("φ"), " (T m)"))) + axs = Axis[] + for (r, (y, label)) in enumerate(rows) + a = Axis(fig[r, 2]; xlabel=r == 3 ? PSIN : "", xticklabelsvisible=r == 3) + isempty(prof.sing_psi) || vlines!(a, prof.sing_psi; color=(:black, 0.15), linewidth=1) + _profile!(a, prof.psi, y, label) + push!(axs, a) + end + linkxaxes!(axs...) + _rationals_on_top!(fig[1, 2], axs[1], prof) + colsize!(fig.layout, 1, Relative(0.36)) + rowgap!(fig.layout, 8) + colgap!(fig.layout, 24) + fig + end +end + +# θ in degrees on (−180, 180], 0 at the outboard midplane. +_theta_deg(y) = (d = 360y; d > 180 ? d - 360 : d) + +function EQ._plot_gse(gse; n_theta_lines=8) + return themed() do + idx = round.(Int, range(1, length(gse.ys); length=n_theta_lines + 1))[1:(end-1)] + emax, imax = findmax(gse.error) + fig = Figure(; size=(780, 460)) + ax = Axis(fig[1, 1]; yscale=log10, xlabel=PSIN, ylabel="Grad–Shafranov residual", + title=rich("Grad–Shafranov residual: max ", _num(emax), " at ", PSIN, " = ", _num(gse.xs[imax[1]]))) + for i in idx + e = gse.error[:, i] + keep = e .> 0 + lines!(ax, gse.xs[keep], e[keep]; color=_theta_deg(gse.ys[i]), colormap=:romaO, colorrange=(-180, 180), linewidth=1.2) + end + keep = gse.errori .> 0 + lines!(ax, gse.xs[keep], gse.errori[keep]; color=:black, linewidth=2.4, label="θ-integrated") + xlims!(ax, 0, 1) + axislegend(ax; position=:lt) + Colorbar(fig[1, 2]; colormap=:romaO, limits=(-180, 180), ticks=-180:90:180, label="poloidal angle θ (deg)") + fig + end +end diff --git a/ext/GPECMakieExt/errorfields.jl b/ext/GPECMakieExt/errorfields.jl new file mode 100644 index 000000000..a406c5593 --- /dev/null +++ b/ext/GPECMakieExt/errorfields.jl @@ -0,0 +1,607 @@ +# ErrorFields figures. Several runs can be drawn together, told apart by colour and named in the legend; a single run +# carries no label. + +const EFA = A.ErrorFields +const _EF = EFA.EF + +_with_label(lbl, s) = isempty(lbl) ? s : "$lbl: $s" +_pct(x) = x == 0 ? "0 %" : x < 0.01 ? "<0.01 %" : "$(_num(x)) %" +_steps(m, a) = (vcat(m[1] - 1, m, m[end] + 1), vcat(0.0, a, 0.0)) + +# Log ticks at whole decades labelled 10ⁿ, with 2× and 5× added when the range spans fewer than three decades. +function _decade_ticks(lo, hi) + k0, k1 = floor(Int, log10(lo)), ceil(Int, log10(hi)) + vals, labels = Float64[], Any[] + for k in k0:k1, f in (k1 - k0 <= 3 ? (1, 2, 5) : (1,)) + v = f * 10.0^k + lo <= v <= hi || continue + push!(vals, v) + push!(labels, f == 1 ? rich("10", superscript(string(k))) : rich("$(f)×10", superscript(string(k)))) + end + return (vals, labels) +end + +# The largest and smallest bin centres where any of the densities is above 10⁻³ of its peak, so empty tails are not drawn. +_support_end(c, pdfs...) = maximum(p -> c[findlast(>(1e-3 * maximum(p)), p)], pdfs) +_support_start(c, pdfs...) = minimum(p -> c[findfirst(>(1e-3 * maximum(p)), p)], pdfs) + +# Rows of stems and dots: one row per name, one dot per source, on a linear or log value axis. Values that are roundoff +# against the largest are left out; returns how many rows that removed. +function _stem_rows!(ax, names, values, labels; log=false) + big = maximum(v -> maximum(x -> isfinite(x) ? x : 0.0, v; init=0.0), values; init=0.0) + keep = [any(v -> isfinite(v[i]) && v[i] > EFA._ROUNDOFF * big, values) for i in eachindex(names)] + rows = findall(keep) + isempty(rows) && throw(ArgumentError("no coil set has a value above roundoff")) + order = rows[sortperm([maximum(v -> isfinite(v[i]) ? v[i] : 0.0, values) for i in rows])] + k = length(values) + shown = [v[i] for v in values for i in order if isfinite(v[i]) && v[i] > 0] + base = log ? minimum(shown) / 3 : 0.0 + ax.yticks = (1:length(order), names[order]) + ax.ygridvisible = false + for (j, v) in enumerate(values) + y = (1:length(order)) .+ (k == 1 ? 0.0 : (j - (k + 1) / 2) * 0.22) + x = [v[i] for i in order] + ok = [isfinite(xi) && xi > (log ? 0 : -Inf) for xi in x] + rangebars!(ax, y[ok], fill(base, count(ok)), x[ok]; direction=:x, color=(WONG[j], 0.35), whiskerwidth=0, linewidth=2) + scatter!(ax, x[ok], y[ok]; color=WONG[j], markersize=10, label=isempty(labels[j]) ? nothing : labels[j]) + end + log && (ax.xticks = _decade_ticks(base, 1.5 * maximum(shown))) + xlims!(ax, log ? base : 0.0, (log ? 1.6 : 1.1) * maximum(shown)) + k > 1 && axislegend(ax; position=:rb) + return length(names) - length(order) +end + +const _SENSITIVITY_TEXT = Dict( + :shift => ("|δ| per mm of shift", "Dominant-mode overlap per mm of shift"), + :tilt => ("|δ| per degree of tilt", "Dominant-mode overlap per degree of tilt"), + :rim => ("|δ| per mm of rim displacement", "Dominant-mode overlap per mm of rim displacement"), + :as_designed => ("as-designed |δ|", "As-designed dominant-mode overlap"), + :fraction => ("resonant fraction of |b̃| (%)", "Resonant fraction of each coil set's applied field") +) + +function EFA._plot_coil_sensitivities(labels, names, values, quantity, yscale) + return themed() do + xlabel, title = _SENSITIVITY_TEXT[quantity] + fig = Figure() + ax = Axis(fig[1, 1]; xlabel, xscale=yscale === :log10 ? log10 : identity, xminorticksvisible=false, width=640) + omitted = _stem_rows!(ax, names, values, labels; log=yscale === :log10) + ax.height = 30 * (length(names) - omitted) + 30 + ax.title = omitted == 0 ? title : "$title ($omitted at roundoff not shown)" + resize_to_layout!(fig) + fig + end +end + +function EFA._plot_tolerance_pdf(data; corrected=true, normalize=false, xscale=:identity, show_batches=false) + return themed() do + fig = Figure(; size=(820, 460)) + # An as-designed overlap that is roundoff against the sampled mean (axisymmetric coils only) is not marked. + designed(d) = d.abs_delta_total_as_designed > EFA._ROUNDOFF * d.abs_delta_sampled_mean + d1 = data[1][2] + title = + length(data) == 1 ? "Sampled |δ|: mean $(_num(d1.abs_delta_sampled_mean))" * (designed(d1) ? ", as designed $(_num(d1.abs_delta_total_as_designed))" : "") : + "Tolerance Monte Carlo" + xmax = maximum(d -> 1.05 * _support_end(EFA._centers(d.abs_delta_bin_edges), d.abs_delta_pdf, d.abs_delta_efc_pdf), last.(data)) + xmin = xscale === :log10 ? minimum(d -> _support_start(EFA._centers(d.abs_delta_bin_edges), d.abs_delta_pdf, d.abs_delta_efc_pdf), last.(data)) / 1.5 : 0 + ax = Axis(fig[1, 1]; xlabel="dominant-mode overlap |δ|", ylabel=normalize ? "probability density / its peak" : "probability density", title, + xscale=xscale === :log10 ? log10 : identity, xminorticksvisible=false, limits=((xmin, xmax), (0, nothing)), + xticks=xscale === :log10 ? _decade_ticks(xmin, xmax) : Makie.automatic, xtickformat=xscale === :log10 ? Makie.automatic : (vs -> _num.(vs))) + for (j, (lbl, d)) in enumerate(data) + c = EFA._centers(d.abs_delta_bin_edges) + keep = xscale === :log10 ? c .> 0 : trues(length(c)) + scale = normalize ? maximum(d.abs_delta_pdf) : 1.0 + if show_batches && d.abs_delta_pdf_batches !== nothing + nb = size(d.abs_delta_pdf_batches, 2) + for b in 1:nb + lines!(ax, c[keep], d.abs_delta_pdf_batches[keep, b] ./ scale; color=(WONG[j], 0.3), linewidth=1, + label=b == 1 ? _with_label(lbl, "$nb batches") : nothing) + end + end + beyond = show_batches && d.clamped_fraction !== nothing && d.clamped_fraction > 0 ? " ($(_pct(100d.clamped_fraction)) beyond the last bin)" : "" + lines!(ax, c[keep], d.abs_delta_pdf[keep] ./ scale; color=WONG[j], linewidth=2, label=_with_label(lbl, "intrinsic" * beyond)) + corrected && lines!(ax, c[keep], d.abs_delta_efc_pdf[keep] ./ (normalize ? maximum(d.abs_delta_efc_pdf) : 1.0); color=WONG[j], linewidth=2, + linestyle=:dash, label=_with_label(lbl, "corrected")) + designed(d) && vlines!(ax, d.abs_delta_total_as_designed; color=WONG[j], linestyle=:dot, linewidth=1.5, label=_with_label(lbl, "as designed")) + end + axislegend(ax; position=:rt) + fig + end +end + +function _phasor_panel!(gl, p) + total = sum(p.delta) + w = p.delta .* cis(-angle(total)) + tip = cumsum(w) + tail = vcat(zero(ComplexF64), tip[1:(end-1)]) + big = sortperm(abs.(w); rev=true)[1:min(6, length(w))] + grey = RGBAf(0.5, 0.5, 0.5, 0.6) + colour(k) = k in big ? WONG[findfirst(==(k), big)] : grey + span = maximum(abs, vcat(0.0, real.(tip), imag.(tip))) + ax = Axis(gl[1, 1]; aspect=DataAspect(), xlabel="Re δ (rotated so the total is real)", ylabel="Im δ", + limits=((-0.1span, 1.1span), (-0.45span, 0.45span)), title=_with_label(p.label, "As-designed overlaps head to tail: |Σδ| = $(_num(abs(total)))")) + arrows2d!(ax, [Point2f(0, 0)], [Vec2f(abs(total), 0)]; color=(:black, 0.25), shaftwidth=8, tipwidth=18, tiplength=16) + arrows2d!(ax, Point2f.(real.(tail), imag.(tail)), Vec2f.(real.(w), imag.(w)); color=[colour(k) for k in eachindex(w)], shaftwidth=2.5, tipwidth=10, + tiplength=10) + elements = Any[LineElement(; color=colour(k), linewidth=3) for k in big] + labels = ["$(p.names[k]) |δ| = $(_num(abs(w[k])))" for k in big] + if length(w) > length(big) + push!(elements, LineElement(; color=grey, linewidth=3)) + push!(labels, "the other $(length(w) - length(big))") + end + push!(elements, LineElement(; color=(:black, 0.25), linewidth=8)) + push!(labels, "total") + Legend(gl[1, 2], elements, labels; framevisible=false) + return ax +end + +function EFA._plot_overlap_phasors(panels) + return themed() do + fig = Figure() + for (j, p) in enumerate(panels) + gl = fig[1, j] = GridLayout() + _phasor_panel!(gl, p) + end + for c in contents(fig.layout) + c isa GridLayout && for a in contents(c) + a isa Axis && (a.width = 520; a.height = 400) + end + end + resize_to_layout!(fig) + fig + end +end + +const _BUDGET_COLOURS = (RGBf(0.4, 0.4, 0.4), WONG[1], WONG[2], WONG[3]) + +function EFA._plot_tolerance_budget(labels, parts, worst, mc) + return themed() do + total = sum(p[2] for p in parts) + factor, scale = _power(total) + m = length(labels) + fig = Figure(; size=(900, max(380, 120 + 26 * m))) + ax = Axis(fig[1, 1]; yticks=(1:m, labels), xlabel=rich("worst-case contribution to |δ|", _unit(scale, "")), ygridvisible=false, + title="Tolerance budget: Σ = $(_num(worst)) worst case", limits=((0, nothing), (0.4, m + 0.6))) + x = repeat(1:m; outer=length(parts)) + h = reduce(vcat, (p[2] ./ factor for p in parts)) + g = repeat(1:length(parts); inner=m) + barplot!(ax, x, h; stack=g, color=[_BUDGET_COLOURS[i] for i in g], direction=:x) + used = [any(>(EFA._ROUNDOFF * maximum(total)), p[2]) for p in parts] + elements = Any[PolyElement(; color=c) for c in _BUDGET_COLOURS][used] + names = [p[1] for p in parts][used] + if mc !== nothing + if mc.abs_delta_sampled_mean !== nothing + vlines!(ax, mc.abs_delta_sampled_mean / factor; color=:black, linestyle=:dash, linewidth=1.5) + push!(elements, LineElement(; color=:black, linestyle=:dash)) + push!(names, "Monte Carlo mean |δ|: $(_num(mc.abs_delta_sampled_mean))") + end + if mc.abs_delta_total_as_designed !== nothing && mc.abs_delta_total_as_designed > EFA._ROUNDOFF * worst + vlines!(ax, mc.abs_delta_total_as_designed / factor; color=:black, linestyle=:dot, linewidth=1.5) + push!(elements, LineElement(; color=:black, linestyle=:dot)) + push!(names, "as designed |Σδ|: $(_num(mc.abs_delta_total_as_designed))") + end + end + Legend(fig[1, 2], elements, names; framevisible=false) + fig + end +end + +const _TAPS = ("shift x", "shift y", "shift z", "tilt x", "tilt y", "tilt z") + +function EFA._plot_linearity_residuals(panels, at, yscale) + return themed() do + fig = Figure(; size=(820 * length(panels), 500)) + for (j, p) in enumerate(panels) + # Coil sets whose residuals are all zero have nothing to draw. + shown = [any(v -> isfinite(v) && v > 0, p.residual[:, i]) for i in eachindex(p.names)] + names, r = p.names[shown], p.residual[:, shown] + n = length(names) + title = _with_label(p.label, "Linearity of the rigid-motion response") + all(shown) || (title *= " ($(count(!, shown)) with zero residuals not shown)") + ax = Axis(fig[1, j]; xticks=(1:n, names), xticklabelrotation=π / 6, yscale=yscale === :log10 ? log10 : identity, yminorticksvisible=false, + ylabel=at === :step ? "2nd difference / largest 1st, at the FD step" : "2nd difference / largest 1st, at the tolerance", title) + positive = filter(v -> isfinite(v) && v > 0, vcat(vec(r), 1e-2)) + base = yscale === :log10 ? minimum(positive) / 3 : 0.0 + yscale === :log10 && (ax.yticks = _decade_ticks(base, 3 * maximum(positive))) + for t in 1:6 + x = (1:n) .+ (t - 3.5) * (0.8 / 6) + ok = [isfinite(v) && v > (yscale === :log10 ? 0 : -Inf) for v in r[t, :]] + rangebars!(ax, x[ok], fill(base, count(ok)), r[t, ok]; color=(WONG[t], 0.4), whiskerwidth=0, linewidth=2) + scatter!(ax, x[ok], r[t, ok]; color=WONG[t], markersize=7, label=_TAPS[t]) + end + hlines!(ax, 1e-2; color=:black, linestyle=:dash, linewidth=1, label="1 %") + ylims!(ax, base, (yscale === :log10 ? 3 : 1.15) * maximum(positive)) + axislegend(ax; position=:rt, nbanks=2) + end + fig + end +end + +const _CHECK_MARKERS = (:circle, :diamond, :utriangle, :rect, :star5, :hexagon) + +function EFA._plot_linearity_check(check) + return themed() do + names = unique(check.name) + scales = sort(unique(check.scale)) + positive = filter(v -> isfinite(v) && v > 0, vcat(check.relative_error, 1e-2)) + base = minimum(positive) / 3 + fig = Figure(; size=(1000, 520)) + ax = Axis(fig[1, 1]; xticks=(1:length(names), names), xticklabelrotation=π / 6, yscale=log10, yminorticksvisible=false, + ylabel=rich("|δ", subscript("actual"), " − δ", subscript("linear"), "| / |δ", subscript("linear"), " − δ", subscript("as designed"), "|"), + title="Linear model error at finite displacements", limits=(nothing, (base, 3 * maximum(positive)))) + nslot = 4 * length(scales) + slot = 0 + for (k, kind) in enumerate((:shift, :tilt)), a in 1:2, (si, s) in enumerate(scales) + slot += 1 + rows = findall(i -> check.kind[i] === kind && check.axis[i] == a && check.scale[i] == s && isfinite(check.relative_error[i]) && + check.relative_error[i] > 0, eachindex(check.name)) + isempty(rows) && continue + x = [findfirst(==(check.name[i]), names) + (slot - (nslot + 1) / 2) * (0.8 / nslot) for i in rows] + y = check.relative_error[rows] + rangebars!(ax, x, fill(base, length(x)), y; color=(WONG[k], 0.35), whiskerwidth=0, linewidth=1.5) + scatter!(ax, x, y; color=WONG[k], marker=_CHECK_MARKERS[mod1(si, length(_CHECK_MARKERS))], markersize=8, + label="$kind $(a == 1 ? "x" : "y") × $(_num(s))") + end + hlines!(ax, 1e-2; color=:black, linestyle=:dash, linewidth=1, label="1 %") + Legend(fig[1, 2], ax; framevisible=false, merge=true) + fig + end +end + +function _risk_panel!(pos, data; corrected=true, target_percent=nothing) + s = data[1][2].scan_scale + title = "Locking risk against tolerance scale" + if target_percent !== nothing && length(data) == 1 + scan = _risk_scan(data[1][2]) + a, ac = _EF.allowable_tolerance(scan, target_percent), _EF.allowable_tolerance(scan, target_percent; corrected=true) + title = + "Allowable scale for $(_num(target_percent)) % risk: $(isnan(a) ? "beyond the scan" : _num(a))" * + (corrected ? " ($(isnan(ac) ? "beyond the scan" : _num(ac)) corrected)" : "") + end + # Log-log when any probability is positive; a scale where no sample locked (P = 0) is then left off. + curves(d) = ((d.scan_locking_probability_percent, d.scan_locking_probability_spread_percent, "intrinsic", :solid, :circle), + (d.scan_locking_probability_efc_percent, d.scan_locking_probability_efc_spread_percent, "corrected", :dash, :rect)) + positive = [v for (_, d) in data for (p, _, name) in curves(d) if corrected || name == "intrinsic" for v in p if v > 0] + logy = !isempty(positive) + shown = target_percent === nothing ? positive : [positive; target_percent] + lo, hi = logy ? (minimum(shown) / 3, 3 * maximum(shown)) : (0.0, nothing) + ax = Axis(pos; xscale=log10, xminorticksvisible=false, xticks=(s, _num.(s)), xlabel="tolerance scale (1 = design)", ylabel="locking probability (%)", + title, yscale=logy ? log10 : identity, yminorticksvisible=false, yticks=logy ? _decade_ticks(lo, hi) : Makie.automatic, + limits=((minimum(s) / 1.3, 1.3 * maximum(s)), (lo, hi))) + target_percent === nothing || hlines!(ax, target_percent; color=:black, linestyle=:dash, linewidth=1) + vlines!(ax, 1.0; color=(:black, 0.3), linewidth=1) + for (j, (lbl, d)) in enumerate(data) + for (p, spread, name, style, marker) in curves(d) + (name == "corrected" && !corrected) && continue + k = logy ? p .> 0 : trues(length(p)) + any(k) || continue + # The lower bar stops short of zero so it stays on a log axis. + errorbars!(ax, d.scan_scale[k], p[k], min.(spread[k] ./ 2, 0.9 .* p[k]), spread[k] ./ 2; color=(WONG[j], 0.5), whiskerwidth=6) + scatterlines!(ax, d.scan_scale[k], p[k]; color=WONG[j], linestyle=style, marker, markersize=8, label=_with_label(lbl, name)) + end + end + axislegend(ax; position=:lt) + return ax +end + +_risk_scan(d) = _EF.ToleranceScan(d.scan_scale, d.scan_locking_probability_percent, d.scan_locking_probability_efc_percent, + d.scan_locking_probability_spread_percent, d.scan_locking_probability_efc_spread_percent, 0.0) + +function EFA._plot_locking_risk(data; corrected=true, target_percent=nothing) + return themed() do + fig = Figure(; size=(820, 460)) + _risk_panel!(fig[1, 1], data; corrected, target_percent) + fig + end +end + +# The overlap distributions on a log |δ| axis against the threshold's CDF on a 0–1 right axis, with the nominal +# threshold at the median. +function _threshold_panel!(pos, data) + lo, hi = Inf, 0.0 + for (_, d, T) in data + c = EFA._centers(d.abs_delta_bin_edges) + keep = c .> 0 + support = c[keep][d.abs_delta_pdf[keep].>1e-3*maximum(d.abs_delta_pdf)] + lo = min(lo, minimum(support) / 3) + hi = max(hi, T === nothing ? d.abs_delta_bin_edges[end] : 3 * T[ceil(Int, 0.99 * length(T))], 3 * d.threshold_fit) + end + d1 = data[1][2] + title = + length(data) == 1 && d1.locking_probability_percent !== nothing ? + rich("P", subscript("lock"), " at design tolerances: $(_pct(d1.locking_probability_percent)) ($(_pct(d1.locking_probability_efc_percent)) corrected)") : + "Overlap against the penetration threshold" + ticks = _decade_ticks(lo, hi) + a = Axis(pos; xscale=log10, xminorticksvisible=false, xticks=ticks, limits=((lo, hi), (0, nothing)), xlabel="dominant-mode overlap |δ|", + ylabel="probability density of |δ|", title) + b = Axis(pos; xscale=log10, xminorticksvisible=false, xticks=ticks, limits=((lo, hi), (0, 1)), yaxisposition=:right, ylabel="P(lock | δ)", + xgridvisible=false, ygridvisible=false) + hidexdecorations!(b) + hidespines!(b) + linkxaxes!(a, b) + elements, names = Any[], String[] + threshold_colour = WONG[6] + for (j, (lbl, d, T)) in enumerate(data) + c = EFA._centers(d.abs_delta_bin_edges) + keep = c .> 0 + lines!(a, c[keep], d.abs_delta_pdf[keep]; color=WONG[j], linewidth=2) + lines!(a, c[keep], d.abs_delta_efc_pdf[keep]; color=WONG[j], linewidth=2, linestyle=:dash) + append!(elements, [LineElement(; color=WONG[j], linewidth=2), LineElement(; color=WONG[j], linewidth=2, linestyle=:dash)]) + append!(names, [_with_label(lbl, "intrinsic"), _with_label(lbl, "corrected")]) + if T === nothing + lines!(b, d.abs_delta_bin_edges[2:end], d.locking_probability_given_delta[2:end]; color=threshold_colour, linewidth=2.5) + else + xg = exp10.(range(log10(lo), log10(hi); length=400)) + lines!(b, xg, [searchsortedlast(T, x) / length(T) for x in xg]; color=threshold_colour, linewidth=2.5) + end + scatter!(b, [d.threshold_fit], [0.5]; color=threshold_colour, marker=:diamond, markersize=12) + vlines!(b, d.threshold_fit; color=(threshold_colour, 0.5), linestyle=:dot, linewidth=1.5) + end + append!(elements, [LineElement(; color=threshold_colour, linewidth=2.5), MarkerElement(; color=threshold_colour, marker=:diamond, markersize=12)]) + append!(names, ["threshold CDF", length(data) == 1 ? "nominal threshold $(_num(d1.threshold_fit))" : "nominal threshold"]) + axislegend(a, elements, names; position=:lt) + return a +end + +function EFA._plot_threshold_scaling(data) + return themed() do + fig = Figure(; size=(860, 480)) + _threshold_panel!(fig[1, 1], data) + fig + end +end + +function EFA._plot_dominant_mode_spectrum(data, modes) + return themed() do + fig = Figure(; size=(820, 440)) + ax = Axis(fig[1, 1]; xlabel="poloidal harmonic m", ylabel=rich("|V", subscript("m"), "| (unit norm)"), title="Dominant resonant-coupling modes", + limits=(nothing, (0, nothing))) + styles = (:solid, :dash, :dot, :dashdot) + for (j, (lbl, d)) in enumerate(data), k in modes + k <= size(d.dominant_v, 2) || continue + for n in unique(d.mn_index[:, 2]) + rows = findall(==(n), d.mn_index[:, 2]) + label = _with_label(lbl, "mode $k, σ = $(_num(d.singular_values[k]))") + stairs!(ax, _steps(d.mn_index[rows, 1], abs.(d.dominant_v[rows, k]))...; step=:center, color=WONG[k], linestyle=styles[mod1(j, 4)], + linewidth=k == 1 ? 2.5 : 1.5, label) + end + end + axislegend(ax; position=:rt, merge=true) + fig + end +end + +function EFA._plot_applied_spectra(m, v, spectra; normalize=true) + return themed() do + fig = Figure(; size=(820, 440)) + factor, scale = normalize ? (1.0, "") : _power(reduce(vcat, (s for (_, s) in spectra))) + ax = Axis(fig[1, 1]; xlabel="poloidal harmonic m", ylabel=normalize ? "amplitude / its own peak" : rich("|b̃|", _unit(scale, "T")), + title="Applied spectra against the dominant mode", limits=(nothing, (0, normalize ? 1.08 : nothing))) + if normalize + sx, sy = _steps(m, v ./ maximum(v)) + px = reduce(vcat, ([x - 0.5, x + 0.5] for x in sx)) + py = reduce(vcat, ([y, y] for y in sy)) + band!(ax, px, zeros(length(py)), py; color=(:black, 0.08)) + stairs!(ax, sx, sy; step=:center, color=:black, linewidth=2, label="dominant mode |V|") + end + for (j, (name, a)) in enumerate(spectra) + stairs!(ax, _steps(m, normalize ? a ./ maximum(a) : a ./ factor)...; step=:center, color=WONG[mod1(j, 7)], linewidth=2, label=name) + end + axislegend(ax; position=:rt) + fig + end +end + +function EFA._plot_overlap_contributions(m, curves) + return themed() do + fig = Figure(; size=(820, 440)) + ax = Axis(fig[1, 1]; xlabel="poloidal harmonic m", ylabel="contribution to the resonant fraction (%)", title="Which harmonics make each coil's overlap") + hlines!(ax, 0; color=:black, linewidth=1) + for (j, (name, fraction, c)) in enumerate(curves) + stairs!(ax, _steps(m, 100 .* c)...; step=:center, color=WONG[mod1(j, 7)], linewidth=2, label="$name: sums to $(_num(fraction)) %") + end + axislegend(ax; position=:rt) + fig + end +end + +function EFA._plot_surface_overlay(maps, mode) + return themed() do + k = length(maps) + ncol = k <= 3 ? k : 2 + nrow = cld(k, ncol) + fig = Figure(; size=(k == 1 ? 720 : 470 * ncol + 100, 340 * nrow + 60)) + mpeak = maximum(abs, mode.bn) + local hm + for (j, (name, bm)) in enumerate(maps) + r, c = fldmod1(j, ncol) + peak = maximum(abs, bm.bn) + bottom = r == nrow + ax = Axis(fig[r, c]; title=rich("$name: peak |b", subscript("n"), "| = $(_num(peak)) T"), yticks=-180:90:180, xticks=0:90:360, + xlabel=bottom ? "toroidal angle φ (deg)" : "", ylabel=c == 1 ? "poloidal angle θ (deg)" : "", xticklabelsvisible=bottom, + yticklabelsvisible=c == 1) + hm = heatmap!(ax, rad2deg.(bm.zeta), rad2deg.(bm.theta), permutedims(bm.bn ./ peak); colormap=DIVERGING, colorrange=(-1, 1)) + mpeak > 0 && contour!(ax, rad2deg.(mode.zeta), rad2deg.(mode.theta), permutedims(mode.bn ./ mpeak); levels=[-0.5, -0.2, 0.2, 0.5], color=:black, + linewidth=1) + end + Colorbar(fig[1:nrow, ncol+1], hm; label=rich("b", subscript("n"), " / its peak (contours: dominant mode)")) + fig + end +end + +function EFA._plot_phasing_map(map, quantity, extreme) + return themed() do + arr = quantity === :delta_per_kat ? map.delta_per_kat : map.resonant_fraction_percent + label = quantity === :delta_per_kat ? "|δ| per kAt of the arrays" : "resonant fraction (%)" + names = map.coil_names + relative(a, b) = "toroidal phase of $b relative to $a (deg)" + if length(map.phase_deg) == 1 + flat = maximum(arr) - minimum(arr) <= 1e-6 * maximum(abs, arr) + fig = Figure(; size=(820, 440)) + ax = Axis(fig[1, 1]; xlabel=relative(names[1], names[2]), ylabel=label, xticks=0:90:360, + title=flat ? "$label: independent of phase" : "$label: $(_num(minimum(arr)))–$(_num(maximum(arr))) over the phase") + lines!(ax, map.phase_deg[1], vec(arr); color=WONG[1], linewidth=2) + flat && ylims!(ax, 0, 1.2 * maximum(arr)) + else + fig = Figure(; size=(720, 620)) + v, ph = extreme + ax = Axis(fig[1, 1]; aspect=DataAspect(), xlabel=relative(names[1], names[2]), ylabel=relative(names[2], names[3]), + title="$label: largest $(_num(v)) at ($(_num(ph[1]))°, $(_num(ph[2]))°)") + cf = contourf!(ax, map.phase_deg[1], map.phase_deg[2], arr; levels=12, colormap=:viridis) + scatter!(ax, [ph[1]], [ph[2]]; marker=:star5, markersize=16, color=:white, strokecolor=:black, strokewidth=1) + Colorbar(fig[1, 2], cf; label) + end + fig + end +end + +function EFA._plot_efc_ntv_limits(curves, threshold, budget; band=false, profiles=false) + return themed() do + couplings = first.(curves) + scanned = filter(_EF.has_rotation_scan, couplings) + npanel = 1 + !isempty(scanned) + profiles + fig = Figure(; size=(700 + 520 * (npanel - 1), 480)) + ax = Axis(fig[1, 1]; xlabel=rich("intrinsic overlap δ", subscript("EF"), " / nominal threshold"), ylabel="correction current (kAt)", + title="Correction current with its own NTV torque ($(_num(budget)) N·m budget)", limits=((0, nothing), (0, nothing))) + xmax, ymax = 0.0, 0.0 + for (j, (c, cv)) in enumerate(curves) + x = cv.delta_ef ./ threshold + xmax = max(xmax, isfinite(cv.with_ntv) ? 1.6 * cv.with_ntv / threshold : x[end]) + inview = x .<= xmax + ymax = max(ymax, maximum(filter(isfinite, vcat(cv.current_linear[inview], cv.current_ntv[inview])); init=0.0)) + if band + lo, hi = cv.limits_pessimistic.with_ntv, cv.limits_optimistic.with_ntv + isfinite(lo) && vspan!(ax, lo / threshold, min(hi, cv.delta_ef[end]) / threshold; color=(WONG[j], 0.12)) + band!(ax, x, cv.current_ntv_optimistic, cv.current_ntv_pessimistic; color=(WONG[j], 0.15)) + end + lines!(ax, x, cv.current_linear; color=WONG[j], linewidth=2, label="$(c.coil_name): single-mode") + lines!(ax, x, cv.current_ntv; color=WONG[j], linewidth=2, linestyle=:dash, label="$(c.coil_name): with residual NTV") + lines!(ax, x, cv.current_ntv_upper; color=(WONG[j], 0.7), linewidth=1, linestyle=:dashdot, label="$(c.coil_name): over-correction edge") + isfinite(cv.with_ntv) && vlines!(ax, cv.with_ntv / threshold; color=WONG[j], linestyle=:dot, linewidth=1.5, + label="correctable up to $(_num(cv.with_ntv / threshold)) × threshold") + isfinite(cv.residual_only) && scatter!(ax, [cv.residual_only / threshold], [0.0]; color=WONG[j], marker=:circle, markersize=10, + label="zero rotation, residual torque") + isfinite(cv.torque_only) && scatter!(ax, [cv.torque_only / threshold], [0.0]; color=WONG[j], marker=:star5, markersize=13, + label="zero rotation, whole-field torque") + end + xmax > 0 && xlims!(ax, 0, xmax) + ymax > 0 && ylims!(ax, 0, 1.15ymax) + axislegend(ax; position=:lt) + col = 2 + if !isempty(scanned) + b = Axis(fig[1, col]; xlabel="E×B rotation shift Δω (krad/s)", ylabel="NTV torque per kAt² (N·m)", title="Torque against rotation") + hlines!(b, 0; color=:black, linewidth=1) + for (j, c) in enumerate(scanned) + x = c.rotation_shift ./ 1e3 + scatterlines!(b, x, c.torque_full_scan; color=WONG[j], markersize=7, label="$(c.coil_name): whole field") + scatterlines!(b, x, c.torque_residual_scan; color=WONG[j], linestyle=:dash, marker=:diamond, markersize=7, label="$(c.coil_name): residual") + isfinite(c.omega_reference) && vlines!(b, -c.omega_reference / 1e3; color=(:black, 0.4), linewidth=1, label="rotation brought to rest") + end + axislegend(b; position=:lt, merge=true) + col += 1 + end + if profiles + p = Axis(fig[1, col]; xlabel=PSIN, ylabel="NTV torque per kAt² from the axis (N·m)", title="Where the torque is deposited (zero rotation shift)") + for (j, c) in enumerate(couplings) + isempty(c.psi) && continue + i0 = something(findfirst(==(0.0), c.rotation_shift), argmin(abs.(c.rotation_shift))) + lines!(p, c.psi, c.torque_full_profile[:, i0]; color=WONG[j], linewidth=2, label="$(c.coil_name): whole field") + lines!(p, c.psi, c.torque_residual_profile[:, i0]; color=WONG[j], linewidth=2, linestyle=:dash, label="$(c.coil_name): residual") + end + axislegend(p; position=:lt) + end + fig + end +end + +function EFA._plot_correction_requirement(req) + return themed() do + n = length(req.rational_psi) + k = length(req.array_names) + labels = ["$m/$nn" for (m, nn) in zip(req.rational_m, req.rational_n)] + source = abs.(req.resonant_field_source_t) + vals = filter(v -> isfinite(v) && v > 0, vcat(source, vec(abs.(req.resonant_field_dominant_t)), abs.(req.resonant_field_least_squares_t))) + base = minimum(vals) / 3 + fig = Figure(; size=(700 + 360 * k, 560)) + ax = Axis(fig[1, 1]; xticks=(1:n, labels), xlabel="rational surface q = m/n", ylabel="|resonant field| (T)", yscale=log10, yminorticksvisible=false, + yticks=_decade_ticks(base, 3 * maximum(vals)), + title="Resonant field of $(req.source_name) and after correction", limits=((0.4, n + 0.6), (base, 3 * maximum(vals)))) + series = vcat([("as built", source, :black, :circle)], + [("after $nm alone", abs.(req.resonant_field_dominant_t[:, j]), WONG[j], :diamond) for (j, nm) in enumerate(req.array_names)], + [("after joint least squares", abs.(req.resonant_field_least_squares_t), :firebrick, :star5)]) + w = 0.8 / length(series) + for (s, (name, v, color, marker)) in enumerate(series) + x = (1:n) .+ (s - (length(series) + 1) / 2) * w + ok = [isfinite(y) && y > 0 for y in v] + rangebars!(ax, x[ok], fill(base, count(ok)), v[ok]; color=(color, 0.35), whiskerwidth=0, linewidth=2) + scatter!(ax, x[ok], v[ok]; color, marker, markersize=10, label=name) + end + Legend(fig[2, 1], ax; orientation=:horizontal, nbanks=2, framevisible=false) + in_kat = all(isfinite, req.current_least_squares_kat) + unit = in_kat ? "kAt" : "× its current as given" + for (j, nm) in enumerate(req.array_names) + fd, fm, fl = + in_kat ? (req.current_dominant_kat[j], req.current_dominant_minimum_norm_kat[j], req.current_least_squares_kat[j]) : + (req.current_factor_dominant[j], req.current_factor_dominant_minimum_norm[j], req.current_factor_least_squares[j]) + pa = PolarAxis(fig[1, j+1]; title="$nm: needed current ($unit)", rticklabelsvisible=false) + elements, names = Any[], String[] + for (f, name, color, width) in ((fd, "this array alone", RGBf(0.4, 0.4, 0.4), 4), (fm, "least current over the set", WONG[1], 2.5), + (fl, "joint least squares", :firebrick, 2)) + isfinite(f) || continue + lines!(pa, [angle(f), angle(f)], [0.0, abs(f)]; color, linewidth=width) + scatter!(pa, [angle(f)], [abs(f)]; color, markersize=9) + push!(elements, LineElement(; color, linewidth=width)) + push!(names, "$name: $(_num(abs(f))) ∠ $(round(Int, rad2deg(angle(f))))°") + end + Legend(fig[2, j+1], elements, names; framevisible=false, labelsize=11) + end + colsize!(fig.layout, 1, Relative(0.5)) + fig + end +end + +function EFA._plot_needed_current(mc, array, dist, limit) + return themed() do + in_kat = isfinite(array.ampere_turns_kat) + per = (in_kat ? array.ampere_turns_kat : 1.0) / abs(array.delta) + c = EFA._centers(in_kat ? dist.current_bin_edges_kat : dist.current_factor_bin_edges) + pdf = in_kat ? dist.current_pdf_per_kat : dist.current_factor_pdf + fig = Figure(; size=(900, 500)) + designed = mc.abs_delta_total_as_designed * per + ntv = limit !== nothing && isfinite(limit.abs_delta_max_correctable) ? limit.abs_delta_max_correctable * per : 0.0 + xmax = 1.05 * max(_support_end(c, pdf), designed, ntv) + ax = Axis(fig[1, 1]; xlabel=in_kat ? "$(array.coil_name) current needed (kAt)" : "factor on $(array.coil_name)'s current needed", + ylabel=in_kat ? "probability density (per kAt)" : "probability density", limits=((0, xmax), (0, nothing)), + title="Current that cancels each sampled machine's intrinsic overlap") + band!(ax, c, zeros(length(c)), pdf; color=(WONG[1], 0.25)) + lines!(ax, c, pdf; color=WONG[1], linewidth=2, label="over the tolerance Monte Carlo") + designed > 1e-3 * maximum(c) && vlines!(ax, designed; color=:black, linestyle=:dot, linewidth=1.5, + label="as designed: $(_num(designed))" * (in_kat ? " kAt" : "")) + if limit !== nothing && isfinite(limit.abs_delta_max_correctable) + vlines!(ax, limit.abs_delta_max_correctable * per; color=:firebrick, linewidth=2, + label="NTV limit at $(_num(limit.torque_budget)) N·m ($(limit.model)): $(_pct(limit.probability_percent)) beyond") + end + axislegend(ax; position=:rt) + fig + end +end + +function EFA._plot_error_field_summary(d, T, threshold, risk, keep) + return themed() do + fig = Figure(; size=(keep === nothing ? 860 : 1250, threshold && risk ? 820 : 480)) + left = Any[] + threshold && push!(left, pos -> _threshold_panel!(pos, [("", d, T)])) + risk && push!(left, pos -> _risk_panel!(pos, [("", d)]; target_percent=1.0)) + for (r, panel!) in enumerate(left) + panel!(fig[r, 1]) + end + if keep !== nothing + names = d.coil_names[keep] + values = abs.(d.abs_delta_shift_per_mm[keep]) + ax = Axis(fig[1:max(1, length(left)), isempty(left) ? 1 : 2]; xscale=log10, xminorticksvisible=false, xlabel="|δ| per mm of shift") + omitted = _stem_rows!(ax, names, [values], [""]; log=true) + ax.title = omitted == 0 ? "Shift sensitivity" : "Shift sensitivity ($omitted with no n ≠ 0 field not shown)" + isempty(left) || colsize!(fig.layout, 2, Relative(0.32)) + end + fig + end +end diff --git a/ext/GPECMakieExt/fields.jl b/ext/GPECMakieExt/fields.jl new file mode 100644 index 000000000..97244d385 --- /dev/null +++ b/ext/GPECMakieExt/fields.jl @@ -0,0 +1,46 @@ +# The two renderers every 2D field view draws with, and the coil traces laid over a control-surface map. + +const DIVERGING = Reverse(:RdBu) + +# A real field on the control surface: toroidal φ against poloidal θ in degrees, θ = 0 at the outboard midplane. +function _surface_slice!(ax, phi, theta, values) + lim = max(maximum(abs, values), floatmin()) + ax.xticks = 0:90:360 + ax.yticks = -180:90:180 + ax.xlabel = "toroidal angle φ (deg)" + ax.ylabel = "poloidal angle θ (deg)" + return heatmap!(ax, phi, theta, permutedims(values); colormap=DIVERGING, colorrange=(-lim, lim)) +end + +# A real field on a poloidal plane, drawn on the flux-surface grid (R, Z) so the map follows the plasma shape; the +# colour range is ±`lim`, and values beyond it take the colormap's end colours. +function _poloidal_slice!(ax, R, Z, values; lim=maximum(abs, values), outlines=()) + lim = max(lim, floatmin()) + cmap = Makie.to_colormap(DIVERGING) + clipped = lim < maximum(abs, values) + ax.aspect = DataAspect() + ax.xlabel = "R (m)" + ax.ylabel = "Z (m)" + hm = surface!(ax, R, Z, zeros(size(R)); color=values, colormap=DIVERGING, colorrange=(-lim, lim), shading=NoShading, + lowclip=clipped ? cmap[1] : nothing, highclip=clipped ? cmap[end] : nothing) + for o in outlines + lines!(ax, o...; color=(:black, 0.35), linewidth=1) + end + return hm +end + +# Coil conductors on a (φ, θ) map: a black outline with a core in the colour of the current's sign, broken where the +# trace wraps in φ or θ. +function _coil_traces!(ax, lines) + cmap = Makie.to_colormap(DIVERGING) + for (phi, theta, current) in lines + breaks = findall(i -> abs(phi[i+1] - phi[i]) > 180 || abs(theta[i+1] - theta[i]) > 180, 1:(length(phi)-1)) + bounds = vcat(0, breaks, length(phi)) + for t in 1:(length(bounds)-1) + r = (bounds[t]+1):bounds[t+1] + lines!(ax, phi[r], theta[r]; color=:black, linewidth=2.5) + lines!(ax, phi[r], theta[r]; color=current > 0 ? cmap[end] : cmap[1], linewidth=1.2) + end + end + return nothing +end diff --git a/ext/GPECMakieExt/forcefreestates.jl b/ext/GPECMakieExt/forcefreestates.jl new file mode 100644 index 000000000..42e48d96a --- /dev/null +++ b/ext/GPECMakieExt/forcefreestates.jl @@ -0,0 +1,339 @@ +# ForceFreeStates and local-stability figures. Each panel draws into its own nested grid `gl`, so the single plots +# and the summary share one drawing function per panel. + +const FFS = A.ForceFreeStates + +# A figure holding one panel in a nested grid. +function _single(panel!, size, args...; kwargs...) + return themed() do + fig = Figure(; size) + gl = fig[1, 1] = GridLayout() + panel!(gl, args...; kwargs...) + fig + end +end + +# Rational surfaces along the top of a ψ_N axis with the title above them, or just the title without surfaces. +function _titled_rationals!(pos, ax, rat, title) + isempty(rat.psi) && return (ax.title = title) + return _surfaces_on_top!(pos, ax, rat.psi, rat.labels; title) +end + +_plural(n, word) = "$n $word" * (n == 1 ? "" : "s") + +function _critical_determinant!(gl, d, rat) + ax = Axis(gl[1, 1]; xlabel=PSIN, ylabel="critical determinant", yscale=Makie.pseudolog10, limits=((0, 1), _symlims(d.crit))) + nz = count(i -> d.crit[i-1] < 0 <= d.crit[i], 2:length(d.crit)) # −→+ crossings, as the integrator counts them + verdict = nz == 0 ? "Fixed boundary: no zero crossings → stable" : "Fixed boundary: $(_plural(nz, "zero crossing")) → unstable" + _titled_rationals!(gl[1, 1], ax, rat, verdict) + hlines!(ax, 0; color=:black, linewidth=1) + lines!(ax, d.psi, d.crit; color=WONG[1], linewidth=1.8) + return ax +end + +const _ENERGY_STYLE = Dict( + :total => (:et, rich("total δW = δW", subscript("p"), " + δW", subscript("v")), :circle, 1), + :plasma => (:ep, rich("plasma δW", subscript("p")), :utriangle, 2), + :vacuum => (:ev, rich("vacuum δW", subscript("v")), :rect, 3) +) + +function _energies!(gl, e; which=(:total, :plasma, :vacuum)) + et = real.(e.et) + nu = count(<(0), et) + title = nu == 0 ? rich("Free boundary: stable, least stable δW = ", _num(et[1])) : "Free boundary: $(_plural(nu, "unstable eigenmode"))" + vals = reduce(vcat, (real.(getfield(e, _ENERGY_STYLE[w][1])) for w in which)) + lims = _symlims(vals) + ax = Axis(gl[1, 1]; xlabel="eigenmode (most unstable first)", ylabel="eigenvalue of W·v = λN·v", title, yscale=Makie.pseudolog10, limits=(nothing, lims)) + hspan!(ax, lims[1], 0; color=(:firebrick, 0.06)) + hlines!(ax, 0; color=:black, linewidth=1) + for w in which + field, label, marker, k = _ENERGY_STYLE[w] + v = real.(getfield(e, field)) + scatter!(ax, 1:length(v), v; color=WONG[k], marker, markersize=k == 1 ? 9 : 7, label) + end + length(which) > 1 && axislegend(ax; position=:rb) + return ax +end + +function _mode_displacement!(gl, d, rat; modes=nothing) + amp = abs.(d.xi) + rows = if modes === nothing + sort(sortperm(vec(maximum(amp; dims=2)); rev=true)[1:min(6, size(amp, 1))]) + else + [m - d.mlow + 1 for m in modes if 1 <= m - d.mlow + 1 <= size(amp, 1)] + end + isempty(rows) && throw(ArgumentError("plot_mode_displacement: no requested m lies in $(d.mlow):$(d.mlow + size(amp, 1) - 1)")) + scaled, scale = _decade(amp[rows, :]) + ax = Axis(gl[1, 1]; xlabel=PSIN, ylabel=rich("|ξ", superscript("ψ"), subscript("m"), "|", _unit(scale, "")), limits=((0, 1), (0, nothing))) + title = rich("ξ", superscript("ψ"), subscript("m"), " of the least stable free-boundary eigenmode", d.dW === nothing ? "" : ", δW = " * _num(real(d.dW))) + _titled_rationals!(gl[1, 1], ax, rat, title) + colors = Makie.resample_cmap(:viridis, length(rows) + 1) + for (j, i) in enumerate(rows) + lines!(ax, d.psi, scaled[j, :]; color=colors[j], linewidth=1.8, label="m = $(d.mlow + i - 1)") + end + axislegend(ax; position=:lt, nbanks=2) + return ax +end + +function _eigenvectors!(gl, d; nmodes=nothing) + v = abs.(d.wt) + m = d.mlow .+ (0:(size(v, 1)-1)) + k = min(size(v, 2), something(nmodes, size(v, 2))) + ax = Axis(gl[1, 1]; xlabel="poloidal harmonic m", ylabel="eigenmode", yreversed=true, title="Free-boundary eigenmodes, least stable at top", + yticks=k <= 15 ? (1:k) : [1; 5:5:k]) + hm = heatmap!(ax, m, 1:k, v[:, 1:k]; colormap=:viridis) + Colorbar(gl[1, 2], hm; label=rich("|v", subscript("m"), "| (unit power norm)")) + return ax +end + +function _delta_strip!(gl, dp) + d = real.([dp.matrix[i, i] for i in axes(dp.matrix, 1)]) + x = 1:length(d) + lims = _symlims(d) + npos = count(>(0), d) + title = npos == 0 ? rich("Tearing: Δ′", subscript("ii"), " < 0 on every surface") : rich("Tearing: Δ′", subscript("ii"), " > 0 on $npos of $(length(d)) surfaces") + ax = Axis(gl[1, 1]; xticks=(x, dp.labels), xlabel="rational surface q = m/n", ylabel=rich("Re Δ′", subscript("ii")), title, + yscale=Makie.pseudolog10, limits=((0.4, length(d) + 0.6), lims)) + hspan!(ax, 0, lims[2]; color=(:firebrick, 0.06)) + hlines!(ax, 0; color=:black, linewidth=1) + rangebars!(ax, x, zeros(length(d)), d; color=(:black, 0.35), whiskerwidth=0) + scatter!(ax, x, d; color=[v > 0 ? :firebrick : WONG[1] for v in d], markersize=12) + return ax +end + +FFS._plot_critical_determinant(d, rat) = _single(_critical_determinant!, (820, 460), d, rat) +FFS._plot_eigenvalues(e; which=(:total, :plasma, :vacuum)) = _single(_energies!, (820, 460), e; which) +FFS._plot_mode_displacement(d, rat; modes=nothing) = _single(_mode_displacement!, (820, 480), d, rat; modes) +FFS._plot_energy_eigenvectors(d; nmodes=nothing) = _single(_eigenvectors!, (820, 520), d; nmodes) + +function FFS._plot_edge_stability_scan(d) + return themed() do + fig = Figure(; size=(820, 460)) + vals = vcat(real.(d.et), real.(d.ep), real.(d.ev)) + lims = _symlims(vals) + ax = Axis(fig[1, 1]; xlabel="edge truncation q", ylabel="free-boundary energy", yscale=Makie.pseudolog10, limits=(nothing, lims), + title="Edge scan: truncation at q = $(_num(d.qlim))") + hspan!(ax, lims[1], 0; color=(:firebrick, 0.06)) + hlines!(ax, 0; color=:black, linewidth=1) + vlines!(ax, d.qlim; color=(:black, 0.5), linestyle=:dash, linewidth=1.2) + for (key, w) in ((:et, :total), (:ep, :plasma), (:ev, :vacuum)) + _, label, marker, k = _ENERGY_STYLE[w] + v = real.(getfield(d, key)) + scatterlines!(ax, d.q, v; color=WONG[k], marker, markersize=6, label) + end + axislegend(ax; position=:rb) + fig + end +end + +# The Δ′ family: complex entries drawn as their real part (default), imaginary part or magnitude. +function _part(z, part) + part === :real && return real.(z) + part === :imag && return imag.(z) + return abs.(z) +end +_part_label(part, sym) = part === :real ? rich("Re ", sym) : part === :imag ? rich("Im ", sym) : rich("|", sym, "|") + +# Signed parts on a diverging symmetric-log map, linear within 10⁻³ of the largest entry; magnitudes on a log map +# from the smallest nonzero entry. +function _complex_heatmap!(ax, x, y, v, part) + lim = max(maximum(abs, v; init=0.0), floatmin()) + if part === :abs + positive = filter(>(0), v) + lo = isempty(positive) ? 1e-6lim : minimum(positive) + return heatmap!(ax, x, y, max.(v, lo); colormap=:viridis, colorscale=log10, colorrange=(lo, lim)) + end + return heatmap!(ax, x, y, v; colormap=Reverse(:RdBu), colorrange=(-lim, lim), colorscale=Makie.Symlog10(1e-3lim)) +end + +# Text colour for a value written on its own heatmap cell: white on the dark ends of the map, black elsewhere. +function _cell_text_color(v, v_all, part) + if part === :abs + lo, hi = log10.(extrema(filter(>(0), v_all))) + return hi > lo && (log10(max(v, 10.0^lo)) - lo) / (hi - lo) < 0.35 ? :white : :black + end + lim = maximum(abs, v_all) + lin = 1e-3lim + return log1p(abs(v) / lin) / log1p(lim / lin) > 0.7 ? :white : :black +end + +# Surface/side axes: L and R ticks on the data axis, the q = m/n group label beyond them, white separators. +function _surface_side_axes!(pos, labels; xgroups=true, kwargs...) + ns = length(labels) + lr = [isodd(i) ? "L" : "R" for i in 1:(2ns)] + groups = ["q = " * l for l in labels] + ax = Axis(pos; yticks=(1:(2ns), lr), yreversed=true, ygridvisible=false, xgridvisible=false, kwargs...) + g = Axis(pos; yticks=(1.5:2:(2ns), groups), yreversed=true, yticksvisible=false, yticklabelpad=24, ylabel="responding surface and side", + xgridvisible=false, ygridvisible=false, xticksvisible=false, xticklabelsvisible=false) + if xgroups + ax.xticks = (1:(2ns), lr) + g.xticks = (1.5:2:(2ns), groups) + g.xticklabelsvisible = true + g.xticklabelpad = 22 + g.xlabel = "driving surface and side" + end + haskey(kwargs, :limits) && (g.limits = kwargs[:limits]) + hidespines!(g) + linkaxes!(ax, g) + for s in 1:(ns-1) + hlines!(ax, 2s + 0.5; color=:white, linewidth=2) + xgroups && vlines!(ax, 2s + 0.5; color=:white, linewidth=2) + end + return ax +end + +function FFS._plot_delta_prime(dp; part=:real) + return themed() do + v = _part(dp.matrix, part) + k = length(dp.labels) + labels = ["q = " * l for l in dp.labels] + fig = Figure(; size=(620, 520)) + ax = Axis(fig[1, 1]; xticks=(1:k, labels), yticks=(1:k, labels), yreversed=true, aspect=DataAspect(), xgridvisible=false, ygridvisible=false, + title=rich(_part_label(part, "Δ′"), " between rational surfaces (PEST-3, ", PSIN, "-referenced)"), xlabel="driving surface", + ylabel="responding surface") + hm = _complex_heatmap!(ax, 1:k, 1:k, permutedims(v), part) + for i in 1:k, j in 1:k + text!(ax, j, i; text=_num(v[i, j]), align=(:center, :center), fontsize=12, color=_cell_text_color(v[i, j], v, part)) + end + Colorbar(fig[1, 2], hm; label=_part_label(part, sub("Δ′", "ij"))) + resize_to_layout!(fig) + fig + end +end + +function FFS._plot_delta_prime_raw(dp; part=:real) + return themed() do + ns = length(dp.labels) + fig = Figure(; size=(760, 700)) + ax = _surface_side_axes!(fig[1, 1], dp.labels; aspect=DataAspect(), limits=((0.5, 2ns + 0.5), (0.5, 2ns + 0.5)), + title=rich(_part_label(part, "D′"), ": outer-region matching matrix")) + hm = _complex_heatmap!(ax, 1:(2ns), 1:(2ns), permutedims(_part(dp.raw, part)), part) + Colorbar(fig[1, 2], hm; label=_part_label(part, "D′")) + resize_to_layout!(fig) + fig + end +end + +function FFS._plot_delta_coil(dp; part=:real) + return themed() do + ns = length(dp.labels) + me = dp.mlow .+ (0:(size(dp.coil, 1)-1)) + fig = Figure(; size=(900, 560)) + ax = _surface_side_axes!(fig[1, 1], dp.labels; xgroups=false, xlabel="applied edge harmonic m", + title=rich(_part_label(part, sub("Δ", "coil")), ": surface response to each edge harmonic"), + limits=((me[1] - 0.5, me[end] + 0.5), (0.5, 2ns + 0.5))) + hm = _complex_heatmap!(ax, me, 1:(2ns), _part(dp.coil, part), part) + Colorbar(fig[1, 2], hm; label=_part_label(part, sub("Δ", "coil"))) + fig + end +end + +function FFS._plot_cond_fbar(d, rat) + return themed() do + fbar = L"\mathrm{cond}(\bar{F})" + fig = Figure(; size=(900, 480)) + ax = Axis(fig[1, 1]; xlabel=PSIN, ylabel=fbar, yscale=log10, limits=((0, 1), nothing)) + title = d.kmsing == 0 ? "Kinetic singular surfaces: none above threshold" : "Kinetic singular surfaces: $(d.kmsing) above threshold" + _titled_rationals!(fig[1, 1], ax, rat, title) + lines!(ax, d.scan_psi, d.scan_cond; color=WONG[1], linewidth=2, label=fbar) + d.thr > 0 && hlines!(ax, d.thr; color=:firebrick, linestyle=:dash, linewidth=1.5, label="threshold $(_num(d.thr))") + isempty(d.k_psi) || vlines!(ax, d.k_psi; color=:firebrick, linewidth=2, label="kinetic singular surface") + axislegend(ax; position=:lb) + fig + end +end + +function _ballooning_verdict(alpha, alpha_crit) + ok = isfinite.(alpha_crit) + nu = count(ok .& (alpha .> alpha_crit)) + return nu == 0 ? "Infinite-n ballooning: stable on every surface" : "Infinite-n ballooning: unstable on $nu of $(count(ok)) surfaces" +end + +function _alpha_top(curves...) + finite = filter(isfinite, reduce(vcat, curves)) + return isempty(finite) ? 1.0 : 1.3 * maximum(finite) +end + +function FFS._plot_ballooning_alpha_boundary(d; psi_min=0.0) + return themed() do + unstable = isfinite.(d.alpha_crit) .& (d.alpha .> d.alpha_crit) + fig = Figure(; size=(820, 460)) + ax = Axis(fig[1, 1]; xlabel=PSIN, ylabel="normalized pressure gradient α", limits=((psi_min, 1), (0, _alpha_top(d.alpha, d.alpha_crit))), + title=_ballooning_verdict(d.alpha, d.alpha_crit)) + band!(ax, d.psi, d.alpha, ifelse.(unstable, d.alpha_crit, d.alpha); color=(:firebrick, 0.15)) + lines!(ax, d.psi, d.alpha; color=:black, linewidth=2.5, label="equilibrium α") + lines!(ax, d.psi, d.alpha_crit; color=WONG[6], linewidth=2, linestyle=:dash, label="first stability boundary") + axislegend(ax; position=:lt) + fig + end +end + +# Each surface's Δ′(α), signed so that positive is stable everywhere, regridded from its own α = α_ref·scale onto a +# shared α axis; NaN past a surface's scan. +function _signed_delta_prime_map(dpmap) + psi, scales, aref = dpmap.psi, dpmap.alpha_scales, dpmap.alpha_ref + signed = dpmap.delta_prime .* sign.(dpmap.delta_prime[:, 1]) + finite_aref = filter(isfinite, aref) + isempty(finite_aref) && throw(ArgumentError("plot_ballooning_alpha_boundaries: the Δ′ map has no valid surfaces")) + alpha = collect(range(0.0, maximum(finite_aref) * scales[end]; length=200)) + z = fill(NaN, length(psi), length(alpha)) + for i in eachindex(psi) + (isfinite(aref[i]) && aref[i] > 0) || continue + a_s = aref[i] .* scales + for (j, a) in enumerate(alpha) + a > a_s[end] && break + k = min(searchsortedlast(a_s, a), length(a_s) - 1) + k < 1 && continue + t = (a - a_s[k]) / (a_s[k+1] - a_s[k]) + z[i, j] = (1 - t) * signed[i, k] + t * signed[i, k+1] + end + end + return psi, alpha, z +end + +function FFS._plot_ballooning_alpha_boundaries(bnd, dpmap; psi_min=0.0) + return themed() do + fig = Figure(; size=(900, 560)) + top = _alpha_top(bnd.alpha, bnd.alpha_critical1, bnd.alpha_critical2) + ax = Axis(fig[1, 1]; xlabel=PSIN, ylabel="normalized pressure gradient α", limits=((psi_min, 1), (0, top)), + title=_ballooning_verdict(bnd.alpha, bnd.alpha_critical1)) + if dpmap !== nothing + psi, alpha, z = _signed_delta_prime_map(dpmap) + finite = filter(isfinite, z) + lim = isempty(finite) ? 1.0 : maximum(abs, finite) + hm = heatmap!(ax, psi, alpha, z; colormap=:RdBu, colorrange=(-lim, lim), colorscale=Makie.Symlog10(1e-3lim), nan_color=:transparent) + contour!(ax, psi, alpha, z; levels=[0.0], color=:black, linewidth=1) + Colorbar(fig[1, 2], hm; label=rich("Δ′ · sign(Δ′", subscript("α=0"), ") (> 0 stable)")) + end + lines!(ax, bnd.psi, bnd.alpha; color=:black, linewidth=2.5, label="equilibrium α") + lines!(ax, bnd.psi, bnd.alpha_critical1; color=WONG[6], linewidth=2, linestyle=:dash, label="first stability boundary") + lines!(ax, bnd.psi, bnd.alpha_critical2; color=WONG[2], linewidth=2, linestyle=:dash, label="second stability boundary") + axislegend(ax; position=:lt) + fig + end +end + +const _SUMMARY_PANELS = Dict( + :critical_determinant => _critical_determinant!, + :energies => _energies!, + :mode_displacement => _mode_displacement!, + :delta_prime => _delta_strip!, + :eigenvectors => _eigenvectors! +) + +# Two panels per row; an odd last panel spans both columns, so no cell is empty. +function FFS._plot_ffs_summary(panels) + return themed() do + n = length(panels) + fig = Figure(; size=(n == 1 ? 820 : 1150, 410 * cld(n, 2))) + for (i, (kind, data...)) in enumerate(panels) + row = cld(i, 2) + col = (n == 1 || (i == n && isodd(n))) ? (1:2) : (isodd(i) ? 1 : 2) + gl = fig[row, col] = GridLayout() + _SUMMARY_PANELS[kind](gl, data...) + end + colgap!(fig.layout, 18) + rowgap!(fig.layout, 14) + fig + end +end diff --git a/ext/GPECMakieExt/perturbedequilibrium.jl b/ext/GPECMakieExt/perturbedequilibrium.jl new file mode 100644 index 000000000..12512534b --- /dev/null +++ b/ext/GPECMakieExt/perturbedequilibrium.jl @@ -0,0 +1,349 @@ +# PerturbedEquilibrium figures: resonant coupling per rational surface, response spectrograms and the summary. + +const PE = A.PerturbedEquilibrium + +# Quantities spanning more than two decades go on a log axis; the rest are scaled by a power of ten. +function _magnitude_row(v, name, unit) + positive = filter(>(0), v) + if !isempty(positive) && maximum(positive) / minimum(positive) > 100 + return (v, rich(name, " (", unit, ")"), log10) + end + scaled, scale = _decade(v) + return (scaled, rich(name, _unit(scale, unit)), identity) +end + +# Each coupling row: values, label, y scale and reference line (nothing for none). +function _coupling_row(d, key) + key === :field && return (_magnitude_row(abs.(d.field), rich("|b̄", subscript("res"), "|"), "T")..., nothing) + key === :current && return (_magnitude_row(abs.(d.current), rich("|I", subscript("res"), "|"), "A")..., nothing) + key === :island_hw && return (d.island_hw, rich("w/2 (", PSIN, ")"), identity, nothing) + key === :chirikov && return (d.chirikov, "effective island-overlap\nChirikov parameter", identity, 1.0) + return (real.(d.delta_prime), "driven Re Δ′", identity, 0.0) +end + +const _ROW_TITLES = Dict( + :field_pen => "Resonant and penetrated field at each rational surface", + :field => "Resonant field at each rational surface", + :current => "Resonant current at each rational surface", + :island_hw => "Island half-width at each rational surface", + :delta_prime => "Forcing-driven Δ′ at each rational surface" +) + +function _coupling_title(d, keys) + :chirikov in keys || return _ROW_TITLES[only(keys) === :field && d.penetrated !== nothing ? :field_pen : only(keys)] + k = count(>=(1), d.chirikov) + return k == 0 ? "Resonant coupling: Chirikov parameter < 1 on every surface" : "Resonant coupling: islands overlap on $k of $(length(d.chirikov)) surfaces" +end + +# The field row with the penetrated field: b̄_res and b̄_pen as paired stems on one shared scale. +function _field_pair!(ax, d, x) + v, ylabel, yscale = _magnitude_row(vcat(abs.(d.field), abs.(d.penetrated)), rich("|b̄|"), "T") + k = length(x) + ax.ylabel, ax.yscale = ylabel, yscale + positive = filter(>(0), v) + base = yscale === log10 ? minimum(positive) / 3 : 0.0 + yscale === identity && ylims!(ax, 0, 1.12 * maximum(positive)) + for (j, (y, name)) in enumerate(((v[1:k], "res"), (v[(k+1):end], "pen"))) + xs = x .+ (j - 1.5) * 0.24 + rangebars!(ax, xs, fill(base, k), y; color=(WONG[j], 0.35), whiskerwidth=0, linewidth=2) + scatter!(ax, xs, y; color=WONG[j], markersize=11, label=rich("|b̄", subscript(name), "|")) + end + axislegend(ax; position=:lt) + return ax +end + +# Rows of stems and dots sharing a categorical q = m/n axis, one colour per toroidal mode number. +function _coupling_rows!(gl, d, keys) + labels = ["$m/$n" for (m, n) in zip(d.m, d.n)] + x = 1:length(labels) + ns = unique(d.n) + colors = [WONG[findfirst(==(n), ns)] for n in d.n] + axes = Axis[] + for (r, key) in enumerate(keys) + y, ylabel, yscale, ref = _coupling_row(d, key) + last = r == length(keys) + ax = Axis(gl[r, 1]; ylabel, yscale, xticks=(x, labels), limits=((0.4, length(x) + 0.6), nothing), xlabel=last ? "rational surface q = m/n" : "", + xticklabelsvisible=last, ylabelsize=14, title=r == 1 ? _coupling_title(d, keys) : "") + if key === :field && d.penetrated !== nothing + push!(axes, _field_pair!(ax, d, x)) + continue + end + ref === nothing || hlines!(ax, ref; color=:black, linewidth=1, linestyle=ref == 1.0 ? :dash : :solid) + positive = filter(>(0), y) + base = yscale === log10 ? minimum(positive) / 3 : 0.0 + yscale === identity && ref === nothing && !isempty(positive) && ylims!(ax, 0, 1.12 * maximum(positive)) + rangebars!(ax, x, fill(base, length(y)), y; color=[(c, 0.35) for c in colors], whiskerwidth=0, linewidth=2) + scatter!(ax, x, y; color=colors, markersize=11) + if r == 1 && length(ns) > 1 + axislegend(ax, [MarkerElement(; color=WONG[k], marker=:circle) for k in eachindex(ns)], ["n = $n" for n in ns]; position=:rt) + end + push!(axes, ax) + end + linkxaxes!(axes...) + rowgap!(gl, 6) + return axes +end + +function PE._plot_resonant_coupling(d, keys) + return themed() do + fig = Figure(; size=length(keys) == 1 ? (720, 400) : (620, 150 * length(keys) + 70)) + gl = fig[1, 1] = GridLayout() + _coupling_rows!(gl, d, keys) + fig + end +end + +const _COMPONENT_STYLE = Dict( + :xi_psi => ("Plasma displacement", rich("ξ", superscript("ψ")), ""), + :b_psi => ("Area-weighted normal field", rich("b̄", superscript("ψ")), "T"), + :Jb_theta => ("Poloidal field", rich("J b", superscript("θ")), "T m²"), + :Jb_zeta => ("Toroidal field", rich("J b", superscript("ζ")), "T m²") +) + +# Piecewise-linear interpolation of (xs, ys) at x, held at the end values outside xs. +function _lerp(xs, ys, x) + k = clamp(searchsortedlast(xs, x), 1, length(xs) - 1) + t = clamp((x - xs[k]) / (xs[k+1] - xs[k]), 0.0, 1.0) + return (1 - t) * ys[k] + t * ys[k+1] +end + +# |harmonic| of the first toroidal mode number on a uniform ψ grid: the response grid holds the integrator's steps, +# which CairoMakie would draw as slanted cells. +function _uniform_harmonics(d) + mpert = d.mhigh - d.mlow + 1 + order = sortperm(d.psi) + psi, z = d.psi[order], abs.(d.data[order, 1:mpert]) + keep = [true; diff(psi) .> 0] + psi, z = psi[keep], z[keep, :] + pu = collect(range(psi[1], psi[end]; length=600)) + zu = [_lerp(psi, view(z, :, j), p) for p in pu, j in 1:mpert] + return collect(d.mlow:d.mhigh), psi, z, pu, zu +end + +function _spectrogram!(gl, d, component; style=:log_heatmap) + name, glyph, unit = _COMPONENT_STYLE[component] + ms, psi, z, pu, zu = _uniform_harmonics(d) + amp = rich("|", glyph, subscript("m"), "|") + if style === :lines + top = sort(sortperm(vec(maximum(z; dims=1)); rev=true)[1:min(8, length(ms))]) + zl, scale = _decade(z[:, top]) + ax = Axis(gl[1, 1]; xlabel=PSIN, ylabel=rich(amp, _unit(scale, unit)), title=rich(name, " ", amp, ", largest harmonics"), + limits=((0, 1), (0, nothing))) + isempty(d.rational_psi) || vlines!(ax, d.rational_psi; color=(:black, 0.15), linewidth=1) + colors = Makie.resample_cmap(:viridis, length(top) + 1) + for (k, j) in enumerate(top) + lines!(ax, psi, zl[:, k]; color=colors[k], linewidth=1.8, label="m = $(ms[j])") + end + axislegend(ax; position=:lt, nbanks=2) + return ax + end + ax = Axis(gl[1, 1]; xlabel="poloidal harmonic m", ylabel=PSIN, title=rich(name, " ", amp), limits=((ms[1] - 0.5, ms[end] + 0.5), (0, 1))) + zmax = max(maximum(zu), floatmin()) + if style === :log_heatmap + hm = heatmap!(ax, ms, pu, permutedims(zu); colormap=:viridis, colorscale=log10, colorrange=(1e-4zmax, zmax), lowclip=Makie.to_colormap(:viridis)[1]) + label = rich(amp, _unit("", unit)) + else + zs, scale = _decade(zu) + hm = heatmap!(ax, ms, pu, permutedims(zs); colormap=:viridis, colorrange=(0, maximum(zs))) + label = rich(amp, _unit(scale, unit)) + end + isempty(d.rational_psi) || hlines!(ax, d.rational_psi; color=(:white, 0.4), linewidth=1) + lines!(ax, [d.nhigh * _lerp(d.q_psi, d.q, p) for p in pu], pu; color=:white, linewidth=1.5, linestyle=:dash, label="m = n q(ψ)") + axislegend(ax; position=:lb) + Colorbar(gl[1, 2], hm; label) + return ax +end + +function PE._plot_mode_spectrogram(d, component; style=:log_heatmap) + return themed() do + fig = Figure(; size=style === :lines ? (820, 480) : (900, 560)) + gl = fig[1, 1] = GridLayout() + _spectrogram!(gl, d, component; style) + fig + end +end + +# |b̄^ψ_m| at the outermost surface as a step line, with the resonant harmonics marked. +function _edge_spectrum!(gl, d) + mpert = d.mhigh - d.mlow + 1 + ms = collect(d.mlow:d.mhigh) + v, scale = _decade(abs.(d.data[end, 1:mpert])) + ax = Axis(gl[1, 1]; xlabel="poloidal harmonic m", ylabel=rich("|b̄", superscript("ψ"), subscript("m"), "| at the edge", _unit(scale, "T")), + title="Edge field spectrum", limits=(nothing, (0, nothing))) + isempty(d.rational_m) || vlines!(ax, d.rational_m; color=(:black, 0.15), linewidth=1) + stairs!(ax, vcat(ms[1] - 1, ms, ms[end] + 1), vcat(0.0, v, 0.0); step=:center, color=WONG[1], linewidth=2) + return ax +end + +const _NORMAL_STYLE = Dict(:b_n => (rich("b", subscript("n")), "T"), :xi_n => (rich("ξ", subscript("n")), "m")) + +# Outward unit normals of a closed counter-clockwise outline, from central differences. +function _outward_normals(R, Z) + dR = circshift(R, -1) .- circshift(R, 1) + dZ = circshift(Z, -1) .- circshift(Z, 1) + dR[1] = dR[end] = R[2] - R[end-1] + dZ[1] = dZ[end] = Z[2] - Z[end-1] + L = hypot.(dR, dZ) + return dZ ./ L, -dR ./ L +end + +# A displacement scale rounded down to one significant figure, so the largest displacement is `frac` of the minor radius. +function _display_scale(R, xi; frac=0.05) + s = frac * (maximum(R) - minimum(R)) / 2 / max(maximum(abs, xi), floatmin()) + p = 10.0^floor(log10(s)) + return max(1, round(Int, floor(s / p) * p)) +end + +# b_n or ξ_n on the poloidal plane in `gl`, with its colourbar, and the displaced boundary when given. The colour range +# is ±the `clip` quantile of |value|. +function _field_rz_panel!(gl, d, component, boundary; clip=0.9) + glyph, unit = _NORMAL_STYLE[component] + a = sort!(abs.(vec(d.values))) + lim = a[clamp(ceil(Int, clip * length(a)), 1, length(a))] + factor, scale = _power([lim]) + title = + boundary === nothing ? rich(glyph, " at φ = ", _num(rad2deg(d.phi)), "°") : + rich(glyph, " at φ = ", _num(rad2deg(d.phi)), "°: largest boundary |ξ", subscript("n"), "| = ", _num(1e3 * maximum(abs, boundary.xi)), " mm") + ax = Axis(gl[1, 1]; title) + hm = _poloidal_slice!(ax, d.R, d.Z, d.values ./ factor; lim=lim / factor, outlines=d.outlines) + Colorbar(gl[1, 2], hm; label=rich(glyph, _unit(scale, unit))) + # The column takes the plasma's own width-to-height ratio, so the panel leaves no empty band beside it. + colsize!(gl, 1, Aspect(1, (maximum(d.R) - minimum(d.R)) / (maximum(d.Z) - minimum(d.Z)))) + if boundary !== nothing + nR, nZ = _outward_normals(boundary.R, boundary.Z) + s = _display_scale(boundary.R, boundary.xi) + lines!(ax, boundary.R, boundary.Z; color=(:black, 0.45), linestyle=:dash, linewidth=1.2, label="equilibrium") + lines!(ax, boundary.R .+ s .* boundary.xi .* nR, boundary.Z .+ s .* boundary.xi .* nZ; color=:black, linewidth=2, + label=rich("displaced, ξ", subscript("n"), " × $s")) + Legend(gl[2, 1:2], ax; orientation=:horizontal, framevisible=false) + rowgap!(gl, 4) + end + return ax +end + +# Each mode's b_n as a band along the boundary cross-section, one panel per mode on one colour scale. +function PE._plot_singular_mode_rz(curves) + return themed() do + n = length(curves) + at = "φ = " * _num(rad2deg(first(curves).phi)) * "°" + fig = Figure(; size=(330n + 150, 560)) + for (j, c) in enumerate(curves) + title = n == 1 ? rich("Dominant mode $(c.mode) at $at, σ = ", _num(c.sigma)) : rich("mode $(c.mode), σ = ", _num(c.sigma)) + ax = Axis(fig[1, j]; title, aspect=DataAspect(), xlabel="R (m)", ylabel="Z (m)", ylabelvisible=j == 1, yticklabelsvisible=j == 1) + lines!(ax, c.R, c.Z; color=c.b, colormap=DIVERGING, colorrange=(-1, 1), linewidth=16) + lines!(ax, c.R, c.Z; color=:black, linewidth=1) + end + Colorbar(fig[1, n+1]; colormap=DIVERGING, limits=(-1, 1), label=rich("b", subscript("n"), " at $at (arb.)")) + n > 1 && Label(fig[0, 1:n], "Dominant resonant-coupling modes on the control surface"; font=:bold, fontsize=16) + fig + end +end + +# |C| with |U| beside it on the shared surface axis, |V| below it on the shared m axis, and σ in the corner. +function PE._plot_coupling_matrix(d) + return themed() do + A = abs.(d.C) + top = max(maximum(A), floatmin()) + ns, rank = size(A, 1), length(d.sigma) + fig = Figure(; size=(1150, 820)) + hax = Axis(fig[1, 1]; ylabel="rational surface q = m/n", yticks=(1:ns, d.labels), xticklabelsvisible=false, + title=rich("Resonant coupling C: |b̄", subscript("res"), "| per unit b̃", subscript("m"))) + hm = heatmap!(hax, d.m, 1:ns, permutedims(A); colormap=:viridis, colorscale=log10, colorrange=(1e-4top, top), + lowclip=Makie.to_colormap(:viridis)[1]) + scatter!(hax, d.m_res, 1:ns; marker=:rect, color=:transparent, strokecolor=:white, strokewidth=1.5, markersize=16) + for s in setdiff(1:ns, d.retained) + poly!(hax, Rect(d.m[1] - 0.5, s - 0.5, length(d.m), 1); color=(:white, 0.55)) + end + Colorbar(fig[1, 3], hm; label="|C| (T per unit b̃)") + + uax = Axis(fig[1, 2]; xlabel="|U| (unit norm)", yticks=(1:ns, d.labels), yticklabelsvisible=false, title="left singular vectors", + limits=((0, 1.05), nothing)) + vax = Axis(fig[2, 1]; xlabel="poloidal harmonic m of the applied field b̃", ylabel="|V| (unit norm)", title="right singular vectors", + limits=(nothing, (0, nothing))) + for k in 1:rank + lw = k == 1 ? 2.5 : 1.5 + isempty(d.retained) || scatterlines!(uax, abs.(d.U[:, k]), d.retained; color=WONG[mod1(k, 7)], linewidth=lw, markersize=10) + stairs!(vax, _steps(d.m, abs.(d.V[:, k]))...; step=:center, color=WONG[mod1(k, 7)], linewidth=lw, label=rich("V", subscript(string(k)))) + end + linkyaxes!(hax, uax) + linkxaxes!(hax, vax) + axislegend(vax; position=:rt, framevisible=false) + + sax = Axis(fig[2, 2]; xlabel="mode k", ylabel=rich("σ", subscript("k"), " (T per unit b̃)"), title="singular values", xticks=1:rank, + limits=(nothing, (0, 1.12 * maximum(d.sigma)))) + stem!(sax, 1:rank, d.sigma; color=[WONG[mod1(k, 7)] for k in 1:rank], stemcolor=(:gray, 0.6), markersize=12) + + colsize!(fig.layout, 1, Auto(3)) + colsize!(fig.layout, 2, Auto(1)) + rowsize!(fig.layout, 1, Auto(1.6)) + rowsize!(fig.layout, 2, Auto(1)) + fig + end +end + +function PE._plot_perturbed_field_rz(d, component, boundary; clip=0.9) + return themed() do + fig = Figure(; size=(560, boundary === nothing ? 680 : 720)) + gl = fig[1, 1] = GridLayout() + _field_rz_panel!(gl, d, component, boundary; clip) + fig + end +end + +function PE._plot_perturbed_equilibrium_summary(coupling, edge, xi, rz) + return themed() do + top = Any[] + coupling === nothing || push!(top, gl -> _coupling_rows!(gl, coupling, (:field,))) + edge === nothing || push!(top, gl -> _edge_spectrum!(gl, edge)) + rows = (isempty(top) ? 0 : 1) + (xi === nothing ? 0 : 1) + ncol = max(1, length(top)) + fig = Figure(; size=(1150 + (rz === nothing ? 0 : 480), rows < 2 ? 560 : 820)) + for (c, draw!) in enumerate(top) + gl = fig[1, c] = GridLayout() + draw!(gl) + end + if xi !== nothing + gl = fig[isempty(top) ? 1 : 2, 1:ncol] = GridLayout() + _spectrogram!(gl, xi, :xi_psi) + isempty(top) || rowsize!(fig.layout, 1, Relative(0.38)) + end + # b_n on the poloidal plane, a column spanning the other panels' height. + if rz !== nothing + gl = fig[1:max(1, rows), rows == 0 ? 1 : ncol + 1] = GridLayout() + _field_rz_panel!(gl, rz, :b_n, nothing) + end + fig + end +end + +function PE._plot_islands(d, name) + return themed() do + chains = d.chains + isempty(chains) && throw(ArgumentError("no island has a nonzero half-width")) + colors = Makie.resample_cmap(:viridis, length(chains) + 2) + fig = Figure(; size=(1250, 640)) + # (θ, ψ_N): the chains in flux coordinates, the outboard midplane central. + bottom = max(0.0, minimum(c.psis - 1.5c.w for c in chains) - 0.03) + a = Axis(fig[1, 1]; xlabel="poloidal angle θ (deg)", ylabel=PSIN, xticks=-180:90:180, limits=((-180, 180), (bottom, 1.02)), + title="$name at φ = $(_num(rad2deg(d.phi)))°") + for (k, c) in enumerate(chains) + shift = circshift(1:length(c.theta), length(c.theta) ÷ 2) + band!(a, sort(c.theta), c.lo[shift], c.hi[shift]; color=(colors[k+1], 0.6)) + hlines!(a, c.psis; color=(colors[k+1], 0.9), linewidth=0.8, linestyle=:dash) + text!(a, -178, c.psis; text=c.label, align=(:left, :center), fontsize=11) + end + # (R, Z): the same chains on the poloidal plane. + b = Axis(fig[1, 2]; aspect=DataAspect(), xlabel="R (m)", ylabel="Z (m)", title="On the poloidal plane") + for s in d.surfaces[1:(end-1)] + lines!(b, s...; color=(:black, 0.15), linewidth=0.8) + end + for (k, c) in enumerate(chains) + poly!(b, Point2f.(c.polygon); color=(colors[k+1], 0.7), strokewidth=0) + end + lines!(b, d.surfaces[end]...; color=:black, linewidth=1.5) + Legend(fig[1, 3], [PolyElement(; color=(colors[k+1], 0.7)) for k in eachindex(chains)], ["$(c.label): $(_num(c.w))" for c in chains], + rich("w/2 (", PSIN, ")"); framevisible=false) + colsize!(fig.layout, 1, Relative(0.5)) + fig + end +end diff --git a/ext/GPECMakieExt/theme.jl b/ext/GPECMakieExt/theme.jl new file mode 100644 index 000000000..05db374c5 --- /dev/null +++ b/ext/GPECMakieExt/theme.jl @@ -0,0 +1,71 @@ +# The shared look of every Analysis figure and the small text helpers its labels use. + +const WONG = Makie.wong_colors() + +const GPEC_THEME = Theme(; + fontsize=15, + figure_padding=(12, 18, 10, 10), + Axis=(titlesize=16, titlefont=:regular, titlegap=6, xlabelsize=15, ylabelsize=15, xticklabelsize=13, yticklabelsize=13, + xgridcolor=(:black, 0.07), ygridcolor=(:black, 0.07), spinewidth=1.0, xtickwidth=1.0, ytickwidth=1.0, + xminorticksvisible=false, yminorticksvisible=false, topspinevisible=true, rightspinevisible=true), + Legend=(framevisible=true, framecolor=(:black, 0.15), backgroundcolor=(:white, 0.88), labelsize=13, padding=(6, 6, 4, 4), rowgap=0, + patchsize=(18, 10)), + Colorbar=(labelsize=15, ticklabelsize=13, width=12, spinewidth=1.0), + Label=(fontsize=15,), + palette=(color=WONG, marker=[:circle, :rect, :utriangle, :diamond, :dtriangle]) +) + +# Build a figure under the GPEC theme without changing the session's global theme. +themed(f) = with_theme(f, GPEC_THEME) + +const SUPERSCRIPTS = Dict(zip("-0123456789", "⁻⁰¹²³⁴⁵⁶⁷⁸⁹")) +_sup(n::Integer) = join(SUPERSCRIPTS[c] for c in string(n)) + +# The power of ten to divide `v` by when its magnitude is outside [1e-2, 1e3), and that factor's axis label. +function _power(v) + finite = filter(isfinite, v) + m = isempty(finite) ? 0.0 : maximum(abs, finite) + (m == 0 || 1e-2 <= m < 1e3) && return 1.0, "" + k = floor(Int, log10(m)) + return 10.0^k, "10$(_sup(k))" +end + +# `v` divided by its power of ten, and that factor's axis label. +function _decade(v) + factor, label = _power(v) + return v ./ factor, label +end + +# Axis-label unit suffix, e.g. " (10⁻⁴ T)". +_unit(scale, unit) = isempty(scale) ? (isempty(unit) ? "" : " ($unit)") : " ($scale" * (isempty(unit) ? "" : " $unit") * ")" + +# Three significant figures, with ×10ⁿ outside [1e-2, 1e3) and no trailing ".0". +function _num(v) + v == 0 && return "0" + a = abs(v) + trim(s) = endswith(s, ".0") ? s[1:(end-2)] : s + 1e-2 <= a < 1e3 && return trim(string(round(v; sigdigits=3))) + k = floor(Int, log10(a)) + return trim(string(round(v / 10.0^k; sigdigits=3))) * "×10" * _sup(k) +end + +sub(a, b) = rich(a, subscript(b)) +sup(a, b) = rich(a, superscript(b)) +const PSIN = sub("ψ", "N") + +# y limits for pseudo-log axes from the data alone, with zero always in view. +function _symlims(v) + lo, hi = min(minimum(v), 0.0), max(maximum(v), 0.0) + return (lo < 0 ? 1.5lo : -1.0, hi > 0 ? 1.25hi : 1.0) +end + +# Rational surfaces as a q = m/n axis along the top of the ψ_N axis `ax`, laid over it at grid position `pos`. +function _surfaces_on_top!(pos, ax, psi, labels; title="") + top = Axis(pos; xaxisposition=:top, xticks=(psi, labels), xticklabelsize=11, xticklabelrotation=π / 2, title, + xgridvisible=false, ygridvisible=false, yticksvisible=false, yticklabelsvisible=false) + hidespines!(top) + linkxaxes!(ax, top) + hideydecorations!(top) + vlines!(ax, psi; color=(:black, 0.18), linewidth=1) + return top +end diff --git a/src/Analysis/Analysis.jl b/src/Analysis/Analysis.jl index cf3e8abdc..2b1f3714b 100644 --- a/src/Analysis/Analysis.jl +++ b/src/Analysis/Analysis.jl @@ -5,7 +5,8 @@ Post-processing and visualization utilities for GPEC simulation outputs. Every registered plot takes a result in memory or the path of the `gpec.h5` it was saved to, and throws an `ArgumentError` naming what to change in the run when that source lacks the data it draws. `available_plots(src)` -lists the plots for a result, a path, or the named tuple `main` returns. +lists the plots for a result, a path, or the named tuple `main` returns. Every plot is exported here and from the +package, so `plot_qprofile(eq)` needs no module prefix. ## Submodules @@ -18,6 +19,29 @@ lists the plots for a result, a path, or the named tuple `main` returns. """ module Analysis +# The plots draw with Makie through the GPECMakieExt extension, which sets this when a backend loads. +const _BACKEND = Ref(false) + +""" + _draw(plotter, args...; save_path=nothing, kwargs...) + +Draw a figure with `plotter`, a drawing function the GPECMakieExt extension defines, and save it when `save_path` +is given. Throws an `ArgumentError` when no Makie backend is loaded. +""" +function _draw(plotter, args...; save_path=nothing, kwargs...) + _BACKEND[] || throw(ArgumentError("plotting needs a Makie backend: `using CairoMakie` for files or `using GLMakie` for interactive windows")) + fig = plotter(args...; kwargs...) + isnothing(save_path) || _save(fig, save_path) + return fig +end + +""" + _save(fig, path) + +Save a figure with the loaded Makie backend; defined by the GPECMakieExt extension. +""" +function _save end + include("Registry.jl") include("ForceFreeStates.jl") include("Equilibrium.jl") @@ -26,6 +50,14 @@ include("PerturbedEquilibrium.jl") include("PerturbedEquilibriumModes.jl") include("ErrorFields.jl") +# Every public plot of the submodules, callable as `plot_x` without its module; two modules defining one name is an error. +for m in (ForceFreeStates, Equilibrium, CoilForcing, PerturbedEquilibrium, ErrorFields), name in names(m; all=true) + startswith(string(name), "plot_") || continue + isdefined(@__MODULE__, name) && error("Analysis: $name is defined by $(nameof(m)) and another plot module") + @eval using .$(nameof(m)): $name + @eval export $name +end + export available_plots end # module Analysis diff --git a/src/Analysis/CoilForcing.jl b/src/Analysis/CoilForcing.jl index 371c5548f..353a56278 100644 --- a/src/Analysis/CoilForcing.jl +++ b/src/Analysis/CoilForcing.jl @@ -1,243 +1,16 @@ """ CoilForcing -Visualization functions for coil geometry and the resulting normal field -perturbations on the plasma boundary. - -Two 3D entry points cover the common cases: [`plot_coil_geometry_3d`](@ref) draws the coils -alone, and [`plot_surface_3d`](@ref) draws the plasma boundary surface — optionally coloured by -any control-surface field (via [`control_surface_scalar`](@ref)) and/or with coils overlaid — -the Julia analog of GPEC's `plot_control_3d`. The `:RdBu` colour scale is shared, so a blue -(positive-current) coil and a blue (positive) field read identically. - -All functions return `Plots.jl` plot objects and accept an optional `save_path` -keyword argument to write the figure to disk. +Coil geometry and fields on the plasma control surface: the coils in 3D and in (R, Z), the forcing spectrum, normal +field maps in (φ, θ), and the 3D boundary coloured by any control-surface field. Coils are coloured on the same +diverging map as the normal field they drive, so a positive current reads as positive b_n beneath the coil. """ module CoilForcing -using Plots import ...Utilities.FourierTransforms: FourierTransform, inverse -import ...PerturbedEquilibrium: PerturbedEquilibriumState -using ..Analysis: register!, _require - -""" - plot_coil_geometry_3d(coil_sets; save_path=nothing, kwargs...) - -Plot the 3D wire geometry of all coil sets. - -Each coil set is shown with a distinct color; individual conductors are plotted -as lines in Cartesian (X, Y, Z) space in meters. - -### Arguments - - - `coil_sets`: `Vector{CoilSet}` from `ForcingTerms.load_coil_sets` - -### Keyword arguments - - - `save_path`: file path to save the figure (default: `nothing`) - - Any extra kwargs are forwarded to `Plots.plot` - -### Returns - -A `Plots.jl` plot object. -""" -function plot_coil_geometry_3d(coil_sets; save_path=nothing, kwargs...) - clim = _current_clim(coil_sets) - - p = plot(; xlabel="X [m]", ylabel="Y [m]", zlabel="Z [m]", - title="Coil geometry (3D)", legend=false, - camera=(30, 20), kwargs...) - - _overlay_coils!(p, coil_sets; clim=clim) - - # Invisible scatter spanning the current range to force the colorbar to appear - scatter!(p, [NaN], [NaN], [NaN]; - zcolor=[0.0], clims=(-clim, clim), color=:RdBu, - colorbar_title="I [A]", markersize=0) - - isnothing(save_path) || savefig(p, save_path) - return p -end - -# Symmetric current colour-limit (centred at zero) for a set of coils. -function _current_clim(coil_sets) - all_currents = [I for cs in coil_sets for I in cs.currents] - return isempty(all_currents) ? 1.0 : max(maximum(abs, all_currents), eps()) -end - -""" - _overlay_coils!(p, coil_sets; clim=nothing) - -Draw each conductor of every coil set onto plot `p` as a 3D `path3d` curve, coloured on a -symmetric `:RdBu` scale by its current (positive = blue, negative = red — the same mapping -used for surface fields, so a blue coil and a blue field read identically). -""" -function _overlay_coils!(p, coil_sets; clim=nothing) - cl = isnothing(clim) ? _current_clim(coil_sets) : clim - for cs in coil_sets - for j in 1:cs.ncoil - t = clamp((cs.currents[j] + cl) / (2cl), 0.0, 1.0) - col = get(cgrad(:RdBu), t) - for k in 1:cs.s - plot!(p, view(cs.x, j, k, :), view(cs.y, j, k, :), view(cs.z, j, k, :); - seriestype=:path3d, color=col, lw=2, linestyle=:solid, label="") - end - end - end - return p -end - -""" - plot_coil_geometry_rz(coil_sets; equil=nothing, psi=nothing, save_path=nothing, kwargs...) - -Plot coil cross-sections in the (R, Z) meridional plane. - -Each coil strand is shown as a closed curve in `(R, Z) = (√(X²+Y²), Z)` space. -If `equil` is provided, the plasma boundary is overplotted for reference. - -### Arguments - - - `coil_sets`: `Vector{CoilSet}` from `ForcingTerms.load_coil_sets` - -### Keyword arguments - - - `equil`: optional `PlasmaEquilibrium`; if given, overlays the plasma boundary - - `psi`: flux surface to trace (default: `equil.rzphi_xs[end]`, the outermost grid point) - - `save_path`: file path to save the figure (default: `nothing`) - - Any extra kwargs are forwarded to `Plots.plot` - -### Returns - -A `Plots.jl` plot object. -""" -function plot_coil_geometry_rz(coil_sets; equil=nothing, psi=nothing, save_path=nothing, kwargs...) - p = plot(; xlabel="R [m]", ylabel="Z [m]", - title="Coil cross-sections (R, Z)", legend=:topright, - aspect_ratio=:equal, kwargs...) - - colors = [:blue, :red, :green, :orange, :purple, :brown, :cyan, :magenta] - for (ci, cs) in enumerate(coil_sets) - col = colors[mod1(ci, length(colors))] - for j in 1:cs.ncoil - for k in 1:cs.s - R_strand = sqrt.(view(cs.x, j, k, :) .^ 2 .+ view(cs.y, j, k, :) .^ 2) - Z_strand = view(cs.z, j, k, :) - label = (j == 1 && k == 1) ? cs.name : "" - plot!(p, R_strand, Z_strand; label=label, color=col, lw=1.5) - end - end - end - - if !isnothing(equil) - psi_bnd = isnothing(psi) ? equil.rzphi_xs[end] : Float64(psi) - mtheta_boundary = length(equil.rzphi_ys) - hint2d = (Ref(1), Ref(1)) - R_bnd = zeros(mtheta_boundary) - Z_bnd = zeros(mtheta_boundary) - for (i, θ_sfl) in enumerate(equil.rzphi_ys) - r_minor = sqrt(equil.rzphi_rsquared((psi_bnd, θ_sfl); hint=hint2d)) - θ_cyl = 2π * (θ_sfl + equil.rzphi_offset((psi_bnd, θ_sfl); hint=hint2d)) - R_bnd[i] = equil.ro + r_minor * cos(θ_cyl) - Z_bnd[i] = equil.zo + r_minor * sin(θ_cyl) - end - plot!(p, [R_bnd; R_bnd[1]], [Z_bnd; Z_bnd[1]]; - label="plasma boundary (ψ=$(round(psi_bnd; digits=3)))", color=:black, lw=2) - end - - isnothing(save_path) || savefig(p, save_path) - return p -end - -""" - plot_bn_contour(bn, mtheta, nzeta; n=nothing, save_path=nothing, kwargs...) - -Plot the normal magnetic field `bn(θ, ζ)` on the plasma boundary as a 2D heatmap. - -Expects the raw Biot-Savart B·n̂ field in Tesla (before `project_normal_flux!` -converts to flux). If plotting post-projection data, note units are T·m² not T. - -### Arguments - - - `bn`: normal field matrix `[mtheta, nzeta]` — typically B·n̂ in Tesla - - `mtheta`: number of poloidal grid points - - `nzeta`: number of toroidal grid points - -### Keyword arguments - - - `n`: toroidal mode number (used for axis label only) - - `save_path`: file path to save the figure (default: `nothing`) - - Any extra kwargs are forwarded to `Plots.heatmap` - -### Returns - -A `Plots.jl` plot object. -""" -function plot_bn_contour(bn::Matrix{Float64}, mtheta::Int, nzeta::Int; - n=nothing, save_path=nothing, kwargs...) - # Shift rows so θ=0 (outboard midplane) is centred on the y-axis at 0° - bn_wrapped = circshift(bn, (mtheta ÷ 2, 0)) - theta_deg = range(-180; step=360.0 / mtheta, length=mtheta) - zeta_deg = range(0; step=360.0 / nzeta, length=nzeta) - - n_label = isnothing(n) ? "" : " (n=$n)" - p = heatmap(zeta_deg, theta_deg, bn_wrapped; - xlabel="zeta [deg]", ylabel="theta [deg]", - ylims=(-180, 180), - title="Normal field bₙ(θ, ζ)$n_label [T]", - color=:RdBu, kwargs...) - - isnothing(save_path) || savefig(p, save_path) - return p -end - -""" - plot_mode_spectrum(forcing_modes; mlow=nothing, mhigh=nothing, save_path=nothing, kwargs...) - -Plot the Fourier mode spectrum `|bmn|` vs poloidal mode number m as a step line, grouped by n. - -### Arguments - - - `forcing_modes`: `Vector{ForcingMode}` from `compute_coil_forcing_modes!` - -### Keyword arguments - - - `mlow`, `mhigh`: poloidal mode range for the x-axis (e.g. from ForceFreeStates config); - if provided, sets xlims to span the full stability range even if some modes are zero - - `save_path`: file path to save the figure (default: `nothing`) - - Any extra kwargs are forwarded to `Plots.plot` - -### Returns - -A `Plots.jl` plot object. -""" -function plot_mode_spectrum(forcing_modes; mlow=nothing, mhigh=nothing, save_path=nothing, kwargs...) - n_vals = sort(unique(m.n for m in forcing_modes)) - colors = [:blue, :red, :green, :orange, :purple, :brown] - - p = plot(; xlabel="Poloidal mode m", ylabel="|Φ_x(m,n)| [unit-norm]", - title="Coil forcing mode spectrum", - legend=:topright, kwargs...) - - for (ni, n) in enumerate(n_vals) - modes_n = filter(m -> m.n == n, forcing_modes) - sort!(modes_n; by=m -> m.m) - m_vals = [m.m for m in modes_n] - amps = abs.([m.amplitude for m in modes_n]) - col = colors[mod1(ni, length(colors))] - m_ext = [m_vals[1] - 1; m_vals; m_vals[end] + 1] - amp_ext = [0.0; amps; 0.0] - plot!(p, m_ext, amp_ext; label="n=$n", color=col, lw=2, seriestype=:steppre) - end - - ylims!(p, (0, Inf)) - if !isnothing(mlow) && !isnothing(mhigh) - # zero endpoints are at mlow-1 and mhigh+1; add ±1 so they're visible with whitespace - xlims!(p, mlow - 2, mhigh + 2) - end - - isnothing(save_path) || savefig(p, save_path) - return p -end +import ...PerturbedEquilibrium: PerturbedEquilibriumState, PlasmaResponse +import ...ForcingTerms as FT +using ..Analysis: register!, _require, _draw # Helicity = sign(Bt)·sign(Ip); the SFL→machine toroidal-angle handedness. _equil_helicity(equil) = equil.params.bt_sign * equil.params.ip_sign @@ -276,9 +49,9 @@ exponential into the `ν` correction times `e^{i n·hel·φ}`. The reconstruction tracks the source coil **toroidally** (the field lobe sits at the coil's φ), carried by `helicity = sign(Bt)·sign(Ip)` in `e^{i n·hel·φ}` (`equil.rzphi_nu`/`equil.params`). - The leading `-` is a display orientation so that **a positive (blue) coil current reads as a - positive (blue) `b_n_x` lobe beneath it** — GPEC's outward-normal flux is intrinsically negative - there, so the overall sign is the blue-coil↔blue-surface convention while the spatial structure + The leading `-` is a display orientation so that **a positive coil current reads as a + positive `b_n_x` lobe beneath it** — GPEC's outward-normal flux is intrinsically negative + there, so the overall sign is the coil↔surface colour convention while the spatial structure and magnitude are physical. Pass `helicity` to override `sign(Bt)·sign(Ip)` if the toroidal tracking is wrong for your machine. @@ -312,12 +85,8 @@ function control_surface_scalar(equil, modes, n::Int; mtheta::Int=180, nphi::Int ft = FourierTransform(mtheta, mpert, m_low) f = inverse(ft, vec) - # SFL→machine toroidal-angle correction e^{i n ν(ψ,θ)}. The modes are decomposed on the SFL - # grid against e^{-i(m θ - n ζ)}, with the machine angle φ = -hel(2π ζ + ν) (see CoilFourier - # `sample_boundary_grid`). Substituting 2π ζ = -hel φ - ν into the inverse turns the ζ - # exponential into e^{i n ν(θ)} e^{i n hel φ}: the per-θ ν enters the field, hel sets the - # toroidal handedness, and the real field reconstructs faithfully — so a positive (blue) coil - # current yields positive (blue) b_n_x directly beneath the coil with no extra sign flip. + # SFL→machine toroidal-angle correction e^{i n ν(ψ,θ)}: substituting 2π ζ = -hel φ - ν into the inverse turns the + # ζ exponential into e^{i n ν(θ)} e^{i n hel φ}. hel = isnothing(helicity) ? _equil_helicity(equil) : helicity hint = (Ref(1), Ref(1)) for i in 1:mtheta @@ -326,13 +95,7 @@ function control_surface_scalar(equil, modes, n::Int; mtheta::Int=180, nphi::Int f[i] *= exp(im * n * ν) end - # θ→φ extension in the machine angle: c(θ,φ) = -Re[ f(θ) e^{i n hel φ} ]. The reconstruction - # tracks the source coil toroidally (the lobe sits at the coil's φ); the leading − is a display - # orientation that makes a positive (blue) coil current read as a positive (blue) b_n_x lobe - # beneath it. GPEC's outward-normal flux is intrinsically negative there, so this overall sign - # is the blue-coil↔blue-surface convention — the spatial structure and magnitude are physical. - # It is a constant flip (not helicity-dependent): the normal-projection sign is set by geometry, - # while hel only carries the toroidal handedness, already in the exponential. + # θ→φ extension in the machine angle; the leading − makes a positive coil current read as positive b_n beneath it. c = zeros(Float64, mtheta, nphi) for k in 1:nphi ph = exp(im * n * hel * (k - 1) * 2π / nphi) @@ -343,21 +106,25 @@ function control_surface_scalar(equil, modes, n::Int; mtheta::Int=180, nphi::Int return c end -# Build the closed (θ,φ)→(x,y,z) boundary surface from the equilibrium splines. -# Returns X, Y, Z each [mtheta+1, nphi+1] (wrapped so the torus closes seamlessly). -function _boundary_surface_xyz(equil, mtheta::Int, nphi::Int, psi) +# The boundary (or flux surface `psi`) in (R, Z) at `mtheta` SFL angles θ = (i-1)/mtheta. +function _surface_rz(equil, mtheta::Int, psi) psi_b = isnothing(psi) ? equil.rzphi_xs[end] : Float64(psi) hint = (Ref(1), Ref(1)) - R = zeros(mtheta) - Z = zeros(mtheta) + R, Z = zeros(mtheta), zeros(mtheta) for i in 1:mtheta θ = (i - 1) / mtheta - r_minor = sqrt(equil.rzphi_rsquared((psi_b, θ); hint=hint)) - θ_cyl = 2π * (θ + equil.rzphi_offset((psi_b, θ); hint=hint)) - R[i] = equil.ro + r_minor * cos(θ_cyl) - Z[i] = equil.zo + r_minor * sin(θ_cyl) + r = sqrt(max(0.0, equil.rzphi_rsquared((psi_b, θ); hint=hint))) + η = 2π * (θ + equil.rzphi_offset((psi_b, θ); hint=hint)) + R[i] = equil.ro + r * cos(η) + Z[i] = equil.zo + r * sin(η) end + return R, Z +end +# Build the closed (θ,φ)→(x,y,z) boundary surface from the equilibrium splines. +# Returns X, Y, Z each [mtheta+1, nphi+1] (wrapped so the torus closes seamlessly). +function _boundary_surface_xyz(equil, mtheta::Int, nphi::Int, psi) + R, Z = _surface_rz(equil, mtheta, psi) X = zeros(mtheta + 1, nphi + 1) Y = zeros(mtheta + 1, nphi + 1) Zs = zeros(mtheta + 1, nphi + 1) @@ -375,115 +142,331 @@ function _boundary_surface_xyz(equil, mtheta::Int, nphi::Int, psi) end # Wrap a [mtheta, nphi] field to [mtheta+1, nphi+1] to match the closed surface mesh. -_wrap_field(c) = [c[i <= size(c, 1) ? i : 1, k <= size(c, 2) ? k : 1] - for i in 1:(size(c, 1)+1), k in 1:(size(c, 2)+1)] +_wrap_field(c) = [c[i <= size(c, 1) ? i : 1, k <= size(c, 2) ? k : 1] for i in 1:(size(c, 1)+1), k in 1:(size(c, 2)+1)] + +# A real control-surface field with θ shifted so the outboard midplane is at 0: φ, θ in degrees and the field [θ, φ]. +function _surface_map(c) + mtheta, nphi = size(c) + θ = collect(range(-180, 180; length=mtheta + 1)[1:mtheta]) + φ = collect(range(0, 360; length=nphi + 1)[1:nphi]) + return (; phi=φ, theta=θ, field=circshift(c, (mtheta ÷ 2, 0))) +end + +# Each conductor of `coil_sets` projected onto the control-surface map: machine φ = atan(y, x), and the boundary θ (SFL, +# outboard midplane at 0) whose point has the same geometric poloidal angle about the magnetic axis. Returns +# (φ, θ, current) per conductor, in degrees. +function _coils_on_surface(equil, coil_sets; ntheta=2048) + psi_b = equil.rzphi_xs[end] + hint = (Ref(1), Ref(1)) + θs = range(0, 1; length=ntheta + 1)[1:ntheta] + geometric = [mod(2π * (θ + equil.rzphi_offset((psi_b, θ); hint=hint)), 2π) for θ in θs] + order = sortperm(geometric) + sorted = geometric[order] + sfl(η) = θs[order[clamp(searchsortedfirst(sorted, mod(η, 2π)), 1, ntheta)]] + lines = Tuple{Vector{Float64},Vector{Float64},Float64}[] + for cs in coil_sets, j in 1:cs.ncoil, k in 1:cs.s + x, y, z = cs.x[j, k, :], cs.y[j, k, :], cs.z[j, k, :] + φ = mod.(rad2deg.(atan.(y, x)), 360) + θ = [mod(360 * sfl(atan(zz - equil.zo, hypot(xx, yy) - equil.ro)) + 180, 360) - 180 for (xx, yy, zz) in zip(x, y, z)] + push!(lines, (φ, θ, cs.currents[j])) + end + return lines +end + +const _WEIGHTINGS = (:root_area, :area, :bare) + +# A control-surface spectrum in any weighting, converted to the bare normal field b (T): b = (S·√A)⁻¹·b̃, b̃ = S⁻¹·b̄. +function _to_bare(resp, v, weighting) + weighting in _WEIGHTINGS || throw(ArgumentError("weighting must be :root_area, :area or :bare")) + S, A = resp.rootarea_to_area_weight, resp.surface_area + btilde = weighting === :root_area ? v : weighting === :area ? S \ v : (S .* sqrt(A)) * v + return (S .* sqrt(A)) \ btilde +end + +# The bare normal field of spectrum `v` on the control surface for toroidal mode `n`, mapped to (φ, θ), with the +# conductors of `coil_sets` projected onto it when given. +function _control_surface_data(resp::PlasmaResponse, v; weighting=:root_area, n=nothing, coil_sets=nothing, mtheta=256, nphi=180) + equil = resp.ffs.equil + nn = isnothing(n) ? first(resp.n_modes) : n + sel = resp.n_modes .== nn + any(sel) || throw(ArgumentError("plot_control_surface: no harmonics with n = $nn")) + b = _to_bare(resp, v, weighting) + c = control_surface_scalar(equil, (resp.m_modes[sel], b[sel]), nn; mtheta, nphi) + return (; _surface_map(c)..., n=nn, coils=coil_sets === nothing ? nothing : _coils_on_surface(equil, coil_sets)) +end + +# Drawing functions, defined by the GPECMakieExt extension. +function _plot_coils_3d end +function _plot_coils_rz end +function _plot_surface_map end +function _plot_forcing_spectrum end """ - plot_surface_3d(equil; color_by=nothing, coil_sets=nothing, mtheta=180, nphi=180, - psi=nothing, clim=nothing, colorbar_title="", title="Plasma boundary", - camera=(35, 25), save_path=nothing, kwargs...) + plot_coil_geometry_3d(coil_sets; equil=nothing, save_path=nothing) + plot_coil_geometry_3d(pe; save_path=nothing) -Plot the 3D plasma boundary surface, optionally coloured by a control-surface field and/or with -coils overlaid — the Julia analog of GPEC's `plot_control_3d`. +Plot the coil conductors in 3D at equal data aspect, coloured by current, with the plasma boundary as an opaque surface +when an equilibrium is given. Use GLMakie to rotate it. -!!! note "Backend for coloured surfaces" +### Arguments - Scalar colouring of the closed surface requires a PlotlyJS-style backend (`plotlyjs()`, - already a GPEC dependency); the default GR backend cannot colour a parametric surface by an - independent array and will draw an **uncoloured** surface with a warning. Call `plotlyjs()` - before requesting `color_by`. Coil overlay and uncoloured surfaces work on any backend. + - `coil_sets`: `Vector{CoilSet}` from `ForcingTerms.load_coil_sets` + - `pe`: A `PerturbedEquilibriumState` forced by coils, drawn with its boundary ### Keyword arguments - - `color_by`: optional real-space `[mtheta, nphi]` scalar (any control-surface output — e.g. from - [`control_surface_scalar`](@ref)). Coloured on a symmetric `:RdBu` scale centred at zero - (positive = blue), matching the coil-current colours. `nothing` draws an uncoloured surface. - - `coil_sets`: optional `Vector{CoilSet}` overlaid as 3D curves (see [`plot_coil_geometry_3d`](@ref)). - - `mtheta`, `nphi`, `psi`: surface grid resolution and flux surface (must match `color_by`). - - `clim`: symmetric colour limit (default `maximum(abs, color_by)`). - - `save_path`: file path to save the figure; extra `kwargs` forward to `Plots.surface`. + - `equil`: A `PlasmaEquilibrium` whose boundary is drawn (default: `nothing`) + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. +""" +function plot_coil_geometry_3d(coil_sets; equil=nothing, save_path=nothing) + surface = isnothing(equil) ? nothing : _boundary_surface_xyz(equil, 120, 160, nothing) + return _draw(_plot_coils_3d, coil_sets, surface, nothing; save_path) +end - plot_surface_3d(equil, modes, n; coil_sets=nothing, colorbar_title="", kwargs...) +function plot_coil_geometry_3d(pe::PerturbedEquilibriumState; save_path=nothing) + _require(plot_coil_geometry_3d, pe) + return plot_coil_geometry_3d(pe.coil_sets; equil=pe.response.ffs.equil, save_path) +end -Convenience method: colour the surface by [`control_surface_scalar(equil, modes, n; …)`](@ref). -""" -function plot_surface_3d(equil; color_by=nothing, coil_sets=nothing, mtheta::Int=180, - nphi::Int=180, psi=nothing, clim=nothing, colorbar_title="", - title="Plasma boundary", camera=(35, 25), save_path=nothing, kwargs...) - X, Y, Z = _boundary_surface_xyz(equil, mtheta, nphi, psi) - - common = (; xlabel="X [m]", ylabel="Y [m]", zlabel="Z [m]", title=title, - legend=false, aspect_ratio=:equal, camera=camera) - - want_color = !isnothing(color_by) - if want_color - size(color_by) == (mtheta, nphi) || - error("color_by must be [mtheta, nphi] = ($mtheta, $nphi), got $(size(color_by))") - # Only PlotlyJS-style backends honour `fill_z` on a closed/parametric surface; GR silently - # colours by Z instead, so warn and degrade rather than show a misleading figure. - if !(backend_name() in (:plotlyjs, :pythonplot, :pyplot)) - @warn "plot_surface_3d: the active Plots backend ($(backend_name())) cannot colour a " * - "parametric 3D surface by a scalar — call `plotlyjs()` first for field-coloured " * - "surfaces. Drawing an uncoloured surface." maxlog = 1 - want_color = false +# The coil sets of every coil term of `rmp`, each set's currents scaled by its term's weight (by the weight's magnitude +# when it is complex, which shifts the toroidal phase), with the number of terms that have no geometry. +function _rmp_coil_sets(rmp::FT.RMPField; n=1) + sets, skipped, complex_weight = FT.CoilSet[], 0, false + for t in FT._rmp_terms(rmp) + if !(t isa FT.RMPSource && t.ctrl.forcing_data_format == "coil") + skipped += 1 + continue + end + complex_weight |= !isreal(t.scale) + w = isreal(t.scale) ? real(t.scale) : abs(t.scale) + for cs in FT.load_coil_sets(FT.CoilConfig(t.ctrl), n) + push!(sets, FT.CoilSet(cs.name, cs.ncoil, cs.s, cs.nw, cs.nsec, cs.x, cs.y, cs.z, w .* cs.currents)) end end + return (; sets, skipped, complex_weight) +end - if want_color - cl = isnothing(clim) ? max(maximum(abs, color_by), eps()) : clim - cw = _wrap_field(color_by) - p = surface(X, Y, Z; fill_z=cw, color=:RdBu, clims=(-cl, cl), - colorbar_title=colorbar_title, common..., kwargs...) - else - p = surface(X, Y, Z; color=:grays, alpha=0.5, colorbar=false, common..., kwargs...) - end +""" + plot_sources(rmp; n=1, color=:current, save_path=nothing) - isnothing(coil_sets) || _overlay_coils!(p, coil_sets) - isnothing(save_path) || savefig(p, save_path) - return p -end +Plot the coils of an `RMPField` in 3D at equal data aspect, with no plasma: every coil term of a sum together, each +term's currents scaled by its weight. Mode-table and spectrum terms have no geometry and are counted in the title. Use +GLMakie to rotate it. + +### Arguments + + - `rmp`: An `RMPField` with at least one coil term + +### Keyword arguments + + - `n`: Toroidal mode number that sets the n-modulated tilts (default: `1`) + - `color`: `:current` colours each conductor by its current in kA, `:set` gives each coil set its own colour (default: `:current`) + - `save_path`: If given, save the figure to this path (default: `nothing`) + +### Returns -function plot_surface_3d(equil, modes, n::Int; coil_sets=nothing, mtheta::Int=180, - nphi::Int=180, psi=nothing, helicity=nothing, - colorbar_title="", save_path=nothing, kwargs...) - c = control_surface_scalar(equil, modes, n; mtheta=mtheta, nphi=nphi, psi=psi, helicity=helicity) - return plot_surface_3d(equil; color_by=c, coil_sets=coil_sets, mtheta=mtheta, nphi=nphi, - psi=psi, colorbar_title=colorbar_title, save_path=save_path, kwargs...) +A Makie `Figure`. +""" +function plot_sources(rmp::FT.RMPField; n::Int=1, color::Symbol=:current, save_path=nothing) + _require(plot_sources, rmp) + color in (:current, :set) || throw(ArgumentError("plot_sources: color must be :current or :set")) + d = _rmp_coil_sets(rmp; n) + notes = String[] + d.skipped > 0 && push!(notes, "$(d.skipped) mode-table term$(d.skipped == 1 ? "" : "s") not drawn") + d.complex_weight && push!(notes, "complex weights drawn at their magnitude") + nset, ncond = length(d.sets), sum(cs.ncoil for cs in d.sets) + title = "Coil sources: $nset set$(nset == 1 ? "" : "s"), $ncond conductor$(ncond == 1 ? "" : "s")" * (isempty(notes) ? "" : " ($(join(notes, "; ")))") + return _draw(_plot_coils_3d, d.sets, nothing, nothing; title, by_set=color === :set, save_path) end """ - plot_coil_geometry_3d(pe::PerturbedEquilibriumState; kwargs...) - plot_coil_geometry_rz(pe::PerturbedEquilibriumState; kwargs...) - plot_mode_spectrum(pe::PerturbedEquilibriumState; kwargs...) + plot_coil_geometry_rz(coil_sets; equil=nothing, psi=nothing, save_path=nothing) + plot_coil_geometry_rz(pe; save_path=nothing) + +Plot the coils in the (R, Z) plane, each set labelled beside itself, over the plasma boundary when an equilibrium is +given. A single toroidal loop is a point in (R, Z) and is drawn as a marker. + +### Arguments + + - `coil_sets`: `Vector{CoilSet}` from `ForcingTerms.load_coil_sets` + - `pe`: A `PerturbedEquilibriumState` forced by coils, drawn with its boundary -The coil plots of the coil sets a perturbed equilibrium was forced by: the (R, Z) cross-sections over its plasma -boundary, and the forcing spectrum across its poloidal mode range. +### Keyword arguments + + - `equil`: A `PlasmaEquilibrium` whose boundary is drawn (default: `nothing`) + - `psi`: The flux surface drawn as the boundary (default: the outermost grid surface) + - `save_path`: If given, save the figure to this path (default: `nothing`) + +### Returns + +A Makie `Figure`. """ -function plot_coil_geometry_3d(pe::PerturbedEquilibriumState; kwargs...) - _require(plot_coil_geometry_3d, pe) - return plot_coil_geometry_3d(pe.coil_sets; kwargs...) +function plot_coil_geometry_rz(coil_sets; equil=nothing, psi=nothing, save_path=nothing) + boundary = isnothing(equil) ? nothing : _surface_rz(equil, 256, psi) + return _draw(_plot_coils_rz, coil_sets, boundary; save_path) end -function plot_coil_geometry_rz(pe::PerturbedEquilibriumState; kwargs...) +function plot_coil_geometry_rz(pe::PerturbedEquilibriumState; save_path=nothing) _require(plot_coil_geometry_rz, pe) - return plot_coil_geometry_rz(pe.coil_sets; equil=pe.response.ffs.equil, kwargs...) + return plot_coil_geometry_rz(pe.coil_sets; equil=pe.response.ffs.equil, save_path) end -function plot_mode_spectrum(pe::PerturbedEquilibriumState; kwargs...) +""" + plot_mode_spectrum(forcing_modes; mlow=nothing, mhigh=nothing, rational_m=Int[], save_path=nothing) + plot_mode_spectrum(pe; save_path=nothing) + +Plot the forcing spectrum |Φ_x(m, n)| against poloidal harmonic m as step lines, one per toroidal mode number. + +### Arguments + + - `forcing_modes`: `Vector{ForcingMode}` from `compute_coil_forcing_modes!` + - `pe`: A `PerturbedEquilibriumState`, drawn across its poloidal range with its resonant harmonics marked + +### Keyword arguments + + - `mlow`, `mhigh`: Poloidal range of the x axis (default: the modes' own range) + - `rational_m`: Resonant harmonics to mark (default: none) + - `save_path`: If given, save the figure to this path (default: `nothing`) + +### Returns + +A Makie `Figure`. +""" +function plot_mode_spectrum(forcing_modes; mlow=nothing, mhigh=nothing, rational_m=Int[], save_path=nothing) + return _draw(_plot_forcing_spectrum, forcing_modes, mlow, mhigh, rational_m; save_path) +end + +function plot_mode_spectrum(pe::PerturbedEquilibriumState; save_path=nothing) _require(plot_mode_spectrum, pe) - return plot_mode_spectrum(pe.forcing_modes; mlow=pe.response.ffs.mlow, mhigh=pe.response.ffs.mhigh, kwargs...) + ffs = pe.response.ffs + return plot_mode_spectrum(pe.forcing_modes; mlow=ffs.mlow, mhigh=ffs.mhigh, rational_m=pe.response.rational_m_res, save_path) +end + +""" + plot_control_surface(src, v; weighting=:root_area, n=nothing, label="the given spectrum", arb=false, coils=false, + save_path=nothing) + +Plot any control-surface spectrum, such as a forcing, a response or an eigenvector, as the bare normal field b_n on the +control surface: toroidal angle φ against poloidal angle θ (0 at the outboard midplane). + +### Arguments + + - `src`: A `PlasmaResponse` supplying the control-surface operators and geometry, or a `PerturbedEquilibriumState`, + whose coils can also be drawn + - `v`: The spectrum, one entry per control-surface harmonic + +### Keyword arguments + + - `weighting`: How `v` is stored: `:root_area` (b̃, default), `:area` (b̄) or `:bare` (b) + - `n`: Toroidal mode number drawn (default: the first) + - `label`: The source named in the title, e.g. `"the applied forcing"` + - `arb`: Label the colour scale in arbitrary units, for a normalized vector (default: `false`, tesla) + - `coils`: Draw the coil conductors projected onto the map (default: `false`) + - `save_path`: If given, save the figure to this path (default: `nothing`) + +### Returns + +A Makie `Figure`. +""" +function plot_control_surface(resp::PlasmaResponse, v::AbstractVector; weighting=:root_area, n=nothing, label="the given spectrum", arb=false, + coils=false, save_path=nothing) + coils && throw(ArgumentError("plot_control_surface: coils = true needs the coil-forced PerturbedEquilibriumState, not its PlasmaResponse")) + d = _control_surface_data(resp, v; weighting, n) + return _draw(_plot_surface_map, d; title="Normal field on the control surface from $label", arb, save_path) +end + +function plot_control_surface(pe::PerturbedEquilibriumState, v::AbstractVector; weighting=:root_area, n=nothing, label="the given spectrum", + arb=false, coils=false, save_path=nothing) + coils && isempty(pe.coil_sets) && throw(ArgumentError("plot_control_surface: coils = true needs a coil-forced run")) + d = _control_surface_data(pe.response, v; weighting, n, coil_sets=coils ? pe.coil_sets : nothing) + return _draw(_plot_surface_map, d; title="Normal field on the control surface from $label", arb, save_path) +end + + +""" + plot_bn_contour(pe; coils=false, save_path=nothing) + plot_bn_contour(bn, mtheta, nzeta; n=nothing, save_path=nothing) + +Plot the applied normal field b_n on the control surface, toroidal angle φ against poloidal angle θ (0 at the outboard +midplane), optionally with the coil conductors projected onto it; or a raw Biot–Savart B·n̂ grid in tesla. + +### Arguments + + - `pe`: A `PerturbedEquilibriumState` with a forcing + - `bn`: Normal field `[mtheta, nzeta]` on the boundary grid + +### Keyword arguments + + - `coils`: Draw the coil conductors projected onto the map (default: `false`) + - `n`: Toroidal mode number named in the title (default: `nothing`) + - `save_path`: If given, save the figure to this path (default: `nothing`) + +### Returns + +A Makie `Figure`. +""" +function plot_bn_contour(pe::PerturbedEquilibriumState; coils=false, save_path=nothing) + _require(plot_bn_contour, pe) + return plot_control_surface(pe, pe.forcing_b; weighting=:bare, label="the applied forcing", coils, save_path) +end + +function plot_bn_contour(bn::Matrix{Float64}, mtheta::Int, nzeta::Int; n=nothing, save_path=nothing) + size(bn) == (mtheta, nzeta) || throw(ArgumentError("bn must be [mtheta, nzeta] = ($mtheta, $nzeta), got $(size(bn))")) + d = (; _surface_map(bn)..., n, coils=nothing) + title = "Normal field B·n̂ on the boundary" * (isnothing(n) ? "" : ", n = $n") + return _draw(_plot_surface_map, d; title, arb=false, save_path) +end + +""" + plot_surface_3d(equil; color_by=nothing, coil_sets=nothing, mtheta=180, nphi=180, psi=nothing, clim=nothing, + label="", title="Plasma boundary", save_path=nothing) + plot_surface_3d(equil, modes, n; coil_sets=nothing, mtheta=180, nphi=180, psi=nothing, helicity=nothing, + label="", save_path=nothing) + +Plot the 3D plasma boundary at equal data aspect, unlit and coloured by a control-surface field on a diverging map, +with coils coloured by current on the same map. Use GLMakie to rotate it. The second form colours the surface by +[`control_surface_scalar`](@ref). + +### Keyword arguments + + - `color_by`: A real `[mtheta, nphi]` field on the surface (default: `nothing`, an uncoloured surface) + - `coil_sets`: Coils drawn with the surface (default: `nothing`) + - `mtheta`, `nphi`, `psi`: Surface grid and flux surface; must match `color_by` + - `clim`: Symmetric colour limit (default: the field's largest magnitude) + - `label`: Colour-bar label of the field + - `save_path`: If given, save the figure to this path (default: `nothing`) + +### Returns + +A Makie `Figure`. +""" +function plot_surface_3d(equil; color_by=nothing, coil_sets=nothing, mtheta::Int=180, nphi::Int=180, psi=nothing, clim=nothing, label="", + title="Plasma boundary", save_path=nothing) + isnothing(color_by) || size(color_by) == (mtheta, nphi) || + throw(ArgumentError("color_by must be [mtheta, nphi] = ($mtheta, $nphi), got $(size(color_by))")) + surface = _boundary_surface_xyz(equil, mtheta, nphi, psi) + field = isnothing(color_by) ? nothing : (; values=_wrap_field(color_by), clim, label) + return _draw(_plot_coils_3d, isnothing(coil_sets) ? [] : coil_sets, surface, field; title, save_path) +end + +function plot_surface_3d(equil, modes, n::Int; coil_sets=nothing, mtheta::Int=180, nphi::Int=180, psi=nothing, helicity=nothing, label="", + save_path=nothing) + c = control_surface_scalar(equil, modes, n; mtheta, nphi, psi, helicity) + return plot_surface_3d(equil; color_by=c, coil_sets, mtheta, nphi, psi, label, save_path) end -register!(plot_coil_geometry_3d, (PerturbedEquilibriumState,), (:coil_sets,), "3D coil conductors") -register!(plot_coil_geometry_rz, (PerturbedEquilibriumState,), (:coil_sets,), "coil cross-sections over the plasma boundary") +register!(plot_bn_contour, (PerturbedEquilibriumState,), (:forcing_modes,), "applied b_n on the control surface (coils = true for the coils)") +register!(plot_coil_geometry_3d, (PerturbedEquilibriumState,), (:coil_sets,), "3D coils and plasma boundary") +register!(plot_coil_geometry_rz, (PerturbedEquilibriumState,), (:coil_sets,), "coils over the plasma boundary in (R, Z)") register!(plot_mode_spectrum, (PerturbedEquilibriumState,), (:forcing_modes,), "|Φ_x(m, n)| forcing spectrum") +register!(plot_sources, (FT.RMPField,), (:coil_source,), "the coil sources in 3D, coloured by current or by set") export plot_coil_geometry_3d, plot_coil_geometry_rz -export plot_bn_contour, plot_mode_spectrum +export plot_bn_contour, plot_mode_spectrum, plot_control_surface, plot_sources export plot_surface_3d, control_surface_scalar end # module CoilForcing diff --git a/src/Analysis/Equilibrium.jl b/src/Analysis/Equilibrium.jl index 699158851..59cc9e377 100644 --- a/src/Analysis/Equilibrium.jl +++ b/src/Analysis/Equilibrium.jl @@ -7,19 +7,21 @@ Post-processing and visualization functions for GPEC equilibria. Each plot takes module Equilibrium using HDF5 -using LaTeXStrings -using Plots -using ..Analysis: register!, _require, _can, capabilities +using ..Analysis: register!, _require, _draw import ...Equilibrium: PlasmaEquilibrium -import ...ForceFreeStates: ForceFreeStatesResult +import ...ForceFreeStates: ForceFreeStatesResult, _find_rational_surfaces const Source = Union{AbstractString,PlasmaEquilibrium,ForceFreeStatesResult} -# Rational surfaces (ψ_N, q) to mark: none for a bare equilibrium. -_surfaces(::PlasmaEquilibrium) = (; sing_psi=Float64[], sing_q=Float64[]) -_surfaces(r::ForceFreeStatesResult) = (; sing_psi=[s.psifac for s in r.surfaces], sing_q=[s.q for s in r.surfaces]) -function _surfaces(fid::HDF5.File) +# Rational surfaces (ψ_N, q) to mark: the q = m/n crossings of toroidal mode `n` on a bare equilibrium, and the surfaces +# a stability result or its file solved for. +function _surfaces(eq::PlasmaEquilibrium; n=1) + s = _find_rational_surfaces(eq, n, n) + return (; sing_psi=[x.psifac for x in s], sing_q=[x.m / x.n for x in s]) +end +_surfaces(r::ForceFreeStatesResult; n=1) = (; sing_psi=[s.psifac for s in r.surfaces], sing_q=[s.q for s in r.surfaces]) +function _surfaces(fid::HDF5.File; n=1) haskey(fid, "SingularSurfaces/rational_psi") || return _surfaces(nothing) return (; sing_psi=read(fid["SingularSurfaces/rational_psi"]), sing_q=read(fid["SingularSurfaces/rational_q"])) end @@ -28,26 +30,26 @@ _surfaces(::Nothing) = (; sing_psi=Float64[], sing_q=Float64[]) _equil(eq::PlasmaEquilibrium) = eq _equil(r::ForceFreeStatesResult) = r.equil -function _profile_data(src::Union{PlasmaEquilibrium,ForceFreeStatesResult}) +function _profile_data(src::Union{PlasmaEquilibrium,ForceFreeStatesResult}; n=1) eq = _equil(src) prof = eq.profiles - return (; psi=prof.xs, q=prof.q_spline.y, mu0p=prof.P_spline.y, twopif=prof.F_spline.y, q0=eq.params.q0, q95=eq.params.q95, _surfaces(src)...) + return (; psi=prof.xs, q=prof.q_spline.y, mu0p=prof.P_spline.y, twopif=prof.F_spline.y, q0=eq.params.q0, q95=eq.params.q95, _surfaces(src; n)...) end -function _profile_data(h5path::AbstractString) +function _profile_data(h5path::AbstractString; n=1) h5open(h5path, "r") do fid (; psi=read(fid["Equilibrium/Profiles/psi"]), q=read(fid["Equilibrium/Profiles/q"]), mu0p=read(fid["Equilibrium/Profiles/mu0p"]), twopif=read(fid["Equilibrium/Profiles/2piF"]), q0=read(fid["Equilibrium/q_axis"]), q95=read(fid["Equilibrium/q_95"]), _surfaces(fid)...) end end -function _geometry_data(src::Union{PlasmaEquilibrium,ForceFreeStatesResult}) +function _geometry_data(src::Union{PlasmaEquilibrium,ForceFreeStatesResult}; n=1) eq = _equil(src) return (; rcoords=eq.rzphi_rsquared.nodal_derivs.partials[1, :, :], offset=eq.rzphi_offset.nodal_derivs.partials[1, :, :], - psi=eq.rzphi_xs, theta=eq.rzphi_ys, ro=eq.ro, zo=eq.zo, _surfaces(src)...) + psi=eq.rzphi_xs, theta=eq.rzphi_ys, ro=eq.ro, zo=eq.zo, _surfaces(src; n)...) end -function _geometry_data(h5path::AbstractString) +function _geometry_data(h5path::AbstractString; n=1) h5open(h5path, "r") do fid (; rcoords=read(fid["Equilibrium/Geometry/rcoords"]), offset=read(fid["Equilibrium/Geometry/offset"]), psi=read(fid["Equilibrium/Geometry/psi"]), theta=read(fid["Equilibrium/Geometry/theta"]), ro=read(fid["Equilibrium/R_axis"]), zo=read(fid["Equilibrium/Z_axis"]), _surfaces(fid)...) @@ -67,23 +69,31 @@ function _rz_grid(d) return R, Z end +# Headline scalars; the plasma current is in MA. function _global_data(src::Union{PlasmaEquilibrium,ForceFreeStatesResult}) p = _equil(src).params - return (; q0=p.q0, q95=p.q95, betat=p.betat, betan=p.betan, kappa=p.kappa, li1=p.li1) + return (; q0=p.q0, q95=p.q95, betan=p.betan, kappa=p.kappa, ip=p.crnt, bt=p.bt0) end function _global_data(h5path::AbstractString) h5open(h5path, "r") do fid - (; q0=read(fid["Equilibrium/q_axis"]), q95=read(fid["Equilibrium/q_95"]), betat=read(fid["Equilibrium/beta_t"]), - betan=read(fid["Equilibrium/beta_N"]), kappa=read(fid["Equilibrium/kappa"]), li1=read(fid["Equilibrium/l_i_1"])) + (; q0=read(fid["Equilibrium/q_axis"]), q95=read(fid["Equilibrium/q_95"]), betan=read(fid["Equilibrium/beta_N"]), + kappa=read(fid["Equilibrium/kappa"]), ip=read(fid["Equilibrium/I_p"]), bt=read(fid["Equilibrium/B_T_axis"])) end end +# Drawing functions, defined by the GPECMakieExt extension. +function _plot_qprofile end +function _plot_pressure_profile end +function _plot_f_profile end +function _plot_flux_surfaces end +function _plot_equilibrium_summary end +function _plot_gse end + """ - plot_qprofile(src; show_singular=true, save_path=nothing) + plot_qprofile(src; show_singular=true, n=1, save_path=nothing) -Plot the safety factor q(ψ) profile, with optional vertical markers at each rational surface -and horizontal reference lines at q0 and q95. +Plot the safety factor q against ψ_N, with q₀ and q₉₅ marked and the rational surfaces labelled along the top. ### Arguments @@ -91,51 +101,24 @@ and horizontal reference lines at q0 and q95. ### Keyword arguments - - `show_singular`: If `true`, overlay rational surface locations (default: `true`) - - `save_path`: If provided, save the figure to this path (default: `nothing`) + - `show_singular`: Mark the rational surfaces (default: `true`) + - `n`: Toroidal mode number whose rational surfaces q = m/n are marked on a bare equilibrium (default: `1`); a stability + result or `gpec.h5` marks the surfaces it solved for + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. """ -function plot_qprofile(src::Source; show_singular=true, save_path=nothing) +function plot_qprofile(src::Source; show_singular=true, n=1, save_path=nothing) _require(plot_qprofile, src) - p = _plot_qprofile(_profile_data(src); show_singular) - isnothing(save_path) || savefig(p, save_path) - return p -end - -function _plot_qprofile(d; show_singular=true) - xs, q, q0, q95 = d.psi, d.q, d.q0, d.q95 - p = plot( - xs, q; - xlabel="Norm. Poloidal Flux", - ylabel="q", - title="", - legend=false, - xlims=(0, 1), - left_margin=10Plots.mm, - bottom_margin=5Plots.mm, - right_margin=8Plots.mm # annotations at x=1.0 need room on the right - ) - hline!(p, [q0, q95]; linestyle=:dot, color=:gray, label=nothing) - annotate!(p, 1.0, q0, text(" q0=$(round(q0, digits=2))", 7, :left, :gray)) - annotate!(p, 1.0, q95, text(" q95=$(round(q95, digits=2))", 7, :left, :gray)) - - if show_singular - for (psi_s, q_s) in zip(d.sing_psi, d.sing_q) - vline!(p, [psi_s]; linestyle=:dash, color=:red, label=nothing) - annotate!(p, psi_s, q_s, - text(" q=$(round(q_s, digits=2))", 7, :left, :red)) - end - end - return p + return _draw(_plot_qprofile, _profile_data(src; n); show_singular, save_path) end """ - plot_pressure_profile(src; save_path=nothing) + plot_pressure_profile(src; n=1, save_path=nothing) -Plot the μ₀p(ψ) pressure profile. Vertical dashed lines mark rational surfaces if present. +Plot μ₀p against ψ_N, with the rational surfaces labelled along the top. ### Arguments @@ -143,40 +126,23 @@ Plot the μ₀p(ψ) pressure profile. Vertical dashed lines mark rational surfac ### Keyword arguments - - `save_path`: If provided, save the figure to this path (default: `nothing`) + - `n`: Toroidal mode number whose rational surfaces q = m/n are marked on a bare equilibrium (default: `1`); a stability + result or `gpec.h5` marks the surfaces it solved for + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. """ -function plot_pressure_profile(src::Source; save_path=nothing) +function plot_pressure_profile(src::Source; n=1, save_path=nothing) _require(plot_pressure_profile, src) - p = _plot_pressure_profile(_profile_data(src)) - isnothing(save_path) || savefig(p, save_path) - return p -end - -function _plot_pressure_profile(d) - p = plot( - d.psi, d.mu0p; - xlabel="Norm. Poloidal Flux", - ylabel="μ₀p", - title="", - legend=false, - xlims=(0, 1), - left_margin=10Plots.mm, - bottom_margin=5Plots.mm - ) - for psi_s in d.sing_psi - vline!(p, [psi_s]; linestyle=:dash, color=:red, label=nothing) - end - return p + return _draw(_plot_pressure_profile, _profile_data(src; n); save_path) end """ - plot_f_profile(src; save_path=nothing) + plot_f_profile(src; n=1, save_path=nothing) -Plot the toroidal field function 2πF(ψ) profile (F = RBφ/(2π)). +Plot 2πF = RB_φ against ψ_N, with the rational surfaces labelled along the top. ### Arguments @@ -184,44 +150,23 @@ Plot the toroidal field function 2πF(ψ) profile (F = RBφ/(2π)). ### Keyword arguments - - `save_path`: If provided, save the figure to this path (default: `nothing`) + - `n`: Toroidal mode number whose rational surfaces q = m/n are marked on a bare equilibrium (default: `1`); a stability + result or `gpec.h5` marks the surfaces it solved for + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. """ -function plot_f_profile(src::Source; save_path=nothing) +function plot_f_profile(src::Source; n=1, save_path=nothing) _require(plot_f_profile, src) - p = _plot_f_profile(_profile_data(src)) - isnothing(save_path) || savefig(p, save_path) - return p -end - -function _plot_f_profile(d) - p = plot( - d.psi, d.twopif; - xlabel="Norm. Poloidal Flux", - ylabel="2πF", - title="", - legend=false, - xlims=(0, 1), - left_margin=10Plots.mm, - bottom_margin=5Plots.mm - ) - for psi_s in d.sing_psi - vline!(p, [psi_s]; linestyle=:dash, color=:red, label=nothing) - end - return p + return _draw(_plot_f_profile, _profile_data(src; n); save_path) end """ - plot_flux_surfaces(src; n_psi=11, n_theta=18, save_path=nothing) - -Plot flux surface contours (constant ψ, blue) and field-line angle spokes (constant θ, red) -in physical (R, Z) space, from the nodal grid data. + plot_flux_surfaces(src; n_psi=11, n_theta=18, n=1, save_path=nothing) -Psi contours are drawn at `n_psi` evenly spaced values between psilow and psihigh. -Theta spokes are drawn at `n_theta` evenly spaced values. +Plot flux surfaces and constant-θ spokes in (R, Z) at equal aspect, with the rational surfaces in colour. ### Arguments @@ -229,235 +174,70 @@ Theta spokes are drawn at `n_theta` evenly spaced values. ### Keyword arguments - - `n_psi`: Number of constant-ψ contours to draw (default: 11) - - `n_theta`: Number of constant-θ/θ spokes to draw (default: 18) - - `save_path`: If provided, save the figure to this path (default: `nothing`) + - `n_psi`: Number of flux surfaces drawn (default: 11) + - `n_theta`: Number of constant-θ spokes drawn (default: 18) + - `n`: Toroidal mode number whose rational surfaces q = m/n are marked on a bare equilibrium (default: `1`); a stability + result or `gpec.h5` marks the surfaces it solved for + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. """ -function plot_flux_surfaces(src::Source; n_psi=11, n_theta=18, save_path=nothing) +function plot_flux_surfaces(src::Source; n_psi=11, n_theta=18, n=1, save_path=nothing) _require(plot_flux_surfaces, src) - p = _plot_flux_surfaces(_geometry_data(src); n_psi, n_theta) - isnothing(save_path) || savefig(p, save_path) - return p -end - -function _plot_flux_surfaces(d; n_psi=11, n_theta=18) - xs_rz, ys_rz, psi_sing, q_sing = d.psi, d.theta, d.sing_psi, d.sing_q - - n_psi_grid = length(xs_rz) - n_theta_grid = length(ys_rz) - R_grid, Z_grid = _rz_grid(d) - - p = plot(; - title="Flux surfaces in (R, Z)", - xlabel="R [m]", - ylabel="Z [m]", - aspect_ratio=:equal, - legend=:outertopright, - left_margin=10Plots.mm, - bottom_margin=5Plots.mm - ) - - psi_indices = round.(Int, range(1, n_psi_grid; length=n_psi)) - theta_indices = round.(Int, range(1, n_theta_grid; length=n_theta)) - - for (i, ipsi) in enumerate(psi_indices) - label = i == 1 ? "Constant ψ" : "" - plot!(p, [R_grid[ipsi, :]; R_grid[ipsi, 1]], [Z_grid[ipsi, :]; Z_grid[ipsi, 1]]; - color=:steelblue, linewidth=1.5, label=label) - end - for (i, itheta) in enumerate(theta_indices) - label = i == 1 ? "Constant θ" : "" - plot!(p, R_grid[:, itheta], Z_grid[:, itheta]; - color=:tomato, linewidth=0.8, label=label) - end - # Draw rational surface flux contours in red, annotated at the bottom (min Z) of each - for s in eachindex(psi_sing) - idx = argmin(abs.(xs_rz .- psi_sing[s])) - q_label = abs(q_sing[s] - round(q_sing[s])) < 0.05 ? - "q=$(round(Int, q_sing[s]))" : "q=$(round(q_sing[s], digits=2))" - plot!(p, [R_grid[idx, :]; R_grid[idx, 1]], [Z_grid[idx, :]; Z_grid[idx, 1]]; - color=:red, linewidth=1.5, - label=s == 1 ? "Rational surface" : "") - itheta_bot = argmin(Z_grid[idx, :]) - annotate!(p, R_grid[idx, itheta_bot], Z_grid[idx, itheta_bot], - text(" $q_label", 7, :left, :red)) - end - return p -end - -""" - plot_gse_by_theta(h5path; n_theta_lines=8, save_path=nothing) - -Plot Grad-Shafranov error vs ψ_N (log scale) for several θ slices, with the -flux-surface-integrated error overplotted as a thick black line. Reads `gse.h5` -(and optionally `gsei.h5`) from the same directory as `h5path`, written when the equilibrium -configuration sets `diagnose_src = true`. - -### Arguments - - - `h5path`: Path to a GPEC HDF5 output file (used to locate `gse.h5`) - -### Keyword arguments - - - `n_theta_lines`: Number of evenly-spaced θ slices to overlay (default: 8) - - `save_path`: If provided, save the figure to this path (default: `nothing`) - -### Returns - -A `Plots.jl` plot object. -""" -function plot_gse_by_theta(h5path::AbstractString; n_theta_lines=8, save_path=nothing) - _require(plot_gse_by_theta, h5path) - gse_path = joinpath(dirname(h5path), "gse.h5") - - gse_data = h5open(gse_path, "r") do fid - read(fid["gse_data"]) # (npsi, ntheta, 7): cols = θ, ψ, flux1, flux2, source, total, error - end - - _, ntheta, _ = size(gse_data) - theta_indices = round.(Int, range(1, ntheta; length=n_theta_lines)) - - p = plot(; - xlabel="Norm. Poloidal Flux", - ylabel="GSE error", - title="", - yscale=:log10, - xlims=(0, 1), - left_margin=10Plots.mm, - bottom_margin=5Plots.mm - ) - for (i, itheta) in enumerate(theta_indices) - psi_col = gse_data[:, itheta, 2] - err_col = gse_data[:, itheta, 7] - plot!(p, psi_col, err_col; label=i == 1 ? "Const. θ" : "", alpha=0.6) - end - - # Overplot flux-surface-integrated error as thick reference line - gsei_path = joinpath(dirname(h5path), "gsei.h5") - if isfile(gsei_path) - xs_int, errori = h5open(gsei_path, "r") do fid - read(fid["xs"]), read(fid["errori"]) - end - plot!(p, xs_int, vec(errori); - color=:black, linewidth=2.5, linestyle=:solid, label="integrated") - end - - isnothing(save_path) || savefig(p, save_path) - return p + return _draw(_plot_flux_surfaces, _geometry_data(src; n); n_psi, n_theta, save_path) end """ - plot_gse_integrated(h5path; save_path=nothing) + plot_equilibrium_summary(src; n=1, save_path=nothing) -Plot the flux-surface-integrated Grad-Shafranov error (log scale) vs ψ_N, reading from -`gsei.h5` in the same directory as `h5path`, written when the equilibrium configuration sets -`diagnose_src = true`. +Plot the equilibrium's headline scalars, its flux surfaces in (R, Z), and q, μ₀p and 2πF against a shared ψ_N axis. ### Arguments - - `h5path`: Path to a GPEC HDF5 output file (used to locate `gsei.h5`) + - `src`: A `PlasmaEquilibrium`, a `ForceFreeStatesResult`, or the path of a `gpec.h5` ### Keyword arguments - - `save_path`: If provided, save the figure to this path (default: `nothing`) + - `n`: Toroidal mode number whose rational surfaces q = m/n are marked on a bare equilibrium (default: `1`); a stability + result or `gpec.h5` marks the surfaces it solved for + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. """ -function plot_gse_integrated(h5path::AbstractString; save_path=nothing) - _require(plot_gse_integrated, h5path) - gsei_path = joinpath(dirname(h5path), "gsei.h5") - - xs, errlogi = h5open(gsei_path, "r") do fid - read(fid["xs"]), read(fid["errlogi"]) - end - - p = plot( - xs, vec(errlogi); - xlabel="Norm. Poloidal Flux", - ylabel="log₁₀(integrated GSE)", - title="Flux-surface-integrated Grad-Shafranov error", - legend=false, - left_margin=10Plots.mm, - bottom_margin=5Plots.mm - ) - - isnothing(save_path) || savefig(p, save_path) - return p +function plot_equilibrium_summary(src::Source; n=1, save_path=nothing) + _require(plot_equilibrium_summary, src) + return _draw(_plot_equilibrium_summary, _global_data(src), _profile_data(src; n), _geometry_data(src; n); save_path) end """ - plot_equilibrium_summary(src; save_path=nothing) - -Summary of equilibrium profiles and geometry. The (R, Z) flux surface plot occupies the -full left column. Profile plots are stacked in the right column: + plot_gse(gse; n_theta_lines=8, save_path=nothing) - - q(ψ) safety factor with rational surface markers (`plot_qprofile`) - - μ₀p(ψ) pressure profile (`plot_pressure_profile`) - - 2πF(ψ) toroidal field function (`plot_f_profile`) - -When the source is a `gpec.h5` path with `gse.h5` beside it (requires `diagnose_src = true`), a combined -Grad-Shafranov error panel (θ slices + integrated, log scale) is appended at the bottom of the right column. +Plot the Grad-Shafranov residual against ψ_N on several θ slices, with the θ-integrated residual as a thick line. +Not registered: the equilibrium does not store its residual yet. ### Arguments - - `src`: A `PlasmaEquilibrium`, a `ForceFreeStatesResult`, or the path of a `gpec.h5` + - `gse`: The named tuple `Equilibrium.equilibrium_gse!` returns ### Keyword arguments - - `save_path`: If provided, save the figure to this path (default: `nothing`) + - `n_theta_lines`: Number of θ slices drawn (default: 8) + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. """ -function plot_equilibrium_summary(src::Source; save_path=nothing) - _require(plot_equilibrium_summary, src) - (; q0, q95, betat, betan, kappa, li1) = _global_data(src) - - title_str = "q0=$(round(q0,digits=2)) q95=$(round(q95,digits=2)) βₜ=$(round(betat,digits=3)) βₙ=$(round(betan,digits=3)) κ=$(round(kappa,digits=2)) li1=$(round(li1,digits=3))" - - profiles = _profile_data(src) - p_rz = _plot_flux_surfaces(_geometry_data(src)) - p_q = _plot_qprofile(profiles; show_singular=true) - p_pres = _plot_pressure_profile(profiles) - p_f = _plot_f_profile(profiles) - p_gse = src isa AbstractString && _can(plot_gse_by_theta, capabilities(src)) ? plot_gse_by_theta(src) : nothing # includes integrated overplot - - # Suppress x-axis labels/ticks on all but the bottom profile panel — they share the - # same ψ_N axis and labeling every panel wastes vertical space. - hide_xaxis!(p) = plot!(p; xlabel="", xformatter=_ -> "", bottom_margin=1Plots.mm) - - if isnothing(p_gse) - hide_xaxis!(p_q) - hide_xaxis!(p_pres) - l = @layout [a{0.38w} [b; c; d]] - p = plot(p_rz, p_q, p_pres, p_f; layout=l, size=(1300, 750), - plot_title=title_str, plot_titlefontsize=10, top_margin=8Plots.mm) - else - hide_xaxis!(p_q) - hide_xaxis!(p_pres) - hide_xaxis!(p_f) - l = @layout [a{0.38w} [b; c; d; e]] - p = plot(p_rz, p_q, p_pres, p_f, p_gse; layout=l, size=(1300, 1000), - plot_title=title_str, plot_titlefontsize=10, top_margin=8Plots.mm) - end - - isnothing(save_path) || savefig(p, save_path) - return p -end +plot_gse(gse::NamedTuple; n_theta_lines=8, save_path=nothing) = _draw(_plot_gse, gse; n_theta_lines, save_path) register!(plot_equilibrium_summary, (PlasmaEquilibrium, ForceFreeStatesResult, AbstractString), (:equilibrium,), "flux surfaces and profiles") register!(plot_qprofile, (PlasmaEquilibrium, ForceFreeStatesResult, AbstractString), (:equilibrium,), "q against ψ_N with the rational surfaces") register!(plot_pressure_profile, (PlasmaEquilibrium, ForceFreeStatesResult, AbstractString), (:equilibrium,), "μ₀p against ψ_N") register!(plot_f_profile, (PlasmaEquilibrium, ForceFreeStatesResult, AbstractString), (:equilibrium,), "2πF against ψ_N") register!(plot_flux_surfaces, (PlasmaEquilibrium, ForceFreeStatesResult, AbstractString), (:equilibrium,), "flux surfaces and θ spokes in (R, Z)") -register!(plot_gse_by_theta, (AbstractString,), (:gse_files,), "Grad-Shafranov error against ψ_N per θ slice") -register!(plot_gse_integrated, (AbstractString,), (:gse_files,), "flux-surface-integrated Grad-Shafranov error") end # module Equilibrium diff --git a/src/Analysis/ErrorFields.jl b/src/Analysis/ErrorFields.jl index 635cc4745..04bf45291 100644 --- a/src/Analysis/ErrorFields.jl +++ b/src/Analysis/ErrorFields.jl @@ -10,14 +10,14 @@ overplotted, with a single-path convenience method for the common case. module ErrorFields using HDF5 -using Plots using LinearAlgebra +using Random using TOML import ...ErrorFields as EF import ...PerturbedEquilibrium as PE import ...ForcingTerms as FT -using ..Analysis: register!, _require, _can, _missing, _entry, _why, capabilities +using ..Analysis: register!, _require, _can, _missing, _entry, _why, capabilities, _draw """ A label paired with something to plot: a `gpec.h5` path, or a result already in memory @@ -30,11 +30,12 @@ const Sources = AbstractVector{<:Pair{String,<:Any}} """ A single unlabelled source: a `gpec.h5` path, or one in-memory result. """ -const SingleSource = Union{AbstractString,EF.ErrorFieldsResult,EF.SensitivityTable,EF.CoilSensitivities,EF.MonteCarloResult,EF.RiskResult,AbstractVector{EF.CoilOverlap}} +const SingleSource = Union{AbstractString,EF.ErrorFieldsResult,EF.SensitivityTable,EF.CoilSensitivities,EF.MonteCarloResult,EF.RiskResult, + AbstractVector{EF.CoilOverlap},PE.PlasmaResponse,PE.PerturbedEquilibriumState} _sources(source) = [_default_label(source) => source] -_default_label(h5path::AbstractString) = basename(dirname(abspath(h5path))) -_default_label(_) = "in memory" +# A single source is drawn without a label; several are told apart by theirs. +_default_label(_) = "" # Every field the plots read. One template so the HDF5 and in-memory loaders cannot drift apart. const _BLANK = (; coil_names=nothing, delta_as_designed=nothing, abs_delta_shift_per_mm=nothing, @@ -79,11 +80,17 @@ rather than separately, which would leave each half unable to find the other. _load(pair::Tuple{EF.MonteCarloResult,EF.RiskResult}) = _load(merge(_present(_load(pair[1])), _present(_load(pair[2])))) +# The (m, n) of each control-surface harmonic, as the writer records them in Info/mn_index. +_mn_index(ffs) = hcat([(i - 1) % ffs.mpert + ffs.mlow for i in 1:ffs.numpert_total], [(i - 1) ÷ ffs.mpert + ffs.nlow for i in 1:ffs.numpert_total]) + +# The dominant resonant-coupling modes of a plasma response, or of the response a perturbed equilibrium was driven on. +_dominant(resp::PE.PlasmaResponse) = (; mn_index=_mn_index(resp.ffs), dominant_v=isempty(resp.dominant_singular_values) ? nothing : resp.dominant_right_singular_vectors, + singular_values=isempty(resp.dominant_singular_values) ? nothing : resp.dominant_singular_values) +_load(resp::PE.PlasmaResponse) = _load(_present(_dominant(resp))) +_load(s::PE.PerturbedEquilibriumState) = _load(s.response) + # The whole assessment: the dominant-mode table and mode labels as the writer records them, from the plasma response it was made on. function _load(r::EF.ErrorFieldsResult) - resp = r.response - ffs = resp.ffs - mn_index = hcat([(i - 1) % ffs.mpert + ffs.mlow for i in 1:ffs.numpert_total], [(i - 1) ÷ ffs.mpert + ffs.nlow for i in 1:ffs.numpert_total]) # The dominant-mode table's shift and tilt sensitivities replace the full per-mode arrays, as in the file. parts = Any[_present(_load(r.sensitivities)), _present(_load(EF.sensitivity_table(r.sensitivities, r.dominant; mode=1)))] r.monte_carlo === nothing || push!(parts, _present(_load(r.monte_carlo))) @@ -96,9 +103,7 @@ function _load(r::EF.ErrorFieldsResult) ctrl = r.settings.ctrl push!( parts, - _present((; tolerances=r.tolerances, fd_step_shift_m=ctrl.fd_step_shift_m, fd_step_tilt_deg=ctrl.fd_step_tilt_deg, mn_index, - dominant_v=isempty(resp.dominant_singular_values) ? nothing : resp.dominant_right_singular_vectors, - singular_values=isempty(resp.dominant_singular_values) ? nothing : resp.dominant_singular_values)) + _present((; tolerances=r.tolerances, fd_step_shift_m=ctrl.fd_step_shift_m, fd_step_tilt_deg=ctrl.fd_step_tilt_deg, _dominant(r.response)...)) ) return _load(merge(parts...)) end @@ -165,98 +170,6 @@ function _load_h5(h5path::AbstractString) end end -_centers(edges) = (edges[1:end-1] .+ edges[2:end]) ./ 2 -_step_series(m, a) = (vcat(m[1] - 1, m, m[end] + 1), vcat(0.0, a, 0.0)) -_empty(msg) = plot(; title=msg, legend=false) - -# A registered source (a path or the assessment) must hold the data a single-source plot draws; other in-memory sources are what they are. -_check(fn, src::Union{AbstractString,EF.ErrorFieldsResult}) = _require(fn, src) -_check(fn, src) = nothing - -function _save(p, save_path) - if save_path !== nothing - Plots.savefig(p, save_path) - println("Saved: ", abspath(save_path)) - end - return p -end - -""" - plot_coil_sensitivities(sources; quantity=:shift, coils=nothing, yscale=:identity, save_path=nothing) - plot_coil_sensitivities(source; kwargs...) - -Grouped bars of the per-coil-set dominant-mode sensitivity across runs, matched by coil set name. -`quantity` is `:shift` (per millimetre of rigid in-plane shift), `:tilt` (per degree), `:rim` (the -same tilt as rim displacement, the unit mechanical tolerances arrive in), `:as_designed` (`|δ|` as -built), or `:fraction` (the resonant share of the coil's own applied spectrum, `100·|δ|·B_T0/‖b̃‖` -in percent, which separates a coil that couples weakly because it is small from one that couples -weakly because its spectrum lies off the dominant mode). `coils` restricts and orders the coil -sets shown; a name a source does not carry is an error rather than an empty bar. -`yscale = :log10` draws the values as stems with markers instead of bars, since a bar's height on -a logarithmic axis is set by the axis floor rather than by the data; non-positive values are -omitted. - -Each source is a `gpec.h5` path, a `SensitivityTable` already in memory, or a vector of -`CoilOverlap` (`:as_designed` and `:fraction` only), so a sweep of coil geometry that was never part of -a run plots the same way a stored run does. `:fraction` needs the applied field as well as the -overlap, so it is not available from a `SensitivityTable`. -""" -function plot_coil_sensitivities(sources::Sources; quantity::Symbol=:shift, coils=nothing, yscale::Symbol=:identity, save_path=nothing) - quantity in (:shift, :tilt, :rim, :as_designed, :fraction) || throw(ArgumentError("quantity must be :shift, :tilt, :rim, :as_designed, or :fraction")) - yscale in (:identity, :log10) || throw(ArgumentError("yscale must be :identity or :log10")) - data = [(lbl, _load(src)) for (lbl, src) in sources] - any(d -> d[2].coil_names === nothing, data) && return _empty("No ErrorFields/CoilSensitivities data — run with an [ErrorFields] section") - names = coils === nothing ? data[1][2].coil_names : String.(collect(coils)) - values = [_sensitivity_values(lbl, d, names, quantity) for (lbl, d) in data] - ylabel = - quantity === :shift ? "|δ| per mm of shift" : - quantity === :tilt ? "|δ| per degree of tilt" : - quantity === :rim ? "|δ| per mm of rim displacement" : quantity === :as_designed ? "|δ_as_designed|" : "resonant fraction of |b̃| [%]" - per = quantity === :shift ? "shift" : quantity === :tilt ? "tilt" : "rim displacement" - title = - quantity === :as_designed ? "As-designed dominant-mode overlap" : - quantity === :fraction ? "Resonant fraction of each coil set's applied field" : - "Dominant-mode error field per $per" - n = length(names) - k = length(data) - width = 0.8 / k - p = plot(; xlabel="coil set", ylabel=ylabel, title=title, xticks=(1:n, names), xrotation=45, legend=:topright, - left_margin=12Plots.mm, bottom_margin=8Plots.mm) - if yscale === :log10 - ylo = _log_floor!(p, reduce(vcat, values)) - ylo === nothing && return _empty("No positive $quantity values to draw on a logarithmic axis") - for (j, (lbl, _)) in enumerate(data) - _stems!(p, (1:n) .+ (j - (k + 1) / 2) * width, values[j], ylo; c=j, label=lbl) - end - else - for (j, (lbl, _)) in enumerate(data) - x = (1:n) .+ (j - (k + 1) / 2) * width - bar!(p, x, values[j]; bar_width=width, label=lbl, alpha=0.8) - end - end - return _save(p, save_path) -end - -# A logarithmic axis for stems: the floor a decade under the smallest positive value, or nothing -# when there is none to draw. Bars on a log axis stretch to whatever the floor is; stems do not. -function _log_floor!(p, values) - positive = filter(v -> isfinite(v) && v > 0, values) - isempty(positive) && return nothing - ylo = minimum(positive) / 10 - plot!(p; yscale=:log10, ylims=(ylo, 3 * maximum(positive))) - return ylo -end - -# Stems from the floor to each positive value, with a marker on top; non-positive values are skipped. -function _stems!(p, x, values, ylo; c, label, marker=:circle, ms=5, lw=3) - keep = [isfinite(v) && v > 0 for v in values] - xs = vec(vcat(x[keep]', x[keep]', fill(NaN, 1, count(keep)))) - ys = vec(vcat(fill(ylo, 1, count(keep)), values[keep]', fill(NaN, 1, count(keep)))) - plot!(p, xs, ys; lw, c, label="") - scatter!(p, x[keep], values[keep]; marker, ms, c, label) - return p -end - # Positions of `names` among a source's coil sets, erroring on a coil the source does not carry # rather than drawing an empty bar for it. function _coil_indices(lbl, d, names) @@ -285,44 +198,136 @@ function _sensitivity_values(lbl, d, names, quantity::Symbol) vals === nothing && throw(ArgumentError("source \"$lbl\" carries no $field, so quantity=:$quantity cannot be drawn from it")) return abs.(vals[idx]) end + +# The six taps' residual ratios per coil set (6 × n), at the finite-difference step or rescaled +# by tolerance / step to the coil's own tolerance. +function _linearity_matrix(lbl, d, names, at::Symbol; tolerances=nothing, fd_step_shift_m=nothing, fd_step_tilt_deg=nothing) + idx = _coil_indices(lbl, d, names) + r = vcat(d.shift_linearity_residual[:, idx], d.tilt_linearity_residual[:, idx]) + at === :step && return r + ts = tolerances === nothing ? d.tolerances : tolerances + ts === nothing && throw(ArgumentError("source \"$lbl\" carries no tolerance snapshot; pass tolerances=ToleranceSet or use at=:step")) + h_shift = something(fd_step_shift_m, d.fd_step_shift_m, EF.ErrorFieldsControl().fd_step_shift_m) + h_tilt = something(fd_step_tilt_deg, d.fd_step_tilt_deg, EF.ErrorFieldsControl().fd_step_tilt_deg) + tol_of = Dict(t.name => t for t in ts.coils) + for (k, i) in enumerate(idx) + t = get(tol_of, d.coil_names[i], nothing) + shift_tol = t === nothing ? 0.0 : t.shift_tol_m + tilt_tol = + t === nothing ? 0.0 : + d.major_radius_m === nothing ? throw(ArgumentError("source \"$lbl\" carries no major radii, needed to convert a tilt tolerance in metres")) : + EF.tilt_tolerance_deg(t.tilt_tol, t.tilt_units, d.major_radius_m[i]; name="coil set $(d.coil_names[i])") + r[1:3, k] .*= shift_tol / h_shift + r[4:6, k] .*= tilt_tol / h_tilt + end + return r +end + +_centers(edges) = (edges[1:end-1] .+ edges[2:end]) ./ 2 + +# A registered source (a path or the assessment) must hold the data a single-source plot draws; other in-memory sources are what they are. +_check(fn, src::Union{AbstractString,EF.ErrorFieldsResult,PE.PlasmaResponse,PE.PerturbedEquilibriumState}) = _require(fn, src) +_check(fn, src) = nothing + +# Below this fraction of the largest value, a per-coil value is roundoff (an axisymmetric coil has no n ≠ 0 field). +const _ROUNDOFF = 1e-10 + +# The NTV correction arrays, which are swept but are not error-field sources. +_efc_names(r::EF.ErrorFieldsResult) = r.couplings === nothing ? String[] : [c.coil_name for c in r.couplings] +_efc_names(h5path::AbstractString) = h5open(f -> haskey(f, EF._NTV_GROUP) ? read(f["$(EF._NTV_GROUP)/coil_name"]) : String[], h5path) +_efc_names(_) = String[] + +# The penetration-threshold samples the locking risk used, rebuilt from the recorded scaling, seed and scenario and +# sorted, so the threshold's CDF can be drawn over any |δ| range; `nothing` when the source does not record them. +function _threshold_samples(r::EF.ErrorFieldsResult) + (r.risk === nothing || r.settings.scenario === nothing) && return nothing + rc = r.settings.risk + ffs = r.response.ffs + n = EF.single_toroidal_mode(ffs.nlow, ffs.nhigh; where="the threshold of this assessment") + sc = EF.threshold_scaling(; n, year=rc.year, dataset=rc.dataset, fit=rc.fit) + return sort(EF.threshold_samples(Random.Xoshiro(rc.seed), sc, r.settings.scenario; nsample=rc.nsample_threshold, dist=rc.distribution)) +end + +function _threshold_samples(h5path::AbstractString) + deck = h5open(f -> haskey(f, "Input/gpec_toml_raw") ? TOML.parse(read(f["Input/gpec_toml_raw"])) : nothing, h5path) + deck === nothing && return nothing + ef = get(deck, "ErrorFields", Dict{String,Any}()) + haskey(ef, "scenario") || return nothing + rk = get(ef, "Risk", Dict{String,Any}()) + rc = EF.RiskControl() + sc = EF.threshold_scaling(; n=EF.scaling_toroidal_mode(h5path), year=get(rk, "year", rc.year), dataset=get(rk, "dataset", rc.dataset), + fit=get(rk, "fit", rc.fit)) + scen = EF.ScenarioParameters(h5path; (Symbol(k) => v for (k, v) in ef["scenario"])...) + return sort( + EF.threshold_samples(Random.Xoshiro(get(rk, "seed", rc.seed)), sc, scen; nsample=get(rk, "nsample_threshold", rc.nsample_threshold), + dist=get(rk, "distribution", rc.distribution)) + ) +end + +_threshold_samples(_) = nothing + +# Drawing functions, defined by the GPECMakieExt extension. +function _plot_coil_sensitivities end +function _plot_tolerance_pdf end +function _plot_overlap_phasors end +function _plot_tolerance_budget end +function _plot_linearity_residuals end +function _plot_linearity_check end +function _plot_locking_risk end +function _plot_threshold_scaling end +function _plot_dominant_mode_spectrum end +function _plot_applied_spectra end +function _plot_overlap_contributions end +function _plot_surface_overlay end +function _plot_phasing_map end +function _plot_efc_ntv_limits end +function _plot_correction_requirement end +function _plot_needed_current end +function _plot_error_field_summary end + +# Each loaded source with its label; a single source carries none. +_labelled(sources) = [(lbl, _load(src)) for (lbl, src) in sources] + +""" + plot_coil_sensitivities(sources; quantity=:shift, coils=nothing, yscale=:identity, save_path=nothing) + plot_coil_sensitivities(source; kwargs...) + +Plot each coil set's dominant-mode sensitivity as a stem and dot per row, across runs matched by coil set name. +`quantity` is `:shift` (per millimetre of rigid in-plane shift), `:tilt` (per degree), `:rim` (the same tilt as rim +displacement, the unit mechanical tolerances arrive in), `:as_designed` (`|δ|` as built), or `:fraction` (the resonant +share of the coil's own applied spectrum, `100·|δ|·B_T0/‖b̃‖` in percent). `coils` restricts and orders the coil sets +shown; a name a source does not carry is an error. `yscale = :log10` puts the values on a log axis. Coil sets whose +value is roundoff (an axisymmetric coil has no n ≠ 0 field) are left out and counted in the title. + +Each source is a `gpec.h5` path, an `ErrorFieldsResult`, a `SensitivityTable`, or a vector of `CoilOverlap` +(`:as_designed` and `:fraction` only). `:fraction` needs the applied field as well as the overlap, so it is not +available from a `SensitivityTable`. +""" +function plot_coil_sensitivities(sources::Sources; quantity::Symbol=:shift, coils=nothing, yscale::Symbol=:identity, save_path=nothing) + quantity in (:shift, :tilt, :rim, :as_designed, :fraction) || throw(ArgumentError("quantity must be :shift, :tilt, :rim, :as_designed, or :fraction")) + yscale in (:identity, :log10) || throw(ArgumentError("yscale must be :identity or :log10")) + data = _labelled(sources) + any(d -> d[2].coil_names === nothing, data) && throw(ArgumentError("plot_coil_sensitivities: a source has no coil sensitivities; run with an [ErrorFields] section")) + names = coils === nothing ? data[1][2].coil_names : String.(collect(coils)) + values = [_sensitivity_values(lbl, d, names, quantity) for (lbl, d) in data] + return _draw(_plot_coil_sensitivities, first.(data), names, values, quantity, yscale; save_path) +end plot_coil_sensitivities(source::SingleSource; kwargs...) = (_check(plot_coil_sensitivities, source); plot_coil_sensitivities(_sources(source); kwargs...)) """ plot_tolerance_pdf(sources; corrected=true, normalize=false, xscale=:identity, show_batches=false, save_path=nothing) - plot_tolerance_pdf(h5path; kwargs...) + plot_tolerance_pdf(source; kwargs...) -The Monte Carlo distributions of the dominant-mode overlap `|δ|` of each run, intrinsic and -(when `corrected`) with error-field correction, with the as-designed overlap marked. -`show_batches` draws each batch's intrinsic histogram faintly under the mean, which is the -sampling noise a risk figure inherits, and notes in the legend the fraction of samples that fell -beyond the last bin edge. +Plot the Monte Carlo distributions of the dominant-mode overlap `|δ|` of each run, intrinsic and (when `corrected`) with +error-field correction, with the as-designed overlap marked unless it is roundoff. `show_batches` draws each batch's intrinsic histogram +faintly under the mean, the sampling noise a risk figure inherits. """ function plot_tolerance_pdf(sources::Sources; corrected::Bool=true, normalize::Bool=false, xscale::Symbol=:identity, show_batches::Bool=false, save_path=nothing) - p = plot(; xlabel="dominant-mode overlap |δ|", ylabel=normalize ? "probability density (normalized)" : "probability density", - title="Tolerance Monte Carlo: |δ| over sampled misalignments", legend=:topright, xscale=xscale, - left_margin=12Plots.mm, bottom_margin=6Plots.mm) - any_data = false - for (j, (lbl, path)) in enumerate(sources) - d = _load(path) - d.abs_delta_pdf === nothing && continue - any_data = true - c = _centers(d.abs_delta_bin_edges) - keep = xscale === :log10 ? c .> 0 : trues(length(c)) - scale = normalize ? maximum(d.abs_delta_pdf) : 1.0 - clamped = "" - if show_batches && d.abs_delta_pdf_batches !== nothing - for b in axes(d.abs_delta_pdf_batches, 2) - plot!(p, c[keep], d.abs_delta_pdf_batches[keep, b] ./ scale; lw=1, c=j, alpha=0.35, label=b == 1 ? "$lbl batches ($(size(d.abs_delta_pdf_batches, 2)))" : "") - end - d.clamped_fraction === nothing || (clamped = " ($(round(100 * d.clamped_fraction; sigdigits=2)) % beyond last bin)") - end - plot!(p, c[keep], d.abs_delta_pdf[keep] ./ scale; lw=2, c=j, label="$lbl intrinsic$clamped") - corrected && plot!(p, c[keep], d.abs_delta_efc_pdf[keep] ./ (normalize ? maximum(d.abs_delta_efc_pdf) : 1.0); lw=2, ls=:dash, c=j, label="$lbl corrected") - vline!(p, [d.abs_delta_total_as_designed]; ls=:dot, c=j, label="$lbl as designed") - end - any_data || return _empty("No ErrorFields/MonteCarlo data — run with a tolerance_file") - return _save(p, save_path) + xscale in (:identity, :log10) || throw(ArgumentError("xscale must be :identity or :log10")) + data = filter(d -> d[2].abs_delta_pdf !== nothing, _labelled(sources)) + isempty(data) && throw(ArgumentError("plot_tolerance_pdf: no source has a tolerance Monte Carlo; run with a tolerance_file")) + return _draw(_plot_tolerance_pdf, data; corrected, normalize, xscale, show_batches, save_path) end plot_tolerance_pdf(source::SingleSource; kwargs...) = (_check(plot_tolerance_pdf, source); plot_tolerance_pdf(_sources(source); kwargs...)) @@ -330,51 +335,29 @@ plot_tolerance_pdf(source::SingleSource; kwargs...) = (_check(plot_tolerance_pdf plot_overlap_phasors(sources; coils=nothing, save_path=nothing) plot_overlap_phasors(source; kwargs...) -Each coil set's as-built dominant-mode overlap `δ` as an arrow in the complex plane, laid head to -tail in `coils` order so the walk ends at the run's total, drawn as one bold arrow from the -origin. The picture is rotated so that the total is real: an arrow pointing along it adds to the -error field, one pointing against it cancels part of another coil's, and the single number -`|Σδ|` a Monte Carlo starts from cannot tell those apart. One panel per source, since the mode -phase is arbitrary between runs. Sources are a `gpec.h5` path, a `SensitivityTable`, or a vector -of `CoilOverlap`; on a file the walk's end is checked against the stored Monte Carlo -`delta_as_designed` and a mismatch is warned about. +Plot each error-field coil set's as-built dominant-mode overlap `δ` as an arrow in the complex plane, laid head to +tail so the walk ends at the run's total, rotated so the total is real: an arrow along it adds to the error field, +one against it cancels part of another coil's. One panel per source, since the mode phase is arbitrary between runs. +The NTV correction arrays and coil sets with no as-designed n ≠ 0 field are left out; throws when no coil set has +one. On a file the walk's end is checked against the stored Monte Carlo `delta_as_designed`. """ function plot_overlap_phasors(sources::Sources; coils=nothing, save_path=nothing) - data = [(lbl, _load(src)) for (lbl, src) in sources] - any(d -> d[2].delta_as_designed === nothing, data) && return _empty("No per-coil overlaps — run with an [ErrorFields] section") - panels = Plots.Plot[] - for (lbl, d) in data - names = coils === nothing ? d.coil_names : String.(collect(coils)) - idx = _coil_indices(lbl, d, names) - z = d.delta_as_designed[idx] - δ = abs.(z) + panels = map(sources) do (lbl, src) + d = _load(src) + d.delta_as_designed === nothing && throw(ArgumentError("plot_overlap_phasors: source \"$lbl\" has no per-coil overlaps; run with an [ErrorFields] section")) + names = coils === nothing ? [nm for nm in d.coil_names if nm ∉ _efc_names(src)] : String.(collect(coils)) + z = d.delta_as_designed[_coil_indices(lbl, d, names)] total = sum(z) if coils === nothing && d.abs_delta_total_as_designed !== nothing && !isapprox(abs(total), d.abs_delta_total_as_designed; rtol=1e-6, atol=eps(Float64)) @warn "$lbl: the head-to-tail total |Σδ| = $(abs(total)) differs from the stored Monte Carlo abs_delta_total_as_designed $(d.abs_delta_total_as_designed)" end - rot = abs(total) > 0 ? cis(-angle(total)) : one(ComplexF64) - w = z .* rot - tip = cumsum(w) - tail = vcat(zero(ComplexF64), tip[1:end-1]) - # Equal aspect keeps the angles honest; the box is padded so a walk along the real axis - # (every coil in phase) does not collapse the panel to a sliver. - xs = vcat(0.0, real.(tip)) - ys = vcat(0.0, imag.(tip)) - span = max(maximum(xs) - minimum(xs), maximum(ys) - minimum(ys), eps()) - pad = 0.1 * span - half_y = max(0.3 * span, 0.5 * (maximum(ys) - minimum(ys)) + pad) - y_mid = 0.5 * (maximum(ys) + minimum(ys)) - p = plot(; xlabel="Re δ (rotated so the total is real)", ylabel="Im δ", title="Overlap phasors: $lbl, |Σδ| = $(round(abs(total); sigdigits=3))", - aspect_ratio=:equal, xlims=(minimum(xs) - pad, maximum(xs) + pad), ylims=(y_mid - half_y, y_mid + half_y), legend=:outerright, - size=(900, 560), left_margin=12Plots.mm, bottom_margin=6Plots.mm, titlefontsize=12) - for (k, nm) in enumerate(names) - plot!(p, [real(tail[k]), real(tip[k])], [imag(tail[k]), imag(tip[k])]; arrow=true, lw=2, c=k, label="$nm (|δ| = $(round(δ[k]; sigdigits=2)))") - end - plot!(p, [0.0, abs(total)], [0.0, 0.0]; arrow=true, lw=4, c=:black, alpha=0.6, label="total") - push!(panels, p) + # Roundoff is judged against the run's error-field scale: every coil set's overlap and the Monte Carlo mean. + scale = max(maximum(abs, d.delta_as_designed), something(d.abs_delta_sampled_mean, 0.0)) + keep = abs.(z) .> _ROUNDOFF * scale + any(keep) || throw(ArgumentError("plot_overlap_phasors: no error-field coil set$(isempty(lbl) ? "" : " of \"$lbl\"") has an as-designed n ≠ 0 field")) + (; label=lbl, names=names[keep], delta=z[keep]) end - length(panels) == 1 && return _save(panels[1], save_path) - return _save(plot(panels...; layout=(1, length(panels)), size=(900 * length(panels), 560)), save_path) + return _draw(_plot_overlap_phasors, panels; save_path) end plot_overlap_phasors(source::SingleSource; kwargs...) = (_check(plot_overlap_phasors, source); plot_overlap_phasors(_sources(source); kwargs...)) @@ -383,52 +366,28 @@ plot_overlap_phasors(source::SingleSource; kwargs...) = (_check(plot_overlap_pha plot_tolerance_budget(table, tolerances, coil_sets; monte_carlo=nothing, kwargs...) plot_tolerance_budget(terms::NamedTuple; monte_carlo=nothing, sort=:total, top=nothing, save_path=nothing) -The worst-case tolerance budget of a run as stacked horizontal bars, one per coil set, coherent -group and the unattributed budget: the as-designed `|δ_as_designed|`, the shift term and the tilt term -of `ErrorFields.worst_case_terms`, whose sum over every bar is the `abs_delta_worst_case` the -Monte Carlo sizes its histogram by. It shows which coil's tolerance the budget is spent on, and -whether it is spent on the coil's shift, its tilt, or on the field it makes as designed. -`sort` orders the bars by `:total` (largest at the top), `:name`, or `:none` (file order); `top` -keeps only that many largest. With a Monte Carlo result (read from the file, or passed as -`monte_carlo`) the sample mean of `|δ|` and the coherent as-designed total are marked, which -puts the worst-case bars against what the sampling typically realises. +Plot the worst-case tolerance budget as stacked horizontal bars, one per coil set, coherent group and the unattributed +budget: the as-designed `|δ|`, the shift term and the tilt term of `ErrorFields.worst_case_terms`, which sum to the +`abs_delta_worst_case` the Monte Carlo sizes its histogram by. `sort` orders the bars by `:total` (largest at the top), +`:name`, or `:none`; `top` keeps only that many largest. With a Monte Carlo result the sample mean of `|δ|` and the +as-designed total are marked. """ function plot_tolerance_budget(terms::NamedTuple; monte_carlo=nothing, sort::Symbol=:total, top=nothing, save_path=nothing) sort in (:total, :name, :none) || throw(ArgumentError("sort must be :total, :name, or :none")) ng = length(terms.group_names) nc = length(terms.coil_names) labels = vcat(terms.coil_names, ["group $g" for g in terms.group_names], ["unattributed"]) - nominal = vcat(terms.abs_delta_as_designed, zeros(ng), 0.0) - shift = vcat(terms.abs_delta_shift_tolerance, terms.abs_delta_group_shift_tolerance, 0.0) - tilt = vcat(terms.abs_delta_tilt_tolerance, terms.abs_delta_group_tilt_tolerance, 0.0) - other = vcat(zeros(nc + ng), terms.abs_delta_unattributed) - total = nominal .+ shift .+ tilt .+ other + parts = ( + ("as designed", vcat(terms.abs_delta_as_designed, zeros(ng), 0.0)), + ("shift tolerance", vcat(terms.abs_delta_shift_tolerance, terms.abs_delta_group_shift_tolerance, 0.0)), + ("tilt tolerance", vcat(terms.abs_delta_tilt_tolerance, terms.abs_delta_group_tilt_tolerance, 0.0)), + ("unattributed", vcat(zeros(nc + ng), terms.abs_delta_unattributed)) + ) + total = sum(p[2] for p in parts) order = sort === :total ? sortperm(total) : sort === :name ? sortperm(labels; rev=true) : collect(reverse(eachindex(labels))) top === nothing || (order = order[max(1, end - top + 1):end]) - m = length(order) - p = plot(; xlabel="worst-case contribution to |δ|", ylabel="", yticks=(1:m, labels[order]), ylims=(0.4, m + 0.6), - title="Tolerance budget by term (Σ over every bar = abs_delta_worst_case = $(round(terms.abs_delta_worst_case; sigdigits=3)))", legend=:bottomright, - size=(900, max(420, 26 * m + 160)), left_margin=12Plots.mm, bottom_margin=8Plots.mm, titlefontsize=12) - segments = (("as designed |δ_as_designed|", nominal, :gray40), ("shift tolerance + 3σ", shift, 1), ("tilt tolerance + 3σ", tilt, 2), ("unattributed budget + 3σ", other, 3)) - labelled = Set{String}() - for (y, i) in enumerate(order) - x0 = 0.0 - for (name, vals, col) in segments - vals[i] > 0 || continue - x1 = x0 + vals[i] - plot!(p, Shape([x0, x1, x1, x0], [y - 0.4, y - 0.4, y + 0.4, y + 0.4]); c=col, lw=0.5, label=name in labelled ? "" : name) - push!(labelled, name) - x0 = x1 - end - end - if monte_carlo !== nothing - d = _load(monte_carlo) - d.abs_delta_sampled_mean === nothing || - vline!(p, [d.abs_delta_sampled_mean]; ls=:dash, c=:black, label="Monte Carlo mean |δ| = $(round(d.abs_delta_sampled_mean; sigdigits=3))") - d.abs_delta_total_as_designed === nothing || - vline!(p, [d.abs_delta_total_as_designed]; ls=:dot, c=:black, label="as designed |Σδ| = $(round(d.abs_delta_total_as_designed; sigdigits=3))") - end - return _save(p, save_path) + mc = monte_carlo === nothing ? nothing : _load(monte_carlo) + return _draw(_plot_tolerance_budget, labels[order], [(name, v[order]) for (name, v) in parts], terms.abs_delta_worst_case, mc; save_path) end function plot_tolerance_budget(table::EF.SensitivityTable, ts::EF.ToleranceSet, coil_sets::AbstractVector{FT.CoilSet}; tolerance_scale::Real=1.0, kwargs...) return plot_tolerance_budget(EF.worst_case_terms(table, ts, collect(coil_sets); tolerance_scale); kwargs...) @@ -436,70 +395,30 @@ end function plot_tolerance_budget(h5path::AbstractString; psi_low::Real=0.0, psi_high::Real=PE.CORE_PSI_HIGH, mode::Int=1, tolerance_scale::Real=1.0, kwargs...) terms = EF.worst_case_terms(h5path; psi_low, psi_high, mode, tolerance_scale) d = _load(h5path) - mc = - d.abs_delta_pdf === nothing ? nothing : - (; - abs_delta_bin_edges=d.abs_delta_bin_edges, - abs_delta_pdf=d.abs_delta_pdf, - abs_delta_sampled_mean=d.abs_delta_sampled_mean, - abs_delta_total_as_designed=d.abs_delta_total_as_designed - ) + mc = d.abs_delta_pdf === nothing ? nothing : (; abs_delta_sampled_mean=d.abs_delta_sampled_mean, abs_delta_total_as_designed=d.abs_delta_total_as_designed) return plot_tolerance_budget(terms; monte_carlo=mc, kwargs...) end """ - plot_linearity_residuals(sources; at=:tolerance, coils=nothing, tolerances=nothing, fd_step_shift_m=nothing, fd_step_tilt_deg=nothing, yscale=:identity, save_path=nothing) + plot_linearity_residuals(sources; at=:tolerance, coils=nothing, tolerances=nothing, fd_step_shift_m=nothing, fd_step_tilt_deg=nothing, yscale=:log10, save_path=nothing) plot_linearity_residuals(source; kwargs...) -How linear each coil set's field is in its rigid motions: for the six taps (shift and tilt about -`x`, `y`, `z`) the ratio of the central second difference `‖b̃(+h) + b̃(−h) − 2b̃(0)‖` to the -largest first difference among the set's taps, as stored in -`ErrorFields/CoilSensitivities/*_linearity_residual`. The normalization is against the set's -*largest* linear response, so a tap whose own first difference vanishes by symmetry is judged -against the terms the model keeps, and a weak tap's own curvature is understated. `at = :step` -shows the ratio at the finite-difference steps the run used; `at = :tolerance` rescales each tap -by `tolerance / step` (the second difference grows as `h²`, the first as `h`), which is the -curvature the linear model neglects over the coil's own tolerance range. Coherent-group -amplitudes are not added. Tolerances come from the file's snapshot or `tolerances`; a coil -without a tolerance block draws at zero. The steps come from the file's recorded deck, or from -`fd_step_shift_m` and `fd_step_tilt_deg` for in-memory `CoilSensitivities`. The dashed line is -the 1 % level at which the sensitivity calculation itself warns; on a linear axis it sets the -scale and hides sub-percent residuals, so `yscale = :log10` draws them as stems and markers -(never bars) over the decades they span. One panel per source. For the model's error at finite -displacements rather than at the step, see [`plot_linearity_check`](@ref). +Plot how linear each coil set's field is in its rigid motions: for the six taps (shift and tilt about `x`, `y`, `z`) +the ratio of the central second difference to the largest first difference among the set's taps, as stored in +`ErrorFields/CoilSensitivities/*_linearity_residual`. `at = :step` shows it at the finite-difference steps; `at = :tolerance` rescales each tap by `tolerance / step` to the coil's own tolerance range. Tolerances come from the file's +snapshot or `tolerances`; steps from the recorded deck or `fd_step_shift_m` and `fd_step_tilt_deg`. The 1 % level at +which the sensitivity calculation warns is marked. One panel per source, on a log axis unless `yscale = :identity`. """ function plot_linearity_residuals(sources::Sources; at::Symbol=:tolerance, coils=nothing, tolerances=nothing, fd_step_shift_m=nothing, - fd_step_tilt_deg=nothing, yscale::Symbol=:identity, save_path=nothing) + fd_step_tilt_deg=nothing, yscale::Symbol=:log10, save_path=nothing) at in (:step, :tolerance) || throw(ArgumentError("at must be :step or :tolerance")) yscale in (:identity, :log10) || throw(ArgumentError("yscale must be :identity or :log10")) - data = [(lbl, _load(src)) for (lbl, src) in sources] - any(d -> d[2].shift_linearity_residual === nothing, data) && return _empty("No ErrorFields/CoilSensitivities linearity residuals — run with an [ErrorFields] section") - taps = ("shift x", "shift y", "shift z", "tilt x", "tilt y", "tilt z") - panels = Plots.Plot[] - for (lbl, d) in data + panels = map(_labelled(sources)) do (lbl, d) + d.shift_linearity_residual === nothing && throw(ArgumentError("plot_linearity_residuals: source \"$lbl\" has no linearity residuals; run with an [ErrorFields] section")) names = coils === nothing ? d.coil_names : String.(collect(coils)) - r = _linearity_matrix(lbl, d, names, at; tolerances, fd_step_shift_m, fd_step_tilt_deg) - n = length(names) - width = 0.8 / 6 - p = plot(; xlabel="coil set", ylabel=at === :step ? "‖2nd diff‖ / max ‖1st diff‖ at the FD step" : "‖2nd diff‖ / max ‖1st diff‖ at the tolerance", - title="Linearity of the rigid-motion response: $lbl", xticks=(1:n, names), xrotation=45, legend=:topright, size=(900, 520), - left_margin=12Plots.mm, bottom_margin=8Plots.mm, titlefontsize=12) - if yscale === :log10 - ylo = _log_floor!(p, vcat(vec(r), 1e-2)) - for (t, tap) in enumerate(taps) - _stems!(p, (1:n) .+ (t - 3.5) * width, r[t, :], ylo; c=t <= 3 ? t : t + 2, label=tap, lw=2, ms=4) - end - else - for (t, tap) in enumerate(taps) - x = (1:n) .+ (t - 3.5) * width - bar!(p, x, r[t, :]; bar_width=width, label=tap, alpha=0.85, c=t <= 3 ? t : t + 2) - end - end - hline!(p, [1e-2]; ls=:dash, c=:black, label="1 % (sensitivity warning level)") - push!(panels, p) + (; label=lbl, names, residual=_linearity_matrix(lbl, d, names, at; tolerances, fd_step_shift_m, fd_step_tilt_deg)) end - length(panels) == 1 && return _save(panels[1], save_path) - return _save(plot(panels...; layout=(1, length(panels)), size=(900 * length(panels), 520)), save_path) + return _draw(_plot_linearity_residuals, panels, at, yscale; save_path) end plot_linearity_residuals(source::SingleSource; kwargs...) = (_check(plot_linearity_residuals, source); plot_linearity_residuals(_sources(source); kwargs...)) @@ -507,278 +426,121 @@ plot_linearity_residuals(source::SingleSource; kwargs...) = (_check(plot_lineari plot_linearity_check(check::EF.LinearityCheck; save_path=nothing) plot_linearity_check(h5path; scales=(1.0, 2.0), save_path=nothing, kwargs...) -The relative error of the linear sensitivity model at finite displacements, from -`ErrorFields.linearity_check`: one stem per row on a logarithmic axis, grouped by coil set -or coherent group, with the shift and tilt rows told apart by colour, the scale by marker, and the -sign by fill. The dashed line is the 1 % level the sensitivity calculation warns at for its own step -residuals. A row whose linear change vanishes has no relative error and is not drawn. +Plot the relative error of the linear sensitivity model at finite displacements, from `ErrorFields.linearity_check`: +one stem per row on a log axis, grouped by coil set or coherent group, shift and tilt told apart by colour and the +scale by marker, with the 1 % level marked. A row whose linear change vanishes has no relative error and is not drawn. """ function plot_linearity_check(check::EF.LinearityCheck; save_path=nothing) - isempty(check.name) && return _empty("No linearity rows: the tolerance set names no coil or group") - names = unique(check.name) - scales = sort(unique(check.scale)) - markers = (:circle, :diamond, :utriangle, :square, :star5, :hexagon) - p = plot(; xlabel="coil set or coherent group", ylabel="|δ_actual − δ_linear| / |δ_linear − δ_as_designed|", - title="Linearity of the overlap at finite displacements", xticks=(1:length(names), names), xrotation=45, legend=:outerright, - size=(1000, 560), left_margin=12Plots.mm, bottom_margin=8Plots.mm, titlefontsize=12) - ylo = _log_floor!(p, vcat(check.relative_error, 1e-2)) - ylo === nothing && return _empty("No finite relative errors to draw") - width = 0.8 / (4 * length(scales)) - slot = 0 - for (k, kind) in enumerate((:shift, :tilt)), a in 1:2, (si, s) in enumerate(scales) - slot += 1 - for (sgn, fill) in ((1, true), (-1, false)) - rows = findall(i -> check.kind[i] === kind && check.axis[i] == a && check.scale[i] == s && sign(check.displacement[i]) == sgn, eachindex(check.name)) - isempty(rows) && continue - x = [findfirst(==(check.name[i]), names) + (slot - (4 * length(scales) + 1) / 2) * width for i in rows] - lbl = sgn > 0 ? "$kind $(a == 1 ? "x" : "y") × $s" : "" - _stems!(p, x, check.relative_error[rows], ylo; c=k, label=lbl, marker=markers[mod1(si, length(markers))], ms=fill ? 5 : 4, lw=1.5) - end - end - hline!(p, [1e-2]; ls=:dash, c=:black, label="1 %") - return _save(p, save_path) + isempty(check.name) && throw(ArgumentError("plot_linearity_check: the tolerance set names no coil or group")) + any(v -> isfinite(v) && v > 0, check.relative_error) || throw(ArgumentError("plot_linearity_check: no finite relative error to draw")) + return _draw(_plot_linearity_check, check; save_path) end plot_linearity_check(h5path::AbstractString; save_path=nothing, kwargs...) = plot_linearity_check(EF.linearity_check(h5path; kwargs...); save_path) -# The six taps' residual ratios per coil set (6 × n), at the finite-difference step or rescaled -# by tolerance / step to the coil's own tolerance. -function _linearity_matrix(lbl, d, names, at::Symbol; tolerances=nothing, fd_step_shift_m=nothing, fd_step_tilt_deg=nothing) - idx = _coil_indices(lbl, d, names) - r = vcat(d.shift_linearity_residual[:, idx], d.tilt_linearity_residual[:, idx]) - at === :step && return r - ts = tolerances === nothing ? d.tolerances : tolerances - ts === nothing && throw(ArgumentError("source \"$lbl\" carries no tolerance snapshot; pass tolerances=ToleranceSet or use at=:step")) - h_shift = something(fd_step_shift_m, d.fd_step_shift_m, EF.ErrorFieldsControl().fd_step_shift_m) - h_tilt = something(fd_step_tilt_deg, d.fd_step_tilt_deg, EF.ErrorFieldsControl().fd_step_tilt_deg) - tol_of = Dict(t.name => t for t in ts.coils) - for (k, i) in enumerate(idx) - t = get(tol_of, d.coil_names[i], nothing) - shift_tol = t === nothing ? 0.0 : t.shift_tol_m - tilt_tol = - t === nothing ? 0.0 : - d.major_radius_m === nothing ? throw(ArgumentError("source \"$lbl\" carries no major radii, needed to convert a tilt tolerance in metres")) : - EF.tilt_tolerance_deg(t.tilt_tol, t.tilt_units, d.major_radius_m[i]; name="coil set $(d.coil_names[i])") - r[1:3, k] .*= shift_tol / h_shift - r[4:6, k] .*= tilt_tol / h_tilt - end - return r -end - """ plot_locking_risk(sources; corrected=true, target_percent=nothing, save_path=nothing) - plot_locking_risk(h5path; kwargs...) + plot_locking_risk(source; kwargs...) -Locking probability against tolerance scale from each run's `ErrorFields/Risk/ToleranceScan/`, -intrinsic and (when `corrected`) with error-field correction, batch spread as error bars, and -the allowable scale at `target_percent` marked where the scan reaches it. +Plot the locking probability against tolerance scale from each run's `ErrorFields/Risk/ToleranceScan/`, intrinsic and +(when `corrected`) with error-field correction, with the batch spread as error bars, on log-log axes when any +probability is positive; a scale where no sample locked is then left off. With `target_percent` the target is marked and the title gives the allowable scale where +the scan reaches it. """ function plot_locking_risk(sources::Sources; corrected::Bool=true, target_percent=nothing, save_path=nothing) - p = plot(; xlabel="tolerance scale", ylabel="locking probability [%]", xscale=:log10, yscale=:log10, legend=:topleft, - title="Locking risk vs tolerance scale", left_margin=12Plots.mm, bottom_margin=6Plots.mm) - any_data = false - risk_floor = 1e-4 # named to avoid shadowing Base.floor inside this function - for (j, (lbl, path)) in enumerate(sources) - d = _load(path) - d.scan_scale === nothing && continue - any_data = true - plot!( - p, - d.scan_scale, - max.(d.scan_locking_probability_percent, risk_floor); - yerror=d.scan_locking_probability_spread_percent ./ 2, - marker=:circle, - lw=2, - c=j, - label="$lbl intrinsic" - ) - corrected && plot!( - p, - d.scan_scale, - max.(d.scan_locking_probability_efc_percent, risk_floor); - yerror=d.scan_locking_probability_efc_spread_percent ./ 2, - marker=:square, - lw=2, - ls=:dash, - c=j, - label="$lbl corrected" - ) - if target_percent !== nothing - scan = EF.ToleranceScan( - d.scan_scale, - d.scan_locking_probability_percent, - d.scan_locking_probability_efc_percent, - d.scan_locking_probability_spread_percent, - d.scan_locking_probability_efc_spread_percent, - 0.0 - ) - for (corr, mk) in ((false, :diamond), (true, :star5)) - (corr && !corrected) && continue - s = EF.allowable_tolerance(scan, target_percent; corrected=corr) - isnan(s) || scatter!(p, [s], [target_percent]; marker=mk, ms=8, c=j, label="$lbl allowable ($(corr ? "corrected" : "intrinsic"))") - end - end - end - any_data || return _empty("No ErrorFields/Risk/ToleranceScan data — set scan_scales in [ErrorFields.Risk]") - target_percent === nothing || hline!(p, [target_percent]; ls=:dash, c=:gray, label="target $(target_percent) %") - return _save(p, save_path) + data = filter(d -> d[2].scan_scale !== nothing, _labelled(sources)) + isempty(data) && throw(ArgumentError("plot_locking_risk: no source has a tolerance scan; set scan_scales in [ErrorFields.Risk]")) + return _draw(_plot_locking_risk, data; corrected, target_percent, save_path) end plot_locking_risk(source::SingleSource; kwargs...) = (_check(plot_locking_risk, source); plot_locking_risk(_sources(source); kwargs...)) """ plot_threshold_scaling(sources; save_path=nothing) - plot_threshold_scaling(h5path; kwargs...) + plot_threshold_scaling(source; kwargs...) -The sampled penetration-threshold density and `P(lock|δ)` of each run against its overlap -distributions, on a logarithmic `|δ|` axis, with the fitted threshold marked. +Plot each run's overlap distributions against the penetration threshold on a log `|δ|` axis: the threshold as its +cumulative distribution P(lock | δ) on a 0–1 right axis, with the nominal threshold (the fit at the central exponents, +the CDF's median) marked. The CDF is rebuilt from the recorded scaling and scenario when the source records them, and +otherwise drawn from the stored values on the Monte Carlo bins. A source needs the overlap distribution and the +threshold together; pass a `(MonteCarloResult, RiskResult)` tuple for results in memory. """ function plot_threshold_scaling(sources::Sources; save_path=nothing) - p = plot(; xlabel="dominant-mode overlap |δ|", ylabel="normalized density / P(lock | δ)", xscale=:log10, legend=:topleft, - title="Overlap distribution vs penetration threshold", left_margin=12Plots.mm, bottom_margin=6Plots.mm) - any_data = false - for (j, (lbl, path)) in enumerate(sources) - d = _load(path) - # Needs the overlap distribution and the threshold together; an in-memory source carrying - # only one of the two is skipped rather than indexed into a nothing. - (d.threshold_pdf === nothing || d.abs_delta_pdf === nothing || d.abs_delta_bin_edges === nothing) && continue - any_data = true - c = _centers(d.abs_delta_bin_edges) - keep = c .> 0 - plot!(p, c[keep], d.abs_delta_pdf[keep] ./ maximum(d.abs_delta_pdf); lw=2, c=j, label="$lbl intrinsic |δ|") - plot!(p, c[keep], d.abs_delta_efc_pdf[keep] ./ maximum(d.abs_delta_efc_pdf); lw=2, ls=:dash, c=j, label="$lbl corrected |δ|") - plot!(p, c[keep], d.threshold_pdf[keep] ./ maximum(d.threshold_pdf); lw=2, ls=:dot, c=j, label="$lbl threshold") - plot!(p, d.abs_delta_bin_edges[2:end], d.locking_probability_given_delta[2:end]; lw=1.5, ls=:dashdot, c=j, label="$lbl P(lock | δ)") - vline!(p, [d.threshold_fit]; ls=:dot, c=:black, label=j == 1 ? "fitted threshold" : "") - end - any_data || return _empty("No ErrorFields/Risk data — run with an [ErrorFields.scenario] table") - return _save(p, save_path) + data = [ + (lbl, d, _threshold_samples(src)) for (lbl, src) in sources for d in (_load(src),) + if d.threshold_pdf !== nothing && d.abs_delta_pdf !== nothing && d.abs_delta_bin_edges !== nothing + ] + isempty(data) && throw(ArgumentError("plot_threshold_scaling: no source has both the overlap distribution and the threshold; run with an [ErrorFields.scenario] table")) + return _draw(_plot_threshold_scaling, data; save_path) end plot_threshold_scaling(source::SingleSource; kwargs...) = (_check(plot_threshold_scaling, source); plot_threshold_scaling(_sources(source); kwargs...)) """ - plot_dominant_mode_spectrum(sources; mode=1, save_path=nothing) - plot_dominant_mode_spectrum(h5path; kwargs...) + plot_dominant_mode_spectrum(sources; modes=1:3, save_path=nothing) + plot_dominant_mode_spectrum(source; kwargs...) -The right singular vector of the full-window dominant coupling mode of each run, `|V[m, mode]|` -against poloidal mode number (one series per toroidal mode), from -`PerturbedEquilibrium/SingularCoupling/DominantMode/`. +Plot the right singular vectors of the dominant resonant-coupling modes, `|V[m, k]|` against poloidal harmonic m for +each mode `k` in `modes` (one step line per toroidal mode number), labelled with their singular values. """ -function plot_dominant_mode_spectrum(sources::Sources; mode::Int=1, save_path=nothing) - p = plot(; xlabel="poloidal mode m", ylabel="|V[m, $mode]|", title="Dominant resonant-coupling mode spectrum", legend=:topright, - left_margin=12Plots.mm, bottom_margin=6Plots.mm) - any_data = false - for (j, (lbl, path)) in enumerate(sources) - d = _load(path) - (d.dominant_v === nothing || d.mn_index === nothing) && continue - any_data = true - mode <= size(d.dominant_v, 2) || continue - for n in unique(d.mn_index[:, 2]) - rows = findall(==(n), d.mn_index[:, 2]) - me, ae = _step_series(d.mn_index[rows, 1], abs.(d.dominant_v[rows, mode])) - plot!(p, me, ae; seriestype=:steppre, lw=2, c=j, label="$lbl n=$n (σ = $(round(d.singular_values[mode]; sigdigits=3)))") - end - end - any_data || return _empty("No PerturbedEquilibrium/SingularCoupling/DominantMode data") - return _save(p, save_path) +function plot_dominant_mode_spectrum(sources::Sources; modes=1:3, save_path=nothing) + data = filter(d -> d[2].dominant_v !== nothing && d[2].mn_index !== nothing, _labelled(sources)) + isempty(data) && throw(ArgumentError("plot_dominant_mode_spectrum: no source has the dominant coupling modes")) + return _draw(_plot_dominant_mode_spectrum, data, collect(modes); save_path) end plot_dominant_mode_spectrum(source::SingleSource; kwargs...) = (_check(plot_dominant_mode_spectrum, source); plot_dominant_mode_spectrum(_sources(source); kwargs...)) +# The coil sets with a significant as-designed overlap; the axisymmetric ones have no n ≠ 0 spectrum to draw. +function _significant_overlaps(overlaps) + isempty(overlaps) && throw(ArgumentError("no coil overlaps to plot")) + big = maximum(o -> abs(o.delta), overlaps) + keep = [o for o in overlaps if abs(o.delta) > _ROUNDOFF * big] + isempty(keep) && throw(ArgumentError("no coil set has an as-designed n ≠ 0 field")) + return keep +end + """ plot_applied_spectra(ctx, overlaps; normalize=true, save_path=nothing) plot_applied_spectra(h5path, coil_sets; normalize=true, save_path=nothing, kwargs...) -Each coil set's applied spectrum against the dominant resonant mode. - -Both views answer different questions and the plot shows whichever is asked for: `normalize = true` -scales every curve to its own peak, which says whether a coil drives a *different* part of the -spectrum; `false` leaves the amplitudes, which says whether it simply drives *less*. A coil that -couples weakly for the first reason needs a geometry change; one that couples weakly for the second -may just be further away. +Plot each coil set's applied spectrum against the dominant resonant mode. `normalize = true` scales every curve to its +own peak, which says whether a coil drives a different part of the spectrum; `false` keeps the amplitudes, which says +whether it simply drives less. """ -function plot_applied_spectra(ctx::EF.ResonantDriveContext, overlaps::AbstractVector{EF.CoilOverlap}; - normalize::Bool=true, save_path=nothing) - isempty(overlaps) && return _empty("No coil overlaps to plot") - m = ctx.rc.m_modes - mode = first(overlaps).mode - p = plot(; xlabel="poloidal mode m", ylabel=normalize ? "amplitude / own peak" : "|b̃| (T)", - title="Applied spectra against the dominant resonant mode", legend=:topright, - left_margin=13Plots.mm, bottom_margin=7Plots.mm) - v = abs.(ctx.dom.right_singular_vectors[:, mode]) - mv, av = _step_series(m, v ./ maximum(v)) - normalize && plot!(p, mv, av; seriestype=:steppre, lw=3, c=:black, fillrange=0, fillalpha=0.12, label="dominant mode |V|") - for (j, o) in enumerate(overlaps) - a = abs.(o.spectrum) - scale = normalize ? maximum(a) : 1.0 - ms, as = _step_series(m, scale > 0 ? a ./ scale : a) - plot!(p, ms, as; seriestype=:steppre, lw=2, c=j, label=o.coil_name) - end - return _save(p, save_path) +function plot_applied_spectra(ctx::EF.ResonantDriveContext, overlaps::AbstractVector{EF.CoilOverlap}; normalize::Bool=true, save_path=nothing) + ovs = _significant_overlaps(overlaps) + v = abs.(ctx.dom.right_singular_vectors[:, first(ovs).mode]) + return _draw(_plot_applied_spectra, ctx.rc.m_modes, v, [(o.coil_name, abs.(o.spectrum)) for o in ovs]; normalize, save_path) end """ plot_overlap_contributions(ctx, overlaps; save_path=nothing) plot_overlap_contributions(h5path, coil_sets; save_path=nothing, kwargs...) -Which poloidal harmonics produced each coil set's overlap, as `conj(V[m])·b̃[m]` rotated so that -the coil's own total is real. The bars therefore sum to its resonant fraction, printed in the -legend. - -This is what separates a coil that drives the resonant harmonics weakly from one that drives them -strongly and cancels against itself. The single complex overlap cannot tell those apart. +Plot which poloidal harmonics produce each coil set's overlap, `conj(V[m])·b̃[m]` rotated so the coil's own total is +real; each coil's steps sum to its resonant fraction, given in the legend. It separates a coil that drives the +resonant harmonics weakly from one that drives them strongly and cancels against itself. """ function plot_overlap_contributions(ctx::EF.ResonantDriveContext, overlaps::AbstractVector{EF.CoilOverlap}; save_path=nothing) - isempty(overlaps) && return _empty("No coil overlaps to plot") - m = ctx.rc.m_modes - v = ctx.dom.right_singular_vectors[:, first(overlaps).mode] - p = plot(; xlabel="poloidal mode m", ylabel="contribution to overlap", - title="Per-harmonic contribution, aligned to each coil's own overlap phase", legend=:topleft, - left_margin=13Plots.mm, bottom_margin=7Plots.mm) - for (j, o) in enumerate(overlaps) - o.spectrum_norm_t > 0 || continue - c = real.(conj.(v) .* o.spectrum .* cis(-angle(o.resonant_field_t))) ./ o.spectrum_norm_t - ms, as = _step_series(m, c) - plot!(p, ms, as; seriestype=:steppre, lw=2, c=j, - label="$(o.coil_name) (sums to $(round(o.resonant_fraction_percent; digits=1))%)") - end - hline!(p, [0]; c=:black, ls=:dot, label="") - return _save(p, save_path) + ovs = [o for o in _significant_overlaps(overlaps) if o.spectrum_norm_t > 0] + v = ctx.dom.right_singular_vectors[:, first(ovs).mode] + curves = [(o.coil_name, o.resonant_fraction_percent, real.(conj.(v) .* o.spectrum .* cis(-angle(o.resonant_field_t))) ./ o.spectrum_norm_t) for o in ovs] + return _draw(_plot_overlap_contributions, ctx.rc.m_modes, curves; save_path) end """ - plot_surface_overlay(ctx, overlaps; ntheta=256, nzeta=180, levels=4, save_path=nothing) + plot_surface_overlay(ctx, overlaps; ntheta=256, nzeta=180, top=4, save_path=nothing) plot_surface_overlay(h5path, coil_sets; save_path=nothing, kwargs...) -Each coil set's applied normal field on the control surface, with contours of the dominant resonant -mode over it. One panel per coil, each scaled to its own peak so the shapes are comparable. - -This is the view that makes a weak overlap physical rather than numerical: a coil driving a -different poloidal region from the one the dominant mode occupies couples poorly however strong it -is. The poloidal angle is cut at 180° so the outboard midplane sits in the middle of each panel -rather than being split across its edges. +Plot the applied normal field on the control surface of the `top` coil sets with the largest overlap, each scaled to +its own peak, with contours of the dominant resonant mode over it: a coil driving a different poloidal region from the +one the dominant mode occupies couples poorly however strong it is. Toroidal angle φ against poloidal angle θ, with the +outboard midplane at θ = 0. """ -function plot_surface_overlay(ctx::EF.ResonantDriveContext, overlaps::AbstractVector{EF.CoilOverlap}; - ntheta::Int=256, nzeta::Int=180, levels::Int=4, save_path=nothing) - isempty(overlaps) && return _empty("No coil overlaps to plot") +function plot_surface_overlay(ctx::EF.ResonantDriveContext, overlaps::AbstractVector{EF.CoilOverlap}; ntheta::Int=256, nzeta::Int=180, top::Int=4, + save_path=nothing) + ovs = sort(_significant_overlaps(overlaps); by=o -> -abs(o.delta))[1:min(top, end)] m, n = ctx.rc.m_modes, ctx.rc.n_modes - v = ctx.dom.right_singular_vectors[:, first(overlaps).mode] - vmap = FT.reconstruct_bn(v, m, n; ntheta, nzeta) - vpeak = maximum(abs, vmap.bn) - lv = vpeak > 0 ? range(-0.7, 0.7; length=levels + 2)[2:(end-1)] : [0.0] - - k = length(overlaps) - rows = ceil(Int, k / 2) - p = plot(; layout=(rows, min(k, 2)), size=(575 * min(k, 2), 430 * rows), - left_margin=13Plots.mm, bottom_margin=8Plots.mm) - for (j, o) in enumerate(overlaps) - bmap = FT.reconstruct_bn(o.spectrum, m, n; ntheta, nzeta) - peak = maximum(abs, bmap.bn) - heatmap!(p[j], rad2deg.(bmap.zeta), rad2deg.(bmap.theta), peak > 0 ? bmap.bn ./ peak : bmap.bn; - c=:balance, clims=(-1, 1), colorbar=false, yticks=-180:90:180, - xlabel="toroidal angle φ (deg)", ylabel="poloidal angle θ (deg), 0 = outboard midplane", - title="$(o.coil_name) (peak $(round(peak; sigdigits=3)) T)") - vpeak > 0 && contour!(p[j], rad2deg.(vmap.zeta), rad2deg.(vmap.theta), vmap.bn ./ vpeak; - levels=lv, c=:black, lw=1.2, colorbar=false) - end - return _save(p, save_path) + mode = FT.reconstruct_bn(ctx.dom.right_singular_vectors[:, first(ovs).mode], m, n; ntheta, nzeta) + maps = [(o.coil_name, FT.reconstruct_bn(o.spectrum, m, n; ntheta, nzeta)) for o in ovs] + return _draw(_plot_surface_overlay, maps, mode; save_path) end for fn in (:plot_applied_spectra, :plot_overlap_contributions, :plot_surface_overlay) @@ -794,32 +556,15 @@ end plot_phasing_map(h5path, coil_names; psi_low=0.0, psi_high=CORE_PSI_HIGH, mode=1, nphase=180, quantity=:delta_per_kat, save_path=nothing) plot_phasing_map(map::EF.PhasingMap; quantity=:delta_per_kat, save_path=nothing) -Contour map of the dominant-mode overlap per kilo-ampere-turn (`:delta_per_kat`) or of the -resonant fraction of the applied field (`:resonant_fraction_percent`) against the relative phases of two -or three coil arrays (`EF.phasing_map`). Two arrays give a line, three a filled contour whose -axes are the phase of the middle array relative to the first and of the third relative to the -middle; the extreme is marked. +Plot the dominant-mode overlap per kilo-ampere-turn (`:delta_per_kat`) or the resonant fraction of the applied field +(`:resonant_fraction_percent`) against the relative toroidal phases of two or three coil arrays (`EF.phasing_map`): +a line for two arrays, a filled contour with the extreme marked for three. """ function plot_phasing_map(map::EF.PhasingMap; quantity::Symbol=:delta_per_kat, save_path=nothing) - arr = - quantity === :delta_per_kat ? map.delta_per_kat : - quantity === :resonant_fraction_percent ? map.resonant_fraction_percent : - throw(ArgumentError("quantity must be :delta_per_kat or :resonant_fraction_percent")) - label = quantity === :delta_per_kat ? "|δ| per kAt" : "resonant fraction [%]" - names = map.coil_names - if length(map.phase_deg) == 1 - p = plot(map.phase_deg[1], vec(arr); lw=2, xlabel="Δφ($(names[2]) − $(names[1])) [deg]", ylabel=label, legend=false, - title="Two-array phasing", left_margin=12Plots.mm, bottom_margin=6Plots.mm) - elseif length(map.phase_deg) == 2 - p = contourf(map.phase_deg[1], map.phase_deg[2], permutedims(arr); levels=12, xlabel="Δφ($(names[2]) − $(names[1])) [deg]", - ylabel="Δφ($(names[3]) − $(names[2])) [deg]", title="Three-array phasing: $label", colorbar_title=label, aspect_ratio=:equal, - left_margin=12Plots.mm, bottom_margin=6Plots.mm, size=(720, 640)) - v, ph = EF.extreme_phasing(map; quantity) - scatter!(p, [ph[1]], [ph[2]]; marker=:star5, ms=10, c=:white, label="max $(round(v; sigdigits=3))") - else - throw(ArgumentError("plot_phasing_map draws maps of one or two relative phases (two or three arrays)")) - end - return _save(p, save_path) + quantity in (:delta_per_kat, :resonant_fraction_percent) || throw(ArgumentError("quantity must be :delta_per_kat or :resonant_fraction_percent")) + length(map.phase_deg) in (1, 2) || throw(ArgumentError("plot_phasing_map draws maps of one or two relative phases (two or three arrays)")) + extreme = length(map.phase_deg) == 2 ? EF.extreme_phasing(map; quantity) : nothing + return _draw(_plot_phasing_map, map, quantity, extreme; save_path) end function plot_phasing_map(h5path::AbstractString, coil_names::AbstractVector{<:AbstractString}; psi_low::Real=0.0, psi_high::Real=PE.CORE_PSI_HIGH, mode::Int=1, nphase::Int=180, quantity::Symbol=:delta_per_kat, save_path=nothing) @@ -830,90 +575,25 @@ end plot_efc_ntv_limits(h5path; torque_budget, delta_threshold=nothing, safety_factor=1.0, delta_max=15, profiles=false, save_path=nothing) plot_efc_ntv_limits(couplings::Vector{EF.EFCCoupling}; delta_threshold, torque_budget, kwargs...) -Correction current against intrinsic overlap for each correction array of a run -(`ErrorFields/NTV/`): the linear single-mode current and the NTV-limited current whose residual -torque lowers the threshold, with the largest correctable overlap marked; when the run -tabulated the torques against rotation, a second panel shows those tables with the offsets -found and the reference rotation, and the curve uses the torque balance (`model`, -`rotation_exponent` as in `efc_current_curve`). The zero-rotation limits are marked with the -residual torque (circle) and with the whole field's torque (star); nonzero `torque_rtol` or -`budget_rtol` shade the band between the pessimistic and optimistic curves and the range of the -correctable limit. `delta_threshold` defaults to the run's fitted -penetration threshold (`ErrorFields/Risk/threshold_fit`); `torque_budget` is the torque, -N·m, that would bring the reference rotation to rest. `profiles` adds a panel of the torque -integrated from the axis, `T(ψ)` per kAt² at zero rotation shift for the whole field and the -residual, which shows where in the plasma the torque that survives correction is deposited. +Plot the correction current against intrinsic overlap for each correction array (`ErrorFields/NTV/`): the +single-mode current and the NTV-limited current whose residual torque lowers the threshold, with the largest +correctable overlap marked and the zero-rotation limits as points (residual torque: circle, whole field: star). When +the run tabulated the torques against rotation, a second panel shows those tables with the reference rotation; with +`profiles`, a third the torque integrated from the axis at zero rotation shift. Nonzero `torque_rtol` or +`budget_rtol` shade the band between the pessimistic and optimistic curves. `delta_threshold` defaults to the run's +fitted penetration threshold; `torque_budget` is the torque, N·m, that would bring the reference rotation to rest. """ function plot_efc_ntv_limits(couplings::Vector{EF.EFCCoupling}; delta_threshold::Real, torque_budget::Real, safety_factor::Real=1.0, delta_max::Real=15, model::Symbol=:auto, rotation_exponent::Real=1.0, torque_rtol::Real=0.0, budget_rtol::Real=0.0, profiles::Bool=false, save_path=nothing) - band = torque_rtol > 0 || budget_rtol > 0 - band_label = "NTV band (T₀ ±$(round(Int, 100budget_rtol)) %, torque ±$(round(Int, 100torque_rtol)) %)" - p = plot(; xlabel="intrinsic overlap δ_EF / δ_thresh", ylabel="correction current [kAt]", legend=:topleft, - title="Error-field correction against its own NTV torque (budget $(torque_budget) N·m)", left_margin=12Plots.mm, bottom_margin=6Plots.mm) - for (j, c) in enumerate(couplings) - curve = EF.efc_current_curve(c; delta_threshold, torque_budget, safety_factor, delta_max, model, rotation_exponent, torque_rtol, budget_rtol) - x = curve.delta_ef ./ delta_threshold - tag = curve.model === :torque_balance ? "torque balance" : "linear budget" - if band - lo, hi = curve.limits_pessimistic.with_ntv, curve.limits_optimistic.with_ntv - isfinite(lo) && vspan!(p, [lo, min(hi, x[end] * delta_threshold)] ./ delta_threshold; c=j, alpha=0.12, lw=0, label="") - plot!(p, x, curve.current_ntv_pessimistic; lw=1, c=j, alpha=0.5, label="$(c.coil_name) $band_label") - plot!(p, x, curve.current_ntv_optimistic; lw=1, c=j, alpha=0.5, label="") - end - plot!(p, x, curve.current_linear; lw=2, c=j, label="$(c.coil_name) single-mode") - plot!(p, x, curve.current_ntv; lw=2, ls=:dash, c=j, label="$(c.coil_name) with residual NTV ($tag)") - plot!(p, x, curve.current_ntv_upper; lw=1, ls=:dashdot, c=j, alpha=0.7, label="$(c.coil_name) over-correction edge") - isfinite(curve.with_ntv) && vline!(p, [curve.with_ntv / delta_threshold]; ls=:dot, c=j, label="$(c.coil_name) correctable limit") - isfinite(curve.residual_only) && scatter!(p, [curve.residual_only / delta_threshold], [0.0]; marker=:circle, ms=6, c=j, - label="$(c.coil_name) zero rotation, residual torque") - isfinite(curve.torque_only) && scatter!(p, [curve.torque_only / delta_threshold], [0.0]; marker=:star5, ms=9, c=j, - label="$(c.coil_name) zero rotation, whole-field torque") - end - panels = [p] - scanned = filter(EF.has_rotation_scan, couplings) - isempty(scanned) || push!(panels, _ntv_rotation_panel(scanned)) - profiles && push!(panels, _ntv_profile_panel(couplings)) - length(panels) == 1 && return _save(p, save_path) - return _save(plot(panels...; layout=(1, length(panels)), size=(750 * length(panels), 550)), save_path) -end - -# The tabulated torques against the rotation shift, with the balance's reference rotation and the found offsets. -function _ntv_rotation_panel(scanned) - q = plot(; xlabel="E×B rotation shift Δω [krad/s]", ylabel="NTV torque per kAt² [N·m]", legend=:topright, title="Torque against rotation", - left_margin=12Plots.mm, bottom_margin=6Plots.mm) - hline!(q, [0.0]; c=:gray, label="") - for (j, c) in enumerate(scanned) - x = c.rotation_shift ./ 1e3 - plot!(q, x, c.torque_full_scan; lw=2, c=j, marker=:circle, ms=3, label="$(c.coil_name) whole field") - plot!(q, x, c.torque_residual_scan; lw=2, ls=:dash, c=j, marker=:diamond, ms=3, label="$(c.coil_name) residual") - for z in EF.torque_zero_crossings(c) - vline!(q, [z / 1e3]; ls=:dot, c=j, label="") - end - isfinite(c.omega_offset_estimate) && - vline!(q, [-abs(c.omega_offset_estimate) / 1e3, abs(c.omega_offset_estimate) / 1e3]; ls=:dashdot, c=:gray, label=j == 1 ? "±|rough offset| (offset_factor·ω_*T)" : "") - isfinite(c.omega_reference) && vline!(q, [-c.omega_reference / 1e3]; ls=:solid, c=:black, alpha=0.4, label=j == 1 ? "rotation brought to rest (−ω_ref)" : "") - end - return q -end - -# The torque integrated from the axis at zero rotation shift, whole field against residual. -function _ntv_profile_panel(couplings) - r = plot(; xlabel="normalized flux ψ_N", ylabel="NTV torque per kAt² integrated from the axis [N·m]", legend=:topleft, - title="Where the torque is deposited (zero rotation shift)", left_margin=12Plots.mm, bottom_margin=6Plots.mm) - for (j, c) in enumerate(couplings) - isempty(c.psi) && continue - i0 = findfirst(==(0.0), c.rotation_shift) - i0 === nothing && (i0 = argmin(abs.(c.rotation_shift))) - plot!(r, c.psi, c.torque_full_profile[:, i0]; lw=2, c=j, label="$(c.coil_name) whole field") - plot!(r, c.psi, c.torque_residual_profile[:, i0]; lw=2, ls=:dash, c=j, label="$(c.coil_name) residual") - end - return r + curves = [(c, EF.efc_current_curve(c; delta_threshold, torque_budget, safety_factor, delta_max, model, rotation_exponent, torque_rtol, budget_rtol)) + for c in couplings] + return _draw(_plot_efc_ntv_limits, curves, Float64(delta_threshold), Float64(torque_budget); band=torque_rtol > 0 || budget_rtol > 0, profiles, + save_path) end function plot_efc_ntv_limits(h5path::AbstractString; torque_budget::Real, delta_threshold=nothing, kwargs...) couplings = EF.read_efc_couplings(h5path) - # A run may carry [ErrorFields.NTV] without [ErrorFields.scenario], in which case there is no - # stored threshold. Say what to do about it rather than letting HDF5 raise a bare KeyError. + # A run may carry [ErrorFields.NTV] without [ErrorFields.scenario], in which case there is no stored threshold. δt = delta_threshold if δt === nothing key = "$(EF._RISK_GROUP)/threshold_fit" @@ -933,55 +613,16 @@ end plot_correction_requirement(req::EF.CorrectionRequirement; save_path=nothing) plot_correction_requirement(h5path, source_name, array_names; save_path=nothing, kwargs...) -Two panels of a `ErrorFields.correction_requirement`: the resonant field on each rational -surface before correction, after each array's dominant-mode correction alone and after the joint -least-squares correction, as stems on a logarithmic axis per surface; then one polar panel per -array with the complex current it needs as a phasor, magnitude as radius in kilo-ampere-turns (or -as a factor on the array's current as given when any array's ampere-turns are unknown) and toroidal phase -as angle: the array's solo dominant-mode factor in grey, the minimum-current member of the set's -dominant-mode family in blue, and the joint least-squares factor in red. A joint factor far from -the solo one, or a surface left with most of its field, says the single-mode picture of the -correction is not the whole story. +Plot an `ErrorFields.correction_requirement`: the resonant field on each rational surface before correction, after +each array's dominant-mode correction alone and after the joint least-squares correction, as stems on a log axis; then +one polar panel per array with the complex current it needs as a phasor (kilo-ampere-turns, or a factor on the array's +current as given when any array's ampere-turns are unknown): the solo dominant-mode factor, the minimum-current member +of the set's dominant-mode family, and the joint least-squares factor. """ function plot_correction_requirement(req::EF.CorrectionRequirement; save_path=nothing) - n = length(req.rational_psi) - labels = ["$(m)/$(nn)\nψ=$(round(x; digits=3))" for (m, nn, x) in zip(req.rational_m, req.rational_n, req.rational_psi)] - # One slot per surface in ψ order rather than a position at ψ itself, so neighbouring edge surfaces do not overprint. - p = plot(; xlabel="rational surface (m/n, ψ_N)", ylabel="|resonant field| [T]", title="Resonant field per surface: $(req.source_name) and its correction", - xticks=(1:n, labels), legend=:outerright, size=(1000, 520), left_margin=12Plots.mm, bottom_margin=12Plots.mm, titlefontsize=12) vals = vcat(abs.(req.resonant_field_source_t), vec(abs.(req.resonant_field_dominant_t)), abs.(req.resonant_field_least_squares_t)) - ylo = _log_floor!(p, vals) - ylo === nothing && return _empty("No finite resonant field to draw") - k = length(req.array_names) - width = 0.8 / (k + 2) - offsets = ((1:(k+2)) .- (k + 3) / 2) .* width - _stems!(p, (1:n) .+ offsets[1], abs.(req.resonant_field_source_t), ylo; c=:black, label="source as built", marker=:circle, ms=6, lw=3) - for (j, nm) in enumerate(req.array_names) - _stems!(p, (1:n) .+ offsets[j+1], abs.(req.resonant_field_dominant_t[:, j]), ylo; c=j, label="after $nm, dominant mode only", marker=:diamond, ms=5, lw=2) - end - _stems!(p, (1:n) .+ offsets[end], abs.(req.resonant_field_least_squares_t), ylo; c=:red, label="after joint least squares", marker=:star5, ms=7, lw=2) - # One polar panel per array: a factor is a complex multiple of the array's current, so its magnitude - # is the radius and its toroidal phase the angle, which bars with printed phases only hint at. - panels = Plots.Plot[p] - # In kilo-ampere-turns when every array's ampere-turns are known, so arrays with different pattern - # currents share one radial unit; otherwise as a factor on each array's current as given. - in_kat = all(isfinite, req.current_least_squares_kat) - unit = in_kat ? "kAt" : "× its current as given" - for (j, nm) in enumerate(req.array_names) - fd, fm, fl = - in_kat ? (req.current_dominant_kat[j], req.current_dominant_minimum_norm_kat[j], req.current_least_squares_kat[j]) : - (req.current_factor_dominant[j], req.current_factor_dominant_minimum_norm[j], req.current_factor_least_squares[j]) - q = plot(; proj=:polar, title="$nm: needed current [$unit]", titlefontsize=10, legend=:outerbottom, legendfontsize=7) - for (f, lbl, col, w) in - ((fd, "dominant mode, this array alone", :gray40, 4), (fm, "dominant mode, minimum current over the set", :blue, 2.5), (fl, "joint least squares", :red, 2)) - isfinite(f) || continue - plot!(q, [angle(f), angle(f)], [0.0, abs(f)]; lw=w, c=col, label="$lbl: $(round(abs(f); sigdigits=3)) $unit ∠ $(round(rad2deg(angle(f)); digits=0))°") - scatter!(q, [angle(f)], [abs(f)]; ms=6, c=col, label="") - end - push!(panels, q) - end - n_pol = length(panels) - 1 - return _save(plot(panels...; layout=Plots.grid(1, n_pol + 1; widths=vcat(0.5, fill(0.5 / n_pol, n_pol))), size=(1000 + 420 * n_pol, 540)), save_path) + any(v -> isfinite(v) && v > 0, vals) || throw(ArgumentError("plot_correction_requirement: no finite resonant field to draw")) + return _draw(_plot_correction_requirement, req; save_path) end plot_correction_requirement(h5path::AbstractString, source_name::AbstractString, array_names; save_path=nothing, kwargs...) = plot_correction_requirement(EF.correction_requirement(h5path, source_name, array_names; kwargs...); save_path) @@ -989,62 +630,48 @@ plot_correction_requirement(h5path::AbstractString, source_name::AbstractString, """ plot_needed_current(mc, array::EF.CoilOverlap; coupling=nothing, delta_threshold=nothing, torque_budget=nothing, save_path=nothing) -The distribution of the factor on `array`'s current that the sampled machine needs to cancel its -dominant-mode overlap (`ErrorFields.needed_current_distribution`), with the as-designed -factor marked and, when the array's `EFCCoupling`, the threshold and the torque budget are given, -the NTV-limited correctable overlap as a vertical line and the fraction beyond it -(`ErrorFields.uncorrectable_probability`) in the legend. +Plot the distribution of the current `array` needs to cancel each sampled machine's dominant-mode overlap +(`ErrorFields.needed_current_distribution`), in kilo-ampere-turns when the array's ampere-turns are known, with the +as-designed value marked; with the array's `EFCCoupling`, the threshold and the torque budget, also the NTV-limited +correctable current and the fraction of samples beyond it (`ErrorFields.uncorrectable_probability`). """ function plot_needed_current(mc::EF.MonteCarloResult, array::EF.CoilOverlap; coupling=nothing, delta_threshold=nothing, torque_budget=nothing, save_path=nothing) - d = EF.needed_current_distribution(mc, array) - # Kilo-ampere-turns when the array's ampere-turns are known, else a factor on its current as given. - in_kat = isfinite(array.ampere_turns_kat) - per_delta = (in_kat ? array.ampere_turns_kat : 1.0) / abs(array.delta) - c = in_kat ? _centers(d.current_bin_edges_kat) : _centers(d.current_factor_bin_edges) - xlabel = in_kat ? "needed current on $(array.coil_name) [kAt]" : "needed factor on $(array.coil_name)'s current (|δ| / |δ_$(array.coil_name)|)" - p = plot(; xlabel, ylabel="probability density", title="Correction current the sampled machine needs", legend=:topright, left_margin=12Plots.mm, - bottom_margin=6Plots.mm) - plot!(p, c, in_kat ? d.current_pdf_per_kat : d.current_factor_pdf; lw=2, c=1, label="intrinsic |δ| over the tolerance samples") - vline!(p, [mc.abs_delta_total_as_designed * per_delta]; ls=:dot, c=:black, label="as designed") + limit = nothing if coupling !== nothing (delta_threshold === nothing || torque_budget === nothing) && throw(ArgumentError("give delta_threshold and torque_budget with the coupling")) - u = EF.uncorrectable_probability(mc, coupling; delta_threshold, torque_budget) - isfinite(u.abs_delta_max_correctable) && vline!(p, [u.abs_delta_max_correctable * per_delta]; ls=:dash, c=:red, lw=2, - label="NTV-limited correctable overlap ($(u.model)); $(round(u.probability_percent; sigdigits=2)) % of samples beyond") + limit = (EF.uncorrectable_probability(mc, coupling; delta_threshold, torque_budget)..., torque_budget=Float64(torque_budget)) end - return _save(p, save_path) + return _draw(_plot_needed_current, mc, array, EF.needed_current_distribution(mc, array), limit; save_path) end """ plot_error_field_summary(src; save_path=nothing) -Panels of a run's error-field assessment, as many as it produced: coil sensitivities to shift, the tolerance -Monte Carlo distributions, the overlap distribution against the threshold, and the locking risk against -tolerance scale. `src` is an `ErrorFields.ErrorFieldsResult` or the path of a `gpec.h5` it was saved to. +Plot the panels of a run's error-field assessment it supplies: the overlap distribution against the threshold's CDF, +titled with the locking probability; the locking risk against tolerance scale, titled with the allowable scale for 1 % +risk; and the coil sets' shift sensitivity. `src` is an `ErrorFields.ErrorFieldsResult` or the path of a `gpec.h5` it +was saved to. """ function plot_error_field_summary(source::Union{AbstractString,EF.ErrorFieldsResult}; save_path=nothing) + _require(plot_error_field_summary, source) caps = capabilities(source) - src = _sources(source) - panels = (plot_coil_sensitivities, plot_tolerance_pdf, plot_threshold_scaling, plot_locking_risk) - drawn = [fn(src) for fn in panels if _can(fn, caps)] - if isempty(drawn) - missing = unique(reduce(vcat, (_missing(_entry(fn), caps) for fn in panels))) - throw(ArgumentError("plot_error_field_summary: no panel can be drawn: $(_why(missing))")) - end - n = length(drawn) - n == 3 && push!(drawn, plot(; framestyle=:none)) - p = n <= 2 ? plot(drawn...; layout=(1, n), size=(700n, 500)) : plot(drawn...; layout=(2, 2), size=(1400, 1000)) - return _save(p, save_path) + shown = [fn for fn in (plot_threshold_scaling, plot_locking_risk, plot_coil_sensitivities) if _can(fn, caps)] + d = _load(source) + threshold = plot_threshold_scaling in shown ? _threshold_samples(source) : nothing + keep = plot_coil_sensitivities in shown ? [nm ∉ _efc_names(source) for nm in d.coil_names] : nothing + return _draw(_plot_error_field_summary, d, threshold, plot_threshold_scaling in shown, plot_locking_risk in shown, keep; save_path) end const _EF_SOURCES = (EF.ErrorFieldsResult, AbstractString) -register!(plot_error_field_summary, _EF_SOURCES, (), "sensitivities, tolerance Monte Carlo, threshold and locking risk") +register!(plot_error_field_summary, _EF_SOURCES, (), "overlap against the threshold, locking risk and coil sensitivities"; + any_of=((:ef_monte_carlo, :ef_risk), (:ef_scan,), (:ef_sensitivities,))) register!(plot_coil_sensitivities, _EF_SOURCES, (:ef_sensitivities,), "per-coil dominant-mode sensitivities") register!(plot_overlap_phasors, _EF_SOURCES, (:ef_sensitivities,), "as-designed overlap δ of each coil set in the complex plane") register!(plot_linearity_residuals, _EF_SOURCES, (:ef_sensitivities,), "linearity residuals of the coil sensitivities") -register!(plot_dominant_mode_spectrum, _EF_SOURCES, (:dominant_mode,), "spectrum of the dominant resonant-coupling mode") +register!(plot_dominant_mode_spectrum, (_EF_SOURCES..., PE.PlasmaResponse, PE.PerturbedEquilibriumState), (:dominant_mode,), + "spectra of the dominant resonant-coupling modes") register!(plot_tolerance_pdf, _EF_SOURCES, (:ef_monte_carlo,), "Monte Carlo distribution of |δ|") -register!(plot_threshold_scaling, _EF_SOURCES, (:ef_monte_carlo, :ef_risk), "|δ| distribution against the penetration threshold") +register!(plot_threshold_scaling, _EF_SOURCES, (:ef_monte_carlo, :ef_risk), "|δ| distribution against the threshold's CDF") register!(plot_locking_risk, _EF_SOURCES, (:ef_scan,), "locking risk against tolerance scale") end # module ErrorFields diff --git a/src/Analysis/ForceFreeStates.jl b/src/Analysis/ForceFreeStates.jl index b36636e25..3ced9a384 100644 --- a/src/Analysis/ForceFreeStates.jl +++ b/src/Analysis/ForceFreeStates.jl @@ -8,12 +8,8 @@ naming what to change in the run when the source lacks the data it draws (see `A module ForceFreeStates using HDF5 -using LaTeXStrings -using Plots -using Printf -using Statistics: quantile -using ..Analysis: register!, _require, _can, _missing, _entry, _why, capabilities +using ..Analysis: register!, _require, _can, _missing, _entry, _why, capabilities, _draw import ...ForceFreeStates: ForceFreeStatesResult import ...LocalStability: LocalStabilityResult @@ -23,12 +19,19 @@ const _FWD = "ForceFreeStates/Solutions/ForwardIntegration" const _GAL = "ForceFreeStates/Solutions/GalerkinIntegration" const _FBS = "ForceFreeStates/FreeBoundaryStability" -_label(h5path::AbstractString) = h5path -_label(::ForceFreeStatesResult) = "in memory" - # Least-stable total energy, or `nothing` without free-boundary energies. _first_energy(et) = isempty(et) ? nothing : et[1] +# The rational surfaces as (ψ_N, "m/n" labels). +_rationals(r::ForceFreeStatesResult) = (; psi=[s.psifac for s in r.surfaces], labels=["$(s.m[1])/$(s.n[1])" for s in r.surfaces]) +function _rationals(h5path::AbstractString) + h5open(h5path, "r") do fid + haskey(fid, "SingularSurfaces/rational_psi") || return (; psi=Float64[], labels=String[]) + m, n = vec(read(fid["SingularSurfaces/rational_m"])), vec(read(fid["SingularSurfaces/rational_n"])) + (; psi=vec(read(fid["SingularSurfaces/rational_psi"])), labels=["$mi/$ni" for (mi, ni) in zip(m, n)]) + end +end + function _mode_displacement_data(r::ForceFreeStatesResult) sol = r.solution return (; mlow=r.mlow, psi=sol.psi_store, xi=sol.u_store[:, 1, 1, :], dW=r.free_boundary === nothing ? nothing : _first_energy(r.free_boundary.et)) @@ -43,11 +46,88 @@ function _mode_displacement_data(h5path::AbstractString) end end +_criterion_data(r::ForceFreeStatesResult) = (; psi=r.diagnostics.psi_store, crit=r.diagnostics.crit_store) +_criterion_data(h5path::AbstractString) = h5open(fid -> (; psi=read(fid["$_FWD/psi"]), crit=read(fid["$_FWD/crit"])), h5path, "r") + +_energy_eigenvector_data(r::ForceFreeStatesResult) = (; wt=r.free_boundary.wt, mlow=r.mlow) +_energy_eigenvector_data(h5path::AbstractString) = h5open(fid -> (; wt=read(fid["$_FBS/W_freeboundary_eigenmodes"]), mlow=read(fid["Info/mlow"])), h5path, "r") + +_energy_data(r::ForceFreeStatesResult) = (; et=r.free_boundary.et, ep=r.free_boundary.ep, ev=r.free_boundary.ev) +function _energy_data(h5path::AbstractString) + h5open(h5path, "r") do fid + (; et=read(fid["$_FBS/eigenmode_energies"]), ep=read(fid["$_FBS/eigenmode_plasma_energies"]), ev=read(fid["$_FBS/eigenmode_vacuum_energies"])) + end +end + +function _edge_scan_data(r::ForceFreeStatesResult) + es = r.diagnostics.edge_scan + return (; q=es.q, et=es.total_eigenvalue, ep=es.plasma_energy, ev=es.vacuum_energy, qlim=r.qlim) +end + +function _edge_scan_data(h5path::AbstractString) + h5open(h5path, "r") do fid + (; q=read(fid["ForceFreeStates/EdgeScan/q"]), et=read(fid["ForceFreeStates/EdgeScan/total_energy"]), + ep=read(fid["ForceFreeStates/EdgeScan/plasma_energy"]), ev=read(fid["ForceFreeStates/EdgeScan/vacuum_energy"]), qlim=read(fid["Info/qlim"])) + end +end + +# The Δ′ family with "m/n" labels for the surfaces it spans: Galerkin solves across its in-band subset of the rational surfaces. +# `coil` is (edge harmonic × surface side), empty when the run computed no coil response. +function _delta_prime_data(r::ForceFreeStatesResult) + dp = r.delta_prime + k = size(dp.matrix, 1) + g = r.galerkin + m, n = g !== nothing && length(g.sing_m) == k ? (g.sing_m, g.sing_n) : ([s.m[1] for s in r.surfaces[1:k]], [s.n[1] for s in r.surfaces[1:k]]) + return (; matrix=dp.matrix, raw=dp.raw, coil=permutedims(dp.coil), labels=["$mi/$ni" for (mi, ni) in zip(m, n)], mlow=r.mlow) +end + +function _delta_prime_data(h5path::AbstractString) + h5open(h5path, "r") do fid + matrix = read(fid["SingularSurfaces/Delta_prime_matrix"]) + k = size(matrix, 1) + group = haskey(fid, "$_GAL/rational_m") && length(fid["$_GAL/rational_m"]) == k ? _GAL : "SingularSurfaces" + m, n = vec(read(fid["$group/rational_m"]))[1:k], vec(read(fid["$group/rational_n"]))[1:k] + coil = haskey(fid, "SingularSurfaces/Delta_coil") ? read(fid["SingularSurfaces/Delta_coil"]) : zeros(ComplexF64, 0, 2k) + (; matrix, raw=read(fid["SingularSurfaces/Delta_prime_raw"]), coil, labels=["$mi/$ni" for (mi, ni) in zip(m, n)], mlow=read(fid["Info/mlow"])) + end +end + +_ballooning_data(r::LocalStabilityResult) = (; psi=r.ballooning_psi, alpha=r.alpha, alpha_crit=r.alpha_critical) +_ballooning_data(h5path::AbstractString) = + h5open(fid -> (; psi=read(fid["LocalStability/ballooning_psi"]), alpha=read(fid["LocalStability/alpha"]), alpha_crit=read(fid["LocalStability/alpha_critical"])), h5path, "r") + +function _cond_fbar_data(r::ForceFreeStatesResult) + k = r.kinetic + return (; scan_psi=k.scan_psi, scan_cond=k.scan_cond, thr=k.scan_threshold, k_psi=[s.psifac for s in k.kinsing], kmsing=k.kmsing) +end + +function _cond_fbar_data(h5path::AbstractString) + h5open(h5path, "r") do fid + kg = fid["SingularSurfaces/Kinetic"] + (; scan_psi=read(kg["scan_psi"]), scan_cond=read(kg["scan_cond"]), thr=read(kg["scan_threshold"]), k_psi=read(kg["rational_psi"]), + kmsing=read(kg["rational_count"])) + end +end + +# Drawing functions, defined by the GPECMakieExt extension. +function _plot_mode_displacement end +function _plot_critical_determinant end +function _plot_energy_eigenvectors end +function _plot_eigenvalues end +function _plot_edge_stability_scan end +function _plot_delta_prime end +function _plot_delta_prime_raw end +function _plot_delta_coil end +function _plot_cond_fbar end +function _plot_ballooning_alpha_boundary end +function _plot_ballooning_alpha_boundaries end +function _plot_ffs_summary end + """ - plot_mode_displacement(src; modes=1:5, save_path=nothing) + plot_mode_displacement(src; modes=nothing, save_path=nothing) -Plot |ξ_ψ| vs ψ_N for the least stable eigenmode, showing one curve per requested -poloidal mode number m. The title includes the first eigenvalue dW = et[1]. +Plot |ξ^ψ_m| against ψ_N for the least stable free-boundary eigenmode, one curve per harmonic, with the rational +surfaces labelled along the top. ### Arguments @@ -55,49 +135,23 @@ poloidal mode number m. The title includes the first eigenvalue dW = et[1]. ### Keyword arguments - - `modes`: Iterable of m values to plot (default: `1:5`) - - `save_path`: If provided, save the figure to this path (default: `nothing`) + - `modes`: The m values to draw (default: `nothing`, the six largest) + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. """ -function plot_mode_displacement(src::Source; modes=1:5, save_path=nothing) +function plot_mode_displacement(src::Source; modes=nothing, save_path=nothing) _require(plot_mode_displacement, src) - p = _plot_mode_displacement(_mode_displacement_data(src); modes) - isnothing(save_path) || savefig(p, save_path) - return p -end - -function _plot_mode_displacement(d; modes=1:5) - mpert = size(d.xi, 1) - mhigh = d.mlow + mpert - 1 - title_str = isnothing(d.dW) ? "Least stable mode" : - "Least stable mode, δW = $(round(real(d.dW), sigdigits=4))" - - p = plot(; - xlims=(0, 1), - xlabel="Norm. Poloidal Flux", - ylabel="|ξ^ψ|", - title=title_str, - left_margin=10Plots.mm, - bottom_margin=5Plots.mm - ) - for m in modes - d.mlow <= m <= mhigh || continue - plot!(p, d.psi, abs.(d.xi[m-d.mlow+1, :]); label="m=$m") - end - return p + return _draw(_plot_mode_displacement, _mode_displacement_data(src), _rationals(src); modes, save_path) end -_criterion_data(r::ForceFreeStatesResult) = (; psi=r.diagnostics.psi_store, crit=r.diagnostics.crit_store) -_criterion_data(h5path::AbstractString) = h5open(fid -> (; psi=read(fid["$_FWD/psi"]), crit=read(fid["$_FWD/crit"])), h5path, "r") - """ - plot_fixed_boundary_stability_criterion(src; save_path=nothing) + plot_critical_determinant(src; save_path=nothing) -Plot the critical determinant (smallest eigenvalue of W⁻¹, `crit`) vs ψ_N. -A sign change in `crit` during integration indicates an ideal fixed-boundary instability. +Plot the critical determinant against ψ_N on a signed pseudo-log axis, titled with its zero crossings (none means +fixed-boundary stable). ### Arguments @@ -105,47 +159,22 @@ A sign change in `crit` during integration indicates an ideal fixed-boundary ins ### Keyword arguments - - `save_path`: If provided, save the figure to this path (default: `nothing`) + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. """ -function plot_fixed_boundary_stability_criterion(src::Source; save_path=nothing) - _require(plot_fixed_boundary_stability_criterion, src) - p = _plot_fixed_boundary_stability_criterion(_criterion_data(src)) - isnothing(save_path) || savefig(p, save_path) - return p -end - -function _plot_fixed_boundary_stability_criterion(d) - p = plot( - d.psi, d.crit; - xlims=(0, 1), - xlabel="Norm. Poloidal Flux", - ylabel="|Dᶜ|", - title="Fixed-Boundary Stability", - legend=false, - left_margin=10Plots.mm, - bottom_margin=5Plots.mm - ) - hline!(p, [0.0]; linestyle=:dash, color=:black, label=nothing) - return p +function plot_critical_determinant(src::Source; save_path=nothing) + _require(plot_critical_determinant, src) + return _draw(_plot_critical_determinant, _criterion_data(src), _rationals(src); save_path) end -_energy_eigenvector_data(r::ForceFreeStatesResult) = (; wt=r.free_boundary.wt, psio=r.equil.psio, mlow=r.mlow) -_energy_eigenvector_data(h5path::AbstractString) = - h5open(fid -> (; wt=read(fid["$_FBS/W_freeboundary_eigenmodes"]), psio=read(fid["Equilibrium/psi_total"]), mlow=read(fid["Info/mlow"])), h5path, "r") - """ - plot_energy_eigenvectors(src; matrix_type=:total, save_path=nothing) - -Heatmap of energy eigenvector magnitudes vs (m, mode index). - -Only `matrix_type=:total` is supported (the total energy eigenvector matrix `Wₜ`). Plasma and vacuum eigenvectors -are not stored separately. + plot_energy_eigenvectors(src; nmodes=nothing, save_path=nothing) -Eigenvectors are scaled by χ₁ = 2π ψ₀ × 10⁻³ to match GPEC conventions. +Plot |v_m| of the free-boundary energy eigenvectors (unit power norm) against poloidal harmonic m, least stable at +the top. ### Arguments @@ -153,73 +182,50 @@ Eigenvectors are scaled by χ₁ = 2π ψ₀ × 10⁻³ to match GPEC convention ### Keyword arguments - - `matrix_type`: Energy matrix to plot; only `:total` is currently supported - - `save_path`: If provided, save the figure to this path (default: `nothing`) + - `nmodes`: Number of least stable eigenmodes drawn (default: all) + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. """ -function plot_energy_eigenvectors(src::Source; matrix_type=:total, save_path=nothing) - matrix_type == :total || - throw(ArgumentError("matrix_type=$matrix_type not supported; only :total has an eigenvector matrix (ep/ev are eigenvalue vectors, not matrices)")) +function plot_energy_eigenvectors(src::Source; nmodes=nothing, save_path=nothing) _require(plot_energy_eigenvectors, src) - p = _plot_energy_eigenvectors(_energy_eigenvector_data(src)) - isnothing(save_path) || savefig(p, save_path) - return p + return _draw(_plot_energy_eigenvectors, _energy_eigenvector_data(src); nmodes, save_path) end -function _plot_energy_eigenvectors(d) - chi1 = 2π * d.psio - wt_scaled = d.wt * (chi1 * 1e-3) - - nmn = size(wt_scaled, 1) - nmodes = size(wt_scaled, 2) - m_vals = (0:(nmn-1)) .+ d.mlow - - return heatmap( - m_vals, 1:nmodes, abs.(wt_scaled'); - xlabel="Poloidal Harmonic", - ylabel="Eigenmode Index", - title="Total Energy Eigenvectors", - colorbar_title="Harmonic Amplitude", - left_margin=10Plots.mm, - right_margin=20Plots.mm, - bottom_margin=5Plots.mm - ) -end +const _ENERGIES = (:total, :plasma, :vacuum) -function _edge_scan_data(r::ForceFreeStatesResult) - es = r.diagnostics.edge_scan - return (; q=es.q, et=es.total_eigenvalue, ep=es.plasma_energy, ev=es.vacuum_energy, evonly=es.vacuum_eigenvalue, qlim=r.qlim, label=_label(r)) -end +""" + plot_eigenvalues(src; matrix_type=:all, save_path=nothing) -function _edge_scan_data(h5path::AbstractString) - h5open(h5path, "r") do fid - (; q=read(fid["ForceFreeStates/EdgeScan/q"]), et=read(fid["ForceFreeStates/EdgeScan/total_energy"]), - ep=read(fid["ForceFreeStates/EdgeScan/plasma_energy"]), ev=read(fid["ForceFreeStates/EdgeScan/vacuum_energy"]), - evonly=read(fid["ForceFreeStates/EdgeScan/vacuum_eigenvalue"]), qlim=read(fid["Info/qlim"]), label=_label(h5path)) - end -end +Plot the free-boundary energy eigenvalues (the power-normalized pencil W·v = λN·v), most unstable first: the total +δW and its plasma and vacuum parts, on a signed pseudo-log axis with the unstable sign shaded. -""" - plot_edge_stability_scan(src; save_path=nothing, ylims=(-2, 3), kwargs...) +### Arguments -Plot the edge stability scan energy components (et, ep, ev, evonly) vs ψ_N. + - `src`: A `ForceFreeStatesResult` with free-boundary energies (`vac_flag = true`), or the path of a `gpec.h5` it was saved to + +### Keyword arguments -The edge scan evaluates `δW_total = δW_plasma + δW_vacuum` at each stored integration step -in the region [dW_edge_scan_start, psilim], with the plasma boundary swept from dW_edge_scan_start to psilim. -A positive et indicates stability; the truncation point is chosen at the peak et. + - `matrix_type`: `:all` (default), or one of `:total`, `:plasma`, `:vacuum` + - `save_path`: If given, save the figure to this path (default: `nothing`) + +### Returns -Four subplots are shown: +A Makie `Figure`. +""" +function plot_eigenvalues(src::Source; matrix_type=:all, save_path=nothing) + matrix_type === :all || matrix_type in _ENERGIES || throw(ArgumentError("matrix_type must be :all, :total, :plasma or :vacuum")) + _require(plot_eigenvalues, src) + return _draw(_plot_eigenvalues, _energy_data(src); which=matrix_type === :all ? _ENERGIES : (matrix_type,), save_path) +end - - **Total energy** `et = ep + ev`: total free-boundary energy eigenvalue - - **Plasma energy** `ep`: plasma contribution to δW - - **Vacuum energy** `ev`: vacuum (wv) contribution with singfac scaling - - **Vacuum-only eigenvalue** `evonly`: smallest eigenvalue of wv alone (no plasma response) +""" + plot_edge_stability_scan(src; save_path=nothing) -A horizontal dashed line at zero marks the stability boundary. A vertical dashed line marks -`psilim` (the final truncation psi, where the peak et was found). +Plot the total, plasma and vacuum free-boundary energies against the edge truncation q, with the chosen truncation +marked. ### Arguments @@ -227,128 +233,50 @@ A horizontal dashed line at zero marks the stability boundary. A vertical dashed ### Keyword arguments - - `save_path`: If provided, save the figure to this path (default: `nothing`) - - `ylims`: y-axis limits applied to all panels (default: `(-2, 3)`) - - `kwargs...`: Additional Plots.jl keyword arguments applied to all line plots (e.g. `lw=2`) + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. """ -function plot_edge_stability_scan(src::Source; save_path=nothing, ylims=(-2, 3), kwargs...) +function plot_edge_stability_scan(src::Source; save_path=nothing) _require(plot_edge_stability_scan, src) - d = _edge_scan_data(src) - q, qlim = d.q, d.qlim - - kw_re = (xlabel="q", label="Re", ylims=ylims, kwargs...) - kw_im = (xlabel="q", label="Im", ls=:dash, ylims=ylims, kwargs...) - vl_kw = (color=:gray, lw=1, ls=:dot, label=false) - hl_kw = (color=:black, lw=1, ls=:dash, label=false) - - p_et = plot(q, real.(d.et); ylabel="Total Energy", title="Edge Stability Scan: δW vs q", kw_re...) - plot!(p_et, q, imag.(d.et); kw_im...) - hline!(p_et, [0.0]; hl_kw...) - vline!(p_et, [qlim]; vl_kw...) - - p_ep = plot(q, real.(d.ep); ylabel="Plasma Energy", kw_re...) - plot!(p_ep, q, imag.(d.ep); kw_im...) - hline!(p_ep, [0.0]; hl_kw...) - vline!(p_ep, [qlim]; vl_kw...) - - p_ev = plot(q, real.(d.ev); ylabel="Vacuum Energy", kw_re...) - plot!(p_ev, q, imag.(d.ev); kw_im...) - hline!(p_ev, [0.0]; hl_kw...) - vline!(p_ev, [qlim]; vl_kw...) - - p_evonly = plot(q, d.evonly; ylabel="Min Vac. Eigenvalue", legend=false, xlabel="q", ylims=ylims, kwargs...) - hline!(p_evonly, [0.0]; hl_kw...) - vline!(p_evonly, [qlim]; vl_kw...) - - p = plot(p_et, p_ep, p_ev, p_evonly; - layout=(4, 1), - size=(900, 1100), - plot_title="Edge stability scan: $(d.label)", - left_margin=12Plots.mm, - bottom_margin=4Plots.mm) - - isnothing(save_path) || savefig(p, save_path) - return p + return _draw(_plot_edge_stability_scan, _edge_scan_data(src); save_path) end -const _ENERGY_FIELDS = Dict(:total => :et, :plasma => :ep, :vacuum => :ev) -const _ENERGY_DATASETS = Dict(:total => "eigenmode_energies", :plasma => "eigenmode_plasma_energies", :vacuum => "eigenmode_vacuum_energies") - -_eigenvalue_data(r::ForceFreeStatesResult, matrix_type) = getfield(r.free_boundary, _ENERGY_FIELDS[matrix_type]) -_eigenvalue_data(h5path::AbstractString, matrix_type) = h5open(fid -> read(fid["$_FBS/$(_ENERGY_DATASETS[matrix_type])"]), h5path, "r") +const _PARTS = (:real, :imag, :abs) +_check_part(part) = part in _PARTS || throw(ArgumentError("part must be :real, :imag or :abs")) """ - plot_eigenvalues(src; matrix_type=:total, save_path=nothing) + plot_delta_prime(src; part=:real, save_path=nothing) -Scatter plot of energy eigenvalues vs mode index. Points are colored red (unstable, Re < 0) -or blue (stable, Re > 0), with a dashed reference line at zero. +Plot the inter-surface Δ′ matrix (PEST-3 convention, ψ_N-referenced) as a heatmap with each entry written in, +rows and columns labelled by rational surface. ### Arguments - - `src`: A `ForceFreeStatesResult` with free-boundary energies (`vac_flag = true`), or the path of a `gpec.h5` it was saved to + - `src`: A Riccati or Galerkin `ForceFreeStatesResult`, or the path of a `gpec.h5` it was saved to ### Keyword arguments - - `matrix_type`: Which eigenvalues to plot: `:total` (`et`), `:plasma` (`ep`), or `:vacuum` (`ev`) - - `save_path`: If provided, save the figure to this path (default: `nothing`) + - `part`: `:real` (default, diverging symmetric-log colours), `:imag`, or `:abs` (log colours) + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. """ -function plot_eigenvalues(src::Source; matrix_type=:total, save_path=nothing) - haskey(_ENERGY_FIELDS, matrix_type) || throw(ArgumentError("matrix_type must be :total, :plasma, or :vacuum")) - _require(plot_eigenvalues, src) - et = _eigenvalue_data(src, matrix_type) - - nmodes = length(et) - ev_real = real.(et) - colors = [v < 0 ? :red : :blue for v in ev_real] # red = negative (unstable), blue = positive (stable) - - p = scatter( - 1:nmodes, ev_real; - xlabel="mode index", - ylabel="Re(eigenvalue)", - title="Eigenvalue spectrum ($matrix_type)", - legend=false, - color=colors, - markerstrokewidth=0, - left_margin=10Plots.mm, - bottom_margin=5Plots.mm - ) - hline!(p, [0]; linestyle=:dash, color=:black, label=nothing) - - isnothing(save_path) || savefig(p, save_path) - return p -end - -# Δ′_ii on the surfaces the Δ′ matrix spans: Galerkin solves across its in-band subset of the rational surfaces. -function _delta_prime_data(r::ForceFreeStatesResult) - dp = r.delta_prime.matrix - k = minimum(size(dp)) - g = r.galerkin - psi, q = g !== nothing && length(g.sing_psi) == k ? (g.sing_psi, g.sing_q) : ([s.psifac for s in r.surfaces[1:k]], [s.q for s in r.surfaces[1:k]]) - return (; psi, q, delta_prime=[dp[i, i] for i in 1:k]) -end - -function _delta_prime_data(h5path::AbstractString) - h5open(h5path, "r") do fid - dp = read(fid["SingularSurfaces/Delta_prime_matrix"]) - k = minimum(size(dp)) - group = haskey(fid, "$_GAL/rational_psi") && length(fid["$_GAL/rational_psi"]) == k ? _GAL : "SingularSurfaces" - (; psi=read(fid["$group/rational_psi"])[1:k], q=read(fid["$group/rational_q"])[1:k], delta_prime=[dp[i, i] for i in 1:k]) - end +function plot_delta_prime(src::Source; part=:real, save_path=nothing) + _check_part(part) + _require(plot_delta_prime, src) + return _draw(_plot_delta_prime, _delta_prime_data(src); part, save_path) end """ - plot_delta_prime(src; save_path=nothing) + plot_delta_prime_raw(src; part=:real, save_path=nothing) -Scatter plot of the diagonal `Re(Δ′_ii)` of the inter-surface Δ′ matrix (PEST-3 convention, ψ_N-referenced) vs ψ_N -of each rational surface it spans. Points are colored red (tearing unstable, Re(Δ′) > 0) or blue (tearing stable). +Plot the raw outer-region matching matrix D′ as a heatmap, with the left and right sides of each rational surface +grouped under it on both axes. ### Arguments @@ -356,122 +284,49 @@ of each rational surface it spans. Points are colored red (tearing unstable, Re( ### Keyword arguments - - `save_path`: If provided, save the figure to this path (default: `nothing`) + - `part`: `:real` (default, diverging symmetric-log colours), `:imag`, or `:abs` (log colours) + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. """ -function plot_delta_prime(src::Source; save_path=nothing) - _require(plot_delta_prime, src) - p = _plot_delta_prime(_delta_prime_data(src)) - isnothing(save_path) || savefig(p, save_path) - return p -end - -function _plot_delta_prime(d) - dp_real = real.(d.delta_prime) - colors = [v > 0 ? :red : :steelblue for v in dp_real] - - p = scatter( - d.psi, dp_real; - xlims=(0, 1), - xlabel="Norm. Poloidal Flux", - ylabel="Re(Δ'_ii)", - title="Tearing stability Δ'", - legend=false, - color=colors, - markersize=7, - markerstrokewidth=0, - left_margin=10Plots.mm, - bottom_margin=5Plots.mm - ) - hline!(p, [0.0]; linestyle=:dash, color=:black, label=nothing) - return p +function plot_delta_prime_raw(src::Source; part=:real, save_path=nothing) + _check_part(part) + _require(plot_delta_prime_raw, src) + return _draw(_plot_delta_prime_raw, _delta_prime_data(src); part, save_path) end -_ballooning_data(r::LocalStabilityResult) = (; psi=r.ballooning_psi, alpha=r.alpha, alpha_crit=r.alpha_critical) -_ballooning_data(h5path::AbstractString) = - h5open(fid -> (; psi=read(fid["LocalStability/ballooning_psi"]), alpha=read(fid["LocalStability/alpha"]), alpha_crit=read(fid["LocalStability/alpha_critical"])), h5path, "r") - """ - plot_ballooning_alpha_boundary(src; save_path=nothing, psi_min=0.0) + plot_delta_coil(src; part=:real, save_path=nothing) -Plot the BALOO-style infinite-n ballooning stability diagram: the experimental -pressure gradient α (solid) and the first stability boundary α_crit (dashed) versus -normalized poloidal flux ψ_N. Surfaces where the experimental α lies above the boundary -are ballooning-unstable. +Plot Δ_coil, the response at each side of each rational surface to a unit source on each edge poloidal harmonic, +as a heatmap. ### Arguments - - `src`: A `LocalStabilityResult`, or the path of a `gpec.h5` written with a `[LocalStability]` section + - `src`: A Riccati `ForceFreeStatesResult` or a Galerkin one solved with `rpec_flag = true`, or the path of a `gpec.h5` it was saved to ### Keyword arguments - - `save_path`: If provided, save the figure to this path (default: `nothing`) - - `psi_min`: Lower ψ_N axis limit; set near the edge (e.g. `0.9`) to focus on the - pedestal (default: `0.0`) + - `part`: `:real` (default, diverging symmetric-log colours), `:imag`, or `:abs` (log colours) + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. """ -function plot_ballooning_alpha_boundary(src::Union{AbstractString,LocalStabilityResult}; save_path=nothing, psi_min=0.0) - _require(plot_ballooning_alpha_boundary, src) - d = _ballooning_data(src) - - p = plot(; - xlims=(psi_min, 1), - xlabel=L"\psi_N", - ylabel=L"\alpha", - title="Infinite-n ballooning stability", - framestyle=:box, - legend=:topleft, - left_margin=10Plots.mm, - bottom_margin=5Plots.mm - ) - - # NaN entries leave natural gaps over always-stable surfaces; isfinite masking would - # wrongly connect across such gaps with a straight line. - plot!(p, d.psi, d.alpha; lw=2, color=:black, label="Experimental gradient") - plot!(p, d.psi, d.alpha_crit; lw=2, linestyle=:dash, color=:red, label="1st stability boundary") - - isnothing(save_path) || savefig(p, save_path) - return p -end - -function _cond_fbar_data(r::ForceFreeStatesResult) - k = r.kinetic - return (; scan_psi=k.scan_psi, scan_cond=k.scan_cond, thr=k.scan_threshold, k_psi=[s.psifac for s in k.kinsing], - i_psi=[s.psifac for s in r.surfaces], i_q=[s.q for s in r.surfaces], kmsing=k.kmsing) -end - -function _cond_fbar_data(h5path::AbstractString) - h5open(h5path, "r") do fid - kg = fid["SingularSurfaces/Kinetic"] - (; scan_psi=read(kg["scan_psi"]), scan_cond=read(kg["scan_cond"]), thr=read(kg["scan_threshold"]), k_psi=read(kg["rational_psi"]), - i_psi=read(fid["SingularSurfaces/rational_psi"]), i_q=read(fid["SingularSurfaces/rational_q"]), kmsing=read(kg["rational_count"])) - end +function plot_delta_coil(src::Source; part=:real, save_path=nothing) + _check_part(part) + _require(plot_delta_coil, src) + return _draw(_plot_delta_coil, _delta_prime_data(src); part, save_path) end """ - plot_cond_fbar(src; save_path=nothing, zoom=false) - -Plot `cond(F̄)` vs ψ from the kinetic-singular-surface scan (populated when ForceFreeStates runs with -a `kinetic` source and `singfac_min > 0`). + plot_cond_fbar(src; save_path=nothing) -`F̄` is the kinetic Euler-Lagrange matrix formed by Schur-reducing the six -kinetic matrices against the ideal A/B/C/D/E/H blocks (Logan 2015 Appendix -C). Peaks in `cond(F̄)` locate "kinetically-displaced" singular surfaces — -roots of `det(F̄)` that are not at ideal rational surfaces. When a peak -exceeds the threshold stored in `scan_threshold` the ODE integrator stops -there and steps across trapezoidally, mirroring Fortran `ode_kin_cross`. - -The plot overlays the ideal rational surfaces (dotted grey, labelled with -their q value) and any accepted kinetic singular surfaces (solid crimson). -If no peak exceeds the threshold, `kmsing = 0` and the kinetic ODE runs as -a single chunk. This diagnostic is useful for anyone asking *where* the -kinetic resonances land relative to the ideal ones. +Plot cond(F̄) against ψ_N from the kinetic singular-surface scan, with the threshold, the rational surfaces and any +accepted kinetic singular surfaces marked. ### Arguments @@ -479,236 +334,110 @@ kinetic resonances land relative to the ideal ones. ### Keyword arguments - - `save_path`: If provided, save the figure to this path (default: `nothing`) - - `zoom`: If `true`, auto-scale the y-axis to the scan data (threshold shown - as annotation only); if `false` (default), always include the threshold - line in the y-range + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. """ -function plot_cond_fbar(src::Source; save_path=nothing, zoom=false) +function plot_cond_fbar(src::Source; save_path=nothing) _require(plot_cond_fbar, src) - (; scan_psi, scan_cond, thr, k_psi, i_psi, i_q, kmsing) = _cond_fbar_data(src) - - finite_cond = filter(isfinite, scan_cond) - data_max = isempty(finite_cond) ? 1.0 : maximum(finite_cond) - data_min = isempty(finite_cond) ? 1.0 : max(minimum(finite_cond), 1e-3) - - p = plot(scan_psi, scan_cond; - yscale=:log10, - lw=2, - color=:steelblue, - label="cond(F̄)", - xlabel="Norm. Poloidal Flux", - ylabel="cond(F̄)", - title="Kinetic F̄ condition number (kmsing = $kmsing)", - legend=:topleft, - left_margin=12Plots.mm, - bottom_margin=4Plots.mm, - size=(900, 500) - ) - - if zoom - # Auto-scale y to data and annotate the threshold at the top edge if off-scale. - ylims!(p, (data_min / 2, data_max * 3)) - if thr > 0 && thr > data_max * 3 - annotate!(p, [(scan_psi[end], data_max * 2.5, - text(@sprintf("threshold = %.0e (off-scale)", thr), :right, 9, :red))]) - elseif thr > 0 - hline!(p, [thr]; color=:red, linestyle=:dash, lw=1.5, - label=@sprintf("threshold = %.0e", thr)) - end - elseif thr > 0 - hline!(p, [thr]; color=:red, linestyle=:dash, lw=1.5, - label=@sprintf("threshold = %.0e", thr)) - end + return _draw(_plot_cond_fbar, _cond_fbar_data(src), _rationals(src); save_path) +end - if !isempty(i_psi) - vline!(p, i_psi; color=:gray, linestyle=:dot, lw=1, - label="ideal rational (q = m/n)") - y_label = zoom ? data_max * 1.2 : max(data_max * 1.2, sqrt(data_max * (thr > 0 ? thr : data_max))) - for (idx, ps) in enumerate(i_psi) - annotate!(p, [(ps, y_label, - text(@sprintf("q=%.0f", i_q[idx]), :right, 8, :gray))]) - end - end +""" + plot_ballooning_alpha_boundary(src; psi_min=0.0, save_path=nothing) - if !isempty(k_psi) - vline!(p, k_psi; color=:crimson, lw=1.5, label="accepted kinsing") - end +Plot the infinite-n ballooning diagram: the equilibrium pressure gradient α and its first stability boundary against +ψ_N, with the unstable surfaces shaded. - isnothing(save_path) || savefig(p, save_path) - return p -end +### Arguments -# The summary's panels in drawing order, each with the plot whose capabilities it needs. -_ffs_panels(src) = ( - plot_energy_eigenvectors => () -> _plot_energy_eigenvectors(_energy_eigenvector_data(src)), - plot_fixed_boundary_stability_criterion => () -> _plot_fixed_boundary_stability_criterion(_criterion_data(src)), - plot_mode_displacement => () -> _plot_mode_displacement(_mode_displacement_data(src)), - plot_delta_prime => () -> _plot_delta_prime(_delta_prime_data(src)) -) + - `src`: A `LocalStabilityResult`, or the path of a `gpec.h5` written with a `[LocalStability]` section -""" - plot_ffs_summary(src; save_path=nothing) +### Keyword arguments + + - `psi_min`: Lower ψ_N limit, e.g. `0.9` for the pedestal (default: `0.0`) + - `save_path`: If given, save the figure to this path (default: `nothing`) + +### Returns -Summary of ForceFreeStates (DCON-style) stability results, with each panel the source can supply: +A Makie `Figure`. +""" +function plot_ballooning_alpha_boundary(src::Union{AbstractString,LocalStabilityResult}; psi_min=0.0, save_path=nothing) + _require(plot_ballooning_alpha_boundary, src) + return _draw(_plot_ballooning_alpha_boundary, _ballooning_data(src); psi_min, save_path) +end - - Energy eigenvector heatmap (`plot_energy_eigenvectors`) - - Critical determinant |D_c| vs ψ_N (`plot_fixed_boundary_stability_criterion`) - - Least stable mode displacement (`plot_mode_displacement`) - - Tearing stability Δ' at each rational surface (`plot_delta_prime`) +""" + plot_ballooning_alpha_boundaries(bnd; psi_min=0.0, save_path=nothing) + plot_ballooning_alpha_boundaries(bnd, dpmap; psi_min=0.0, save_path=nothing) -Panels the source cannot draw are left out; it throws an `ArgumentError` only when none can be drawn. +Plot the infinite-n ballooning diagram with the first and second stability boundaries; with `dpmap`, over a map of +Δ′ signed so that positive is stable on every surface, with its zero contour. ### Arguments - - `src`: A `ForceFreeStatesResult`, or the path of a `gpec.h5` it was saved to + - `bnd`: The named tuple `LocalStability.ballooning_alpha_boundaries` returns + - `dpmap`: The named tuple `LocalStability.ballooning_delta_prime_map` returns ### Keyword arguments - - `save_path`: If provided, save the figure to this path (default: `nothing`) + - `psi_min`: Lower ψ_N limit (default: `0.0`) + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. """ -function plot_ffs_summary(src::Source; save_path=nothing) - p = _summary(_ffs_panels(src), capabilities(src), plot_ffs_summary) - isnothing(save_path) || savefig(p, save_path) - return p -end - -# Draw the panels `caps` allows, two per row; throw naming every reason when none can be drawn. -function _summary(panels, caps, fn) - drawn = [draw() for (panel_fn, draw) in panels if _can(panel_fn, caps)] - if isempty(drawn) - missing = unique(reduce(vcat, (_missing(_entry(panel_fn), caps) for (panel_fn, _) in panels))) - throw(ArgumentError("$(nameof(fn)): no panel can be drawn: $(_why(missing))")) - end - n = length(drawn) - n <= 2 && return plot(drawn...; layout=(1, n), size=(550n, 500), right_margin=10Plots.mm) - n == 3 && push!(drawn, plot(; framestyle=:none)) - return plot(drawn...; layout=(2, 2), size=(1100, 900), right_margin=10Plots.mm) -end +plot_ballooning_alpha_boundaries(bnd; psi_min=0.0, save_path=nothing) = _draw(_plot_ballooning_alpha_boundaries, bnd, nothing; psi_min, save_path) +plot_ballooning_alpha_boundaries(bnd, dpmap; psi_min=0.0, save_path=nothing) = _draw(_plot_ballooning_alpha_boundaries, bnd, dpmap; psi_min, save_path) -register!(plot_ffs_summary, (ForceFreeStatesResult, AbstractString), (), "the stability panels the run supplies") -register!(plot_mode_displacement, (ForceFreeStatesResult, AbstractString), (:solution_profiles,), "|ξ_ψ| per m of the least stable mode") -register!(plot_fixed_boundary_stability_criterion, (ForceFreeStatesResult, AbstractString), (:fixed_boundary_criterion,), "critical determinant against ψ_N") -register!(plot_energy_eigenvectors, (ForceFreeStatesResult, AbstractString), (:free_boundary,), "free-boundary energy eigenvectors per m") -register!(plot_eigenvalues, (ForceFreeStatesResult, AbstractString), (:free_boundary,), "free-boundary energy eigenvalues") -register!(plot_edge_stability_scan, (ForceFreeStatesResult, AbstractString), (:edge_scan,), "edge energies against the truncation q") -register!(plot_delta_prime, (ForceFreeStatesResult, AbstractString), (:delta_prime,), "Δ′_ii at each rational surface") -register!(plot_cond_fbar, (ForceFreeStatesResult, AbstractString), (:kinetic_scan,), "cond(F̄) scan for kinetic singular surfaces") -register!(plot_ballooning_alpha_boundary, (LocalStabilityResult, AbstractString), (:ballooning_scan,), "ballooning α against its first stability boundary") +# The summary's panels in drawing order, each with the plot whose capabilities it needs and the data it draws. +_ffs_panels(src) = ( + plot_critical_determinant => () -> (:critical_determinant, _criterion_data(src), _rationals(src)), + plot_eigenvalues => () -> (:energies, _energy_data(src)), + plot_mode_displacement => () -> (:mode_displacement, _mode_displacement_data(src), _rationals(src)), + plot_delta_prime => () -> (:delta_prime, _delta_prime_data(src)), + plot_energy_eigenvectors => () -> (:eigenvectors, _energy_eigenvector_data(src)) +) """ - plot_ballooning_alpha_boundaries(bnd; save_path=nothing, psi_min=0.0) + plot_ffs_summary(src; save_path=nothing) -Plot the BALOO-style infinite-n ballooning stability diagram with first and second stability -boundaries: experimental α (solid black), 1st stability boundary α_crit1 (dashed red), and -2nd stability boundary α_crit2 (dashed blue) versus ψ_N. `NaN` entries in the boundary -arrays leave natural gaps over always-stable surfaces without requiring explicit masking. +Plot the stability panels the source supplies, two per row: the critical determinant, the free-boundary energies, +the least stable mode's displacement, Δ′_ii at each rational surface, and the energy eigenvectors. ### Arguments - - `bnd`: NamedTuple with fields `psi`, `alpha`, `alpha_critical1`, `alpha_critical2` - (as returned by `LocalStability.ballooning_alpha_boundaries`) + - `src`: A `ForceFreeStatesResult`, or the path of a `gpec.h5` it was saved to ### Keyword arguments - - `save_path`: If provided, save the figure to this path (default: `nothing`) - - `psi_min`: Lower ψ_N axis limit (default: `0.0`) + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. Throws an `ArgumentError` naming every missing capability when no panel can be drawn. """ -function plot_ballooning_alpha_boundaries(bnd; save_path=nothing, psi_min=0.0) - p = plot(; - xlims=(psi_min, 1), - xlabel=L"\psi_N", - ylabel=L"\alpha", - title="Infinite-n ballooning stability", - framestyle=:box, - legend=:topleft, - left_margin=10Plots.mm, - bottom_margin=5Plots.mm - ) - plot!(p, bnd.psi, bnd.alpha; lw=2, color=:black, label="Experimental gradient") - plot!(p, bnd.psi, bnd.alpha_critical1; lw=2, color=:red, marker=:circle, label="1st stability boundary") - plot!(p, bnd.psi, bnd.alpha_critical2; lw=2, color=:blue, marker=:circle, label="2nd stability boundary") - isnothing(save_path) || savefig(p, save_path) - return p +function plot_ffs_summary(src::Source; save_path=nothing) + _require(plot_ffs_summary, src) + caps = capabilities(src) + panels = [data() for (panel_fn, data) in _ffs_panels(src) if _can(panel_fn, caps)] + return _draw(_plot_ffs_summary, panels; save_path) end -""" - plot_ballooning_alpha_boundaries(bnd, dpmap; save_path=nothing, psi_min=0.0) - -Same diagram drawn over a heatmap of the signed Δ' from -`LocalStability.ballooning_delta_prime_map`: each surface's Δ'(α) is oriented by the -sign of its α=0 (stable) value so that positive is stable everywhere, regridded from -its native physical α = α_ref*scale onto a shared uniform α axis, and shown with the -Δ'=0 contour, the extracted boundaries, and the scan cap `max_alpha_scale*α_exp`. -Color limits are set to the 90th percentile of |Δ'| so the pole regions inside the -unstable band do not wash out the marginal structure. -""" -function plot_ballooning_alpha_boundaries(bnd, dpmap; save_path=nothing, psi_min=0.0) - psi = dpmap.psi - scales = dpmap.alpha_scales - alpha_ref = dpmap.alpha_ref - signed = dpmap.delta_prime .* sign.(dpmap.delta_prime[:, 1]) - - finite_aref = filter(isfinite, alpha_ref) - isempty(finite_aref) && error("delta-prime map contains no valid surfaces") - alpha_axis = collect(range(0.0, maximum(finite_aref) * scales[end]; length=200)) - z = fill(NaN, length(alpha_axis), length(psi)) - for i in eachindex(psi) - (isfinite(alpha_ref[i]) && alpha_ref[i] > 0.0) || continue - a_surf = alpha_ref[i] .* scales - for (j, a) in enumerate(alpha_axis) - a > a_surf[end] && break - k = min(searchsortedlast(a_surf, a), length(a_surf) - 1) - k < 1 && continue - t = (a - a_surf[k]) / (a_surf[k+1] - a_surf[k]) - z[j, i] = (1 - t) * signed[i, k] + t * signed[i, k+1] - end - end - - # Color limits track the unstable-side magnitudes; deep stable values and pole - # regions clamp at the ends instead of washing out the marginal band. - finite_z = abs.(filter(isfinite, vec(z))) - finite_neg = abs.(filter(x -> isfinite(x) && x < 0.0, vec(z))) - lim = isempty(finite_neg) ? (isempty(finite_z) ? 1.0 : quantile(finite_z, 0.9)) : quantile(finite_neg, 0.95) - lim = max(lim, eps(Float64)) - - p = heatmap( - psi, - alpha_axis, - z; - c=cgrad(:RdBu), - clims=(-lim, lim), - # Leading newline offsets the rotated title clear of the colorbar tick labels (GR quirk) - colorbar_title="\n" * L"\Delta' \times \mathrm{sign}(\Delta'_{\alpha=0})", - xlims=(psi_min, 1), - xlabel=L"\psi_N", - ylabel=L"\alpha", - title="Infinite-n ballooning stability", - framestyle=:box, - legend=:topleft, - left_margin=10Plots.mm, - bottom_margin=5Plots.mm, - right_margin=15Plots.mm - ) - contour!(p, psi, alpha_axis, z; levels=[0.0], color=:black, linewidth=1, colorbar_entry=false) - plot!(p, bnd.psi, bnd.alpha; lw=2, color=:black, label="Experimental gradient") - plot!(p, bnd.psi, bnd.alpha_critical1; lw=2, color=:red, linestyle=:dash, label="1st stability boundary") - plot!(p, bnd.psi, bnd.alpha_critical2; lw=2, color=:blue, linestyle=:dash, label="2nd stability boundary") - plot!(p, psi, scales[end] .* alpha_ref; lw=2, color=:gray, linestyle=:dot, label="scan cap") - isnothing(save_path) || savefig(p, save_path) - return p -end +register!(plot_ffs_summary, (ForceFreeStatesResult, AbstractString), (), "the stability panels the run supplies"; + any_of=((:fixed_boundary_criterion,), (:free_boundary,), (:solution_profiles,), (:delta_prime,))) +register!(plot_critical_determinant, (ForceFreeStatesResult, AbstractString), (:fixed_boundary_criterion,), "critical determinant against ψ_N") +register!(plot_eigenvalues, (ForceFreeStatesResult, AbstractString), (:free_boundary,), "free-boundary energy eigenvalues") +register!(plot_mode_displacement, (ForceFreeStatesResult, AbstractString), (:solution_profiles,), "|ξ^ψ_m| of the least stable mode") +register!(plot_energy_eigenvectors, (ForceFreeStatesResult, AbstractString), (:free_boundary,), "free-boundary energy eigenvectors per m") +register!(plot_edge_stability_scan, (ForceFreeStatesResult, AbstractString), (:edge_scan,), "edge energies against the truncation q") +register!(plot_delta_prime, (ForceFreeStatesResult, AbstractString), (:delta_prime,), "Δ′ matrix between rational surfaces") +register!(plot_delta_prime_raw, (ForceFreeStatesResult, AbstractString), (:delta_prime,), "raw outer-region matching matrix D′") +register!(plot_delta_coil, (ForceFreeStatesResult, AbstractString), (:delta_coil,), "Δ_coil: surface response to each edge harmonic") +register!(plot_cond_fbar, (ForceFreeStatesResult, AbstractString), (:kinetic_scan,), "cond(F̄) scan for kinetic singular surfaces") +register!(plot_ballooning_alpha_boundary, (LocalStabilityResult, AbstractString), (:ballooning_scan,), "ballooning α against its first stability boundary") end # module ForceFreeStates diff --git a/src/Analysis/PerturbedEquilibrium.jl b/src/Analysis/PerturbedEquilibrium.jl index 76fd0aa85..6ceabfe16 100644 --- a/src/Analysis/PerturbedEquilibrium.jl +++ b/src/Analysis/PerturbedEquilibrium.jl @@ -8,39 +8,107 @@ naming what to change in the run when the source lacks the data it draws (see `A module PerturbedEquilibrium using HDF5 -using LaTeXStrings -using Plots -using ..Analysis: register!, _require, _can, capabilities, REASONS -import ...PerturbedEquilibrium: PerturbedEquilibriumState +using ..Analysis: register!, _require, _can, capabilities, REASONS, _draw +import ...PerturbedEquilibrium: PerturbedEquilibriumState, PlasmaResponse +import ..CoilForcing: plot_control_surface, _control_surface_data, _equil_helicity const Source = Union{AbstractString,PerturbedEquilibriumState} const _COUPLING = "PerturbedEquilibrium/SingularCoupling/" const _RESPONSE = "PerturbedEquilibrium/Response/" +# The penetrated field when the run carries a nonzero one (matched runs), otherwise `nothing`. +_penetrated(v) = isempty(v) || all(iszero, v) ? nothing : v + function _coupling_data(s::PerturbedEquilibriumState) r = s.response - return (; field=s.resonant_area_weighted_field, current=s.resonant_current, island_hw=s.island_half_width, chirikov=s.chirikov_parameter, - delta_prime=s.delta_prime, psi=r.rational_psi, q=r.rational_q, n=r.rational_n) + return (; field=s.resonant_area_weighted_field, penetrated=_penetrated(s.penetrated_area_weighted_field), current=s.resonant_current, island_hw=s.island_half_width, + chirikov=s.chirikov_parameter, + delta_prime=s.delta_prime, psi=r.rational_psi, q=r.rational_q, m=r.rational_m_res, n=r.rational_n) end function _coupling_data(h5path::AbstractString) h5open(h5path, "r") do fid rd(name) = read(fid[_COUPLING*name]) - (; field=rd("resonant_area_weighted_field"), current=rd("resonant_current"), island_hw=rd("island_half_width"), chirikov=rd("chirikov_parameter"), - delta_prime=rd("Delta_prime"), psi=rd("rational_psi"), q=rd("rational_q"), n=rd("rational_n")) + pen = haskey(fid, _COUPLING * "penetrated_area_weighted_field") ? _penetrated(rd("penetrated_area_weighted_field")) : nothing + (; field=rd("resonant_area_weighted_field"), penetrated=pen, current=rd("resonant_current"), island_hw=rd("island_half_width"), chirikov=rd("chirikov_parameter"), + delta_prime=rd("Delta_prime"), psi=rd("rational_psi"), q=rd("rational_q"), m=rd("rational_m"), n=rd("rational_n")) + end +end + +const _COMPONENTS = Dict( + :xi_psi => (dataset="xi_psi", field=:psi, modes=:xi_modes, needs=:response_xi), + :b_psi => (dataset="b_psi_area_weighted", field=:b_psi_area_weighted, modes=:b_modes, needs=:response_b), # area-normalized b^ψ + :Jb_theta => (dataset="Jb_theta", field=:theta, modes=:b_modes, needs=:response_b), + :Jb_zeta => (dataset="Jb_zeta", field=:zeta, modes=:b_modes, needs=:response_b) +) + +# A response component with q on the equilibrium's profile nodes and the rational surfaces the coupling resolved. +function _response_data(s::PerturbedEquilibriumState, component) + c = _COMPONENTS[component] + ffs = s.response.ffs + prof = ffs.equil.profiles + return (; data=getfield(getfield(s, c.modes), c.field), psi=s.psi_grid, mlow=ffs.mlow, mhigh=ffs.mhigh, nhigh=ffs.nhigh, + q_psi=prof.xs, q=prof.q_spline.y, rational_psi=s.response.rational_psi, rational_m=s.response.rational_m_res) +end + +function _response_data(h5path::AbstractString, component) + h5open(h5path, "r") do fid + coupled = haskey(fid, _COUPLING * "rational_psi") + (; data=read(fid[_RESPONSE*_COMPONENTS[component].dataset]), psi=read(fid[_RESPONSE*"psi"]), mlow=read(fid["Info/mlow"]), + mhigh=read(fid["Info/mhigh"]), nhigh=read(fid["Info/nhigh"]), q_psi=read(fid["Equilibrium/Profiles/psi"]), + q=read(fid["Equilibrium/Profiles/q"]), rational_psi=coupled ? read(fid[_COUPLING*"rational_psi"]) : Float64[], + rational_m=coupled ? read(fid[_COUPLING*"rational_m"]) : Int[]) end end +# Check `component` names a response field `src` holds; `_require` covers ξ, which every component's run also has. +function _require_component(fn, src, component) + haskey(_COMPONENTS, component) || throw(ArgumentError("component must be one of :xi_psi, :b_psi, :Jb_theta, :Jb_zeta")) + _require(fn, src) + need = _COMPONENTS[component].needs + need in capabilities(src) || throw(ArgumentError("$(nameof(fn)): $(REASONS[need])")) + return nothing +end + +# Drawing functions, defined by the GPECMakieExt extension. +function _plot_resonant_coupling end +function _plot_mode_spectrogram end +function _plot_perturbed_equilibrium_summary end + +const _COUPLING_ROWS = (:field, :current, :island_hw, :chirikov, :delta_prime) + """ - plot_resonant_area_weighted_field_amplitude(src; save_path=nothing) + plot_resonant_area_weighted_field(src; save_path=nothing) + +Plot the resonant coupling at each rational surface in rows sharing a q = m/n axis: the resonant area-weighted field +|b̄_res|, the resonant current |I_res|, the island half-width w/2, the effective island-overlap Chirikov parameter +and the forcing-driven Re Δ′. Toroidal mode numbers are distinguished by colour; the title counts the surfaces with +overlapping islands. + +### Arguments + + - `src`: A `PerturbedEquilibriumState`, or the path of a `gpec.h5` it was saved to + +### Keyword arguments + + - `save_path`: If given, save the figure to this path (default: `nothing`) + +### Returns + +A Makie `Figure`. +""" +function plot_resonant_area_weighted_field(src::Source; save_path=nothing) + _require(plot_resonant_area_weighted_field, src) + return _draw(_plot_resonant_coupling, _coupling_data(src), _COUPLING_ROWS; save_path) +end -Scatter plot of the resonant area-weighted field `|b^r|` per rational surface vs ψ_N. One marker series -per toroidal mode n. Integer-valued q rational surfaces are annotated. The resonant area-weighted field -`b^r = Φ^r/A^r` is the resonant flux normalized by the scalar surface area, in tesla [Pharr 2026]. +""" + plot_resonant_area_weighted_field_amplitude(src; save_path=nothing) -Requires the perturbed equilibrium singular coupling. +Plot the resonant area-weighted field |b̄_res| = |Φ_res|/A at each rational surface [Pharr 2026], in tesla, beside the +penetrated field |b̄_pen| when the run carries one. ### Arguments @@ -48,46 +116,21 @@ Requires the perturbed equilibrium singular coupling. ### Keyword arguments - - `save_path`: If provided, save the figure to this path (default: `nothing`) + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. """ function plot_resonant_area_weighted_field_amplitude(src::Source; save_path=nothing) _require(plot_resonant_area_weighted_field_amplitude, src) - p = _plot_resonant_area_weighted_field_amplitude(_coupling_data(src)) - isnothing(save_path) || savefig(p, save_path) - return p -end - -function _plot_resonant_area_weighted_field_amplitude(d) - resonant_area_weighted_field, rational_psi, rational_q, rational_n = d.field, d.psi, d.q, d.n - - p = plot(; xlabel="Norm. Poloidal Flux", ylabel="|b^r| [T]", - title="Resonant area-weighted field |b^r| per surface", legend=:outertopright, - left_margin=10Plots.mm, bottom_margin=5Plots.mm) - - for nn in unique(rational_n) - mask = rational_n .== nn - scatter!(p, rational_psi[mask], abs.(resonant_area_weighted_field[mask]); - label="n=$nn", markersize=7, markerstrokewidth=0) - for k in findall(mask) - abs(rational_q[k] - round(rational_q[k])) < 0.05 || continue - annotate!(p, rational_psi[k], abs(resonant_area_weighted_field[k]), - text(" q=$(round(Int, rational_q[k]))", 8, :left, :black)) - end - end - return p + return _draw(_plot_resonant_coupling, _coupling_data(src), (:field,); save_path) end """ plot_island_widths(src; save_path=nothing) -Scatter plot of island half-width `w/2` per rational surface vs ψ_N. -Integer-valued q rational surfaces are annotated. - -Requires the perturbed equilibrium singular coupling. +Plot the island half-width w/2 at each rational surface, in ψ_N. ### Arguments @@ -95,50 +138,21 @@ Requires the perturbed equilibrium singular coupling. ### Keyword arguments - - `save_path`: If provided, save the figure to this path (default: `nothing`) + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. """ function plot_island_widths(src::Source; save_path=nothing) _require(plot_island_widths, src) - p = _plot_island_widths(_coupling_data(src)) - isnothing(save_path) || savefig(p, save_path) - return p -end - -function _plot_island_widths(d) - island_hw, rational_psi, rational_q = d.island_hw, d.psi, d.q - - p = scatter( - rational_psi, island_hw; - xlabel="Norm. Poloidal Flux", - ylabel="w/2", - title="Island half-widths", - legend=false, - color=:steelblue, - markersize=7, - markerstrokewidth=0, - left_margin=10Plots.mm, - bottom_margin=5Plots.mm - ) - for k in eachindex(rational_psi) - abs(rational_q[k] - round(rational_q[k])) < 0.05 || continue - annotate!(p, rational_psi[k], island_hw[k], - text(" q=$(round(Int, rational_q[k]))", 8, :left, :black)) - end - return p + return _draw(_plot_resonant_coupling, _coupling_data(src), (:island_hw,); save_path) end """ plot_chirikov_parameter(src; save_path=nothing) -Scatter plot of the Chirikov overlap parameter per rational surface vs ψ_N, with a -horizontal reference line at K = 1 (island overlap threshold). Points are colored red -when K > 1. Integer-valued q rational surfaces are annotated. - -Requires the perturbed equilibrium singular coupling. +Plot the effective island-overlap Chirikov parameter at each rational surface, with the overlap threshold 1 marked. ### Arguments @@ -146,56 +160,22 @@ Requires the perturbed equilibrium singular coupling. ### Keyword arguments - - `save_path`: If provided, save the figure to this path (default: `nothing`) + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. """ function plot_chirikov_parameter(src::Source; save_path=nothing) _require(plot_chirikov_parameter, src) - p = _plot_chirikov_parameter(_coupling_data(src)) - isnothing(save_path) || savefig(p, save_path) - return p -end - -function _plot_chirikov_parameter(d) - chirikov, rational_psi, rational_q = d.chirikov, d.psi, d.q - - colors = [k > 1.0 ? :red : :steelblue for k in chirikov] - - p = scatter( - rational_psi, chirikov; - xlabel="Norm. Poloidal Flux", - ylabel="K (Chirikov)", - title="Chirikov overlap parameter", - legend=false, - color=colors, - markersize=7, - markerstrokewidth=0, - left_margin=10Plots.mm, - bottom_margin=5Plots.mm - ) - hline!(p, [1.0]; linestyle=:dash, color=:black, label=nothing) - for k in eachindex(rational_psi) - abs(rational_q[k] - round(rational_q[k])) < 0.05 || continue - annotate!(p, rational_psi[k], chirikov[k], - text(" q=$(round(Int, rational_q[k]))", 8, :left, :black)) - end - return p + return _draw(_plot_resonant_coupling, _coupling_data(src), (:chirikov,); save_path) end """ plot_driven_delta_prime(src; save_path=nothing) -Scatter plot of `Re(Δ')` per rational surface vs ψ_N, computed by the perturbed -equilibrium singular coupling. -One marker series per toroidal mode n. Integer-valued q rational surfaces are -annotated. - -This is the forcing-driven Δ' (response to the applied perturbation amplitudes -in `forcing_modes`); for the equilibrium-intrinsic Δ' of a Riccati or Galerkin solve, -see `ForceFreeStates.plot_delta_prime`. +Plot the forcing-driven Re Δ′ at each rational surface, the response to the applied forcing; for the +equilibrium's own Δ′ of a Riccati or Galerkin solve, see `ForceFreeStates.plot_delta_prime`. ### Arguments @@ -203,54 +183,51 @@ see `ForceFreeStates.plot_delta_prime`. ### Keyword arguments - - `save_path`: If provided, save the figure to this path (default: `nothing`) + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. """ function plot_driven_delta_prime(src::Source; save_path=nothing) _require(plot_driven_delta_prime, src) - p = _plot_driven_delta_prime(_coupling_data(src)) - isnothing(save_path) || savefig(p, save_path) - return p + return _draw(_plot_resonant_coupling, _coupling_data(src), (:delta_prime,); save_path) end -function _plot_driven_delta_prime(d) - delta_prime, rational_psi, rational_q, rational_n = d.delta_prime, d.psi, d.q, d.n - - p = plot(; xlabel="Norm. Poloidal Flux", ylabel="Re(Δ')", - title="Tearing stability Δ' (driven, perturbed equilibrium)", legend=:outertopright, - left_margin=10Plots.mm, bottom_margin=5Plots.mm) - hline!(p, [0.0]; linestyle=:dash, color=:black, label=nothing) - - for nn in unique(rational_n) - mask = rational_n .== nn - dp_n = real.(delta_prime[mask]) - colors = [v > 0 ? :red : :steelblue for v in dp_n] - scatter!(p, rational_psi[mask], dp_n; label="n=$nn", color=colors, - markersize=7, markerstrokewidth=0) - for (k, idx) in enumerate(findall(mask)) - abs(rational_q[idx] - round(rational_q[idx])) < 0.05 || continue - annotate!(p, rational_psi[idx], dp_n[k], - text(" q=$(round(Int, rational_q[idx]))", 8, :left, :black)) - end - end - return p -end +const _STYLES = (:log_heatmap, :linear_heatmap, :lines) """ - plot_resonant_area_weighted_field(src; save_path=nothing) + plot_mode_spectrogram(src; component=:xi_psi, style=:log_heatmap, save_path=nothing) + +Plot a response component's poloidal harmonics against ψ_N, with the rational surfaces and the resonance m = n q(ψ) +marked. -Five-panel summary of resonant coupling quantities at each rational surface vs ψ_N: +### Arguments + + - `src`: A `PerturbedEquilibriumState` with the reconstructed response, or the path of a `gpec.h5` it was saved to + +### Keyword arguments + + - `component`: `:xi_psi` (ξ^ψ, default), `:b_psi` (the area-weighted b̄^ψ, in T), `:Jb_theta` or `:Jb_zeta` (J·b^θ and + J·b^ζ, in T m²) + - `style`: `:log_heatmap` (default), `:linear_heatmap`, or `:lines` (the eight largest harmonics) + - `save_path`: If given, save the figure to this path (default: `nothing`) + +### Returns + +A Makie `Figure`. +""" +function plot_mode_spectrogram(src::Source; component=:xi_psi, style=:log_heatmap, save_path=nothing) + style in _STYLES || throw(ArgumentError("style must be :log_heatmap, :linear_heatmap or :lines")) + _require_component(plot_mode_spectrogram, src, component) + return _draw(_plot_mode_spectrogram, _response_data(src, component), component; style, save_path) +end - - `|b^r|`: resonant area-weighted field (`plot_resonant_area_weighted_field_amplitude`) - - `Re(Δ')`: tearing stability parameter (`plot_driven_delta_prime`) - - `|I_res|`: resonant current - - `w/2`: island half-width (`plot_island_widths`) - - `K`: Chirikov overlap parameter (`plot_chirikov_parameter`) +""" + plot_perturbed_equilibrium_summary(src; save_path=nothing) -Inspired by `plot_resonant_flux.py` from OMFIT GPEC. +Plot the panels the source supplies: the resonant field at each rational surface, the edge b̄^ψ spectrum, the +ξ^ψ spectrogram and, from a state in memory, b_n on the poloidal plane at φ = 0. ### Arguments @@ -258,241 +235,316 @@ Inspired by `plot_resonant_flux.py` from OMFIT GPEC. ### Keyword arguments - - `save_path`: If provided, save the figure to this path (default: `nothing`) + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. Throws an `ArgumentError` when no panel can be drawn. """ -function plot_resonant_area_weighted_field(src::Source; save_path=nothing) - _require(plot_resonant_area_weighted_field, src) - d = _coupling_data(src) - p1 = _plot_resonant_area_weighted_field_amplitude(d) - p2 = _plot_driven_delta_prime(d) - p3 = _plot_resonant_current(d) - p4 = _plot_island_widths(d) - p5 = _plot_chirikov_parameter(d) - - # Move outer legends inside so all panels have the same right margin - for p in (p1, p2, p3) - plot!(p; title="", legend=:topright) - end - for p in (p4, p5) - plot!(p; title="") - end - - hide_xaxis!(p) = plot!(p; xlabel="", xformatter=_ -> "", bottom_margin=1Plots.mm) - for p in (p1, p2, p3, p4) - hide_xaxis!(p) - end +function plot_perturbed_equilibrium_summary(src::Source; save_path=nothing) + _require(plot_perturbed_equilibrium_summary, src) + caps = capabilities(src) + coupling = _can(plot_resonant_area_weighted_field_amplitude, caps) ? _coupling_data(src) : nothing + edge = :response_b in caps ? _response_data(src, :b_psi) : nothing + xi = _can(plot_mode_spectrogram, caps) ? _response_data(src, :xi_psi) : nothing + rz = src isa PerturbedEquilibriumState && _can(plot_perturbed_field_rz, caps) ? _poloidal_data(src, :b_n) : nothing + return _draw(_plot_perturbed_equilibrium_summary, coupling, edge, xi, rz; save_path) +end - p = plot(p1, p2, p3, p4, p5; layout=(5, 1), size=(900, 1600)) +# (R, Z) of the flux surface ψ at SFL angle θ ∈ [0, 1). +function _rz(equil, psi, theta, hint) + r = sqrt(max(0.0, equil.rzphi_rsquared((psi, theta); hint))) + eta = 2π * (theta + equil.rzphi_offset((psi, theta); hint)) + return equil.ro + r * cos(eta), equil.zo + r * sin(eta) +end - isnothing(save_path) || savefig(p, save_path) - return p +# A closed flux-surface outline in (R, Z). +function _outline(equil, psi; ntheta=256) + hint = (Ref(1), Ref(1)) + pts = [_rz(equil, psi, θ, hint) for θ in range(0, 1; length=ntheta + 1)] + return first.(pts), last.(pts) end -# Internal helper — resonant current scatter plot -function _plot_resonant_current(d) - resonant_current, rational_psi, rational_q, rational_n = d.current, d.psi, d.q, d.n - - p = plot(; xlabel="Norm. Poloidal Flux", ylabel="|I_res|", - title="Resonant current |I_res| per surface", legend=:outertopright, - left_margin=10Plots.mm, bottom_margin=5Plots.mm) - - for nn in unique(rational_n) - mask = rational_n .== nn - rc_n = abs.(resonant_current[mask]) - scatter!(p, rational_psi[mask], rc_n; - label="n=$nn", markersize=7, markerstrokewidth=0) - for (k, idx) in enumerate(findall(mask)) - abs(rational_q[idx] - round(rational_q[idx])) < 0.05 || continue - annotate!(p, rational_psi[idx], rc_n[k], - text(" q=$(round(Int, rational_q[idx]))", 8, :left, :black)) - end +const _NORMAL = Dict(:b_n => :b_n_modes, :xi_n => :xi_n_modes) + +# The stored normal field or displacement summed over harmonics into real space on the poloidal plane at toroidal angle +# φ, on the flux-surface grid (every `every`-th ψ), with outlines of the rational surfaces and the boundary. +function _poloidal_data(pe::PerturbedEquilibriumState, component; phi=0.0, ntheta=256, every=4) + haskey(_NORMAL, component) || throw(ArgumentError("component must be :b_n or :xi_n")) + X = getfield(pe, _NORMAL[component]) + resp = pe.response + equil = resp.ffs.equil + rows = 1:every:length(pe.psi_grid) + θs = range(0, 1; length=ntheta + 1) + R, Z, V = (zeros(length(rows), length(θs)) for _ in 1:3) + hint = (Ref(1), Ref(1)) + for (a, i) in enumerate(rows), (b, θ) in enumerate(θs) + psi = pe.psi_grid[i] + R[a, b], Z[a, b] = _rz(equil, psi, θ, hint) + ν = equil.rzphi_nu((psi, θ); hint) + V[a, b] = real(sum(X[i, k] * cis(2π * resp.m_modes[k] * θ + resp.n_modes[k] * (ν - phi)) for k in axes(X, 2))) end - - return p + outlines = [_outline(equil, psi) for psi in vcat(resp.rational_psi, pe.psi_grid[end])] + return (; R, Z, values=V, outlines, phi) end -const _COMPONENTS = Dict( - :xi_psi => (dataset="xi_psi", field=:psi, modes=:xi_modes, needs=:response_xi), - :b_psi => (dataset="b_psi_area_weighted", field=:b_psi_area_weighted, modes=:b_modes, needs=:response_b), # area-normalized b^ψ - :Jb_theta => (dataset="Jb_theta", field=:theta, modes=:b_modes, needs=:response_b), - :Jb_zeta => (dataset="Jb_zeta", field=:zeta, modes=:b_modes, needs=:response_b) -) - -function _response_data(s::PerturbedEquilibriumState, component) - c = _COMPONENTS[component] - ffs = s.response.ffs - return (; data=getfield(getfield(s, c.modes), c.field), psi=s.psi_grid, mlow=ffs.mlow, mhigh=ffs.mhigh, nhigh=ffs.nhigh, - q95=ffs.equil.params.q95, rational_psi=s.response.rational_psi) +# The boundary in (R, Z) at toroidal angle φ with its normal displacement ξ_n there. +function _boundary_displacement(pe::PerturbedEquilibriumState; phi=0.0, ntheta=720) + resp = pe.response + equil = resp.ffs.equil + psi = pe.psi_grid[end] + X = pe.xi_n_modes[end, :] + hint = (Ref(1), Ref(1)) + θs = range(0, 1; length=ntheta + 1) + pts = [_rz(equil, psi, θ, hint) for θ in θs] + xi = [real(sum(X[k] * cis(2π * resp.m_modes[k] * θ + resp.n_modes[k] * (equil.rzphi_nu((psi, θ); hint) - phi)) for k in eachindex(X))) for θ in θs] + return (; R=first.(pts), Z=last.(pts), xi) end -function _response_data(h5path::AbstractString, component) - h5open(h5path, "r") do fid - (; data=read(fid[_RESPONSE*_COMPONENTS[component].dataset]), psi=read(fid[_RESPONSE*"psi"]), mlow=read(fid["Info/mlow"]), - mhigh=read(fid["Info/mhigh"]), nhigh=read(fid["Info/nhigh"]), q95=read(fid["Equilibrium/q_95"]), - rational_psi=haskey(fid, _COUPLING * "rational_psi") ? read(fid[_COUPLING*"rational_psi"]) : Float64[]) +# Island chains at toroidal angle φ between ψ_s ∓ (w/2)|cos(ξ/2)|, ξ = 2π m θ + n (ν − φ) + α with α the phase of the +# surface's resonant field, in (θ, ψ_N) and as (R, Z) polygons; ψ is clipped to the last flux surface. +function _island_chains(pe::PerturbedEquilibriumState, halfwidth; phi=0.0, ntheta=1440) + resp = pe.response + equil = resp.ffs.equil + psimax = pe.psi_grid[end] + hint = (Ref(1), Ref(1)) + θs = collect(range(0, 1; length=ntheta + 1)) + chains = NamedTuple[] + for row in eachindex(resp.rational_psi) + psis, m, n, w = resp.rational_psi[row], resp.rational_m_res[row], resp.rational_n[row], halfwidth[row] + w > 0 || continue + α = angle(pe.resonant_area_weighted_field[row]) + half = [w * abs(cos((2π * m * θ + n * (equil.rzphi_nu((psis, θ); hint) - phi) + α) / 2)) for θ in θs] + lo, hi = clamp.(psis .- half, 0.0, psimax), clamp.(psis .+ half, 0.0, psimax) + outer = [_rz(equil, p, θ, hint) for (p, θ) in zip(hi, θs)] + inner = [_rz(equil, p, θ, hint) for (p, θ) in zip(lo, θs)] + push!(chains, (; label="$m/$n", psis, w, theta=360 .* θs .- 180, lo, hi, polygon=vcat(outer, reverse(inner)))) end + return (; chains, phi, surfaces=[_outline(equil, p * psimax) for p in range(0.1, 1; length=10)]) end -# Check `component` names a response field `src` holds; `_require` covers ξ, which every component's run also has. -function _require_component(fn, src, component) - haskey(_COMPONENTS, component) || throw(ArgumentError("component must be one of :xi_psi, :b_psi, :Jb_theta, :Jb_zeta")) - _require(fn, src) - need = _COMPONENTS[component].needs - need in capabilities(src) || throw(ArgumentError("$(nameof(fn)): $(REASONS[need])")) - return nothing -end +# Drawing functions, defined by the GPECMakieExt extension. +function _plot_perturbed_field_rz end +function _plot_islands end """ - plot_mode_spectrogram(src; component=:xi_psi, save_path=nothing) + plot_perturbed_field_rz(pe; component=:b_n, phi=0.0, deform=false, clip=0.9, save_path=nothing) + +Plot the perturbed normal field b_n or normal displacement ξ_n on the poloidal plane at toroidal angle φ, drawn on the +flux-surface grid with the rational surfaces outlined; with `deform`, also the boundary displaced along its normal by +a scaled ξ_n over the equilibrium outline. + +### Arguments + + - `pe`: A `PerturbedEquilibriumState` with the stored normal field and displacement + +### Keyword arguments + + - `component`: `:b_n` (T, default) or `:xi_n` (m) + - `phi`: Toroidal angle in radians (default: `0.0`) + - `deform`: Draw the displaced boundary, scaled so its largest displacement is 5 % of the minor radius (default: `false`) + - `clip`: Quantile of |value| that sets the symmetric colour range; larger values saturate. `1` shows the full range (default: `0.9`) + - `save_path`: If given, save the figure to this path (default: `nothing`) + +### Returns -Two-panel spectrogram of a perturbed equilibrium response field component: +A Makie `Figure`. +""" +function plot_perturbed_field_rz(pe::PerturbedEquilibriumState; component=:b_n, phi=0.0, deform=false, clip=0.9, save_path=nothing) + _require(plot_perturbed_field_rz, pe) + 0 < clip <= 1 || throw(ArgumentError("plot_perturbed_field_rz: clip must be in (0, 1], got $clip")) + d = _poloidal_data(pe, component; phi) + boundary = deform ? _boundary_displacement(pe; phi) : nothing + return _draw(_plot_perturbed_field_rz, d, component, boundary; clip, save_path) +end - - Top: `|component|` vs ψ_N, one curve per poloidal mode m. Only resonant modes - (m ∈ [0, nhigh·q95)) are labeled to keep the legend readable. - - Bottom: Heatmap of `|component|` in (m, ψ_N) space (psi on vertical axis), with - white dashed lines at rational surface locations. +""" + plot_effective_islands(pe; phi=0.0, save_path=nothing) -Inspired by `plot_spectrograms.py` from OMFIT GPEC. +Plot the island chains the effective resonant flux would open, in (θ, ψ_N) and on the poloidal plane at toroidal angle +φ, with each chain's half-width w/2 (in ψ_N) listed. ### Arguments - - `src`: A `PerturbedEquilibriumState` with the reconstructed response, or the path of a `gpec.h5` it was saved to + - `pe`: A `PerturbedEquilibriumState` with the singular coupling ### Keyword arguments - - `component`: Response field component to plot; one of `:xi_psi`, `:b_psi`, - `:Jb_theta`, `:Jb_zeta` (default: `:xi_psi`). `:b_psi` is the area-normalized - `b_psi_area_weighted` field; `:Jb_theta` and `:Jb_zeta` are the Jacobian-weighted - contravariant field components. - - `save_path`: If provided, save the figure to this path (default: `nothing`) + - `phi`: Toroidal angle in radians (default: `0.0`) + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. """ -function plot_mode_spectrogram(src::Source; component=:xi_psi, save_path=nothing) - _require_component(plot_mode_spectrogram, src, component) - p = _plot_mode_spectrogram(_response_data(src, component), component) - isnothing(save_path) || savefig(p, save_path) - return p +function plot_effective_islands(pe::PerturbedEquilibriumState; phi=0.0, save_path=nothing) + _require(plot_effective_islands, pe) + return _draw(_plot_islands, _island_chains(pe, pe.island_half_width; phi), "Effective islands"; save_path) end -function _plot_mode_spectrogram(d, component) - mpert = d.mhigh - d.mlow + 1 - m_vals = d.mlow:d.mhigh - data_mn = d.data[:, 1:mpert] # (npsi, mpert): first mpert columns = first n's modes - - # Top panel: line plot per mode — only label resonant range m ∈ [0, nhigh·q95) - m_max_legend = d.nhigh * d.q95 - p1 = plot(; - xlabel="Norm. Poloidal Flux", - ylabel="|$(component)|", - title="Mode spectrogram: $(component)", - legend=:outertopright, - left_margin=10Plots.mm, - bottom_margin=5Plots.mm - ) - cmap = cgrad(:roma, mpert; categorical=true) - for (i, m) in enumerate(m_vals) - show_label = 0 <= m < m_max_legend - plot!(p1, d.psi, abs.(data_mn[:, i]); - label=(show_label ? "m=$m" : nothing), color=cmap[i], linewidth=1.5) - end +""" + plot_islands(pe; phi=0.0, save_path=nothing) - # Bottom panel: heatmap in (m, ψ_N) space — psi on vertical axis, m on horizontal - p2 = heatmap( - collect(m_vals), d.psi, abs.(data_mn); - xlabel="m", - ylabel="Norm. Poloidal Flux", - title="", - colorbar_title="|$(component)|", - left_margin=10Plots.mm, - right_margin=20Plots.mm, - bottom_margin=5Plots.mm - ) - # Overlay rational surface locations as white dashed lines - for psi_s in d.rational_psi - hline!(p2, [psi_s]; linestyle=:dash, color=:white, linewidth=1.5, label=nothing) - end +Plot the island chains of the penetrated field in (θ, ψ_N) and on the poloidal plane at toroidal angle φ. The +half-width scales from the effective one as w² ∝ |b̄|: w_pen = w_eff √(|b̄_pen| / |b̄_res|). + +### Arguments - return plot(p1, p2; layout=(2, 1), size=(950, 750)) + - `pe`: A `PerturbedEquilibriumState` from a matched run, which carries a penetrated field + +### Keyword arguments + + - `phi`: Toroidal angle in radians (default: `0.0`) + - `save_path`: If given, save the figure to this path (default: `nothing`) + +### Returns + +A Makie `Figure`. +""" +function plot_islands(pe::PerturbedEquilibriumState; phi=0.0, save_path=nothing) + _require(plot_islands, pe) + ratio = abs.(pe.penetrated_area_weighted_field) ./ max.(abs.(pe.resonant_area_weighted_field), floatmin()) + return _draw(_plot_islands, _island_chains(pe, pe.island_half_width .* sqrt.(ratio); phi), "Penetrated islands"; save_path) end """ - plot_perturbed_equilibrium_summary(src; save_path=nothing) + plot_dominant_mode_surface(src; mode=1, n=nothing, save_path=nothing) + +Plot dominant resonant-coupling mode `mode`, the right singular vector of the resonant coupling matrix, as the normal +field it represents on the control surface: toroidal angle φ against poloidal angle θ, in arbitrary units (the vector +has unit norm). + +### Arguments -Composite summary of perturbed equilibrium results, with each panel the source can supply: + - `src`: A `PlasmaResponse`, or a `PerturbedEquilibriumState` (its response's modes) - - Top-left: Resonant area-weighted field per surface (`plot_resonant_area_weighted_field_amplitude`) - - Top-right: Edge |b_ψ| spectrum - - Bottom: ξ_ψ mode spectrogram (`plot_mode_spectrogram`) +### Keyword arguments + + - `mode`: Which dominant mode, 1 the strongest (default: `1`) + - `n`: Toroidal mode number drawn (default: the first) + - `save_path`: If given, save the figure to this path (default: `nothing`) + +### Returns -Panels the source cannot draw are left out; it throws an `ArgumentError` only when none can be drawn. +A Makie `Figure`. +""" +function plot_dominant_mode_surface(src::Union{PlasmaResponse,PerturbedEquilibriumState}; mode::Int=1, n=nothing, save_path=nothing) + _require(plot_dominant_mode_surface, src) + resp = src isa PlasmaResponse ? src : src.response + 1 <= mode <= length(resp.dominant_singular_values) || + throw(ArgumentError("plot_dominant_mode_surface: mode must be 1 to $(length(resp.dominant_singular_values))")) + σ = resp.dominant_singular_values[mode] + return plot_control_surface(resp, resp.dominant_right_singular_vectors[:, mode]; n, arb=true, label="dominant mode $mode (σ = $(round(σ; sigdigits=3)))", + save_path) +end + +# Each dominant mode's b_n at toroidal angle `phi` around the control-surface cross-section, normalized to its peak there, +# with the boundary in (R, Z). The map's φ = 0 column of the spectrum rotated by e^{i n h φ} is the field at φ. +function _singular_mode_rz_data(resp::PlasmaResponse, modes, phi; mtheta=512) + equil = resp.ffs.equil + hint = (Ref(1), Ref(1)) + turn = cis(first(resp.n_modes) * _equil_helicity(equil) * phi) + return map(modes) do k + v = resp.dominant_right_singular_vectors[:, k] + d = _control_surface_data(resp, v .* turn; nphi=1, mtheta) + b = d.field[:, 1] ./ maximum(abs, d.field[:, 1]) + pts = [_rz(equil, equil.rzphi_xs[end], mod(θ / 360, 1), hint) for θ in d.theta] + R, Z = first.(pts), last.(pts) + (; mode=k, sigma=resp.dominant_singular_values[k], phi, R=[R; R[1]], Z=[Z; Z[1]], b=[b; b[1]]) + end +end + +function _plot_singular_mode_rz end + +""" + plot_singular_mode_rz(src; modes=nothing, phi=0.0, save_path=nothing) + +Plot dominant resonant-coupling modes on the control-surface cross-section: the boundary in (R, Z) as a band coloured +by each mode's normal field b_n at toroidal angle φ, normalized to its peak, one panel per mode. The modes are the +stored decomposition's, over the rational surfaces with ψ_N ≤ 0.9. ### Arguments - - `src`: A `PerturbedEquilibriumState`, or the path of a `gpec.h5` it was saved to + - `src`: A `PlasmaResponse`, or a `PerturbedEquilibriumState` (its response's modes) ### Keyword arguments - - `save_path`: If provided, save the figure to this path (default: `nothing`) + - `modes`: Which dominant modes, 1 the strongest (default: the first three that exist) + - `phi`: Toroidal angle in radians (default: `0.0`) + - `save_path`: If given, save the figure to this path (default: `nothing`) ### Returns -A `Plots.jl` plot object. +A Makie `Figure`. """ -function plot_perturbed_equilibrium_summary(src::Source; save_path=nothing) - caps = capabilities(src) - top = Plots.Plot[] - _can(plot_resonant_area_weighted_field_amplitude, caps) && push!(top, _plot_resonant_area_weighted_field_amplitude(_coupling_data(src))) - :response_b in caps && push!(top, _plot_bpsi_edge_spectrum(_response_data(src, :b_psi))) - has_spectro = _can(plot_mode_spectrogram, caps) - if !has_spectro && isempty(top) - throw(ArgumentError("plot_perturbed_equilibrium_summary: no panel can be drawn: $(REASONS[:singular_coupling]); $(REASONS[:response_xi])")) - end - p = if !has_spectro - plot(top...; layout=(1, length(top)), size=(550 * length(top), 450)) - else - p_spectro = _plot_mode_spectrogram(_response_data(src, :xi_psi), :xi_psi) - isempty(top) ? p_spectro : plot(top..., p_spectro; layout=@layout([grid(1, length(top)){0.35h}; b]), size=(1100, 1100)) - end +function plot_singular_mode_rz(src::Union{PlasmaResponse,PerturbedEquilibriumState}; modes=nothing, phi=0.0, save_path=nothing) + _require(plot_singular_mode_rz, src) + resp = src isa PlasmaResponse ? src : src.response + nk = length(resp.dominant_singular_values) + ks = something(modes, 1:min(3, nk)) + all(k -> 1 <= k <= nk, ks) || throw(ArgumentError("plot_singular_mode_rz: modes must lie in 1 to $nk")) + return _draw(_plot_singular_mode_rz, _singular_mode_rz_data(resp, collect(ks), phi); save_path) +end - isnothing(save_path) || savefig(p, save_path) - return p +# The resonant coupling matrix of toroidal mode number `n` and its SVD C = U Σ Vᴴ: rows are rational surfaces, columns +# poloidal harmonics; U has rows only for the surfaces inside the SVD's core window, at `retained` in the row order. +function _coupling_matrix_data(resp::PlasmaResponse, n) + n = something(n, first(resp.n_modes)) + n in resp.n_modes || throw(ArgumentError("plot_coupling_matrix: n = $n is not one of the response's toroidal mode numbers $(unique(resp.n_modes))")) + cols = findall(==(n), resp.n_modes) + rows = findall(==(n), resp.rational_n) + inwindow = [i for (i, r) in enumerate(resp.dominant_rational_index) if r in rows] + return (; m=resp.m_modes[cols], labels=["$(resp.rational_m_res[r])/$n" for r in rows], m_res=resp.rational_m_res[rows], + C=resp.C_resonant_area_weighted_field[rows, cols], sigma=resp.dominant_singular_values, + U=resp.dominant_left_singular_vectors[inwindow, :], retained=[findfirst(==(resp.dominant_rational_index[i]), rows) for i in inwindow], + V=resp.dominant_right_singular_vectors[cols, :]) end -# Internal helper — |b_psi(m)| spectrum at the outermost psi surface -function _plot_bpsi_edge_spectrum(d) - mpert = d.mhigh - d.mlow + 1 - m_vals = d.mlow:d.mhigh - bpsi_edge = abs.(d.data[end, 1:mpert]) - - p = bar( - collect(m_vals), bpsi_edge; - xlabel="m", - ylabel="|b_ψ|", - title="b_ψ spectrum at ψ edge", - legend=false, - color=:steelblue, - linewidth=0, - left_margin=10Plots.mm, - bottom_margin=5Plots.mm - ) - - return p +function _plot_coupling_matrix end + +""" + plot_coupling_matrix(src; n=nothing, save_path=nothing) + +Plot the resonant coupling matrix C, the resonant area-weighted field b̄_res at each rational surface per unit applied +harmonic b̃_m, with its singular value decomposition C = U Σ Vᴴ: |C| against surface and m, the left singular vectors +|U| on the same surface axis, the right singular vectors |V| on the same m axis (V₁ is the dominant mode), and the +singular values σ. Surfaces outside the decomposition's core window are shaded. + +### Arguments + + - `src`: A `PlasmaResponse`, or a `PerturbedEquilibriumState` (its response's matrix) + +### Keyword arguments + + - `n`: Toroidal mode number drawn (default: the first) + - `save_path`: If given, save the figure to this path (default: `nothing`) + +### Returns + +A Makie `Figure`. +""" +function plot_coupling_matrix(src::Union{PlasmaResponse,PerturbedEquilibriumState}; n=nothing, save_path=nothing) + _require(plot_coupling_matrix, src) + return _draw(_plot_coupling_matrix, _coupling_matrix_data(src isa PlasmaResponse ? src : src.response, n); save_path) end const _PE_SOURCES = (PerturbedEquilibriumState, AbstractString) -register!(plot_perturbed_equilibrium_summary, _PE_SOURCES, (), "resonant field, edge b_ψ spectrum and ξ spectrogram") -register!(plot_resonant_area_weighted_field, _PE_SOURCES, (:singular_coupling,), "five resonant-coupling panels per rational surface") +register!(plot_perturbed_equilibrium_summary, _PE_SOURCES, (), "resonant field, edge b̄^ψ spectrum, ξ^ψ spectrogram and b_n on the poloidal plane"; + any_of=((:singular_coupling,), (:response_b,), (:response_xi,), (:response_n,))) +register!(plot_resonant_area_weighted_field, _PE_SOURCES, (:singular_coupling,), "resonant coupling rows per rational surface") register!(plot_resonant_area_weighted_field_amplitude, _PE_SOURCES, (:singular_coupling,), "|b̄_res| per rational surface") register!(plot_island_widths, _PE_SOURCES, (:singular_coupling,), "island half-width per rational surface") -register!(plot_chirikov_parameter, _PE_SOURCES, (:singular_coupling,), "Chirikov parameter per rational surface") +register!(plot_chirikov_parameter, _PE_SOURCES, (:singular_coupling,), "effective island-overlap Chirikov parameter per rational surface") register!(plot_driven_delta_prime, _PE_SOURCES, (:singular_coupling,), "forcing-driven Δ′ per rational surface") -register!(plot_mode_spectrogram, _PE_SOURCES, (:response_xi,), "|response| per m against ψ_N (component = :xi_psi, :b_psi, :Jb_theta, :Jb_zeta)") +register!(plot_mode_spectrogram, _PE_SOURCES, (:response_xi,), "response harmonics against ψ_N (component = :xi_psi, :b_psi, :Jb_theta, :Jb_zeta)") +register!(plot_dominant_mode_surface, (PlasmaResponse, PerturbedEquilibriumState), (:dominant_mode,), + "a dominant resonant-coupling mode as b_n on the control surface") +register!(plot_singular_mode_rz, (PlasmaResponse, PerturbedEquilibriumState), (:dominant_mode,), + "dominant resonant-coupling modes as b_n on the control-surface cross-section") +register!(plot_coupling_matrix, (PlasmaResponse, PerturbedEquilibriumState), (:coupling_matrices, :dominant_mode), + "resonant coupling matrix |C| with its singular values and left and right singular vectors") +register!(plot_perturbed_field_rz, (PerturbedEquilibriumState,), (:response_n,), "b_n or ξ_n on the poloidal plane (deform = true for the displaced boundary)") +register!(plot_effective_islands, (PerturbedEquilibriumState,), (:singular_coupling,), "island chains from the effective resonant flux") +register!(plot_islands, (PerturbedEquilibriumState,), (:penetrated_field,), "island chains from the penetrated field") end # module PerturbedEquilibrium diff --git a/src/Analysis/Registry.jl b/src/Analysis/Registry.jl index 4f72964ac..03940daa3 100644 --- a/src/Analysis/Registry.jl +++ b/src/Analysis/Registry.jl @@ -7,7 +7,8 @@ using HDF5 import ..Equilibrium: PlasmaEquilibrium import ..LocalStability: LocalStabilityResult import ..ForceFreeStates: ForceFreeStatesResult -import ..PerturbedEquilibrium: PerturbedEquilibriumState +import ..PerturbedEquilibrium: PerturbedEquilibriumState, PlasmaResponse +import ..ForcingTerms as FT import ..ErrorFields as EF """ @@ -21,18 +22,21 @@ A registered plot. - `sources::Tuple` - The types it accepts as its first argument; `AbstractString` means a `gpec.h5` path. - `needs::Tuple{Vararg{Symbol}}` - The capabilities (see [`capabilities`](@ref)) a source must have for it to draw. - `description::String` - One line on what it draws. + - `any_of::Tuple` - For a summary, the capabilities of each of its panels: it draws when a source has every capability + of at least one panel, and is empty for a single plot. """ struct PlotEntry fn::Function sources::Tuple needs::Tuple{Vararg{Symbol}} description::String + any_of::Tuple end const PLOTS = PlotEntry[] # Add a plot to the registry; called by each plotting submodule as it loads. -register!(fn, sources, needs, description) = (push!(PLOTS, PlotEntry(fn, Tuple(sources), Tuple(needs), description)); fn) +register!(fn, sources, needs, description; any_of=()) = (push!(PLOTS, PlotEntry(fn, Tuple(sources), Tuple(needs), description, Tuple(any_of))); fn) """ What each capability needs, worded as what to change in the run to get it. @@ -45,16 +49,20 @@ const REASONS = Dict{Symbol,String}( :free_boundary => "needs the free-boundary energies: run with vac_flag = true and the Forward or Riccati integrator", :edge_scan => "needs the edge stability scan: set dW_edge_scan_start below psilim", :delta_prime => "needs the Δ′ matrix: use the Riccati or Galerkin integrator (Forward computes none)", + :delta_coil => "needs Δ_coil: use the Riccati integrator, or Galerkin with rpec_flag = true", :kinetic_scan => "needs the kinetic singular-surface scan: run with a kinetic source and singfac_min > 0", :ballooning_scan => "needs the ballooning scan: add a [LocalStability] section", - :gse_files => "needs gse.h5 next to the file: set diagnose_src = true in [Equilibrium]", :singular_coupling => "needs the singular coupling: run PerturbedEquilibrium with compute_singular_coupling = true", :response_xi => "needs the reconstructed displacement ξ: run PerturbedEquilibrium on a Forward free-boundary result", + :response_n => "needs the stored normal field and displacement: run PerturbedEquilibrium on a Forward free-boundary result", + :penetrated_field => "needs a penetrated field: ideal runs have none; it comes from a matched (resistive) run", :response_b => "needs the reconstructed perturbed field b: run PerturbedEquilibrium on a Forward free-boundary result", :coil_sets => "needs coil forcing: give ForcingTerms coil sets (forcing_data_format = \"coil\")", :forcing_modes => "needs forcing modes: give ForcingTerms a forcing", :ef_sensitivities => "needs the coil sensitivities: add an [ErrorFields] section", :dominant_mode => "needs the dominant resonant-coupling mode: run PerturbedEquilibrium with compute_singular_coupling = true", + :coil_source => "needs a coil source: give RMPField coil sets (a mode table or spectrum has no geometry to draw)", + :coupling_matrices => "needs the resonant coupling matrices: run plasma_response with compute_singular_coupling = true", :ef_monte_carlo => "needs the tolerance Monte Carlo: give ErrorFields a tolerance file", :ef_risk => "needs the locking risk: give ErrorFields a tolerance file and a scenario", :ef_scan => "needs the tolerance scan: set RiskControl scan_scales" @@ -82,17 +90,31 @@ function capabilities(r::ForceFreeStatesResult) diag === nothing || isempty(diag.edge_scan.psi) || push!(caps, :edge_scan) r.free_boundary === nothing || isempty(r.free_boundary.et) || push!(caps, :free_boundary) r.delta_prime === nothing || isempty(r.delta_prime.matrix) || push!(caps, :delta_prime) + r.delta_prime === nothing || isempty(r.delta_prime.coil) || push!(caps, :delta_coil) isempty(r.kinetic.scan_psi) || push!(caps, :kinetic_scan) return caps end capabilities(r::LocalStabilityResult) = isempty(r.alpha) ? Set{Symbol}() : Set([:ballooning_scan]) -function capabilities(s::PerturbedEquilibriumState) +capabilities(r::FT.RMPField) = + any(t -> t isa FT.RMPSource && t.ctrl.forcing_data_format == "coil", FT._rmp_terms(r)) ? Set([:coil_source]) : Set{Symbol}() + +function capabilities(r::PlasmaResponse) caps = Set{Symbol}() + isempty(r.C_resonant_area_weighted_field) || push!(caps, :coupling_matrices) + isempty(r.dominant_singular_values) || push!(caps, :dominant_mode) + return caps +end + +# A perturbed equilibrium holds its response's capabilities as well as those of its own drive. +function capabilities(s::PerturbedEquilibriumState) + caps = capabilities(s.response) isempty(s.resonant_area_weighted_field) || push!(caps, :singular_coupling) s.xi_modes === nothing || push!(caps, :response_xi) s.b_modes === nothing || push!(caps, :response_b) + s.b_n_modes === nothing || s.xi_n_modes === nothing || push!(caps, :response_n) + any(!iszero, s.penetrated_area_weighted_field) && push!(caps, :penetrated_field) isempty(s.coil_sets) || push!(caps, :coil_sets) isempty(s.forcing_modes) || push!(caps, :forcing_modes) return caps @@ -115,6 +137,7 @@ const _H5_CAPABILITIES = ( :edge_scan => "ForceFreeStates/EdgeScan/psi", :free_boundary => "ForceFreeStates/FreeBoundaryStability/eigenmode_energies", :delta_prime => "SingularSurfaces/Delta_prime_matrix", + :delta_coil => "SingularSurfaces/Delta_coil", :kinetic_scan => "SingularSurfaces/Kinetic/scan_psi", :ballooning_scan => "LocalStability/alpha", :singular_coupling => "PerturbedEquilibrium/SingularCoupling/resonant_area_weighted_field", @@ -137,7 +160,6 @@ function capabilities(h5path::AbstractString) any(has, keys isa Tuple ? keys : (keys,)) && push!(caps, cap) end end - isfile(joinpath(dirname(h5path), "gse.h5")) && push!(caps, :gse_files) return caps end @@ -149,7 +171,12 @@ function _entry(fn) end _accepts(e::PlotEntry, src) = any(T -> src isa T, e.sources) -_missing(e::PlotEntry, caps) = [k for k in e.needs if k ∉ caps] +# The capabilities `caps` lacks for plot `e`; for a summary, every panel's when no panel can be drawn. +function _missing(e::PlotEntry, caps) + own = [k for k in e.needs if k ∉ caps] + panels = [[k for k in panel if k ∉ caps] for panel in e.any_of] + return isempty(panels) || any(isempty, panels) ? own : unique(vcat(own, panels...)) +end _why(missing) = join((REASONS[k] for k in missing), "; ") """ @@ -210,19 +237,23 @@ end Base.length(c::PlotCatalog) = sum(length(rows) for (_, rows) in c.sections; init=0) -_qualified(e::PlotEntry) = string(nameof(parentmodule(e.fn)), ".", nameof(e.fn)) - function Base.show(io::IO, ::MIME"text/plain", c::PlotCatalog) isempty(c.sections) && return print(io, "PlotCatalog: no registered plots accept this source") for (i, (label, rows)) in enumerate(c.sections) - i > 1 && print(io, "\n") + i > 1 && print(io, "\n\n") ok = count(r -> isempty(r[2]), rows) print(io, "Plots for ", label, ": ", ok, " of ", length(rows), " available") - width = maximum((length(_qualified(e)) for (e, _) in rows); init=0) - for (e, missing) in rows - mark = isempty(missing) ? "✓" : "✗" - note = isempty(missing) ? e.description : _why(missing) - print(io, "\n ", mark, " ", rpad(_qualified(e), width), " ", note) + # One group per Analysis submodule, in registration order, each aligned on its own longest name. + for mod in unique(parentmodule(e.fn) for (e, _) in rows) + group = [(e, missing) for (e, missing) in rows if parentmodule(e.fn) === mod] + width = maximum(length(string(nameof(e.fn))) for (e, _) in group) + print(io, "\n\n", nameof(mod)) + for (e, missing) in group + mark = isempty(missing) ? "✓" : "✗" + # A summary's reasons are its panels', listed on their own rows. + note = isempty(missing) ? e.description : isempty(e.any_of) ? _why(missing) : "no panel can be drawn: see the reasons of the plots it combines" + print(io, "\n ", mark, " ", rpad(string(nameof(e.fn)), width), " ", note) + end end end return nothing diff --git a/src/Display.jl b/src/Display.jl index 28264f92a..662cbbd47 100644 --- a/src/Display.jl +++ b/src/Display.jl @@ -11,6 +11,7 @@ using .PerturbedEquilibrium: PerturbedEquilibriumState using .KineticForces: MethodResult, KineticForcesState using .ErrorFields: MonteCarloResult, RiskResult, EFCCoupling using .Tearing: GrowthRateResult +using .ForcingTerms: RMPSource, RMPSpectrum, RMPFieldSum _fmt(x::Real) = @sprintf("%.4g", x) _fmt(::Nothing) = "—" @@ -276,13 +277,37 @@ function Base.show(io::IO, ::MIME"text/plain", r::PlasmaResponse) isempty(r.dominant_singular_values) || _row(io, "Dominant σ", _list(_fmt.(r.dominant_singular_values); max=3)) _row(io, "Torque resp.", isempty(r.T_xe) ? "not computed" : "T_xe $(_shape(r.T_xe))") r.odet_from_gal && _row(io, "Basis", "Galerkin-native solution") + _plots_row(io, r) + return nothing +end + +# Forcing sources: the source, and its weight when it is not 1 (always in a sum, where the weights are the point). + +_weight(w) = string(iszero(real(w)) && !iszero(imag(w)) ? _fmt(imag(w)) * "im" : _fmt(w), " × ") + +function _rmp_label(f::RMPSource) + c = f.ctrl + c.forcing_data_format == "coil" || return string(basename(c.forcing_data_file), " (", c.forcing_data_format, ")") + sets = [isempty(get(d, "name", "")) ? get(d, "source", "coil set") : d["name"] for d in c.coil_sets_raw] + return string(isempty(c.machine) ? "" : c.machine * " ", "coils ", _list(sets)) +end +_rmp_label(f::RMPSpectrum) = string("spectrum of ", _plural(length(f.modes), "mode"), ", n = ", _irange([m.n for m in f.modes])) + +Base.show(io::IO, f::Union{RMPSource,RMPSpectrum}) = print(io, "RMPField(", f.scale == 1 ? "" : _weight(f.scale), _rmp_label(f), ")") +Base.show(io::IO, f::RMPFieldSum) = print(io, "RMPField(sum of ", _plural(length(f.terms), "term"), ")") + +function Base.show(io::IO, ::MIME"text/plain", f::RMPFieldSum) + print(io, "RMPField sum of ", _plural(length(f.terms), "term")) + for t in f.terms + print(io, "\n ", _weight(t.scale), _rmp_label(t)) + end return nothing end function _forcing_label(s::PerturbedEquilibriumState) isempty(s.coil_sets) || return "coils: " * _list([c.name for c in s.coil_sets]) s.forcing === nothing && return "none" - return string(nameof(typeof(s.forcing))) + return sprint(show, s.forcing) end function Base.show(io::IO, s::PerturbedEquilibriumState) diff --git a/src/ForcingTerms/ForcingTerms.jl b/src/ForcingTerms/ForcingTerms.jl index 98738ad6a..c8ee6eab0 100644 --- a/src/ForcingTerms/ForcingTerms.jl +++ b/src/ForcingTerms/ForcingTerms.jl @@ -161,6 +161,8 @@ function load_forcing_ascii!( if ncols < 4 error("ASCII forcing file must have 4 columns (n, m, amplitude_real, amplitude_imag)") end + bad = findfirst(i -> !all(x -> x isa Real, @view(data[i, 1:4])) || !isinteger(data[i, 1]) || !isinteger(data[i, 2]), 1:nrows) + bad === nothing || error("ASCII forcing file row $bad is not integer n and m followed by two numbers: $(join(data[bad, :], " "))") empty!(forcing_modes) @@ -257,19 +259,29 @@ Abstract supertype of every external-forcing description the scripting API accep resonant-magnetic-perturbation field, described by where it comes from and how strongly it drives (a forcing-mode file, a coil set with currents, or a precomputed spectrum). -Nothing is read or computed at construction; the modes are materialized against an equilibrium -when the perturbed-equilibrium stage runs, so one `RMPField` can drive several solves. +Construction checks the description and throws an `ArgumentError` naming the problem: a forcing file must exist and +parse as a mode table with a known normalization, and every coil set must resolve to geometry and carry currents. The +modes are materialized against an equilibrium only when the perturbed-equilibrium stage runs, so one `RMPField` can +drive several solves. ## Construction RMPField(path; format=..., scale=1.0, kwargs...) - RMPField(coil_sets::Vector{Dict{String,Any}}; scale=1.0, kwargs...) + RMPField(coil_sets; scale=1.0, kwargs...) RMPField(ctrl::ForcingTermsControl; scale=1.0) RMPField(modes::Vector{ForcingMode}; scale=1.0) The first form points at a forcing-mode file, `format` defaulting to `"hdf5"` for an `.h5` -or `.hdf5` extension and `"ascii"` otherwise. The second form takes TOML-shaped coil blocks -(the `[[ForcingTerms.coil_set]]` layout) and selects the coil format. Remaining `kwargs` are +or `.hdf5` extension and `"ascii"` otherwise. The second form takes one coil set or a vector of them, each a +NamedTuple or Dict with the `[[ForcingTerms.coil_set]]` keys, and selects the coil format: + +```julia +RMPField((name="c", currents=[1e3, 500, -500, -1e3, -500, 500]); machine="d3d") # the DIII-D C-coil, in amperes +RMPField([(name="iu", currents=iu), (name="il", currents=il)]; machine="d3d") +RMPField((source="pf_hoop", radius=12.0, height=1.2, currents=[2e3])) +``` + +Remaining `kwargs` are [`ForcingTermsControl`](@ref) fields. These three return a single-source leaf ([`RMPSource`](@ref)). The last form takes a control-surface spectrum computed elsewhere, already in the unit-norm `Φ_x` convention, and returns a spectrum-literal leaf ([`RMPSpectrum`](@ref)). @@ -351,20 +363,77 @@ Forcing-data format implied by a file extension: `"hdf5"` for `.h5`/`.hdf5`, els """ _infer_format(path::AbstractString) = lowercase(splitext(path)[2]) in (".h5", ".hdf5") ? "hdf5" : "ascii" -RMPField(ctrl::ForcingTermsControl; scale::Number=1.0) = RMPSource(ctrl, ComplexF64(scale)) +const _NORMALIZATIONS = ("normal_field_T", "sfl_flux_Wb", "unit_flux") -function RMPField(path::AbstractString; format::String=_infer_format(path), scale::Number=1.0, kwargs...) - ctrl = ForcingTermsControl(; forcing_data_file=abspath(path), forcing_data_format=format, kwargs...) - return RMPSource(ctrl, ComplexF64(scale)) +# Read a forcing-mode file now, so a missing or malformed file fails when the source is built. +function _check_forcing_file(ctrl::ForcingTermsControl) + path = ctrl.forcing_data_file + isfile(path) || throw(ArgumentError("RMPField: no forcing file at $path")) + modes = ForcingMode[] + tag = try + load_forcing_data!(modes, path, ctrl.forcing_data_format) + catch err + throw( + ArgumentError( + "RMPField: $path is not a forcing-mode table (format $(ctrl.forcing_data_format)): $(sprint(showerror, err)). " * + "A mode table holds rows of n, m, Re, Im (see `load_forcing_data!`); a coil geometry file goes in a coil set, " * + "as in RMPField((name=\"set\", dat_file=path, currents=[...]))." + ) + ) + end + isempty(modes) && throw(ArgumentError("RMPField: $path holds no forcing modes")) + tag in _NORMALIZATIONS || throw(ArgumentError("RMPField: $path declares normalization \"$tag\"; use one of $(join(_NORMALIZATIONS, ", "))")) + return nothing end -function RMPField(coil_sets::Vector{Dict{String,Any}}; scale::Number=1.0, kwargs...) - ctrl = ForcingTermsControl(; forcing_data_format="coil", coil_sets_raw=coil_sets, kwargs...) +# Resolve every coil set now, so an unknown key, a missing geometry file or a missing current fails when the source is +# built; window-pane sets placed by standoff need the plasma surface and are resolved when materialized. +function _check_coil_sets(ctrl::ForcingTermsControl) + isempty(ctrl.coil_sets_raw) && throw(ArgumentError("RMPField: a coil source needs at least one coil set")) + cfg = CoilConfig(ctrl) + for sc in cfg.coil_sets + label = isempty(sc.name) ? "a coil set" : "coil set \"$(sc.name)\"" + all(iszero, sc.currents) && throw(ArgumentError("RMPField: $label has no currents; give `currents` in amperes, one per conductor")) + isnan(sc.standoff) || continue + cs = try + _build_raw_coil_set(cfg, sc) + catch err + hint = + isempty(cfg.machine) && sc.source in ("", "file") && isempty(sc.dat_file) && isempty(sc.h5_file) ? + " (a named set is read from _.dat: pass machine=\"d3d\", say, or give a dat_file)" : "" + throw(ArgumentError("RMPField: $label: $(sprint(showerror, err))$hint")) + end + length(sc.currents) > cs.ncoil && + throw(ArgumentError("RMPField: $label has $(length(sc.currents)) currents for its $(cs.ncoil) conductors")) + end + return nothing +end + +function RMPField(ctrl::ForcingTermsControl; scale::Number=1.0) + fmt = ctrl.forcing_data_format + fmt == "coil" ? _check_coil_sets(ctrl) : + fmt in ("ascii", "hdf5") ? _check_forcing_file(ctrl) : + throw(ArgumentError("RMPField: forcing_data_format \"$fmt\" is not \"ascii\", \"hdf5\" or \"coil\"")) return RMPSource(ctrl, ComplexF64(scale)) end -RMPField(modes::Vector{ForcingMode}; scale::Number=1.0) = - RMPSpectrum([ForcingMode(; n=mode.n, m=mode.m, amplitude=mode.amplitude) for mode in modes], ComplexF64(scale)) +RMPField(path::AbstractString; format::String=_infer_format(path), scale::Number=1.0, kwargs...) = + RMPField(ForcingTermsControl(; forcing_data_file=abspath(path), forcing_data_format=format, kwargs...); scale) + +# A coil set as the [[ForcingTerms.coil_set]] table: a Dict or NamedTuple, keys as strings or symbols. +_coil_dict(d::AbstractDict) = Dict{String,Any}(string(k) => v for (k, v) in d) +_coil_dict(nt::NamedTuple) = Dict{String,Any}(string(k) => v for (k, v) in pairs(nt)) +_coil_dict(x) = throw(ArgumentError("RMPField: a coil set is a Dict or NamedTuple of [[ForcingTerms.coil_set]] keys, not a $(typeof(x))")) + +RMPField(coil_sets::AbstractVector; scale::Number=1.0, kwargs...) = + RMPField(ForcingTermsControl(; forcing_data_format="coil", coil_sets_raw=_coil_dict.(coil_sets), kwargs...); scale) +RMPField(coil_set::Union{AbstractDict,NamedTuple}; kwargs...) = RMPField([coil_set]; kwargs...) + +function RMPField(modes::Vector{ForcingMode}; scale::Number=1.0) + isempty(modes) && throw(ArgumentError("RMPField: a spectrum needs at least one forcing mode")) + all(m -> isfinite(m.amplitude), modes) || throw(ArgumentError("RMPField: a spectrum amplitude is not finite")) + return RMPSpectrum([ForcingMode(; n=mode.n, m=mode.m, amplitude=mode.amplitude) for mode in modes], ComplexF64(scale)) +end """ Flattened weighted-leaf list of any [`RMPField`](@ref). diff --git a/src/GeneralizedPerturbedEquilibrium.jl b/src/GeneralizedPerturbedEquilibrium.jl index 26db532d1..50ecbd255 100755 --- a/src/GeneralizedPerturbedEquilibrium.jl +++ b/src/GeneralizedPerturbedEquilibrium.jl @@ -81,7 +81,12 @@ using .ForcingTerms: RMPField using .PerturbedEquilibrium: plasma_response, perturbed_equilibrium, PlasmaResponse using .KineticForces: kinetic_torque, KineticForcesControl, KineticForcesResult using .ErrorFields: error_fields, ErrorFieldsResult -using .Analysis: available_plots +# The plots and `available_plots`, so one `using` reaches every figure. +for name in names(Analysis) + name === :Analysis && continue + @eval using .Analysis: $name + @eval export $name +end using .ForceFreeStates: ForceBalanceProblem, kinetic_matrices, _prepare_solve, maybe_reform_equilibrium, ForceFreeStatesControl, kinetic_source include("Deck.jl") @@ -193,7 +198,9 @@ function main_from_inputs( if deck.perturbed_equilibrium !== nothing pe = _timed_stage(runtimes, "perturbed_equilibrium", "Perturbed Equilibrium") do - perturbed_equilibrium(plasma_response(ffs; verbose, deck.perturbed_equilibrium.response...), RMPField(deck.forcing); + # A replay from stored coil geometry skips the source checks: the geometry files they read may be gone. + rmp = preloaded_coil_sets === nothing ? RMPField(deck.forcing) : ForcingTerms.RMPSource(deck.forcing, 1) + perturbed_equilibrium(plasma_response(ffs; verbose, deck.perturbed_equilibrium.response...), rmp; coil_sets=preloaded_coil_sets, verbose, deck.perturbed_equilibrium.drive...) end resp = pe.response diff --git a/test/error_fields_fixture.jl b/test/error_fields_fixture.jl new file mode 100644 index 000000000..760fba638 --- /dev/null +++ b/test/error_fields_fixture.jl @@ -0,0 +1,39 @@ +# The coil-forced TJ run the error-field tests share: three PF hoops outside the plasma (R ≈ 9–11 m), one tilted so +# the run has a nominal n=1 forcing, one axisymmetric so its rigid-motion response is known exactly, and one tilted the +# other way as the correction array, which is swept but is not an error-field source. Copies the deck into `dir`, runs +# it and returns main's results, the gpec.h5 path and the deck as run. +function error_fields_fixture(dir) + GPEC = GeneralizedPerturbedEquilibrium + template = joinpath(@__DIR__, "test_data", "regression_tj_ideal_example") + for name in readdir(template) + cp(joinpath(template, name), joinpath(dir, name)) + end + toml_path = joinpath(dir, "gpec.toml") + inputs = TOML.parsefile(toml_path) + inputs["ForcingTerms"] = Dict{String,Any}( + "forcing_data_format" => "coil", "mtheta_coil" => 240, "nzeta_coil" => 32, + "coil_set" => [ + Dict{String,Any}("name" => "hoop_tilted", "source" => "pf_hoop", "radius" => 12.0, "height" => 1.2, "currents" => [2.0e3], "tiltx" => [3.0]), + Dict{String,Any}("name" => "hoop_axi", "source" => "pf_hoop", "radius" => 12.0, "height" => -1.2, "currents" => [2.0e3]), + Dict{String,Any}("name" => "hoop_efc", "source" => "pf_hoop", "radius" => 12.0, "height" => 0.0, "currents" => [2.0e3], "tilty" => [2.0]) + ]) + inputs["PerturbedEquilibrium"] = Dict{String,Any}( + "compute_singular_coupling" => true) + cp(joinpath(@__DIR__, "test_data", "ErrorFields", "tolerances_two_hoops.toml"), joinpath(dir, "tolerances.toml")) + # Kinetic profiles consistent with this pressure, for the NTV torque couplings. + cp(joinpath(@__DIR__, "..", "examples", "TJ_kinetic_NTV_example", "kinetic.dat"), joinpath(dir, "kinetic.dat")) + inputs["Equilibrium"]["kinetic_file"] = "kinetic.dat" + # Exercise the kinetic pipeline at smoke-test precision, retaining all bounce harmonics. + inputs["KineticForces"] = Dict{String,Any}( + "rtol_psi" => 0.2, "rtol_pitch" => 1e-2, "atol_pitch" => 1e-5, + "rtol_x" => 1e-3, "atol_x" => 1e-6) + # Small batches exercise orchestration; distribution accuracy is tested on synthetic inputs. + inputs["ErrorFields"] = Dict{String,Any}("tolerance_file" => "tolerances.toml", + "MonteCarlo" => Dict{String,Any}("nsample" => 512, "nbatch" => 2, "seed" => 5, "nbins" => 16), + "scenario" => Dict{String,Any}("n_e" => 12.0), + "Risk" => Dict{String,Any}("nsample_threshold" => 512, "seed" => 3, "scan_scales" => [0.5, 1.0, 2.0]), + # The smallest rotation scan that exercises the table path (three shifts, no refinement). + "NTV" => Dict{String,Any}("efc_coils" => ["hoop_efc"], "rotation_scan_points" => 3, "rotation_scan_max_points" => 3)) + open(io -> TOML.print(io, inputs), toml_path, "w") + return GPEC.main([dir]), joinpath(dir, "gpec.h5"), inputs +end diff --git a/test/runtests_analysis_plots.jl b/test/runtests_analysis_plots.jl index b2df0da29..695122838 100644 --- a/test/runtests_analysis_plots.jl +++ b/test/runtests_analysis_plots.jl @@ -1,11 +1,10 @@ +using CairoMakie using HDF5 -using Plots using TOML # Every registered plot against real writer output, from the result in memory and from the gpec.h5 it was saved to: # both sources must supply the same data, draw the same plots, and refuse the same ones with the registry's reason. @testset "Analysis plots from a result or gpec.h5" begin - ENV["GKSwstype"] = "100" GPEC = GeneralizedPerturbedEquilibrium A = GPEC.Analysis @@ -51,7 +50,7 @@ using TOML sources = A._accepts(e, h5) ? (x, h5) : (x,) if A._can(e.fn, caps) for src in sources - @test e.fn(src) isa Plots.Plot + @test e.fn(src) isa Figure end else reason = A._why(A._missing(e, caps)) @@ -80,28 +79,56 @@ using TOML "currents" => [2.0e3], "tiltx" => [3.0])]), "PerturbedEquilibrium" => Dict{String,Any}("compute_singular_coupling" => true)) ) - for x in (ret.equil, ret.ffs, ret.pe) + for x in (ret.equil, ret.ffs, ret.resp, ret.pe) _check_sources(x, h5) end ret.local_stability === nothing || _check_sources(ret.local_stability, h5) for component in (:xi_psi, :b_psi, :Jb_theta, :Jb_zeta) @test _same(A.PerturbedEquilibrium._response_data(ret.pe, component), A.PerturbedEquilibrium._response_data(h5, component)) - @test A.PerturbedEquilibrium.plot_mode_spectrogram(ret.pe; component) isa Plots.Plot + @test A.PerturbedEquilibrium.plot_mode_spectrogram(ret.pe; component) isa Figure end @test_throws ArgumentError A.PerturbedEquilibrium.plot_mode_spectrogram(h5; component=:b_theta) + @test A.PerturbedEquilibrium.plot_mode_spectrogram(h5; style=:lines) isa Figure + @test_throws ArgumentError A.PerturbedEquilibrium.plot_mode_spectrogram(ret.pe; style=:contour) + + # Field views that take more than the result: any control-surface spectrum, the displaced boundary, the 3D surface. + CF = A.CoilForcing + @test CF.plot_control_surface(ret.pe, ret.pe.forcing_b_rootarea; label="the applied forcing", coils=true) isa Figure + @test CF.plot_control_surface(ret.pe, ret.pe.forcing_b_rootarea ./ maximum(abs, ret.pe.forcing_b_rootarea); arb=true) isa Figure + @test_throws ArgumentError CF.plot_control_surface(ret.pe, ret.pe.forcing_b; weighting=:flux) + @test A.PerturbedEquilibrium.plot_perturbed_field_rz(ret.pe; component=:xi_n, deform=true) isa Figure + @test CF.plot_surface_3d(ret.equil, ret.pe.forcing_modes, 1; coil_sets=ret.pe.coil_sets, mtheta=60, nphi=60) isa Figure + @test CF.plot_bn_contour(randn(32, 16), 32, 16; n=1) isa Figure + + # The response alone draws its dominant modes, with no forcing; a perturbed equilibrium inherits them. + @test A.capabilities(ret.resp) ⊆ A.capabilities(ret.pe) + @test plot_dominant_mode_surface(ret.resp; mode=length(ret.resp.dominant_singular_values)) isa Figure + @test_throws "mode must be 1 to" plot_dominant_mode_surface(ret.resp; mode=length(ret.resp.dominant_singular_values) + 1) + @test_throws "coils = true needs" CF.plot_control_surface(ret.resp, ret.resp.dominant_right_singular_vectors[:, 1]; coils=true) + # A bare equilibrium marks the q = m/n crossings of n = 1, which include every surface the solve found. + bare, solved = A.Equilibrium._surfaces(ret.equil), A.Equilibrium._surfaces(ret.ffs) + @test all(q -> any(≈(q), bare.sing_q), solved.sing_q) && !isempty(bare.sing_q) + @test plot_equilibrium_summary(ret.equil; n=2) isa Figure + # An RMPField draws its coil sources in 3D before any plasma; a mode table has no geometry to draw. + hoop = RMPField((name="hoop", source="pf_hoop", radius=12.0, height=1.2, currents=[2.0e3], tiltx=[3.0])) + @test plot_sources(hoop) isa Figure && plot_sources(2hoop - 0.5im * hoop; color=:set) isa Figure + @test_throws A.REASONS[:coil_source] plot_sources(RMPField([GPEC.ForcingTerms.ForcingMode(; n=1, m=2, amplitude=1e-4)])) + # A summary none of whose panels this run supplies is listed as unavailable, and says so when called. + @test occursin("✗ plot_error_field_summary", repr(MIME"text/plain"(), available_plots(h5))) + @test_throws A.REASONS[:ef_sensitivities] plot_error_field_summary(h5) # The catalog lists a Forward run's absent products with what to change, for a result, a path and main's tuple. listing = repr(MIME"text/plain"(), available_plots(ret.ffs)) - @test occursin("✓ ForceFreeStates.plot_mode_displacement", listing) - @test occursin("✗ ForceFreeStates.plot_delta_prime", listing) && occursin(A.REASONS[:delta_prime], listing) - @test occursin("✗ Equilibrium.plot_gse_by_theta", repr(MIME"text/plain"(), available_plots(h5))) + @test occursin(r"\nForceFreeStates\n", listing) && occursin("✓ plot_mode_displacement", listing) + @test occursin("✗ plot_delta_prime ", listing) && occursin(A.REASONS[:delta_prime], listing) @test length(available_plots(ret).sections) == count(x -> x !== nothing && any(e -> A._accepts(e, x), A.PLOTS), unique(values(ret))) - @test A.plot_counts(ret.ffs) == (11, 13) + @test A.plot_counts(ret.ffs) == (11, 15) # Optional outputs this run does not produce throw instead of drawing a placeholder. @test_throws ArgumentError A.ForceFreeStates.plot_cond_fbar(h5) - @test_throws ArgumentError A.Equilibrium.plot_gse_by_theta(h5) - @test_throws ArgumentError A.Equilibrium.plot_gse_integrated(h5) + + # The Grad-Shafranov residual plot is an unregistered stub that draws the residual computed in memory. + @test A.Equilibrium.plot_gse(GPEC.Equilibrium.equilibrium_gse!(ret.equil)) isa Figure png = joinpath(dir, "qprofile.png") A.Equilibrium.plot_qprofile(ret.equil; save_path=png) @@ -120,9 +147,15 @@ using TOML _check_sources(ret.ffs, h5) @test :delta_prime in A.capabilities(ret.ffs) && :solution_profiles ∉ A.capabilities(ret.ffs) @test_throws A.REASONS[:solution_profiles] A.ForceFreeStates.plot_mode_displacement(ret.ffs) - @test occursin("✗ ForceFreeStates.plot_mode_displacement", repr(MIME"text/plain"(), available_plots(ret.ffs))) - # The summary leaves out the ξ panel rather than failing: three panels and a blank fourth. - @test length(A.ForceFreeStates.plot_ffs_summary(ret.ffs).subplots) == 4 + @test occursin("✗ plot_mode_displacement", repr(MIME"text/plain"(), available_plots(ret.ffs))) + # The summary leaves out the ξ panel rather than failing, keeping the other four. + caps = A.capabilities(ret.ffs) + @test count(((f, _),) -> A._can(f, caps), A.ForceFreeStates._ffs_panels(ret.ffs)) == 4 + @test A.ForceFreeStates.plot_ffs_summary(ret.ffs) isa Figure + # The Δ′ family draws any part of its complex entries and refuses others. + @test A.ForceFreeStates.plot_delta_prime(ret.ffs; part=:abs) isa Figure + @test A.ForceFreeStates.plot_delta_coil(h5; part=:imag) isa Figure + @test_throws ArgumentError A.ForceFreeStates.plot_delta_prime(ret.ffs; part=:phase) end end @@ -137,7 +170,7 @@ using TOML h5 = joinpath(dir, "gpec.h5") save_to_hdf5(h5, ffs) _check_sources(ffs, h5) - @test A.ForceFreeStates.plot_cond_fbar(ffs) isa Plots.Plot + @test A.ForceFreeStates.plot_cond_fbar(ffs) isa Figure end end end diff --git a/test/runtests_error_fields.jl b/test/runtests_error_fields.jl index af15828d0..1fbdb5869 100644 --- a/test/runtests_error_fields.jl +++ b/test/runtests_error_fields.jl @@ -8,7 +8,6 @@ using GeneralizedPerturbedEquilibrium using HDF5 using TOML using LinearAlgebra -using Plots # The ErrorFields coil linearization: a closed-form check on the cancelling offset, then one # coil-forced Solovev run with an axisymmetric PF hoop (whose rigid-motion response is known @@ -16,6 +15,7 @@ using Plots # through the post-hoc gpec.h5 entry point that rebuilds the equilibrium from the file. include("h5_metadata_check.jl") include("display_check.jl") +include("error_fields_fixture.jl") @testset "error fields" begin GPEC = GeneralizedPerturbedEquilibrium @@ -62,47 +62,9 @@ include("display_check.jl") end @testset "coil-forced TJ run: identities, output, and post-hoc entry point" begin - template = joinpath(@__DIR__, "test_data", "regression_tj_ideal_example") - mktempdir() do dir - for name in readdir(template) - cp(joinpath(template, name), joinpath(dir, name)) - end - toml_path = joinpath(dir, "gpec.toml") - inputs = TOML.parsefile(toml_path) - # Three PF hoops outside the plasma (R ≈ 9–11 m): one tilted so the run has - # a nominal n=1 forcing, one axisymmetric so its rigid-motion response is known exactly, - # and one tilted the other way as the correction array, which is swept but is not an - # error-field source. - inputs["ForcingTerms"] = Dict{String,Any}( - "forcing_data_format" => "coil", "mtheta_coil" => 240, "nzeta_coil" => 32, - "coil_set" => [ - Dict{String,Any}("name" => "hoop_tilted", "source" => "pf_hoop", "radius" => 12.0, "height" => 1.2, "currents" => [2.0e3], "tiltx" => [3.0]), - Dict{String,Any}("name" => "hoop_axi", "source" => "pf_hoop", "radius" => 12.0, "height" => -1.2, "currents" => [2.0e3]), - Dict{String,Any}("name" => "hoop_efc", "source" => "pf_hoop", "radius" => 12.0, "height" => 0.0, "currents" => [2.0e3], "tilty" => [2.0]) - ]) - inputs["PerturbedEquilibrium"] = Dict{String,Any}( - "compute_singular_coupling" => true) - cp(joinpath(@__DIR__, "test_data", "ErrorFields", "tolerances_two_hoops.toml"), joinpath(dir, "tolerances.toml")) - # Kinetic profiles consistent with this pressure, for the NTV torque couplings. - cp(joinpath(@__DIR__, "..", "examples", "TJ_kinetic_NTV_example", "kinetic.dat"), joinpath(dir, "kinetic.dat")) - inputs["Equilibrium"]["kinetic_file"] = "kinetic.dat" - # Exercise the kinetic pipeline at smoke-test precision, retaining all bounce harmonics. - inputs["KineticForces"] = Dict{String,Any}( - "rtol_psi" => 0.2, "rtol_pitch" => 1e-2, "atol_pitch" => 1e-5, - "rtol_x" => 1e-3, "atol_x" => 1e-6) - # Small batches exercise orchestration; distribution accuracy is tested on synthetic inputs. - inputs["ErrorFields"] = Dict{String,Any}("tolerance_file" => "tolerances.toml", - "MonteCarlo" => Dict{String,Any}("nsample" => 512, "nbatch" => 2, "seed" => 5, "nbins" => 16), - "scenario" => Dict{String,Any}("n_e" => 12.0), - "Risk" => Dict{String,Any}("nsample_threshold" => 512, "seed" => 3, "scan_scales" => [0.5, 1.0, 2.0]), - # The smallest rotation scan that exercises the table path (three shifts, no refinement). - "NTV" => Dict{String,Any}("efc_coils" => ["hoop_efc"], "rotation_scan_points" => 3, "rotation_scan_max_points" => 3)) - open(io -> TOML.print(io, inputs), toml_path, "w") - - res = GPEC.main([dir]) + res, h5path, inputs = error_fields_fixture(dir) sens, pe, ffs = res.error_fields.sensitivities, res.pe, res.ffs - h5path = joinpath(dir, "gpec.h5") @test sens isa EF.CoilSensitivities # Per-stage wall-clock records: the ErrorFields stage must appear now that it is a @@ -241,25 +203,8 @@ include("display_check.jl") @test rescan.locking_probability_percent[1] == again.locking_probability_percent @test rescan.locking_probability_efc_percent[1] == again.locking_probability_efc_percent - # Construct every plot; render representative bar, contour, polar, and composite layouts. + # The assessment in memory supplies what its saved file does. AEF = GPEC.Analysis.ErrorFields - for (name, fn) in (("sens", AEF.plot_coil_sensitivities), ("abs_delta_pdf", AEF.plot_tolerance_pdf), ("risk", AEF.plot_locking_risk), - ("thr", AEF.plot_threshold_scaling), ("mode", AEF.plot_dominant_mode_spectrum)) - png = joinpath(dir, "plot_$name.png") - @test fn(h5path; save_path=name == "sens" ? png : nothing) isa Plots.Plot - if name == "sens" - @test isfile(png) - end - end - # The spectrum plot draws the stored dominant mode, padded to zero at both ends by the step convention. - spec = AEF.plot_dominant_mode_spectrum(h5path) - @test maximum(spec.series_list[1][:y]) ≈ maximum(abs.(dom.right_singular_vectors[:, 1])) rtol = 1e-10 - @test spec.series_list[1][:y][1] == 0.0 && spec.series_list[1][:y][end] == 0.0 - @test AEF.plot_coil_sensitivities(["run" => h5path, "again" => h5path]; quantity=:tilt)[1][:yaxis][:guide] == "|δ| per degree of tilt" - @test AEF.plot_coil_sensitivities(h5path; quantity=:rim)[1][:yaxis][:guide] == "|δ| per mm of rim displacement" - @test_throws ArgumentError AEF.plot_coil_sensitivities(h5path; quantity=:bogus) - - # The assessment in memory supplies what its saved file does, and every registered plot draws from both. ef = res.error_fields from_result, from_file = AEF._load(ef), AEF._load(h5path) for k in keys(from_result) @@ -267,45 +212,11 @@ include("display_check.jl") a, b = from_result[k], from_file[k] @test (a === nothing && b === nothing) || (a isa AbstractArray{<:Number} ? isapprox(a, b; rtol=1e-10) : isequal(a, b)) end - for e in GPEC.Analysis.PLOTS - GPEC.Analysis._accepts(e, ef) || continue - @test e.fn(ef) isa Plots.Plot - @test e.fn(h5path) isa Plots.Plot - end - # An in-memory table plots the same way a stored run does, which is the whole point of - # the dual entry points: coil geometry that was never part of a run has no file to read. - in_memory = AEF.plot_coil_sensitivities(table) - @test in_memory isa Plots.Plot - @test AEF.plot_coil_sensitivities(["file" => h5path, "memory" => table]) isa Plots.Plot - @test isequal(in_memory.series_list[1][:y], AEF.plot_coil_sensitivities(h5path).series_list[1][:y]) - - # The threshold plot needs the overlap distribution and the penetration threshold, which - # live on two different result types. A lone RiskResult cannot supply the distribution, - # so it must be skipped rather than indexed into a missing field; the pair must work. - paired = AEF.plot_threshold_scaling(["pair" => (mc, risk)]) - @test paired isa Plots.Plot - @test !isempty(paired.series_list) - @test AEF.plot_threshold_scaling(["risk only" => risk]) isa Plots.Plot - @test isequal(AEF.plot_threshold_scaling(["f" => h5path]).series_list[1][:y], - AEF.plot_threshold_scaling(["p" => (mc, risk)]).series_list[1][:y]) - - # The three coil diagnostics, from a context and from a file. + # The coil overlaps, from a context rebuilt from the file. ctx_plot = EF.ResonantDriveContext(h5path) plot_sets = FT.load_coil_sets(ctx_plot.cfg, 1; equil=ctx_plot.equil) ovs_plot = EF.coil_overlaps(ctx_plot, plot_sets) - for (name, fn) in (("spectra", AEF.plot_applied_spectra), ("contrib", AEF.plot_overlap_contributions), - ("surface", AEF.plot_surface_overlay)) - png = joinpath(dir, "diag_$name.png") - kwargs = name == "surface" ? (; ntheta=32, nzeta=24, save_path=png) : (;) - @test fn(ctx_plot, ovs_plot; kwargs...) isa Plots.Plot - if name == "surface" - @test isfile(png) - end - end - @test AEF.plot_applied_spectra(h5path, plot_sets) isa Plots.Plot - @test AEF.plot_applied_spectra(ctx_plot, ovs_plot; normalize=false)[1][:yaxis][:guide] == "|b̃| (T)" - @test length(AEF.plot_surface_overlay(ctx_plot, ovs_plot; ntheta=32, nzeta=24).subplots) == 2 * cld(length(ovs_plot), 2) # a two-column grid # The per-harmonic contributions the plot draws sum to each coil's resonant fraction, # which is what makes them readable as a decomposition. Asserted on the numbers rather @@ -317,10 +228,7 @@ include("display_check.jl") @test 100 * sum(bars) ≈ o.resonant_fraction_percent rtol = 1e-10 end # The resonant fraction the plot derives from the stored field and overlap is the one - # the overlap diagnostics report; a table alone cannot supply it, and a name no source - # carries is an error rather than an empty bar. - frac = AEF.plot_coil_sensitivities(h5path; quantity=:fraction) - @test frac[1][:yaxis][:guide] == "resonant fraction of |b̃| [%]" + # the overlap diagnostics report. fraction_drawn = AEF._sensitivity_values("run", AEF._load(h5path), sens.coil_names, :fraction) for o in ovs_plot drawn = fraction_drawn[findfirst(==(o.coil_name), sens.coil_names)] @@ -336,7 +244,7 @@ include("display_check.jl") @test 0 < only(o for o in ovs_plot if o.coil_name == "hoop_tilted").resonant_fraction_percent <= 100 @test isnan(EF.resonant_fraction_percent(1.0, 1e-20, 1.0)) && isnan(EF.resonant_fraction_percent(1.0, 1e-13, 0.0)) @test EF.resonant_fraction_percent(0.5, 1.0, 1.0) == 50.0 - @test AEF.plot_coil_sensitivities(h5path; quantity=:fraction, yscale=:log10) isa Plots.Plot + # The linear model at finite displacements: the prediction is the table's, the error of the # predicted change is small at the tolerance and shrinks with the displacement, and a @@ -354,9 +262,6 @@ include("display_check.jl") small = EF.linearity_check(ctx_plot, plot_sets, snapshot; scales=(1e-2,)) @test maximum(filter(!isnan, small.relative_error)) < maximum(finite) @test_throws ArgumentError EF.linearity_check(ctx_plot, plot_sets, snapshot; scales=(0.0,)) - @test AEF.plot_linearity_check(lin) isa Plots.Plot - @test AEF.plot_linearity_residuals(h5path; at=:step, yscale=:log10) isa Plots.Plot - @test_throws ArgumentError AEF.plot_linearity_residuals(h5path; yscale=:bogus) # The correction requirement. An array identical to the source needs exactly −1 of its # current and the joint least squares leaves nothing; a second, differently tilted hoop @@ -406,36 +311,18 @@ include("display_check.jl") from_file = EF.correction_requirement(h5path, "hoop_tilted", ["hoop_tilted"]) @test from_file.current_factor_dominant ≈ self_req.current_factor_dominant atol = 1e-10 @test_throws ArgumentError EF.correction_requirement(h5path, "hoop_tilted", ["no_such"]) - @test AEF.plot_correction_requirement(two; save_path=joinpath(dir, "requirement.png")) isa Plots.Plot - @test length(AEF.plot_correction_requirement(two).subplots) == 1 + 2 # the surfaces, then one polar panel per array - @test isfile(joinpath(dir, "requirement.png")) # The needed-current distribution is the |δ| histogram rescaled by the array's overlap. nd = EF.needed_current_distribution(mc, src) @test sum(nd.current_factor_pdf .* diff(nd.current_factor_bin_edges)) ≈ 1 atol = 1e-6 @test nd.current_factor_bin_edges[end] ≈ mc.abs_delta_bin_edges[end] / abs(src.delta) @test nd.current_bin_edges_kat ≈ nd.current_factor_bin_edges .* src.ampere_turns_kat @test sum(nd.current_pdf_per_kat .* diff(nd.current_bin_edges_kat)) ≈ 1 atol = 1e-6 - @test AEF.plot_needed_current(mc, src) isa Plots.Plot @test all( isnan(a) ? isnan(b) : isapprox(a, b; rtol=1e-8) for (a, b) in zip(AEF._sensitivity_values("ovs", AEF._load(ovs_plot), sens.coil_names, :fraction), fraction_drawn) ) - @test_throws ArgumentError AEF.plot_coil_sensitivities(table; quantity=:fraction) - @test_throws ArgumentError AEF.plot_coil_sensitivities(h5path; coils=["hoop_tilted", "no_such_coil"]) - @test_throws ArgumentError AEF.plot_coil_sensitivities(ovs_plot; quantity=:shift) - # On a logarithmic axis the values are stems and markers, never bars. - stems = AEF.plot_coil_sensitivities(h5path; yscale=:log10) - @test stems[1][:yaxis][:scale] === :log10 - @test all(st[:seriestype] in (:path, :scatter) for st in stems.series_list) - @test AEF.plot_coil_sensitivities(h5path; quantity=:as_designed, yscale=:log10) isa Plots.Plot - - # The head-to-tail phasor walk ends at the coherent as-designed total the Monte Carlo - # starts from; the tilted hoop carries the whole n=1 overlap and the axisymmetric one none. - phasors = AEF.plot_overlap_phasors(h5path) - @test phasors isa Plots.Plot + + # The head-to-tail phasor walk ends at the coherent as-designed total the Monte Carlo starts from. @test abs(sum(AEF._load(h5path).delta_as_designed[1:2])) ≈ mc.abs_delta_total_as_designed rtol = 1e-10 - @test length(AEF.plot_overlap_phasors(["file" => h5path, "memory" => table]).subplots) == 2 - @test AEF.plot_overlap_phasors(ovs_plot; coils=["hoop_axi"]) isa Plots.Plot - @test_throws ArgumentError AEF.plot_overlap_phasors(h5path; coils=["no_such_coil"]) # The worst-case budget's terms sum to the bound the Monte Carlo sized its histogram # by, from memory and from the file, and each term is what the bound charges: the @@ -456,21 +343,9 @@ include("display_check.jl") from_file = EF.worst_case_terms(h5path) @test from_file.abs_delta_worst_case ≈ mc.abs_delta_worst_case rtol = 1e-10 @test EF.worst_case_terms(table, snapshot, plot_sets; tolerance_scale=2.0).abs_delta_shift_tolerance[i_axi] > terms.abs_delta_shift_tolerance[i_axi] - budget = AEF.plot_tolerance_budget(h5path) - @test budget isa Plots.Plot - @test AEF.plot_tolerance_budget(table, snapshot, plot_sets; monte_carlo=mc, sort=:name) isa Plots.Plot - @test AEF.plot_tolerance_budget(terms; top=1, sort=:none) isa Plots.Plot - @test_throws ArgumentError AEF.plot_tolerance_budget(terms; sort=:bogus) # Linearity residuals: at the tolerance each tap is the stored ratio scaled by # tolerance / step, with the metres tilt of the tilted hoop converted through its radius. - lin_step = AEF.plot_linearity_residuals(h5path; at=:step) - @test lin_step isa Plots.Plot - @test AEF.plot_linearity_residuals(h5path) isa Plots.Plot - @test AEF.plot_linearity_residuals(sens; at=:step) isa Plots.Plot - @test AEF.plot_linearity_residuals(sens; tolerances=snapshot, fd_step_shift_m=1e-3, fd_step_tilt_deg=0.1) isa Plots.Plot - @test_throws ArgumentError AEF.plot_linearity_residuals(sens) - @test_throws ArgumentError AEF.plot_linearity_residuals(h5path; at=:bogus) i_tilted = findfirst(==("hoop_tilted"), sens.coil_names) tilt_deg = EF.tilt_tolerance_deg(0.002, "m", sens.major_radius_m[i_tilted]) @test tilt_deg ≈ EF.tilt_tolerance_deg(0.002, "m", plot_sets[findfirst(cs -> cs.name == "hoop_tilted", plot_sets)]) @@ -482,20 +357,11 @@ include("display_check.jl") @test at_tol[6, i_axi] ≈ sens.tilt_linearity_residual[3, i_axi] * (0.019 / 0.1) rtol = 1e-12 @test AEF._linearity_matrix("run", AEF._load(h5path), sens.coil_names, :step) == hcat(vcat(sens.shift_linearity_residual, sens.tilt_linearity_residual)) - # Batches draw under the mean without turning the density into bars. - batched = AEF.plot_tolerance_pdf(h5path; show_batches=true) - @test length(batched.series_list) == length(AEF.plot_tolerance_pdf(h5path).series_list) + mc.nbatch - - # The target risk adds its own line and the allowable-tolerance markers. - @test length(AEF.plot_locking_risk(h5path; target_percent=1.0).series_list) > length(AEF.plot_locking_risk(h5path).series_list) - @test length(AEF.plot_error_field_summary(h5path; save_path=joinpath(dir, "summary.png")).subplots) == 4 pmap = EF.phasing_map(h5path, ["hoop_tilted", "hoop_axi"]; nphase=36) @test length(pmap.phase_deg) == 1 && size(pmap.delta_per_kat) == (36,) kat = sens.winding_multiplier .* sens.peak_current ./ 1e3 δ_each = [dot(dom.right_singular_vectors[:, 1], sens.field_as_designed[:, j]) / kat[j] / sens.b_t0 for j in 1:2] @test pmap.delta_per_kat ≈ abs.(δ_each[1] .+ δ_each[2] .* cis.(deg2rad.(pmap.phase_deg[1]))) - @test AEF.plot_phasing_map(pmap) isa Plots.Plot - @test AEF.plot_phasing_map(h5path, ["hoop_tilted", "hoop_axi"]; nphase=12) isa Plots.Plot from_file = EF.CoilSensitivities(h5path) @test from_file.coil_names == sens.coil_names @test from_file.m_modes == sens.m_modes && from_file.n_modes == sens.n_modes @@ -567,16 +433,7 @@ include("display_check.jl") ) @test unc.probability_percent ≈ clamp(mass_beyond, 0, 100) rtol = 1e-10 @test EF.uncorrectable_probability(mc, c; delta_threshold=1e3 * mc.abs_delta_bin_edges[end], torque_budget=1.0).probability_percent == 0 - @test AEF.plot_needed_current(mc, src_efc; coupling=c, delta_threshold=risk.threshold_fit, torque_budget=1.0) isa Plots.Plot - @test_throws ArgumentError AEF.plot_needed_current(mc, src_efc; coupling=c) @test length(curve.delta_ef) == 500 && all(curve.current_linear .>= 0) - ntv_plot = GPEC.Analysis.ErrorFields.plot_efc_ntv_limits(h5path; torque_budget=1.0) - @test length(ntv_plot.series_list) >= 2 # the single-mode and NTV-limited currents - @test length(ntv_plot.subplots) == 2 # the scan adds the torque-against-rotation panel - with_profiles = GPEC.Analysis.ErrorFields.plot_efc_ntv_limits(h5path; torque_budget=1.0, profiles=true) - @test length(with_profiles.subplots) == 3 # and the deposition profile its own panel - @test with_profiles[3].series_list[1][:y] == c.torque_full_profile[:, 2] - @test with_profiles[3].series_list[2][:y] == c.torque_residual_profile[:, 2] attached = pe.response.ffs.equil.kinetic pe.response.ffs.equil.kinetic = nothing @test_throws "needs kinetic profiles" EF.efc_couplings(pe.response, sets, rc, dom, cfg, GPEC.KineticForces.KineticForcesControl()) diff --git a/test/runtests_error_fields_plots.jl b/test/runtests_error_fields_plots.jl new file mode 100644 index 000000000..5667da369 --- /dev/null +++ b/test/runtests_error_fields_plots.jl @@ -0,0 +1,115 @@ +using CairoMakie +using HDF5 +using TOML + +include("error_fields_fixture.jl") + +# Every ErrorFields figure against the coil-forced TJ run, from the assessment in memory, from its gpec.h5 and from the +# post-hoc results the plots also take. +@testset "ErrorFields plots" begin + GPEC = GeneralizedPerturbedEquilibrium + PE = GPEC.PerturbedEquilibrium + EF = GPEC.ErrorFields + FT = GPEC.ForcingTerms + AEF = GPEC.Analysis.ErrorFields + _axes(fig) = [b for b in fig.content if b isa Axis] + _plots(ax, T) = [p for p in ax.scene.plots if p isa T] + + mktempdir() do dir + res, h5path, _ = error_fields_fixture(dir) + ef = res.error_fields + sens, mc, risk = ef.sensitivities, ef.monte_carlo, ef.risk + dom = PE.dominant_coupling(PE.ResonantCoupling(res.pe.response)) + table = EF.sensitivity_table(sens, dom) + snapshot = EF.read_tolerance_snapshot(h5path) + ctx = EF.ResonantDriveContext(h5path) + sets = FT.load_coil_sets(ctx.cfg, 1; equil=ctx.equil) + ovs = EF.coil_overlaps(ctx, sets) + + # Every registered plot draws from the assessment and from its file. + for e in GPEC.Analysis.PLOTS + GPEC.Analysis._accepts(e, ef) || continue + @test e.fn(ef) isa Figure + @test e.fn(h5path) isa Figure + end + png = joinpath(dir, "summary.png") + @test AEF.plot_error_field_summary(h5path; save_path=png) isa Figure + @test isfile(png) + + # The spectrum plot draws the stored dominant mode, padded to zero at both ends by the step convention. + y = last.(_plots(only(_axes(AEF.plot_dominant_mode_spectrum(h5path))), Stairs)[1][1][]) + @test maximum(y) ≈ maximum(abs.(dom.right_singular_vectors[:, 1])) rtol = 1e-6 + @test y[1] == 0 && y[end] == 0 + + # Coil sensitivities: the quantity names the axis, several runs share it, and an in-memory table draws what + # the file does. + @test only(_axes(AEF.plot_coil_sensitivities(["run" => h5path, "again" => h5path]; quantity=:tilt))).xlabel[] == "|δ| per degree of tilt" + @test only(_axes(AEF.plot_coil_sensitivities(h5path; quantity=:rim))).xlabel[] == "|δ| per mm of rim displacement" + dots(fig) = _plots(only(_axes(fig)), Scatter)[1][1][] + @test dots(AEF.plot_coil_sensitivities(table)) == dots(AEF.plot_coil_sensitivities(h5path)) + @test AEF.plot_coil_sensitivities(["file" => h5path, "memory" => table]) isa Figure + @test AEF.plot_coil_sensitivities(h5path; quantity=:fraction, yscale=:log10) isa Figure + @test only(_axes(AEF.plot_coil_sensitivities(h5path; yscale=:log10))).xscale[] === log10 + @test_throws ArgumentError AEF.plot_coil_sensitivities(h5path; quantity=:bogus) + @test_throws ArgumentError AEF.plot_coil_sensitivities(table; quantity=:fraction) + @test_throws ArgumentError AEF.plot_coil_sensitivities(h5path; coils=["hoop_tilted", "no_such_coil"]) + + # The threshold needs the overlap distribution and the threshold together: a pair draws what the file does, + # a lone RiskResult cannot. + lines_of(fig) = [p[1][] for p in _plots(_axes(fig)[1], Lines)] + @test lines_of(AEF.plot_threshold_scaling(["pair" => (mc, risk)])) == lines_of(AEF.plot_threshold_scaling(["f" => h5path])) + @test_throws ArgumentError AEF.plot_threshold_scaling(["risk only" => risk]) + + # Distributions, risk, budget and linearity, with their options. + @test length(_plots(only(_axes(AEF.plot_tolerance_pdf(h5path; show_batches=true))), Lines)) == + length(_plots(only(_axes(AEF.plot_tolerance_pdf(h5path))), Lines)) + mc.nbatch + @test length(_plots(only(_axes(AEF.plot_locking_risk(h5path; target_percent=1.0))), HLines)) == 1 + @test AEF.plot_tolerance_budget(h5path) isa Figure + terms = EF.worst_case_terms(EF.without_coils(table, ["hoop_efc"]), snapshot, sets) + @test AEF.plot_tolerance_budget(table, snapshot, sets; monte_carlo=mc, sort=:name) isa Figure + @test AEF.plot_tolerance_budget(terms; top=1, sort=:none) isa Figure + @test_throws ArgumentError AEF.plot_tolerance_budget(terms; sort=:bogus) + @test AEF.plot_linearity_residuals(h5path; at=:step, yscale=:log10) isa Figure + @test AEF.plot_linearity_residuals(sens; tolerances=snapshot, fd_step_shift_m=1e-3, fd_step_tilt_deg=0.1) isa Figure + @test_throws ArgumentError AEF.plot_linearity_residuals(sens) + @test_throws ArgumentError AEF.plot_linearity_residuals(h5path; at=:bogus) + @test_throws ArgumentError AEF.plot_linearity_residuals(h5path; yscale=:bogus) + + # Phasors: one panel per source; the axisymmetric hoop alone has no n ≠ 0 field to draw. + @test length(_axes(AEF.plot_overlap_phasors(["file" => h5path, "memory" => table]))) == 2 + @test_throws ArgumentError AEF.plot_overlap_phasors(h5path; coils=["hoop_axi"]) + @test_throws ArgumentError AEF.plot_overlap_phasors(h5path; coils=["no_such_coil"]) + + # The coil diagnostics from a context rebuilt from the file. + @test AEF.plot_applied_spectra(ctx, ovs) isa Figure + @test AEF.plot_applied_spectra(h5path, sets) isa Figure + @test AEF.plot_overlap_contributions(ctx, ovs) isa Figure + png = joinpath(dir, "surface.png") + @test AEF.plot_surface_overlay(ctx, ovs; ntheta=32, nzeta=24, save_path=png) isa Figure + @test isfile(png) + @test_throws ArgumentError AEF.plot_coil_sensitivities(ovs; quantity=:shift) + @test AEF.plot_linearity_check(EF.linearity_check(ctx, sets, snapshot; scales=(1.0, 2.0))) isa Figure + + # The correction requirement: the surfaces, then one polar panel per array. + src = only(o for o in ovs if o.coil_name == "hoop_tilted") + cfg_y = FT.CoilConfig(; machine=ctx.cfg.machine, dat_dir=ctx.cfg.dat_dir, mtheta_coil=ctx.cfg.mtheta_coil, nzeta_coil=ctx.cfg.nzeta_coil, + coil_sets=[FT.CoilSetConfig(; name="hoop_y", source="pf_hoop", radius=1.6, height=-0.3, currents=[2.0e3], tilty=[3.0])]) + arr_y = only(EF.coil_overlaps(ctx, FT.load_coil_sets(cfg_y, 1; equil=ctx.equil))) + req = AEF.plot_correction_requirement(EF.correction_requirement(ctx, src, [src, arr_y])) + @test length(_axes(req)) == 1 && count(b -> b isa PolarAxis, req.content) == 2 + + # Phasing, the needed current and the NTV limits. + @test AEF.plot_phasing_map(EF.phasing_map(h5path, ["hoop_tilted", "hoop_axi"]; nphase=36)) isa Figure + @test AEF.plot_phasing_map(h5path, ["hoop_tilted", "hoop_axi"]; nphase=12) isa Figure + c = only(ef.couplings) + src_efc = only(o for o in ovs if o.coil_name == "hoop_efc") + @test AEF.plot_needed_current(mc, src) isa Figure + @test AEF.plot_needed_current(mc, src_efc; coupling=c, delta_threshold=risk.threshold_fit, torque_budget=1.0) isa Figure + @test_throws ArgumentError AEF.plot_needed_current(mc, src_efc; coupling=c) + # The rotation scan adds the torque-against-rotation panel, and profiles the deposition panel. + @test length(_axes(AEF.plot_efc_ntv_limits(h5path; torque_budget=1.0))) == 2 + deposition = _axes(AEF.plot_efc_ntv_limits(h5path; torque_budget=1.0, profiles=true)) + @test length(deposition) == 3 + @test last.(_plots(deposition[3], Lines)[1][1][]) ≈ c.torque_full_profile[:, 2] rtol = 1e-6 + end +end diff --git a/test/runtests_solve_api.jl b/test/runtests_solve_api.jl index 2c17a146b..41883afea 100644 --- a/test/runtests_solve_api.jl +++ b/test/runtests_solve_api.jl @@ -279,10 +279,19 @@ include("display_check.jl") end end + # A forcing-mode table and its HDF5 twin, for the construction checks. + forcing_dir = mktempdir() + table = joinpath(forcing_dir, "forcing.dat") + write(table, "# normalization: normal_field_T\n1 2 1e-4 0.0\n1 3 5e-5 0.0\n") + table_h5 = joinpath(forcing_dir, "forcing.h5") + h5open(table_h5, "w") do f + f["n"], f["m"], f["amplitude_real"], f["amplitude_imag"] = [1, 1], [2, 3], [1e-4, 5e-5], [0.0, 0.0] + end + @testset "RMPField algebra is lazy and flattens" begin - a = RMPField("a.dat") - b = RMPField("b.dat"; scale=0.5) - c = RMPField("c.dat") + a = RMPField(table) + b = RMPField(table; scale=0.5) + c = RMPField(table_h5) s = a + b @test s isa GPEC.ForcingTerms.RMPFieldSum @test length(s.terms) == 2 @@ -294,18 +303,37 @@ include("display_check.jl") @test (a * 3).scale == 3.0 + 0.0im @test (a-b).terms[2].scale == -0.5 + 0.0im @test (-a).scale == -1.0 + 0.0im + @test repr(a) == "RMPField(forcing.dat (ascii))" && repr(3 * a) == "RMPField(3 × forcing.dat (ascii))" + @test occursin("-0.5im × forcing.h5 (hdf5)", repr(MIME"text/plain"(), a - 0.5im * c)) end @testset "RMPField infers its format and carries its scale" begin - @test RMPField("forcing.dat").ctrl.forcing_data_format == "ascii" - @test RMPField("forcing.h5").ctrl.forcing_data_format == "hdf5" - @test RMPField("forcing.dat"; format="hdf5").ctrl.forcing_data_format == "hdf5" - @test isabspath(RMPField("forcing.dat").ctrl.forcing_data_file) - @test RMPField("forcing.dat"; scale=3.0).scale == 3.0 - coil_field = RMPField(Dict{String,Any}[Dict{String,Any}("name" => "iu")]; machine="d3d") + @test RMPField(table).ctrl.forcing_data_format == "ascii" + @test RMPField(table_h5).ctrl.forcing_data_format == "hdf5" + @test isabspath(RMPField(table).ctrl.forcing_data_file) + @test RMPField(table; scale=3.0).scale == 3.0 + # One coil set or several, as NamedTuples or Dicts with the [[ForcingTerms.coil_set]] keys. + c_coil = (name="c", currents=[1e3, 500, -500, -1e3, -500, 500]) + coil_field = RMPField(c_coil; machine="d3d") @test coil_field.ctrl.forcing_data_format == "coil" @test coil_field.ctrl.machine == "d3d" - @test length(coil_field.ctrl.coil_sets_raw) == 1 + @test coil_field.ctrl.coil_sets_raw == [Dict{String,Any}("name" => "c", "currents" => c_coil.currents)] + @test length(RMPField([c_coil, Dict("name" => "iu", "currents" => fill(1e3, 6))]; machine="d3d").ctrl.coil_sets_raw) == 2 + @test repr(coil_field) == "RMPField(d3d coils c)" + end + + @testset "RMPField checks its source when built" begin + @test_throws "no forcing file" RMPField(joinpath(forcing_dir, "missing.dat")) + geometry = joinpath(pkgdir(GPEC), "src", "ForcingTerms", "coil_geometries", "d3d_c.dat") + @test_throws "is not a forcing-mode table" RMPField(geometry) + @test_throws "is not a forcing-mode table" RMPField(table; format="hdf5") + @test_throws "is not \"ascii\", \"hdf5\" or \"coil\"" RMPField(table; format="csv") + @test_throws "has no currents" RMPField((name="c",); machine="d3d") + @test_throws "currents for its 6 conductors" RMPField((name="c", currents=ones(7)); machine="d3d") + @test_throws "pass machine" RMPField((name="c", currents=ones(6))) + @test_throws "unknown keys" RMPField((name="c", curents=ones(6)); machine="d3d") + @test_throws "a coil set is a Dict or NamedTuple" RMPField([1, 2]) + @test_throws "at least one forcing mode" RMPField(GPEC.ForcingTerms.ForcingMode[]) end @testset "rejected keyword combinations" begin diff --git a/test/suites.jl b/test/suites.jl index dfe79f9e4..0b05f2b51 100644 --- a/test/suites.jl +++ b/test/suites.jl @@ -31,7 +31,8 @@ const TEST_SUITES = [ "runtests_kinetic_profiles.jl" => "core", "runtests_resist_eval.jl" => "core", "runtests_localstability.jl" => "core", - "runtests_analysis_plots.jl" => "api", + "runtests_analysis_plots.jl" => "plots", + "runtests_error_fields_plots.jl" => "plots", "runtests_slayer_params.jl" => "errorfields", "runtests_slayer_riccati.jl" => "errorfields", "runtests_slayer_inputs.jl" => "errorfields",