diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index 9638907..7b65fee 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -22,7 +22,43 @@ jobs:
- name: Instantiate test environment
run: julia --project=test -e 'using Pkg; Pkg.instantiate()'
- name: Check formatting
- run: julia --project=test -e 'using Runic; Runic.main(["--check", "src/", "test/", "docs/"])'
+ run: julia --project=test -e 'using Runic; Runic.main(["--check", "src/", "test/", "docs/", "benchmarking/"])'
+
+ benchmark:
+ name: Benchmark
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ steps:
+ - uses: actions/checkout@v4
+ - uses: julia-actions/setup-julia@v1
+ with:
+ version: '1'
+ - uses: julia-actions/cache@v1
+ - name: Configure benchmark environment
+ run: |
+ julia --project=benchmarking -e '
+ using Pkg
+ Pkg.develop(PackageSpec(path=pwd()))
+ Pkg.instantiate()'
+ - name: Run request benchmarks
+ run: |
+ julia --project=benchmarking benchmarking/run_benchmarks.jl \
+ --only julia --output benchmarking/benchmark_ci.csv
+ julia --project=benchmarking benchmarking/write_ci_results.jl \
+ benchmarking/benchmark_ci.csv benchmarking/benchmark_ci.json
+ - name: Record benchmark results
+ continue-on-error: true
+ uses: benchmark-action/github-action-benchmark@v1
+ with:
+ name: KEGGAPI request time
+ tool: customSmallerIsBetter
+ output-file-path: benchmarking/benchmark_ci.json
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ auto-push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
+ alert-threshold: '200%'
+ fail-on-alert: true
+ summary-always: true
test:
name: Test (${{ matrix.version }}; ${{ matrix.os }} ${{ matrix.arch }})
diff --git a/.gitignore b/.gitignore
index ad48938..6e4b024 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@
/docs/Manifest.toml
/docs/build/
/benchmarking/Manifest.toml
+/benchmarking/benchmark_ci.*
*.png
-!benchmark_compare.png
+!benchmarking/benchmark.png
benchmarking/.Rhistory
diff --git a/benchmarking/Project.toml b/benchmarking/Project.toml
index 4546546..f2bbc0b 100644
--- a/benchmarking/Project.toml
+++ b/benchmarking/Project.toml
@@ -1,9 +1,4 @@
[deps]
-AlgebraOfGraphics = "cbdf2221-f076-402e-a563-3d30da359d67"
-CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
-CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
-Chain = "8be319e6-bccf-4806-a6f7-6fae938471bc"
-DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"
KEGGAPI = "e8256861-d17c-4800-bf17-838497555b93"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
diff --git a/benchmarking/README.md b/benchmarking/README.md
index 8aadc87..8cdd0af 100644
--- a/benchmarking/README.md
+++ b/benchmarking/README.md
@@ -1,32 +1,21 @@
# Benchmarking
This benchmark compares `KEGGAPI.jl`, `KEGGREST` for R, `Bio.KEGG.REST` for
-Python, and raw `curl` across these KEGG operations:
-
-| Operation | Query | Covered by |
-|:----------|:-------------------------------|:----------------------|
-| `Info` | `info/kegg` | all |
-| `List` | `list/pathway` | all |
-| `Find` | `find/compound/glucose` | all |
-| `Get` | `get/hsa:10458` | all |
-| `GetSeq` | `get/hsa:10458/aaseq` | all |
-| `Conv` | `conv/ncbi-geneid/eco:b0002` | all |
-| `Link` | `link/pathway/hsa:10458` | all |
-| `Ddi` | `ddi/D00564` | KEGGAPI.jl, curl only |
-
-KEGGREST and Bio.KEGG.REST do not wrap `ddi`, so the figure omits their bars for
-that operation. The `conv` case uses `ncbi-geneid` rather than
-`ncbi-proteinid` because Bio.KEGG.REST only recognises the legacy outside-database
-names (`ncbi-gi | ncbi-geneid | uniprot`) and rejects the newer ones.
-
-`KEGGAPI.jl`'s chunked `kegg_get` waits between requests to respect KEGG's rate
-limit. The Julia runner passes `request_delay = 0.0` because it already spaces
-calls and must not time the same delay twice.
-
-Each interface runs every operation in one process, which excludes interpreter
-startup from the measurements. The runner discards a warm-up call before the
-measured replicates and spaces calls by `--pause`
-seconds (default 0.4 s) to stay under KEGG's limit of 3 requests per second.
+Python, and raw `curl`. [`cases.tsv`](cases.tsv) defines 25 distinct requests for
+each operation: `info`, `list`, `find`, `get`, amino-acid `get`, `conv`, `link`,
+and `ddi`. Single-entry gene and drug requests keep response sizes bounded,
+while the `info` and `find` cases span valid databases and search terms.
+KEGGREST and Bio.KEGG.REST do not wrap `ddi`, so their distributions omit that
+operation.
+
+Each interface runs in one process, which excludes interpreter startup. One
+warm-up call per operation precedes the measurements. Calls are spaced by
+`--pause` seconds, 0.4 by default, to stay below KEGG's limit of three requests
+per second. The CSV retains each request timing, and the plot shows its
+distribution instead of reducing all requests to one value.
+
+`KEGGAPI.jl`'s chunked `kegg_get` can wait between requests. The Julia runner
+passes `request_delay = 0.0` because the runner already spaces calls.
## Running
@@ -36,13 +25,13 @@ One-time setup of the isolated benchmarking environment:
julia --project=benchmarking -e 'using Pkg; Pkg.develop(PackageSpec(path=".")); Pkg.instantiate()'
```
-Run the benchmarks (writes `benchmark_compare.csv`):
+Run the benchmarks, which writes `benchmark_compare.csv`:
```bash
-julia --project=benchmarking benchmarking/run_benchmarks.jl --nreps 15
+julia --project=benchmarking benchmarking/run_benchmarks.jl
```
-Regenerate the figure used in the main README (writes `benchmark_compare.png`):
+Regenerate the SVG used in the main README and its PNG counterpart:
```bash
julia --project=benchmarking benchmarking/plot_benchmarks.jl
@@ -100,26 +89,33 @@ Set `JULIA`, `RSCRIPT`, or `PYTHON` to select an interpreter. For example, use
```bash
PYTHON=/tmp/kegg_bench_venv/bin/python \
- julia --project=benchmarking benchmarking/run_benchmarks.jl --nreps 15
+ julia --project=benchmarking benchmarking/run_benchmarks.jl
```
+Use `--only julia,curl` to select interfaces. `--nreps N` repeats all 25 cases
+for each selected operation.
+
## Layout
| Path | Purpose |
|:-------------------------|:---------------------------------------------------|
-| `run_benchmarks.jl` | Orchestrator: runs each interface, writes the CSV |
-| `plot_benchmarks.jl` | Renders the CSV to `benchmark_compare.png` |
+| `cases.tsv` | The 25 requests for each operation |
+| `run_benchmarks.jl` | Runs each interface and writes raw timings |
+| `plot_benchmarks.jl` | Plots the request-time distributions |
+| `write_ci_results.jl` | Converts Julia timings to CI benchmark JSON |
| `runners/bench_julia.jl` | KEGGAPI.jl timings |
| `runners/bench_r.R` | KEGGREST timings |
| `runners/bench_python.py`| Bio.KEGG.REST timings |
| `runners/bench_curl.sh` | Raw REST timings via `curl -w %{time_total}` |
-Each runner prints one `Function,Language,seconds` row per replicate. Run an
+Each runner prints one `Function,Request,Language,Seconds` row per call. Run an
individual runner with:
```bash
-julia --project=benchmarking benchmarking/runners/bench_julia.jl 5 0.4
+julia --project=benchmarking benchmarking/runners/bench_julia.jl 1 0.4
```
Network round-trip time to `rest.kegg.jp` dominates these results. Compare
interfaces within one run because location and server load vary between runs.
+CI records the median Julia time for each operation and warns, without failing
+the job, when it exceeds the previous value by the configured threshold.
diff --git a/benchmarking/benchmark.png b/benchmarking/benchmark.png
new file mode 100644
index 0000000..a5e04b0
Binary files /dev/null and b/benchmarking/benchmark.png differ
diff --git a/benchmarking/benchmark.svg b/benchmarking/benchmark.svg
index 865ce74..5201721 100644
--- a/benchmarking/benchmark.svg
+++ b/benchmarking/benchmark.svg
@@ -1,598 +1,504 @@
-
-
diff --git a/benchmarking/benchmark_compare.csv b/benchmarking/benchmark_compare.csv
index 888c8a9..fe1f03f 100644
--- a/benchmarking/benchmark_compare.csv
+++ b/benchmarking/benchmark_compare.csv
@@ -1,33 +1,751 @@
-Function,Language,Median
-info,KEGGAPI.jl,0.307882604
-info,KEGGREST (R),0.34338712692260753
-info,Bio.KEGG.REST (Python),1.167023624991998
-info,curl,1.172729
-list,KEGGAPI.jl,0.3293254165
-list,KEGGREST (R),0.347944498062134
-list,Bio.KEGG.REST (Python),1.7430932079441845
-list,curl,1.6577104999999999
-find,KEGGAPI.jl,0.7023177085
-find,KEGGREST (R),0.7486366033554075
-find,Bio.KEGG.REST (Python),1.8074901875806972
-find,curl,1.5090075
-get,KEGGAPI.jl,0.273175521
-get,KEGGREST (R),0.341122031211853
-get,Bio.KEGG.REST (Python),1.111077479668893
-get,curl,1.125364
-getseq,KEGGAPI.jl,0.2717634375
-getseq,KEGGREST (R),0.29120397567749046
-getseq,Bio.KEGG.REST (Python),1.137285729055293
-getseq,curl,1.1196380000000001
-conv,KEGGAPI.jl,0.309721292
-conv,KEGGREST (R),0.3259974718093875
-conv,Bio.KEGG.REST (Python),1.1793734789825976
-conv,curl,1.1173565
-link,KEGGAPI.jl,0.31500602099999997
-link,KEGGREST (R),0.331033945083618
-link,Bio.KEGG.REST (Python),1.1386673335218802
-link,curl,1.1256504999999999
-ddi,KEGGAPI.jl,1.0907793125
-ddi,KEGGREST (R),
-ddi,Bio.KEGG.REST (Python),
-ddi,curl,3.0146245
+Function,Request,Language,Seconds
+info,info/kegg,KEGGAPI.jl,0.3077775
+info,info/pathway,KEGGAPI.jl,0.96303975
+info,info/brite,KEGGAPI.jl,0.935796208
+info,info/module,KEGGAPI.jl,0.922787416
+info,info/ko,KEGGAPI.jl,0.963228875
+info,info/genome,KEGGAPI.jl,0.99582475
+info,info/genes,KEGGAPI.jl,0.315099542
+info,info/ligand,KEGGAPI.jl,0.62070375
+info,info/compound,KEGGAPI.jl,0.621208875
+info,info/glycan,KEGGAPI.jl,0.543819458
+info,info/reaction,KEGGAPI.jl,0.53362475
+info,info/rclass,KEGGAPI.jl,0.583050083
+info,info/enzyme,KEGGAPI.jl,0.826399
+info,info/network,KEGGAPI.jl,0.509424167
+info,info/variant,KEGGAPI.jl,0.525780708
+info,info/disease,KEGGAPI.jl,0.547429292
+info,info/drug,KEGGAPI.jl,0.597524375
+info,info/dgroup,KEGGAPI.jl,0.498197167
+info,info/cel,KEGGAPI.jl,0.642881459
+info,info/hsa,KEGGAPI.jl,0.621316833
+info,info/eco,KEGGAPI.jl,1.953235917
+info,info/sce,KEGGAPI.jl,0.621393541
+info,info/ath,KEGGAPI.jl,0.622009375
+info,info/dme,KEGGAPI.jl,0.564229834
+info,info/mmu,KEGGAPI.jl,0.577999833
+list,list/hsa:10458,KEGGAPI.jl,0.722918417
+list,list/hsa:7157,KEGGAPI.jl,0.72444375
+list,list/hsa:672,KEGGAPI.jl,0.724431084
+list,list/hsa:675,KEGGAPI.jl,0.679090042
+list,list/hsa:1956,KEGGAPI.jl,0.700290667
+list,list/hsa:2064,KEGGAPI.jl,0.7044555
+list,list/hsa:5290,KEGGAPI.jl,0.709485333
+list,list/hsa:7422,KEGGAPI.jl,0.67905875
+list,list/hsa:3845,KEGGAPI.jl,0.768545708
+list,list/hsa:5970,KEGGAPI.jl,0.702529334
+list,list/hsa:4609,KEGGAPI.jl,0.74600525
+list,list/hsa:2475,KEGGAPI.jl,0.723711666
+list,list/hsa:5728,KEGGAPI.jl,0.724450375
+list,list/hsa:1029,KEGGAPI.jl,0.724611958
+list,list/hsa:3553,KEGGAPI.jl,0.725165833
+list,list/hsa:7124,KEGGAPI.jl,0.688680167
+list,list/hsa:7040,KEGGAPI.jl,0.758334667
+list,list/hsa:3091,KEGGAPI.jl,0.690401709
+list,list/hsa:3105,KEGGAPI.jl,0.757856
+list,list/hsa:4318,KEGGAPI.jl,0.72437525
+list,list/hsa:5243,KEGGAPI.jl,0.726477792
+list,list/hsa:5468,KEGGAPI.jl,0.686373792
+list,list/hsa:2099,KEGGAPI.jl,0.68047525
+list,list/hsa:2100,KEGGAPI.jl,0.700951291
+list,list/hsa:348,KEGGAPI.jl,0.724667334
+find,find/compound/glucose,KEGGAPI.jl,0.6898745
+find,find/compound/fructose,KEGGAPI.jl,0.680679584
+find,find/compound/galactose,KEGGAPI.jl,0.665822
+find,find/compound/mannose,KEGGAPI.jl,0.6789645
+find,find/compound/sucrose,KEGGAPI.jl,0.702786625
+find,find/compound/lactose,KEGGAPI.jl,0.708557084
+find,find/compound/maltose,KEGGAPI.jl,0.739025084
+find,find/compound/ribose,KEGGAPI.jl,0.72092275
+find,find/compound/xylose,KEGGAPI.jl,0.725615125
+find,find/compound/pyruvate,KEGGAPI.jl,0.727562834
+find,find/compound/citrate,KEGGAPI.jl,0.719372833
+find,find/compound/acetate,KEGGAPI.jl,1.032336875
+find,find/compound/ethanol,KEGGAPI.jl,0.724714542
+find,find/compound/cholesterol,KEGGAPI.jl,0.673380958
+find,find/compound/adenine,KEGGAPI.jl,0.678385417
+find,find/compound/guanine,KEGGAPI.jl,0.717631542
+find,find/compound/cytosine,KEGGAPI.jl,0.714372208
+find,find/compound/thymine,KEGGAPI.jl,0.733448209
+find,find/compound/uracil,KEGGAPI.jl,0.726009083
+find,find/compound/glycine,KEGGAPI.jl,0.683401834
+find,find/compound/alanine,KEGGAPI.jl,0.682084583
+find,find/compound/valine,KEGGAPI.jl,0.700989792
+find,find/compound/leucine,KEGGAPI.jl,0.7233725
+find,find/compound/serine,KEGGAPI.jl,0.726117292
+find,find/compound/cysteine,KEGGAPI.jl,0.682259375
+get,get/hsa:10458,KEGGAPI.jl,0.356735375
+get,get/hsa:7157,KEGGAPI.jl,0.272564833
+get,get/hsa:672,KEGGAPI.jl,0.357257875
+get,get/hsa:675,KEGGAPI.jl,0.275365
+get,get/hsa:1956,KEGGAPI.jl,0.356997667
+get,get/hsa:2064,KEGGAPI.jl,0.273656709
+get,get/hsa:5290,KEGGAPI.jl,0.600749209
+get,get/hsa:7422,KEGGAPI.jl,0.270681458
+get,get/hsa:3845,KEGGAPI.jl,0.313876375
+get,get/hsa:5970,KEGGAPI.jl,0.316080959
+get,get/hsa:4609,KEGGAPI.jl,0.271843042
+get,get/hsa:2475,KEGGAPI.jl,0.354312083
+get,get/hsa:5728,KEGGAPI.jl,0.275390459
+get,get/hsa:1029,KEGGAPI.jl,0.297292333
+get,get/hsa:3553,KEGGAPI.jl,0.271996125
+get,get/hsa:7124,KEGGAPI.jl,0.309515083
+get,get/hsa:7040,KEGGAPI.jl,0.314916333
+get,get/hsa:3091,KEGGAPI.jl,0.273901292
+get,get/hsa:3105,KEGGAPI.jl,0.27249875
+get,get/hsa:4318,KEGGAPI.jl,0.272858583
+get,get/hsa:5243,KEGGAPI.jl,0.280674708
+get,get/hsa:5468,KEGGAPI.jl,0.276074083
+get,get/hsa:2099,KEGGAPI.jl,0.289789208
+get,get/hsa:2100,KEGGAPI.jl,0.330727833
+get,get/hsa:348,KEGGAPI.jl,0.284997042
+getseq,get/hsa:10458/aaseq,KEGGAPI.jl,0.346396375
+getseq,get/hsa:7157/aaseq,KEGGAPI.jl,0.3104015
+getseq,get/hsa:672/aaseq,KEGGAPI.jl,0.27192175
+getseq,get/hsa:675/aaseq,KEGGAPI.jl,0.275351208
+getseq,get/hsa:1956/aaseq,KEGGAPI.jl,0.270801584
+getseq,get/hsa:2064/aaseq,KEGGAPI.jl,0.338588959
+getseq,get/hsa:5290/aaseq,KEGGAPI.jl,0.315220958
+getseq,get/hsa:7422/aaseq,KEGGAPI.jl,0.312460166
+getseq,get/hsa:3845/aaseq,KEGGAPI.jl,0.314449084
+getseq,get/hsa:5970/aaseq,KEGGAPI.jl,0.268376417
+getseq,get/hsa:4609/aaseq,KEGGAPI.jl,0.269938625
+getseq,get/hsa:2475/aaseq,KEGGAPI.jl,0.299718583
+getseq,get/hsa:5728/aaseq,KEGGAPI.jl,0.31735925
+getseq,get/hsa:1029/aaseq,KEGGAPI.jl,0.315183208
+getseq,get/hsa:3553/aaseq,KEGGAPI.jl,0.312809
+getseq,get/hsa:7124/aaseq,KEGGAPI.jl,0.313729625
+getseq,get/hsa:7040/aaseq,KEGGAPI.jl,0.271941417
+getseq,get/hsa:3091/aaseq,KEGGAPI.jl,0.269240542
+getseq,get/hsa:3105/aaseq,KEGGAPI.jl,0.311101083
+getseq,get/hsa:4318/aaseq,KEGGAPI.jl,0.305006875
+getseq,get/hsa:5243/aaseq,KEGGAPI.jl,0.3142715
+getseq,get/hsa:5468/aaseq,KEGGAPI.jl,0.313958
+getseq,get/hsa:2099/aaseq,KEGGAPI.jl,0.314631583
+getseq,get/hsa:2100/aaseq,KEGGAPI.jl,0.314252333
+getseq,get/hsa:348/aaseq,KEGGAPI.jl,0.314387959
+conv,conv/ncbi-geneid/hsa:10458,KEGGAPI.jl,0.314524708
+conv,conv/ncbi-geneid/hsa:7157,KEGGAPI.jl,0.313243791
+conv,conv/ncbi-geneid/hsa:672,KEGGAPI.jl,0.317403667
+conv,conv/ncbi-geneid/hsa:675,KEGGAPI.jl,0.312777209
+conv,conv/ncbi-geneid/hsa:1956,KEGGAPI.jl,0.319520041
+conv,conv/ncbi-geneid/hsa:2064,KEGGAPI.jl,0.3136645
+conv,conv/ncbi-geneid/hsa:5290,KEGGAPI.jl,0.316226917
+conv,conv/ncbi-geneid/hsa:7422,KEGGAPI.jl,0.309582208
+conv,conv/ncbi-geneid/hsa:3845,KEGGAPI.jl,0.310918959
+conv,conv/ncbi-geneid/hsa:5970,KEGGAPI.jl,0.314732792
+conv,conv/ncbi-geneid/hsa:4609,KEGGAPI.jl,0.416112292
+conv,conv/ncbi-geneid/hsa:2475,KEGGAPI.jl,0.315479208
+conv,conv/ncbi-geneid/hsa:5728,KEGGAPI.jl,0.31318275
+conv,conv/ncbi-geneid/hsa:1029,KEGGAPI.jl,0.314787167
+conv,conv/ncbi-geneid/hsa:3553,KEGGAPI.jl,0.315413208
+conv,conv/ncbi-geneid/hsa:7124,KEGGAPI.jl,0.313842291
+conv,conv/ncbi-geneid/hsa:7040,KEGGAPI.jl,0.318847291
+conv,conv/ncbi-geneid/hsa:3091,KEGGAPI.jl,0.312884208
+conv,conv/ncbi-geneid/hsa:3105,KEGGAPI.jl,0.311124625
+conv,conv/ncbi-geneid/hsa:4318,KEGGAPI.jl,0.34654
+conv,conv/ncbi-geneid/hsa:5243,KEGGAPI.jl,0.310097791
+conv,conv/ncbi-geneid/hsa:5468,KEGGAPI.jl,0.311857125
+conv,conv/ncbi-geneid/hsa:2099,KEGGAPI.jl,0.310580625
+conv,conv/ncbi-geneid/hsa:2100,KEGGAPI.jl,0.393567667
+conv,conv/ncbi-geneid/hsa:348,KEGGAPI.jl,0.314307583
+link,link/pathway/hsa:10458,KEGGAPI.jl,0.3188535
+link,link/pathway/hsa:7157,KEGGAPI.jl,0.314266583
+link,link/pathway/hsa:672,KEGGAPI.jl,0.314931292
+link,link/pathway/hsa:675,KEGGAPI.jl,0.3186485
+link,link/pathway/hsa:1956,KEGGAPI.jl,0.311933458
+link,link/pathway/hsa:2064,KEGGAPI.jl,0.316663833
+link,link/pathway/hsa:5290,KEGGAPI.jl,0.318829542
+link,link/pathway/hsa:7422,KEGGAPI.jl,0.313101458
+link,link/pathway/hsa:3845,KEGGAPI.jl,0.312684458
+link,link/pathway/hsa:5970,KEGGAPI.jl,0.316117875
+link,link/pathway/hsa:4609,KEGGAPI.jl,0.312753792
+link,link/pathway/hsa:2475,KEGGAPI.jl,0.3192855
+link,link/pathway/hsa:5728,KEGGAPI.jl,0.312728208
+link,link/pathway/hsa:1029,KEGGAPI.jl,0.313635541
+link,link/pathway/hsa:3553,KEGGAPI.jl,0.315000667
+link,link/pathway/hsa:7124,KEGGAPI.jl,0.315748
+link,link/pathway/hsa:7040,KEGGAPI.jl,0.315760959
+link,link/pathway/hsa:3091,KEGGAPI.jl,0.321853291
+link,link/pathway/hsa:3105,KEGGAPI.jl,0.314124
+link,link/pathway/hsa:4318,KEGGAPI.jl,0.31995
+link,link/pathway/hsa:5243,KEGGAPI.jl,0.31542175
+link,link/pathway/hsa:5468,KEGGAPI.jl,0.314314083
+link,link/pathway/hsa:2099,KEGGAPI.jl,0.31315025
+link,link/pathway/hsa:2100,KEGGAPI.jl,0.313166583
+link,link/pathway/hsa:348,KEGGAPI.jl,0.313986042
+ddi,ddi/D00006,KEGGAPI.jl,0.761043791
+ddi,ddi/D00008,KEGGAPI.jl,0.821654166
+ddi,ddi/D00015,KEGGAPI.jl,0.871222875
+ddi,ddi/D00018,KEGGAPI.jl,0.826554334
+ddi,ddi/D00020,KEGGAPI.jl,0.827731459
+ddi,ddi/D00028,KEGGAPI.jl,0.764735291
+ddi,ddi/D00032,KEGGAPI.jl,0.785970375
+ddi,ddi/D00033,KEGGAPI.jl,0.825911
+ddi,ddi/D00043,KEGGAPI.jl,0.826639375
+ddi,ddi/D00045,KEGGAPI.jl,0.827301541
+ddi,ddi/D00049,KEGGAPI.jl,0.824407584
+ddi,ddi/D00052,KEGGAPI.jl,0.827566875
+ddi,ddi/D00058,KEGGAPI.jl,0.758327
+ddi,ddi/D00059,KEGGAPI.jl,0.79421825
+ddi,ddi/D00060,KEGGAPI.jl,0.8251195
+ddi,ddi/D00062,KEGGAPI.jl,0.76262425
+ddi,ddi/D00063,KEGGAPI.jl,0.791356167
+ddi,ddi/D00066,KEGGAPI.jl,0.824179708
+ddi,ddi/D00067,KEGGAPI.jl,0.768654417
+ddi,ddi/D00068,KEGGAPI.jl,0.78047
+ddi,ddi/D00069,KEGGAPI.jl,0.763107375
+ddi,ddi/D00070,KEGGAPI.jl,0.788219459
+ddi,ddi/D00073,KEGGAPI.jl,0.764393208
+ddi,ddi/D00074,KEGGAPI.jl,0.788815917
+ddi,ddi/D00075,KEGGAPI.jl,0.825214625
+info,info/kegg,KEGGREST (R),0.312999999999999
+info,info/pathway,KEGGREST (R),1.176
+info,info/brite,KEGGREST (R),2.334
+info,info/module,KEGGREST (R),0.795999999999999
+info,info/ko,KEGGREST (R),1.37
+info,info/genome,KEGGREST (R),1.377
+info,info/genes,KEGGREST (R),0.315999999999999
+info,info/ligand,KEGGREST (R),0.598999999999997
+info,info/compound,KEGGREST (R),0.553000000000001
+info,info/glycan,KEGGREST (R),0.579000000000001
+info,info/reaction,KEGGREST (R),0.524000000000001
+info,info/rclass,KEGGREST (R),0.533000000000001
+info,info/enzyme,KEGGREST (R),0.811
+info,info/network,KEGGREST (R),0.527000000000001
+info,info/variant,KEGGREST (R),0.593999999999998
+info,info/disease,KEGGREST (R),0.594999999999999
+info,info/drug,KEGGREST (R),0.583000000000002
+info,info/dgroup,KEGGREST (R),0.509
+info,info/cel,KEGGREST (R),0.570999999999998
+info,info/hsa,KEGGREST (R),0.583000000000002
+info,info/eco,KEGGREST (R),0.564999999999998
+info,info/sce,KEGGREST (R),0.567
+info,info/ath,KEGGREST (R),0.568000000000005
+info,info/dme,KEGGREST (R),0.633000000000003
+info,info/mmu,KEGGREST (R),0.561
+list,list/hsa:10458,KEGGREST (R),0.700000000000003
+list,list/hsa:7157,KEGGREST (R),0.693000000000005
+list,list/hsa:672,KEGGREST (R),0.692
+list,list/hsa:675,KEGGREST (R),0.706999999999994
+list,list/hsa:1956,KEGGREST (R),0.713999999999999
+list,list/hsa:2064,KEGGREST (R),0.724999999999994
+list,list/hsa:5290,KEGGREST (R),0.787999999999997
+list,list/hsa:7422,KEGGREST (R),0.716000000000001
+list,list/hsa:3845,KEGGREST (R),0.731999999999999
+list,list/hsa:5970,KEGGREST (R),0.720999999999997
+list,list/hsa:4609,KEGGREST (R),0.677
+list,list/hsa:2475,KEGGREST (R),0.667000000000002
+list,list/hsa:5728,KEGGREST (R),0.722000000000001
+list,list/hsa:1029,KEGGREST (R),0.725000000000001
+list,list/hsa:3553,KEGGREST (R),0.719999999999999
+list,list/hsa:7124,KEGGREST (R),0.722000000000001
+list,list/hsa:7040,KEGGREST (R),0.713000000000001
+list,list/hsa:3091,KEGGREST (R),0.737000000000002
+list,list/hsa:3105,KEGGREST (R),0.710999999999999
+list,list/hsa:4318,KEGGREST (R),0.736000000000004
+list,list/hsa:5243,KEGGREST (R),0.722000000000001
+list,list/hsa:5468,KEGGREST (R),0.722999999999999
+list,list/hsa:2099,KEGGREST (R),0.676000000000002
+list,list/hsa:2100,KEGGREST (R),0.655000000000001
+list,list/hsa:348,KEGGREST (R),0.701999999999998
+find,find/compound/glucose,KEGGREST (R),0.709000000000003
+find,find/compound/fructose,KEGGREST (R),0.769000000000005
+find,find/compound/galactose,KEGGREST (R),0.722000000000008
+find,find/compound/mannose,KEGGREST (R),0.722000000000008
+find,find/compound/sucrose,KEGGREST (R),0.721999999999994
+find,find/compound/lactose,KEGGREST (R),0.688999999999993
+find,find/compound/maltose,KEGGREST (R),0.762
+find,find/compound/ribose,KEGGREST (R),0.652000000000001
+find,find/compound/xylose,KEGGREST (R),0.701999999999998
+find,find/compound/pyruvate,KEGGREST (R),0.709000000000003
+find,find/compound/citrate,KEGGREST (R),0.721999999999994
+find,find/compound/acetate,KEGGREST (R),0.703999999999994
+find,find/compound/ethanol,KEGGREST (R),0.694000000000003
+find,find/compound/cholesterol,KEGGREST (R),0.677999999999997
+find,find/compound/adenine,KEGGREST (R),0.712000000000003
+find,find/compound/guanine,KEGGREST (R),0.670000000000002
+find,find/compound/cytosine,KEGGREST (R),0.661999999999992
+find,find/compound/thymine,KEGGREST (R),0.728999999999999
+find,find/compound/uracil,KEGGREST (R),0.671999999999997
+find,find/compound/glycine,KEGGREST (R),0.777000000000001
+find,find/compound/alanine,KEGGREST (R),0.697000000000003
+find,find/compound/valine,KEGGREST (R),0.691000000000003
+find,find/compound/leucine,KEGGREST (R),0.674999999999997
+find,find/compound/serine,KEGGREST (R),0.718000000000004
+find,find/compound/cysteine,KEGGREST (R),0.73299999999999
+get,get/hsa:10458,KEGGREST (R),0.344999999999999
+get,get/hsa:7157,KEGGREST (R),0.328000000000003
+get,get/hsa:672,KEGGREST (R),0.367999999999995
+get,get/hsa:675,KEGGREST (R),0.356999999999999
+get,get/hsa:1956,KEGGREST (R),0.361000000000004
+get,get/hsa:2064,KEGGREST (R),0.343000000000004
+get,get/hsa:5290,KEGGREST (R),0.408999999999992
+get,get/hsa:7422,KEGGREST (R),0.338000000000008
+get,get/hsa:3845,KEGGREST (R),0.379999999999995
+get,get/hsa:5970,KEGGREST (R),0.356999999999999
+get,get/hsa:4609,KEGGREST (R),0.341000000000008
+get,get/hsa:2475,KEGGREST (R),0.351000000000013
+get,get/hsa:5728,KEGGREST (R),0.317000000000007
+get,get/hsa:1029,KEGGREST (R),0.332000000000008
+get,get/hsa:3553,KEGGREST (R),0.349000000000004
+get,get/hsa:7124,KEGGREST (R),0.411999999999992
+get,get/hsa:7040,KEGGREST (R),0.34899999999999
+get,get/hsa:3091,KEGGREST (R),0.325000000000003
+get,get/hsa:3105,KEGGREST (R),0.334000000000003
+get,get/hsa:4318,KEGGREST (R),0.318000000000012
+get,get/hsa:5243,KEGGREST (R),0.298000000000002
+get,get/hsa:5468,KEGGREST (R),0.317000000000007
+get,get/hsa:2099,KEGGREST (R),0.347999999999999
+get,get/hsa:2100,KEGGREST (R),0.334000000000003
+get,get/hsa:348,KEGGREST (R),0.304000000000002
+getseq,get/hsa:10458/aaseq,KEGGREST (R),0.277000000000001
+getseq,get/hsa:7157/aaseq,KEGGREST (R),0.287000000000006
+getseq,get/hsa:672/aaseq,KEGGREST (R),0.283999999999992
+getseq,get/hsa:675/aaseq,KEGGREST (R),0.277000000000001
+getseq,get/hsa:1956/aaseq,KEGGREST (R),0.278000000000006
+getseq,get/hsa:2064/aaseq,KEGGREST (R),0.280000000000001
+getseq,get/hsa:5290/aaseq,KEGGREST (R),0.284000000000006
+getseq,get/hsa:7422/aaseq,KEGGREST (R),0.283999999999992
+getseq,get/hsa:3845/aaseq,KEGGREST (R),0.284000000000006
+getseq,get/hsa:5970/aaseq,KEGGREST (R),0.283000000000001
+getseq,get/hsa:4609/aaseq,KEGGREST (R),0.319000000000003
+getseq,get/hsa:2475/aaseq,KEGGREST (R),0.281000000000006
+getseq,get/hsa:5728/aaseq,KEGGREST (R),0.283000000000001
+getseq,get/hsa:1029/aaseq,KEGGREST (R),0.285000000000011
+getseq,get/hsa:3553/aaseq,KEGGREST (R),0.280000000000001
+getseq,get/hsa:7124/aaseq,KEGGREST (R),0.274000000000001
+getseq,get/hsa:7040/aaseq,KEGGREST (R),0.286000000000001
+getseq,get/hsa:3091/aaseq,KEGGREST (R),0.277000000000001
+getseq,get/hsa:3105/aaseq,KEGGREST (R),0.272999999999996
+getseq,get/hsa:4318/aaseq,KEGGREST (R),0.282000000000011
+getseq,get/hsa:5243/aaseq,KEGGREST (R),0.284999999999997
+getseq,get/hsa:5468/aaseq,KEGGREST (R),0.275999999999996
+getseq,get/hsa:2099/aaseq,KEGGREST (R),0.284999999999997
+getseq,get/hsa:2100/aaseq,KEGGREST (R),0.296000000000006
+getseq,get/hsa:348/aaseq,KEGGREST (R),0.283999999999992
+conv,conv/ncbi-geneid/hsa:10458,KEGGREST (R),0.334000000000003
+conv,conv/ncbi-geneid/hsa:7157,KEGGREST (R),0.318999999999988
+conv,conv/ncbi-geneid/hsa:672,KEGGREST (R),0.317000000000007
+conv,conv/ncbi-geneid/hsa:675,KEGGREST (R),0.319999999999993
+conv,conv/ncbi-geneid/hsa:1956,KEGGREST (R),0.326999999999998
+conv,conv/ncbi-geneid/hsa:2064,KEGGREST (R),0.316000000000003
+conv,conv/ncbi-geneid/hsa:5290,KEGGREST (R),0.361000000000018
+conv,conv/ncbi-geneid/hsa:7422,KEGGREST (R),0.325999999999993
+conv,conv/ncbi-geneid/hsa:3845,KEGGREST (R),0.320999999999998
+conv,conv/ncbi-geneid/hsa:5970,KEGGREST (R),0.323999999999984
+conv,conv/ncbi-geneid/hsa:4609,KEGGREST (R),0.319999999999993
+conv,conv/ncbi-geneid/hsa:2475,KEGGREST (R),0.320000000000022
+conv,conv/ncbi-geneid/hsa:5728,KEGGREST (R),0.316000000000003
+conv,conv/ncbi-geneid/hsa:1029,KEGGREST (R),0.317000000000007
+conv,conv/ncbi-geneid/hsa:3553,KEGGREST (R),0.323000000000008
+conv,conv/ncbi-geneid/hsa:7124,KEGGREST (R),0.37299999999999
+conv,conv/ncbi-geneid/hsa:7040,KEGGREST (R),0.318000000000012
+conv,conv/ncbi-geneid/hsa:3091,KEGGREST (R),0.322000000000003
+conv,conv/ncbi-geneid/hsa:3105,KEGGREST (R),0.310000000000002
+conv,conv/ncbi-geneid/hsa:4318,KEGGREST (R),0.317000000000007
+conv,conv/ncbi-geneid/hsa:5243,KEGGREST (R),0.370999999999981
+conv,conv/ncbi-geneid/hsa:5468,KEGGREST (R),0.332999999999998
+conv,conv/ncbi-geneid/hsa:2099,KEGGREST (R),0.317999999999984
+conv,conv/ncbi-geneid/hsa:2100,KEGGREST (R),0.313999999999993
+conv,conv/ncbi-geneid/hsa:348,KEGGREST (R),0.317999999999984
+link,link/pathway/hsa:10458,KEGGREST (R),0.326999999999998
+link,link/pathway/hsa:7157,KEGGREST (R),0.323999999999984
+link,link/pathway/hsa:672,KEGGREST (R),0.322000000000003
+link,link/pathway/hsa:675,KEGGREST (R),0.321000000000026
+link,link/pathway/hsa:1956,KEGGREST (R),0.325999999999993
+link,link/pathway/hsa:2064,KEGGREST (R),0.323999999999984
+link,link/pathway/hsa:5290,KEGGREST (R),0.322000000000003
+link,link/pathway/hsa:7422,KEGGREST (R),0.321999999999974
+link,link/pathway/hsa:3845,KEGGREST (R),0.362000000000023
+link,link/pathway/hsa:5970,KEGGREST (R),0.319000000000017
+link,link/pathway/hsa:4609,KEGGREST (R),0.358000000000004
+link,link/pathway/hsa:2475,KEGGREST (R),0.322000000000003
+link,link/pathway/hsa:5728,KEGGREST (R),0.323000000000008
+link,link/pathway/hsa:1029,KEGGREST (R),0.319999999999993
+link,link/pathway/hsa:3553,KEGGREST (R),0.319999999999993
+link,link/pathway/hsa:7124,KEGGREST (R),0.317000000000007
+link,link/pathway/hsa:7040,KEGGREST (R),0.328000000000003
+link,link/pathway/hsa:3091,KEGGREST (R),0.317999999999984
+link,link/pathway/hsa:3105,KEGGREST (R),0.322000000000003
+link,link/pathway/hsa:4318,KEGGREST (R),0.322000000000003
+link,link/pathway/hsa:5243,KEGGREST (R),0.320999999999998
+link,link/pathway/hsa:5468,KEGGREST (R),0.322000000000003
+link,link/pathway/hsa:2099,KEGGREST (R),0.322000000000003
+link,link/pathway/hsa:2100,KEGGREST (R),0.319999999999993
+link,link/pathway/hsa:348,KEGGREST (R),0.321999999999974
+info,info/kegg,Bio.KEGG.REST (Python),1.1378032910000009
+info,info/pathway,Bio.KEGG.REST (Python),2.259181583
+info,info/brite,Bio.KEGG.REST (Python),2.052842709
+info,info/module,Bio.KEGG.REST (Python),2.6720954999999975
+info,info/ko,Bio.KEGG.REST (Python),2.0495354999999975
+info,info/genome,Bio.KEGG.REST (Python),2.668302875000002
+info,info/genes,Bio.KEGG.REST (Python),1.5442346250000014
+info,info/ligand,Bio.KEGG.REST (Python),1.8475924170000013
+info,info/compound,Bio.KEGG.REST (Python),1.7816276659999986
+info,info/glycan,Bio.KEGG.REST (Python),1.7350019169999982
+info,info/reaction,Bio.KEGG.REST (Python),1.8257569590000031
+info,info/rclass,Bio.KEGG.REST (Python),2.772457250000002
+info,info/enzyme,Bio.KEGG.REST (Python),2.105032792000003
+info,info/network,Bio.KEGG.REST (Python),1.5964059169999985
+info,info/variant,Bio.KEGG.REST (Python),1.537703124999993
+info,info/disease,Bio.KEGG.REST (Python),1.5240346659999986
+info,info/drug,Bio.KEGG.REST (Python),1.6628786249999976
+info,info/dgroup,Bio.KEGG.REST (Python),1.5764427080000019
+info,info/cel,Bio.KEGG.REST (Python),1.6372939580000008
+info,info/hsa,Bio.KEGG.REST (Python),1.7642906669999974
+info,info/eco,Bio.KEGG.REST (Python),1.634666666000001
+info,info/sce,Bio.KEGG.REST (Python),1.7353652919999973
+info,info/ath,Bio.KEGG.REST (Python),1.6279540830000059
+info,info/dme,Bio.KEGG.REST (Python),1.641668249999995
+info,info/mmu,Bio.KEGG.REST (Python),1.6841685840000054
+list,list/hsa:10458,Bio.KEGG.REST (Python),1.9267421659999968
+list,list/hsa:7157,Bio.KEGG.REST (Python),1.936393707999997
+list,list/hsa:672,Bio.KEGG.REST (Python),1.8476595420000024
+list,list/hsa:675,Bio.KEGG.REST (Python),1.7531275829999942
+list,list/hsa:1956,Bio.KEGG.REST (Python),1.8420920829999972
+list,list/hsa:2064,Bio.KEGG.REST (Python),1.9484575419999999
+list,list/hsa:5290,Bio.KEGG.REST (Python),1.9512792919999953
+list,list/hsa:7422,Bio.KEGG.REST (Python),1.8207060830000046
+list,list/hsa:3845,Bio.KEGG.REST (Python),1.8234489580000002
+list,list/hsa:5970,Bio.KEGG.REST (Python),1.700368791999992
+list,list/hsa:4609,Bio.KEGG.REST (Python),1.7480958329999936
+list,list/hsa:2475,Bio.KEGG.REST (Python),1.850909915999992
+list,list/hsa:5728,Bio.KEGG.REST (Python),1.951909624999999
+list,list/hsa:1029,Bio.KEGG.REST (Python),1.8555264999999963
+list,list/hsa:3553,Bio.KEGG.REST (Python),1.9439987909999985
+list,list/hsa:7124,Bio.KEGG.REST (Python),1.7259076659999977
+list,list/hsa:7040,Bio.KEGG.REST (Python),1.8532386669999994
+list,list/hsa:3091,Bio.KEGG.REST (Python),1.8632421249999993
+list,list/hsa:3105,Bio.KEGG.REST (Python),1.965623249999993
+list,list/hsa:4318,Bio.KEGG.REST (Python),1.9397121660000067
+list,list/hsa:5243,Bio.KEGG.REST (Python),1.8659156249999995
+list,list/hsa:5468,Bio.KEGG.REST (Python),1.92992979200001
+list,list/hsa:2099,Bio.KEGG.REST (Python),1.851579166999997
+list,list/hsa:2100,Bio.KEGG.REST (Python),2.0641882909999936
+list,list/hsa:348,Bio.KEGG.REST (Python),2.0410075000000063
+find,find/compound/glucose,Bio.KEGG.REST (Python),2.1579835419999824
+find,find/compound/fructose,Bio.KEGG.REST (Python),2.0562182500000006
+find,find/compound/galactose,Bio.KEGG.REST (Python),1.5416677080000056
+find,find/compound/mannose,Bio.KEGG.REST (Python),1.5113994169999785
+find,find/compound/sucrose,Bio.KEGG.REST (Python),1.7765986660000124
+find,find/compound/lactose,Bio.KEGG.REST (Python),1.6023077920000048
+find,find/compound/maltose,Bio.KEGG.REST (Python),1.6573601249999967
+find,find/compound/ribose,Bio.KEGG.REST (Python),1.5697123749999946
+find,find/compound/xylose,Bio.KEGG.REST (Python),1.5397108749999973
+find,find/compound/pyruvate,Bio.KEGG.REST (Python),1.621569041999976
+find,find/compound/citrate,Bio.KEGG.REST (Python),1.565646541999996
+find,find/compound/acetate,Bio.KEGG.REST (Python),2.136780625
+find,find/compound/ethanol,Bio.KEGG.REST (Python),1.971567666999988
+find,find/compound/cholesterol,Bio.KEGG.REST (Python),1.607041584000001
+find,find/compound/adenine,Bio.KEGG.REST (Python),1.6821092919999785
+find,find/compound/guanine,Bio.KEGG.REST (Python),1.7476097910000021
+find,find/compound/cytosine,Bio.KEGG.REST (Python),1.5476472919999935
+find,find/compound/thymine,Bio.KEGG.REST (Python),1.6381023749999883
+find,find/compound/uracil,Bio.KEGG.REST (Python),1.6441996249999988
+find,find/compound/glycine,Bio.KEGG.REST (Python),1.4687432089999959
+find,find/compound/alanine,Bio.KEGG.REST (Python),1.8178203749999966
+find,find/compound/valine,Bio.KEGG.REST (Python),1.5512657080000167
+find,find/compound/leucine,Bio.KEGG.REST (Python),1.5342897919999814
+find,find/compound/serine,Bio.KEGG.REST (Python),1.5143259580000006
+find,find/compound/cysteine,Bio.KEGG.REST (Python),1.536184416000026
+get,get/hsa:10458,Bio.KEGG.REST (Python),1.1664195419999999
+get,get/hsa:7157,Bio.KEGG.REST (Python),1.28126658299999
+get,get/hsa:672,Bio.KEGG.REST (Python),1.3383629169999836
+get,get/hsa:675,Bio.KEGG.REST (Python),1.4438358749999907
+get,get/hsa:1956,Bio.KEGG.REST (Python),1.4361752080000088
+get,get/hsa:2064,Bio.KEGG.REST (Python),1.296142792000012
+get,get/hsa:5290,Bio.KEGG.REST (Python),1.7445253340000022
+get,get/hsa:7422,Bio.KEGG.REST (Python),1.2338122499999997
+get,get/hsa:3845,Bio.KEGG.REST (Python),1.3781010419999973
+get,get/hsa:5970,Bio.KEGG.REST (Python),1.3974503330000232
+get,get/hsa:4609,Bio.KEGG.REST (Python),1.1026948749999974
+get,get/hsa:2475,Bio.KEGG.REST (Python),3.9425574170000175
+get,get/hsa:5728,Bio.KEGG.REST (Python),1.0311535000000163
+get,get/hsa:1029,Bio.KEGG.REST (Python),1.1159964160000015
+get,get/hsa:3553,Bio.KEGG.REST (Python),1.0675411670000017
+get,get/hsa:7124,Bio.KEGG.REST (Python),1.4035540419999961
+get,get/hsa:7040,Bio.KEGG.REST (Python),1.0284751670000105
+get,get/hsa:3091,Bio.KEGG.REST (Python),1.0417248329999893
+get,get/hsa:3105,Bio.KEGG.REST (Python),1.2218096670000023
+get,get/hsa:4318,Bio.KEGG.REST (Python),1.131122041999987
+get,get/hsa:5243,Bio.KEGG.REST (Python),1.145474207999996
+get,get/hsa:5468,Bio.KEGG.REST (Python),1.188769083000011
+get,get/hsa:2099,Bio.KEGG.REST (Python),1.0474247089999835
+get,get/hsa:2100,Bio.KEGG.REST (Python),1.0234109999999816
+get,get/hsa:348,Bio.KEGG.REST (Python),1.2552709159999722
+getseq,get/hsa:10458/aaseq,Bio.KEGG.REST (Python),1.4446801659999835
+getseq,get/hsa:7157/aaseq,Bio.KEGG.REST (Python),1.4872600829999953
+getseq,get/hsa:672/aaseq,Bio.KEGG.REST (Python),1.2895276249999768
+getseq,get/hsa:675/aaseq,Bio.KEGG.REST (Python),1.0388489580000169
+getseq,get/hsa:1956/aaseq,Bio.KEGG.REST (Python),1.0286315839999816
+getseq,get/hsa:2064/aaseq,Bio.KEGG.REST (Python),1.0227877500000204
+getseq,get/hsa:5290/aaseq,Bio.KEGG.REST (Python),1.0754189170000075
+getseq,get/hsa:7422/aaseq,Bio.KEGG.REST (Python),1.0341356670000152
+getseq,get/hsa:3845/aaseq,Bio.KEGG.REST (Python),1.0383827080000003
+getseq,get/hsa:5970/aaseq,Bio.KEGG.REST (Python),1.0346831249999866
+getseq,get/hsa:4609/aaseq,Bio.KEGG.REST (Python),1.10999187500002
+getseq,get/hsa:2475/aaseq,Bio.KEGG.REST (Python),1.0368355839999879
+getseq,get/hsa:5728/aaseq,Bio.KEGG.REST (Python),1.071545083000018
+getseq,get/hsa:1029/aaseq,Bio.KEGG.REST (Python),1.027099082999996
+getseq,get/hsa:3553/aaseq,Bio.KEGG.REST (Python),1.0338142919999882
+getseq,get/hsa:7124/aaseq,Bio.KEGG.REST (Python),1.1326496660000203
+getseq,get/hsa:7040/aaseq,Bio.KEGG.REST (Python),1.643319417000015
+getseq,get/hsa:3091/aaseq,Bio.KEGG.REST (Python),1.2871899580000274
+getseq,get/hsa:3105/aaseq,Bio.KEGG.REST (Python),1.3294830830000137
+getseq,get/hsa:4318/aaseq,Bio.KEGG.REST (Python),1.2646227079999903
+getseq,get/hsa:5243/aaseq,Bio.KEGG.REST (Python),1.2711517089999802
+getseq,get/hsa:5468/aaseq,Bio.KEGG.REST (Python),1.2695187499999747
+getseq,get/hsa:2099/aaseq,Bio.KEGG.REST (Python),1.2725486670000237
+getseq,get/hsa:2100/aaseq,Bio.KEGG.REST (Python),1.3201035839999804
+getseq,get/hsa:348/aaseq,Bio.KEGG.REST (Python),1.3887070000000108
+conv,conv/ncbi-geneid/hsa:10458,Bio.KEGG.REST (Python),1.3768580839999913
+conv,conv/ncbi-geneid/hsa:7157,Bio.KEGG.REST (Python),1.3060283749999826
+conv,conv/ncbi-geneid/hsa:672,Bio.KEGG.REST (Python),1.419429124999965
+conv,conv/ncbi-geneid/hsa:675,Bio.KEGG.REST (Python),1.383622833000004
+conv,conv/ncbi-geneid/hsa:1956,Bio.KEGG.REST (Python),1.3958427910000069
+conv,conv/ncbi-geneid/hsa:2064,Bio.KEGG.REST (Python),1.5400292080000213
+conv,conv/ncbi-geneid/hsa:5290,Bio.KEGG.REST (Python),4.613783832999957
+conv,conv/ncbi-geneid/hsa:7422,Bio.KEGG.REST (Python),1.3350833339999895
+conv,conv/ncbi-geneid/hsa:3845,Bio.KEGG.REST (Python),1.3941767910000067
+conv,conv/ncbi-geneid/hsa:5970,Bio.KEGG.REST (Python),1.3142880839999975
+conv,conv/ncbi-geneid/hsa:4609,Bio.KEGG.REST (Python),1.3238829589999455
+conv,conv/ncbi-geneid/hsa:2475,Bio.KEGG.REST (Python),1.3569682090000015
+conv,conv/ncbi-geneid/hsa:5728,Bio.KEGG.REST (Python),1.3128397079999559
+conv,conv/ncbi-geneid/hsa:1029,Bio.KEGG.REST (Python),1.3374027080000133
+conv,conv/ncbi-geneid/hsa:3553,Bio.KEGG.REST (Python),1.3272456669999997
+conv,conv/ncbi-geneid/hsa:7124,Bio.KEGG.REST (Python),1.3378876660000287
+conv,conv/ncbi-geneid/hsa:7040,Bio.KEGG.REST (Python),1.4399041670000088
+conv,conv/ncbi-geneid/hsa:3091,Bio.KEGG.REST (Python),1.3349166659999696
+conv,conv/ncbi-geneid/hsa:3105,Bio.KEGG.REST (Python),1.3862223749999885
+conv,conv/ncbi-geneid/hsa:4318,Bio.KEGG.REST (Python),1.2973417499999869
+conv,conv/ncbi-geneid/hsa:5243,Bio.KEGG.REST (Python),1.3109360419999803
+conv,conv/ncbi-geneid/hsa:5468,Bio.KEGG.REST (Python),1.3305285829999889
+conv,conv/ncbi-geneid/hsa:2099,Bio.KEGG.REST (Python),1.3210788339999908
+conv,conv/ncbi-geneid/hsa:2100,Bio.KEGG.REST (Python),1.3180549999999585
+conv,conv/ncbi-geneid/hsa:348,Bio.KEGG.REST (Python),1.4988959589999808
+link,link/pathway/hsa:10458,Bio.KEGG.REST (Python),1.3288588749999803
+link,link/pathway/hsa:7157,Bio.KEGG.REST (Python),1.3457278750000228
+link,link/pathway/hsa:672,Bio.KEGG.REST (Python),1.3125581249999527
+link,link/pathway/hsa:675,Bio.KEGG.REST (Python),1.3022608750000018
+link,link/pathway/hsa:1956,Bio.KEGG.REST (Python),1.3175900830000273
+link,link/pathway/hsa:2064,Bio.KEGG.REST (Python),1.3273051250000094
+link,link/pathway/hsa:5290,Bio.KEGG.REST (Python),1.3433206249999898
+link,link/pathway/hsa:7422,Bio.KEGG.REST (Python),1.322914959000002
+link,link/pathway/hsa:3845,Bio.KEGG.REST (Python),1.1359465829999635
+link,link/pathway/hsa:5970,Bio.KEGG.REST (Python),1.1417904580000027
+link,link/pathway/hsa:4609,Bio.KEGG.REST (Python),1.3258713749999629
+link,link/pathway/hsa:2475,Bio.KEGG.REST (Python),1.1387891669999703
+link,link/pathway/hsa:5728,Bio.KEGG.REST (Python),1.0752243750000048
+link,link/pathway/hsa:1029,Bio.KEGG.REST (Python),1.4973894589999759
+link,link/pathway/hsa:3553,Bio.KEGG.REST (Python),1.4009690409999962
+link,link/pathway/hsa:7124,Bio.KEGG.REST (Python),1.475534916000015
+link,link/pathway/hsa:7040,Bio.KEGG.REST (Python),1.3431667920000336
+link,link/pathway/hsa:3091,Bio.KEGG.REST (Python),1.3109967920000258
+link,link/pathway/hsa:3105,Bio.KEGG.REST (Python),1.4512476670000183
+link,link/pathway/hsa:4318,Bio.KEGG.REST (Python),1.4566849999999931
+link,link/pathway/hsa:5243,Bio.KEGG.REST (Python),1.424360374999992
+link,link/pathway/hsa:5468,Bio.KEGG.REST (Python),1.3410890829999857
+link,link/pathway/hsa:2099,Bio.KEGG.REST (Python),1.3325150830000325
+link,link/pathway/hsa:2100,Bio.KEGG.REST (Python),1.3314765419999617
+link,link/pathway/hsa:348,Bio.KEGG.REST (Python),1.3103822079999645
+info,info/kegg,curl,1.322392
+info,info/pathway,curl,1.897206
+info,info/brite,curl,1.796503
+info,info/module,curl,1.928149
+info,info/ko,curl,2.010719
+info,info/genome,curl,2.068994
+info,info/genes,curl,1.330207
+info,info/ligand,curl,1.637724
+info,info/compound,curl,1.57954
+info,info/glycan,curl,1.517033
+info,info/reaction,curl,1.496665
+info,info/rclass,curl,1.555349
+info,info/enzyme,curl,1.838024
+info,info/network,curl,1.565784
+info,info/variant,curl,1.610395
+info,info/disease,curl,1.524115
+info,info/drug,curl,1.558095
+info,info/dgroup,curl,1.495107
+info,info/cel,curl,1.559044
+info,info/hsa,curl,1.56265
+info,info/eco,curl,1.578661
+info,info/sce,curl,1.535565
+info,info/ath,curl,1.54854
+info,info/dme,curl,1.549765
+info,info/mmu,curl,1.546549
+list,list/hsa:10458,curl,1.754025
+list,list/hsa:7157,curl,1.659672
+list,list/hsa:672,curl,1.692028
+list,list/hsa:675,curl,1.667961
+list,list/hsa:1956,curl,1.669167
+list,list/hsa:2064,curl,1.710764
+list,list/hsa:5290,curl,1.723103
+list,list/hsa:7422,curl,1.718145
+list,list/hsa:3845,curl,1.762285
+list,list/hsa:5970,curl,1.659278
+list,list/hsa:4609,curl,1.734134
+list,list/hsa:2475,curl,1.792645
+list,list/hsa:5728,curl,1.728749
+list,list/hsa:1029,curl,1.712311
+list,list/hsa:3553,curl,1.719025
+list,list/hsa:7124,curl,1.705707
+list,list/hsa:7040,curl,1.702842
+list,list/hsa:3091,curl,1.757566
+list,list/hsa:3105,curl,1.762245
+list,list/hsa:4318,curl,1.700349
+list,list/hsa:5243,curl,1.75333
+list,list/hsa:5468,curl,1.711922
+list,list/hsa:2099,curl,1.724266
+list,list/hsa:2100,curl,1.771723
+list,list/hsa:348,curl,1.726561
+find,find/compound/glucose,curl,2.060762
+find,find/compound/fructose,curl,1.730552
+find,find/compound/galactose,curl,1.48732
+find,find/compound/mannose,curl,1.506387
+find,find/compound/sucrose,curl,1.447524
+find,find/compound/lactose,curl,1.523535
+find,find/compound/maltose,curl,1.479297
+find,find/compound/ribose,curl,1.519659
+find,find/compound/xylose,curl,1.47697
+find,find/compound/pyruvate,curl,1.523155
+find,find/compound/citrate,curl,1.504457
+find,find/compound/acetate,curl,2.030077
+find,find/compound/ethanol,curl,1.708204
+find,find/compound/cholesterol,curl,1.52411
+find,find/compound/adenine,curl,1.534985
+find,find/compound/guanine,curl,1.522478
+find,find/compound/cytosine,curl,1.535496
+find,find/compound/thymine,curl,1.51614
+find,find/compound/uracil,curl,1.440645
+find,find/compound/glycine,curl,1.459744
+find,find/compound/alanine,curl,1.688229
+find,find/compound/valine,curl,1.652786
+find,find/compound/leucine,curl,1.473687
+find,find/compound/serine,curl,1.570675
+find,find/compound/cysteine,curl,1.499064
+get,get/hsa:10458,curl,1.102962
+get,get/hsa:7157,curl,1.28206
+get,get/hsa:672,curl,1.258738
+get,get/hsa:675,curl,1.325361
+get,get/hsa:1956,curl,1.685351
+get,get/hsa:2064,curl,1.291893
+get,get/hsa:5290,curl,1.585455
+get,get/hsa:7422,curl,1.021244
+get,get/hsa:3845,curl,1.374268
+get,get/hsa:5970,curl,1.344754
+get,get/hsa:4609,curl,1.299873
+get,get/hsa:2475,curl,1.290724
+get,get/hsa:5728,curl,1.088543
+get,get/hsa:1029,curl,1.038785
+get,get/hsa:3553,curl,1.039356
+get,get/hsa:7124,curl,1.289689
+get,get/hsa:7040,curl,1.051094
+get,get/hsa:3091,curl,1.024622
+get,get/hsa:3105,curl,1.030356
+get,get/hsa:4318,curl,1.036827
+get,get/hsa:5243,curl,1.0279
+get,get/hsa:5468,curl,1.022028
+get,get/hsa:2099,curl,1.073177
+get,get/hsa:2100,curl,1.068155
+get,get/hsa:348,curl,1.077396
+getseq,get/hsa:10458/aaseq,curl,1.03664
+getseq,get/hsa:7157/aaseq,curl,1.021925
+getseq,get/hsa:672/aaseq,curl,1.075162
+getseq,get/hsa:675/aaseq,curl,1.076278
+getseq,get/hsa:1956/aaseq,curl,1.134205
+getseq,get/hsa:2064/aaseq,curl,1.097801
+getseq,get/hsa:5290/aaseq,curl,1.023933
+getseq,get/hsa:7422/aaseq,curl,1.184752
+getseq,get/hsa:3845/aaseq,curl,1.091252
+getseq,get/hsa:5970/aaseq,curl,1.028201
+getseq,get/hsa:4609/aaseq,curl,1.176584
+getseq,get/hsa:2475/aaseq,curl,1.035608
+getseq,get/hsa:5728/aaseq,curl,1.123888
+getseq,get/hsa:1029/aaseq,curl,1.41752
+getseq,get/hsa:3553/aaseq,curl,1.059988
+getseq,get/hsa:7124/aaseq,curl,1.131823
+getseq,get/hsa:7040/aaseq,curl,1.320676
+getseq,get/hsa:3091/aaseq,curl,1.34669
+getseq,get/hsa:3105/aaseq,curl,1.338214
+getseq,get/hsa:4318/aaseq,curl,1.278413
+getseq,get/hsa:5243/aaseq,curl,1.352818
+getseq,get/hsa:5468/aaseq,curl,1.342408
+getseq,get/hsa:2099/aaseq,curl,1.3558
+getseq,get/hsa:2100/aaseq,curl,1.369172
+getseq,get/hsa:348/aaseq,curl,1.266235
+conv,conv/ncbi-geneid/hsa:10458,curl,1.308543
+conv,conv/ncbi-geneid/hsa:7157,curl,1.303341
+conv,conv/ncbi-geneid/hsa:672,curl,1.300993
+conv,conv/ncbi-geneid/hsa:675,curl,1.406502
+conv,conv/ncbi-geneid/hsa:1956,curl,1.330483
+conv,conv/ncbi-geneid/hsa:2064,curl,1.321999
+conv,conv/ncbi-geneid/hsa:5290,curl,1.298372
+conv,conv/ncbi-geneid/hsa:7422,curl,1.386182
+conv,conv/ncbi-geneid/hsa:3845,curl,1.321902
+conv,conv/ncbi-geneid/hsa:5970,curl,1.30425
+conv,conv/ncbi-geneid/hsa:4609,curl,1.311538
+conv,conv/ncbi-geneid/hsa:2475,curl,1.365007
+conv,conv/ncbi-geneid/hsa:5728,curl,1.331298
+conv,conv/ncbi-geneid/hsa:1029,curl,1.378926
+conv,conv/ncbi-geneid/hsa:3553,curl,1.360778
+conv,conv/ncbi-geneid/hsa:7124,curl,1.332136
+conv,conv/ncbi-geneid/hsa:7040,curl,1.30291
+conv,conv/ncbi-geneid/hsa:3091,curl,1.305841
+conv,conv/ncbi-geneid/hsa:3105,curl,1.323523
+conv,conv/ncbi-geneid/hsa:4318,curl,1.344628
+conv,conv/ncbi-geneid/hsa:5243,curl,1.293187
+conv,conv/ncbi-geneid/hsa:5468,curl,1.339419
+conv,conv/ncbi-geneid/hsa:2099,curl,1.311107
+conv,conv/ncbi-geneid/hsa:2100,curl,1.332708
+conv,conv/ncbi-geneid/hsa:348,curl,1.324312
+link,link/pathway/hsa:10458,curl,1.391532
+link,link/pathway/hsa:7157,curl,1.31629
+link,link/pathway/hsa:672,curl,1.349629
+link,link/pathway/hsa:675,curl,1.305197
+link,link/pathway/hsa:1956,curl,1.310748
+link,link/pathway/hsa:2064,curl,1.30643
+link,link/pathway/hsa:5290,curl,1.333224
+link,link/pathway/hsa:7422,curl,1.323807
+link,link/pathway/hsa:3845,curl,1.348449
+link,link/pathway/hsa:5970,curl,1.076229
+link,link/pathway/hsa:4609,curl,1.313767
+link,link/pathway/hsa:2475,curl,1.067359
+link,link/pathway/hsa:5728,curl,1.093161
+link,link/pathway/hsa:1029,curl,1.319468
+link,link/pathway/hsa:3553,curl,1.311523
+link,link/pathway/hsa:7124,curl,1.318955
+link,link/pathway/hsa:7040,curl,1.332867
+link,link/pathway/hsa:3091,curl,1.346838
+link,link/pathway/hsa:3105,curl,1.322622
+link,link/pathway/hsa:4318,curl,1.316255
+link,link/pathway/hsa:5243,curl,1.336
+link,link/pathway/hsa:5468,curl,1.382411
+link,link/pathway/hsa:2099,curl,1.620365
+link,link/pathway/hsa:2100,curl,1.324806
+link,link/pathway/hsa:348,curl,1.379263
+ddi,ddi/D00006,curl,1.808338
+ddi,ddi/D00008,curl,1.886911
+ddi,ddi/D00015,curl,1.755841
+ddi,ddi/D00018,curl,1.823794
+ddi,ddi/D00020,curl,1.782555
+ddi,ddi/D00028,curl,1.900043
+ddi,ddi/D00032,curl,1.908649
+ddi,ddi/D00033,curl,1.95868
+ddi,ddi/D00043,curl,2.023666
+ddi,ddi/D00045,curl,1.604179
+ddi,ddi/D00049,curl,1.914641
+ddi,ddi/D00052,curl,2.05876
+ddi,ddi/D00058,curl,1.910477
+ddi,ddi/D00059,curl,2.11394
+ddi,ddi/D00060,curl,1.95688
+ddi,ddi/D00062,curl,1.600534
+ddi,ddi/D00063,curl,1.865384
+ddi,ddi/D00066,curl,1.60293
+ddi,ddi/D00067,curl,1.639928
+ddi,ddi/D00068,curl,1.638694
+ddi,ddi/D00069,curl,1.718556
+ddi,ddi/D00070,curl,1.607039
+ddi,ddi/D00073,curl,1.720881
+ddi,ddi/D00074,curl,1.811919
+ddi,ddi/D00075,curl,3.444944
diff --git a/benchmarking/benchmark_compare.png b/benchmarking/benchmark_compare.png
deleted file mode 100644
index f26c16e..0000000
Binary files a/benchmarking/benchmark_compare.png and /dev/null differ
diff --git a/benchmarking/cases.tsv b/benchmarking/cases.tsv
new file mode 100644
index 0000000..3cacf66
--- /dev/null
+++ b/benchmarking/cases.tsv
@@ -0,0 +1,201 @@
+operation argument1 argument2
+info kegg -
+info pathway -
+info brite -
+info module -
+info ko -
+info genome -
+info genes -
+info ligand -
+info compound -
+info glycan -
+info reaction -
+info rclass -
+info enzyme -
+info network -
+info variant -
+info disease -
+info drug -
+info dgroup -
+info cel -
+info hsa -
+info eco -
+info sce -
+info ath -
+info dme -
+info mmu -
+list hsa:10458 -
+list hsa:7157 -
+list hsa:672 -
+list hsa:675 -
+list hsa:1956 -
+list hsa:2064 -
+list hsa:5290 -
+list hsa:7422 -
+list hsa:3845 -
+list hsa:5970 -
+list hsa:4609 -
+list hsa:2475 -
+list hsa:5728 -
+list hsa:1029 -
+list hsa:3553 -
+list hsa:7124 -
+list hsa:7040 -
+list hsa:3091 -
+list hsa:3105 -
+list hsa:4318 -
+list hsa:5243 -
+list hsa:5468 -
+list hsa:2099 -
+list hsa:2100 -
+list hsa:348 -
+find compound glucose
+find compound fructose
+find compound galactose
+find compound mannose
+find compound sucrose
+find compound lactose
+find compound maltose
+find compound ribose
+find compound xylose
+find compound pyruvate
+find compound citrate
+find compound acetate
+find compound ethanol
+find compound cholesterol
+find compound adenine
+find compound guanine
+find compound cytosine
+find compound thymine
+find compound uracil
+find compound glycine
+find compound alanine
+find compound valine
+find compound leucine
+find compound serine
+find compound cysteine
+get hsa:10458 -
+get hsa:7157 -
+get hsa:672 -
+get hsa:675 -
+get hsa:1956 -
+get hsa:2064 -
+get hsa:5290 -
+get hsa:7422 -
+get hsa:3845 -
+get hsa:5970 -
+get hsa:4609 -
+get hsa:2475 -
+get hsa:5728 -
+get hsa:1029 -
+get hsa:3553 -
+get hsa:7124 -
+get hsa:7040 -
+get hsa:3091 -
+get hsa:3105 -
+get hsa:4318 -
+get hsa:5243 -
+get hsa:5468 -
+get hsa:2099 -
+get hsa:2100 -
+get hsa:348 -
+getseq hsa:10458 -
+getseq hsa:7157 -
+getseq hsa:672 -
+getseq hsa:675 -
+getseq hsa:1956 -
+getseq hsa:2064 -
+getseq hsa:5290 -
+getseq hsa:7422 -
+getseq hsa:3845 -
+getseq hsa:5970 -
+getseq hsa:4609 -
+getseq hsa:2475 -
+getseq hsa:5728 -
+getseq hsa:1029 -
+getseq hsa:3553 -
+getseq hsa:7124 -
+getseq hsa:7040 -
+getseq hsa:3091 -
+getseq hsa:3105 -
+getseq hsa:4318 -
+getseq hsa:5243 -
+getseq hsa:5468 -
+getseq hsa:2099 -
+getseq hsa:2100 -
+getseq hsa:348 -
+conv ncbi-geneid hsa:10458
+conv ncbi-geneid hsa:7157
+conv ncbi-geneid hsa:672
+conv ncbi-geneid hsa:675
+conv ncbi-geneid hsa:1956
+conv ncbi-geneid hsa:2064
+conv ncbi-geneid hsa:5290
+conv ncbi-geneid hsa:7422
+conv ncbi-geneid hsa:3845
+conv ncbi-geneid hsa:5970
+conv ncbi-geneid hsa:4609
+conv ncbi-geneid hsa:2475
+conv ncbi-geneid hsa:5728
+conv ncbi-geneid hsa:1029
+conv ncbi-geneid hsa:3553
+conv ncbi-geneid hsa:7124
+conv ncbi-geneid hsa:7040
+conv ncbi-geneid hsa:3091
+conv ncbi-geneid hsa:3105
+conv ncbi-geneid hsa:4318
+conv ncbi-geneid hsa:5243
+conv ncbi-geneid hsa:5468
+conv ncbi-geneid hsa:2099
+conv ncbi-geneid hsa:2100
+conv ncbi-geneid hsa:348
+link pathway hsa:10458
+link pathway hsa:7157
+link pathway hsa:672
+link pathway hsa:675
+link pathway hsa:1956
+link pathway hsa:2064
+link pathway hsa:5290
+link pathway hsa:7422
+link pathway hsa:3845
+link pathway hsa:5970
+link pathway hsa:4609
+link pathway hsa:2475
+link pathway hsa:5728
+link pathway hsa:1029
+link pathway hsa:3553
+link pathway hsa:7124
+link pathway hsa:7040
+link pathway hsa:3091
+link pathway hsa:3105
+link pathway hsa:4318
+link pathway hsa:5243
+link pathway hsa:5468
+link pathway hsa:2099
+link pathway hsa:2100
+link pathway hsa:348
+ddi D00006 -
+ddi D00008 -
+ddi D00015 -
+ddi D00018 -
+ddi D00020 -
+ddi D00028 -
+ddi D00032 -
+ddi D00033 -
+ddi D00043 -
+ddi D00045 -
+ddi D00049 -
+ddi D00052 -
+ddi D00058 -
+ddi D00059 -
+ddi D00060 -
+ddi D00062 -
+ddi D00063 -
+ddi D00066 -
+ddi D00067 -
+ddi D00068 -
+ddi D00069 -
+ddi D00070 -
+ddi D00073 -
+ddi D00074 -
+ddi D00075 -
diff --git a/benchmarking/figure.jl b/benchmarking/figure.jl
new file mode 100644
index 0000000..cf5cb2e
--- /dev/null
+++ b/benchmarking/figure.jl
@@ -0,0 +1,2160 @@
+### A Pluto.jl notebook ###
+# v0.20.24
+
+using Markdown
+using InteractiveUtils
+
+# ╔═╡ 4c14aad4-bb94-11f1-90df-4d61c6cdbcfe
+begin
+ using AlgebraOfGraphics
+ using CairoMakie
+ using CSV
+ using DataFrames
+ using Statistics
+end
+
+# ╔═╡ 5e83c41a-85f0-4095-92d2-5ceeb6d10408
+md"""
+# KEGG API benchmark
+
+Median request time for each client and KEGG operation. KEGGREST and
+Bio.KEGG.REST do not provide a DDI method.
+"""
+
+# ╔═╡ 6c1424e5-60a9-406d-b3b5-66eabec2ad44
+begin
+ csv_path = joinpath(@__DIR__, "benchmark_compare.csv")
+ isfile(csv_path) || error("$csv_path not found. Run run_benchmarks.jl first.")
+ benchmarks = CSV.read(csv_path, DataFrame)
+
+ expected_columns = ["Function", "Request", "Language", "Seconds"]
+ names(benchmarks) == expected_columns || error("Unexpected columns in $csv_path")
+ all(isfinite, benchmarks.Seconds) && all(>=(0), benchmarks.Seconds) ||
+ error("Benchmark timings must be finite and nonnegative")
+
+ function median_value(values)
+ sorted = sort(collect(values))
+ middle = length(sorted) ÷ 2
+ return isodd(length(sorted)) ? sorted[middle + 1] : (sorted[middle] + sorted[middle + 1]) / 2
+ end
+
+ plot_data = combine(
+ groupby(filter(:Function => !=("getseq"), benchmarks), [:Function, :Language]),
+ :Seconds => median_value => :Median,
+ :Seconds => minimum => :Minimum,
+ :Seconds => maximum => :Maximum,
+ :Seconds => std => :sd,
+ :Seconds => mean => :mean,
+ )
+ new_rows = DataFrame(
+ Function = ["ddi", "ddi"],
+ Language = ["Bio.KEGG.REST (Python)", "KEGGREST (R)"],
+ Median = [0,0],
+ Minimum = [0,0],
+ Maximum = [0,0],
+ sd = [0,0],
+ mean = [0,0]
+ )
+
+ # Append the new rows to the DataFrame
+ #append!(new_rows, plot_data)
+ #plot_data
+
+end
+
+# ╔═╡ 91976867-0ecd-4d32-abc7-f64c817e6894
+
+
+# ╔═╡ 4e2857e6-0765-422c-a22a-7ea60c91ce57
+begin
+ nerd_font = String(readchomp(`fc-match -f "%{file}" "JetBrainsMono Nerd Font"`))
+ language_names = Dict(
+ "KEGGAPI.jl" => "KEGGAPI.jl \ue624",
+ "KEGGREST (R)" => "KEGGREST \ue68a",
+ "Bio.KEGG.REST (Python)" => "Bio.KEGG.REST \ue73c",
+ "curl" => "curl \uf31a",
+ )
+ client_order = [language_names[k] for k in [
+ "curl",
+ "Bio.KEGG.REST (Python)",
+ "KEGGREST (R)",
+ "KEGGAPI.jl",
+ ]]
+ language_order = reverse(client_order)
+end
+
+# ╔═╡ 7c3a0227-12da-4440-bb2d-f5fb37dba4ee
+begin
+ set_theme!(theme_minimal(); Axis = (; yticklabelfont = nerd_font))
+ figure = draw(
+ data(plot_data)
+ * mapping(
+ :Language => (name -> language_names[String(name)]) => "",
+ :Median => "Seconds per call (median)",
+ col = :Function => presorted,
+ color = :Language,
+ )
+ * visual(
+ BarPlot;
+ direction = :x,
+ bar_labels = :y,
+ flip_labels_at = (0, 1.0),
+ label_formatter = value -> trunc(value; digits = 2),
+ ),
+ scales(
+ Y = (; categories = language_order),
+ Color = (; palette = ["gainsboro", "#0031a9", "gainsboro", "gainsboro"]),
+ );
+ figure = (; size = (1000, 250)),
+ axis = (;
+ limits = (0, nothing, nothing, nothing),
+ xticks = [-1],
+ bottomspinevisible = false,
+ ),
+ legend = (; show = false),
+ facet = (; linkxaxes = :none),
+ )
+end
+
+# ╔═╡ dece6392-d991-440d-97e1-9f51c30570d0
+let
+ set_theme!(theme_minimal(); Axis = (; yticklabelfont = nerd_font))
+ figure = draw(
+ data(plot_data)
+ * mapping(
+ :Language => (name -> language_names[String(name)]) => "",
+ :Minimum => "Seconds per call (minimum)",
+ col = :Function => presorted,
+ color = :Language,
+ )
+ * visual(
+ BarPlot;
+ direction = :x,
+ bar_labels = :y,
+ flip_labels_at = (0, 1.0),
+ label_formatter = value -> trunc(value; digits = 2),
+ ),
+ scales(
+ Y = (; categories = language_order),
+ Color = (; palette = ["gainsboro", "#0031a9", "gainsboro", "gainsboro"]),
+ );
+ figure = (; size = (1000, 250)),
+ axis = (;
+ limits = (0, nothing, nothing, nothing),
+ xticks = [-1],
+ bottomspinevisible = false,
+ ),
+ legend = (; show = false),
+ facet = (; linkxaxes = :none),
+ )
+end
+
+# ╔═╡ 1164a037-20ce-4731-bbb2-80e6bdddd495
+let
+ set_theme!(theme_minimal(); Axis = (; yticklabelfont = nerd_font))
+ figure = draw(
+ data(plot_data)
+ * mapping(
+ :Language => (name -> language_names[String(name)]) => "",
+ :Maximum => "Seconds per call (maximum)",
+ col = :Function => presorted,
+ color = :Language,
+ )
+ * visual(
+ BarPlot;
+ direction = :x,
+ bar_labels = :y,
+ flip_labels_at = (0, 1.0),
+ label_formatter = value -> trunc(value; digits = 2),
+ ),
+ scales(
+ Y = (; categories = language_order),
+ Color = (; palette = ["gainsboro", "#0031a9", "gainsboro", "gainsboro"]),
+ );
+ figure = (; size = (1000, 250)),
+ axis = (;
+ limits = (0, nothing, nothing, nothing),
+ xticks = [-1],
+ bottomspinevisible = false,
+ ),
+ legend = (; show = false),
+ facet = (; linkxaxes = :none),
+ )
+end
+
+# ╔═╡ 7023db0d-ad08-4e7a-9c0d-e04f0b49dd57
+let
+ set_theme!()
+ client = :Language => (name -> language_names[String(name)]) => "Client"
+
+ bars = mapping(
+ :Function => presorted,
+ :mean => "Request time (seconds)";
+ dodge = client,
+ color = client,
+ ) * visual(BarPlot)
+
+ error_bars = mapping(
+ :Function => presorted,
+ :mean => "Request time (seconds)",
+ :sd;
+ dodge_x = client,
+
+ ) * visual(Errorbars; color = :black, whiskerwidth = 4, linewidth = 1)
+
+ draw(
+ data(plot_data) * (bars + error_bars) + mapping([0.5+i for i in 1:6]) * visual(VLines, color=:black, linestyle=:dot),
+ scales(
+ DodgeX = (; categories = client_order),
+ Color = (; categories = client_order,
+ #palette = [:lightgreen, :magenta, :orange, :gainsboro]
+ palette = :julialegacy
+ ),
+ );
+ figure = (; size = (700, 250)),
+ axis = (;
+ #title = "Mean request time ± 1 SD",
+ xlabel = "KEGG operation",
+ limits = (0.5, 7.5, 0, nothing),
+ xgridvisible=false
+ ),
+ legend = (; framevisible = false, labelfont = nerd_font)
+ )
+end
+
+# ╔═╡ 11d16508-1718-4ade-b713-2809b8d7be3d
+benchmarks
+
+# ╔═╡ 0eea9b04-1741-47f2-9c45-1bedc8a08769
+draw(
+ data(sort(transform(benchmarks, :Function => (x -> replace(x, "getseq" => "get")) => :Function), :Seconds))
+ * mapping(:Seconds, layout=:Function => presorted, color=:Language)
+ * visual(Lines),
+ scales(
+ #Layout = (; categories = [""])
+ ),
+ axis = (; limits = (nothing, nothing, 0.2, 1) ),
+ figure = (; sharex=false)
+)
+
+
+# ╔═╡ f221ca13-ad01-480b-a923-c7e9eaaff71b
+transform(benchmarks, :Function => (x -> replace(x, "getseq" => "get")) => :Function)
+
+# ╔═╡ 7b38d542-57d9-4f1a-af35-1bf1a39a9b3f
+
+
+# ╔═╡ d9a02b4d-ee72-40a3-9be2-fad6256ce600
+let
+ f=Figure(size=(250,50))
+ ax = Axis(f[1,1])
+ heatmap!(ax,rand(128,1), colormap=:Oranges)
+ hidedecorations!(ax)
+ f
+end
+
+# ╔═╡ 6e70799b-3271-4dbf-85e9-ded8ac07bbc9
+let
+ f=Figure(size=(250,50))
+ ax = Axis(f[1,1])
+ heatmap!(ax,rand(128,1), colormap=:Greens)
+ hidedecorations!(ax)
+ f
+end
+
+# ╔═╡ 2babdf19-d03a-4223-8cb0-c9b8a4510fd1
+let
+ f=Figure(size=(250,50))
+ ax = Axis(f[1,1])
+ heatmap!(ax,rand(128,1), colormap=:Purples)
+ hidedecorations!(ax)
+ f
+end
+
+# ╔═╡ 4ad6cf74-42d3-44f5-b694-3b2f005bfda3
+let
+ f=Figure(size=(250,50))
+ ax = Axis(f[1,1])
+ heatmap!(ax,rand(128,1), colormap=:Greys)
+ hidedecorations!(ax)
+ f
+end
+
+# ╔═╡ 7249e211-b3d0-41f8-a5b2-40c5076c81f5
+let
+ s = "Hello, World!"
+ f=Figure(size=(200,250))
+ ax = Axis(f[1,1], xticks=(1:length(s), string.(collect(s))))
+ heatmap!(ax,rand(length(s), 128), colormap=:Purples)
+ #hidedecorations!(ax)
+ f
+end
+
+# ╔═╡ fcfb2fd2-e8a5-414e-b3d2-b208123ad874
+
+
+# ╔═╡ 00000000-0000-0000-0000-000000000001
+PLUTO_PROJECT_TOML_CONTENTS = """
+[deps]
+AlgebraOfGraphics = "cbdf2221-f076-402e-a563-3d30da359d67"
+CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
+CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
+DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
+Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
+
+[compat]
+AlgebraOfGraphics = "~0.13.2"
+CSV = "~1.1.0"
+CairoMakie = "~0.15.15"
+DataFrames = "~1.8.2"
+"""
+
+# ╔═╡ 00000000-0000-0000-0000-000000000002
+PLUTO_MANIFEST_TOML_CONTENTS = """
+# This file is machine-generated - editing it directly is not advised
+
+julia_version = "1.12.6"
+manifest_format = "2.0"
+project_hash = "ce5cf1cf37c02c4f860ceb198180abae7539b906"
+
+[[deps.AbstractFFTs]]
+deps = ["LinearAlgebra"]
+git-tree-sha1 = "d92ad398961a3ed262d8bf04a1a2b8340f915fef"
+uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c"
+version = "1.5.0"
+
+ [deps.AbstractFFTs.extensions]
+ AbstractFFTsChainRulesCoreExt = "ChainRulesCore"
+ AbstractFFTsTestExt = "Test"
+
+ [deps.AbstractFFTs.weakdeps]
+ ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
+ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
+
+[[deps.AbstractTrees]]
+git-tree-sha1 = "2d9c9a55f9c93e8887ad391fbae72f8ef55e1177"
+uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
+version = "0.4.5"
+
+[[deps.Accessors]]
+deps = ["CompositionsBase", "ConstructionBase", "Dates", "InverseFunctions", "MacroTools"]
+git-tree-sha1 = "7063ad1083578215c7c4bf410368150abe8d5524"
+uuid = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697"
+version = "0.1.45"
+
+ [deps.Accessors.extensions]
+ AxisKeysExt = "AxisKeys"
+ IntervalSetsExt = "IntervalSets"
+ LinearAlgebraExt = "LinearAlgebra"
+ StaticArraysExt = "StaticArrays"
+ StructArraysExt = "StructArrays"
+ TestExt = "Test"
+ UnitfulExt = "Unitful"
+
+ [deps.Accessors.weakdeps]
+ AxisKeys = "94b1ba4f-4ee9-5380-92f1-94cde586c3c5"
+ IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953"
+ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
+ StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
+ StructArrays = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"
+ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
+ Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
+
+[[deps.Adapt]]
+deps = ["LinearAlgebra"]
+git-tree-sha1 = "7c2c19b5a26e601634bf718490b89d59685f122e"
+uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
+version = "4.7.1"
+weakdeps = ["SparseArrays", "StaticArrays"]
+
+ [deps.Adapt.extensions]
+ AdaptSparseArraysExt = "SparseArrays"
+ AdaptStaticArraysExt = "StaticArrays"
+
+[[deps.AdaptivePredicates]]
+git-tree-sha1 = "7e651ea8d262d2d74ce75fdf47c4d63c07dba7a6"
+uuid = "35492f91-a3bd-45ad-95db-fcad7dcfedb7"
+version = "1.2.0"
+
+[[deps.AlgebraOfGraphics]]
+deps = ["Accessors", "Colors", "DataAPI", "Dates", "Dictionaries", "FileIO", "GLM", "GeoInterface", "GeometryBasics", "GridLayoutBase", "Isoband", "KernelDensity", "Loess", "Makie", "NaturalSort", "PlotUtils", "PolygonOps", "PooledArrays", "PrecompileTools", "RelocatableFolders", "StatsBase", "StructArrays", "Tables"]
+git-tree-sha1 = "88b1f363a4c69268ab705bcb5a049768a367bed9"
+uuid = "cbdf2221-f076-402e-a563-3d30da359d67"
+version = "0.13.2"
+
+ [deps.AlgebraOfGraphics.extensions]
+ AlgebraOfGraphicsDynamicQuantitiesExt = "DynamicQuantities"
+ AlgebraOfGraphicsUnitfulExt = "Unitful"
+
+ [deps.AlgebraOfGraphics.weakdeps]
+ DynamicQuantities = "06fc5a27-2a28-4c7c-a15d-362465fb6821"
+ Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
+
+[[deps.AliasTables]]
+deps = ["PtrArrays", "Random"]
+git-tree-sha1 = "9876e1e164b144ca45e9e3198d0b689cadfed9ff"
+uuid = "66dad0bd-aa9a-41b7-9441-69ab47430ed8"
+version = "1.1.3"
+
+[[deps.Animations]]
+deps = ["Colors"]
+git-tree-sha1 = "e092fa223bf66a3c41f9c022bd074d916dc303e7"
+uuid = "27a7e980-b3e6-11e9-2bcd-0b925532e340"
+version = "0.4.2"
+
+[[deps.ArgTools]]
+uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
+version = "1.1.2"
+
+[[deps.Artifacts]]
+uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
+version = "1.11.0"
+
+[[deps.Automa]]
+deps = ["PrecompileTools", "TranscodingStreams"]
+git-tree-sha1 = "94eab0b3ccdcac361188cc661daf69d4433c1818"
+uuid = "67c07d97-cdcb-5c2c-af73-a7f9c32a568b"
+version = "1.2.0"
+
+[[deps.AxisAlgorithms]]
+deps = ["LinearAlgebra", "Random", "SparseArrays", "WoodburyMatrices"]
+git-tree-sha1 = "01b8ccb13d68535d73d2b0c23e39bd23155fb712"
+uuid = "13072b0f-2c55-5437-9ae7-d433b7a33950"
+version = "1.1.0"
+
+[[deps.AxisArrays]]
+deps = ["Dates", "IntervalSets", "IterTools", "RangeArrays"]
+git-tree-sha1 = "4126b08903b777c88edf1754288144a0492c05ad"
+uuid = "39de3d68-74b9-583c-8d2d-e117c070f3a9"
+version = "0.4.8"
+
+[[deps.Base64]]
+uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
+version = "1.11.0"
+
+[[deps.BaseDirs]]
+git-tree-sha1 = "8c290a1b223deaeea9aea44b235d24546da8eb98"
+uuid = "18cc8868-cbac-4acf-b575-c8ff214dc66f"
+version = "1.4.0"
+
+[[deps.Bzip2_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "1b96ea4a01afe0ea4090c5c8039690672dd13f2e"
+uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
+version = "1.0.9+0"
+
+[[deps.CEnum]]
+git-tree-sha1 = "389ad5c84de1ae7cf0e28e381131c98ea87d54fc"
+uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82"
+version = "0.5.0"
+
+[[deps.CRC32c]]
+uuid = "8bf52ea8-c179-5cab-976a-9e18b702a9bc"
+version = "1.11.0"
+
+[[deps.CRlibm]]
+deps = ["CRlibm_jll"]
+git-tree-sha1 = "66188d9d103b92b6cd705214242e27f5737a1e5e"
+uuid = "96374032-68de-5a5b-8d9e-752f78720389"
+version = "1.0.2"
+
+[[deps.CRlibm_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "e329286945d0cfc04456972ea732551869af1cfc"
+uuid = "4e9b3aee-d8a1-5a3d-ad8b-7d824db253f0"
+version = "1.0.1+0"
+
+[[deps.CSV]]
+deps = ["CodecZlib", "DataStrings", "Dates", "Downloads", "Durations", "Mmap", "Parsers", "PooledArrays", "PrecompileTools", "Printf", "Tables", "Unicode"]
+git-tree-sha1 = "ed05d41d96b1a815d52e9de1ef472971bc509832"
+uuid = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
+version = "1.1.0"
+
+ [deps.CSV.extensions]
+ CSVDataDecimalsExt = "DataDecimals"
+ CSVFilePathsBaseExt = "FilePathsBase"
+ CSVInlineStringsExt = "InlineStrings"
+
+ [deps.CSV.weakdeps]
+ DataDecimals = "3e2245cb-6932-498d-a7cf-39d39de8bde3"
+ FilePathsBase = "48062228-2e41-5def-b9a4-89aafe57970f"
+ InlineStrings = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48"
+
+[[deps.Cairo]]
+deps = ["Cairo_jll", "Colors", "Glib_jll", "Graphics", "Libdl", "Pango_jll"]
+git-tree-sha1 = "71aa551c5c33f1a4415867fe06b7844faadb0ae9"
+uuid = "159f3aea-2a34-519c-b102-8c37f9878175"
+version = "1.1.1"
+
+[[deps.CairoMakie]]
+deps = ["CRC32c", "Cairo", "Cairo_jll", "Colors", "FileIO", "FreeType", "GeometryBasics", "LinearAlgebra", "Makie", "PrecompileTools"]
+git-tree-sha1 = "1cda0b7d5abfc95357dae18aca934d401f7869ad"
+uuid = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
+version = "0.15.15"
+
+[[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 = "1fa950ebc3e37eccd51c6a8fe1f92f7d86263522"
+uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a"
+version = "1.18.7+0"
+
+[[deps.ChainRulesCore]]
+deps = ["Compat", "LinearAlgebra"]
+git-tree-sha1 = "12177ad6b3cad7fd50c8b3825ce24a99ad61c18f"
+uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
+version = "1.26.1"
+weakdeps = ["SparseArrays"]
+
+ [deps.ChainRulesCore.extensions]
+ ChainRulesCoreSparseArraysExt = "SparseArrays"
+
+[[deps.CodecZlib]]
+deps = ["TranscodingStreams", "Zlib_jll"]
+git-tree-sha1 = "970758a3d591a2a5c2a907c53f2e2f8c1b1d3537"
+uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
+version = "0.7.9"
+
+[[deps.CodecZstd]]
+deps = ["TranscodingStreams", "Zstd_jll"]
+git-tree-sha1 = "da54a6cd93c54950c15adf1d336cfd7d71f51a56"
+uuid = "6b39b394-51ab-5f42-8807-6242bab2b4c2"
+version = "0.8.7"
+
+[[deps.ColorBrewer]]
+deps = ["Colors", "JSON"]
+git-tree-sha1 = "07da79661b919001e6863b81fc572497daa58349"
+uuid = "a2cac450-b92f-5266-8821-25eda20663c8"
+version = "0.4.2"
+
+[[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 = "67e11ee83a43eb71ddc950302c53bf33f0690dfe"
+uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
+version = "0.12.1"
+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", "Reexport"]
+git-tree-sha1 = "37ea44092930b1811e666c3bc38065d7d87fcc74"
+uuid = "5ae59095-9a9b-59fe-a467-6f913c188581"
+version = "0.13.1"
+
+[[deps.CommonSolve]]
+deps = ["PrecompileTools"]
+git-tree-sha1 = "6c389fa857f6ca5a95474b52a52023fd77f24cb7"
+uuid = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2"
+version = "0.2.14"
+
+[[deps.Compat]]
+deps = ["TOML", "UUIDs"]
+git-tree-sha1 = "9d8a54ce4b17aa5bdce0ea5c34bc5e7c340d16ad"
+uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
+version = "4.18.1"
+weakdeps = ["Dates", "LinearAlgebra"]
+
+ [deps.Compat.extensions]
+ CompatLinearAlgebraExt = "LinearAlgebra"
+
+[[deps.CompilerSupportLibraries_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
+version = "1.3.0+1"
+
+[[deps.CompositionsBase]]
+git-tree-sha1 = "802bb88cd69dfd1509f6670416bd4434015693ad"
+uuid = "a33af91c-f02d-484b-be07-31d278c5ca2b"
+version = "0.1.2"
+weakdeps = ["InverseFunctions"]
+
+ [deps.CompositionsBase.extensions]
+ CompositionsBaseInverseFunctionsExt = "InverseFunctions"
+
+[[deps.ComputePipeline]]
+deps = ["Observables", "Preferences"]
+git-tree-sha1 = "7bc84b769c1d384315e7b5c4ac03a6c303e6cf35"
+uuid = "95dc2771-c249-4cd0-9c9f-1f3b4330693c"
+version = "0.1.8"
+
+[[deps.ConstructionBase]]
+git-tree-sha1 = "b4b092499347b18a015186eae3042f72267106cb"
+uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
+version = "1.6.0"
+weakdeps = ["IntervalSets", "LinearAlgebra", "StaticArrays"]
+
+ [deps.ConstructionBase.extensions]
+ ConstructionBaseIntervalSetsExt = "IntervalSets"
+ ConstructionBaseLinearAlgebraExt = "LinearAlgebra"
+ ConstructionBaseStaticArraysExt = "StaticArrays"
+
+[[deps.Contour]]
+git-tree-sha1 = "439e35b0b36e2e5881738abc8857bd92ad6ff9a8"
+uuid = "d38c429a-6771-53c6-b99e-75d170b6e991"
+version = "0.6.3"
+
+[[deps.CoreMath]]
+deps = ["CoreMath_jll"]
+git-tree-sha1 = "8c0480f92b1b1796239156a1b9b1bfb1b39499b4"
+uuid = "b7a15901-be09-4a0e-87d2-2e66b0e09b5a"
+version = "0.1.0"
+
+[[deps.CoreMath_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "a692a4c1dc59a4b8bc0b6403876eb3250fde2bc3"
+uuid = "a38c48d9-6df1-5ac9-9223-b6ada3b5572b"
+version = "0.1.0+0"
+
+[[deps.Crayons]]
+git-tree-sha1 = "54b76cbb40d9a0f5368c880725b2f141da77c94f"
+uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
+version = "4.2.0"
+
+[[deps.DataAPI]]
+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.DataStrings]]
+git-tree-sha1 = "b8032263d364177a0b192ea15f277df4e6ab07e2"
+uuid = "48204bd6-5611-42ea-b167-fc1d713f9be2"
+version = "1.1.0"
+
+[[deps.DataStructures]]
+deps = ["OrderedCollections"]
+git-tree-sha1 = "b0bc6d2cad1fed8b7fd59a1551a991cb3d2809e6"
+uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
+version = "0.19.6"
+
+[[deps.DataValueInterfaces]]
+git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
+uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
+version = "1.0.0"
+
+[[deps.Dates]]
+deps = ["Printf"]
+uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
+version = "1.11.0"
+
+[[deps.DelaunayTriangulation]]
+deps = ["AdaptivePredicates", "EnumX", "ExactPredicates", "Random"]
+git-tree-sha1 = "4ac548adcad90c1d5d677af13568a748af4c952b"
+uuid = "927a84f5-c5f4-47a5-9785-b46e178433df"
+version = "1.6.7"
+
+[[deps.Dictionaries]]
+deps = ["Indexing", "Random", "Serialization"]
+git-tree-sha1 = "a55766a9c8f66cf19ffcdbdb1444e249bb4ace33"
+uuid = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4"
+version = "0.4.6"
+
+[[deps.Distances]]
+deps = ["LinearAlgebra", "Statistics", "StatsAPI"]
+git-tree-sha1 = "c7e3a542b999843086e2f29dac96a618c105be1d"
+uuid = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7"
+version = "0.10.12"
+weakdeps = ["ChainRulesCore", "SparseArrays"]
+
+ [deps.Distances.extensions]
+ DistancesChainRulesCoreExt = "ChainRulesCore"
+ DistancesSparseArraysExt = "SparseArrays"
+
+[[deps.Distributed]]
+deps = ["Random", "Serialization", "Sockets"]
+uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
+version = "1.11.0"
+
+[[deps.Distributions]]
+deps = ["AliasTables", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "Roots", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns"]
+git-tree-sha1 = "a958ab3a40c755563f5e1405c0846cb0446bf19d"
+uuid = "31c24e10-a181-5473-b8eb-7969acd0382f"
+version = "0.25.131"
+
+ [deps.Distributions.extensions]
+ DistributionsChainRulesCoreExt = "ChainRulesCore"
+ DistributionsDensityInterfaceExt = "DensityInterface"
+ DistributionsSparseConnectivityTracerExt = "SparseConnectivityTracer"
+ DistributionsTestExt = "Test"
+
+ [deps.Distributions.weakdeps]
+ ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
+ DensityInterface = "b429d917-457f-4dbc-8f4c-0cc954292b1d"
+ SparseConnectivityTracer = "9f842d2f-2579-4b1d-911e-f412cf18a3f5"
+ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
+
+[[deps.DocStringExtensions]]
+git-tree-sha1 = "7442a5dfe1ebb773c29cc2962a8980f47221d76c"
+uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
+version = "0.9.5"
+
+[[deps.Downloads]]
+deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"]
+uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
+version = "1.7.0"
+
+[[deps.Durations]]
+deps = ["Dates"]
+git-tree-sha1 = "a2f9c974a78a23661f0e41d0b80b687f844c018e"
+uuid = "41ac09f5-0a95-4810-aa18-c88bf48ee130"
+version = "1.2.0"
+
+ [deps.Durations.extensions]
+ DurationsArrowExt = "Arrow"
+
+ [deps.Durations.weakdeps]
+ Arrow = "69666777-d1a9-59fb-9406-91d4454c9d45"
+
+[[deps.EarCut_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "e3290f2d49e661fbd94046d7e3726ffcb2d41053"
+uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
+version = "2.2.4+0"
+
+[[deps.EnumX]]
+git-tree-sha1 = "c49898e8438c828577f04b92fc9368c388ac783c"
+uuid = "4e289a0a-7415-4d19-859d-a7e5c4648b56"
+version = "1.0.7"
+
+[[deps.ExactPredicates]]
+deps = ["IntervalArithmetic", "Random", "StaticArrays"]
+git-tree-sha1 = "83231673ea4d3d6008ac74dc5079e77ab2209d8f"
+uuid = "429591f6-91af-11e9-00e2-59fbe8cec110"
+version = "2.2.9"
+
+[[deps.Expat_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "2bfb1e047e2ad0a5ca94365340bde8005d637568"
+uuid = "2e619515-83b5-522b-bb60-26c02a35a201"
+version = "2.8.4+0"
+
+[[deps.Extents]]
+git-tree-sha1 = "b309b36a9e02fe7be71270dd8c0fd873625332b4"
+uuid = "411431e0-e8b7-467b-b5e0-f676ba4f2910"
+version = "0.1.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 = "e3c081ec777297fb8fc433012d15a6eaf806b4d2"
+uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5"
+version = "9.0.1+0"
+
+[[deps.FFTA]]
+deps = ["AbstractFFTs", "DocStringExtensions", "LinearAlgebra", "MuladdMacro", "Primes", "Random", "Reexport"]
+git-tree-sha1 = "65e55303b72f4a567a51b174dd2c47496efeb95a"
+uuid = "b86e33f2-c0db-4aa1-a6e0-ab43e668529e"
+version = "0.3.1"
+
+[[deps.FileIO]]
+deps = ["Pkg", "Requires", "UUIDs"]
+git-tree-sha1 = "6621fef488e496356c9c9625d0562c12a6070819"
+uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
+version = "1.20.0"
+
+ [deps.FileIO.extensions]
+ HTTPExt = "HTTP"
+
+ [deps.FileIO.weakdeps]
+ HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
+
+[[deps.FilePaths]]
+deps = ["FilePathsBase", "MacroTools", "Reexport"]
+git-tree-sha1 = "a1b2fbfe98503f15b665ed45b3d149e5d8895e4c"
+uuid = "8fc22ac5-c921-52a6-82fd-178b2807b824"
+version = "0.9.0"
+
+ [deps.FilePaths.extensions]
+ FilePathsGlobExt = "Glob"
+ FilePathsURIParserExt = "URIParser"
+ FilePathsURIsExt = "URIs"
+
+ [deps.FilePaths.weakdeps]
+ Glob = "c27321d9-0574-5035-807b-f59d2c89b15c"
+ URIParser = "30578b45-9adc-5946-b283-645ec420af67"
+ URIs = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
+
+[[deps.FilePathsBase]]
+deps = ["Compat", "Dates"]
+git-tree-sha1 = "3bab2c5aa25e7840a4b065805c0cdfc01f3068d2"
+uuid = "48062228-2e41-5def-b9a4-89aafe57970f"
+version = "0.9.24"
+
+ [deps.FilePathsBase.extensions]
+ FilePathsBaseMmapExt = "Mmap"
+ FilePathsBaseTestExt = "Test"
+
+ [deps.FilePathsBase.weakdeps]
+ Mmap = "a63ad114-7e13-5084-954f-fe012c677804"
+ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
+
+[[deps.FileWatching]]
+uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"
+version = "1.11.0"
+
+[[deps.FillArrays]]
+deps = ["LinearAlgebra"]
+git-tree-sha1 = "5bad39456d9f0166184fce2248783dd9862645c1"
+uuid = "1a297f60-69ca-5386-bcde-b61e274b549b"
+version = "1.17.0"
+weakdeps = ["PDMats", "SparseArrays", "StaticArrays", "Statistics"]
+
+ [deps.FillArrays.extensions]
+ FillArraysPDMatsExt = "PDMats"
+ FillArraysSparseArraysExt = "SparseArrays"
+ FillArraysStaticArraysExt = "StaticArrays"
+ FillArraysStatisticsExt = "Statistics"
+
+[[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.FreeType]]
+deps = ["CEnum", "FreeType2_jll"]
+git-tree-sha1 = "907369da0f8e80728ab49c1c7e09327bf0d6d999"
+uuid = "b38be410-82b0-50bf-ab77-7b57e271db43"
+version = "4.1.1"
+
+[[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.FreeTypeAbstraction]]
+deps = ["BaseDirs", "ColorVectorSpace", "Colors", "FreeType", "GeometryBasics", "Mmap"]
+git-tree-sha1 = "4ebb930ef4a43817991ba35db6317a05e59abd11"
+uuid = "663a7486-cb36-511b-a19d-713bb74d65c9"
+version = "0.10.8"
+
+[[deps.FriBidi_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "7a214fdac5ed5f59a22c2d9a885a16da1c74bbc7"
+uuid = "559328eb-81f9-559d-9380-de523a88c83c"
+version = "1.0.17+0"
+
+[[deps.Future]]
+deps = ["Random"]
+uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820"
+version = "1.11.0"
+
+[[deps.GLM]]
+deps = ["Distributions", "LinearAlgebra", "LogExpFunctions", "Printf", "Reexport", "SparseArrays", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsModels"]
+git-tree-sha1 = "c963639ae5b9aab54f543bdc7504f42f59880bec"
+uuid = "38e38edf-8417-5370-95a0-9cbb8c7f171a"
+version = "1.9.5"
+
+[[deps.Gamma]]
+deps = ["LogExpFunctions"]
+git-tree-sha1 = "becc397f7cfb06e343496ae6ffb04818a851da51"
+uuid = "a0844989-3bd2-4988-8bea-c9407ab0941b"
+version = "1.2.0"
+
+[[deps.GeoFormatTypes]]
+git-tree-sha1 = "7528a7956248c723d01a0a9b0447bf254bf4da52"
+uuid = "68eda718-8dee-11e9-39e7-89f7f65f511f"
+version = "0.4.5"
+
+[[deps.GeoInterface]]
+deps = ["DataAPI", "Extents", "GeoFormatTypes"]
+git-tree-sha1 = "20f6b13b28c6304104968374f38c24ef71a8ea16"
+uuid = "cf35fbd7-0cd7-5166-be24-54bfbe79505f"
+version = "1.6.2"
+
+ [deps.GeoInterface.extensions]
+ GeoInterfaceMakieExt = ["Makie", "GeometryBasics"]
+ GeoInterfaceRecipesBaseExt = "RecipesBase"
+
+ [deps.GeoInterface.weakdeps]
+ GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
+ Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
+ RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
+
+[[deps.GeometryBasics]]
+deps = ["EarCut_jll", "LinearAlgebra", "PrecompileTools", "Random", "StaticArrays"]
+git-tree-sha1 = "ec46c5825710fa1a15d468acb2d93cc939a7a5fe"
+uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
+version = "0.5.13"
+weakdeps = ["Extents", "GeoInterface", "IntervalSets"]
+
+ [deps.GeometryBasics.extensions]
+ ExtentsExt = "Extents"
+ GeometryBasicsGeoInterfaceExt = "GeoInterface"
+ IntervalSetsExt = "IntervalSets"
+
+[[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.Giflib_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "6570366d757b50fabae9f4315ad74d2e40c0560a"
+uuid = "59f7168a-df46-5410-90c8-f2779963d0ec"
+version = "5.2.3+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.Graphics]]
+deps = ["Colors", "LinearAlgebra", "NaNMath"]
+git-tree-sha1 = "a641238db938fff9b2f60d08ed9030387daf428c"
+uuid = "a2bd30eb-e257-5431-a919-1863eab51364"
+version = "1.1.3"
+
+[[deps.Graphite2_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "69ffb934a5c5b7e086a0b4fee3427db2556fba6e"
+uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472"
+version = "1.3.16+0"
+
+[[deps.GridLayoutBase]]
+deps = ["GeometryBasics", "InteractiveUtils", "Observables"]
+git-tree-sha1 = "ef70da5e123a06a29e2d6ddff0f09985bc226491"
+uuid = "3955a311-db13-416c-9275-1d80ed98e5e9"
+version = "0.11.3"
+
+[[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"
+
+[[deps.HypergeometricFunctions]]
+deps = ["Gamma", "LinearAlgebra"]
+git-tree-sha1 = "31bb6c92405c084617facc1d7ed9eb6c402d061e"
+uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a"
+version = "0.3.30"
+
+[[deps.ImageAxes]]
+deps = ["AxisArrays", "ImageBase", "ImageCore", "Reexport", "SimpleTraits"]
+git-tree-sha1 = "e12629406c6c4442539436581041d372d69c55ba"
+uuid = "2803e5a7-5153-5ecf-9a86-9b4c37f5f5ac"
+version = "0.6.12"
+
+[[deps.ImageBase]]
+deps = ["ImageCore", "Reexport"]
+git-tree-sha1 = "eb49b82c172811fd2c86759fa0553a2221feb909"
+uuid = "c817782e-172a-44cc-b673-b171935fbb9e"
+version = "0.1.7"
+
+[[deps.ImageCore]]
+deps = ["ColorVectorSpace", "Colors", "FixedPointNumbers", "MappedArrays", "MosaicViews", "OffsetArrays", "PaddedViews", "PrecompileTools", "Reexport"]
+git-tree-sha1 = "8c193230235bbcee22c8066b0374f63b5683c2d3"
+uuid = "a09fc81d-aa75-5fe9-8630-4744c3626534"
+version = "0.10.5"
+
+[[deps.ImageIO]]
+deps = ["FileIO", "IndirectArrays", "JpegTurbo", "LazyModules", "Netpbm", "OpenEXR", "PNGFiles", "QOI", "Sixel", "TiffImages", "UUIDs", "WebP"]
+git-tree-sha1 = "f0f005f997dfb8c5fe23920d99458a9619873893"
+uuid = "82e4d734-157c-48bb-816b-45c225c6df19"
+version = "0.6.10"
+
+[[deps.ImageMetadata]]
+deps = ["AxisArrays", "ImageAxes", "ImageBase", "ImageCore"]
+git-tree-sha1 = "2a81c3897be6fbcde0802a0ebe6796d0562f63ec"
+uuid = "bc367c6b-8a6b-528e-b4bd-a4b897500b49"
+version = "0.9.10"
+
+[[deps.Imath_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "dcc8d0cd653e55213df9b75ebc6fe4a8d3254c65"
+uuid = "905a6f67-0a94-5f89-b386-d35d92009cd1"
+version = "3.2.2+0"
+
+[[deps.Indexing]]
+git-tree-sha1 = "ce1566720fd6b19ff3411404d4b977acd4814f9f"
+uuid = "313cdc1a-70c2-5d6a-ae34-0150d3930a38"
+version = "1.1.1"
+
+[[deps.IndirectArrays]]
+git-tree-sha1 = "012e604e1c7458645cb8b436f8fba789a51b257f"
+uuid = "9b13fd28-a010-5f03-acff-a1bbcff69959"
+version = "1.0.0"
+
+[[deps.Inflate]]
+git-tree-sha1 = "d1b1b796e47d94588b3757fe84fbf65a5ec4a80d"
+uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9"
+version = "0.1.5"
+
+[[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.IntegerMathUtils]]
+git-tree-sha1 = "c72458f1962faeb003bf23cbdb75164fe6280906"
+uuid = "18e54dd8-cb9d-406c-a71d-865a43cbb235"
+version = "0.1.4"
+
+[[deps.InteractiveUtils]]
+deps = ["Markdown"]
+uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
+version = "1.11.0"
+
+[[deps.Interpolations]]
+deps = ["Adapt", "AxisAlgorithms", "ChainRulesCore", "LinearAlgebra", "OffsetArrays", "Random", "Ratios", "SharedArrays", "SparseArrays", "StaticArrays", "WoodburyMatrices"]
+git-tree-sha1 = "48922d06068130f87e43edef52382e6a94305ae6"
+uuid = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
+version = "0.16.3"
+
+ [deps.Interpolations.extensions]
+ InterpolationsForwardDiffExt = "ForwardDiff"
+ InterpolationsUnitfulExt = "Unitful"
+
+ [deps.Interpolations.weakdeps]
+ ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
+ Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
+
+[[deps.IntervalArithmetic]]
+deps = ["CRlibm", "CoreMath", "MacroTools", "OpenBLASConsistentFPCSR_jll", "Printf", "Random", "RoundingEmulator"]
+git-tree-sha1 = "1c531bf0f8a5c60a340926e058fd3f209b5eef5d"
+uuid = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253"
+version = "1.0.12"
+
+ [deps.IntervalArithmetic.extensions]
+ IntervalArithmeticArblibExt = "Arblib"
+ IntervalArithmeticDiffRulesExt = "DiffRules"
+ IntervalArithmeticForwardDiffExt = "ForwardDiff"
+ IntervalArithmeticIntervalSetsExt = "IntervalSets"
+ IntervalArithmeticIrrationalConstantsExt = "IrrationalConstants"
+ IntervalArithmeticLinearAlgebraExt = "LinearAlgebra"
+ IntervalArithmeticMakieExt = "Makie"
+ IntervalArithmeticRecipesBaseExt = "RecipesBase"
+ IntervalArithmeticSparseArraysExt = "SparseArrays"
+
+ [deps.IntervalArithmetic.weakdeps]
+ Arblib = "fb37089c-8514-4489-9461-98f9c8763369"
+ DiffRules = "b552c78f-8df3-52c6-915a-8e097449b14b"
+ ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
+ IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953"
+ IrrationalConstants = "92d709cd-6900-40b7-9082-c6be49f344b6"
+ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
+ Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
+ RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
+ SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
+
+[[deps.IntervalSets]]
+git-tree-sha1 = "79d6bd28c8d9bccc2229784f1bd637689b256377"
+uuid = "8197267c-284f-5f27-9208-e0e47529a953"
+version = "0.7.14"
+
+ [deps.IntervalSets.extensions]
+ IntervalSetsRandomExt = "Random"
+ IntervalSetsRecipesBaseExt = "RecipesBase"
+ IntervalSetsStatisticsExt = "Statistics"
+
+ [deps.IntervalSets.weakdeps]
+ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
+ RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
+ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
+
+[[deps.InverseFunctions]]
+git-tree-sha1 = "a779299d77cd080bf77b97535acecd73e1c5e5cb"
+uuid = "3587e190-3f89-42d0-90ee-14403ec27112"
+version = "0.1.17"
+
+ [deps.InverseFunctions.extensions]
+ InverseFunctionsDatesExt = "Dates"
+ InverseFunctionsTestExt = "Test"
+
+ [deps.InverseFunctions.weakdeps]
+ Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
+ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
+
+[[deps.InvertedIndices]]
+git-tree-sha1 = "6da3c4316095de0f5ee2ebd875df8721e7e0bdbe"
+uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f"
+version = "1.3.1"
+
+[[deps.IrrationalConstants]]
+git-tree-sha1 = "b2d91fe939cae05960e760110b328288867b5758"
+uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
+version = "0.2.6"
+
+[[deps.Isoband]]
+deps = ["isoband_jll"]
+git-tree-sha1 = "f9b6d97355599074dc867318950adaa6f9946137"
+uuid = "f1662d9f-8043-43de-a69a-05efc1cc6ff4"
+version = "0.1.1"
+
+[[deps.IterTools]]
+git-tree-sha1 = "42d5f897009e7ff2cf88db414a389e5ed1bdd023"
+uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
+version = "1.10.0"
+
+[[deps.IteratorInterfaceExtensions]]
+git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
+uuid = "82899510-4779-5014-852e-03e436cf321d"
+version = "1.0.0"
+
+[[deps.JLLWrappers]]
+deps = ["Artifacts", "Preferences"]
+git-tree-sha1 = "7204148362dafe5fe6a273f855b8ccbe4df8173e"
+uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
+version = "1.8.0"
+
+[[deps.JSON]]
+deps = ["Dates", "Logging", "Parsers", "PrecompileTools", "StructUtils", "UUIDs", "Unicode"]
+git-tree-sha1 = "633b5a34494e711f694ccbc88a6e00102f10238c"
+uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
+version = "1.9.0"
+
+ [deps.JSON.extensions]
+ JSONArrowExt = ["ArrowTypes"]
+
+ [deps.JSON.weakdeps]
+ ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd"
+
+[[deps.JpegTurbo]]
+deps = ["CEnum", "FileIO", "ImageCore", "JpegTurbo_jll", "TOML"]
+git-tree-sha1 = "9496de8fb52c224a2e3f9ff403947674517317d9"
+uuid = "b835a17e-a41a-41e7-81f0-2f016b05efe0"
+version = "0.1.6"
+
+[[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.KernelDensity]]
+deps = ["Distributions", "DocStringExtensions", "FFTA", "Interpolations", "StatsBase"]
+git-tree-sha1 = "9eda8292dd3268b3b7ec9df21bbfac24e177ec52"
+uuid = "5ab0869b-81aa-558d-bb23-cbf5423bbe9b"
+version = "0.6.12"
+
+[[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"
+
+[[deps.LaTeXStrings]]
+git-tree-sha1 = "f88f3ccef05a6a72a0cf0ed417c8fd68530f4ab2"
+uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
+version = "1.4.1"
+
+[[deps.LazyModules]]
+git-tree-sha1 = "a560dd966b386ac9ae60bdd3a3d3a326062d3c3e"
+uuid = "8cdb02fc-e678-4876-92c5-9defec4f444e"
+version = "0.3.1"
+
+[[deps.LibCURL]]
+deps = ["LibCURL_jll", "MozillaCACerts_jll"]
+uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"
+version = "0.6.4"
+
+[[deps.LibCURL_jll]]
+deps = ["Artifacts", "LibSSH2_jll", "Libdl", "OpenSSL_jll", "Zlib_jll", "nghttp2_jll"]
+uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"
+version = "8.15.0+0"
+
+[[deps.LibGit2]]
+deps = ["LibGit2_jll", "NetworkOptions", "Printf", "SHA"]
+uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"
+version = "1.11.0"
+
+[[deps.LibGit2_jll]]
+deps = ["Artifacts", "LibSSH2_jll", "Libdl", "OpenSSL_jll"]
+uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5"
+version = "1.9.0+0"
+
+[[deps.LibSSH2_jll]]
+deps = ["Artifacts", "Libdl", "OpenSSL_jll"]
+uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"
+version = "1.11.3+1"
+
+[[deps.Libdl]]
+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 = "cc3ad4faf30015a3e8094c9b5b7f19e85bdf2386"
+uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9"
+version = "2.42.0+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.LinearAlgebra]]
+deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"]
+uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
+version = "1.12.0"
+
+[[deps.Loess]]
+deps = ["Distances", "LinearAlgebra", "Statistics", "StatsAPI", "StatsFuns"]
+git-tree-sha1 = "aac61f60ed2b1561ae1bbef4e6d7b75c27f1f9e3"
+uuid = "4345ca2d-374a-55d4-8d30-97f9976e7612"
+version = "0.6.6"
+
+[[deps.LogExpFunctions]]
+deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"]
+git-tree-sha1 = "bba2d9aa057d8f126415de240573e86a8f39d2a1"
+uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
+version = "1.0.1"
+
+ [deps.LogExpFunctions.extensions]
+ LogExpFunctionsChainRulesCoreExt = "ChainRulesCore"
+ LogExpFunctionsChangesOfVariablesExt = "ChangesOfVariables"
+ LogExpFunctionsInverseFunctionsExt = "InverseFunctions"
+
+ [deps.LogExpFunctions.weakdeps]
+ ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
+ ChangesOfVariables = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0"
+ InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112"
+
+[[deps.Logging]]
+uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
+version = "1.11.0"
+
+[[deps.MacroTools]]
+git-tree-sha1 = "1e0228a030642014fe5cfe68c2c0a818f9e3f522"
+uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
+version = "0.5.16"
+
+[[deps.Makie]]
+deps = ["Animations", "Base64", "CRC32c", "ColorBrewer", "ColorSchemes", "ColorTypes", "Colors", "ComputePipeline", "Contour", "Dates", "DelaunayTriangulation", "Distributions", "DocStringExtensions", "Downloads", "FFMPEG_jll", "FileIO", "FilePaths", "FixedPointNumbers", "Format", "FreeType", "FreeTypeAbstraction", "GeometryBasics", "GridLayoutBase", "ImageBase", "ImageIO", "InteractiveUtils", "Interpolations", "IntervalSets", "InverseFunctions", "Isoband", "KernelDensity", "LaTeXStrings", "LinearAlgebra", "MacroTools", "Markdown", "MathTeXEngine", "Observables", "OffsetArrays", "PNGFiles", "Packing", "Pkg", "PlotUtils", "PolygonOps", "PrecompileTools", "Printf", "REPL", "Random", "RelocatableFolders", "Scratch", "ShaderAbstractions", "SignedDistanceFields", "SparseArrays", "Statistics", "StatsBase", "StatsFuns", "StructArrays", "TriplotBase", "UnicodeFun", "Unitful"]
+git-tree-sha1 = "5f6f5d1b1fb7ff98c9a083bbfd4c661a9808e758"
+uuid = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
+version = "0.24.15"
+
+ [deps.Makie.extensions]
+ MakieDynamicQuantitiesExt = "DynamicQuantities"
+
+ [deps.Makie.weakdeps]
+ DynamicQuantities = "06fc5a27-2a28-4c7c-a15d-362465fb6821"
+
+[[deps.MappedArrays]]
+git-tree-sha1 = "0ee4497a4e80dbd29c058fcee6493f5219556f40"
+uuid = "dbb5928d-eab1-5f90-85c2-b9b0edb7c900"
+version = "0.4.3"
+
+[[deps.Markdown]]
+deps = ["Base64", "JuliaSyntaxHighlighting", "StyledStrings"]
+uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
+version = "1.11.0"
+
+[[deps.MathTeXEngine]]
+deps = ["AbstractTrees", "Automa", "DataStructures", "FreeTypeAbstraction", "GeometryBasics", "LaTeXStrings", "REPL", "RelocatableFolders", "UnicodeFun"]
+git-tree-sha1 = "aa1078778be5a8e5259ff04fbc3d258b3e78d464"
+uuid = "0a4f8689-d25c-4efe-a92b-7142dfc1aa53"
+version = "0.6.9"
+
+[[deps.Missings]]
+deps = ["DataAPI"]
+git-tree-sha1 = "ec4f7fbeab05d7747bdf98eb74d130a2a2ed298d"
+uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
+version = "1.2.0"
+
+[[deps.Mmap]]
+uuid = "a63ad114-7e13-5084-954f-fe012c677804"
+version = "1.11.0"
+
+[[deps.MosaicViews]]
+deps = ["MappedArrays", "OffsetArrays", "PaddedViews", "StackViews"]
+git-tree-sha1 = "7b86a5d4d70a9f5cdf2dacb3cbe6d251d1a61dbe"
+uuid = "e94cdb99-869f-56ef-bcf0-1ae2bcbe0389"
+version = "0.3.4"
+
+[[deps.MozillaCACerts_jll]]
+uuid = "14a3606d-f60d-562e-9121-12d972cd8159"
+version = "2025.11.4"
+
+[[deps.MuladdMacro]]
+deps = ["PrecompileTools"]
+git-tree-sha1 = "283bf85d4a767481dd924dff0eee1735e95f449e"
+uuid = "46d2c3a1-f734-5fdb-9937-b9b9aeba4221"
+version = "0.2.7"
+
+[[deps.NaNMath]]
+deps = ["OpenLibm_jll"]
+git-tree-sha1 = "dbd2e8cd2c1c27f0b584f6661b4309609c5a685e"
+uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
+version = "1.1.4"
+
+[[deps.NaturalSort]]
+git-tree-sha1 = "eda490d06b9f7c00752ee81cfa451efe55521e21"
+uuid = "c020b1a1-e9b0-503a-9c33-f039bfc54a85"
+version = "1.0.0"
+
+[[deps.Netpbm]]
+deps = ["FileIO", "ImageCore", "ImageMetadata"]
+git-tree-sha1 = "d92b107dbb887293622df7697a2223f9f8176fcd"
+uuid = "f09324ee-3d7c-5217-9330-fc30815ba969"
+version = "1.1.1"
+
+[[deps.NetworkOptions]]
+uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
+version = "1.3.0"
+
+[[deps.Observables]]
+git-tree-sha1 = "7438a59546cf62428fc9d1bc94729146d37a7225"
+uuid = "510215fc-4207-5dde-b226-833fc4488ee2"
+version = "0.5.5"
+
+[[deps.OffsetArrays]]
+git-tree-sha1 = "117432e406b5c023f665fa73dc26e79ec3630151"
+uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
+version = "1.17.0"
+weakdeps = ["Adapt"]
+
+ [deps.OffsetArrays.extensions]
+ OffsetArraysAdaptExt = "Adapt"
+
+[[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"
+uuid = "6cdc7f73-28fd-5e50-80fb-958a8875b1af"
+version = "0.3.34+0"
+
+[[deps.OpenBLAS_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
+uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"
+version = "0.3.29+0"
+
+[[deps.OpenEXR]]
+deps = ["Colors", "FileIO", "OpenEXR_jll"]
+git-tree-sha1 = "97db9e07fe2091882c765380ef58ec553074e9c7"
+uuid = "52e1d378-f018-4a11-a4be-720524705ac7"
+version = "0.3.3"
+
+[[deps.OpenEXR_jll]]
+deps = ["Artifacts", "Imath_jll", "JLLWrappers", "Libdl", "Zlib_jll"]
+git-tree-sha1 = "1bcebd887dd33f1108210b3954049b1bb8af0e7a"
+uuid = "18a262bb-aa17-5467-a713-aee519bc75cb"
+version = "3.4.15+0"
+
+[[deps.OpenLibm_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "05823500-19ac-5b8b-9628-191a04bc5112"
+version = "0.8.7+0"
+
+[[deps.OpenSSL_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
+version = "3.5.4+0"
+
+[[deps.OpenSpecFun_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"]
+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 = "05f45c2e0de6259db764adbfd2f1dc6d3f8de13c"
+uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
+version = "2.0.1"
+
+[[deps.PCRE2_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15"
+version = "10.44.0+1"
+
+[[deps.PDMats]]
+deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"]
+git-tree-sha1 = "123266c25174ef6c8d4718920abc206452cf8de6"
+uuid = "90014a1f-27ba-587c-ab20-58faa44d9150"
+version = "0.11.41"
+weakdeps = ["StatsBase"]
+
+ [deps.PDMats.extensions]
+ StatsBaseExt = "StatsBase"
+
+[[deps.PNGFiles]]
+deps = ["Base64", "CEnum", "ImageCore", "IndirectArrays", "OffsetArrays", "libpng_jll"]
+git-tree-sha1 = "32b657a0d57c310a1a172bfc8c8cf68c5e674323"
+uuid = "f57f5aa1-a3ce-4bc8-8ab9-96f992907883"
+version = "0.4.5"
+
+[[deps.Packing]]
+deps = ["GeometryBasics"]
+git-tree-sha1 = "bc5bf2ea3d5351edf285a06b0016788a121ce92c"
+uuid = "19eb6ba3-879d-56ad-ad62-d5c202156566"
+version = "0.5.1"
+
+[[deps.PaddedViews]]
+deps = ["OffsetArrays"]
+git-tree-sha1 = "0fac6313486baae819364c52b4f483450a9d793f"
+uuid = "5432bcbf-9aad-5242-b902-cca2824c8663"
+version = "0.5.12"
+
+[[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.Parsers]]
+deps = ["Dates", "PrecompileTools"]
+git-tree-sha1 = "663e8b48b789916221e0765393b289ca6c88f24e"
+uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
+version = "3.0.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"
+
+[[deps.Pkg]]
+deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"]
+uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
+version = "1.12.1"
+weakdeps = ["REPL"]
+
+ [deps.Pkg.extensions]
+ REPLExt = "REPL"
+
+[[deps.PkgVersion]]
+deps = ["Pkg"]
+git-tree-sha1 = "f9501cc0430a26bc3d156ae1b5b0c1b47af4d6da"
+uuid = "eebad327-c553-4316-9ea0-9fa01ccd7688"
+version = "0.3.3"
+
+[[deps.PlotUtils]]
+deps = ["ColorSchemes", "Colors", "Dates", "PrecompileTools", "Printf", "Reexport", "Statistics"]
+git-tree-sha1 = "f20e945b895d2009c6c28d8bbf40a5cd846f7c2f"
+uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043"
+version = "1.5.0"
+
+[[deps.PolygonOps]]
+git-tree-sha1 = "77b3d3605fc1cd0b42d95eba87dfcd2bf67d5ff6"
+uuid = "647866c9-e3ac-4575-94e7-e3d426903924"
+version = "0.1.2"
+
+[[deps.PooledArrays]]
+deps = ["DataAPI", "Future"]
+git-tree-sha1 = "36d8b4b899628fb92c2749eb488d884a926614d3"
+uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720"
+version = "1.4.3"
+
+[[deps.PrecompileTools]]
+deps = ["Preferences"]
+git-tree-sha1 = "edbeefc7a4889f528644251bdb5fc9ab5348bc2c"
+uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
+version = "1.3.4"
+
+[[deps.Preferences]]
+deps = ["TOML"]
+git-tree-sha1 = "5005266de4bfe50e53ff44a5cb5c540b6e47a254"
+uuid = "21216c6a-2e73-6563-6e65-726566657250"
+version = "1.6.0"
+
+[[deps.PrettyTables]]
+deps = ["Crayons", "LaTeXStrings", "Markdown", "PrecompileTools", "Printf", "REPL", "Reexport", "StringManipulation", "Tables"]
+git-tree-sha1 = "1b8aa19f229b1cea7fc93874a52e49db6a854450"
+uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
+version = "3.4.8"
+
+ [deps.PrettyTables.extensions]
+ PrettyTablesExcelExt = "XLSX"
+ PrettyTablesTypstryExt = "Typstry"
+
+ [deps.PrettyTables.weakdeps]
+ Typstry = "f0ed7684-a786-439e-b1e3-3b82803b501e"
+ XLSX = "fdbf4ff8-1666-58a4-91e7-1b58723a45e0"
+
+[[deps.Primes]]
+deps = ["IntegerMathUtils"]
+git-tree-sha1 = "25cdd1d20cd005b52fc12cb6be3f75faaf59bb9b"
+uuid = "27ebfcd6-29c5-5fa9-bf4b-fb8fc14df3ae"
+version = "0.5.7"
+
+[[deps.Printf]]
+deps = ["Unicode"]
+uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
+version = "1.11.0"
+
+[[deps.ProgressMeter]]
+deps = ["Distributed", "Printf"]
+git-tree-sha1 = "fbb92c6c56b34e1a2c4c36058f68f332bec840e7"
+uuid = "92933f4c-e287-5a05-a399-4b506db050ca"
+version = "1.11.0"
+
+[[deps.PtrArrays]]
+git-tree-sha1 = "4fbbafbc6251b883f4d2705356f3641f3652a7fe"
+uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d"
+version = "1.4.0"
+
+[[deps.QOI]]
+deps = ["ColorTypes", "FileIO", "FixedPointNumbers"]
+git-tree-sha1 = "472daaa816895cb7aee81658d4e7aec901fa1106"
+uuid = "4b34888f-f399-49d4-9bb3-47ed5cae4e65"
+version = "1.0.2"
+
+[[deps.QuadGK]]
+deps = ["DataStructures", "LinearAlgebra"]
+git-tree-sha1 = "5e8e8b0ab68215d7a2b14b9921a946fee794749e"
+uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc"
+version = "2.11.3"
+
+ [deps.QuadGK.extensions]
+ QuadGKEnzymeExt = "Enzyme"
+
+ [deps.QuadGK.weakdeps]
+ Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9"
+
+[[deps.REPL]]
+deps = ["InteractiveUtils", "JuliaSyntaxHighlighting", "Markdown", "Sockets", "StyledStrings", "Unicode"]
+uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
+version = "1.11.0"
+
+[[deps.Random]]
+deps = ["SHA"]
+uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
+version = "1.11.0"
+
+[[deps.RangeArrays]]
+git-tree-sha1 = "b9039e93773ddcfc828f12aadf7115b4b4d225f5"
+uuid = "b3c3ace0-ae52-54e7-9d0b-2c1406fd6b9d"
+version = "0.3.2"
+
+[[deps.Ratios]]
+deps = ["Requires"]
+git-tree-sha1 = "1342a47bf3260ee108163042310d26f2be5ec90b"
+uuid = "c84ed2f1-dad5-54f0-aa8e-dbefe2724439"
+version = "0.4.5"
+weakdeps = ["FixedPointNumbers"]
+
+ [deps.Ratios.extensions]
+ RatiosFixedPointNumbersExt = "FixedPointNumbers"
+
+[[deps.Reexport]]
+git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
+uuid = "189a3867-3050-52da-a836-e630ba90ab69"
+version = "1.2.2"
+
+[[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"
+uuid = "ae029012-a4dd-5104-9daa-d747884805df"
+version = "1.3.1"
+
+[[deps.Rmath]]
+deps = ["Random", "Rmath_jll"]
+git-tree-sha1 = "5b3d50eb374cea306873b371d3f8d3915a018f0b"
+uuid = "79098fc4-a85e-5d69-aa6a-4863f24498fa"
+version = "0.9.0"
+
+[[deps.Rmath_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "6d40b2fe70437b01397d2a4d5b020008da4e7019"
+uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f"
+version = "0.5.2+0"
+
+[[deps.Roots]]
+deps = ["Accessors", "CommonSolve", "Printf"]
+git-tree-sha1 = "4db094d5e079abbda658acfe1c4d098430417717"
+uuid = "f2b01f46-fcfa-551c-844a-d8ac1e96c665"
+version = "3.0.8"
+
+ [deps.Roots.extensions]
+ RootsChainRulesCoreExt = "ChainRulesCore"
+ RootsForwardDiffExt = "ForwardDiff"
+ RootsIntervalRootFindingExt = "IntervalRootFinding"
+ RootsSymPyExt = "SymPy"
+ RootsSymPyPythonCallExt = "SymPyPythonCall"
+ RootsUnitfulExt = "Unitful"
+
+ [deps.Roots.weakdeps]
+ ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
+ ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
+ IntervalRootFinding = "d2bf35a9-74e0-55ec-b149-d360ff49b807"
+ SymPy = "24249f21-da20-56a4-8eb1-6a02cf4ae2e6"
+ SymPyPythonCall = "bc8888f7-b21e-4b7c-a06a-5d9c9496438c"
+ Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
+
+[[deps.RoundingEmulator]]
+git-tree-sha1 = "40b9edad2e5287e05bd413a38f61a8ff55b9557b"
+uuid = "5eaf0fd0-dfba-4ccb-bf02-d820a40db705"
+version = "0.2.1"
+
+[[deps.SHA]]
+uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
+version = "0.7.0"
+
+[[deps.SIMD]]
+deps = ["PrecompileTools"]
+git-tree-sha1 = "e24dc23107d426a096d3eae6c165b921e74c18e4"
+uuid = "fdea26ae-647d-5447-a871-4b548cad5224"
+version = "3.7.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.ShaderAbstractions]]
+deps = ["ColorTypes", "FixedPointNumbers", "GeometryBasics", "LinearAlgebra", "Observables", "StaticArrays"]
+git-tree-sha1 = "57aa595158717ef165e6f5ab639fe2e3178c0a2b"
+uuid = "65257c39-d410-5151-9873-9b3e5be5013e"
+version = "0.5.1"
+
+[[deps.SharedArrays]]
+deps = ["Distributed", "Mmap", "Random", "Serialization"]
+uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"
+version = "1.11.0"
+
+[[deps.ShiftedArrays]]
+git-tree-sha1 = "503688b59397b3307443af35cd953a13e8005c16"
+uuid = "1277b4bf-5013-50f5-be3d-901d8477a67a"
+version = "2.0.0"
+
+[[deps.SignedDistanceFields]]
+deps = ["Statistics"]
+git-tree-sha1 = "3949ad92e1c9d2ff0cd4a1317d5ecbba682f4b92"
+uuid = "73760f76-fbc4-59ce-8f25-708e95d2df96"
+version = "0.4.1"
+
+[[deps.SimpleTraits]]
+deps = ["InteractiveUtils", "MacroTools"]
+git-tree-sha1 = "7ddb0b49c109481b046972c0e4ab02b2127d6a75"
+uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d"
+version = "0.9.6"
+
+[[deps.Sixel]]
+deps = ["Dates", "FileIO", "ImageCore", "IndirectArrays", "OffsetArrays", "REPL", "libsixel_jll"]
+git-tree-sha1 = "0494aed9501e7fb65daba895fb7fd57cc38bc743"
+uuid = "45858cf5-a6b0-47a3-bbea-62219f50df47"
+version = "0.1.5"
+
+[[deps.Sockets]]
+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"
+version = "1.12.0"
+
+[[deps.SpecialFunctions]]
+deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
+git-tree-sha1 = "429071b23f4c9a13fb6582f807cc2ef454082408"
+uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
+version = "2.9.0"
+weakdeps = ["ChainRulesCore"]
+
+ [deps.SpecialFunctions.extensions]
+ SpecialFunctionsChainRulesCoreExt = "ChainRulesCore"
+
+[[deps.StackViews]]
+deps = ["OffsetArrays"]
+git-tree-sha1 = "be1cf4eb0ac528d96f5115b4ed80c26a8d8ae621"
+uuid = "cae243ae-269e-4f55-b966-ac2d0dc13c15"
+version = "0.1.2"
+
+[[deps.StaticArrays]]
+deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"]
+git-tree-sha1 = "39e70e0ab5d7f89833a62ab7c79df15d4fc417c1"
+uuid = "90137ffa-7385-5640-81b9-e52037218182"
+version = "1.9.22"
+weakdeps = ["ChainRulesCore", "Statistics"]
+
+ [deps.StaticArrays.extensions]
+ StaticArraysChainRulesCoreExt = "ChainRulesCore"
+ StaticArraysStatisticsExt = "Statistics"
+
+[[deps.StaticArraysCore]]
+git-tree-sha1 = "6ab403037779dae8c514bad259f32a447262455a"
+uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c"
+version = "1.4.4"
+
+[[deps.Statistics]]
+deps = ["LinearAlgebra"]
+git-tree-sha1 = "e2b53ce13a53367e96601081e33d34746b571bad"
+uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
+version = "1.11.5"
+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.StatsFuns]]
+deps = ["HypergeometricFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"]
+git-tree-sha1 = "91a5737baed20ee31f3faea0e51f57461f6a689e"
+uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c"
+version = "2.2.1"
+weakdeps = ["ChainRulesCore", "InverseFunctions"]
+
+ [deps.StatsFuns.extensions]
+ StatsFunsChainRulesCoreExt = "ChainRulesCore"
+ StatsFunsInverseFunctionsExt = "InverseFunctions"
+
+[[deps.StatsModels]]
+deps = ["DataAPI", "DataStructures", "LinearAlgebra", "Printf", "REPL", "ShiftedArrays", "SparseArrays", "StatsAPI", "StatsBase", "StatsFuns", "Tables"]
+git-tree-sha1 = "0db41c4e0d9f3fa195395a6401a8290752c9cd3d"
+uuid = "3eaba693-59b7-5ba5-a881-562e759f1c8d"
+version = "0.7.10"
+
+[[deps.StringManipulation]]
+deps = ["PrecompileTools"]
+git-tree-sha1 = "773065c6e0e903924a9d838259be74338422aef2"
+uuid = "892a3eda-7b42-436c-8928-eab12a02cf0e"
+version = "0.5.0"
+
+[[deps.StructArrays]]
+deps = ["ConstructionBase", "DataAPI", "Tables"]
+git-tree-sha1 = "ad8002667372439f2e3611cfd14097e03fa4bccd"
+uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"
+version = "0.7.3"
+
+ [deps.StructArrays.extensions]
+ StructArraysAdaptExt = "Adapt"
+ StructArraysGPUArraysCoreExt = ["GPUArraysCore", "KernelAbstractions"]
+ StructArraysLinearAlgebraExt = "LinearAlgebra"
+ StructArraysSparseArraysExt = "SparseArrays"
+ StructArraysStaticArraysExt = "StaticArrays"
+
+ [deps.StructArrays.weakdeps]
+ Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
+ GPUArraysCore = "46192b85-c4d5-4398-a991-12ede77f4527"
+ KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c"
+ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
+ SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
+ StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
+
+[[deps.StructUtils]]
+deps = ["Dates", "UUIDs"]
+git-tree-sha1 = "b814d5005d6a529d740ffe06f8a86396f6501138"
+uuid = "ec057cc2-7a8d-4b58-b3b3-92acb9f63b42"
+version = "2.9.2"
+
+ [deps.StructUtils.extensions]
+ StructUtilsLazilyInitializedFieldsExt = ["LazilyInitializedFields"]
+ StructUtilsMeasurementsExt = ["Measurements"]
+ StructUtilsStaticArraysCoreExt = ["StaticArraysCore"]
+ StructUtilsTablesExt = ["Tables"]
+
+ [deps.StructUtils.weakdeps]
+ 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"
+
+[[deps.SuiteSparse]]
+deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"]
+uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9"
+
+[[deps.SuiteSparse_jll]]
+deps = ["Artifacts", "Libdl", "libblastrampoline_jll"]
+uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c"
+version = "7.8.3+2"
+
+[[deps.TOML]]
+deps = ["Dates"]
+uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
+version = "1.0.3"
+
+[[deps.TableTraits]]
+deps = ["IteratorInterfaceExtensions"]
+git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
+uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
+version = "1.0.1"
+
+[[deps.Tables]]
+deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"]
+git-tree-sha1 = "a94d9bdda1b7bed0046cea645639ab3f62196fac"
+uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
+version = "1.14.0"
+
+[[deps.Tar]]
+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.TiffImages]]
+deps = ["CodecZstd", "ColorTypes", "DataStructures", "DocStringExtensions", "FileIO", "FixedPointNumbers", "IndirectArrays", "Inflate", "Mmap", "OffsetArrays", "PkgVersion", "PrecompileTools", "ProgressMeter", "SIMD", "UUIDs"]
+git-tree-sha1 = "9ca5f1f2d42f80df4b8c9f6ab5a64f438bbd9976"
+uuid = "731e570b-9d59-4bfa-96dc-6df516fadf69"
+version = "0.11.9"
+
+[[deps.TranscodingStreams]]
+git-tree-sha1 = "0c45878dcfdcfa8480052b6ab162cdd138781742"
+uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
+version = "0.11.3"
+
+[[deps.TriplotBase]]
+git-tree-sha1 = "4d4ed7f294cda19382ff7de4c137d24d16adc89b"
+uuid = "981d1d27-644d-49a2-9326-4793e63143c3"
+version = "0.1.0"
+
+[[deps.UUIDs]]
+deps = ["Random", "SHA"]
+uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
+version = "1.11.0"
+
+[[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.Unitful]]
+deps = ["Dates", "LinearAlgebra", "Random"]
+git-tree-sha1 = "1f0f9f401753701a7e4113b5056ca38d33875b55"
+uuid = "1986cc42-f94f-5a68-af5c-568840ba703d"
+version = "1.29.0"
+
+ [deps.Unitful.extensions]
+ ConstructionBaseUnitfulExt = "ConstructionBase"
+ ForwardDiffExt = "ForwardDiff"
+ InverseFunctionsUnitfulExt = "InverseFunctions"
+ LatexifyExt = ["Latexify", "LaTeXStrings"]
+ NaNMathExt = "NaNMath"
+ PrintfExt = "Printf"
+
+ [deps.Unitful.weakdeps]
+ ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
+ ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
+ InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112"
+ LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
+ Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316"
+ NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
+ Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
+
+[[deps.WebP]]
+deps = ["CEnum", "ColorTypes", "FileIO", "FixedPointNumbers", "ImageCore", "libwebp_jll"]
+git-tree-sha1 = "aa1ca3c47f119fbdae8770c29820e5e6119b83f2"
+uuid = "e3aaa7dc-3e4b-44e0-be63-ffb868ccd7c1"
+version = "0.1.3"
+
+[[deps.WoodburyMatrices]]
+deps = ["LinearAlgebra", "SparseArrays"]
+git-tree-sha1 = "248a7031b3da79a127f14e5dc5f417e26f9f6db7"
+uuid = "efce3f68-66dc-5838-9240-27a6d6f5f9b6"
+version = "1.1.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_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_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_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_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"
+version = "1.3.1+2"
+
+[[deps.Zstd_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "446b23e73536f84e8037f5dce465e92275f6a308"
+uuid = "3161d3a3-bdf6-5164-811a-617609db77b4"
+version = "1.5.7+1"
+
+[[deps.isoband_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "51b5eeb3f98367157a7a12a1fb0aa5328946c03c"
+uuid = "9a68df92-36a6-505f-a73e-abb412b6bfb4"
+version = "0.2.3+0"
+
+[[deps.libaom_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "ef17c47d22224aaecc76e597ab21a072e025cf7b"
+uuid = "a4ae2306-e953-59d6-aa16-d00cac43593b"
+version = "3.14.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"
+
+[[deps.libblastrampoline_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
+version = "5.15.0+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.libfdk_aac_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl"]
+git-tree-sha1 = "646634dd19587a56ee2f1199563ec056c5f228df"
+uuid = "f638f0a6-7fb0-5443-88ba-1cc74229b280"
+version = "2.0.4+0"
+
+[[deps.libpng_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"]
+git-tree-sha1 = "e51150d5ab85cee6fc36726850f0e627ad2e4aba"
+uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f"
+version = "1.6.58+0"
+
+[[deps.libsixel_jll]]
+deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "libpng_jll"]
+git-tree-sha1 = "c1733e347283df07689d71d61e14be986e49e47a"
+uuid = "075b6546-f08a-558a-be8f-8157d0f608a5"
+version = "1.10.5+0"
+
+[[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.libwebp_jll]]
+deps = ["Artifacts", "Giflib_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libglvnd_jll", "Libtiff_jll", "libpng_jll"]
+git-tree-sha1 = "4e4282c4d846e11dce56d74fa8040130b7a95cb3"
+uuid = "c5f90fcd-3b7e-5836-afba-fc50a0988cb2"
+version = "1.6.0+0"
+
+[[deps.nghttp2_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"
+version = "1.64.0+1"
+
+[[deps.p7zip_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
+uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
+version = "17.7.0+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"
+"""
+
+# ╔═╡ Cell order:
+# ╠═4c14aad4-bb94-11f1-90df-4d61c6cdbcfe
+# ╟─5e83c41a-85f0-4095-92d2-5ceeb6d10408
+# ╠═6c1424e5-60a9-406d-b3b5-66eabec2ad44
+# ╠═91976867-0ecd-4d32-abc7-f64c817e6894
+# ╠═4e2857e6-0765-422c-a22a-7ea60c91ce57
+# ╠═7c3a0227-12da-4440-bb2d-f5fb37dba4ee
+# ╠═dece6392-d991-440d-97e1-9f51c30570d0
+# ╠═1164a037-20ce-4731-bbb2-80e6bdddd495
+# ╠═7023db0d-ad08-4e7a-9c0d-e04f0b49dd57
+# ╠═11d16508-1718-4ade-b713-2809b8d7be3d
+# ╠═0eea9b04-1741-47f2-9c45-1bedc8a08769
+# ╠═f221ca13-ad01-480b-a923-c7e9eaaff71b
+# ╠═7b38d542-57d9-4f1a-af35-1bf1a39a9b3f
+# ╠═d9a02b4d-ee72-40a3-9be2-fad6256ce600
+# ╠═6e70799b-3271-4dbf-85e9-ded8ac07bbc9
+# ╠═2babdf19-d03a-4223-8cb0-c9b8a4510fd1
+# ╠═4ad6cf74-42d3-44f5-b694-3b2f005bfda3
+# ╠═7249e211-b3d0-41f8-a5b2-40c5076c81f5
+# ╠═fcfb2fd2-e8a5-414e-b3d2-b208123ad874
+# ╟─00000000-0000-0000-0000-000000000001
+# ╟─00000000-0000-0000-0000-000000000002
diff --git a/benchmarking/notebook.jl b/benchmarking/notebook.jl
deleted file mode 100644
index 77233cb..0000000
--- a/benchmarking/notebook.jl
+++ /dev/null
@@ -1,68 +0,0 @@
-### A Pluto.jl notebook ###
-# v0.20.24
-
-using Markdown
-using InteractiveUtils
-
-# ╔═╡ 434324c8-8a87-11f1-adb0-599460332cfd
-begin
- using Pkg
- Pkg.activate(".")
- Pkg.add(["CSV", "DataFrames", "Chain", "CairoMakie", "AlgebraOfGraphics"])
-
- using CSV
- using DataFrames
- using Chain
- using CairoMakie
- using AlgebraOfGraphics
-end
-
-# ╔═╡ 39f4659c-fd34-444d-8cbd-1fa73468ffd4
-df = CSV.read("/Users/carlos/git/KEGGAPI.jl/benchmarking/benchmark_compare.csv", DataFrame)
-
-# ╔═╡ 1b2ff946-cfca-4e81-9391-cdfe7054fdf5
-name_map = Dict(
- "KEGGAPI.jl" => "KEGGAPI.jl",
- "KEGGREST (R)" => "KEGGREST",
- "Bio.KEGG.REST (Python)" => "Bio.KEGG",
- "curl" => "curl"
-)
-
-# ╔═╡ 5807e3a7-e12f-4ce4-b19b-37f281397904
-let
- px = 250
- set_theme!(theme_minimal())
- f = draw(
- data(filter(r -> r.Function != "getseq", eachrow(df)))
- * mapping(
- :Language => (x -> name_map[String(x)]) => "",
- :Median => "Seconds per call (median)",
- col = :Function => presorted,
- color = :Language
- )
- * visual(
- BarPlot,
- direction = :x,
- bar_labels = :y,
- flip_labels_at = (0, 1.1),
- label_formatter = x -> trunc(x, digits = 2)
- ),
-
- scales(;
- Y = (; categories = ["KEGGAPI.jl", "KEGGREST", "Bio.KEGG", "curl"] |> reverse),
- Color = (; palette = ["gainsboro", "#0031a9", "gainsboro", "gainsboro"])
- ),
- figure = (; size = (4 * px, px)),
- axis = (; limits = (0, nothing, nothing, nothing), xticks = [-1], bottomspinevisible = false),
- legend = (; show = false),
- facet = (; linkxaxes = :none)
- )
- save("benchmark.svg", f)
- f
-end
-
-# ╔═╡ Cell order:
-# ╠═434324c8-8a87-11f1-adb0-599460332cfd
-# ╠═39f4659c-fd34-444d-8cbd-1fa73468ffd4
-# ╠═1b2ff946-cfca-4e81-9391-cdfe7054fdf5
-# ╠═5807e3a7-e12f-4ce4-b19b-37f281397904
diff --git a/benchmarking/plot_benchmarks.jl b/benchmarking/plot_benchmarks.jl
old mode 100644
new mode 100755
index b004aa5..4b7a5ca
--- a/benchmarking/plot_benchmarks.jl
+++ b/benchmarking/plot_benchmarks.jl
@@ -1,62 +1,51 @@
#!/usr/bin/env julia
-#
-# Render benchmark_compare.csv to benchmark_compare.png (the figure used in the
-# README).
-#
-# julia --project=benchmarking benchmarking/plot_benchmarks.jl
-#
+# Render the request-time distributions in benchmark_compare.csv.
using DelimitedFiles
using Plots
-using Plots.PlotMeasures # provides `mm` for margins
-using StatsPlots # provides `groupedbar`
+using Plots.PlotMeasures
+using StatsPlots
const BENCHDIR = @__DIR__
const CSV = joinpath(BENCHDIR, "benchmark_compare.csv")
+const CANONICAL = ["info", "list", "find", "get", "getseq", "conv", "link", "ddi"]
isfile(CSV) || error("$CSV not found. Run run_benchmarks.jl first.")
+raw, header = readdlm(CSV, ',', String, header = true)
+vec(header) == ["Function", "Request", "Language", "Seconds"] || error("Unexpected columns in $CSV")
-# Read as text so blank medians (unsupported/skipped pairs) survive parsing.
-raw, _ = readdlm(CSV, ',', String, header = true)
functions = strip.(raw[:, 1])
-languages = strip.(raw[:, 2])
-medians = [isempty(strip(s)) ? NaN : parse(Float64, strip(s)) for s in raw[:, 3]]
+languages = strip.(raw[:, 3])
+seconds = parse.(Float64, strip.(raw[:, 4]))
+all(isfinite, seconds) && all(>=(0), seconds) ||
+ error("Benchmark timings must be finite and nonnegative")
-# One grouped bar per operation, one series per interface. Operations follow the
-# canonical KEGG order rather than the CSV's order; anything not listed is
-# appended so new cases still plot.
-const CANONICAL = ["info", "list", "find", "get", "getseq", "conv", "link", "ddi"]
present = unique(functions)
-fn_order = [f for f in CANONICAL if f in present]
-append!(fn_order, sort([f for f in present if f ∉ CANONICAL]))
-lang_order = unique(languages)
+fn_order = [function_name for function_name in CANONICAL if function_name in present]
+append!(fn_order, sort([function_name for function_name in present if function_name ∉ CANONICAL]))
+fn_index = Dict(function_name => index for (index, function_name) in enumerate(fn_order))
+ordered_functions = [fn_index[function_name] for function_name in functions]
-# Blank cells -- e.g. ddi, which KEGGREST and Bio.KEGG.REST do not wrap -- stay
-# NaN so no bar is drawn for them.
-matrix = fill(NaN, length(fn_order), length(lang_order))
-for i in eachindex(functions)
- r = findfirst(==(functions[i]), fn_order)
- c = findfirst(==(languages[i]), lang_order)
- matrix[r, c] = medians[i]
-end
-
-plt = groupedbar(
- fn_order,
- matrix;
- label = permutedims(lang_order),
+plt = groupedboxplot(
+ ordered_functions,
+ seconds;
+ group = languages,
+ label = permutedims(unique(languages)),
+ xticks = (eachindex(fn_order), fn_order),
xlabel = "KEGG operation",
- ylabel = "Median time per call (s)",
- title = "KEGGAPI.jl benchmarks",
+ ylabel = "Request time (s)",
+ title = "KEGG API request-time distributions",
legend = :topleft,
- # Headroom so the tallest bar is not clipped by the axis.
- ylims = (0, 1.08 * maximum(filter(!isnan, matrix))),
- bar_width = 0.7,
+ outliers = true,
+ bar_width = 0.75,
framestyle = :box,
- size = (1000, 500),
+ size = (1100, 600),
dpi = 200,
left_margin = 8mm,
bottom_margin = 8mm,
)
-out = joinpath(BENCHDIR, "benchmark_compare.png")
-savefig(plt, out)
-@info "Wrote $out"
+for extension in ("svg", "png")
+ output = joinpath(BENCHDIR, "benchmark.$extension")
+ savefig(plt, output)
+ @info "Wrote $output"
+end
diff --git a/benchmarking/run_benchmarks.jl b/benchmarking/run_benchmarks.jl
old mode 100644
new mode 100755
index a64a933..5cdc3d5
--- a/benchmarking/run_benchmarks.jl
+++ b/benchmarking/run_benchmarks.jl
@@ -1,118 +1,134 @@
#!/usr/bin/env julia
#
-# Benchmark KEGGAPI.jl against KEGGREST (R), Bio.KEGG.REST (Python) and raw curl.
+# Benchmark KEGGAPI.jl against KEGGREST, Bio.KEGG.REST, and raw curl.
#
-# julia --project=benchmarking benchmarking/run_benchmarks.jl [--nreps N] [--pause S]
+# julia --project=benchmarking benchmarking/run_benchmarks.jl
#
-# Each interface runs the same three operations (info/kegg, list/pathway,
-# get/hsa:10458) inside a single process, so what is measured is per-call time
-# rather than interpreter startup. Interfaces whose dependencies are missing are
-# reported and skipped, so this runs even with only Julia available.
-#
-# Writes benchmark_compare.csv (Function,Language,Mean,SD) next to this script.
-
-using Statistics
-using Printf
+# Each runner emits one timing for every request in cases.tsv. Optional
+# interfaces are skipped when their dependencies are unavailable.
const BENCHDIR = @__DIR__
const RUNNERS = joinpath(BENCHDIR, "runners")
+const CASE_FILE = joinpath(BENCHDIR, "cases.tsv")
+const OPERATIONS = Set(["info", "list", "find", "get", "getseq", "conv", "link", "ddi"])
+
+function validate_cases(path)
+ lines = readlines(path)
+ first(lines) == "operation\targument1\targument2" || error("Unexpected header in $path")
+ rows = [Tuple(split(line, '\t'; keepempty = true)) for line in Iterators.drop(lines, 1)]
+ all(length(row) == 3 for row in rows) || error("Every benchmark case must have three fields")
+ length(unique(rows)) == length(rows) || error("Benchmark cases must be unique")
+ counts = Dict(operation => count(row -> first(row) == operation, rows) for operation in OPERATIONS)
+ Set(first.(rows)) == OPERATIONS || error("Unexpected benchmark operations in $path")
+ all(==(25), values(counts)) || error("Each operation must have exactly 25 benchmark cases")
+ return nothing
+end
-# ------------------------------------------------------------------ CLI parsing
function parse_args(args)
- nreps, pause = 5, 0.4
+ nreps, pause = 1, 0.4
+ only = Set(["julia", "r", "python", "curl"])
+ output = joinpath(BENCHDIR, "benchmark_compare.csv")
i = 1
while i <= length(args)
+ i == length(args) && error("Missing value for $(args[i])")
if args[i] == "--nreps"
- nreps = parse(Int, args[i + 1]); i += 2
+ nreps = parse(Int, args[i + 1])
elseif args[i] == "--pause"
- pause = parse(Float64, args[i + 1]); i += 2
+ pause = parse(Float64, args[i + 1])
+ elseif args[i] == "--only"
+ only = Set(split(args[i + 1], ','))
+ elseif args[i] == "--output"
+ output = abspath(args[i + 1])
else
error("Unknown argument: $(args[i])")
end
+ i += 2
end
- return nreps, pause
+ nreps > 0 || error("--nreps must be positive")
+ pause >= 0 || error("--pause must be nonnegative")
+ valid = Set(["julia", "r", "python", "curl"])
+ only ⊆ valid || error("--only accepts a comma-separated subset of $(join(sort!(collect(valid)), ", "))")
+ return (; nreps, pause, only, output)
end
-const NREPS, PAUSE = parse_args(ARGS)
-
-# ------------------------------------------------------------------ Interfaces
-# `probe` must succeed for the interface to be benchmarked.
struct Interface
+ key::String
name::String
probe::Cmd
run::Cmd
end
-# Interpreters are overridable so a virtualenv / custom R can be used, e.g.
-# PYTHON=/path/to/venv/bin/python julia --project=benchmarking ...
+const OPTIONS = parse_args(ARGS)
+validate_cases(CASE_FILE)
const JULIA = get(ENV, "JULIA", "julia")
const RSCRIPT = get(ENV, "RSCRIPT", "Rscript")
const PYTHON = get(ENV, "PYTHON", "python3")
interfaces = [
Interface(
+ "julia",
"KEGGAPI.jl",
`$JULIA --version`,
- `$JULIA --project=$BENCHDIR $(joinpath(RUNNERS, "bench_julia.jl")) $NREPS $PAUSE`,
+ `$JULIA --project=$BENCHDIR $(joinpath(RUNNERS, "bench_julia.jl")) $(OPTIONS.nreps) $(OPTIONS.pause)`,
),
Interface(
+ "r",
"KEGGREST (R)",
`$RSCRIPT -e "suppressMessages(library(KEGGREST))"`,
- `$RSCRIPT $(joinpath(RUNNERS, "bench_r.R")) $NREPS $PAUSE`,
+ `$RSCRIPT $(joinpath(RUNNERS, "bench_r.R")) $(OPTIONS.nreps) $(OPTIONS.pause)`,
),
Interface(
+ "python",
"Bio.KEGG.REST (Python)",
`$PYTHON -c "import Bio.KEGG.REST"`,
- `$PYTHON $(joinpath(RUNNERS, "bench_python.py")) $NREPS $PAUSE`,
+ `$PYTHON $(joinpath(RUNNERS, "bench_python.py")) $(OPTIONS.nreps) $(OPTIONS.pause)`,
),
Interface(
+ "curl",
"curl",
`curl --version`,
- `bash $(joinpath(RUNNERS, "bench_curl.sh")) $NREPS $PAUSE`,
+ `bash $(joinpath(RUNNERS, "bench_curl.sh")) $(OPTIONS.nreps) $(OPTIONS.pause)`,
),
]
-available(iface) = success(pipeline(iface.probe, stdout = devnull, stderr = devnull))
+available(interface) = success(pipeline(interface.probe, stdout = devnull, stderr = devnull))
-# ------------------------------------------------------------------ Run
-samples = Dict{Tuple{String, String}, Vector{Float64}}()
-for iface in interfaces
- if !available(iface)
- @warn "Skipping $(iface.name): dependencies not installed" probe = iface.probe
- continue
- end
-
- @info "Benchmarking $(iface.name) ($NREPS replicates)..."
- output = try
- read(iface.run, String)
- catch e
- @warn "Skipping $(iface.name): runner failed" exception = e
+samples = NamedTuple{(:operation, :request, :language, :seconds), Tuple{String, String, String, Float64}}[]
+for interface in interfaces
+ interface.key in OPTIONS.only || continue
+ if !available(interface)
+ @warn "Skipping $(interface.name): dependencies not installed" probe = interface.probe
continue
end
+ operation_count = interface.key in ("r", "python") ? 7 : 8
+ expected_samples = 25 * operation_count * OPTIONS.nreps
+ @info "Benchmarking $(interface.name)" requests = expected_samples
+ output = read(interface.run, String)
+ initial_length = length(samples)
for line in eachline(IOBuffer(output))
+ isempty(strip(line)) && continue
fields = split(strip(line), ',')
- length(fields) == 3 || continue # NOTE: ignore any incidental stdout noise
- fn, lang, secs = fields
- push!(get!(samples, (String(fn), String(lang)), Float64[]), parse(Float64, secs))
+ length(fields) == 4 || error("Malformed output from $(interface.name): $line")
+ operation, request, language, seconds = fields
+ language == interface.name || error("Unexpected interface label '$language'")
+ elapsed = parse(Float64, seconds)
+ isfinite(elapsed) && elapsed >= 0 || error("Invalid timing from $(interface.name): $seconds")
+ push!(samples, (; operation = String(operation), request = String(request), language = String(language), seconds = elapsed))
end
+ length(samples) - initial_length == expected_samples ||
+ error("Expected $expected_samples results from $(interface.name)")
end
-isempty(samples) && error("No interface could be benchmarked.")
-
-# Every operation x interface pair gets a row. Pairs with no samples -- an
-# interface that was skipped, or an operation it does not wrap (e.g. ddi in
-# KEGGREST) -- are written with an empty median rather than omitted.
-const OPERATIONS = ["info", "list", "find", "get", "getseq", "conv", "link", "ddi"]
-
-observed = unique(first.(keys(samples)))
-operations = vcat(OPERATIONS, sort([op for op in observed if op ∉ OPERATIONS]))
+isempty(samples) && error("No interface could be benchmarked")
-outfile = joinpath(BENCHDIR, "benchmark_compare.csv")
-open(outfile, "w") do io
- println(io, "Function,Language,Median")
- for op in operations, iface in interfaces
- times = get(samples, (op, iface.name), Float64[])
- println(io, "$op,$(iface.name),", isempty(times) ? "" : median(times))
+# issue #45: retain every request timing so plots and CI use the distribution,
+# rather than reducing repeated calls to one median before writing the file.
+mkpath(dirname(OPTIONS.output))
+open(OPTIONS.output, "w") do io
+ println(io, "Function,Request,Language,Seconds")
+ for sample in samples
+ println(io, "$(sample.operation),$(sample.request),$(sample.language),$(sample.seconds)")
end
end
+@info "Wrote $(OPTIONS.output)" samples = length(samples)
diff --git a/benchmarking/runners/bench_curl.sh b/benchmarking/runners/bench_curl.sh
old mode 100644
new mode 100755
index 7374dba..6c3c22b
--- a/benchmarking/runners/bench_curl.sh
+++ b/benchmarking/runners/bench_curl.sh
@@ -1,42 +1,49 @@
#!/usr/bin/env bash
-# curl runner: times raw REST calls and prints one CSV row per replicate.
-# Usage: bash runners/bench_curl.sh
+# Time raw REST calls and print one CSV row per request.
+# Usage: bash bench_curl.sh
set -euo pipefail
-NREPS="${1:-5}"
+NREPS="${1:-1}"
PAUSE="${2:-0.4}"
-
BASE="https://rest.kegg.jp"
-
-# label|path pairs -- keep in sync with the other runners.
-CASES=(
- "info|$BASE/info/kegg"
- "list|$BASE/list/pathway"
- "find|$BASE/find/compound/glucose"
- "get|$BASE/get/hsa:10458"
- "getseq|$BASE/get/hsa:10458/aaseq"
- "conv|$BASE/conv/ncbi-geneid/eco:b0002"
- "link|$BASE/link/pathway/hsa:10458"
- "ddi|$BASE/ddi/D00564"
-)
-
-# Must match the interface name in run_benchmarks.jl.
+CASE_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/cases.tsv"
LABEL="curl"
-# `curl -w %{time_total}` reports the transfer time without shell startup cost.
+case_path() {
+ local operation="$1" argument1="$2" argument2="$3"
+ case "$operation" in
+ info) printf 'info/%s' "$argument1" ;;
+ list) printf 'list/%s' "$argument1" ;;
+ find) printf 'find/%s/%s' "$argument1" "$argument2" ;;
+ get) printf 'get/%s' "$argument1" ;;
+ getseq) printf 'get/%s/aaseq' "$argument1" ;;
+ conv) printf 'conv/%s/%s' "$argument1" "$argument2" ;;
+ link) printf 'link/%s/%s' "$argument1" "$argument2" ;;
+ ddi) printf 'ddi/%s' "$argument1" ;;
+ *) echo "Unknown benchmark operation: $operation" >&2; return 1 ;;
+ esac
+}
+
timeit() {
- curl -s -o /dev/null -w '%{time_total}' "$1"
+ curl -fsS -o /dev/null -w '%{time_total}' "$1"
}
-# Warm up before the measured replicates.
-for case in "${CASES[@]}"; do
- curl -s -o /dev/null "${case#*|}"
+# Warm up each operation once so connection setup is absent from measurements.
+previous_operation=""
+while IFS=$'\t' read -r operation argument1 argument2; do
+ [[ "$operation" == "operation" ]] && continue
+ [[ "$operation" == "$previous_operation" ]] && continue
+ path="$(case_path "$operation" "$argument1" "$argument2")"
+ curl -fsS -o /dev/null "$BASE/$path"
+ previous_operation="$operation"
sleep "$PAUSE"
-done
+done < "$CASE_FILE"
for _ in $(seq 1 "$NREPS"); do
- for case in "${CASES[@]}"; do
- echo "${case%%|*},$LABEL,$(timeit "${case#*|}")"
+ while IFS=$'\t' read -r operation argument1 argument2; do
+ [[ "$operation" == "operation" ]] && continue
+ path="$(case_path "$operation" "$argument1" "$argument2")"
+ echo "$operation,$path,$LABEL,$(timeit "$BASE/$path")"
sleep "$PAUSE"
- done
+ done < "$CASE_FILE"
done
diff --git a/benchmarking/runners/bench_julia.jl b/benchmarking/runners/bench_julia.jl
index b591c74..959750f 100644
--- a/benchmarking/runners/bench_julia.jl
+++ b/benchmarking/runners/bench_julia.jl
@@ -1,33 +1,56 @@
-# Julia runner: times KEGGAPI.jl calls and prints one CSV row per replicate.
-# Usage: julia --project=. runners/bench_julia.jl
+# Julia runner: time KEGGAPI.jl calls and print one CSV row per request.
+# Usage: julia --project=benchmarking bench_julia.jl
using KEGGAPI
-const NREPS = length(ARGS) >= 1 ? parse(Int, ARGS[1]) : 5
+const NREPS = length(ARGS) >= 1 ? parse(Int, ARGS[1]) : 1
const PAUSE = length(ARGS) >= 2 ? parse(Float64, ARGS[2]) : 0.4
-
-# This runner already spaces its calls, so disable the internal batching delay
-# to measure request and parsing work only.
-get_entry(args...) = KEGGAPI.kegg_get(args...; request_delay = 0.0)
-
-# (label, thunk) pairs -- keep in sync with the other runners.
-const CASES = [
- ("info", () -> KEGGAPI.kegg_info("kegg")),
- ("list", () -> KEGGAPI.kegg_list("pathway")),
- ("find", () -> KEGGAPI.kegg_find("compound", "glucose")),
- ("get", () -> get_entry("hsa:10458")),
- ("getseq", () -> get_entry("hsa:10458", :aaseq)),
- ("conv", () -> KEGGAPI.kegg_conv("ncbi-geneid", "eco:b0002")),
- ("link", () -> KEGGAPI.kegg_link("pathway", "hsa:10458")),
- ("ddi", () -> KEGGAPI.kegg_ddi("D00564")),
-]
-
-# Must match the interface name in run_benchmarks.jl.
+const CASE_FILE = joinpath(@__DIR__, "..", "cases.tsv")
const LABEL = "KEGGAPI.jl"
-# Warm up so compilation latency is not attributed to the first replicate.
-for (_, thunk) in CASES
- thunk()
- sleep(PAUSE)
+struct BenchmarkCase
+ operation::String
+ argument1::String
+ argument2::String
+end
+
+function load_cases(path)
+ lines = readlines(path)
+ first(lines) == "operation\targument1\targument2" || error("Unexpected case-file header")
+ cases = BenchmarkCase[]
+ for (index, line) in enumerate(Iterators.drop(lines, 1))
+ fields = split(line, '\t'; keepempty = true)
+ length(fields) == 3 || error("Invalid benchmark case on line $(index + 1)")
+ push!(cases, BenchmarkCase(fields...))
+ end
+ return cases
+end
+
+function request_path(case)
+ op, arg1, arg2 = case.operation, case.argument1, case.argument2
+ op == "info" && return "info/$arg1"
+ op == "list" && return "list/$arg1"
+ op == "find" && return "find/$arg1/$arg2"
+ op == "get" && return "get/$arg1"
+ op == "getseq" && return "get/$arg1/aaseq"
+ op == "conv" && return "conv/$arg1/$arg2"
+ op == "link" && return "link/$arg1/$arg2"
+ op == "ddi" && return "ddi/$arg1"
+ error("Unknown benchmark operation: $op")
+end
+
+# The runner spaces calls itself. Disable KEGGAPI's internal batching delay so
+# the measurement contains only request and parsing time.
+function run_case(case)
+ op, arg1, arg2 = case.operation, case.argument1, case.argument2
+ op == "info" && return KEGGAPI.kegg_info(arg1)
+ op == "list" && return KEGGAPI.kegg_list(arg1)
+ op == "find" && return KEGGAPI.kegg_find(arg1, arg2)
+ op == "get" && return KEGGAPI.kegg_get(arg1; request_delay = 0.0)
+ op == "getseq" && return KEGGAPI.kegg_get(arg1, :aaseq; request_delay = 0.0)
+ op == "conv" && return KEGGAPI.kegg_conv(arg1, arg2)
+ op == "link" && return KEGGAPI.kegg_link(arg1, arg2)
+ op == "ddi" && return KEGGAPI.kegg_ddi(arg1)
+ error("Unknown benchmark operation: $op")
end
function timeit(f)
@@ -36,7 +59,18 @@ function timeit(f)
return (time_ns() - t0) / 1.0e9
end
-for _ in 1:NREPS, (label, thunk) in CASES
- println("$label,$LABEL,", timeit(thunk))
+const CASES = load_cases(CASE_FILE)
+
+# Warm up each operation once so compilation is absent from measured calls.
+warmed = Set{String}()
+for case in CASES
+ case.operation in warmed && continue
+ run_case(case)
+ push!(warmed, case.operation)
+ sleep(PAUSE)
+end
+
+for _ in 1:NREPS, case in CASES
+ println("$(case.operation),$(request_path(case)),$LABEL,", timeit(() -> run_case(case)))
sleep(PAUSE)
end
diff --git a/benchmarking/runners/bench_python.py b/benchmarking/runners/bench_python.py
index 987839a..f85b022 100644
--- a/benchmarking/runners/bench_python.py
+++ b/benchmarking/runners/bench_python.py
@@ -1,43 +1,89 @@
-"""Python runner: times Bio.KEGG.REST calls, one CSV row per replicate.
+"""Time Bio.KEGG.REST calls and print one CSV row per request.
-Usage: python3 runners/bench_python.py
+Usage: python3 bench_python.py
"""
+import csv
+import os
import sys
import time
import Bio.KEGG.REST as BK
-NREPS = int(sys.argv[1]) if len(sys.argv) > 1 else 5
+NREPS = int(sys.argv[1]) if len(sys.argv) > 1 else 1
PAUSE = float(sys.argv[2]) if len(sys.argv) > 2 else 0.4
-
-# (label, thunk) pairs -- keep in sync with the other runners. Bio.KEGG.REST has
-# no ddi wrapper, so that case is absent here and reported as unsupported.
-CASES = [
- ("info", lambda: BK.kegg_info("kegg")),
- ("list", lambda: BK.kegg_list("pathway")),
- ("find", lambda: BK.kegg_find("compound", "glucose")),
- ("get", lambda: BK.kegg_get("hsa:10458")),
- ("getseq", lambda: BK.kegg_get("hsa:10458", "aaseq")),
- ("conv", lambda: BK.kegg_conv("ncbi-geneid", "eco:b0002")),
- ("link", lambda: BK.kegg_link("pathway", "hsa:10458")),
-]
-
-# Must match the interface name in run_benchmarks.jl.
+CASE_FILE = os.path.join(os.path.dirname(__file__), "..", "cases.tsv")
LABEL = "Bio.KEGG.REST (Python)"
+def load_cases(path):
+ with open(path, newline="", encoding="utf-8") as handle:
+ return list(csv.DictReader(handle, delimiter="\t"))
+
+
+def request_path(case):
+ op = case["operation"]
+ arg1 = case["argument1"]
+ arg2 = case["argument2"]
+ if op == "info":
+ return "info/{}".format(arg1)
+ if op == "list":
+ return "list/{}".format(arg1)
+ if op == "find":
+ return "find/{}/{}".format(arg1, arg2)
+ if op == "get":
+ return "get/{}".format(arg1)
+ if op == "getseq":
+ return "get/{}/aaseq".format(arg1)
+ if op == "conv":
+ return "conv/{}/{}".format(arg1, arg2)
+ if op == "link":
+ return "link/{}/{}".format(arg1, arg2)
+ raise ValueError("Unknown benchmark operation: {}".format(op))
+
+
+def run_case(case):
+ op = case["operation"]
+ arg1 = case["argument1"]
+ arg2 = case["argument2"]
+ if op == "info":
+ response = BK.kegg_info(arg1)
+ elif op == "list":
+ response = BK.kegg_list(arg1)
+ elif op == "find":
+ response = BK.kegg_find(arg1, arg2)
+ elif op == "get":
+ response = BK.kegg_get(arg1)
+ elif op == "getseq":
+ response = BK.kegg_get(arg1, "aaseq")
+ elif op == "conv":
+ response = BK.kegg_conv(arg1, arg2)
+ elif op == "link":
+ response = BK.kegg_link(arg1, arg2)
+ else:
+ raise ValueError("Unknown benchmark operation: {}".format(op))
+ response.read()
+
+
def timeit(fn):
- t0 = time.perf_counter()
- fn().read()
- return time.perf_counter() - t0
+ start = time.perf_counter()
+ fn()
+ return time.perf_counter() - start
+
+# Bio.KEGG.REST has no DDI wrapper.
+CASES = [case for case in load_cases(CASE_FILE) if case["operation"] != "ddi"]
-# Warm up (connection setup, imports) before the measured replicates.
-for _, thunk in CASES:
- thunk().read()
+# Warm up each operation once so connection setup is absent from measurements.
+warmed = set()
+for case in CASES:
+ if case["operation"] in warmed:
+ continue
+ run_case(case)
+ warmed.add(case["operation"])
time.sleep(PAUSE)
for _ in range(NREPS):
- for label, thunk in CASES:
- print("{},{},{}".format(label, LABEL, timeit(thunk)))
+ for case in CASES:
+ elapsed = timeit(lambda: run_case(case))
+ print("{},{},{},{}".format(case["operation"], request_path(case), LABEL, elapsed))
time.sleep(PAUSE)
diff --git a/benchmarking/runners/bench_r.R b/benchmarking/runners/bench_r.R
index dc8e53b..f2bb1a7 100644
--- a/benchmarking/runners/bench_r.R
+++ b/benchmarking/runners/bench_r.R
@@ -1,42 +1,76 @@
-# R runner: times KEGGREST calls and prints one CSV row per replicate.
-# Usage: Rscript runners/bench_r.R
+# Time KEGGREST calls and print one CSV row per request.
+# Usage: Rscript bench_r.R
suppressMessages(library(KEGGREST))
args <- commandArgs(trailingOnly = TRUE)
-nreps <- if (length(args) >= 1) as.integer(args[1]) else 5
+nreps <- if (length(args) >= 1) as.integer(args[1]) else 1
pause <- if (length(args) >= 2) as.numeric(args[2]) else 0.4
+script_arg <- grep("^--file=", commandArgs(trailingOnly = FALSE), value = TRUE)
+if (length(script_arg) != 1) stop("bench_r.R must be run with Rscript")
+script_path <- sub("^--file=", "", script_arg[[1]])
+case_file <- file.path(dirname(normalizePath(script_path)), "..", "cases.tsv")
+label <- "KEGGREST (R)"
-# (label, thunk) pairs -- keep in sync with the other runners. KEGGREST has no
-# ddi wrapper, so that case is absent here and reported as unsupported.
-# NOTE: keggList (not keggInfo) is the counterpart of the list operation.
-cases <- list(
- list("info", function() keggInfo("kegg")),
- list("list", function() keggList("pathway")),
- list("find", function() keggFind("compound", "glucose")),
- list("get", function() keggGet("hsa:10458")),
- list("getseq", function() keggGet("hsa:10458", "aaseq")),
- list("conv", function() keggConv("ncbi-geneid", "eco:b0002")),
- list("link", function() keggLink("pathway", "hsa:10458"))
-)
+cases <- read.delim(case_file, stringsAsFactors = FALSE, check.names = FALSE)
+# KEGGREST has no DDI wrapper.
+cases <- cases[cases$operation != "ddi", ]
-# Must match the interface name in run_benchmarks.jl.
-label <- "KEGGREST (R)"
+request_path <- function(case) {
+ op <- case$operation
+ arg1 <- case$argument1
+ arg2 <- case$argument2
+ if (op == "info") return(sprintf("info/%s", arg1))
+ if (op == "list") return(sprintf("list/%s", arg1))
+ if (op == "find") return(sprintf("find/%s/%s", arg1, arg2))
+ if (op == "get") return(sprintf("get/%s", arg1))
+ if (op == "getseq") return(sprintf("get/%s/aaseq", arg1))
+ if (op == "conv") return(sprintf("conv/%s/%s", arg1, arg2))
+ if (op == "link") return(sprintf("link/%s/%s", arg1, arg2))
+ stop(sprintf("Unknown benchmark operation: %s", op))
+}
+
+run_case <- function(case) {
+ op <- case$operation
+ arg1 <- case$argument1
+ arg2 <- case$argument2
+ result <- switch(
+ op,
+ info = keggInfo(arg1),
+ list = keggList(arg1),
+ find = keggFind(arg1, arg2),
+ get = keggGet(arg1),
+ getseq = keggGet(arg1, "aaseq"),
+ conv = keggConv(arg1, arg2),
+ link = keggLink(arg1, arg2),
+ stop(sprintf("Unknown benchmark operation: %s", op))
+ )
+ invisible(result)
+}
timeit <- function(fn) {
- t0 <- Sys.time()
- invisible(fn())
- as.numeric(difftime(Sys.time(), t0, units = "secs"))
+ start <- proc.time()[["elapsed"]]
+ fn()
+ proc.time()[["elapsed"]] - start
}
-# Warm up before the measured replicates.
-for (case in cases) {
- invisible(case[[2]]())
+# Warm up each operation once so connection setup is absent from measurements.
+warmed <- character()
+for (i in seq_len(nrow(cases))) {
+ case <- cases[i, ]
+ if (case$operation %in% warmed) next
+ run_case(case)
+ warmed <- c(warmed, case$operation)
Sys.sleep(pause)
}
-for (i in seq_len(nreps)) {
- for (case in cases) {
- cat(sprintf("%s,%s,%s\n", case[[1]], label, timeit(case[[2]])))
+for (rep in seq_len(nreps)) {
+ for (i in seq_len(nrow(cases))) {
+ case <- cases[i, ]
+ elapsed <- timeit(function() run_case(case))
+ cat(sprintf(
+ "%s,%s,%s,%s\n",
+ case$operation, request_path(case), label, elapsed
+ ))
Sys.sleep(pause)
}
}
diff --git a/benchmarking/write_ci_results.jl b/benchmarking/write_ci_results.jl
new file mode 100755
index 0000000..53822ca
--- /dev/null
+++ b/benchmarking/write_ci_results.jl
@@ -0,0 +1,39 @@
+#!/usr/bin/env julia
+# Convert KEGGAPI.jl request timings to github-action-benchmark's custom JSON.
+using DelimitedFiles
+using Printf
+using Statistics
+
+length(ARGS) == 2 || error("Usage: write_ci_results.jl INPUT.csv OUTPUT.json")
+input, output = ARGS
+raw, header = readdlm(input, ',', String, header = true)
+vec(header) == ["Function", "Request", "Language", "Seconds"] || error("Unexpected columns in $input")
+
+samples = Dict{String, Vector{Float64}}()
+for row in eachrow(raw)
+ strip(row[3]) == "KEGGAPI.jl" || continue
+ elapsed = parse(Float64, strip(row[4]))
+ isfinite(elapsed) && elapsed >= 0 || error("Benchmark timings must be finite and nonnegative")
+ push!(get!(samples, strip(row[1]), Float64[]), elapsed)
+end
+isempty(samples) && error("No KEGGAPI.jl timings found in $input")
+
+const ORDER = ["info", "list", "find", "get", "getseq", "conv", "link", "ddi"]
+operations = [operation for operation in ORDER if haskey(samples, operation)]
+for operation in operations
+ length(samples[operation]) >= 25 || error("Expected at least 25 '$operation' timings")
+end
+
+open(output, "w") do io
+ println(io, "[")
+ for (index, operation) in enumerate(operations)
+ index > 1 && println(io, ",")
+ @printf(
+ io,
+ " {\"name\": \"%s request\", \"unit\": \"seconds\", \"value\": %.9g}",
+ operation,
+ median(samples[operation]),
+ )
+ end
+ println(io, "\n]")
+end