diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d6cc6e9aa..f94de0e24 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -19,6 +19,14 @@ updates: directory: "/docs" schedule: interval: "daily" + groups: + js-deps: + patterns: + - "*" + update-types: + - "major" + - "minor" + - "patch" - package-ecosystem: "pip" directory: "/" diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index be27f15d5..b5466ad53 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -3,7 +3,8 @@ name: Linux on: push: branches: - - '**' + - master + - 'release-*' paths: - '**' tags: @@ -11,16 +12,22 @@ on: pull_request: branches: - 'master' + - 'release-*' paths: - '**' workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + permissions: contents: write jobs: test: - runs-on: ${{ matrix.os }} + name: Test (${{ matrix.config.test-id }}, Julia ${{ matrix.config.julia-version }}, Python ${{ matrix.config.python-version }}, shard ${{ matrix.shard-index }}) + runs-on: ${{ matrix.config.os }} timeout-minutes: 60 env: COVERAGE_PROCESS_START: "${{ github.workspace }}/.coveragerc" @@ -28,40 +35,29 @@ jobs: run: shell: bash strategy: + fail-fast: false matrix: - julia-version: ['1'] - python-version: ['3.13'] - os: [ubuntu-latest] - test-id: [main] - include: - - julia-version: '1.10' - python-version: '3.10' - os: ubuntu-latest - test-id: include - - julia-version: '1' - python-version: '3.13' - os: ubuntu-latest - test-id: include - - julia-version: '1' - python-version: '3.9' - os: ubuntu-latest - test-id: include - + config: + - {julia-version: '1', python-version: '3.14', os: ubuntu-latest, test-id: main} + - {julia-version: '1.10', python-version: '3.10', os: ubuntu-latest, test-id: include} + - {julia-version: '1', python-version: '3.14', os: ubuntu-latest, test-id: include} + - {julia-version: '1', python-version: '3.10', os: ubuntu-latest, test-id: include} + shard-index: [0, 1, 2, 3] steps: - uses: actions/checkout@v7 - name: "Set up Julia" - uses: julia-actions/setup-julia@v2 + uses: julia-actions/setup-julia@v3 with: - version: ${{ matrix.julia-version }} + version: ${{ matrix.config.julia-version }} - name: "Cache Julia" - uses: julia-actions/cache@v2 + uses: julia-actions/cache@v3 with: - cache-name: ${{ matrix.os }}-test-${{ matrix.julia-version }}-${{ matrix.python-version }} + cache-name: ${{ matrix.config.os }}-test-${{ matrix.config.julia-version }}-${{ matrix.config.python-version }} cache-packages: false - name: "Set up Python" - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: - python-version: ${{ matrix.python-version }} + python-version: ${{ matrix.config.python-version }} cache: pip - name: "Install PySR" run: | @@ -69,32 +65,47 @@ jobs: pip install '.[dev]' python -c 'import pysr' - name: "Assert Julia version" - if: ${{ matrix.julia-version != '1'}} - run: python3 -c "from pysr import jl; assert jl.VERSION.major == jl.seval('v\"${{ matrix.julia-version }}\"').major; assert jl.VERSION.minor == jl.seval('v\"${{ matrix.julia-version }}\"').minor" + if: ${{ matrix.config.julia-version != '1'}} + run: python3 -c "from pysr import jl; assert jl.VERSION.major == jl.seval('v\"${{ matrix.config.julia-version }}\"').major; assert jl.VERSION.minor == jl.seval('v\"${{ matrix.config.julia-version }}\"').minor" - name: "Set up coverage for subprocesses" run: echo 'import coverage; coverage.process_startup()' > "${{ github.workspace }}/sitecustomize.py" - name: "Run tests" - run: coverage run -m pysr test main,cli,startup + run: coverage run -m pysr test main,cli,startup,interrupt + env: + PYSR_TEST_SHARD_COUNT: "4" + PYSR_TEST_SHARD_INDEX: ${{ matrix.shard-index }} - name: "Run JAX tests" run: coverage run --append -m pysr test jax - if: ${{ matrix.test-id == 'main' }} + if: ${{ matrix.config.test-id == 'main' && matrix.shard-index == 0 }} - name: "Run Torch tests" run: coverage run --append -m pysr test torch - if: ${{ matrix.test-id == 'main' }} + if: ${{ matrix.config.test-id == 'main' && matrix.shard-index == 0 }} - name: "Build coverage report" run: coverage xml - name: "Upload results to Codecov" - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v7 with: token: ${{ secrets.CODECOV_TOKEN }} slug: astroautomata/PySR + required_tests: + name: test (1, 3.14, ubuntu-latest, ${{ matrix.test-id }}) + needs: test + if: ${{ always() }} + runs-on: ubuntu-latest + strategy: + matrix: + test-id: [main, include] + steps: + - name: Require all test shards to pass + run: test "${{ needs.test.result }}" = success + dev_install: runs-on: ${{ matrix.os }} strategy: matrix: os: ['ubuntu-latest'] - python-version: ['3.13'] + python-version: ['3.14'] julia-version: ['1'] include: - os: ubuntu-latest @@ -102,7 +113,7 @@ jobs: julia-version: '1.10' steps: - uses: actions/checkout@v7 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 - name: "Install PySR" run: | python -m pip install --upgrade pip @@ -111,28 +122,28 @@ jobs: run: PYSR_TEST_JULIA_VERSION=${{ matrix.julia-version }} PYSR_TEST_PYTHON_VERSION=${{ matrix.python-version }} python -m pysr test dev conda_test: + name: Conda (Python ${{ matrix.python-version }}, shard ${{ matrix.shard-index }}) runs-on: ${{ matrix.os }} defaults: run: shell: bash -l {0} strategy: + fail-fast: false matrix: - # NOTE: pin to a known-supported CPython; `3` can resolve to free-threaded - # builds (e.g. 3.14t) which PythonCall.jl doesn't support. - python-version: ['3.13'] + python-version: ['3.14'] os: ['ubuntu-latest'] - + shard-index: [0, 1, 2, 3] steps: - uses: actions/checkout@v7 - name: "Cache conda" - uses: actions/cache@v5 + uses: actions/cache@v6 env: CACHE_NUMBER: 0 with: path: ~/conda_pkgs_dir key: ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-${{ hashFiles('environment.yml') }} - name: "Set up Conda" - uses: conda-incubator/setup-miniconda@v3 + uses: conda-incubator/setup-miniconda@v4 with: miniforge-variant: Miniforge3 miniforge-version: latest @@ -140,8 +151,12 @@ jobs: python-version: ${{ matrix.python-version }} activate-environment: pysr-test environment-file: environment.yml + - name: "Set up Julia" + uses: julia-actions/setup-julia@v3 + with: + version: "1" - name: "Cache Julia" - uses: julia-actions/cache@v2 + uses: julia-actions/cache@v3 with: cache-name: ${{ matrix.os }}-conda-${{ matrix.python-version }} cache-packages: false @@ -151,6 +166,9 @@ jobs: python3 -c 'import pysr' - name: "Run tests" run: cd /tmp && python -m pysr test main + env: + PYSR_TEST_SHARD_COUNT: "4" + PYSR_TEST_SHARD_INDEX: ${{ matrix.shard-index }} types: name: Check types @@ -161,14 +179,14 @@ jobs: strategy: matrix: python-version: - - '3.13' + - '3.14' - '3.10' os: ['ubuntu-latest'] steps: - uses: actions/checkout@v7 - name: "Set up Python" - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} cache: pip @@ -184,7 +202,7 @@ jobs: if: ${{ matrix.python-version == '3.10' }} beartype: - name: Test with beartype + name: Test with beartype (shard ${{ matrix.shard-index }}) runs-on: ubuntu-latest defaults: run: @@ -192,13 +210,14 @@ jobs: env: PYSR_USE_BEARTYPE: "1" strategy: + fail-fast: false matrix: - python-version: ['3.13'] - + python-version: ['3.14'] + shard-index: [0, 1, 2, 3] steps: - uses: actions/checkout@v7 - name: "Set up Python" - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} cache: pip @@ -208,6 +227,9 @@ jobs: pip install '.[dev]' - name: "Run tests" run: python -m pysr test main,jax,torch + env: + PYSR_TEST_SHARD_COUNT: "4" + PYSR_TEST_SHARD_INDEX: ${{ matrix.shard-index }} autodiff_backends: name: Test autodiff backends @@ -217,17 +239,17 @@ jobs: shell: bash -l {0} strategy: matrix: - python-version: ['3.13'] + python-version: ['3.14'] julia-version: ['1'] steps: - uses: actions/checkout@v7 - name: "Set up Julia" - uses: julia-actions/setup-julia@v2 + uses: julia-actions/setup-julia@v3 with: version: ${{ matrix.julia-version }} - name: "Set up Python" - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} cache: pip @@ -251,11 +273,11 @@ jobs: steps: - uses: actions/checkout@v7 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} cache: pip - - uses: julia-actions/setup-julia@v2 + - uses: julia-actions/setup-julia@v3 with: version: ${{ matrix.julia-version }} @@ -282,10 +304,9 @@ jobs: steps: - uses: actions/checkout@v7 - name: "Set up Python" - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: '3.x' - cache: pip - name: "Install validator deps" run: | python -m pip install --upgrade pip diff --git a/.github/workflows/CI_Windows.yml b/.github/workflows/CI_Windows.yml index b1d442395..a0ec7604d 100644 --- a/.github/workflows/CI_Windows.yml +++ b/.github/workflows/CI_Windows.yml @@ -15,19 +15,25 @@ on: - '**' workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: test: + name: Test (shard ${{ matrix.shard-index }}) runs-on: ${{ matrix.os }} timeout-minutes: 60 defaults: run: shell: bash strategy: + fail-fast: false matrix: julia-version: ['1'] - python-version: ['3.13'] + python-version: ['3.14'] os: [windows-latest] - + shard-index: [0, 1, 2, 3] steps: - uses: actions/checkout@v7 - name: "Set up Julia" @@ -35,12 +41,12 @@ jobs: with: version: ${{ matrix.julia-version }} - name: "Cache Julia" - uses: julia-actions/cache@v2 + uses: julia-actions/cache@v3 with: cache-name: ${{ matrix.os }}-test-${{ matrix.julia-version }}-${{ matrix.python-version }} cache-packages: false - name: "Set up Python" - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} cache: pip @@ -51,8 +57,13 @@ jobs: python -c 'import pysr' - name: "Run tests" run: | - python -m pysr test main,cli,startup + python -m pysr test main,cli,startup,interrupt + env: + PYSR_TEST_SHARD_COUNT: "4" + PYSR_TEST_SHARD_INDEX: ${{ matrix.shard-index }} - name: "Install Torch" run: pip install torch # (optional import) + if: ${{ matrix.shard-index == 0 }} - name: "Run Torch tests" run: python -m pysr test torch + if: ${{ matrix.shard-index == 0 }} diff --git a/.github/workflows/CI_apptainer.yml b/.github/workflows/CI_apptainer.yml index 7910b0ebe..c7902dffd 100644 --- a/.github/workflows/CI_apptainer.yml +++ b/.github/workflows/CI_apptainer.yml @@ -1,18 +1,18 @@ name: Apptainer on: - push: - branches: - - 'master' - paths: - - '**' - tags: - - 'v*.*.*' pull_request: branches: - 'master' + - 'release-*' paths: - - '**' + - '.github/workflows/CI_apptainer.yml' + - 'Apptainer.def' + - 'pyproject.toml' + - 'pysr/juliapkg.json' + - 'uv.lock' + schedule: + - cron: "0 10 8 * *" workflow_dispatch: jobs: diff --git a/.github/workflows/CI_conda_forge.yml b/.github/workflows/CI_conda_forge.yml index 6d17275aa..24294b61f 100644 --- a/.github/workflows/CI_conda_forge.yml +++ b/.github/workflows/CI_conda_forge.yml @@ -1,46 +1,60 @@ -name: conda-forge +name: conda-forge published-package canary -# This CI tries the conda-forge version of PySR +# Weekly smoke test for the latest published PySR package on conda-forge. on: + pull_request: + branches: + - 'master' + - 'release-*' + paths: + - '.github/workflows/CI_conda_forge.yml' schedule: - # Run at the 0th minute of the 10th hour (UTC). - # This means the job will run at 5am EST. - - cron: "0 10 * * *" - # This will automatically run on master branch only. + - cron: "0 10 * * 1" workflow_dispatch: jobs: conda_test: runs-on: ${{ matrix.os }} timeout-minutes: 60 + env: + SCIPY_ARRAY_API: "1" defaults: run: shell: bash -el {0} strategy: fail-fast: false matrix: - # NOTE: pin to avoid free-threaded Python variants (e.g. 3.14t) - python-version: ['3.10', '3.13'] + python-version: ['3.10', '3.14'] os: ['ubuntu-latest', 'windows-latest', 'macos-latest'] steps: - name: "Set up Conda" - uses: conda-incubator/setup-miniconda@v3 + uses: conda-incubator/setup-miniconda@v4 with: miniforge-variant: Miniforge3 miniforge-version: latest auto-activate-base: true python-version: ${{ matrix.python-version }} activate-environment: pysr-test + - name: "Set up Julia" + uses: julia-actions/setup-julia@v3 + with: + version: "1" - name: "Install pysr" run: | conda install -y pysr - python -c "import pysr" + python -c 'import importlib.metadata, pysr; print("Installed PySR", importlib.metadata.version("pysr"))' echo "Finished." - name: "Run tests" run: | echo "Running tests" pip install pytest nbval + if [[ "$RUNNER_OS" == "Windows" ]]; then + status=0 + python -m pysr test main,startup > canary.log 2>&1 || status=$? + cat canary.log + exit "$status" + fi python -m pysr test main,startup echo "Finished." diff --git a/.github/workflows/CI_docker.yml b/.github/workflows/CI_docker.yml index f71601c81..4a6080119 100644 --- a/.github/workflows/CI_docker.yml +++ b/.github/workflows/CI_docker.yml @@ -1,18 +1,18 @@ name: Docker on: - push: - branches: - - 'master' - paths: - - '**' - tags: - - 'v*.*.*' pull_request: branches: - 'master' + - 'release-*' paths: - - '**' + - '.github/workflows/CI_docker.yml' + - 'Dockerfile' + - 'pyproject.toml' + - 'pysr/juliapkg.json' + - 'uv.lock' + schedule: + - cron: "0 10 1 * *" workflow_dispatch: jobs: diff --git a/.github/workflows/CI_docker_large_nightly.yml b/.github/workflows/CI_docker_large_nightly.yml index c7215458b..1f6273b48 100644 --- a/.github/workflows/CI_docker_large_nightly.yml +++ b/.github/workflows/CI_docker_large_nightly.yml @@ -2,11 +2,14 @@ name: Docker_Large_Nightly on: schedule: - # Run at the 0th minute of the 10th hour (UTC). - # This means the job will run at 5am EST. - - cron: "0 10 * * *" - # This will automatically run on master branch only. + - cron: "0 10 15 * *" workflow_dispatch: + inputs: + include-arm64: + description: "Also run a slow linux/arm64 QEMU smoke test" + required: false + default: false + type: boolean jobs: test: @@ -21,7 +24,7 @@ jobs: julia-version: ['1.10', '1'] python-version: ['3.10', '3'] os: [ubuntu-latest] - arch: ['linux/amd64', 'linux/arm64'] + arch: ${{ fromJSON(github.event_name == 'workflow_dispatch' && github.event.inputs.include-arm64 == 'true' && '["linux/amd64", "linux/arm64"]' || '["linux/amd64"]') }} steps: diff --git a/.github/workflows/CI_large_nightly.yml b/.github/workflows/CI_large_nightly.yml index dfd495146..b6cf4d755 100644 --- a/.github/workflows/CI_large_nightly.yml +++ b/.github/workflows/CI_large_nightly.yml @@ -24,7 +24,7 @@ jobs: fail-fast: false matrix: julia-version: ['1.10', '1'] - python-version: ['3.10', '3.13'] + python-version: ['3.10', '3.14'] os: [ubuntu-latest, macos-latest, windows-latest] steps: @@ -34,7 +34,7 @@ jobs: with: version: ${{ matrix.julia-version }} - name: "Set up Python" - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} - name: "Install PySR" diff --git a/.github/workflows/CI_mac.yml b/.github/workflows/CI_mac.yml index 300f27c2e..b7b757aa0 100644 --- a/.github/workflows/CI_mac.yml +++ b/.github/workflows/CI_mac.yml @@ -15,19 +15,25 @@ on: - '**' workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: test: + name: Test (shard ${{ matrix.shard-index }}) runs-on: ${{ matrix.os }} timeout-minutes: 60 defaults: run: shell: bash strategy: + fail-fast: false matrix: julia-version: ['1'] - python-version: ['3.13'] + python-version: ['3.14'] os: [macos-latest] - + shard-index: [0, 1, 2, 3] steps: - uses: actions/checkout@v7 - name: "Set up Julia" @@ -35,12 +41,12 @@ jobs: with: version: ${{ matrix.julia-version }} - name: "Cache Julia" - uses: julia-actions/cache@v2 + uses: julia-actions/cache@v3 with: cache-name: ${{ matrix.os }}-test-${{ matrix.julia-version }}-${{ matrix.python-version }} cache-packages: false - name: "Set up Python" - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} cache: pip @@ -51,8 +57,13 @@ jobs: python -c 'import pysr' - name: "Run tests" run: | - python -m pysr test main,cli,startup + python -m pysr test main,cli,startup,interrupt + env: + PYSR_TEST_SHARD_COUNT: "4" + PYSR_TEST_SHARD_INDEX: ${{ matrix.shard-index }} - name: "Run JAX tests" run: python -m pysr test jax + if: ${{ matrix.shard-index == 0 }} - name: "Run Torch tests" run: python -m pysr test torch + if: ${{ matrix.shard-index == 0 }} diff --git a/.github/workflows/CI_slurm.yml b/.github/workflows/CI_slurm.yml index 8a9456e7e..41769c2ac 100644 --- a/.github/workflows/CI_slurm.yml +++ b/.github/workflows/CI_slurm.yml @@ -1,6 +1,7 @@ name: slurm on: + pull_request: schedule: - cron: "0 10 * * *" workflow_dispatch: @@ -16,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v7 - name: "Set up Python" - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: "3.12" - name: "Install PySR" diff --git a/.github/workflows/docker_deploy.yml b/.github/workflows/docker_deploy.yml index f3dc6aecc..f5d91773e 100644 --- a/.github/workflows/docker_deploy.yml +++ b/.github/workflows/docker_deploy.yml @@ -1,8 +1,6 @@ name: Deploy Docker on: - schedule: - - cron: "0 10 * * *" push: branches: - "master" @@ -19,19 +17,17 @@ jobs: matrix: os: [ubuntu-latest] arch: [linux/amd64] - python-version: [3.12.3] - julia-version: [1.10.3] steps: - name: Checkout uses: actions/checkout@v7 - name: Login to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@v4 if: github.event_name != 'pull_request' with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub registry - uses: docker/login-action@v3 + uses: docker/login-action@v4 if: github.event_name != 'pull_request' with: registry: ghcr.io @@ -39,7 +35,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Docker meta id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: # List of Docker images to use as base name for tags images: | @@ -58,9 +54,9 @@ jobs: - name: Set up QEMU uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Build and push - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . platforms: ${{ matrix.arch }} @@ -69,6 +65,3 @@ jobs: labels: ${{ steps.meta.outputs.labels }} cache-from: type=registry,ref=mcranmer/pysr:buildcache cache-to: type=registry,ref=mcranmer/pysr:buildcache,mode=max - build-args: | - PYVERSION=${{ matrix.python-version }} - JLVERSION=${{ matrix.julia-version }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 110b552dc..60f7ce394 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -20,6 +20,10 @@ on: - 'README.md' workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + permissions: contents: write pull-requests: read @@ -28,24 +32,26 @@ permissions: jobs: build: runs-on: ubuntu-latest - strategy: - matrix: - target: [primary, secondary] steps: + # determine_bases reads git tag to decide which aliases a release still owns. - uses: actions/checkout@v7 - - uses: actions/setup-node@v6 + with: + fetch-depth: 0 + - uses: actions/setup-node@v7 with: node-version: 22 cache: npm cache-dependency-path: docs/package-lock.json - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: '3.11' - - uses: julia-actions/setup-julia@v2 + - uses: julia-actions/setup-julia@v3 with: version: '1' - name: Install julia deps run: julia --project=docs -e 'using Pkg; Pkg.instantiate()' + - name: Test deployment versioning + run: julia --project=docs docs/test_deploy_versions.jl - name: Install python deps run: pip install -e .[docs] - name: Install npm deps @@ -60,5 +66,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} DAMTP_DEPLOY_KEY: ${{ secrets.DAMTP_DEPLOY_KEY }} - DEPLOYMENT_TARGET: ${{ matrix.target }} run: julia --project=docs docs/deploy.jl diff --git a/.github/workflows/paper_image_cleanup.yml b/.github/workflows/paper_image_cleanup.yml index 89df4a10b..c515d6b65 100644 --- a/.github/workflows/paper_image_cleanup.yml +++ b/.github/workflows/paper_image_cleanup.yml @@ -28,7 +28,7 @@ jobs: fetch-depth: 1 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v7 with: python-version: "3.11" diff --git a/.github/workflows/pypi_deploy.yml b/.github/workflows/pypi_deploy.yml index 77bbe04ee..3e94931ce 100644 --- a/.github/workflows/pypi_deploy.yml +++ b/.github/workflows/pypi_deploy.yml @@ -22,7 +22,7 @@ jobs: with: ref: ${{ inputs.publish_ref }} - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: 3.10.8 - name: Install building tools @@ -32,12 +32,11 @@ jobs: - name: Publish distribution 📦 to Test PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: - password: ${{ secrets.TEST_PYPI_API_TOKEN }} repository-url: https://test.pypi.org/legacy/ skip-existing: true + attestations: false verbose: true - name: Publish distribution 📦 to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: - password: ${{ secrets.PYPI_API_TOKEN }} verbose: true diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 75a480d54..a9d9e674a 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -19,7 +19,7 @@ jobs: sha: ${{ steps.release.outputs.sha }} steps: - id: release - uses: googleapis/release-please-action@v4 + uses: googleapis/release-please-action@v5 with: token: ${{ secrets.GITHUB_TOKEN }} target-branch: master @@ -42,7 +42,7 @@ jobs: with: ref: ${{ needs.release-please.outputs.sha }} - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: 3.10.8 - name: Install building tools @@ -52,12 +52,24 @@ jobs: - name: Publish distribution 📦 to Test PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: - password: ${{ secrets.TEST_PYPI_API_TOKEN }} repository-url: https://test.pypi.org/legacy/ skip-existing: true + attestations: false verbose: true - name: Publish distribution 📦 to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: - password: ${{ secrets.PYPI_API_TOKEN }} verbose: true + + deploy-docs: + needs: release-please + if: ${{ needs.release-please.outputs.release_created == 'true' }} + runs-on: ubuntu-latest + permissions: + actions: write + steps: + # A tag pushed with GITHUB_TOKEN cannot start a workflow run, but workflow_dispatch can. + - name: Build documentation for the new tag + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh workflow run docs.yml --repo ${{ github.repository }} --ref ${{ needs.release-please.outputs.tag_name }} diff --git a/.github/workflows/update_backend.yml b/.github/workflows/update_backend.yml index 9efd9c0d1..e8be9b6c6 100644 --- a/.github/workflows/update_backend.yml +++ b/.github/workflows/update_backend.yml @@ -3,6 +3,12 @@ on: schedule: - cron: '00 00 * * *' workflow_dispatch: + inputs: + force: + description: "Force update even if an open PR already exists" + required: false + default: false + type: boolean permissions: contents: write @@ -14,10 +20,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: '3' - cache: pip - name: "Install dependencies" run: python -m pip install --upgrade pip @@ -30,12 +35,25 @@ jobs: cd SymbolicRegression.jl echo "version=$(git describe --tags --match='v*' --abbrev=0 | sed 's/^v//')" >> $GITHUB_OUTPUT + - name: "Check for existing open PR" + id: existing-pr + env: + GH_TOKEN: ${{ github.token }} + run: | + if gh pr list --repo "$GITHUB_REPOSITORY" --head "backend-update/v${{ steps.get-latest.outputs.version }}" --state open | grep -q .; then + echo "exists=true" >> $GITHUB_OUTPUT + else + echo "exists=false" >> $GITHUB_OUTPUT + fi + - name: "Update SymbolicRegression.jl version in PySR" + if: ${{ steps.existing-pr.outputs.exists != 'true' || (github.event_name == 'workflow_dispatch' && inputs.force) }} run: | python .github/workflows/update_backend_version.py ${{ steps.get-latest.outputs.version }} - name: "Create PR if necessary" id: cpr + if: ${{ steps.existing-pr.outputs.exists != 'true' || (github.event_name == 'workflow_dispatch' && inputs.force) }} uses: peter-evans/create-pull-request@v8 with: branch: backend-update/v${{ steps.get-latest.outputs.version }} @@ -50,7 +68,7 @@ jobs: pysr/juliapkg.json - name: "Trigger CI workflows (backend update PR)" - if: steps.cpr.outputs.pull-request-number != '' + if: ${{ steps.cpr.outputs.pull-request-number != '' || (github.event_name == 'workflow_dispatch' && inputs.force) }} env: GH_TOKEN: ${{ github.token }} run: | @@ -58,7 +76,3 @@ jobs: gh workflow run "Linux" --repo "$GITHUB_REPOSITORY" --ref "$ref" || true gh workflow run "Windows" --repo "$GITHUB_REPOSITORY" --ref "$ref" || true gh workflow run "macOS" --repo "$GITHUB_REPOSITORY" --ref "$ref" || true - gh workflow run "Docker" --repo "$GITHUB_REPOSITORY" --ref "$ref" || true - gh workflow run "Apptainer" --repo "$GITHUB_REPOSITORY" --ref "$ref" || true - gh workflow run "Documentation" --repo "$GITHUB_REPOSITORY" --ref "$ref" || true - gh workflow run "CodeQL" --repo "$GITHUB_REPOSITORY" --ref "$ref" || true diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 3f5ba0b24..69e82f12f 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "2.0.0a2" + ".": "2.6.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 10cae715c..c96e61711 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,761 @@ # Changelog +## [2.6.0](https://github.com/astroautomata/PySR/compare/v2.5.2...v2.6.0) (2026-09-29) + + +### Backend: SymbolicRegression.jl 2.4.2 → 2.5.0 + +PySR now uses [SymbolicRegression.jl 2.5.0](https://github.com/astroautomata/SymbolicRegression.jl/releases/tag/v2.5.0) ([#1398](https://github.com/astroautomata/PySR/issues/1398)). The new backend makes the first fit in a Python session faster and speeds up `parallelism="multiprocessing"`, mostly at start-up. Search behavior is unchanged. + +* **Faster first fit.** PySR always loads PythonCall, and loading it invalidated much of the code that SymbolicRegression.jl precompiles, so every Python session recompiled the search on its first fit. The backend now reruns its precompile workload after PythonCall loads, including a search shaped like PySR's default call. On a 12-core node, the README example's first fit went from 17.4 s to 7.0 s. Custom operators still compile on first use, and installing or updating the backend takes a few seconds longer, once. ([SymbolicRegression.jl#743](https://github.com/astroautomata/SymbolicRegression.jl/pull/743)) +* **Multiprocessing start-up checks run on all workers at once.** Before a search, the main process runs quick checks on every worker. It used to wait for the answers one worker at a time, and each worker's first answer includes compiling code, so the waits added up. It now waits on all workers together. If a check fails on a worker, the original error is still raised, after every worker has answered. ([SymbolicRegression.jl#745](https://github.com/astroautomata/SymbolicRegression.jl/pull/745)) +* **Workers fetch their own starting populations.** During warm-up, the main process used to pull each starting population back from its worker, one at a time, before sending it out again. Each worker now fetches its own, as in PySR 1. ([SymbolicRegression.jl#744](https://github.com/astroautomata/SymbolicRegression.jl/pull/744)) +* **The equations file is rewritten only when it changes.** The hall-of-fame CSV used to be rewritten on every cycle. It is now written only when the best equations change. This applies to every parallelism mode. ([SymbolicRegression.jl#747](https://github.com/astroautomata/SymbolicRegression.jl/pull/747)) + +On one 128-core AMD EPYC node with `procs=128` (256 populations, 2,000 rows, 5 features), a 100-iteration multiprocessing fit went from about 264 s to 136 s, and a 1-iteration fit from about 176 s to 54 s. + +## [2.5.2](https://github.com/astroautomata/PySR/compare/v2.5.1...v2.5.2) (2026-09-28) + + +### Performance Improvements + +* check for Julia functions with isa ([#1395](https://github.com/astroautomata/PySR/issues/1395)) ([bfba1b6](https://github.com/astroautomata/PySR/commit/bfba1b62e3d4d4db843c7c9f4da7253e21caee72)) +* copy NumPy fit data through PyArray ([#1393](https://github.com/astroautomata/PySR/issues/1393)) ([415700d](https://github.com/astroautomata/PySR/commit/415700d6ce3083b380c846bfd47a9f9b4e760320)) +* pass operators and constraints to Julia as typed values ([#1394](https://github.com/astroautomata/PySR/issues/1394)) ([81cb12b](https://github.com/astroautomata/PySR/commit/81cb12b7d927ce3b9bf86465500fc5dd3fa74572)) + + +### Documentation + +* update moved UAV skin friction paper link ([#1391](https://github.com/astroautomata/PySR/issues/1391)) ([f60d7e4](https://github.com/astroautomata/PySR/commit/f60d7e49fec05e1b27fa15589f6cd56f4360c035)) + +## [2.5.1](https://github.com/astroautomata/PySR/compare/v2.5.0...v2.5.1) (2026-09-27) + + +### Bug Fixes + +* stop searches gracefully on Windows console Ctrl-C ([#1386](https://github.com/astroautomata/PySR/issues/1386)) ([da64e04](https://github.com/astroautomata/PySR/commit/da64e0453148263d83765ba3ce8f66f74c68875c)) + + +### Documentation + +* fix broken links, version menu, same-tab nav, and favicon ([#1388](https://github.com/astroautomata/PySR/issues/1388)) ([e5b5454](https://github.com/astroautomata/PySR/commit/e5b5454a4f31f6902af52dff7a4c23c961d6dbf2)) +* point Julia documentation links to julia.pysr.ai ([#1387](https://github.com/astroautomata/PySR/issues/1387)) ([e999da7](https://github.com/astroautomata/PySR/commit/e999da72531bcd3975789c6670939d36d5970186)) +* serve documentation at pysr.ai ([#1384](https://github.com/astroautomata/PySR/issues/1384)) ([7873cfa](https://github.com/astroautomata/PySR/commit/7873cfafddc1cf501da8a14ce18be8270c76388a)) + +## [2.5.0](https://github.com/astroautomata/PySR/compare/v2.4.1...v2.5.0) (2026-09-20) + + +### Features + +* enable concurrent GC sweeping by default ([#1379](https://github.com/astroautomata/PySR/issues/1379)) ([5f14395](https://github.com/astroautomata/PySR/commit/5f14395eee3438f93ee39b56d07ddcf971769057)) + + +### Dependencies + +* require JuliaCall 0.9.36 and remove workaround ([#1380](https://github.com/astroautomata/PySR/issues/1380)) ([51aea3e](https://github.com/astroautomata/PySR/commit/51aea3e13a43b5acf3bee53643bfdfe76db05d68)) + + +### Documentation + +* compress agent skill ([#1375](https://github.com/astroautomata/PySR/issues/1375)) ([57ed43a](https://github.com/astroautomata/PySR/commit/57ed43ad65651c7a681f75a23ef0e58f72a32e1b)) +* document complexity_mapping, guesses in SKILL ([#1372](https://github.com/astroautomata/PySR/issues/1372)) ([b566bdb](https://github.com/astroautomata/PySR/commit/b566bdb42f8eb169141b8659410d5735db49bf2f)) +* explain agent skill installation in README ([#1377](https://github.com/astroautomata/PySR/issues/1377)) ([258872e](https://github.com/astroautomata/PySR/commit/258872e309c469f2078675bd724611057e4d38c5)) +* fix <details> headings ([#1374](https://github.com/astroautomata/PySR/issues/1374)) ([8507021](https://github.com/astroautomata/PySR/commit/85070216ec3012c27f7e196143e2ba113b7d7a9e)) +* link Julia docs to the pinned backend version ([#1370](https://github.com/astroautomata/PySR/issues/1370)) ([aecba32](https://github.com/astroautomata/PySR/commit/aecba3215592e8e85f49fbc193a0a032284c0073)) + +## [2.4.1](https://github.com/astroautomata/PySR/compare/v2.4.0...v2.4.1) (2026-09-16) + + +### Documentation + +* expand examples and split into topic pages ([#1350](https://github.com/astroautomata/PySR/issues/1350)) ([49773c3](https://github.com/astroautomata/PySR/commit/49773c30d43fb2d968d698605f72224489f74445)) +* restore accurate Python API feature card ([#1368](https://github.com/astroautomata/PySR/issues/1368)) ([876d3bb](https://github.com/astroautomata/PySR/commit/876d3bba0a05c1e51eb08b6c3aff7601dd4e06f9)) + +## [2.4.0](https://github.com/astroautomata/PySR/compare/v2.3.1...v2.4.0) (2026-09-10) + + +### Features + +* add an optional invalid value hook ([#1361](https://github.com/astroautomata/PySR/issues/1361)) ([f3420e8](https://github.com/astroautomata/PySR/commit/f3420e873c4e86bacdfeb1e5f4caa6a52845ddd4)) +* add TypeSpec definitions for methods on the generated type ([#1359](https://github.com/astroautomata/PySR/issues/1359)) ([4c61b90](https://github.com/astroautomata/PySR/commit/4c61b906f5f2f225959f84a8d0b77c79e292e36f)) + + +### Documentation + +* update README and documentation to the new PySR logo ([#1357](https://github.com/astroautomata/PySR/issues/1357)) ([11d1ae2](https://github.com/astroautomata/PySR/commit/11d1ae2d95b8467fede423b1ea4121e264cf193e)) + +## [2.3.1](https://github.com/astroautomata/PySR/compare/v2.3.0...v2.3.1) (2026-09-10) + + +### Bug Fixes + +* restore Python executable for JuliaCall 0.9.35 ([#1362](https://github.com/astroautomata/PySR/issues/1362)) ([61b9027](https://github.com/astroautomata/PySR/commit/61b90272450f248cab237da27475d178501fcade)) + +## [2.3.0](https://github.com/astroautomata/PySR/compare/v2.2.1...v2.3.0) (2026-09-07) + + +### Features + +* expose search tracing on PySRRegressor ([#1347](https://github.com/astroautomata/PySR/issues/1347)) ([e694439](https://github.com/astroautomata/PySR/commit/e694439d498ae7ce6cbf0fe8ab40949944016416)) +* seed template parameters through guesses ([#1352](https://github.com/astroautomata/PySR/issues/1352)) ([fe41c38](https://github.com/astroautomata/PySR/commit/fe41c3858972822064b055aa476404b168cb996a)) + + +### Bug Fixes + +* **docker:** use Debian Trixie base ([#1356](https://github.com/astroautomata/PySR/issues/1356)) ([567ed69](https://github.com/astroautomata/PySR/commit/567ed690c88dd54c3851579701bd1039112f8f95)) +* migrate tracing to JSON with SymbolicRegression 2.4 ([#1354](https://github.com/astroautomata/PySR/issues/1354)) ([203f13c](https://github.com/astroautomata/PySR/commit/203f13c492d8bf7d3ccbb00e1dc5375df2380c8a)) + + +### Documentation + +* clarify guesses and template parameters ([#1353](https://github.com/astroautomata/PySR/issues/1353)) ([0caa20a](https://github.com/astroautomata/PySR/commit/0caa20a9d7828c2a90622a013627fa6257ab4c50)) +* generate legacy top-level redirects from the stable release ([#1346](https://github.com/astroautomata/PySR/issues/1346)) ([5114e66](https://github.com/astroautomata/PySR/commit/5114e668bc2f45386113f702abf93772fd5d9f66)) +* redirect nested documentation pages through stable ([#1348](https://github.com/astroautomata/PySR/issues/1348)) ([24cc669](https://github.com/astroautomata/PySR/commit/24cc669edf7bdc881a42b0e33d58675e2e6ba4fc)) +* replace README header video with a logo hero ([#1342](https://github.com/astroautomata/PySR/issues/1342)) ([8ffc78a](https://github.com/astroautomata/PySR/commit/8ffc78ab1851e4bcf9e51ec2d7cdd4755e41b881)) +* tweak badges ([#1344](https://github.com/astroautomata/PySR/issues/1344)) ([21e2ed5](https://github.com/astroautomata/PySR/commit/21e2ed552ffa4233e711539be12d9610ec724c40)) + +## [2.2.1](https://github.com/astroautomata/PySR/compare/v2.2.0...v2.2.1) (2026-09-02) + + +### Bug Fixes + +* require juliacall 0.9.29+ and Python 3.10+ ([#1335](https://github.com/astroautomata/PySR/issues/1335)) ([b87067a](https://github.com/astroautomata/PySR/commit/b87067affa242d050ed124801872ea589f2323d0)) + +## [2.2.0](https://github.com/astroautomata/PySR/compare/v2.1.0...v2.2.0) (2026-09-02) + + +### Features + +* release the GIL during the search ([#1330](https://github.com/astroautomata/PySR/issues/1330)) ([b15bf62](https://github.com/astroautomata/PySR/commit/b15bf623d2ae44b1296288fc01b601d96de856b2)) +* stop searches gracefully on Windows interrupts ([#1329](https://github.com/astroautomata/PySR/issues/1329)) ([e180079](https://github.com/astroautomata/PySR/commit/e1800797e2e2b7e88e455f2667484e6a37ab89f8)) + + +### Documentation + +* correct AdaptiveMutationWeightsPlugin default state ([#1332](https://github.com/astroautomata/PySR/issues/1332)) ([9909624](https://github.com/astroautomata/PySR/commit/990962428fa1c946c0a9c27ab7e12db80dec867c)) +* update the stable redirect on every release ([#1328](https://github.com/astroautomata/PySR/issues/1328)) ([82c5d70](https://github.com/astroautomata/PySR/commit/82c5d70404d011a785590c5db353397171e1d938)) + +## [2.1.0](https://github.com/astroautomata/PySR/compare/v2.0.0...v2.1.0) (2026-08-29) + + +### Features + +* stop autoloading the juliacall IPython extension by default ([#1325](https://github.com/astroautomata/PySR/issues/1325)) ([b3f91a1](https://github.com/astroautomata/PySR/commit/b3f91a17cb75da8b2a5a520bf157f7491dd74e4c)) +* stop searches gracefully on interrupt instead of killing the kernel ([#1310](https://github.com/astroautomata/PySR/issues/1310)) ([5bd53f6](https://github.com/astroautomata/PySR/commit/5bd53f662ee1d3516f084d556f7871e2299411a0)) + + +### Documentation + +* add two research showcase papers ([#1323](https://github.com/astroautomata/PySR/issues/1323)) ([7727b01](https://github.com/astroautomata/PySR/commit/7727b0179a97114de317f136c15b1f83bb7ceff0)) +* update SKILL.md to v2 ([#1321](https://github.com/astroautomata/PySR/issues/1321)) ([d64ef62](https://github.com/astroautomata/PySR/commit/d64ef62b48926b1871dd0930cc5ee21d6618814c)) + +## [2.0.0](https://github.com/astroautomata/PySR/compare/v1.5.9...v2.0.0) (2026-08-25) + +PySR 2.0.0 brings a two-year transformation of the library into the Python API, moving from a fixed scalar-tree search interface to a modular PyTorch-like framework for symbolic learning while keeping the familiar v1 estimator workflow. Operators can take any number of arguments, `TypeSpec` supports user-defined value types, and mutations, crossovers, and the search loop are configurable objects. Guesses mix expressions into populations throughout a run, which helps connect PySR to agentic coding loops. Automatic batching and a reusable backend evaluation buffer make large searches faster with less configuration. + +--- + +### Highlights + +#### Operators with any number of arguments + +`operators` takes an arity-keyed dict, so ternary operators like `clamp`, `fma`, and `muladd`, along with `max`/`min` over three or more arguments, are searchable ([#999](https://github.com/astroautomata/PySR/pull/999)). Before v2 you had to fake `clamp(x0*x1, -1, 1)` as a tall nest of binary operators, which the search rarely found and never found cheaply. `binary_operators` and `unary_operators` still work, and they are mutually exclusive with `operators`. + +```python +from pysr import PySRRegressor + +model = PySRRegressor( + operators={1: ["sin"], 2: ["+", "*", "-"], 3: ["clamp", "fma"]}, + niterations=40, +) +model.fit(X, y) +``` + +
+How arity flows through the tree type, dimensional analysis, and SymPy export + +The node type became `Node{T,D}` in DynamicExpressions ([#127](https://github.com/SymbolicML/DynamicExpressions.jl/pull/127)), where `D` is the maximum arity, and SymbolicRegression.jl generalized mutation, crossover, constraint checking, and dimensional analysis over it ([#471](https://github.com/astroautomata/SymbolicRegression.jl/pull/471), [#472](https://github.com/astroautomata/SymbolicRegression.jl/pull/472), [#464](https://github.com/astroautomata/SymbolicRegression.jl/pull/464)). + +`constraints` entries must now match operator arity exactly: a tuple of length N for an N-argument operator, else `ValueError: Operator '' has arity N but constraint tuple has length M`. Unary operators still default to `-1`, and arity 2 and above default to `tuple([-1] * arity)`. + +SymPy export keeps up ([#999](https://github.com/astroautomata/PySR/pull/999)): `Max(*args)` and `Min(*args)` replace the old two-argument `Piecewise` form, and `fma`, `muladd`, and `clamp` gained mappings. That is what makes these operators usable outside Julia. + +
+ +#### Seed the search with `guesses` + +Give PySR any guess for the final expressions, and it mixes those guesses into the populations throughout the search ([#999](https://github.com/astroautomata/PySR/pull/999); backend [#469](https://github.com/astroautomata/SymbolicRegression.jl/pull/469), [#500](https://github.com/astroautomata/SymbolicRegression.jl/pull/500)). When `should_optimize_constants=True`, constants in a guess are optimized before the candidate enters the population, so the structure can be useful even when its initial constants are inaccurate. `fraction_replaced_guesses` controls the fraction of each population drawn from guesses at the end of every cycle. Guesses also support custom value types ([#1316](https://github.com/astroautomata/PySR/pull/1316)). + +```python +from pysr import PySRRegressor + +model = PySRRegressor( + binary_operators=["+", "*"], + unary_operators=["sin"], + guesses=["sin(x0 * 2.1 - 0.5)", "x0 * 3.0 + x2"], + fraction_replaced_guesses=0.01, +) +model.fit(X, y) +``` + +
+Accepted guess shapes + +`guesses` is a `PySRRegressor` constructor parameter, not a `fit` keyword. + +Accepted shapes: + +- `list[str]` for single-output regression. +- `list[list[str]]` for multi-output, one inner list per output. A plain list with `nout > 1` raises `ValueError: For multi-output (nout > 1) guesses must be a list of lists`. +- `list[dict[str, str]]` for `TemplateExpressionSpec`, keyed by sub-expression name, using `#1`, `#2` as placeholders for the sub-expression arguments. + +Preparation happens in `_prepare_guesses_for_julia` (`pysr/sr.py:3413`). + +
+ +#### `TypeSpec`: symbolic regression over your own value type + +Declare a Julia struct and PySR will search over it ([#1280](https://github.com/astroautomata/PySR/pull/1280)). 2D vectors, strings, tensors, variable-length constant containers: anything you can write as a struct with a sampler. PySR compiles the declaration into an isolated fingerprinted Julia module and wires up evaluation, mutation, constant optimization, printing, and serialization. The hall of fame then prints equations whose leaves are not numbers. + +```python +from pysr import PySRRegressor, TypeSpec + +type_spec = TypeSpec( + name="Vec2", + fields={"data": "Vector{Float64}"}, + sample="rng -> Vec2(randn(rng, 2))", + scalar_constants="value -> value.data", + with_scalar_constants="(value, constants) -> Vec2(constants)", +) +model = PySRRegressor( + type_spec=type_spec, + operators={ + 1: ["rotate90(x::Vec2) = Vec2([-x.data[2], x.data[1]])"], + 2: ["add_vectors(x::Vec2, y::Vec2) = Vec2(x.data + y.data)"], + }, + elementwise_loss="vector_loss(x::Vec2, y::Vec2)::Float64 = sum(abs2, x.data - y.data)", +) +model.fit(X, y) +``` + +
+Validation, module compilation, and what each hook buys you + +`TypeSpec.__post_init__` validates eagerly: field names must be identifiers, `fields` must be non-empty, `scalar_constants` and `with_scalar_constants` must be supplied together, and a spec without them requires an explicit `mutate` so the search has some way to move. + +`scalar_constants` and `with_scalar_constants` are what turn on continuous constant optimization: the first flattens a value to a `Vector{Float64}`, the second rebuilds a value from optimized numbers. Supply `mutate` to define discrete moves instead, which is how string and container types work. + +Each spec compiles to one deterministic fingerprinted module per process. Identical source reuses the existing module; conflicting source for the same name warns on replacement, and checkpoint restore refuses a conflicting definition rather than silently binding to the wrong struct. Expression specs opt in through a new `supports_type_spec` property, `True` for `ExpressionSpec` and `TemplateExpressionSpec`. + +Under `parallelism="multiprocessing"`, a preamble that needs an external Julia package also needs `worker_imports=[...]`. + +For boxed element types, DynamicExpressions added fused-kernel indexing ([#198](https://github.com/SymbolicML/DynamicExpressions.jl/pull/198)): 27.9% faster on `String` keep-left and 15.5% faster on custom-struct addition in the backend's own benchmarks. + +
+ +#### Mutations are objects you can configure + +The thirteen built-in mutations became classes, passed as a weighted mapping; two of them, `ConstantMutation` and `BacksolveMutation`, carry their own hyperparameters ([#1282](https://github.com/astroautomata/PySR/pull/1282); backend [#610](https://github.com/astroautomata/SymbolicRegression.jl/pull/610), [#645](https://github.com/astroautomata/SymbolicRegression.jl/pull/645), [#663](https://github.com/astroautomata/SymbolicRegression.jl/pull/663)). `mutations=` overrides or extends the defaults by type. `default_mutations=` replaces the whole set. + +```python +from pysr import PySRRegressor, ConstantMutation, BacksolveMutation + +model = PySRRegressor( + mutations={ + ConstantMutation(perturbation_factor=0.1, probability_negate=0.02): 0.05, + BacksolveMutation( + max_library_size=1000, + max_terms=12, + min_improvement=1e-4, + node_attempts=16, + ): 0.1, + }, +) +model.fit(X, y) +``` + +
+The full class list, how weights resolve, and writing your own in Julia + +Exported from `pysr.mutations`: `AbstractMutation`, `ConstantMutation(perturbation_factor=0.086, probability_negate=0.01)`, `OperatorMutation`, `FeatureMutation`, `SwapOperandsMutation`, `AddNodeMutation`, `InsertNodeMutation`, `DeleteNodeMutation`, `RotateTreeMutation`, `BacksolveMutation(max_library_size=500, max_terms=8, min_improvement=1e-3, node_attempts=8)`, `SimplifyMutation`, `RandomizeMutation`, `OptimizeMutation`, `DoNothingMutation`. + +The scalar `weight_*` parameters still work and now default to `None`, with fallbacks equal to the v1 numbers. Reading `PySRRegressor().weight_add_node` gives `None` rather than `2.47`. `FeatureMutation` is new as a first-class move ([#475](https://github.com/astroautomata/SymbolicRegression.jl/pull/475), with `weight_mutate_feature` falling back to 0.1): rewiring a leaf to a different input column used to happen only as an accident of delete-then-add, and it helps most on wide-feature problems. + +On the Julia side, a mutation is a type plus a `mutate!` method, so you can define your own and pass it in the same mapping without touching the search loop. + +
+ +#### A composable search loop through plugins + +PySR 2.0 introduces plugins as the extension interface for the search loop ([#1282](https://github.com/astroautomata/PySR/pull/1282); backend [#645](https://github.com/astroautomata/SymbolicRegression.jl/pull/645), [#663](https://github.com/astroautomata/SymbolicRegression.jl/pull/663)). Simulated annealing, adaptive parsimony, adaptive mutation weights, and mutation bursts use explicit hooks for lifecycle events, selection biasing, mutation conditioning, and population seeding. This separates optional search behavior from the optimized core and lets several plugins compose in one run. + +```python +from pysr import PySRRegressor, AdaptiveMutationWeightsPlugin, MutationBurstPlugin + +model = PySRRegressor( + plugins=[ + AdaptiveMutationWeightsPlugin(smoothing=0.05, reward="loss"), + MutationBurstPlugin(retry_attempts=8), + ], +) +model.fit(X, y) +``` + +
+The plugin interface, defaults, and composition rules + +Exported from `pysr.plugins`: `AbstractPlugin`, `SimulatedAnnealingPlugin(alpha=0.1)`, `AdaptiveParsimonyPlugin(tournament=True, mutation_acceptance=True)`, `AdaptiveMutationWeightsPlugin(smoothing=0.02, floor=0.05, reward="cost"|"loss")`, and `MutationBurstPlugin(retry_attempts=4, compound_probability=0.25, compound_max_steps=2)`. + +`plugins=` extends and overrides `default_plugins=` by plugin type. `default_plugins=[]` runs the core search without the default plugin set. A custom plugin implements `AbstractPlugin` and the hooks it needs; unrelated hooks retain their default behavior. + +`AdaptiveMutationWeightsPlugin` is enabled by default ([#678](https://github.com/astroautomata/SymbolicRegression.jl/pull/678)). Mutation probabilities move toward operations that improve cost. In the backend's ASV runs, multithreaded runtime was 12.9 s without adaptation and 13.1 s with it, while the aggregate score improved by 0.0143. + +`MutationBurstPlugin` retries rejected mutations and can chain further mutations after an acceptance. `MutationBurstPlugin(retry_attempts=1, compound_max_steps=1)` restores the previous single-attempt behavior without consuming an extra RNG draw. + +The annealing schedule was ported to a plugin bit for bit. Restoring the original quotient after a `LinRange` rewrite preserved the previous hall-of-fame hashes ([#652](https://github.com/astroautomata/SymbolicRegression.jl/pull/652)). + +
+ +#### `BacksolveMutation`: analytic inversion plus sparse regression + +`BacksolveMutation` inverts the evaluation path to work out what a subtree *should* have returned, then fits a replacement by greedy forward selection over a library of the population's best subtrees, constrained by the remaining complexity budget ([#573](https://github.com/astroautomata/SymbolicRegression.jl/pull/573), thanks @ayagh19; exposed by [#1282](https://github.com/astroautomata/PySR/pull/1282)). It targets exactly the case random perturbation is bad at: a correct outer form with a wrong inner argument, like `sin(3.7*x0 + 0.1)` when the true phase is 2.4. + +```python +from pysr import PySRRegressor + +model = PySRRegressor( + binary_operators=["+", "*", "-"], + unary_operators=["sin"], + weight_backsolve=0.5, # effective default 0.0, experimental: knobs may move +) +model.fit(X, y) +``` + +
+How the inversion and the sparse fit work + +For a target subtree, backsolve walks up the tree, inverting each operator on the path to the root to get the residual target the subtree needs to produce. It then builds a library of candidate basis functions from the best subtrees currently in the population and uses greedy forward selection to fit a sparse combination within the remaining complexity budget. + +Tuning lives on the mutation object rather than in global weights: `BacksolveMutation(max_library_size=500, max_terms=8, min_improvement=1e-3, node_attempts=8)`. The `weight_backsolve` keyword sets its weight in the default mapping. + +
+ +#### Automatic batching + +`batching="auto"` and `batch_size=None` are the new defaults ([#1045](https://github.com/astroautomata/PySR/pull/1045), [#1285](https://github.com/astroautomata/PySR/pull/1285); backend [#676](https://github.com/astroautomata/SymbolicRegression.jl/pull/676)). Above 1000 rows PySR minibatches, choosing 128, 256, or 512 from the dataset size. Large fits get much faster with no configuration at all. Minibatches guide the inner evolution; hall-of-fame candidates are reevaluated on the full dataset before they are returned. Pass `batching=False` for the v1 search contract. + +```python +from pysr import PySRRegressor + +# 200k rows: v2 minibatches on its own +model = PySRRegressor(niterations=40) +model.fit(X, y) + +# full-data evolution, as in v1 +model = PySRRegressor(niterations=40, batching=False, batch_size=50) +model.fit(X, y) +``` + +Measured locally: the identical default `PySRRegressor` script on a 20000x5 dataset took a median 63.9 s on 1.5.9 and 12.7 s on PR #1280 head `e6064687456c8d7a1c8746f47f003b83f5d39bfb` (stale package metadata `2.0.0a2`; M1 Pro, 4 threads, 3 repeats, warmed). This is the batching default acting, and v1 prints its own warning recommending batching at this size. It is a defaults win, separate from the engine work below. + +
+The exact batching thresholds + +With `batching="auto"` and `batch_size=None`: full data for N <= 1000, batch size 128 for N < 5000, 256 for N < 50000, and 512 above that. Setting `batch_size` explicitly overrides the choice; setting `batching=True` or `batching=False` overrides the automatic decision. + +
+ +#### A reusable evaluation arena in the backend + +Evaluation buffers are now allocated once in a contiguous arena and reused across mutation, crossover, loss evaluation, constant optimization, and template inner calls ([#654](https://github.com/astroautomata/SymbolicRegression.jl/pull/654), [#668](https://github.com/astroautomata/SymbolicRegression.jl/pull/668); DynamicExpressions [#180](https://github.com/SymbolicML/DynamicExpressions.jl/pull/180), [#186](https://github.com/SymbolicML/DynamicExpressions.jl/pull/186); pulled into PySR by [#1282](https://github.com/astroautomata/PySR/pull/1282)). On SymbolicRegression.jl's own 8-thread benchmark this took a full search from 9.541 s to 5.880 s median with allocations dropping from 59.10 GB to 10.71 GB, and the hall of fame came out byte-identical. There is no API change; you get it by upgrading. + +```python +from pysr import PySRRegressor + +# nothing to configure +model = PySRRegressor(niterations=40) +model.fit(X, y) +``` + +Measured locally: the identical 2000x5 default fit script, warmed, 5 alternating repeats at 8 threads on an M1 Pro, ran a median 6.36 s on 1.5.9 and 5.26 s on PR #1280 head `e6064687456c8d7a1c8746f47f003b83f5d39bfb` (stale package metadata `2.0.0a2`). With batching pinned equal at 20000 rows, 1.5.9 was the faster of the two on this machine, so the arena figures above remain the backend's own result. Full numbers and method caveats are in the release measurement log. + +
+Where the allocations went, and the Julia-level API change behind it + +The 38.4% runtime and 81.9% allocation figures are SymbolicRegression.jl's own 8-thread arena benchmark, not a PySR measurement; treat them as the backend's numbers. + +`EvalOptions` was renamed `EvalContext` and arena lifetimes became caller-owned ([#187](https://github.com/SymbolicML/DynamicExpressions.jl/pull/187), [#192](https://github.com/SymbolicML/DynamicExpressions.jl/pull/192), [#186](https://github.com/SymbolicML/DynamicExpressions.jl/pull/186); [#668](https://github.com/astroautomata/SymbolicRegression.jl/pull/668), [#670](https://github.com/astroautomata/SymbolicRegression.jl/pull/670)). The deprecated `EvalOptions` binding is kept. Unknown evaluation keywords now error instead of being ignored. None of this is visible from Python. + +
+ +#### Faster startup + +Backend precompilation was narrowed to the workload PySR's `precision=32` default actually uses, the Float32 single-output search ([#642](https://github.com/astroautomata/SymbolicRegression.jl/pull/642)), and PySR sets `precompile_float64: false` in its Julia preferences ([#1279](https://github.com/astroautomata/PySR/pull/1279)). On a clean depot, precompilation drops from about 43 s to about 17 s and the cache from 59 to 39 MiB. This is precompile time, not steady-state fit time; the backend reports no repeatable change in default first-fit time. + +```python +# on a fresh Julia depot +import pysr +from pysr import PySRRegressor + +PySRRegressor(niterations=1).fit(X, y) +``` + +
+Why the Float32 path + +PySR defaults to `precision=32`, so precompiling the Float64 path paid for a workload most users never hit. The preference is set with the backend pin in `pysr/juliapkg.json`. + +
+ +--- + +### Notable changes + +- `autodiff_backend` accepts `"Zygote"`, `"Mooncake"`, and `"Enzyme"`, loading the Julia package on demand; Enzyme is no longer experimental ([#999](https://github.com/astroautomata/PySR/pull/999); [#468](https://github.com/astroautomata/SymbolicRegression.jl/pull/468), [#632](https://github.com/astroautomata/SymbolicRegression.jl/pull/632), [#537](https://github.com/astroautomata/SymbolicRegression.jl/pull/537), [#566](https://github.com/astroautomata/SymbolicRegression.jl/pull/566)). +- DynamicDiff v0.3 adds symbolic differentiation support for expressions containing n-arity operator nodes ([DynamicDiff #4](https://github.com/MilesCranmer/DynamicDiff.jl/pull/4)). +- All `weight_*` parameters default to `None`, with fallbacks equal to the v1 numbers, so behavior is preserved but `PySRRegressor().weight_add_node` reads `None` instead of `2.47` ([#1282](https://github.com/astroautomata/PySR/pull/1282)). +- Adaptive mutation weights are on by default: probabilities move during the run based on observed cost improvement, at parity overhead in the backend's ASV runs ([#678](https://github.com/astroautomata/SymbolicRegression.jl/pull/678), [#1282](https://github.com/astroautomata/PySR/pull/1282)). +- BREAKING: `cluster_manager="slurm"` now loads SlurmClusterManager.jl and expects an existing allocation. Request resources with `sbatch` or `salloc`, run Python once inside the allocation, and set `procs` to the allocation's task count; PySR no longer allocates for you. Other managers keep ClusterManagers ([#794](https://github.com/astroautomata/PySR/pull/794)). +- BREAKING: `requires-python >= 3.9`, `juliacall>=0.9.28,<0.9.36`, `pandas<4`, and the SymbolicRegression.jl `~2.0.0` backend requirement affect environment resolution ([#1052](https://github.com/astroautomata/PySR/pull/1052), [#1035](https://github.com/astroautomata/PySR/pull/1035), [#1129](https://github.com/astroautomata/PySR/pull/1129), [#1047](https://github.com/astroautomata/PySR/pull/1047), [#1312](https://github.com/astroautomata/PySR/pull/1312)). +- SymPy export gained `Max(*args)` and `Min(*args)` in place of two-argument `Piecewise`, plus mappings for `fma`, `muladd`, and `clamp`. Re-exported v1 models print differently ([#999](https://github.com/astroautomata/PySR/pull/999)). +- `FeatureMutation` and `weight_mutate_feature` (default 0.1) make rewiring a leaf to a different input column its own weighted move, which also removes a generate-and-reject loop in templates ([#475](https://github.com/astroautomata/SymbolicRegression.jl/pull/475), [#999](https://github.com/astroautomata/PySR/pull/999), [#1282](https://github.com/astroautomata/PySR/pull/1282)). +- `MutationBurstPlugin` retries a rejected mutation (4 attempts) and chains further mutations after acceptance (p = 0.25), flagged extra experimental; `retry_attempts=1, compound_max_steps=1` reproduces the old inner loop without consuming an extra RNG draw ([#645](https://github.com/astroautomata/SymbolicRegression.jl/pull/645), [#1282](https://github.com/astroautomata/PySR/pull/1282)). +- Custom JAX operator mappings survive checkpoint round trips ([#1199](https://github.com/astroautomata/PySR/pull/1199)). +- Documentation now includes the complete v1-to-v2 migration guide, a PDE discovery example, and updated agent skill guidance ([#1302](https://github.com/astroautomata/PySR/pull/1302), [#1311](https://github.com/astroautomata/PySR/pull/1311)). +- `worker_imports` and `worker_timeout` make external-package operators, objectives, and TypeSpec preambles work under multiprocessing, and restart a worker that stops responding ([#999](https://github.com/astroautomata/PySR/pull/999); [#488](https://github.com/astroautomata/SymbolicRegression.jl/pull/488)). + +
+ A multiprocessing run with an external Julia package + + ```python + from pysr import PySRRegressor + + model = PySRRegressor( + parallelism="multiprocessing", + procs=8, + worker_imports=["SpecialFunctions"], + worker_timeout=120.0, + unary_operators=["myerf(x) = SpecialFunctions.erf(x)"], + ) + model.fit(X, y) + ``` + + Workers see only the listed modules; arbitrary bindings from `Main` remain unavailable. + +
+ +- With `warm_start=True`, a fit that raises now restores the previous model state before re-raising, so a bad operator or a Ctrl-C no longer destroys the equations you already had ([#1280](https://github.com/astroautomata/PySR/pull/1280), `_rollback_failed_warm_start`). +- User Julia code survives pickling and multiprocessing: operators, objectives, complexity mappings, early-stop conditions, templates, and worker definitions are replayed where v1 could fail or silently lose them ([#1280](https://github.com/astroautomata/PySR/pull/1280)). +- Mis-shaped Julia loss functions are caught before the search starts, with a message naming the wrong signature instead of a `MethodError` five minutes in ([#1138](https://github.com/astroautomata/PySR/pull/1138), [#1184](https://github.com/astroautomata/PySR/pull/1184)). +- `torch_format` modules register constants as buffers, so an exported equation follows `.to(device)`, `.cuda()`, and `.half()` and appears in `state_dict()` ([#1058](https://github.com/astroautomata/PySR/pull/1058)). +- Backend correctness fixes: constant optimization escapes zero-valued constants, multiprocessing teardown stops hanging, an `X`/`y` row mismatch errors early with a `DimensionMismatch`, expression-level losses skip simplification, discrete custom-value mutation works, and hall-of-fame CSV quoting is fixed ([#637](https://github.com/astroautomata/SymbolicRegression.jl/pull/637), [#641](https://github.com/astroautomata/SymbolicRegression.jl/pull/641), [#660](https://github.com/astroautomata/SymbolicRegression.jl/pull/660), [#674](https://github.com/astroautomata/SymbolicRegression.jl/pull/674), [#687](https://github.com/astroautomata/SymbolicRegression.jl/pull/687), [#698](https://github.com/astroautomata/SymbolicRegression.jl/pull/698) via [#1282](https://github.com/astroautomata/PySR/pull/1282)). +- `tempdir=` is respected for temporary equation files, which unblocks read-only `/tmp` and quota-constrained HPC setups ([#1207](https://github.com/astroautomata/PySR/pull/1207)). +- The canonical repo is now [github.com/astroautomata/PySR](https://github.com/astroautomata/PySR) (old links redirect) and the docs at [ai.damtp.cam.ac.uk/pysr](https://ai.damtp.cam.ac.uk/pysr) moved from MkDocs to VitePress, so bookmarked deep links and local docs commands change. The PyPI name `pysr` and `import pysr` are unchanged ([#1272](https://github.com/astroautomata/PySR/pull/1272), [#1056](https://github.com/astroautomata/PySR/pull/1056); docs [#483](https://github.com/astroautomata/SymbolicRegression.jl/pull/483), [#491](https://github.com/astroautomata/SymbolicRegression.jl/pull/491)). +- The repo ships an agent skill at `skills/pysr/SKILL.md`, so coding agents read current v2 API guidance instead of guessing at v0.x-era arguments ([#1264](https://github.com/astroautomata/PySR/pull/1264)). +- Julia only: `machine`/`fit!`/`predict`/`report` work without MLJ or MLJBase loaded, via a new `SymbolicRegressionTablesExt` ([#680](https://github.com/astroautomata/SymbolicRegression.jl/pull/680)). +- Julia only: custom crossovers via `AbstractCrossover`, mirroring the mutation API with `SubtreeCrossover`, a `crossovers=` mapping, and an `attempt` counter so an expensive crossover can cheap out on retries. No PySR keyword ([#664](https://github.com/astroautomata/SymbolicRegression.jl/pull/664), [#666](https://github.com/astroautomata/SymbolicRegression.jl/pull/666)). +- BREAKING, Julia only: `use_recorder`/`recorder_file` are replaced by `use_tracing`/`tracing_file` writing versioned JSONL. Memory scales with in-flight records rather than the whole search history, and disabled tracing is verified zero-allocation ([#651](https://github.com/astroautomata/SymbolicRegression.jl/pull/651)). + +--- + +### Migration guide + +Ordered by the sequence you will actually hit them. + +#### Hard failures + +##### 1. Python 3.8 install fails + +`requires-python` moved from `>=3.8` to `>=3.9`. `juliacall` is constrained to `>=0.9.28,<0.9.36`, so an environment holding another juliacall consumer may fail to resolve ([#1052](https://github.com/astroautomata/PySR/pull/1052), [#1047](https://github.com/astroautomata/PySR/pull/1047), [#1312](https://github.com/astroautomata/PySR/pull/1312)). + +##### 2. No v1 checkpoint loads (`ValueError`) + +```python +# v1-written model.pkl +import pickle +model = pickle.load(open("model.pkl", "rb")) +# v2: ValueError: Unsupported PySR checkpoint schema: expected 3, found None. +``` + +Refitting is the only path. `warm_start` from a v1 checkpoint is impossible. Pickles from the 2.0 prerelease builds (schema 2) also fail at schema 3 ([#1282](https://github.com/astroautomata/PySR/pull/1282), gate at `pysr/sr.py:1623-1631`). + +##### 3. `ParametricExpressionSpec` is gone (`ImportError`) + +No shim ([#1277](https://github.com/astroautomata/PySR/pull/1277); backend [#656](https://github.com/astroautomata/SymbolicRegression.jl/pull/656)). + +```python +# v1 +from pysr import PySRRegressor, ParametricExpressionSpec +model = PySRRegressor(expression_spec=ParametricExpressionSpec(max_parameters=2)) +model.fit(X, y, category=category) +model.predict(X, category=category) + +# v2 +from pysr import PySRRegressor, TemplateExpressionSpec +spec = TemplateExpressionSpec( + expressions=["f"], + variable_names=["x1", "x2", "category"], + parameters={"p": n_categories}, + combine="p[category] * f(x1, x2)", +) +model = PySRRegressor(expression_spec=spec) +model.fit(X, y) # category is now a column of X, 1-indexed for Julia +model.predict(X) +``` + +##### 4. `category=` is gone from `fit` and `predict` (`TypeError`) + +Same fix as above. The Julia `NamedTuple{(:class,)}` extra-data path was removed with it ([#1277](https://github.com/astroautomata/PySR/pull/1277)). + +##### 5. Legacy positional `TemplateExpressionSpec` removed + +Dangerous because a positional call now silently changes meaning: `combine` is the first dataclass field, where `function_symbols` used to be ([#1280](https://github.com/astroautomata/PySR/pull/1280)). + +```python +# v1 legacy form (accepted in 1.5.9) +spec = TemplateExpressionSpec(["f", "g"], "sin(f(x1, x2)) + g(x3)^2", {"f": 2, "g": 1}) + +# v2 +spec = TemplateExpressionSpec( + combine="sin(f(x1, x2)) + g(x3)^2", + expressions=["f", "g"], + variable_names=["x1", "x2", "x3"], +) +``` + +`julia_expression_options()` and the `num_features` keyword are gone. A custom `AbstractExpressionSpec` subclass must implement `supports_type_spec`, `_julia_expression_spec_source(*, prototype)`, and `_julia_expression_spec_function_selector()` to support `type_spec`. + +##### 6. `operators` and `binary_operators`/`unary_operators` are mutually exclusive (`ValueError`) + +`_validate_and_modify_params` rejects the combination: "Cannot use `operators` with `binary_operators` or `unary_operators`." + +```python +# v1 +model = PySRRegressor(binary_operators=["+", "*"], unary_operators=["sin"]) + +# v2, either style, never both +model = PySRRegressor(operators={1: ["sin"], 2: ["+", "*"], 3: ["clamp", "fma"]}) +``` + +##### 7. `constraints` tuples must match operator arity exactly (`ValueError`) + +`Operator '' has arity N but constraint tuple has length M`. Unary operators still default to `-1`; arity 2 and above default to `tuple([-1] * arity)`. + +```python +# v1 +model = PySRRegressor( + binary_operators=["+", "*"], + unary_operators=["sin"], + constraints={"sin": 9, "*": (-1, 9)}, +) + +# v2, one entry per argument +model = PySRRegressor( + operators={1: ["sin"], 2: ["+", "*"], 3: ["clamp"]}, + constraints={"sin": 9, "*": (-1, 9), "clamp": (-1, 5, 5)}, +) +``` + +##### 8. HPC: `cluster_manager="slurm"` no longer allocates + +Request resources with `sbatch` or `salloc`, run Python once inside the allocation, and set `procs` to the allocation's task count ([#794](https://github.com/astroautomata/PySR/pull/794)). Other managers (`pbs`, `lsf`, `sge`, `qrsh`, `scyld`, `htc`) are unchanged. + +For example, a minimal batch script for 16 workers is: + +```bash +#!/bin/bash +#SBATCH --ntasks=16 + +python script.py +``` + +Submit it with `sbatch pysr_job.sh`. Do not wrap the Python command in `srun`, which would start one Python driver per task. + +```python +# inside script.py +model = PySRRegressor(parallelism="multiprocessing", cluster_manager="slurm", procs=16) +``` + +##### 9. Julia side only + +`use_recorder`/`recorder_file` become `use_tracing`/`tracing_file`; `Node{T}` becomes `Node{T,D}`; `EvalOptions` becomes `EvalContext`; `ParametricExpression` and `ParametricNode` are removed from SymbolicRegression.jl; SymbolicUtils is pinned to v4, so old SciML stacks cannot co-install; `EquationSearch`, `score_func`, and the old `calculate_pareto_frontier` signatures are deprecated. + +#### Silent behavior changes to re-tune or pin + +| parameter | v1 | v2 | note | +|---|---|---|---| +| `annealing` | `False` | `True` | matches SymbolicRegression.jl; changes every accept/reject decision | +| `crossover_probability` | `0.0259` | `0.2` | about 8x more recombination | +| `batching` | `False` | `"auto"` | on above 1000 rows; hall-of-fame candidates are reevaluated on the full dataset before return | +| `batch_size` | `50` | `None` | full data for N <= 1000, 128 for N < 5000, 256 for N < 50000, 512 above | +| all `weight_*` | floats | `None` | fallbacks match the v1 numbers, and adaptive weights now move them during the run | +| adaptive mutation weights | off | on | `AdaptiveMutationWeightsPlugin` is in the default set | +| SymPy export of `max`/`min` | `Piecewise` | `Max`/`Min` | re-exported v1 models print differently | +| torch export constants | Python floats | registered buffers | now in `state_dict()` and follow `.to(device)` | + +New `weight_*` entries: `weight_mutate_feature` (`None`, falling back to 0.1) and `weight_backsolve` (`None`, falling back to 0.0, so off). + +The `crossover_probability` move to 0.2 came out of a 560-search factorial ablation (+2.24% aggregate held-out Pareto NMSE) and a 420-search sweep in which 0.20 was the only setting that helped ([#643](https://github.com/astroautomata/SymbolicRegression.jl/pull/643)). `annealing=True` matches the backend default ([#1283](https://github.com/astroautomata/PySR/pull/1283); [#652](https://github.com/astroautomata/SymbolicRegression.jl/pull/652)). + +#### v1-like configuration + +For a search close to the v1 defaults, set the changed values explicitly and omit the adaptive-mutation plugin: + +```python +from pysr import AdaptiveParsimonyPlugin, PySRRegressor + +model = PySRRegressor( + batching=False, + batch_size=50, + annealing=False, + crossover_probability=0.0259, + default_plugins=[AdaptiveParsimonyPlugin()], + weight_add_node=2.47, + weight_insert_node=0.0112, + weight_delete_node=0.87, + weight_do_nothing=0.273, + weight_mutate_constant=0.0346, + weight_mutate_operator=0.293, + weight_mutate_feature=0.0, + weight_swap_operands=0.198, + weight_rotate_tree=4.26, + weight_randomize=0.000502, + weight_simplify=0.00209, + weight_optimize=0.0, + weight_backsolve=0.0, +) +``` + +This preserves the v1 search configuration, but it does not make a run bit-for-bit identical. Backend implementation changes still alter random-number consumption and search trajectories. + +--- + +### Other changes + +Docs: + +- `mutations` and `plugins` are placed in `pysr/param_groupings.yml`, so the generated options docs group them sensibly. + +Bug fixes: + +- `exports["jax_format"]` and `exports["torch_format"]` are `pd.Series` on the equations index rather than raw lists (`pysr/export.py`). +- Fitting with DataFrame column names containing spaces no longer breaks `predict` ([#1136](https://github.com/astroautomata/PySR/pull/1136)). +- `TemplateExpressionSpec.num_features` keys are converted to Julia symbols, fixing a silently ignored per-sub-expression feature limit on the legacy path that [#1280](https://github.com/astroautomata/PySR/pull/1280) then removed ([#1209](https://github.com/astroautomata/PySR/pull/1209)). +- `Complex{T}` derivatives work in DynamicDiff's ForwardDiff fallback and throw `DomainError` when Cauchy-Riemann disagrees, rather than returning a wrong gradient ([DynamicDiff #10](https://github.com/MilesCranmer/DynamicDiff.jl/pull/10)). + +Misc: + +- `pysr test` gained `autodiff` and `slurm` groups, so you can verify an Enzyme or Mooncake install, or a Slurm setup, locally ([#1111](https://github.com/astroautomata/PySR/pull/1111)). +- BREAKING: custom `AbstractExpressionSpec` subclasses must implement `supports_type_spec`, `_julia_expression_spec_source(*, prototype)`, and `_julia_expression_spec_function_selector()` to support `type_spec`; `julia_expression_options()` is gone ([#1280](https://github.com/astroautomata/PySR/pull/1280)). +- DynamicExpressions gained fused-kernel indexing for boxed element types: 27.9% faster on `String` keep-left, 15.5% on custom-struct addition, and about 1% for plain `Float64`, which matters to `TypeSpec` users ([#198](https://github.com/SymbolicML/DynamicExpressions.jl/pull/198)). +- SymbolicUtils is pinned to v4 and the SymbolicRegression.jl deprecated-API surface (`EquationSearch`, `score_func`, old `calculate_pareto_frontier` signatures) is formally deprecated in `src/deprecates.jl`. + +--- + +### Backend versions + +PySR `2.0.0` (tag `v2.0.0`) requires: + +- [SymbolicRegression.jl](https://github.com/astroautomata/SymbolicRegression.jl) `~2.0.0`, with `preferences: {"precompile_float64": false}` in `pysr/juliapkg.json`. [Release notes](https://github.com/astroautomata/SymbolicRegression.jl/releases/tag/v2.0.0). +- [DynamicExpressions.jl](https://github.com/SymbolicML/DynamicExpressions.jl) `~2.10` (up from `~1.10.1`). [Release notes](https://github.com/SymbolicML/DynamicExpressions.jl/releases). +- [DynamicDiff.jl](https://github.com/MilesCranmer/DynamicDiff.jl) `0.3` (up from `0.2`). [Release notes](https://github.com/MilesCranmer/DynamicDiff.jl/releases). +- SymbolicUtils.jl `4`. + +Docs: [ai.damtp.cam.ac.uk/pysr](https://ai.damtp.cam.ac.uk/pysr). Repo: [github.com/astroautomata/PySR](https://github.com/astroautomata/PySR). + + +## [2.0.0-beta.4](https://github.com/astroautomata/PySR/compare/v2.0.0-beta.3...v2.0.0-beta.4) (2026-08-24) + + +### Features + +* support guesses with custom types ([#1316](https://github.com/astroautomata/PySR/issues/1316)) ([6115158](https://github.com/astroautomata/PySR/commit/61151582bb21a58eefbccca9c979ce1fbe85f3fc)) + +## [2.0.0-beta.3](https://github.com/astroautomata/PySR/compare/v2.0.0-beta.2...v2.0.0-beta.3) (2026-08-22) + + +### Bug Fixes + +* preserve custom JAX mappings in checkpoints ([#1199](https://github.com/astroautomata/PySR/issues/1199)) ([0d78783](https://github.com/astroautomata/PySR/commit/0d78783b8aca3c3e42e8001397ee3a5a819dc6dd)) + + +### Dependencies + +* raise jax, ipython, ipykernel, and pytest-cov ceilings ([#1307](https://github.com/astroautomata/PySR/issues/1307)) ([3f37488](https://github.com/astroautomata/PySR/commit/3f37488da78d735df9bee37bf3a23bf01f98ee1c)) +* raise juliacall ceiling to 0.9.36 ([#1312](https://github.com/astroautomata/PySR/issues/1312)) ([494798e](https://github.com/astroautomata/PySR/commit/494798e76511aed9db1f57078c6ec8366c6e06fc)) + + +### Documentation + +* add PDE discovery example and skill guidance ([#1311](https://github.com/astroautomata/PySR/issues/1311)) ([459a720](https://github.com/astroautomata/PySR/commit/459a720bd72cb0bd02e96ba7b877460d4a2739c2)) +* add PySR v1 to v2 migration guide ([#1302](https://github.com/astroautomata/PySR/issues/1302)) ([b0bb321](https://github.com/astroautomata/PySR/commit/b0bb321451fc8a489c711a5a7c37bff03fbb50c9)) +* replace python feature card ([#1303](https://github.com/astroautomata/PySR/issues/1303)) ([b99a314](https://github.com/astroautomata/PySR/commit/b99a314da625d3bce5a9dc25e7618a8681a8ffc4)) + +## [2.0.0-beta.2](https://github.com/astroautomata/PySR/compare/v2.0.0-beta.1...v2.0.0-beta.2) (2026-08-18) + + +### ⚠ BREAKING CHANGES + +* the deprecated positional and `function_symbols=...` forms of `TemplateExpressionSpec` are removed; pass explicit `combine=`, `expressions=`, and `variable_names=` keywords. +* checkpoints written before this change (schema 2, from v2.0.0-beta.1 and earlier betas) fail to load with an explicit schema error rather than restoring incomplete state. + +### Features + +* accept sample_weight as an alias for weights in fit ([e2a159b](https://github.com/astroautomata/PySR/commit/e2a159b615b8258c192608cccde90a19fcc31563)) +* require keyword arguments for TemplateExpressionSpec ([e2a159b](https://github.com/astroautomata/PySR/commit/e2a159b615b8258c192608cccde90a19fcc31563)) +* support custom value types via TypeSpec ([#1280](https://github.com/astroautomata/PySR/issues/1280)) ([e2a159b](https://github.com/astroautomata/PySR/commit/e2a159b615b8258c192608cccde90a19fcc31563)) + + +### Bug Fixes + +* avoid duplicate PyPI attestations ([#1292](https://github.com/astroautomata/PySR/issues/1292)) ([71242e8](https://github.com/astroautomata/PySR/commit/71242e8420e90db7be4f8ff064ab3dc9df2c52d2)) +* bump the checkpoint schema to version 3 ([e2a159b](https://github.com/astroautomata/PySR/commit/e2a159b615b8258c192608cccde90a19fcc31563)) +* rebuild Julia-backed equation columns after unpickling ([e2a159b](https://github.com/astroautomata/PySR/commit/e2a159b615b8258c192608cccde90a19fcc31563)) + + +### Documentation + +* rewrite the examples for TypeSpec and template expressions ([e2a159b](https://github.com/astroautomata/PySR/commit/e2a159b615b8258c192608cccde90a19fcc31563)) + +## [2.0.0-beta.1](https://github.com/astroautomata/PySR/compare/v2.0.0a2...v2.0.0-beta.1) (2026-08-11) + + +### ⚠ BREAKING CHANGES + +* enable annealing by default ([#1283](https://github.com/astroautomata/PySR/issues/1283)) +* remove ParametricExpressionSpec ([#1277](https://github.com/astroautomata/PySR/issues/1277)) +* switch to SlurmClusterManager.jl for slurm allocations ([#794](https://github.com/astroautomata/PySR/issues/794)) + +### Features + +* expose new plugin interface + upgrade to v2.0.0-beta.3 ([#1282](https://github.com/astroautomata/PySR/issues/1282)) ([e20c880](https://github.com/astroautomata/PySR/commit/e20c88015cc4294ea41d52cc2e9345bed7f1ebac)) +* remove ParametricExpressionSpec ([#1277](https://github.com/astroautomata/PySR/issues/1277)) ([d5f0bb0](https://github.com/astroautomata/PySR/commit/d5f0bb0b4b1e5d13463ba988c3ff15fba00bfe13)) +* set precompile_float64=false preference for SymbolicRegression ([#1279](https://github.com/astroautomata/PySR/issues/1279)) ([b89f920](https://github.com/astroautomata/PySR/commit/b89f9209d8ead59974bcff8f0f295b71c4a8fb7c)) +* switch to SlurmClusterManager.jl for slurm allocations ([#794](https://github.com/astroautomata/PySR/issues/794)) ([49f44a4](https://github.com/astroautomata/PySR/commit/49f44a420c3c08c4406c8c9685ba9d34d7773b23)) + + +### Bug Fixes + +* convert num_features dict keys to Julia Symbols ([#1209](https://github.com/astroautomata/PySR/issues/1209)) ([8aa59b8](https://github.com/astroautomata/PySR/commit/8aa59b82bfbe29daba59e38c2c063d8184c9dd0b)), closes [#811](https://github.com/astroautomata/PySR/issues/811) +* enable annealing by default ([#1283](https://github.com/astroautomata/PySR/issues/1283)) ([f4dc86b](https://github.com/astroautomata/PySR/commit/f4dc86b21df97724f0e5efc8f9bc4ce34b8814d4)) +* respect tempdir for temporary equation files ([#1207](https://github.com/astroautomata/PySR/issues/1207)) ([beaa405](https://github.com/astroautomata/PySR/commit/beaa4053a1352789176b1b3bae356007dcbebabd)) + + +### Documentation + +* add agent skill for using PySR effectively ([#1264](https://github.com/astroautomata/PySR/issues/1264)) ([fdedcc8](https://github.com/astroautomata/PySR/commit/fdedcc892db4ae2fed289601718074ec45a596d0)) +* add angular coefficients paper ([da6d2d2](https://github.com/astroautomata/PySR/commit/da6d2d27f782156e5378ecd0255474cad5dc684d)) +* add biomass pyrolysis paper ([ccb473e](https://github.com/astroautomata/PySR/commit/ccb473ec715f353e32729f6921352a572f6ca71f)) +* add dark energy symbolic regression paper ([da9ad80](https://github.com/astroautomata/PySR/commit/da9ad80bc0187ca65f12b331d919c322264ea874)) +* add human mobility models paper ([f47c4d2](https://github.com/astroautomata/PySR/commit/f47c4d27496659ea97cc72ab9cfe138964c3ea53)) +* add microbial growth models paper ([f7c72fb](https://github.com/astroautomata/PySR/commit/f7c72fbc3ba13ed6e7e5f69f946c3c57d0a2d755)) +* add paper showcase entries ([9283914](https://github.com/astroautomata/PySR/commit/9283914523b52e5e992f4238d7ca6692de7825d1)) +* add s-stars chaos paper ([cfc4907](https://github.com/astroautomata/PySR/commit/cfc490762cac17ea248cfb83555c261779fa61e9)) +* add skin friction estimation paper ([8ec43f0](https://github.com/astroautomata/PySR/commit/8ec43f082e2b449d7dece4cb5ba2372915b81a7c)) +* add yawed wind turbines paper ([e1dc986](https://github.com/astroautomata/PySR/commit/e1dc986ef096b3c0c14e7f7f4429aee6b730f429)) +* update contributors list ([#1286](https://github.com/astroautomata/PySR/issues/1286)) ([10b3637](https://github.com/astroautomata/PySR/commit/10b36376b2866e09e8382669df20e4a7a1539ec5)) +* use Float32 literals in custom loss example ([#1276](https://github.com/astroautomata/PySR/issues/1276)) ([2bd7db2](https://github.com/astroautomata/PySR/commit/2bd7db238a70773c9b4882259b940f3dec8c8591)) + ## [2.0.0a2](https://github.com/MilesCranmer/PySR/compare/v2.0.0a1...v2.0.0a2) (2026-05-15) This is an alpha release of v2.0.0. It includes backend, packaging, export, and documentation updates since `v2.0.0a1`. diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index dc24232bd..9badcb607 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -6,7 +6,7 @@ In this guide you will get an overview of the contribution workflow from opening ## New contributor guide -To get an overview of the project, read PySR's [README](README.md). The [PySR docs](https://ai.damtp.cam.ac.uk/pysr/) give additional information. +To get an overview of the project, read PySR's [README](README.md). The [PySR docs](https://pysr.ai/) give additional information. Here are some resources to help you get started with open source contributions in general: - [Finding ways to contribute to open source on GitHub](https://docs.github.com/en/get-started/exploring-projects-on-github/finding-ways-to-contribute-to-open-source-on-github) @@ -39,7 +39,7 @@ Scan through our [existing issues](https://github.com/astroautomata/PySR/issues) 2. Create a working branch and start with your changes! 3. (Optional) If you would like to make changes to PySR itself, skip to step 4. However, if you are interested in making changes to the _symbolic regression code_ itself, -check out the [guide](https://ai.damtp.cam.ac.uk/pysr/backend/) on modifying a custom SymbolicRegression.jl library. +check out the [guide](https://pysr.ai/backend/) on modifying a custom SymbolicRegression.jl library. In this case, you might instead be interested in making suggestions to the [SymbolicRegression.jl](https://github.com/astroautomata/SymbolicRegression.jl) library. 4. You can install your local version of PySR with `pip install -e '.[dev]'`, and run tests with `python -m pysr test main`. @@ -77,51 +77,103 @@ Thanks for being part of the PySR community! - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Mark Kittisopikul
Mark Kittisopikul

💻 💡 🚇 📦 📣 👀 🔧 ⚠️
T Coxon
T Coxon

🐛 💻 🔌 💡 🚇 🚧 👀 🔧 ⚠️ 📓
Dhananjay Ashok
Dhananjay Ashok

💻 🌍 💡 🚧 ⚠️
Johan Blåbäck
Johan Blåbäck

🐛 💻 💡 🚧 📣 👀 ⚠️ 📓
JuliusMartensen
JuliusMartensen

🐛 💻 📖 🔌 💡 🚇 🚧 📦 📣 👀 🔧 📓
ngam
ngam

💻 🚇 📦 👀 🔧 ⚠️
Christopher Rowley
Christopher Rowley

💻 💡 🚇 📦 👀
Kaze Wong
Kaze Wong

🐛 💻 💡 🚇 🚧 📣 👀 🔬 📓
Adil
Adil
Christopher Rowley
Christopher Rowley
Mark Kittisopikul
Mark Kittisopikul
T Coxon
T Coxon
Dhananjay Ashok
Dhananjay Ashok
Johan Blåbäck
Johan Blåbäck
JuliusMartensen
JuliusMartensen
ngam
ngam
Christopher Rackauckas
Christopher Rackauckas

🐛 💻 🔌 💡 🚇 📣 👀 🔬 🔧 ⚠️ 📓
Patrick Kidger
Patrick Kidger

🐛 💻 📖 🔌 💡 🚧 📣 👀 🔬 🔧 ⚠️ 📓
Okon Samuel
Okon Samuel

🐛 💻 📖 🚧 💡 🚇 👀 ⚠️ 📓
William Booth-Clibborn
William Booth-Clibborn

💻 🌍 📖 📓 🚧 👀 🔧 ⚠️
Pablo Lemos
Pablo Lemos

🐛 💡 📣 👀 🔬 📓
Jerry Ling
Jerry Ling

🐛 💻 📖 🌍 💡 📣 👀 📓
Charles Fox
Charles Fox

🐛 💻 💡 🚧 📣 👀 🔬 📓
Johann Brehmer
Johann Brehmer

💻 📖 💡 📣 👀 🔬 ⚠️ 📓
Kaze Wong
Kaze Wong
Christopher Rackauckas
Christopher Rackauckas
Patrick Kidger
Patrick Kidger
Okon Samuel
Okon Samuel
William Booth-Clibborn
William Booth-Clibborn
Aya Ghaleb
Aya Ghaleb
gca30
gca30
Niklas Heim
Niklas Heim
Marius Millea
Marius Millea

💻 💡 📣 👀 📓
Coba
Coba

🐛 💻 💡 👀 📓
foxtran
foxtran

💻 💡 🚧 🔧 📓
Shah Mahdi Hasan
Shah Mahdi Hasan

🐛 💻 👀 📓
Pietro Monticone
Pietro Monticone

🐛 📖 💡
Mateusz Kubica
Mateusz Kubica

📖 💡
Jay Wadekar
Jay Wadekar

🐛 💡 📣 🔬
Anthony Blaom, PhD
Anthony Blaom, PhD

🚇 💡 👀
Atharva Sehgal
Atharva Sehgal
wkharold
wkharold
William Moses
William Moses
Ardeleanu Cristian
Ardeleanu Cristian
gm89uk
gm89uk
Pablo Lemos
Pablo Lemos
Jerry Ling
Jerry Ling
Charles Fox
Charles Fox
Jgmedina95
Jgmedina95

🐛 💡 👀
Michael Abbott
Michael Abbott

💻 💡 👀 🔧
Oscar Smith
Oscar Smith

💻 💡
Eric Hanson
Eric Hanson

💡 📣 📓
Henrique Becker
Henrique Becker

💻 💡 👀
qwertyjl
qwertyjl

🐛 📖 💡 📓
Rik Huijzer
Rik Huijzer

💡 🚇
Hongyu Wang
Hongyu Wang

💡 📣 🔬
Johann Brehmer
Johann Brehmer
Marius Millea
Marius Millea
Coba
Coba
foxtran
foxtran
Shah Mahdi Hasan
Shah Mahdi Hasan
Dilum Aluthge
Dilum Aluthge
Sebastian Micluța-Câmpeanu
Sebastian Micluța-Câmpeanu
Tim Holy
Tim Holy
Zehao Jin
Zehao Jin

🔬 📣
Tanner Mengel
Tanner Mengel

🔬 📣
Arthur Grundner
Arthur Grundner

🔬 📣
sjwetzel
sjwetzel

🔬 📣 📓
Saurav Maheshkar
Saurav Maheshkar

🔧
BrotherHa
BrotherHa
William Thompson
William Thompson
Tom Jelen
Tom Jelen
Miguel Crispim Romao
Miguel Crispim Romao
Andy Dienes
Andy Dienes
Harsh Singh
Harsh Singh
Pietro Monticone
Pietro Monticone
Mateusz Kubica
Mateusz Kubica
William Booth-Clibborn
William Booth-Clibborn
Raúl Peralta Lozada
Raúl Peralta Lozada
Roy Hvaara
Roy Hvaara
Vishal Jain
Vishal Jain
spaette
spaette
Yi-Xin Liu
Yi-Xin Liu
spinnau
spinnau
Xianda Sun
Xianda Sun
Jay Wadekar
Jay Wadekar
Anthony Blaom, PhD
Anthony Blaom, PhD
Jgmedina95
Jgmedina95
Michael Abbott
Michael Abbott
Oscar Smith
Oscar Smith
Eric Hanson
Eric Hanson
Henrique Becker
Henrique Becker
qwertyjl
qwertyjl
Rik Huijzer
Rik Huijzer
Hongyu Wang
Hongyu Wang
Zehao Jin
Zehao Jin
Tanner Mengel
Tanner Mengel
Arthur Grundner
Arthur Grundner
sjwetzel
sjwetzel
Saurav Maheshkar
Saurav Maheshkar
chris-soelistyo
chris-soelistyo
Ilya Orson
Ilya Orson
Ho Fung Tsoi
Ho Fung Tsoi
LionessOfCintra
LionessOfCintra
Manuel Morales
Manuel Morales
Paulo Montero Camacho
Paulo Montero Camacho
Writu Dasgupta
Writu Dasgupta
Anubhav Kamal
Anubhav Kamal
anthony-sun
anthony-sun
Hao Guo
Hao Guo
Trailblazer
Trailblazer
Christos Pliakos
Christos Pliakos
Sousa Neto
Sousa Neto
Leonardo Voltolini
Leonardo Voltolini
Daniel Eduardo Conde Villatoro
Daniel Eduardo Conde Villatoro
Sam Beckers
Sam Beckers
diff --git a/Dockerfile b/Dockerfile index ef817124f..9a2f133a6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ ARG JLVERSION=1.11 ARG PYVERSION=3.12 -ARG BASE_IMAGE=bullseye +ARG BASE_IMAGE=trixie FROM julia:${JLVERSION}-${BASE_IMAGE} AS jl FROM python:${PYVERSION}-${BASE_IMAGE} AS pysr-runtime diff --git a/README.md b/README.md index 62f575626..6377049ee 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,20 @@ -[//]: # (Logo:) +

PySR
Symbolic Regression in Python and Julia
Docs  ×  Examples  ×  API  ×  Forum  ×  Agents
PyPI conda-forge arXiv

-
- -PySR searches for symbolic expressions which optimize a particular objective. - -https://github.com/astroautomata/PySR/assets/7593028/c8511a49-b408-488f-8f18-b1749078268f - - -# PySR: High-Performance Symbolic Regression in Python and Julia - -| **Docs** | **Forums** | **Paper** | **colab demo** | -|:---:|:---:|:---:|:---:| -|[![Documentation](https://github.com/astroautomata/PySR/actions/workflows/docs.yml/badge.svg)](https://ai.damtp.cam.ac.uk/pysr/)|[![Discussions](https://img.shields.io/badge/discussions-github-informational)](https://github.com/astroautomata/PySR/discussions)|[![Paper](https://img.shields.io/badge/arXiv-2305.01582-b31b1b)](https://arxiv.org/abs/2305.01582)|[![Colab](https://img.shields.io/badge/colab-notebook-yellow)](https://colab.research.google.com/github/astroautomata/PySR/blob/master/examples/pysr_demo.ipynb)| - -| **pip** | **conda** | **Stats** | -| :---: | :---: | :---: | -|[![PyPI version](https://badge.fury.io/py/pysr.svg)](https://badge.fury.io/py/pysr)|[![Conda Version](https://img.shields.io/conda/vn/conda-forge/pysr.svg)](https://anaconda.org/conda-forge/pysr)|
pip: [![Downloads](https://static.pepy.tech/badge/pysr)](https://pypi.org/project/pysr/)
conda: [![Anaconda-Server Badge](https://anaconda.org/conda-forge/pysr/badges/downloads.svg)](https://anaconda.org/conda-forge/pysr)
| - -
+```bash +pip install pysr +``` If you find PySR useful, please cite the paper [arXiv:2305.01582](https://arxiv.org/abs/2305.01582). -If you've finished a project with PySR, please submit a PR to showcase your work on the [research showcase page](https://ai.damtp.cam.ac.uk/pysr/papers)! +If you've finished a project with PySR, please submit a PR to showcase your work on the [research showcase page](https://pysr.ai/papers)! **Contents**: - [Why PySR?](#why-pysr) - [Installation](#installation) - [Quickstart](#quickstart) -- [→ Documentation](https://ai.damtp.cam.ac.uk/pysr) +- [→ Documentation](https://pysr.ai) - [Contributors](#contributors-) -
- -### Test status - -| **Linux** | **Windows** | **macOS** | -|---|---|---| -|[![Linux](https://github.com/astroautomata/PySR/actions/workflows/CI.yml/badge.svg)](https://github.com/astroautomata/PySR/actions/workflows/CI.yml)|[![Windows](https://github.com/astroautomata/PySR/actions/workflows/CI_Windows.yml/badge.svg)](https://github.com/astroautomata/PySR/actions/workflows/CI_Windows.yml)|[![macOS](https://github.com/astroautomata/PySR/actions/workflows/CI_mac.yml/badge.svg)](https://github.com/astroautomata/PySR/actions/workflows/CI_mac.yml)| -| **Docker** | **Conda** | **Coverage** | -|[![Docker](https://github.com/astroautomata/PySR/actions/workflows/CI_docker.yml/badge.svg)](https://github.com/astroautomata/PySR/actions/workflows/CI_docker.yml)|[![conda-forge](https://github.com/astroautomata/PySR/actions/workflows/CI_conda_forge.yml/badge.svg)](https://github.com/astroautomata/PySR/actions/workflows/CI_conda_forge.yml)|[![codecov](https://codecov.io/gh/astroautomata/PySR/branch/master/graph/badge.svg)](https://codecov.io/gh/astroautomata/PySR)| - -
- ## Why PySR? PySR is an open-source tool for *Symbolic Regression*: a machine learning @@ -65,24 +38,29 @@ an explicit and powerful way to interpret deep neural networks. ## Installation -### Pip - -You can install PySR with pip: +You can install PySR with pip or conda: ```bash pip install pysr ``` +```bash +conda install -c conda-forge pysr +``` + Julia dependencies will be installed at first import. -### Conda +### AI Agent Skill -Similarly, with conda: +Install the [PySR skill](skills/pysr/SKILL.md) to provide your coding +agents guidance using PySR as part of a larger agentic loop: ```bash -conda install -c conda-forge pysr +npx skills add astroautomata/PySR ``` +Add `--global` to install the skill globally. +
@@ -255,16 +233,16 @@ model = PySRRegressor.from_file("hall_of_fame.2022-08-10_100832.281.pkl") There are several other useful features such as denoising (e.g., `denoise=True`), feature selection (e.g., `select_k_features=3`). -For examples of these and other features, see the [examples page](https://ai.damtp.cam.ac.uk/pysr/examples). -For a detailed look at more options, see the [options page](https://ai.damtp.cam.ac.uk/pysr/options). -You can also see the full API at [this page](https://ai.damtp.cam.ac.uk/pysr/api). -There are also tips for tuning PySR on [this page](https://ai.damtp.cam.ac.uk/pysr/tuning). +For examples of these and other features, see the [examples page](https://pysr.ai/examples). +For a detailed look at more options, see the [options page](https://pysr.ai/options). +You can also see the full API at [this page](https://pysr.ai/api). +There are also tips for tuning PySR on [this page](https://pysr.ai/tuning). ### Detailed Example The following code makes use of as many PySR features as possible. Note that is just a demonstration of features and you should not use this example as-is. -For details on what each parameter does, check out the [API page](https://ai.damtp.cam.ac.uk/pysr/api/). +For details on what each parameter does, check out the [API page](https://pysr.ai/api). ```python model = PySRRegressor( @@ -379,51 +357,103 @@ If you have an idea for a new feature, don't hesitate to share it on the [issues - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - - - + + + + + + +
Mark Kittisopikul
Mark Kittisopikul

💻 💡 🚇 📦 📣 👀 🔧 ⚠️
T Coxon
T Coxon

🐛 💻 🔌 💡 🚇 🚧 👀 🔧 ⚠️ 📓
Dhananjay Ashok
Dhananjay Ashok

💻 🌍 💡 🚧 ⚠️
Johan Blåbäck
Johan Blåbäck

🐛 💻 💡 🚧 📣 👀 ⚠️ 📓
JuliusMartensen
JuliusMartensen

🐛 💻 📖 🔌 💡 🚇 🚧 📦 📣 👀 🔧 📓
ngam
ngam

💻 🚇 📦 👀 🔧 ⚠️
Christopher Rowley
Christopher Rowley

💻 💡 🚇 📦 👀
Kaze Wong
Kaze Wong

🐛 💻 💡 🚇 🚧 📣 👀 🔬 📓
Adil
Adil
Christopher Rowley
Christopher Rowley
Mark Kittisopikul
Mark Kittisopikul
T Coxon
T Coxon
Dhananjay Ashok
Dhananjay Ashok
Johan Blåbäck
Johan Blåbäck
JuliusMartensen
JuliusMartensen
ngam
ngam
Kaze Wong
Kaze Wong
Christopher Rackauckas
Christopher Rackauckas
Patrick Kidger
Patrick Kidger
Okon Samuel
Okon Samuel
William Booth-Clibborn
William Booth-Clibborn
Aya Ghaleb
Aya Ghaleb
gca30
gca30
Niklas Heim
Niklas Heim
Atharva Sehgal
Atharva Sehgal
wkharold
wkharold
William Moses
William Moses
Ardeleanu Cristian
Ardeleanu Cristian
gm89uk
gm89uk
Pablo Lemos
Pablo Lemos
Jerry Ling
Jerry Ling
Charles Fox
Charles Fox
Johann Brehmer
Johann Brehmer
Marius Millea
Marius Millea
Coba
Coba
foxtran
foxtran
Shah Mahdi Hasan
Shah Mahdi Hasan
Dilum Aluthge
Dilum Aluthge
Sebastian Micluța-Câmpeanu
Sebastian Micluța-Câmpeanu
Tim Holy
Tim Holy
BrotherHa
BrotherHa
William Thompson
William Thompson
Tom Jelen
Tom Jelen
Miguel Crispim Romao
Miguel Crispim Romao
Andy Dienes
Andy Dienes
Harsh Singh
Harsh Singh
Pietro Monticone
Pietro Monticone
Mateusz Kubica
Mateusz Kubica
William Booth-Clibborn
William Booth-Clibborn
Raúl Peralta Lozada
Raúl Peralta Lozada
Roy Hvaara
Roy Hvaara
Vishal Jain
Vishal Jain
spaette
spaette
Yi-Xin Liu
Yi-Xin Liu
spinnau
spinnau
Xianda Sun
Xianda Sun
Christopher Rackauckas
Christopher Rackauckas

🐛 💻 🔌 💡 🚇 📣 👀 🔬 🔧 ⚠️ 📓
Patrick Kidger
Patrick Kidger

🐛 💻 📖 🔌 💡 🚧 📣 👀 🔬 🔧 ⚠️ 📓
Okon Samuel
Okon Samuel

🐛 💻 📖 🚧 💡 🚇 👀 ⚠️ 📓
William Booth-Clibborn
William Booth-Clibborn

💻 🌍 📖 📓 🚧 👀 🔧 ⚠️
Pablo Lemos
Pablo Lemos

🐛 💡 📣 👀 🔬 📓
Jerry Ling
Jerry Ling

🐛 💻 📖 🌍 💡 📣 👀 📓
Charles Fox
Charles Fox

🐛 💻 💡 🚧 📣 👀 🔬 📓
Johann Brehmer
Johann Brehmer

💻 📖 💡 📣 👀 🔬 ⚠️ 📓
Jay Wadekar
Jay Wadekar
Anthony Blaom, PhD
Anthony Blaom, PhD
Jgmedina95
Jgmedina95
Michael Abbott
Michael Abbott
Oscar Smith
Oscar Smith
Eric Hanson
Eric Hanson
Henrique Becker
Henrique Becker
qwertyjl
qwertyjl
Marius Millea
Marius Millea

💻 💡 📣 👀 📓
Coba
Coba

🐛 💻 💡 👀 📓
foxtran
foxtran

💻 💡 🚧 🔧 📓
Shah Mahdi Hasan
Shah Mahdi Hasan

🐛 💻 👀 📓
Pietro Monticone
Pietro Monticone

🐛 📖 💡
Mateusz Kubica
Mateusz Kubica

📖 💡
Jay Wadekar
Jay Wadekar

🐛 💡 📣 🔬
Anthony Blaom, PhD
Anthony Blaom, PhD

🚇 💡 👀
Rik Huijzer
Rik Huijzer
Hongyu Wang
Hongyu Wang
Zehao Jin
Zehao Jin
Tanner Mengel
Tanner Mengel
Arthur Grundner
Arthur Grundner
sjwetzel
sjwetzel
Saurav Maheshkar
Saurav Maheshkar
chris-soelistyo
chris-soelistyo
Jgmedina95
Jgmedina95

🐛 💡 👀
Michael Abbott
Michael Abbott

💻 💡 👀 🔧
Oscar Smith
Oscar Smith

💻 💡
Eric Hanson
Eric Hanson

💡 📣 📓
Henrique Becker
Henrique Becker

💻 💡 👀
qwertyjl
qwertyjl

🐛 📖 💡 📓
Rik Huijzer
Rik Huijzer

💡 🚇
Hongyu Wang
Hongyu Wang

💡 📣 🔬
Ilya Orson
Ilya Orson
Ho Fung Tsoi
Ho Fung Tsoi
LionessOfCintra
LionessOfCintra
Manuel Morales
Manuel Morales
Paulo Montero Camacho
Paulo Montero Camacho
Writu Dasgupta
Writu Dasgupta
Anubhav Kamal
Anubhav Kamal
anthony-sun
anthony-sun
Zehao Jin
Zehao Jin

🔬 📣
Tanner Mengel
Tanner Mengel

🔬 📣
Arthur Grundner
Arthur Grundner

🔬 📣
sjwetzel
sjwetzel

🔬 📣 📓
Saurav Maheshkar
Saurav Maheshkar

🔧
Hao Guo
Hao Guo
Trailblazer
Trailblazer
Christos Pliakos
Christos Pliakos
Sousa Neto
Sousa Neto
Leonardo Voltolini
Leonardo Voltolini
Daniel Eduardo Conde Villatoro
Daniel Eduardo Conde Villatoro
Sam Beckers
Sam Beckers
diff --git a/docs/all_contributors/.all-contributorsrc b/docs/all_contributors/.all-contributorsrc index ed64c0639..c3ef9dbdd 100644 --- a/docs/all_contributors/.all-contributorsrc +++ b/docs/all_contributors/.all-contributorsrc @@ -11,6 +11,30 @@ "commit": false, "commitConvention": "angular", "contributors": [ + { + "login": "adil-soubki", + "name": "Adil", + "avatar_url": "https://avatars.githubusercontent.com/u/5231841?v=4", + "profile": "https://github.com/adil-soubki", + "contributions": [ + "code", + "plugin", + "ideas" + ] + }, + { + "login": "cjdoris", + "name": "Christopher Rowley", + "avatar_url": "https://avatars.githubusercontent.com/u/1844215?v=4", + "profile": "https://cjdoris.github.io/", + "contributions": [ + "code", + "ideas", + "infra", + "platform", + "review" + ] + }, { "login": "mkitti", "name": "Mark Kittisopikul", @@ -108,19 +132,6 @@ "test" ] }, - { - "login": "cjdoris", - "name": "Christopher Rowley", - "avatar_url": "https://avatars.githubusercontent.com/u/1844215?v=4", - "profile": "https://cjdoris.github.io/", - "contributions": [ - "code", - "ideas", - "infra", - "platform", - "review" - ] - }, { "login": "kazewong", "name": "Kaze Wong", @@ -210,6 +221,84 @@ "test" ] }, + { + "login": "ayagh19", + "name": "Aya Ghaleb", + "avatar_url": "https://avatars.githubusercontent.com/u/124587945?v=4", + "profile": "https://github.com/ayagh19", + "contributions": [ + "code" + ] + }, + { + "login": "gca30", + "name": "gca30", + "avatar_url": "https://avatars.githubusercontent.com/u/124273598?v=4", + "profile": "https://github.com/gca30", + "contributions": [ + "code" + ] + }, + { + "login": "nmheim", + "name": "Niklas Heim", + "avatar_url": "https://avatars.githubusercontent.com/u/29552345?v=4", + "profile": "https://github.com/nmheim", + "contributions": [ + "code", + "test", + "doc" + ] + }, + { + "login": "atharvas", + "name": "Atharva Sehgal", + "avatar_url": "https://avatars.githubusercontent.com/u/20322919?v=4", + "profile": "https://github.com/atharvas", + "contributions": [ + "code" + ] + }, + { + "login": "wkharold", + "name": "wkharold", + "avatar_url": "https://avatars.githubusercontent.com/u/103685?v=4", + "profile": "https://github.com/wkharold", + "contributions": [ + "infra", + "code", + "test" + ] + }, + { + "login": "wsmoses", + "name": "William Moses", + "avatar_url": "https://avatars.githubusercontent.com/u/1260124?v=4", + "profile": "https://wsmoses.com", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "grezde", + "name": "Ardeleanu Cristian", + "avatar_url": "https://avatars.githubusercontent.com/u/43924925?v=4", + "profile": "https://grezde.github.io", + "contributions": [ + "code", + "test" + ] + }, + { + "login": "gm89uk", + "name": "gm89uk", + "avatar_url": "https://avatars.githubusercontent.com/u/127948719?v=4", + "profile": "https://github.com/gm89uk", + "contributions": [ + "question" + ] + }, { "login": "Pablo-Lemos", "name": "Pablo Lemos", @@ -323,6 +412,94 @@ "userTesting" ] }, + { + "login": "DilumAluthge", + "name": "Dilum Aluthge", + "avatar_url": "https://avatars.githubusercontent.com/u/5619885?v=4", + "profile": "https://aluthge.com", + "contributions": [ + "code" + ] + }, + { + "login": "SebastianM-C", + "name": "Sebastian Micluța-Câmpeanu", + "avatar_url": "https://avatars.githubusercontent.com/u/31181429?v=4", + "profile": "https://github.com/SebastianM-C", + "contributions": [ + "code", + "maintenance" + ] + }, + { + "login": "timholy", + "name": "Tim Holy", + "avatar_url": "https://avatars.githubusercontent.com/u/1525481?v=4", + "profile": "https://neuroscience.wustl.edu/people/timothy-holy-phd/", + "contributions": [ + "code", + "maintenance" + ] + }, + { + "login": "BrotherHa", + "name": "BrotherHa", + "avatar_url": "https://avatars.githubusercontent.com/u/190199534?v=4", + "profile": "https://github.com/BrotherHa", + "contributions": [ + "bug", + "code" + ] + }, + { + "login": "sefffal", + "name": "William Thompson", + "avatar_url": "https://avatars.githubusercontent.com/u/7330605?v=4", + "profile": "https://wthompson.space", + "contributions": [ + "infra" + ] + }, + { + "login": "tomjelen", + "name": "Tom Jelen", + "avatar_url": "https://avatars.githubusercontent.com/u/2547785?v=4", + "profile": "https://abzu.ai", + "contributions": [ + "bug", + "code" + ] + }, + { + "login": "romanovzky", + "name": "Miguel Crispim Romao", + "avatar_url": "https://avatars.githubusercontent.com/u/7794475?v=4", + "profile": "https://www.miguelromao.me/", + "contributions": [ + "bug", + "code" + ] + }, + { + "login": "adienes", + "name": "Andy Dienes", + "avatar_url": "https://avatars.githubusercontent.com/u/51664769?v=4", + "profile": "https://github.com/adienes", + "contributions": [ + "bug", + "code" + ] + }, + { + "login": "singhharsh1708", + "name": "Harsh Singh ", + "avatar_url": "https://avatars.githubusercontent.com/u/143034341?v=4", + "profile": "https://singhharsh.in", + "contributions": [ + "bug", + "code" + ] + }, { "login": "pitmonticone", "name": "Pietro Monticone", @@ -344,6 +521,79 @@ "ideas" ] }, + { + "login": "WilliamBC-SL", + "name": "William Booth-Clibborn", + "avatar_url": "https://avatars.githubusercontent.com/u/118170949?v=4", + "profile": "https://github.com/WilliamBC-SL", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "RaulPL", + "name": "Raúl Peralta Lozada", + "avatar_url": "https://avatars.githubusercontent.com/u/3116652?v=4", + "profile": "https://raulpl.github.io/about", + "contributions": [ + "code" + ] + }, + { + "login": "hvaara", + "name": "Roy Hvaara", + "avatar_url": "https://avatars.githubusercontent.com/u/1535968?v=4", + "profile": "https://www.linkedin.com/in/hvaara/", + "contributions": [ + "doc" + ] + }, + { + "login": "VishalJ99", + "name": "Vishal Jain", + "avatar_url": "https://avatars.githubusercontent.com/u/51826812?v=4", + "profile": "https://github.com/VishalJ99", + "contributions": [ + "doc" + ] + }, + { + "login": "spaette", + "name": "spaette", + "avatar_url": "https://avatars.githubusercontent.com/u/111918424?v=4", + "profile": "https://github.com/spaette", + "contributions": [ + "doc" + ] + }, + { + "login": "liuyxpp", + "name": "Yi-Xin Liu", + "avatar_url": "https://avatars.githubusercontent.com/u/1089344?v=4", + "profile": "http://www.yxliu.group", + "contributions": [ + "doc" + ] + }, + { + "login": "spinnau", + "name": "spinnau", + "avatar_url": "https://avatars.githubusercontent.com/u/2995937?v=4", + "profile": "https://github.com/spinnau", + "contributions": [ + "doc" + ] + }, + { + "login": "sunxd3", + "name": "Xianda Sun", + "avatar_url": "https://avatars.githubusercontent.com/u/5433119?v=4", + "profile": "https://github.com/sunxd3", + "contributions": [ + "doc" + ] + }, { "login": "JayWadekar", "name": "Jay Wadekar", @@ -504,6 +754,166 @@ "contributions": [ "tool" ] + }, + { + "login": "chris-soelistyo", + "name": "chris-soelistyo", + "avatar_url": "https://avatars.githubusercontent.com/u/68875981?v=4", + "profile": "https://github.com/chris-soelistyo", + "contributions": [ + "research", + "promotion" + ] + }, + { + "login": "IlyaOrson", + "name": "Ilya Orson ", + "avatar_url": "https://avatars.githubusercontent.com/u/12092488?v=4", + "profile": "https://ilyaorson.gitlab.io", + "contributions": [ + "research", + "promotion" + ] + }, + { + "login": "hftsoi", + "name": "Ho Fung Tsoi", + "avatar_url": "https://avatars.githubusercontent.com/u/51976330?v=4", + "profile": "https://hftsoi.github.io", + "contributions": [ + "research", + "promotion" + ] + }, + { + "login": "LionessOfCintra", + "name": "LionessOfCintra", + "avatar_url": "https://avatars.githubusercontent.com/u/92221853?v=4", + "profile": "https://github.com/LionessOfCintra", + "contributions": [ + "research", + "promotion" + ] + }, + { + "login": "manuel-morales-a", + "name": "Manuel Morales ", + "avatar_url": "https://avatars.githubusercontent.com/u/64017590?v=4", + "profile": "https://github.com/manuel-morales-a", + "contributions": [ + "research", + "promotion" + ] + }, + { + "login": "paulomontero", + "name": "Paulo Montero Camacho", + "avatar_url": "https://avatars.githubusercontent.com/u/23636178?v=4", + "profile": "https://paulomontero.github.io", + "contributions": [ + "research", + "promotion" + ] + }, + { + "login": "luna026", + "name": "Writu Dasgupta", + "avatar_url": "https://avatars.githubusercontent.com/u/88938665?v=4", + "profile": "https://github.com/luna026", + "contributions": [ + "research", + "promotion" + ] + }, + { + "login": "anubhavkamal", + "name": "Anubhav Kamal", + "avatar_url": "https://avatars.githubusercontent.com/u/23038512?v=4", + "profile": "https://github.com/anubhavkamal", + "contributions": [ + "research", + "promotion" + ] + }, + { + "login": "anthony-sun", + "name": "anthony-sun", + "avatar_url": "https://avatars.githubusercontent.com/u/115842064?v=4", + "profile": "https://github.com/anthony-sun", + "contributions": [ + "research", + "promotion" + ] + }, + { + "login": "Nithouson", + "name": "Hao Guo", + "avatar_url": "https://avatars.githubusercontent.com/u/26868834?v=4", + "profile": "https://nithouson.github.io", + "contributions": [ + "research", + "promotion" + ] + }, + { + "login": "TrailblazerH", + "name": "Trailblazer", + "avatar_url": "https://avatars.githubusercontent.com/u/177746076?v=4", + "profile": "https://github.com/TrailblazerH", + "contributions": [ + "research", + "promotion" + ] + }, + { + "login": "christospliakos", + "name": "Christos Pliakos", + "avatar_url": "https://avatars.githubusercontent.com/u/64842094?v=4", + "profile": "https://github.com/christospliakos", + "contributions": [ + "research", + "promotion" + ] + }, + { + "login": "zouzaxd", + "name": "Sousa Neto", + "avatar_url": "https://avatars.githubusercontent.com/u/103605983?v=4", + "profile": "https://github.com/zouzaxd", + "contributions": [ + "research", + "promotion" + ] + }, + { + "login": "LeoVoltolini", + "name": "Leonardo Voltolini", + "avatar_url": "https://avatars.githubusercontent.com/u/94749527?v=4", + "profile": "https://github.com/LeoVoltolini", + "contributions": [ + "research", + "promotion" + ] + }, + { + "login": "con13375", + "name": "Daniel Eduardo Conde Villatoro", + "avatar_url": "https://avatars.githubusercontent.com/u/19805622?v=4", + "profile": "https://github.com/con13375", + "contributions": [ + "research", + "promotion" + ] + }, + { + "login": "sambeckers", + "name": "Sam Beckers", + "avatar_url": "https://avatars.githubusercontent.com/u/127021792?v=4", + "profile": "https://github.com/sambeckers", + "contributions": [ + "research", + "promotion" + ] } ], "contributorsPerLine": 8, @@ -517,5 +927,6 @@ "description": "Examples.", "symbol": "🌍" } - } + }, + "contributorTemplate": "\">?s=<%= options.imageSize %>\" width=\"<%= options.imageSize %>px;\" alt=\"<%= contributor.name %>\"/>
<%= contributor.name %>
" } diff --git a/docs/all_contributors/.gitignore b/docs/all_contributors/.gitignore index 3c3629e64..22fbdbe40 100644 --- a/docs/all_contributors/.gitignore +++ b/docs/all_contributors/.gitignore @@ -1 +1,3 @@ node_modules +yarn.lock +package-lock.json diff --git a/docs/all_contributors/run_all_contrib.sh b/docs/all_contributors/run_all_contrib.sh index 562efda08..278e30ed0 100755 --- a/docs/all_contributors/run_all_contrib.sh +++ b/docs/all_contributors/run_all_contrib.sh @@ -1,4 +1,4 @@ #!/bin/bash -yarn install --frozen-lockfile +yarn install yarn all-contributors $@ diff --git a/docs/all_contributors/yarn.lock b/docs/all_contributors/yarn.lock deleted file mode 100644 index 5a7097e39..000000000 --- a/docs/all_contributors/yarn.lock +++ /dev/null @@ -1,427 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@babel/runtime@^7.18.9", "@babel/runtime@^7.7.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438" - integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ== - dependencies: - regenerator-runtime "^0.13.11" - -all-contributors-cli@6.25.1: - version "6.25.1" - resolved "https://registry.yarnpkg.com/all-contributors-cli/-/all-contributors-cli-6.25.1.tgz#d6fbd4e7264939dc8efe897a9ccddc5329e46235" - integrity sha512-Q9MfsO6FH09h71IG6OobfESA7lbooB3/bnO2wnuXbLPye1lFsYsa/jpNZP0YBx6zbhwXqqm6CXRZ9HO/tmD4NQ== - dependencies: - "@babel/runtime" "^7.7.6" - async "^3.1.0" - chalk "^4.0.0" - didyoumean "^1.2.1" - inquirer "^7.3.3" - json-fixer "^1.6.8" - lodash "^4.11.2" - node-fetch "^2.6.0" - pify "^5.0.0" - yargs "^15.0.1" - -ansi-escapes@^4.2.1: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -async@^3.1.0: - version "3.2.4" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" - integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== - -camelcase@^5.0.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chardet@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" - integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== - -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - -cli-width@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" - integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== - -cliui@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" - integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^6.2.0" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== - -didyoumean@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" - integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -external-editor@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" - integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== - dependencies: - chardet "^0.7.0" - iconv-lite "^0.4.24" - tmp "^0.0.33" - -figures@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" - integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== - dependencies: - escape-string-regexp "^1.0.5" - -find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -get-caller-file@^2.0.1: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -iconv-lite@^0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -inquirer@^7.3.3: - version "7.3.3" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" - integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== - dependencies: - ansi-escapes "^4.2.1" - chalk "^4.1.0" - cli-cursor "^3.1.0" - cli-width "^3.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.19" - mute-stream "0.0.8" - run-async "^2.4.0" - rxjs "^6.6.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - through "^2.3.6" - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -json-fixer@^1.6.8: - version "1.6.15" - resolved "https://registry.yarnpkg.com/json-fixer/-/json-fixer-1.6.15.tgz#f1f03b6771fcb383695d458c53e50b10999fba7f" - integrity sha512-TuDuZ5KrgyjoCIppdPXBMqiGfota55+odM+j2cQ5rt/XKyKmqGB3Whz1F8SN8+60yYGy/Nu5lbRZ+rx8kBIvBw== - dependencies: - "@babel/runtime" "^7.18.9" - chalk "^4.1.2" - pegjs "^0.10.0" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -lodash@^4.11.2, lodash@^4.17.19: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -mute-stream@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" - integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== - -node-fetch@^2.6.0: - version "2.6.12" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.12.tgz#02eb8e22074018e3d5a83016649d04df0e348fba" - integrity sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g== - dependencies: - whatwg-url "^5.0.0" - -onetime@^5.1.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -pegjs@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/pegjs/-/pegjs-0.10.0.tgz#cf8bafae6eddff4b5a7efb185269eaaf4610ddbd" - integrity sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow== - -pify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" - integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA== - -regenerator-runtime@^0.13.11: - version "0.13.11" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" - integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== - -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - -run-async@^2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" - integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== - -rxjs@^6.6.0: - version "6.6.7" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" - integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== - dependencies: - tslib "^1.9.0" - -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== - -signal-exit@^3.0.2: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -string-width@^4.1.0, string-width@^4.2.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -through@^2.3.6: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== - -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - -tslib@^1.9.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - -which-module@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" - integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== - -wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -y18n@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" - integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== - -yargs-parser@^18.1.2: - version "18.1.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" - integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs@^15.0.1: - version "15.4.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" - integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== - dependencies: - cliui "^6.0.0" - decamelize "^1.2.0" - find-up "^4.1.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^4.2.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^18.1.2" diff --git a/docs/deploy.jl b/docs/deploy.jl index bb051270c..d6738f1e3 100644 --- a/docs/deploy.jl +++ b/docs/deploy.jl @@ -1,28 +1,56 @@ #!/usr/bin/env julia # -# Deploy built VitePress documentation using DocumenterVitepress.jl -# This script is called after VitePress build is complete and handles: -# - Regular deployments to gh-pages -# - PR preview deployments to gh-pages/previews/PR##/ -# - Dual deployment to secondary repository (ai.damtp.cam.ac.uk) -# +# Build and deploy VitePress documentation to versioned gh-pages directories. +# PR previews deploy to gh-pages/previews/PR##/. using DocumenterVitepress -# Get deployment target from environment (for dual deployment) -deployment_target = get(ENV, "DEPLOYMENT_TARGET", "primary") - println("Starting DocumenterVitepress deployment...") -println("Deployment target: $deployment_target") println("Event: $(get(ENV, "GITHUB_EVENT_NAME", "unknown"))") println("Ref: $(get(ENV, "GITHUB_REF", "unknown"))") # Get deployment decision from Documenter to determine correct subfolder using Documenter +include("deploy_versions.jl") + +struct StableRedirectVersion + base_version::DocumenterVitepress.BaseVersion + root_stubs::Dict{String,String} +end + +function Documenter.determine_deploy_subfolder(deploy_decision, ::StableRedirectVersion) + return nothing +end + +function Documenter.postprocess_before_push( + versions::StableRedirectVersion; + subfolder, + devurl, + deploy_dir, + dirname, +) + Documenter.postprocess_before_push( + versions.base_version; subfolder, devurl, deploy_dir, dirname + ) + root = stable_deploy_root(deploy_dir) + for (path, content) in versions.root_stubs + destination = joinpath(root, path) + mkpath(Base.dirname(destination)) + write(destination, content) + end + for path in stale_root_stubs(root, keys(versions.root_stubs)) + println("Removing stale redirect $path") + rm(joinpath(root, path)) + directory = joinpath(root, Base.dirname(path)) + while directory != root && isempty(readdir(directory)) + rm(directory) + directory = Base.dirname(directory) + end + end + return +end -# Custom DeployConfig that bypasses PR origin check for cross-repo deployments -# This allows deploying PR previews to ai.damtp.cam.ac.uk/pysr even though -# PRs exist in astroautomata/PySR (Documenter's security check would normally block this) +# Custom DeployConfig that bypasses PR origin checks for cross-repo previews. struct BypassPRCheckConfig <: Documenter.DeployConfig end function Documenter.deploy_folder( @@ -102,161 +130,89 @@ end Documenter.authentication_method(::BypassPRCheckConfig) = Documenter.SSH Documenter.documenter_key(::BypassPRCheckConfig) = ENV["DOCUMENTER_KEY"] -# Configure deployment based on target -if deployment_target == "secondary" - # Secondary: Use custom config to bypass PR origin check - deploy_config = BypassPRCheckConfig() - damtp_key = get(ENV, "DAMTP_DEPLOY_KEY", "") - if isempty(damtp_key) - error("DAMTP_DEPLOY_KEY environment variable is required for secondary deployment but is not set") - end - ENV["DOCUMENTER_KEY"] = damtp_key +deploy_config = BypassPRCheckConfig() +damtp_key = get(ENV, "DAMTP_DEPLOY_KEY", "") +isempty(damtp_key) && error("DAMTP_DEPLOY_KEY environment variable is required for deployment but is not set") +ENV["DOCUMENTER_KEY"] = damtp_key + +deploy_decision = Documenter.deploy_folder( + deploy_config; + repo="github.com/ai-damtp-cam-ac-uk/pysr", + devbranch="master", + devurl="dev", + push_preview=true, +) - deploy_decision = Documenter.deploy_folder( - deploy_config; - repo="github.com/ai-damtp-cam-ac-uk/pysr", - devbranch="master", - devurl="dev", - push_preview=true, - ) -else - # Primary: Use normal Documenter flow with security checks - deploy_config = Documenter.auto_detect_deploy_system() +println("Deploy decision: all_ok=$(deploy_decision.all_ok), is_preview=$(deploy_decision.is_preview), subfolder=$(deploy_decision.subfolder)") - deploy_decision = Documenter.deploy_folder( - deploy_config; - repo="github.com/astroautomata/PySR", - devbranch="master", - devurl="dev", - push_preview=true, - ) +if !deploy_decision.all_ok || isempty(deploy_decision.subfolder) + println("Deployment skipped because no deployable subfolder was selected") + exit(0) end -println("Deploy decision: all_ok=$(deploy_decision.all_ok), is_preview=$(deploy_decision.is_preview), subfolder=$(deploy_decision.subfolder)") - -# Build VitePress with the correct base path for this deployment -# VitePress needs the base path set at build time (it's hardcoded into assets) -# Primary uses /PySR/ (capital P), secondary uses /pysr/ (lowercase p) -base_prefix = deployment_target == "secondary" ? "/pysr/" : "/PySR/" -full_base = "$(base_prefix)$(deploy_decision.subfolder)$(isempty(deploy_decision.subfolder) ? "" : "/")" +subfolder = deploy_decision.subfolder -# The version picker needs __DEPLOY_ABSPATH__ to construct URLs to sibling versions -# This should be the shared prefix (e.g., /pysr/ or /PySR/) -deploy_abspath = base_prefix +base_prefix = "/" +repo_url = "github.com/ai-damtp-cam-ac-uk/pysr.git" -println("Building VitePress with base: $full_base (deploy abspath: $deploy_abspath)") +# VitePress bakes the base path into every asset URL at build time +full_base = "$(base_prefix)$(subfolder)/" +println("Building VitePress with base: $full_base (deploy abspath: $base_prefix)") config_path = joinpath(@__DIR__, "src", ".vitepress", "config.mts") original_config = read(config_path, String) -# Match either /pysr/ or /PySR/ in the config -modified_config = replace(original_config, r"base:\s*'/[Pp]y[Ss][Rr]/'" => "base: '$full_base'") -# Also update __DEPLOY_ABSPATH__ so version picker works correctly -modified_config = replace( - modified_config, - r"__DEPLOY_ABSPATH__\s*:\s*JSON\.stringify\(getBaseRepository\([^)]+\)\)" => - "__DEPLOY_ABSPATH__: JSON.stringify('$deploy_abspath')", -) -# Primary deployment should point to Cambridge as canonical -# Secondary (Cambridge) should have empty canonical (it's already the canonical site) -canonical_domain = deployment_target == "primary" ? "https://ai.damtp.cam.ac.uk/pysr/" : "" -modified_config = replace( - modified_config, - r"const canonicalDomain = '';" => - "const canonicalDomain = '$canonical_domain';", -) +modified_config = replace(original_config, r"base:\s*'/'" => "base: '$full_base'") write(config_path, modified_config) try - # Build VitePress (outputs to docs/dist/) cd(@__DIR__) do run(`npm run build:vitepress`) end println("VitePress build complete") finally - # Restore original config (don't commit the modified version) write(config_path, original_config) println("Restored original config.mts") end -# DocumenterVitepress expects files in dist/1/ (versioned subdirectory) -# But VitePress builds directly to dist/, so we need to restructure -dist_root = joinpath(@__DIR__, "dist") -dist_versioned = joinpath(dist_root, "1") - -if !isdir(dist_versioned) && isdir(dist_root) - println("Restructuring dist/ for DocumenterVitepress...") - # Move all files from dist/ to dist/1/ - temp_dir = joinpath(@__DIR__, "dist_temp") - mv(dist_root, temp_dir) - mkpath(dist_root) - mv(temp_dir, dist_versioned) -end - -# Create bases.txt with the correct subfolder from deploy_decision -# This tells DocumenterVitepress where to deploy (e.g., "dev", "previews/PR1056", "v1.2.3") -# Don't overwrite if it already exists with content (DocumenterVitepress may generate multi-base configs) -bases_file = joinpath(dist_root, "bases.txt") -if !isfile(bases_file) - println("Creating bases.txt with subfolder: $(deploy_decision.subfolder)") - write(bases_file, "$(deploy_decision.subfolder)\n") -else - bases = filter(!isempty, readlines(bases_file)) - if isempty(bases) - println("Fixing empty bases.txt with subfolder: $(deploy_decision.subfolder)") - write(bases_file, "$(deploy_decision.subfolder)\n") - else - println("bases.txt already exists with $(length(bases)) bases: $bases") - # Don't overwrite it - DocumenterVitepress may have generated multiple bases - end -end +# The version picker resolves each folder's version through this file +write( + joinpath(@__DIR__, "dist", "siteinfo.js"), + "var DOCUMENTER_CURRENT_VERSION = $(repr(subfolder));\n", +) -# Create redirect index.html at root to redirect to dev -# Only do this when deploying to dev (not for version tags) -# Once there are tagged versions, DocumenterVitepress will handle this automatically -if deploy_decision.subfolder == "dev" - redirect_html = """ - -""" - redirect_file = joinpath(dist_root, "index.html") - println("Creating redirect index.html to ./dev/") - write(redirect_file, redirect_html) +deploy(folder, target; versions = DocumenterVitepress.BaseVersion(folder)) = + Documenter.deploydocs(; + root = @__DIR__, + repo = repo_url, + deploy_config = deploy_config, + push_preview = true, + devbranch = "master", + devurl = "dev", + target = target, + dirname = folder, + versions = versions, + ) - # Create siteinfo.js for dev version - # This file is required by the version picker to show all available versions - siteinfo_dir = joinpath(dist_versioned, "dev") - if isdir(siteinfo_dir) - siteinfo_content = """var DOCUMENTER_CURRENT_VERSION = "dev"; -""" - siteinfo_file = joinpath(siteinfo_dir, "siteinfo.js") - println("Creating siteinfo.js for dev version") - write(siteinfo_file, siteinfo_content) - else - println("Warning: dev directory not found at $(siteinfo_dir), skipping siteinfo.js creation") +deploy(subfolder, "dist") + +if claims_stable(subfolder) + println("Pointing stable at $subfolder") + dist_dir = joinpath(@__DIR__, "dist") + redirect_files = stable_redirect_files(built_pages(dist_dir), subfolder) + stable_dir = joinpath(@__DIR__, "dist_stable") + mkpath(stable_dir) + root_stubs = Dict{String,String}() + for (path, content) in redirect_files + if startswith(path, "stable/") + destination = joinpath(stable_dir, path[(length("stable/") + 1):end]) + mkpath(Base.dirname(destination)) + write(destination, content) + else + root_stubs[path] = content + end end -end - -if deployment_target == "secondary" - # Secondary deployment to ai.damtp.cam.ac.uk - println("Deploying to secondary repository (ai.damtp.cam.ac.uk)") - DocumenterVitepress.deploydocs(; - root=@__DIR__, - repo="github.com/ai-damtp-cam-ac-uk/pysr.git", - deploy_config=deploy_config, # Use custom config with bypassed PR check - push_preview=true, - target="dist", - devbranch="master", - ) -else - # Primary deployment to astroautomata/PySR - println("Deploying to primary repository (astroautomata/PySR)") - DocumenterVitepress.deploydocs(; - root=@__DIR__, - repo="github.com/astroautomata/PySR.git", - deploy_config=deploy_config, # Use normal GitHubActions config - push_preview=true, - target="dist", - devbranch="master", - ) + versions = StableRedirectVersion(DocumenterVitepress.BaseVersion("stable"), root_stubs) + deploy("stable", "dist_stable"; versions) end println("Deployment complete!") diff --git a/docs/deploy_versions.jl b/docs/deploy_versions.jl new file mode 100644 index 000000000..53379019d --- /dev/null +++ b/docs/deploy_versions.jl @@ -0,0 +1,80 @@ +using DocumenterVitepress + +# Whether this tag is the newest release, and so owns the `stable` alias. +# +# Julia parses Python-style prerelease tags such as v2.0.0a2 as v2.0.0-a2, and +# determine_bases then reports the alpha as owning the v2.0.0 release folder, so only +# plain release tags reach it. +function claims_stable(subfolder; kwargs...) + occursin(r"^v\d+(\.\d+){0,2}$", subfolder) || return false + return "stable" in DocumenterVitepress.determine_bases(subfolder; keep = :patch, kwargs...) +end + +const STUB_MARKER = "" + +stable_deploy_root(deploy_dir) = dirname(rstrip(deploy_dir, '/')) + +# Directories at the root of gh-pages that a `stable` deployment must never rewrite. +const PRESERVED_ROOT_DIRS = (".git", "previews", "stable") + +redirect_stub(target) = """$(STUB_MARKER) + +""" + +# Page paths are URL-relative and may be nested, so every stub has to climb back to the +# root before naming its target. +climb(depth) = repeat("../", depth) +depth_of(path) = count(==('/'), path) + +function add_stub!(files, path, target) + haskey(files, path) && error("Conflicting redirect stubs for $path") + files[path] = redirect_stub(target) + return files +end + +function stable_redirect_files(page_paths, stable_version) + pages = sort(filter(page -> page != "index" && basename(page) != "404", page_paths)) + files = Dict("stable/index.html" => redirect_stub("../$(stable_version)/")) + for page in pages + depth = depth_of(page) + add_stub!( + files, + "stable/$(page).html", + "$(climb(depth + 1))$(stable_version)/$(page).html", + ) + add_stub!(files, "$(page).html", "$(climb(depth))stable/$(page).html") + # A page that is already a directory index has no separate directory form. + basename(page) == "index" && continue + add_stub!(files, "$(page)/index.html", "$(climb(depth + 1))stable/$(page).html") + end + return files +end + +# Every `.html` file below `dist`, named the way its URL is, so nested pages get stubs. +function built_pages(dist_dir) + pages = String[] + for (directory, _, files) in walkdir(dist_dir), file in files + endswith(file, ".html") || continue + push!(pages, splitext(relpath(joinpath(directory, file), dist_dir))[1]) + end + return sort(pages) +end + +# Stubs left at the root by earlier deployments for pages that no longer exist. Only +# files carrying the marker are candidates, so version folders and hand-written pages are +# never touched, and `stable` is skipped because its deployment replaces it wholesale. +function stale_root_stubs(root, current_paths) + stale = String[] + for (directory, directories, files) in walkdir(root) + relative = relpath(directory, root) + relative == "." && filter!(!in(PRESERVED_ROOT_DIRS), directories) + for file in files + endswith(file, ".html") || continue + path = relative == "." ? file : joinpath(relative, file) + path in current_paths && continue + startswith(read(joinpath(root, path), String), STUB_MARKER) && + push!(stale, path) + end + end + return sort(stale) +end diff --git a/docs/gen_api.py b/docs/gen_api.py index 0e9c5a2db..edaa2c041 100644 --- a/docs/gen_api.py +++ b/docs/gen_api.py @@ -345,50 +345,12 @@ def process_markdown_file(source_path: Path, output_path: Path) -> None: print(f"Generated {output_path} from {source_path}") -def process_readme_to_index(readme_path: Path, output_path: Path) -> None: - """Convert README.md to index.md with VitePress syntax.""" - content = readme_path.read_text() - - # Add the video splash at the top - video_splash = '
\n' - content = video_splash + content - - # Convert
blocks to VitePress ::: details syntax - # Pattern matches:
\n\n### Title\n\n\ncontent\n
- def replace_details(match): - summary = match.group(1).strip() - body = match.group(2).strip() - # Extract title from summary (remove ### prefix if present) - title = summary.replace("###", "").strip() - return f"::: details {title}\n\n{body}\n\n:::" - - # Match
......
blocks - details_pattern = r"
\s*\s*(.*?)\s*\s*(.*?)\s*
" - content = re.sub(details_pattern, replace_details, content, flags=re.DOTALL) - - output_path.write_text(content) - print(f"Generated {output_path} from {readme_path}") - - def main(): """Main entry point.""" # Get the directory containing this script script_dir = Path(__file__).parent.resolve() src_dir = script_dir / "src" - # Generate index.md from README.md - readme_path = script_dir.parent / "README.md" - index_path = src_dir / "index.md" - if readme_path.exists(): - try: - process_readme_to_index(readme_path, index_path) - except Exception as e: - print(f"Error generating index.md: {e}", file=sys.stderr) - import traceback - - traceback.print_exc() - sys.exit(1) - # Process _api.md → api.md and _api-advanced.md → api-advanced.md for source_name, output_name in [ ("_api.md", "api.md"), diff --git a/docs/gen_vitepress_index.py b/docs/gen_vitepress_index.py index 3806d12b0..5ddbab121 100644 --- a/docs/gen_vitepress_index.py +++ b/docs/gen_vitepress_index.py @@ -3,7 +3,30 @@ Generate VitePress-compatible index.md by combining _index.md template with README.md content. """ +import re from pathlib import Path +from urllib.parse import urlsplit + +REPO_ROOT = Path(__file__).resolve().parent.parent +REPO_URL = "https://github.com/astroautomata/PySR" + + +def repo_link(match): + url = match.group(2) + parts = urlsplit(url) + if parts.scheme or parts.netloc or url.startswith(("/", "#")): + return match.group(0) + + path = (REPO_ROOT / parts.path).resolve() + if not path.is_relative_to(REPO_ROOT) or not path.exists(): + return match.group(0) + if path.is_relative_to(REPO_ROOT / "docs" / "src") and path.suffix == ".md": + return match.group(0) + + kind = "tree" if path.is_dir() else "blob" + suffix = f"?{parts.query}" if parts.query else "" + suffix += f"#{parts.fragment}" if parts.fragment else "" + return f"{match.group(1)}{REPO_URL}/{kind}/master/{path.relative_to(REPO_ROOT).as_posix()}{suffix})" def process_readme_content(readme_content): @@ -24,20 +47,13 @@ def process_readme_content(readme_content): if not found_first_header: continue - # Skip GitHub user-images URLs - if "user-images" in line: - continue - # Adjust header levels: # -> ## if line.startswith("# ") and not line.startswith("# PySR"): line = "#" + line - # Transform "### Test status" to "**Test status**" - if line == "### Test status": - line = "**Test status**" - # Fix internal links to use VitePress paths - line = line.replace("(https://ai.damtp.cam.ac.uk/pysr/papers)", "(/papers)") + line = line.replace("(https://pysr.ai/papers)", "(/papers)") + line = re.sub(r"(?=14.6" } @@ -1394,6 +1397,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "license": "MIT", "engines": { "node": ">=6" } @@ -1414,7 +1418,8 @@ "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" }, "node_modules/ccount": { "version": "2.0.1", @@ -1447,6 +1452,7 @@ "version": "1.0.0-rc.10", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", "integrity": "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==", + "license": "MIT", "dependencies": { "cheerio-select": "^1.5.0", "dom-serializer": "^1.3.2", @@ -1467,6 +1473,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.6.0.tgz", "integrity": "sha512-eq0GdBvxVFbqWgmCm7M3XGs1I8oLy/nExUnh6oLqmBditPO9AqQJrkslDpMun/hZ0yyTs8L0m85OHp4ho6Qm9g==", + "license": "BSD-2-Clause", "dependencies": { "css-select": "^4.3.0", "css-what": "^6.0.1", @@ -1491,6 +1498,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "license": "MIT", "engines": { "node": ">= 6" } @@ -1510,6 +1518,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.0.1", @@ -1525,6 +1534,7 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", "engines": { "node": ">= 6" }, @@ -1538,9 +1548,10 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/d3-format": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", "engines": { "node": ">=12" } @@ -1569,6 +1580,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "license": "MIT", "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^4.2.0", @@ -1582,6 +1594,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "license": "BSD-2-Clause", "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } @@ -1595,12 +1608,14 @@ "type": "github", "url": "https://github.com/sponsors/fb55" } - ] + ], + "license": "BSD-2-Clause" }, "node_modules/domhandler": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.2.0" }, @@ -1615,6 +1630,7 @@ "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^1.0.1", "domelementtype": "^2.2.0", @@ -1693,6 +1709,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-3.0.0.tgz", "integrity": "sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -1704,6 +1721,7 @@ "version": "3.2.25", "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -1799,6 +1817,7 @@ "url": "https://github.com/sponsors/fb55" } ], + "license": "MIT", "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^4.0.0", @@ -1810,6 +1829,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "license": "BSD-2-Clause", "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } @@ -1847,6 +1867,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/juice/-/juice-8.1.0.tgz", "integrity": "sha512-FLzurJrx5Iv1e7CfBSZH68dC04EEvXvvVvPYB7Vx1WAuhCp1ZPIMtqxc+WTWxVkpTIC2Ach/GAv0rQbtGf6YMA==", + "license": "MIT", "dependencies": { "cheerio": "1.0.0-rc.10", "commander": "^6.1.0", @@ -1887,9 +1908,20 @@ } }, "node_modules/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz", + "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", "dependencies": { "uc.micro": "^2.0.0" } @@ -1921,13 +1953,24 @@ "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==" }, "node_modules/markdown-it": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", - "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.0.tgz", + "integrity": "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", "dependencies": { "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.0", + "entities": "^4.5.0", + "linkify-it": "^5.0.2", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" @@ -1945,15 +1988,17 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/markdown-it-mathjax3/-/markdown-it-mathjax3-4.3.2.tgz", "integrity": "sha512-TX3GW5NjmupgFtMJGRauioMbbkGsOXAAt1DZ/rzzYmTHqzkO1rNAdiMD4NiruurToPApn2kYy76x02QN26qr2w==", + "license": "MIT", "dependencies": { "juice": "^8.0.0", "mathjax-full": "^3.2.0" } }, "node_modules/mathjax-full": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.2.tgz", - "integrity": "sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.1.tgz", + "integrity": "sha512-aUz9o16MGZdeiIBwZjAfUBTiJb7LRqzZEl1YOZ8zQMGYIyh1/nxRebxKxjDe9L+xcZCr2OHdzoFBMcd6VnLv9Q==", + "license": "Apache-2.0", "dependencies": { "esm": "^3.2.25", "mhchemparser": "^4.1.0", @@ -1989,12 +2034,14 @@ "node_modules/mensch": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/mensch/-/mensch-0.3.4.tgz", - "integrity": "sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g==" + "integrity": "sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g==", + "license": "MIT" }, "node_modules/mhchemparser": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.2.1.tgz", - "integrity": "sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ==" + "integrity": "sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ==", + "license": "Apache-2.0" }, "node_modules/micromark-util-character": { "version": "2.1.1", @@ -2105,7 +2152,8 @@ "node_modules/mj-context-menu": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz", - "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==" + "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==", + "license": "Apache-2.0" }, "node_modules/nanoid": { "version": "3.3.11", @@ -2144,6 +2192,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -2163,6 +2212,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0" }, @@ -2191,12 +2241,14 @@ "node_modules/parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "license": "MIT" }, "node_modules/parse5-htmlparser2-tree-adapter": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "license": "MIT", "dependencies": { "parse5": "^6.0.1" } @@ -2391,6 +2443,7 @@ "version": "1.12.2", "resolved": "https://registry.npmjs.org/slick/-/slick-1.12.2.tgz", "integrity": "sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==", + "license": "MIT (http://mootools.net/license.txt)", "engines": { "node": "*" } @@ -2430,11 +2483,12 @@ } }, "node_modules/speech-rule-engine": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.1.2.tgz", - "integrity": "sha512-S6ji+flMEga+1QU79NDbwZ8Ivf0S/MpupQQiIC0rTpU/ZTKgcajijJJb1OcByBQDjrXCN1/DJtGz4ZJeBMPGJw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.1.4.tgz", + "integrity": "sha512-i/VCLG1fvRc95pMHRqG4aQNscv+9aIsqA2oI7ZQS51sTdUcDHYX6cpT8/tqZ+enjs1tKVwbRBWgxut9SWn+f9g==", + "license": "Apache-2.0", "dependencies": { - "@xmldom/xmldom": "0.9.8", + "@xmldom/xmldom": "0.9.10", "commander": "13.1.0", "wicked-good-xpath": "1.3.0" }, @@ -2446,6 +2500,7 @@ "version": "13.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "license": "MIT", "engines": { "node": ">=18" } @@ -2507,7 +2562,8 @@ "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" }, "node_modules/trim-lines": { "version": "3.0.1", @@ -2526,7 +2582,8 @@ "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" }, "node_modules/unist-util-is": { "version": "6.0.0", @@ -2595,6 +2652,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/valid-data-url/-/valid-data-url-3.0.1.tgz", "integrity": "sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA==", + "license": "MIT", "engines": { "node": ">=10" } @@ -2724,12 +2782,17 @@ } }, "node_modules/vitepress-plugin-tabs": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/vitepress-plugin-tabs/-/vitepress-plugin-tabs-0.6.0.tgz", - "integrity": "sha512-35mQUbI6lcVrLj0PpwautXm8LiV6Gz5ZDlklVRf/0KDd6gGs8HaZztqOxdffnzCiKIFLURBx930xq28it9jQTg==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/vitepress-plugin-tabs/-/vitepress-plugin-tabs-0.9.0.tgz", + "integrity": "sha512-OB+/T6SkwGIQHIQi1NLl011kWCMzfsVEx0/q+NfF6EmvvpcfDzRClc/iEwUlq8TkQnFxANu+GTdmvtXEjJM2Lg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/sapphi-red" + }, "peerDependencies": { - "vitepress": "^1.0.0", - "vue": "^3.3.8" + "vitepress": "^1.0.0 || ^2.0.0-alpha.17", + "vue": "^3.5.0" } }, "node_modules/vue": { @@ -2756,6 +2819,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-6.0.1.tgz", "integrity": "sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A==", + "license": "MIT", "dependencies": { "ansi-colors": "^4.1.1", "escape-goat": "^3.0.0", @@ -2772,6 +2836,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz", "integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==", + "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.0.1" }, @@ -2786,6 +2851,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "license": "BSD-2-Clause", "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } @@ -2794,6 +2860,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-5.0.1.tgz", "integrity": "sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==", + "license": "MIT", "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^3.3.0", @@ -2808,6 +2875,7 @@ "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -2818,12 +2886,14 @@ "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -2832,7 +2902,8 @@ "node_modules/wicked-good-xpath": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz", - "integrity": "sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw==" + "integrity": "sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw==", + "license": "MIT" }, "node_modules/zwitch": { "version": "2.0.4", diff --git a/docs/package.json b/docs/package.json index dfec562ea..e92aaa1df 100644 --- a/docs/package.json +++ b/docs/package.json @@ -11,11 +11,11 @@ }, "dependencies": { "@nolebase/vitepress-plugin-enhanced-readabilities": "^2.14.0", - "d3-format": "^3.1.0", - "markdown-it": "^14.1.0", + "d3-format": "^3.1.2", + "markdown-it": "^14.2.0", "markdown-it-footnote": "^4.0.0", "markdown-it-mathjax3": "^4.3.2", "vitepress": "^1.6.3", - "vitepress-plugin-tabs": "^0.6.0" + "vitepress-plugin-tabs": "^0.9.0" } } diff --git a/docs/papers.yml b/docs/papers.yml index 21afd44fd..2709f1152 100644 --- a/docs/papers.yml +++ b/docs/papers.yml @@ -2,6 +2,35 @@ # information to generate the "Research Showcase" papers: + - title: "Maximum updraft velocity beyond CAPE: the role of boundary layer dynamics and pressure perturbations" + authors: + - Bety Pechacova (1) + - Alejandro Casallas (1) + - Tom Beucler (2,3) + - Lokahith Agasthya (4) + - Caroline Muller (1) + affiliations: + 1: Institute of Science and Technology Austria + 2: Faculty of Geosciences and Environment, University of Lausanne + 3: Expertise Center for Climate Extremes, University of Lausanne + 4: Department of Meteorology and Geophysics, University of Vienna + link: https://doi.org/10.48550/arXiv.2607.23627 + abstract: "Deep convective updraft velocities play a key role in the Earth's climate system, influencing precipitation extremes, lightning, and the planetary energy budget. While Convective Available Potential Energy (CAPE) is widely used to explain maximum updraft velocity ($w_{\\max}$), CAPE is an imperfect predictor as updrafts are also influenced by entrainment, boundary layer dynamics, pressure perturbations, and condensate loading. However, the relative importance of these processes and how they interact to set $w_{\\max}$ in individual clouds remains unclear. Here, we use equation learning to identify compact, physically interpretable relationships linking environmental and in-cloud conditions to $w_{\\max}$ in individual tracked clouds across idealized radiative–convective equilibrium regimes spanning a range of sea surface temperatures and radiative cooling rates. For pre-storm prediction, CAPE and local mean boundary layer vertical velocity ($\\overline{w_{\\mathrm{bl}}}$) together explain nearly half the variance in $w_{\\max}$ across regimes ($R^2=0.47$). While CAPE captures regime-mean differences, it has little predictive value within a single simulation. $\\overline{w_{\\mathrm{bl}}}$ is essential for capturing cloud-to-cloud variability, including the suppression of $w_{\\max}$ even at high $\\mathrm{CAPE}$ values. At the time of peak intensity, a simple approximate Bernoulli-like invariant combining maximum pressure perturbation and maximum cloud condensate explains 89% of the variance ($R^2=0.89$). The tight link between $w_{\\max}$ and pressure perturbation supports the sticky thermals hypothesis and highlights the importance of dynamic pressure effects, often neglected in updraft theories. These results highlight $\\overline{w_{\\mathrm{bl}}}$ as an important regulator of convective intensity alongside CAPE, and demonstrate that dynamic pressure plays an important role within individual updrafts." + image: https://raw.githubusercontent.com/MilesCranmer/PySR_Docs/d6a8df0a069efb549ed9fc53e87cf0877ad3fee6/images/pr1295_updraft_velocity.png + date: 2026-07-26 + - title: Improvement of spatiotemporal generalization in radiocesium transfer models for wheat using symbolic regression + authors: + - Katsutoshi Seki (1) + - Noriko Yamaguchi (2) + - Tetsuya Eguchi (2) + - Masato Igura (2) + affiliations: + 1: Natural Science Laboratory, Toyo University + 2: Institute for Agro-Environmental Sciences, NARO + link: https://doi.org/10.1016/j.jenvrad.2026.108077 + abstract: "Accurate prediction of the soil-to-plant transfer factor (TF) of radiocesium ($^{137}$Cs) is essential for assessing radionuclide transfer to the human food chain and supporting radiological risk management after nuclear contamination. Semi-empirical TF models based on exchangeable potassium (K) and soil fixation capacity are widely used, but their predictive performance often deteriorates when applied to unobserved sites or years. In this study, we evaluated whether symbolic regression (SR), an approach that derives interpretable mathematical equations from data, can improve the spatiotemporal generalization of TF models. We analyzed long-term monitoring data from 11 upland wheat fields in Japan collected over five survey years ($n$ = 36). Model performance was assessed using structured cross-validation, including leave-one-site-out (LOSO) and leave-one-year-out (LOYO), to explicitly test spatial and temporal extrapolation. A simple K-based model substantially improved baseline predictions compared with a conventional semi-empirical model. However, linear additive extensions incorporating radiocesium interception potential (RIP), cation exchange capacity, pH, and soil $^{137}$Cs did not consistently improve predictive performance under LOSO or LOYO. In contrast, the selected SR model showed the lowest point-estimate prediction errors in both spatial and temporal validations. The selected equation represented a non-linear interaction between exchangeable K and RIP, consistent with reduced K sensitivity in soils with strong specific fixation sites. However, the selected SR-derived equation remains empirical and is valid only within the observed soil-property range and may show nonphysical behavior under extreme RIP conditions. These results suggest that interpretable non-linear equations derived by SR may improve TF prediction within a restricted soil domain." + image: https://raw.githubusercontent.com/MilesCranmer/PySR_Docs/d6a8df0a069efb549ed9fc53e87cf0877ad3fee6/images/pr1206_radiocesium_transfer_models.png + date: 2026-06-13 - title: Discovering data-driven microbial growth models with symbolic regression authors: - T. Anthony Sun (1) @@ -80,7 +109,7 @@ papers: - Pericles Panagiotou (1) affiliations: 1: Laboratory of Fluid Mechanics and Turbomachinery, Department of Mechanical Engineering, Aristotle University of Thessaloniki, 54124 Thessaloniki, Greece - link: https://www.aifluids.net/proceedings/S6P16.pdf + link: https://www.aifluids.net/proceedings/C.%20Pliakos,%20G.%20Efrem,%20D.%20Terzis,%20P.%20Panagiotou,%20Data-driven%20skin%20friction%20estimation%20for%20UAV%20wings%20in%20subsonic%20flows.pdf abstract: Accurate estimation of the skin friction coefficient (𝐶𝑓) is essential for estimating the wall shear stresses (𝜏𝑤) and ultimately the first-layer cell height (𝑦) in wall-resolved RANS simulations of wings, where turbulence models are used, demanding a specific grid resolution near walls (primarily the 𝑦𝑡𝑎𝑟𝑔𝑒𝑡⁺). Conventional flat-plate correlations often fail to account for the three-dimensional nature of real wing flows, introducing uncertainties in 𝐶𝑓 predictions and leading to multiple CFD analyses and mesh refinements to meet the targets. In this work, we propose a machine-learning-based approach exploring symbolic regression to derive a model that correlates wing-specific parameters (e.g., Reynolds number, angle of attack, thickness-to-chord ratio, wing sweep angle) with 𝐶𝑓 at the Mean Aerodynamic Chord (MAC). Data are acquired from an in-house database of over 5,000 RANS simulations for UAV wings operating in the low subsonic regime, covering a wide design space, all conducted following best-practice CFD guidelines to ensure high fidelity. These analyses are performed at various flow conditions covering Reynolds numbers from 10⁵ to 10⁷ and include the complete drag polar for each wing. The proposed correlation provides improved agreement with CFD data and enables more accurate 𝑦⁺ estimations. Validation on different wing geometries, including the ONERA M6 and in-house UAV wings, confirmed the robustness of the model, which improves boundary-layer resolution with only a marginal (~2%) increase in total mesh size, while achieving an R² of 0.68 with negligible computational inference cost. This explicit, data-driven equation offers an efficient method for streamlining mesh generation in aerodynamic simulations. image: https://raw.githubusercontent.com/MilesCranmer/PySR_Docs/48020babcca1e73d2f5bbbd4ef3f0ec3265c59fb/images/pr1127_skin_friction.png date: 2025-05-30 diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index 1921fdd00..7572d3fda 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -1,20 +1,31 @@ +import { readFileSync } from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' import { defineConfig } from 'vitepress' import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs' import mathjax3 from "markdown-it-mathjax3"; import footnote from "markdown-it-footnote"; -function getBaseRepository(base: string): string { - if (!base || base === '/') return '/'; - const parts = base.split('/').filter(Boolean); - return parts.length > 0 ? `/${parts[0]}/` : '/'; +// Maps the juliapkg.json pin to the SymbolicRegression.jl docs folder covering +// every version the pin admits, following Julia Pkg compat semantics: +// `=X.Y.Z` is exact, `~X.Y.Z` allows patch bumps, `^X.Y.Z` (or bare) allows minor bumps. +export function symbolicRegressionDocsSubfolder(pkg: { version?: unknown; rev?: unknown }): string { + const compat = typeof pkg.version === 'string' ? pkg.version.trim().match(/^([=~^]?)(\d+)(?:\.(\d+))?(?:\.(\d+))?$/) : null + if (compat) { + const [, op, major, minor, patch] = compat + if (op === '=' && patch !== undefined) return `v${major}.${minor}.${patch}` + if ((op === '~' || op === '=' || major === '0') && minor !== undefined) return `v${major}.${minor}` + return `v${major}` + } + return typeof pkg.rev === 'string' && /^v\d+\.\d+\.\d+$/.test(pkg.rev) ? pkg.rev : 'dev' } -// Canonical domain for SEO - will be replaced by deploy.jl at build time -// Empty string means no canonical tag (this site is canonical) -const canonicalDomain = ''; +const configDirectory = path.dirname(fileURLToPath(import.meta.url)) +const juliaPkg = JSON.parse(readFileSync(path.resolve(configDirectory, '../../../pysr/juliapkg.json'), 'utf8')) +const symbolicRegressionDocsVersion = symbolicRegressionDocsSubfolder(juliaPkg.packages.SymbolicRegression) const baseTemp = { - base: '/pysr/', + base: '/', } const nav = [ @@ -23,13 +34,13 @@ const nav = [ { text: 'API', link: '/api' }, { text: 'Forum', - link: 'https://github.com/MilesCranmer/PySR/discussions' + link: 'https://github.com/astroautomata/PySR/discussions' }, { text: 'Python', items: [ { text: 'Python', link: '/' }, - { text: 'Julia', link: 'https://ai.damtp.cam.ac.uk/symbolicregression/dev/' } + { text: 'Julia', link: `https://julia.pysr.ai/${symbolicRegressionDocsVersion}/`, target: '_self', noIcon: true } ] }, { @@ -39,7 +50,7 @@ const nav = [ // https://vitepress.dev/reference/site-config export default defineConfig({ - base: '/pysr/', + base: '/', title: 'PySR', description: 'High-Performance Symbolic Regression in Python and Julia', lastUpdated: true, @@ -47,25 +58,17 @@ export default defineConfig({ outDir: '../dist', srcExclude: ['**/_*.md'], head: [ - ['link', { rel: 'icon', href: `${baseTemp.base}favicon.png` }], - ['script', {src: `${getBaseRepository(baseTemp.base)}versions.js`}], + ['link', { rel: 'icon', type: 'image/x-icon', href: `${baseTemp.base}favicon.ico` }], + ['link', { rel: 'icon', type: 'image/png', sizes: '16x16', href: `${baseTemp.base}favicon-16x16.png` }], + ['link', { rel: 'icon', type: 'image/png', sizes: '32x32', href: `${baseTemp.base}favicon-32x32.png` }], + ['link', { rel: 'apple-touch-icon', sizes: '180x180', href: `${baseTemp.base}apple-touch-icon.png` }], + ['script', {src: '/versions.js'}], ['script', {src: `${baseTemp.base}siteinfo.js`}] ], ignoreDeadLinks: true, - transformHead: ({ pageData }) => { - if (!canonicalDomain) return []; - - // Construct canonical URL for this page - const pagePath = pageData.relativePath.replace(/((^|\/)index)?\.md$/, '$2'); - const canonicalUrl = `${canonicalDomain}${pagePath}`; - - return [ - ['link', { rel: 'canonical', href: canonicalUrl }] - ]; - }, vite: { define: { - __DEPLOY_ABSPATH__: JSON.stringify(getBaseRepository(baseTemp.base)), + __DEPLOY_ABSPATH__: JSON.stringify('/'), }, optimizeDeps: { exclude: [ @@ -96,7 +99,7 @@ export default defineConfig({ }, themeConfig: { outline: 'deep', - logo: 'https://ai.damtp.cam.ac.uk/symbolicregression/dev/logo.png', + logo: 'https://raw.githubusercontent.com/MilesCranmer/PySR_Docs/master/images/pysr-logo.png', search: { provider: 'local', options: { @@ -109,7 +112,21 @@ export default defineConfig({ text: 'Getting Started', items: [ { text: 'Introduction', link: '/' }, - { text: 'Examples', link: '/examples' }, + { + text: 'Examples', + link: '/examples', + collapsed: true, + items: [ + { text: 'Getting started', link: '/examples/getting-started' }, + { text: 'Expression specifications', link: '/examples/expression-specifications' }, + { text: 'Objectives and losses', link: '/examples/objectives' }, + { text: 'Physics and units', link: '/examples/physics' }, + { text: 'Search behaviour', link: '/examples/search-behaviour' }, + { text: 'Instrumentation and workflow', link: '/examples/instrumentation' }, + { text: 'Value types', link: '/examples/value-types' }, + { text: 'Beyond numeric values', link: '/examples/beyond-numeric-values' }, + ] + }, ] }, { @@ -131,16 +148,18 @@ export default defineConfig({ text: 'Advanced', items: [ { text: 'Tuning', link: '/tuning' }, + { text: 'Slurm', link: '/slurm' }, { text: 'Backend', link: '/backend' }, + { text: 'Migrating from v1', link: '/migration' }, ] } ], editLink: { - pattern: 'https://github.com/MilesCranmer/PySR/edit/master/docs/:path', + pattern: 'https://github.com/astroautomata/PySR/edit/master/docs/:path', text: 'Edit this page on GitHub' }, socialLinks: [ - { icon: 'github', link: 'https://github.com/MilesCranmer/PySR' } + { icon: 'github', link: 'https://github.com/astroautomata/PySR' } ], footer: { message: 'Made with VitePress', diff --git a/docs/src/.vitepress/theme/style.css b/docs/src/.vitepress/theme/style.css index e5b79d3d1..d9afca052 100644 --- a/docs/src/.vitepress/theme/style.css +++ b/docs/src/.vitepress/theme/style.css @@ -108,3 +108,14 @@ figure img { .VPNavBarMenuGroup:hover button .text { color: var(--vp-c-text-2) !important; } + +/* A
section whose holds a heading: keep them on one line */ +.vp-doc summary:has(> h3) { + cursor: pointer; + margin-top: 32px; +} + +.vp-doc summary > h3 { + display: inline; + margin: 0; +} diff --git a/docs/src/_api_template.md b/docs/src/_api_template.md index 28cfa3155..2e25d7e86 100644 --- a/docs/src/_api_template.md +++ b/docs/src/_api_template.md @@ -75,13 +75,15 @@ PARAMSKEY heading_level: 3 show_root_full_path: false -::: pysr.ParametricExpressionSpec +::: pysr.AbstractExpressionSpec options: show_root_heading: true heading_level: 3 show_root_full_path: false -::: pysr.AbstractExpressionSpec +## Type Specifications + +::: pysr.TypeSpec options: show_root_heading: true heading_level: 3 @@ -100,3 +102,121 @@ PARAMSKEY show_root_heading: true heading_level: 3 show_root_full_path: false + +## Mutations + +::: pysr.ConstantMutation + options: + show_root_heading: true + heading_level: 3 + show_root_full_path: false + +::: pysr.OperatorMutation + options: + show_root_heading: true + heading_level: 3 + show_root_full_path: false + +::: pysr.FeatureMutation + options: + show_root_heading: true + heading_level: 3 + show_root_full_path: false + +::: pysr.SwapOperandsMutation + options: + show_root_heading: true + heading_level: 3 + show_root_full_path: false + +::: pysr.AddNodeMutation + options: + show_root_heading: true + heading_level: 3 + show_root_full_path: false + +::: pysr.InsertNodeMutation + options: + show_root_heading: true + heading_level: 3 + show_root_full_path: false + +::: pysr.DeleteNodeMutation + options: + show_root_heading: true + heading_level: 3 + show_root_full_path: false + +::: pysr.RotateTreeMutation + options: + show_root_heading: true + heading_level: 3 + show_root_full_path: false + +::: pysr.BacksolveMutation + options: + show_root_heading: true + heading_level: 3 + show_root_full_path: false + +::: pysr.SimplifyMutation + options: + show_root_heading: true + heading_level: 3 + show_root_full_path: false + +::: pysr.RandomizeMutation + options: + show_root_heading: true + heading_level: 3 + show_root_full_path: false + +::: pysr.OptimizeMutation + options: + show_root_heading: true + heading_level: 3 + show_root_full_path: false + +::: pysr.DoNothingMutation + options: + show_root_heading: true + heading_level: 3 + show_root_full_path: false + +::: pysr.AbstractMutation + options: + show_root_heading: true + heading_level: 3 + show_root_full_path: false + +## Plugins + +::: pysr.AdaptiveMutationWeightsPlugin + options: + show_root_heading: true + heading_level: 3 + show_root_full_path: false + +::: pysr.AdaptiveParsimonyPlugin + options: + show_root_heading: true + heading_level: 3 + show_root_full_path: false + +::: pysr.MutationBurstPlugin + options: + show_root_heading: true + heading_level: 3 + show_root_full_path: false + +::: pysr.SimulatedAnnealingPlugin + options: + show_root_heading: true + heading_level: 3 + show_root_full_path: false + +::: pysr.AbstractPlugin + options: + show_root_heading: true + heading_level: 3 + show_root_full_path: false diff --git a/docs/src/_index.md b/docs/src/_index.md index 52587ba3b..df6abef05 100644 --- a/docs/src/_index.md +++ b/docs/src/_index.md @@ -16,7 +16,7 @@ hero: text: View on GitHub link: https://github.com/astroautomata/PySR image: - src: https://ai.damtp.cam.ac.uk/symbolicregression/dev/logo.png + src: https://raw.githubusercontent.com/MilesCranmer/PySR_Docs/master/images/pysr-logo.png alt: PySR features: @@ -33,8 +33,8 @@ features: details: "Customize everything: operators, loss functions, complexity, input types, optimizer, and more." - icon: 🐍 - title: Python Native - details: Built for Python with Julia backend for maximum performance and ease of use. + title: Familiar Python API + details: A scikit-learn interface, with results exported to SymPy, PyTorch, or JAX. --- If you find PySR useful, please cite the paper [arXiv:2305.01582](https://arxiv.org/abs/2305.01582). diff --git a/docs/src/backend.md b/docs/src/backend.md index 801f353cb..3dc4b68cb 100644 --- a/docs/src/backend.md +++ b/docs/src/backend.md @@ -33,9 +33,9 @@ The main search code can be found in `src/SymbolicRegression.jl`. Here are some tips: -- The documentation for the backend is given [here](https://ai.damtp.cam.ac.uk/symbolicregression/dev/). +- The documentation for the backend is given [here](https://julia.pysr.ai/dev/). - Throughout the package, you will often see template functions which typically use a symbol `T` (such as in the string `where {T<:Real}`). Here, `T` is simply the datatype of the input data and stored constants, such as `Float32` or `Float64`. Writing functions in this way lets us write functions generic to types, while still having access to the specific type specified at compilation time. -- Expressions are stored as binary trees, using the `Node{T}` type, described [here](https://ai.damtp.cam.ac.uk/symbolicregression/dev/types/#SymbolicRegression.CoreModule.EquationModule.Node). +- Expressions are stored as binary trees, using the `Node{T}` type, described [here](https://julia.pysr.ai/dev/types#SymbolicRegression.CoreModule.EquationModule.Node). - For reference, the main loop itself is found in the `equation_search` function inside [`src/SymbolicRegression.jl`](https://github.com/astroautomata/SymbolicRegression.jl/blob/master/src/SymbolicRegression.jl). - Parts of the code which are typically edited by users include: - [`src/CheckConstraints.jl`](https://github.com/astroautomata/SymbolicRegression.jl/blob/master/src/CheckConstraints.jl), particularly the function `check_constraints`. This function checks whether a given expression satisfies constraints, such as having a complexity lower than `maxsize`, and whether it contains any forbidden nestings of functions. @@ -70,6 +70,6 @@ For more information on `juliapkg.json`, see [`pyjuliapkg`](https://github.com/J ## Additional notes -If you get comfortable enough with the backend, you might consider using the Julia package directly: the API is given on the [SymbolicRegression.jl documentation](https://ai.damtp.cam.ac.uk/symbolicregression/dev/). +If you get comfortable enough with the backend, you might consider using the Julia package directly: the API is given on the [SymbolicRegression.jl documentation](https://julia.pysr.ai/dev/). If you make a change that you think could be useful to other users, don't hesitate to open a pull request on either the PySR or SymbolicRegression.jl repositories! Contributions are very appreciated. diff --git a/docs/src/components/VersionPicker.vue b/docs/src/components/VersionPicker.vue index 17514e206..2dc8a6b49 100644 --- a/docs/src/components/VersionPicker.vue +++ b/docs/src/components/VersionPicker.vue @@ -34,6 +34,8 @@ function absoluteUrl(relative) { const props = defineProps<{ screenMenu?: boolean }>(); const versions = ref>([]); const currentVersion = ref('Versions'); +// Releases are shown as vX.Y; links still point at the full vX.Y.Z folder. +const minorLabel = (v: string) => /^(v\d+\.\d+)\.\d+$/.exec(v)?.[1] ?? v; const isClient = ref(false); const { site } = useData(); @@ -71,10 +73,21 @@ const loadVersions = async () => { const scriptsLoaded = await waitForScriptsToLoad(); if (scriptsLoaded && window.DOC_VERSIONS && window.DOCUMENTER_CURRENT_VERSION) { - versions.value = window.DOC_VERSIONS.map(v => ({ - text: v, - link: absoluteUrl(`/${v}/`), - })); + // DOC_VERSIONS lists releases newest first, so the first vX.Y.Z of each minor is its latest patch. + // Aliases (v1, v1.5) and prereleases stay reachable by URL, out of the picker. + const seenMinors = new Set(); + versions.value = window.DOC_VERSIONS + .filter(v => { + if (v === 'dev' || v === 'stable') return true; + const minor = /^(v\d+\.\d+)\.\d+$/.exec(v)?.[1]; + if (!minor || seenMinors.has(minor)) return false; + seenMinors.add(minor); + return true; + }) + .map(v => ({ + text: minorLabel(v), + link: absoluteUrl(`/${v}/`), + })); currentVersion.value = window.DOCUMENTER_CURRENT_VERSION; } else { versions.value = [{ text: 'dev', link: absoluteUrl('/dev/') }]; @@ -89,12 +102,12 @@ const loadVersions = async () => { isClient.value = true; }; -const versionItems = computed(() => { - return versions.value.map((v) => ({ - text: v.text, - link: v.link - })); -}); +const versionItems = computed(() => versions.value.map((v) => ({ + text: v.text, + link: v.link, + target: '_self', + noIcon: true +}))); onMounted(() => { if (typeof window !== 'undefined') { @@ -108,12 +121,12 @@ onMounted(() => {