diff --git a/.github/workflows/branch-checks.yaml b/.github/workflows/branch-checks.yaml
index 7245169e..5fa58954 100644
--- a/.github/workflows/branch-checks.yaml
+++ b/.github/workflows/branch-checks.yaml
@@ -9,9 +9,10 @@ jobs:
name: Run all pre-commit hooks
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-python@v3
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
- uses: pre-commit/action@v3.0.1
+
# For feature branches, we don't test the full matrix (os x [stable, loose]) in order to save time & resources.
run-tests-stable:
name: Test stable pip installation on ubuntu-latest
@@ -25,3 +26,13 @@ jobs:
os: ${{ matrix.os }}
install-script: ./pip_install.sh stable,test
test-script: ./run_unit_tests.sh
+
+ get-code-review-input:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: MannLabs/alphashared/actions/get-code-review-input@v1
+ continue-on-error: true
+ with:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ PR_NUMBER: ${{ github.event.number }}
+ EXCLUDED_EXTENSIONS: ipynb;js
diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml
index e9da72d8..49cba5c2 100644
--- a/.github/workflows/create_release.yml
+++ b/.github/workflows/create_release.yml
@@ -1,200 +1,22 @@
+# Create a draft release and build and upload all installers to it.
+name: Create Release
+
on:
workflow_dispatch:
inputs:
- commit_to_release:
- description: 'Enter commit hash to release (example: ef4037cb571f99cb4919b520fde7174972aae473)'
- required: true
- tag_to_release:
- description: 'Enter tag to release (example: v1.5.5)'
- required: true
-
-
-name: Create Draft Release
+ commitish_to_release:
+ type: string
+ description: 'Enter commit hash or branch to release (default: main).'
+ default: "main"
jobs:
- Get_New_Version:
- runs-on: ubuntu-latest
- outputs:
- new_version: ${{ steps.check_release_tag.outputs.new_version }}
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
- with:
- ref: ${{ inputs.commit_to_release }}
-
- - name: Check release tag
- id: check_release_tag
- shell: bash -le {0}
- run: |
- CURRENT_VERSION=$(./misc/get_current_version.sh)
- if [ "v${CURRENT_VERSION}" != "${{ inputs.tag_to_release }}" ]; then
- echo Code version "v${CURRENT_VERSION}" does not match the tag to release ${{ inputs.tag_to_release }}
- exit 1
- fi
- echo "new_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
-
- - uses: mukunku/tag-exists-action@v1.6.0
- id: check-tag
- with:
- tag: ${{ inputs.tag_to_release }}
-
- - name: Check if tag already exists
- run: |
- echo "Tag already exists!"
- exit 1
- if: steps.check-tag.outputs.exists == 'true'
-
-
- Create_Draft_Release:
- runs-on: ubuntu-latest
- needs: Get_New_Version
- outputs:
- upload_url: ${{ steps.draft_release.outputs.upload_url }}
- steps:
- - name: Draft Release
- id: draft_release
- # TODO this action is deprecated, replace with https://github.com/ncipollo/release-action
- # cf. https://github.com/actions/create-release/issues/119#issuecomment-783010321
- uses: actions/create-release@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- tag_name: ${{ inputs.tag_to_release }}
- release_name: ${{ inputs.tag_to_release }}
- draft: true
- prerelease: false
-
- Create_MacOS_Installer:
- needs: [Create_Draft_Release, Get_New_Version]
- env:
- ARCH: x64
- EAGER_IMPORT: true
- runs-on: macos-latest-xlarge
- steps:
- - name : Checkout code
- uses: actions/checkout@v4
- with:
- ref: ${{ inputs.commit_to_release }}
-
- # Build backend
- - name: Install conda
- uses: conda-incubator/setup-miniconda@v3
- with:
- miniconda-version: "latest"
- auto-update-conda: true
- activate-environment: alpha
- python-version: "3.11"
-
- - name: Check arm64
- shell: bash -el {0}
- run: |
- python -c "import platform; print(platform.machine())"
-
- - name: Build backend
- shell: bash -el {0}
- run: |
- release/macos/build_backend_macos.sh
-
- - name: Test backend
- shell: bash -el {0}
- run: |
- dist/alphadia/alphadia --version
-
- # Build GUI
- - name: Setup Node.js
- uses: actions/setup-node@v4
-
- - name: Build GUI
- run: |
- release/macos/build_gui_macos.sh
-
- # combine backend and GUI
- - name: Build package
- shell: bash -el {0}
- run: |
- release/macos/build_pkg_macos.sh
-
- - name: List output files
- run: |
- ls dist
-
- # Upload the package
- - name: Upload a Release Asset
- uses: actions/upload-release-asset@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- upload_url: ${{ needs.Create_Draft_Release.outputs.upload_url }}
- asset_path: dist/alphadia-${{ needs.Get_New_Version.outputs.new_version }}-darwin-${{ env.ARCH }}.pkg
- asset_name: alphadia-${{ needs.Get_New_Version.outputs.new_version }}-darwin-${{ env.ARCH }}.pkg
- asset_content_type: application/zip
-
- Create_Windows_Installer:
- needs: [Create_Draft_Release, Get_New_Version]
- env:
- ARCH: x64
- runs-on: windows-latest
- steps:
- - name : Checkout code
- uses: actions/checkout@v4
- with:
- ref: ${{ inputs.commit_to_release }}
-
- # Build backend
- - name: Install conda
- uses: conda-incubator/setup-miniconda@v3
- with:
- miniconda-version: "latest"
- auto-update-conda: true
- activate-environment: alpha
- python-version: "3.11"
-
- - name: Build Backend
- shell: powershell
- run: |
- release/windows/build_backend.ps1
-
- - name: Test Backend
- shell: powershell
- run: |
- dist\alphadia\alphadia.exe --version
-
- # Build GUI
- - name: Setup Node.js
- uses: actions/setup-node@v4
-
- - name: Build GUI
- shell: powershell
- run: |
- release/windows/build_gui.ps1
-
- # combine backend and GUI
- - name: Check if Innosetup is installed
- shell: powershell
- run: |
- if (-not (Test-Path "C:\Program Files (x86)\Inno Setup 6\ISCC.exe")) {
- Write-Host "Inno Setup is not installed"
- exit 1
- }
- else {
- Write-Host "Inno Setup is installed"
- }
-
- - name: Build Installer
- shell: powershell
- run: |
- release/windows/build_installer.ps1
-
- - name: List output files
- run: |
- ls dist
-
- - name: Upload a Release Asset
- uses: actions/upload-release-asset@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- upload_url: ${{ needs.Create_Draft_Release.outputs.upload_url }}
- asset_path: dist/alphadia-${{ needs.Get_New_Version.outputs.new_version }}-win-${{ env.ARCH }}.exe
- asset_name: alphadia-${{ needs.Get_New_Version.outputs.new_version }}-win-${{ env.ARCH }}.exe
- asset_content_type: application/zip
+ create-release:
+ uses: MannLabs/alphashared/.github/workflows/create_release.yml@v1
+ secrets: inherit
+ permissions:
+ contents: write
+ with:
+ package_name: alphadia
+ commitish_to_release: ${{ inputs.commitish_to_release }}
+ build_nodejs_ui: true
+ test_app: false
diff --git a/.github/workflows/e2e_testing.yml b/.github/workflows/e2e_testing.yml
index 0a6c083a..76365f1e 100644
--- a/.github/workflows/e2e_testing.yml
+++ b/.github/workflows/e2e_testing.yml
@@ -15,7 +15,7 @@ jobs:
strategy:
matrix:
# test case name as defined in e2e_test_cases.yaml
- test_case: [ "basic", "synchropasef", "astral", ]
+ test_case: [ "basic", "synchropasef", "astral", "astral_automatic_calibration", ]
env:
RUN_NAME: alphadia-${{github.sha}}-${{github.run_id}}-${{github.run_attempt}}
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
@@ -24,6 +24,7 @@ jobs:
NUMBA_BOUNDSCHECK: 1
NUMBA_DEVELOPER_MODE: 1
NUMBA_FULL_TRACEBACKS: 1
+ TQDM_MININTERVAL: 10 # avoid lots of tqdm outputs
steps:
- uses: actions/checkout@v4
- name: Conda info
@@ -39,7 +40,7 @@ jobs:
shell: bash -el {0}
run: |
cd tests
- . ./run_e2e_tests.sh ${{ matrix.test_case }} $RUN_NAME ${GITHUB_SHA::7} $BRANCH_NAME
+ . ./run_e2e_tests.sh ${{ matrix.test_case }} $RUN_NAME True ${GITHUB_SHA::7} $BRANCH_NAME
- name: Cleanup
if: always()
shell: bash -el {0}
diff --git a/.github/workflows/legacy_create_release.yml b/.github/workflows/legacy_create_release.yml
new file mode 100644
index 00000000..161d9898
--- /dev/null
+++ b/.github/workflows/legacy_create_release.yml
@@ -0,0 +1,201 @@
+# TODO delete once the new release workflow has run once
+on:
+ workflow_dispatch:
+ inputs:
+ commit_to_release:
+ description: 'Enter commit hash to release (example: ef4037cb571f99cb4919b520fde7174972aae473)'
+ required: true
+ tag_to_release:
+ description: 'Enter tag to release (example: v1.5.5)'
+ required: true
+
+
+name: LEGACY Create Draft Release
+
+jobs:
+ Get_New_Version:
+ runs-on: ubuntu-latest
+ outputs:
+ new_version: ${{ steps.check_release_tag.outputs.new_version }}
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.commit_to_release }}
+
+ - name: Check release tag
+ id: check_release_tag
+ shell: bash -le {0}
+ run: |
+ CURRENT_VERSION=$(./misc/get_current_version.sh)
+ if [ "v${CURRENT_VERSION}" != "${{ inputs.tag_to_release }}" ]; then
+ echo Code version "v${CURRENT_VERSION}" does not match the tag to release ${{ inputs.tag_to_release }}
+ exit 1
+ fi
+ echo "new_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
+
+ - uses: mukunku/tag-exists-action@v1.6.0
+ id: check-tag
+ with:
+ tag: ${{ inputs.tag_to_release }}
+
+ - name: Check if tag already exists
+ run: |
+ echo "Tag already exists!"
+ exit 1
+ if: steps.check-tag.outputs.exists == 'true'
+
+
+ Create_Draft_Release:
+ runs-on: ubuntu-latest
+ needs: Get_New_Version
+ outputs:
+ upload_url: ${{ steps.draft_release.outputs.upload_url }}
+ steps:
+ - name: Draft Release
+ id: draft_release
+ # TODO this action is deprecated, replace with https://github.com/ncipollo/release-action
+ # cf. https://github.com/actions/create-release/issues/119#issuecomment-783010321
+ uses: actions/create-release@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ tag_name: ${{ inputs.tag_to_release }}
+ release_name: ${{ inputs.tag_to_release }}
+ draft: true
+ prerelease: false
+
+ Create_MacOS_Installer:
+ needs: [Create_Draft_Release, Get_New_Version]
+ env:
+ ARCH: x64
+ EAGER_IMPORT: true
+ runs-on: macos-latest-xlarge
+ steps:
+ - name : Checkout code
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.commit_to_release }}
+
+ # Build backend
+ - name: Install conda
+ uses: conda-incubator/setup-miniconda@v3
+ with:
+ miniconda-version: "latest"
+ auto-update-conda: true
+ activate-environment: alpha
+ python-version: "3.11"
+
+ - name: Check arm64
+ shell: bash -el {0}
+ run: |
+ python -c "import platform; print(platform.machine())"
+
+ - name: Build backend
+ shell: bash -el {0}
+ run: |
+ release/macos/build_backend_macos.sh
+
+ - name: Test backend
+ shell: bash -el {0}
+ run: |
+ dist/alphadia/alphadia --version
+
+ # Build GUI
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+
+ - name: Build GUI
+ run: |
+ release/macos/build_gui_macos.sh
+
+ # combine backend and GUI
+ - name: Build package
+ shell: bash -el {0}
+ run: |
+ release/macos/build_pkg_macos.sh
+
+ - name: List output files
+ run: |
+ ls dist
+
+ # Upload the package
+ - name: Upload a Release Asset
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ needs.Create_Draft_Release.outputs.upload_url }}
+ asset_path: dist/alphadia-${{ needs.Get_New_Version.outputs.new_version }}-darwin-${{ env.ARCH }}.pkg
+ asset_name: alphadia-${{ needs.Get_New_Version.outputs.new_version }}-darwin-${{ env.ARCH }}.pkg
+ asset_content_type: application/zip
+
+ Create_Windows_Installer:
+ needs: [Create_Draft_Release, Get_New_Version]
+ env:
+ ARCH: x64
+ runs-on: windows-latest
+ steps:
+ - name : Checkout code
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.commit_to_release }}
+
+ # Build backend
+ - name: Install conda
+ uses: conda-incubator/setup-miniconda@v3
+ with:
+ miniconda-version: "latest"
+ auto-update-conda: true
+ activate-environment: alpha
+ python-version: "3.11"
+
+ - name: Build Backend
+ shell: powershell
+ run: |
+ release/windows/build_backend.ps1
+
+ - name: Test Backend
+ shell: powershell
+ run: |
+ dist\alphadia\alphadia.exe --version
+
+ # Build GUI
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+
+ - name: Build GUI
+ shell: powershell
+ run: |
+ release/windows/build_gui.ps1
+
+ # combine backend and GUI
+ - name: Check if Innosetup is installed
+ shell: powershell
+ run: |
+ if (-not (Test-Path "C:\Program Files (x86)\Inno Setup 6\ISCC.exe")) {
+ Write-Host "Inno Setup is not installed"
+ exit 1
+ }
+ else {
+ Write-Host "Inno Setup is installed"
+ }
+
+ - name: Build Installer
+ shell: powershell
+ run: |
+ release/windows/build_installer.ps1
+
+ - name: List output files
+ run: |
+ ls dist
+
+ - name: Upload a Release Asset
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ needs.Create_Draft_Release.outputs.upload_url }}
+ asset_path: dist/alphadia-${{ needs.Get_New_Version.outputs.new_version }}-win-${{ env.ARCH }}.exe
+ asset_name: alphadia-${{ needs.Get_New_Version.outputs.new_version }}-win-${{ env.ARCH }}.exe
+ asset_content_type: application/zip
diff --git a/.github/workflows/legacy_publish_on_pypi.yml b/.github/workflows/legacy_publish_on_pypi.yml
new file mode 100644
index 00000000..2efb2470
--- /dev/null
+++ b/.github/workflows/legacy_publish_on_pypi.yml
@@ -0,0 +1,126 @@
+# TODO delete once the new release workflow has run once
+on:
+ workflow_dispatch:
+ inputs:
+ tag_to_release:
+ description: 'Enter tag to release (example: v1.5.5)'
+ required: true
+
+name: LEGACY Publish on PyPi
+
+env:
+ PYTHON_VERSION: "3.11"
+
+jobs:
+ Create_PyPi_Release:
+ runs-on: ubuntu-latest
+ outputs:
+ new_version: ${{ steps.get_current_version.outputs.new_version }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.tag_to_release }}
+ - uses: conda-incubator/setup-miniconda@v3
+ with:
+ miniconda-version: "latest"
+ auto-update-conda: true
+ python-version: ${{ env.PYTHON_VERSION }}
+ - name: Conda info
+ shell: bash -le {0}
+ run: conda info
+ - name: Get current version
+ id: get_current_version
+ shell: bash -l {0}
+ run: |
+ CURRENT_VERSION=$(./misc/get_current_version.sh)
+ echo "new_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
+ - uses: actions/cache@v4
+ with:
+ path: ~/.cache/pip
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
+ - name: Prepare distribution
+ shell: bash -le {0}
+ run: |
+ conda create -n alphadia_build python=${{ env.PYTHON_VERSION }} -y
+ conda activate alphadia_build
+ python -m pip install --upgrade pip
+ pip install build twine
+ rm -rf dist
+ rm -rf build
+ python -m build
+ twine check dist/*
+ conda deactivate
+ conda env remove --name alphadia_build -y
+ conda clean --all -y
+ - name: Publish distribution to Test-PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ repository-url: https://test.pypi.org/legacy/
+ user: __token__
+ password: ${{ secrets.TEST_PYPI_API_TOKEN }}
+ - name: Test Test-PyPI loose installation
+ shell: bash -le {0}
+ run: |
+ conda create -n pip_loose_test python=${{ env.PYTHON_VERSION }} -y
+ conda activate pip_loose_test
+ pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple "alphadia==${{ steps.get_current_version.outputs.new_version }}"
+ alphadia -v
+ conda deactivate
+ conda env remove --name pip_stable_test -y
+ conda clean --all -y
+ - name: Test Test-PyPI stable installation
+ shell: bash -le {0}
+ run: |
+ conda create -n pip_stable_test python=${{ env.PYTHON_VERSION }} -y
+ conda activate pip_stable_test
+ pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple "alphadia[stable]==${{ steps.get_current_version.outputs.new_version }}"
+ alphadia -v
+ conda deactivate
+ conda env remove --name pip_stable_test -y
+ conda clean --all -y
+ - name: Publish distribution to PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ user: __token__
+ password: ${{ secrets.PYPI_API_TOKEN }}
+ Test_PyPi_Release:
+ name: Test_PyPi_version_on_${{ matrix.os }}
+ runs-on: ${{ matrix.os }}
+ needs: Create_PyPi_Release
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macOS-latest, windows-latest]
+ steps:
+ - uses: actions/checkout@v4
+ - uses: conda-incubator/setup-miniconda@v3
+ with:
+ miniconda-version: "latest"
+ auto-update-conda: true
+ python-version: ${{ env.PYTHON_VERSION }}
+ - name: Conda info
+ shell: bash -le {0}
+ run: conda info
+ - uses: actions/cache@v4
+ with:
+ path: ~/.cache/pip
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
+ - name: Test PyPI stable installation
+ shell: bash -le {0}
+ run: |
+ conda create -n pip_stable python=${{ env.PYTHON_VERSION }} -y
+ conda activate pip_stable
+ pip install "alphadia[stable]==${{ needs.Create_PyPi_Release.outputs.new_version }}"
+ alphadia -v
+ conda deactivate
+ conda env remove --name pip_stable -y
+ conda clean --all -y
+ - name: Test PyPI loose installation
+ shell: bash -le {0}
+ run: |
+ conda create -n pip_loose python=${{ env.PYTHON_VERSION }} -y
+ conda activate pip_loose
+ pip install alphadia==${{ needs.Create_PyPi_Release.outputs.new_version }}
+ alphadia -v
+ conda deactivate
+ conda env remove --name pip_loose -y
+ conda clean --all -y
diff --git a/.github/workflows/publish_on_pypi.yml b/.github/workflows/publish_on_pypi.yml
index 927408c0..4a538d6f 100644
--- a/.github/workflows/publish_on_pypi.yml
+++ b/.github/workflows/publish_on_pypi.yml
@@ -1,125 +1,21 @@
+# Publish and test releases on Test-PyPI and PyPI.
+name: Publish on PyPi
+
on:
workflow_dispatch:
inputs:
tag_to_release:
- description: 'Enter tag to release (example: v1.5.5)'
+ description: 'Enter tag to release (example: v1.5.5). A tag with the same name must exist in the repository.'
+ type: string
required: true
-name: Publish on PyPi
-
-env:
- PYTHON_VERSION: "3.11"
-
jobs:
- Create_PyPi_Release:
- runs-on: ubuntu-latest
- outputs:
- new_version: ${{ steps.get_current_version.outputs.new_version }}
- steps:
- - uses: actions/checkout@v4
- with:
- ref: ${{ inputs.tag_to_release }}
- - uses: conda-incubator/setup-miniconda@v3
- with:
- miniconda-version: "latest"
- auto-update-conda: true
- python-version: ${{ env.PYTHON_VERSION }}
- - name: Conda info
- shell: bash -le {0}
- run: conda info
- - name: Get current version
- id: get_current_version
- shell: bash -l {0}
- run: |
- CURRENT_VERSION=$(./misc/get_current_version.sh)
- echo "new_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
- - uses: actions/cache@v4
- with:
- path: ~/.cache/pip
- key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
- - name: Prepare distribution
- shell: bash -le {0}
- run: |
- conda create -n alphadia_build python=${{ env.PYTHON_VERSION }} -y
- conda activate alphadia_build
- python -m pip install --upgrade pip
- pip install build twine
- rm -rf dist
- rm -rf build
- python -m build
- twine check dist/*
- conda deactivate
- conda env remove --name alphadia_build -y
- conda clean --all -y
- - name: Publish distribution to Test-PyPI
- uses: pypa/gh-action-pypi-publish@release/v1
- with:
- repository-url: https://test.pypi.org/legacy/
- user: __token__
- password: ${{ secrets.TEST_PYPI_API_TOKEN }}
- - name: Test Test-PyPI loose installation
- shell: bash -le {0}
- run: |
- conda create -n pip_loose_test python=${{ env.PYTHON_VERSION }} -y
- conda activate pip_loose_test
- pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple "alphadia==${{ steps.get_current_version.outputs.new_version }}"
- alphadia -v
- conda deactivate
- conda env remove --name pip_stable_test -y
- conda clean --all -y
- - name: Test Test-PyPI stable installation
- shell: bash -le {0}
- run: |
- conda create -n pip_stable_test python=${{ env.PYTHON_VERSION }} -y
- conda activate pip_stable_test
- pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple "alphadia[stable]==${{ steps.get_current_version.outputs.new_version }}"
- alphadia -v
- conda deactivate
- conda env remove --name pip_stable_test -y
- conda clean --all -y
- - name: Publish distribution to PyPI
- uses: pypa/gh-action-pypi-publish@release/v1
- with:
- user: __token__
- password: ${{ secrets.PYPI_API_TOKEN }}
- Test_PyPi_Release:
- name: Test_PyPi_version_on_${{ matrix.os }}
- runs-on: ${{ matrix.os }}
- needs: Create_PyPi_Release
- strategy:
- matrix:
- os: [ubuntu-latest, macOS-latest, windows-latest]
- steps:
- - uses: actions/checkout@v4
- - uses: conda-incubator/setup-miniconda@v3
- with:
- miniconda-version: "latest"
- auto-update-conda: true
- python-version: ${{ env.PYTHON_VERSION }}
- - name: Conda info
- shell: bash -le {0}
- run: conda info
- - uses: actions/cache@v4
- with:
- path: ~/.cache/pip
- key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
- - name: Test PyPI stable installation
- shell: bash -le {0}
- run: |
- conda create -n pip_stable python=${{ env.PYTHON_VERSION }} -y
- conda activate pip_stable
- pip install "alphadia[stable]==${{ needs.Create_PyPi_Release.outputs.new_version }}"
- alphadia -v
- conda deactivate
- conda env remove --name pip_stable -y
- conda clean --all -y
- - name: Test PyPI loose installation
- shell: bash -le {0}
- run: |
- conda create -n pip_loose python=${{ env.PYTHON_VERSION }} -y
- conda activate pip_loose
- pip install alphadia==${{ needs.Create_PyPi_Release.outputs.new_version }}
- alphadia -v
- conda deactivate
- conda env remove --name pip_loose -y
- conda clean --all -y
+ publish_on_pypi:
+ uses: MannLabs/alphashared/.github/workflows/publish_on_pypi.yml@v1
+ with:
+ # see the documentation of the workflow for more information on the parameters
+ package_name: alphadia
+ tag_to_release: ${{ inputs.tag_to_release }}
+ secrets:
+ test_pypi_api_token: ${{ secrets.TEST_PYPI_API_TOKEN }}
+ pypi_api_token: ${{ secrets.PYPI_API_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 7da601ba..6d8cadbb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,6 +41,8 @@ MANIFEST
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
# *.spec
+dist_pyinstaller/
+build_pyinstaller/
# Installer logs
pip-log.txt
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index cfefafb7..b64f17a2 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -13,3 +13,5 @@ repos:
- id: ruff-format
- id: ruff
args: [ '--fix' ]
+
+exclude: .bumpversion.cfg
diff --git a/alphadia/__init__.py b/alphadia/__init__.py
index 7cde780c..c2f02751 100644
--- a/alphadia/__init__.py
+++ b/alphadia/__init__.py
@@ -1,3 +1,3 @@
#!python
-__version__ = "1.7.2"
+__version__ = "1.8.0"
diff --git a/alphadia/cli.py b/alphadia/cli.py
index 4b78126c..829e3390 100644
--- a/alphadia/cli.py
+++ b/alphadia/cli.py
@@ -1,8 +1,9 @@
#!python
+"""CLI for alphaDIA.
+
+Ideally the CLI module should have as little logic as possible so that the search behaves the same from the CLI or a jupyter notebook.
+"""
-# native imports
-# alpha family imports
-# third party imports
import argparse
import json
import logging
@@ -12,9 +13,9 @@
import yaml
-# alphadia imports
import alphadia
from alphadia import utils
+from alphadia.exceptions import CustomError
from alphadia.workflow import reporting
logger = logging.getLogger()
@@ -325,7 +326,7 @@ def run(*args, **kwargs):
try:
import matplotlib
- # important to supress matplotlib output
+ # important to suppress matplotlib output
matplotlib.use("Agg")
from alphadia.planning import Plan
@@ -341,8 +342,18 @@ def run(*args, **kwargs):
plan.run()
except Exception as e:
- import traceback
+ if isinstance(e, CustomError):
+ exit_code = 1
+ else:
+ import traceback
+
+ logger.info(traceback.format_exc())
+ exit_code = 127
- logger.info(traceback.format_exc())
logger.error(e)
- sys.exit(1)
+ sys.exit(exit_code)
+
+
+# uncomment for debugging:
+# if __name__ == "__main__":
+# run()
diff --git a/alphadia/constants/default.yaml b/alphadia/constants/default.yaml
index 97168d82..14917790 100644
--- a/alphadia/constants/default.yaml
+++ b/alphadia/constants/default.yaml
@@ -11,6 +11,9 @@ general:
wsl: false
mmap_detector_events: false
use_gpu: true
+ # whether to save the libraries to the output directory
+ save_library: True # input library
+ save_mbr_library: True # output library
library_loading:
rt_heuristic: 180
@@ -41,7 +44,43 @@ library_prediction:
# maximum charge state for predicted fragments
max_fragment_charge: 2
instrument: Lumos
- checkpoint_folder_path: None
+
+ # set path for custom peptdeep model. If set to null, the default model will be used
+ peptdeep_model_path: null
+
+ # set peptdeep model type. Possible values are 'generic', 'phospho', 'digly'. If set to null, the generic model will be used
+ peptdeep_model_type: null
+
+# define custom alphabase modifications not part of unimod or alphabase
+# also used for decoy channels
+custom_modififcations:
+ # Dimethyl @K channel decoy
+ Dimethyl:d12@K:
+ composition: H(-2)2H(8)13C(2)
+
+ # Dimethyl @Any_N-term channel decoy
+ Dimethyl:d12@Any_N-term:
+ composition: H(-2)2H(8)13C(2)
+
+ # Dimethyl @Protein_N-term channel decoy
+ Dimethyl:d12@Protein_N-term:
+ composition: H(-2)2H(8)13C(2)
+
+ # mTRAQ @K channel decoy
+ mTRAQ:d12@K:
+ composition: H(12)C(1)13C(10)15N(2)O(1)
+
+ # mTRAQ @Any_N-term channel decoy
+ mTRAQ:d12@Any_N-term:
+ composition: H(12)C(1)13C(14)15N(2)O(1)
+
+ # mTRAQ @Protein_N-term channel decoy
+ mTRAQ:d12@Protein_N-term:
+ composition: H(12)C(1)13C(14)15N(2)O(1)
+
+ # SILAC heavy @K channel decoy
+ Label:13C(12)@K:
+ composition: C(12)
search:
channel_filter: '0'
@@ -49,10 +88,14 @@ search:
compete_for_fragments: True
target_num_candidates: 2
- target_ms1_tolerance: 15
- target_ms2_tolerance: 15
- target_mobility_tolerance: 0.04
- target_rt_tolerance: 60
+ # target ms1 tolerance in ppm
+ target_ms1_tolerance: 5
+ # target ms2 tolerance in ppm
+ target_ms2_tolerance: 10
+ # target ion mobility tolerance in 1/K_0
+ target_mobility_tolerance: 0.0 # default is to optimize automatically
+ # target retention time tolerance in seconds if > 1, or a proportion of the total gradient length if < 1
+ target_rt_tolerance: 0.0 # default is to optimize automatically
quant_window: 3
quant_all: True
@@ -62,17 +105,20 @@ search_advanced:
calibration:
- # minimum number of times (epochs) the updated calibration target has to been passed
- min_epochs: 3
-
# Number of precursors searched and scored per batch
batch_size: 8000
- # recalibration target for the first epoch. For subsequent epochs, the target will increase by this amount.
- recalibration_target: 200
+ # minimum number of precursors to be found before search parameter optimization begins
+ optimization_lock_target: 200
- # TODO: remove as not relevant anymore
- max_epochs: 20
+ # the maximum number of steps that a given optimizer is permitted to take
+ max_steps: 20
+
+ # the minimum number of steps that a given optimizer must take before it can be said to have converged
+ min_steps: 2
+
+ # the maximum number of times an automatic optimizer can be skipped before it is considered to have converged
+ max_skips: 1
# TODO: remove this parameter
final_full_calibration: False
@@ -80,8 +126,14 @@ calibration:
# TODO: remove this parameter
norm_rt_mode: 'linear'
+ # the maximum number of fragments with correlation scores exceeding correlation_threshold to use for calibrating fragment mz (i.e. ms2)
+ max_fragments: 5000
+
+ # the correlation threshold for fragments used to calibrate fragment mz (i.e. ms2)
+ min_correlation: 0.7
+
search_initial:
- # Number of peak groups identified in the convolution score to classify with target decoy comeptition
+ # Number of peak groups identified in the convolution score to classify with target decoy competition
initial_num_candidates: 1
# initial ms1 tolerance in ppm
@@ -91,10 +143,10 @@ search_initial:
initial_ms2_tolerance: 30
# initial ion mobility tolerance in 1/K_0
- initial_mobility_tolerance: 0.08
+ initial_mobility_tolerance: 0.1
- # initial retention time tolerance in seconds
- initial_rt_tolerance: 240
+ # initial retention time tolerance in seconds if > 1, or a proportion of the total gradient length if < 1
+ initial_rt_tolerance: 0.5
selection_config:
peak_len_rt: 10.
@@ -127,6 +179,38 @@ scoring_config:
precursor_mz_tolerance: 10
fragment_mz_tolerance: 15
+# perform non-isobaric multiplexing of any input library
+library_multiplexing:
+ # if true, the library is multiplexed
+ enabled: False
+
+ # if the input library already contains multiplexed channels, the input channel has to be specified.
+ input_channel: 0
+
+ # define channels by their name and how modifications should be translated from the input library to the multiplexed library
+ # channels can be either a number or a string
+ # for every channel, the library gets copied and the modifications are translated according to the mapping
+ # the following example shows how to multiplex mTRAQ to three sample channels and a decoy channel
+ multiplex_mapping: {}
+
+ #0:
+ # mTRAQ@K: mTRAQ@K
+ # mTRAQ@Any_N-term: mTRAQ@Any_N-term
+
+ #4:
+ # mTRAQ@K: mTRAQ:13C(3)15N(1)@K
+ # mTRAQ@Any_N-term: mTRAQ:13C(3)15N(1)@Any_N-term
+
+ #8:
+ # mTRAQ@K: mTRAQ:13C(6)15N(2)@K
+ # mTRAQ@Any_N-term: mTRAQ:13C(6)15N(2)@Any_N-term
+
+ #12:
+ # mTRAQ@K: mTRAQ:d12@K
+ # mTRAQ@Any_N-term: mTRAQ:d12@Any_N-term
+
+
+
multiplexing:
enabled: False
target_channels: '4,8'
@@ -153,8 +237,71 @@ search_output:
# can be either "parquet" or "tsv"
file_format: "tsv"
+# Configuration for the optimization of search parameters. These parameters should not normally be adjusted and are for the use of experienced users only.
+optimization:
+ # The order in which to perform optimization. Should be a list of lists of parameter names
+ # Example:
+ # order_of_optimization:
+ # - - "rt_error"
+ # - - "ms2_error"
+ # - - "ms1_error"
+ # - - "mobility_error"
+ # The above means that first rt_error is optimized, then ms2_error, then ms1_error, and finally mobility_error. (Other examples are shown in Python list format rather than YAML format to save space.)
+ # Example: [['ms1_error', 'ms2_error', 'rt_error', 'mobility_error']] means that all parameters are optimized simultaneously.
+ # Example: [["ms2_error"], ["rt_error"], ["ms1_error"], ["mobility_error"]] means that the parameters are optimized sequentially in the order given.
+ # Example: [["rt_error"], ["ms1_error", "ms2_error"]] means that first rt_error is optimized, then ms1_error and ms2_error are optimized simultaneously, and mobility_error is not optimized at all.
+ # If order_of_optimization is null, first all targeted optimizers run simultaneously, then any remaining automatic optimizers run sequentially in the order [["ms2_error"], ["rt_error"], ["ms1_error"], ["mobility_error"]]
+ order_of_optimization: null
+
+ # Parameters for the update rule for each parameter:
+ # - update_percentile_range: the percentile interval to use (as a decimal)
+ # - update_factor: the factor by which to multiply the result from the percentile interval to get the new parameter value for the next round of search
+ # - try_narrower_parameters: if True, the optimization will try narrower parameters until a substantial (as determined by maximal_decrease) decrease in the feature used for optimization is observed.
+ # - maximal_decrease: the maximal decrease of the parameter value before stopping optimization (only relevant if favour_narrower_parameter is True).
+ # For example, a value of 0.2 indicates up to 20% decrease from the previous parameter is permissible.
+ # - favour_narrower_optimum: if True, the optimization will not take the value that maximizes the feature used for optimization, but instead the smallest value compatible with the maximum_decrease_from_maximum value.
+ # This setting can be useful for optimizing parameters for which many parameter values have similar feature values and therefore favouring narrower parameters helps to overcome noise.
+ # - maximum_decrease_from_maximum: the maximum proportional decrease from the maximum value of the parameter that the designated optimum should have (only relevant if favour_narrower_optimum is True).
+ # For example, a value of 0.1 indicates that the optimum should no more than 10% less than the maximum value.
+ ms2_error:
+ targeted_update_percentile_range: 0.95
+ targeted_update_factor: 1.0
+ automatic_update_percentile_range: 0.99
+ automatic_update_factor: 1.1
+ try_narrower_values: True
+ maximal_decrease: 0.5
+ favour_narrower_optimum: False
+ maximum_decrease_from_maximum: 0.1
+ ms1_error:
+ targeted_update_percentile_range: 0.95
+ targeted_update_factor: 1.0
+ automatic_update_percentile_range: 0.99
+ automatic_update_factor: 1.1
+ try_narrower_values: False
+ maximal_decrease: 0.2
+ favour_narrower_optimum: False
+ maximum_decrease_from_maximum: 0.1
+ mobility_error:
+ targeted_update_percentile_range: 0.95
+ targeted_update_factor: 1.0
+ automatic_update_percentile_range: 0.99
+ automatic_update_factor: 1.1
+ try_narrower_values: False
+ maximal_decrease: 0.2
+ favour_narrower_optimum: False
+ maximum_decrease_from_maximum: 0.1
+ rt_error:
+ targeted_update_percentile_range: 0.95
+ targeted_update_factor: 1.0
+ automatic_update_percentile_range: 0.99
+ automatic_update_factor: 1.1
+ try_narrower_values: True
+ maximal_decrease: 0.2
+ favour_narrower_optimum: True
+ maximum_decrease_from_maximum: 0.1
+
# configuration for the optimization manager
-# initial parameters, will nbe optimized
+# initial parameters, will be optimized
optimization_manager:
fwhm_rt: 5
fwhm_mobility: 0.01
diff --git a/alphadia/data/bruker.py b/alphadia/data/bruker.py
index 73f0f629..3b83f2fa 100644
--- a/alphadia/data/bruker.py
+++ b/alphadia/data/bruker.py
@@ -12,6 +12,7 @@
from alphadia import utils
from alphadia.data.stats import log_stats
+from alphadia.exceptions import NotDiaDataError
logger = logging.getLogger()
@@ -62,10 +63,7 @@ def __init__(
try:
cycle_shape = self._cycle.shape[0]
except AttributeError as e:
- logger.error(
- "Could not find cycle shape. Please check if this is a valid DIA data set."
- )
- raise e
+ raise NotDiaDataError() from e
else:
if cycle_shape != 1:
msg = f"Unexpected cycle shape: {cycle_shape} (expected: 1). "
@@ -107,7 +105,10 @@ def transpose(self):
logger.info("Transposing detector events")
push_indices, tof_indptr, intensity_values = transpose(
- self._tof_indices, self._push_indptr, self._intensity_values
+ self._tof_indices,
+ self._push_indptr,
+ len(self._mz_values),
+ self._intensity_values,
)
logger.info("Finished transposing data")
@@ -861,7 +862,7 @@ def build_chunks(number_of_elements, num_chunks):
@nb.njit
-def transpose(tof_indices, push_indptr, values):
+def transpose(tof_indices, push_indptr, n_tof_indices, values):
"""
The default alphatims data format consists of a sparse matrix where pushes are the rows, tof indices (discrete mz values) the columns and intensities the values.
A lookup starts with a given push index p which points to the row. The start and stop indices of the row are accessed from dia_data.push_indptr[p] and dia_data.push_indptr[p+1].
@@ -879,6 +880,9 @@ def transpose(tof_indices, push_indptr, values):
push_indptr : np.ndarray
start stop values for each row (n_rows +1)
+ n_tof_indices : int
+ number of tof indices which is usually equal to len(dia_data.mz_values)
+
values : np.ndarray
values (n_values)
@@ -898,28 +902,25 @@ def transpose(tof_indices, push_indptr, values):
values (n_values)
"""
- # this is one less than the old col count or the new row count
- max_tof_index = tof_indices.max()
-
- tof_indcount = np.zeros((max_tof_index + 1), dtype=np.uint32)
+ tof_indcount = np.zeros((n_tof_indices), dtype=np.uint32)
# get new row counts
for v in tof_indices:
tof_indcount[v] += 1
# get new indptr
- tof_indptr = np.zeros((max_tof_index + 1 + 1), dtype=np.int64)
+ tof_indptr = np.zeros((n_tof_indices + 1), dtype=np.int64)
- for i in range(max_tof_index + 1):
+ for i in range(n_tof_indices):
tof_indptr[i + 1] = tof_indptr[i] + tof_indcount[i]
- tof_indcount = np.zeros((max_tof_index + 1), dtype=np.uint32)
+ tof_indcount = np.zeros((n_tof_indices), dtype=np.uint32)
# get new values
push_indices = np.zeros((len(tof_indices)), dtype=np.uint32)
new_values = np.zeros_like(values)
- chunks = build_chunks(max_tof_index + 1, 20)
+ chunks = build_chunks(n_tof_indices, 20)
with nb.objmode:
alphatims.utils.set_threads(20)
diff --git a/alphadia/exceptions.py b/alphadia/exceptions.py
new file mode 100644
index 00000000..94efbc7e
--- /dev/null
+++ b/alphadia/exceptions.py
@@ -0,0 +1,61 @@
+"""Module containing custom exceptions."""
+
+
+class CustomError(Exception):
+ """Custom alphaDIA error class."""
+
+ _error_code = None
+ _msg = None
+ _detail_msg = ""
+
+ @property
+ def error_code(self):
+ return self._error_code
+
+ @property
+ def msg(self):
+ return self._msg
+
+ @property
+ def detail_msg(self):
+ return self._detail_msg
+
+
+class BusinessError(CustomError):
+ """Custom error class for 'business' errors.
+
+ A 'business' error is an error that is caused by the input (data, configuration, ...) and not by a
+ malfunction in alphaDIA.
+ """
+
+
+class NoPsmFoundError(BusinessError):
+ """Raise when no PSMs are found in the search results."""
+
+ _error_code = "NO_PSM_FOUND"
+
+ _msg = "No psm files accumulated, can't continue"
+
+
+class NoOptimizationLockTargetError(BusinessError):
+ """Raise when the optimization lock target is not found."""
+
+ _error_code = "NO_OPTIMIZATION_LOCK_TARGET"
+
+ _msg = "Searched all data without finding optimization lock target"
+
+ _detail_msg = """Search for raw file failed as not enough precursors were found for calibration and optimization.
+ This can have the following reasons:
+ 1. The sample was empty and therefore no precursors were found.
+ 2. The sample contains only very few precursors.
+ For small libraries, try to set recalibration_target to a lower value.
+ For large libraries, try to reduce the library size and reduce the initial MS1 and MS2 tolerance.
+ 3. There was a fundamental issue with search parameters."""
+
+
+class NotDiaDataError(BusinessError):
+ """Raise when data is not from DIA."""
+
+ _error_code = "NOT_DIA_DATA"
+
+ _msg = "Could not find cycle shape. Please check if this is a valid DIA data set."
diff --git a/alphadia/libtransform.py b/alphadia/libtransform.py
index 03e78584..2dbd5021 100644
--- a/alphadia/libtransform.py
+++ b/alphadia/libtransform.py
@@ -2,11 +2,13 @@
import logging
import os
import typing
+from functools import reduce
from pathlib import Path
# third party imports
import numpy as np
import pandas as pd
+from alphabase.constants.modification import MOD_DF
# alpha family imports
from alphabase.peptide import fragment
@@ -252,7 +254,8 @@ def __init__(
fragment_mz: list[int] | None = None,
nce: int = 25,
instrument: str = "Lumos",
- checkpoint_folder_path: str | None = None,
+ peptdeep_model_path: str | None = None,
+ peptdeep_model_type: str | None = None,
fragment_types: list[str] | None = None,
max_fragment_charge: int = 2,
) -> None:
@@ -276,9 +279,14 @@ def __init__(
instrument : str, optional
Instrument type for prediction. Default is "Lumos". Must be a valid PeptDeep instrument.
- checkpoint_folder_path : str, optional
+ peptdeep_model_path : str, optional
Path to a folder containing PeptDeep models. If not provided, the default models will be used.
+ peptdeep_model_type : str, optional
+ Use other peptdeep models provided by the peptdeep model manager.
+ Default is None, which means the peptdeep default model ("generic") is being used.
+ Possible values are ['generic','phospho','digly']
+
fragment_types : List[str], optional
Fragment types to predict. Default is ["b", "y"].
@@ -295,7 +303,8 @@ def __init__(
self.nce = nce
self.instrument = instrument
self.mp_process_num = mp_process_num
- self.checkpoint_folder_path = checkpoint_folder_path
+ self.peptdeep_model_path = peptdeep_model_path
+ self.peptdeep_model_type = peptdeep_model_type
self.fragment_types = fragment_types
self.max_fragment_charge = max_fragment_charge
@@ -313,12 +322,23 @@ def forward(self, input: SpecLibBase) -> SpecLibBase:
device = utils.get_torch_device(self.use_gpu)
model_mgr = ModelManager(device=device)
- if self.checkpoint_folder_path is not None:
- logging.info(f"Loading PeptDeep models from {self.checkpoint_folder_path}")
+
+ # will load other model than default generic
+ if self.peptdeep_model_type:
+ logging.info(f"Loading PeptDeep models of type {self.peptdeep_model_type}")
+ model_mgr.load_installed_models(self.peptdeep_model_type)
+
+ if self.peptdeep_model_path and self.peptdeep_model_path != "":
+ if not os.path.exists(self.peptdeep_model_path):
+ raise ValueError(
+ f"PeptDeep model checkpoint folder {self.peptdeep_model_path} does not exist"
+ )
+
+ logging.info(f"Loading PeptDeep models from {self.peptdeep_model_path}")
model_mgr.load_external_models(
- ms2_model_file=os.path.join(self.checkpoint_folder_path, "ms2.pth"),
- rt_model_file=os.path.join(self.checkpoint_folder_path, "rt.pth"),
- ccs_model_file=os.path.join(self.checkpoint_folder_path, "ccs.pth"),
+ ms2_model_file=os.path.join(self.peptdeep_model_path, "ms2.pth"),
+ rt_model_file=os.path.join(self.peptdeep_model_path, "rt.pth"),
+ ccs_model_file=os.path.join(self.peptdeep_model_path, "ccs.pth"),
)
model_mgr.nce = self.nce
@@ -597,6 +617,72 @@ def forward(self, input: SpecLibBase) -> SpecLibBase:
return input
+class MultiplexLibrary(ProcessingStep):
+ def __init__(self, multiplex_mapping: dict, input_channel: str | int | None = None):
+ """Initialize the MultiplexLibrary step."""
+
+ self._multiplex_mapping = multiplex_mapping
+ self._input_channel = input_channel
+
+ def validate(self, input: str) -> bool:
+ """Validate the input object. It is expected that the input is a path to a file which exists."""
+ valid = True
+ valid &= isinstance(input, SpecLibBase)
+
+ # check if all modifications are valid
+ for _, channel_multiplex_mapping in self._multiplex_mapping.items():
+ for key, value in channel_multiplex_mapping.items():
+ for mod in [key, value]:
+ if mod not in MOD_DF.index:
+ logger.error(f"Modification {mod} not found in input library")
+ valid = False
+
+ if "channel" in input.precursor_df.columns:
+ channel_unique = input.precursor_df["channel"].unique()
+ if self._input_channel not in channel_unique:
+ logger.error(
+ f"Input library does not contain channel {self._input_channel}"
+ )
+ valid = False
+
+ if (len(channel_unique) > 1) and (self._input_channel is None):
+ logger.error(
+ f"Input library contains multiple channels {channel_unique}. Please specify a channel."
+ )
+ valid = False
+
+ return valid
+
+ def forward(self, input: SpecLibBase) -> SpecLibBase:
+ """Apply the MultiplexLibrary step to the input object."""
+
+ if "channel" in input.precursor_df.columns:
+ input.precursor_df = input.precursor_df[
+ input.precursor_df["channel"] == self._input_channel
+ ]
+
+ channel_lib_list = []
+ for channel, channel_mod_translations in self._multiplex_mapping.items():
+ logger.info(f"Multiplexing library for channel {channel}")
+ channel_lib = input.copy()
+ for original_mod, channel_mod in channel_mod_translations.items():
+ channel_lib._precursor_df["mods"] = channel_lib._precursor_df[
+ "mods"
+ ].str.replace(original_mod, channel_mod)
+ channel_lib._precursor_df["channel"] = channel
+
+ channel_lib.calc_fragment_mz_df()
+ channel_lib_list.append(channel_lib)
+
+ def apply_func(x, y):
+ x.append(y)
+ return x
+
+ speclib = reduce(lambda x, y: apply_func(x, y), channel_lib_list)
+ speclib.remove_unused_fragments()
+ return speclib
+
+
class FlattenLibrary(ProcessingStep):
def __init__(
self, top_k_fragments: int = 12, min_fragment_intensity: float = 0.01
diff --git a/alphadia/outputaccumulator.py b/alphadia/outputaccumulator.py
index 4d0ef135..16b8b808 100644
--- a/alphadia/outputaccumulator.py
+++ b/alphadia/outputaccumulator.py
@@ -137,15 +137,14 @@ def parse_output_folder(
psm_df["raw_name"] = foldername
# remove decoy precursors
- psm_df = psm_df[psm_df["decoy"] == 0]
+ # assert that decoy is int
+ psm_df["decoy"] = psm_df["decoy"].astype(int)
+ psm_df = psm_df[psm_df["decoy"] == 0].reset_index(drop=True)
self._precursor_df = pd.DataFrame()
for col in psm_df.columns:
self._precursor_df[col] = psm_df[col]
- self._precursor_df["decoy"] = self._precursor_df["decoy"].astype(int)
- self._precursor_df = psm_df[psm_df["decoy"] == 0].reset_index(drop=True)
-
# self._precursor_df.set_index('precursor_idx', inplace=True)
# Change the data type of the mods column to string
self._precursor_df["mods"] = self._precursor_df["mods"].astype(str)
diff --git a/alphadia/outputtransform.py b/alphadia/outputtransform.py
index 33714410..b4184e60 100644
--- a/alphadia/outputtransform.py
+++ b/alphadia/outputtransform.py
@@ -18,21 +18,17 @@
from alphadia import fdr, grouping, libtransform, utils
from alphadia.consensus.utils import read_df, write_df
+from alphadia.exceptions import NoPsmFoundError
from alphadia.outputaccumulator import (
AccumulationBroadcaster,
TransferLearningAccumulator,
)
from alphadia.transferlearning.train import FinetuneManager
+from alphadia.workflow import manager, peptidecentric
logger = logging.getLogger()
-class OutputGenerationError(Exception):
- """Raised when an error occurs during output generation"""
-
- pass
-
-
def get_frag_df_generator(folder_list: list[str]):
"""Return a generator that yields a tuple of (raw_name, frag_df)
@@ -301,10 +297,12 @@ class SearchPlanOutput:
PSM_INPUT = "psm"
PRECURSOR_OUTPUT = "precursors"
STAT_OUTPUT = "stat"
+ INTERNAL_OUTPUT = "internal"
PG_OUTPUT = "protein_groups"
LIBRARY_OUTPUT = "speclib.mbr"
TRANSFER_OUTPUT = "speclib.transfer"
TRANSFER_MODEL = "peptdeep.transfer"
+ TRANSFER_STATS_OUTPUT = "stats.transfer"
def __init__(self, config: dict, output_folder: str):
"""Combine individual searches into and build combined outputs
@@ -369,16 +367,28 @@ def build(
folder_list, save=False, base_spec_lib=base_spec_lib
)
_ = self.build_stat_df(folder_list, psm_df=psm_df, save=True)
+ _ = self.build_internal_df(folder_list, save=True)
_ = self.build_lfq_tables(folder_list, psm_df=psm_df, save=True)
- _ = self.build_library(base_spec_lib, psm_df=psm_df, save=True)
+ _ = self.build_library(
+ base_spec_lib,
+ psm_df=psm_df,
+ )
if self.config["transfer_library"]["enabled"]:
_ = self.build_transfer_library(folder_list, save=True)
if self.config["transfer_learning"]["enabled"]:
- _ = self.build_transfer_model()
+ _ = self.build_transfer_model(save=True)
+
+ def build_transfer_model(self, save=True):
+ """
+ Finetune PeptDeep models using the transfer library
- def build_transfer_model(self):
+ Parameters
+ ----------
+ save : bool, optional
+ Whether to save the statistics of the transfer learning on disk, by default True
+ """
logger.progress("Train PeptDeep Models")
transfer_lib_path = os.path.join(
@@ -397,16 +407,37 @@ def build_transfer_model(self):
device = utils.get_torch_device(self.config["general"]["use_gpu"])
tune_mgr = FinetuneManager(
- device=device, settings=self.config["transfer_learning"]
+ device=device,
+ lr_patience=self.config["transfer_learning"]["lr_patience"],
+ test_interval=self.config["transfer_learning"]["test_interval"],
+ train_fraction=self.config["transfer_learning"]["train_fraction"],
+ validation_fraction=self.config["transfer_learning"]["validation_fraction"],
+ test_fraction=self.config["transfer_learning"]["test_fraction"],
+ epochs=self.config["transfer_learning"]["epochs"],
+ warmup_epochs=self.config["transfer_learning"]["warmup_epochs"],
+ batch_size=self.config["transfer_learning"]["batch_size"],
+ max_lr=self.config["transfer_learning"]["max_lr"],
+ nce=self.config["transfer_learning"]["nce"],
+ instrument=self.config["transfer_learning"]["instrument"],
)
- tune_mgr.finetune_rt(transfer_lib.precursor_df)
- tune_mgr.finetune_charge(transfer_lib.precursor_df)
- tune_mgr.finetune_ms2(
+ rt_stats = tune_mgr.finetune_rt(transfer_lib.precursor_df)
+ charge_stats = tune_mgr.finetune_charge(transfer_lib.precursor_df)
+ ms2_stats = tune_mgr.finetune_ms2(
transfer_lib.precursor_df.copy(), transfer_lib.fragment_intensity_df.copy()
)
tune_mgr.save_models(os.path.join(self.output_folder, self.TRANSFER_MODEL))
+ combined_stats = pd.concat([rt_stats, charge_stats, ms2_stats])
+
+ if save:
+ logger.info("Writing transfer learning stats output to disk")
+ write_df(
+ combined_stats,
+ os.path.join(self.output_folder, self.TRANSFER_STATS_OUTPUT),
+ file_format="tsv",
+ )
+
def build_transfer_library(
self,
folder_list: list[str],
@@ -524,8 +555,7 @@ def build_precursor_table(
logger.warning(e)
if len(psm_df_list) == 0:
- logger.error("No psm files accumulated, can't continue")
- raise OutputGenerationError("No psm files accumulated, can't continue")
+ raise NoPsmFoundError()
logger.info("Building combined output")
psm_df = pd.concat(psm_df_list)
@@ -661,7 +691,10 @@ def build_stat_df(
raw_name = os.path.basename(folder)
stat_df_list.append(
_build_run_stat_df(
- raw_name, psm_df[psm_df["run"] == raw_name], all_channels
+ folder,
+ raw_name,
+ psm_df[psm_df["run"] == raw_name],
+ all_channels,
)
)
@@ -677,6 +710,50 @@ def build_stat_df(
return stat_df
+ def build_internal_df(
+ self,
+ folder_list: list[str],
+ save: bool = True,
+ ):
+ """Build internal data table from a list of seach outputs
+
+ Parameters
+ ----------
+
+ folder_list: List[str]
+ List of folders containing the search outputs
+
+ save: bool
+ Save the precursor table to disk
+
+ Returns
+ -------
+
+ stat_df: pd.DataFrame
+ Precursor table
+ """
+ logger.progress("Building internal statistics")
+
+ internal_df_list = []
+ for folder in folder_list:
+ internal_df_list.append(
+ _build_run_internal_df(
+ folder,
+ )
+ )
+
+ internal_df = pd.concat(internal_df_list)
+
+ if save:
+ logger.info("Writing internal output to disk")
+ write_df(
+ internal_df,
+ os.path.join(self.output_folder, self.INTERNAL_OUTPUT),
+ file_format="tsv",
+ )
+
+ return internal_df
+
def build_lfq_tables(
self,
folder_list: list[str],
@@ -777,7 +854,6 @@ def build_library(
self,
base_spec_lib: base.SpecLibBase,
psm_df: pd.DataFrame | None = None,
- save: bool = True,
):
"""Build spectral library
@@ -790,9 +866,6 @@ def build_library(
psm_df: Union[pd.DataFrame, None]
Combined precursor table. If None, the precursor table is loaded from disk.
- save: bool
- Save the generated spectral library to disk
-
"""
logger.progress("Building spectral library")
@@ -810,15 +883,11 @@ def build_library(
precursor_number = len(mbr_spec_lib.precursor_df)
protein_number = mbr_spec_lib.precursor_df.proteins.nunique()
- # use comma to separate thousands
logger.info(
f"MBR spectral library contains {precursor_number:,} precursors, {protein_number:,} proteins"
)
- logger.info("Writing MBR spectral library to disk")
- mbr_spec_lib.save_hdf(os.path.join(self.output_folder, "speclib.mbr.hdf"))
-
- if save:
+ if self.config["general"]["save_mbr_library"]:
logger.info("Writing MBR spectral library to disk")
mbr_spec_lib.save_hdf(os.path.join(self.output_folder, "speclib.mbr.hdf"))
@@ -826,13 +895,19 @@ def build_library(
def _build_run_stat_df(
- raw_name: str, run_df: pd.DataFrame, channels: list[int] | None = None
+ folder: str,
+ raw_name: str,
+ run_df: pd.DataFrame,
+ channels: list[int] | None = None,
):
"""Build stat dataframe for a single run.
Parameters
----------
+ folder: str
+ Directory containing the raw file and the managers
+
raw_name: str
Name of the raw file
@@ -848,6 +923,9 @@ def _build_run_stat_df(
Dataframe containing the statistics
"""
+ optimization_manager_path = os.path.join(
+ folder, peptidecentric.PeptideCentricWorkflow.OPTIMIZATION_MANAGER_PATH
+ )
if channels is None:
channels = [0]
@@ -872,11 +950,66 @@ def _build_run_stat_df(
if "mobility_fwhm" in channel_df.columns:
base_dict["fwhm_mobility"] = np.mean(channel_df["mobility_fwhm"])
+ if os.path.exists(optimization_manager_path):
+ optimization_manager = manager.OptimizationManager(
+ path=optimization_manager_path
+ )
+
+ base_dict["ms2_error"] = optimization_manager.ms2_error
+ base_dict["ms1_error"] = optimization_manager.ms1_error
+ base_dict["rt_error"] = optimization_manager.rt_error
+ base_dict["mobility_error"] = optimization_manager.mobility_error
+
+ else:
+ logger.warning(f"Error reading optimization manager for {raw_name}")
+ base_dict["ms2_error"] = np.nan
+ base_dict["ms1_error"] = np.nan
+ base_dict["rt_error"] = np.nan
+ base_dict["mobility_error"] = np.nan
+
out_df.append(base_dict)
return pd.DataFrame(out_df)
+def _build_run_internal_df(
+ folder_path: str,
+):
+ """Build stat dataframe for a single run.
+
+ Parameters
+ ----------
+
+ folder_path: str
+ Path (from the base directory of the output_folder attribute of the Plan class) to the directory containing the raw file and the managers
+
+
+ Returns
+ -------
+ pd.DataFrame
+ Dataframe containing the statistics
+
+ """
+ timing_manager_path = os.path.join(
+ folder_path, peptidecentric.PeptideCentricWorkflow.TIMING_MANAGER_PATH
+ )
+ raw_name = os.path.basename(folder_path)
+
+ internal_dict = {
+ "run": [raw_name],
+ }
+
+ if os.path.exists(timing_manager_path):
+ timing_manager = manager.TimingManager(path=timing_manager_path)
+ for key in timing_manager.timings:
+ internal_dict[f"duration_{key}"] = [timing_manager.timings[key]["duration"]]
+
+ else:
+ logger.warning(f"Error reading timing manager for {raw_name}")
+
+ return pd.DataFrame(internal_dict)
+
+
def perform_protein_fdr(psm_df):
"""Perform protein FDR on PSM dataframe"""
diff --git a/alphadia/planning.py b/alphadia/planning.py
index 50f81b46..ae4fd08d 100644
--- a/alphadia/planning.py
+++ b/alphadia/planning.py
@@ -23,27 +23,13 @@
# alphadia imports
from alphadia import libtransform, outputtransform
+from alphadia.exceptions import CustomError
from alphadia.workflow import peptidecentric, reporting
+from alphadia.workflow.base import WorkflowBase
from alphadia.workflow.config import Config
logger = logging.getLogger()
-modification.add_new_modifications(
- {
- "Dimethyl:d12@Protein N-term": {"composition": "H(-2)2H(8)13C(2)"},
- "Dimethyl:d12@Any N-term": {
- "composition": "H(-2)2H(8)13C(2)",
- },
- "Dimethyl:d12@R": {
- "composition": "H(-2)2H(8)13C(2)",
- },
- "Dimethyl:d12@K": {
- "composition": "H(-2)2H(8)13C(2)",
- },
- "Label:13C(12)@K": {"composition": "C(12)"},
- }
-)
-
class Plan:
def __init__(
@@ -134,6 +120,7 @@ def __init__(
level_code = logging.getLevelName(level_to_set)
logger.setLevel(level_code)
+ self.init_alphabase()
self.load_library()
torch.set_num_threads(self.config["general"]["thread_count"])
@@ -174,6 +161,16 @@ def log_environment(self):
logger.progress(f"{'directlfq':<15} : {directlfq.__version__}")
logger.progress("===================================================")
+ def init_alphabase(self):
+ """Init alphabase by registering custom modifications."""
+
+ # register custom modifications
+ if "custom_modififcations" in self.config:
+ n_modifications = len(self.config["custom_modififcations"])
+ logging.info(f"Registering {n_modifications} custom modifications")
+
+ modification.add_new_modifications(self.config["custom_modififcations"])
+
def load_library(self):
"""
Load or build spectral library as configured.
@@ -226,8 +223,11 @@ def _parse_modifications(mod_str: str) -> list[str]:
nce=self.config["library_prediction"]["nce"],
instrument=self.config["library_prediction"]["instrument"],
mp_process_num=self.config["general"]["thread_count"],
- checkpoint_folder_path=self.config["library_prediction"][
- "checkpoint_folder_path"
+ peptdeep_model_path=self.config["library_prediction"][
+ "peptdeep_model_path"
+ ],
+ peptdeep_model_type=self.config["library_prediction"][
+ "peptdeep_model_type"
],
fragment_types=self.config["library_prediction"][
"fragment_types"
@@ -252,6 +252,15 @@ def _parse_modifications(mod_str: str) -> list[str]:
)
spectral_library = harmonize_pipeline(spectral_library)
+ if self.config["library_multiplexing"]["enabled"]:
+ multiplexing = libtransform.MultiplexLibrary(
+ multiplex_mapping=self.config["library_multiplexing"][
+ "multiplex_mapping"
+ ],
+ input_channel=self.config["library_multiplexing"]["input_channel"],
+ )
+ spectral_library = multiplexing(spectral_library)
+
library_path = os.path.join(self.output_folder, "speclib.hdf")
logger.info(f"Saving library to {library_path}")
spectral_library.save_hdf(library_path)
@@ -324,9 +333,16 @@ def run(
logger.info(f"No existing quantification found for {raw_name}")
workflow.load(dia_path, speclib)
- workflow.calibration()
+ workflow.timing_manager.set_start_time("optimization")
+ workflow.search_parameter_optimization()
+ workflow.timing_manager.set_end_time("optimization")
+
+ workflow.timing_manager.set_start_time("extraction")
psm_df, frag_df = workflow.extraction()
+ workflow.timing_manager.set_end_time("extraction")
+ workflow.timing_manager.save()
+
psm_df = psm_df[psm_df["qval"] <= self.config["fdr"]["fdr"]]
if self.config["multiplexing"]["enabled"]:
@@ -340,46 +356,59 @@ def run(
psm_df.to_parquet(psm_location, index=False)
frag_df.to_parquet(frag_location, index=False)
- workflow.reporter.log_string(f"Finished workflow for {raw_name}")
- workflow.reporter.context.__exit__(None, None, None)
- del workflow
-
- except peptidecentric.CalibrationError:
- # get full traceback
- logger.error(
- f"Search for {raw_name} failed as not enough precursors were found for calibration"
- )
- logger.error("This can have the following reasons:")
- logger.error(
- " 1. The sample was empty and therefore nor precursors were found"
- )
- logger.error(" 2. The sample contains only very few precursors.")
- logger.error(
- " For small libraries, try to set recalibration_target to a lower value"
- )
- logger.error(
- " For large libraries, try to reduce the library size and reduce the calibration MS1 and MS2 tolerance"
- )
- logger.error(
- " 3. There was a fundamental issue with search parameters"
- )
+ except CustomError as e:
+ _log_exception_event(e, raw_name, workflow)
continue
+
except Exception as e:
- logger.error(
- f"Search for {raw_name} failed with error {e}", exc_info=True
- )
+ _log_exception_event(e, raw_name, workflow)
raise e
- base_spec_lib = SpecLibBase()
- base_spec_lib.load_hdf(
- os.path.join(self.output_folder, "speclib.hdf"), load_mod_seq=True
- )
+ finally:
+ if workflow.reporter:
+ workflow.reporter.log_string(f"Finished workflow for {raw_name}")
+ workflow.reporter.context.__exit__(None, None, None)
+ del workflow
+
+ try:
+ base_spec_lib = SpecLibBase()
+ base_spec_lib.load_hdf(
+ os.path.join(self.output_folder, "speclib.hdf"), load_mod_seq=True
+ )
- output = outputtransform.SearchPlanOutput(self.config, self.output_folder)
- output.build(workflow_folder_list, base_spec_lib)
+ output = outputtransform.SearchPlanOutput(self.config, self.output_folder)
+ output.build(workflow_folder_list, base_spec_lib)
+ except Exception as e:
+ _log_exception_event(e)
+ raise e
+ finally:
+ self.clean()
logger.progress("=================== Search Finished ===================")
def clean(self):
- if not self.config["library_loading"]["save_hdf"]:
+ if not self.config["general"]["save_library"]:
os.remove(os.path.join(self.output_folder, "speclib.hdf"))
+
+
+def _log_exception_event(
+ e: Exception, raw_name: str | None = None, workflow: WorkflowBase | None = None
+) -> None:
+ """Log exception and emit event to reporter if available."""
+
+ prefix = (
+ "Error:" if raw_name is None else f"Search for {raw_name} failed with error:"
+ )
+
+ if isinstance(e, CustomError):
+ logger.error(f"{prefix} {e.error_code} {e.msg}")
+ logger.error(e.detail_msg)
+ else:
+ logger.error(f"{prefix} {e}", exc_info=True)
+
+ if workflow is not None and workflow.reporter:
+ workflow.reporter.log_string(
+ value=str(e),
+ verbosity="error",
+ )
+ workflow.reporter.log_event(name="exception", value=str(e), exception=e)
diff --git a/alphadia/plexscoring.py b/alphadia/plexscoring.py
index af2e16f4..0a33c8fa 100644
--- a/alphadia/plexscoring.py
+++ b/alphadia/plexscoring.py
@@ -1722,8 +1722,10 @@ def collect_candidates(
"frame_center",
"frame_start",
"frame_stop",
- "score",
]
+
+ candidate_df_columns += ["score"] if "score" in candidates_df.columns else []
+
df = utils.merge_missing_columns(
df,
candidates_df,
diff --git a/alphadia/transferlearning/metrics.py b/alphadia/transferlearning/metrics.py
index c10e7454..81f2e5cf 100644
--- a/alphadia/transferlearning/metrics.py
+++ b/alphadia/transferlearning/metrics.py
@@ -392,8 +392,10 @@ def calculate_test_metric(
for j in range(n_classes):
confusion_matrix[i, j] = np.sum((predictions == i) & (targets == j))
- precision = np.diag(confusion_matrix) / np.sum(confusion_matrix, axis=0)
- recall = np.diag(confusion_matrix) / np.sum(confusion_matrix, axis=1)
+ precision = np.diag(confusion_matrix) / (
+ np.sum(confusion_matrix, axis=0) + 1e-6
+ )
+ recall = np.diag(confusion_matrix) / (np.sum(confusion_matrix, axis=1) + 1e-6)
new_stats = pd.DataFrame(
np.array([np.mean(precision), np.mean(recall)]).reshape(1, 2),
diff --git a/alphadia/transferlearning/train.py b/alphadia/transferlearning/train.py
index 69eaeed7..b9aa2426 100644
--- a/alphadia/transferlearning/train.py
+++ b/alphadia/transferlearning/train.py
@@ -4,6 +4,7 @@
import pandas as pd
import torch
from alphabase.peptide.fragment import remove_unused_fragments
+from alphabase.peptide.mobility import ccs_to_mobility_for_df, mobility_to_ccs_for_df
from peptdeep.model.charge import ChargeModelForModAASeq
from peptdeep.model.model_interface import CallbackHandler, LR_SchedulerInterface
from peptdeep.pretrained_models import ModelManager
@@ -24,23 +25,6 @@
logger = logging.getLogger()
-settings = {
- # --------- USer settings ------------
- "batch_size": 1000,
- "max_lr": 0.0005,
- "train_fraction": 0.7,
- "validation_fraction": 0.2,
- "test_fraction": 0.1,
- "test_interval": 1,
- "lr_patience": 3,
- # --------- Our settings ------------
- "epochs": 51,
- "warmup_epochs": 5,
- # --------------------------
- "nce": 25,
- "instrument": "Lumos",
-}
-
class CustomScheduler(LR_SchedulerInterface):
"""
@@ -58,19 +42,22 @@ class CustomScheduler(LR_SchedulerInterface):
The number of warmup steps. Defaults to 5.
- num_training_steps: int
The number of training steps. Defaults to 50.
+ - lr_patience: int
+ The patience for the ReduceLROnPlateau scheduler. Defaults to 3.
"""
def __init__(self, optimizer: torch.optim.Optimizer, **kwargs):
- self.optimizer = optimizer
- self.num_warmup_steps = kwargs.get("num_warmup_steps", 5)
- self.num_training_steps = kwargs.get("num_training_steps", 50)
- self.reduce_lr_on_plateau = torch.optim.lr_scheduler.ReduceLROnPlateau(
+ self._optimizer = optimizer
+ self._num_warmup_steps = kwargs.get("num_warmup_steps", 5)
+ self._num_training_steps = kwargs.get("num_training_steps", 50)
+ self._lr_patience = kwargs.get("lr_patience", 3)
+ self._reduce_lr_on_plateau = torch.optim.lr_scheduler.ReduceLROnPlateau(
optimizer,
mode="min",
- patience=settings["lr_patience"],
+ patience=self._lr_patience,
factor=0.5,
)
- self.warmup_lr = LambdaLR(optimizer, self._warmup)
+ self._warmup_lr = LambdaLR(optimizer, self._warmup)
def _warmup(self, epoch: int):
"""
@@ -81,7 +68,7 @@ def _warmup(self, epoch: int):
epoch : int
The current epoch number.
"""
- return float(epoch + 1) / float(max(1, self.num_warmup_steps))
+ return float(epoch + 1) / float(max(1, self._num_warmup_steps))
def step(self, epoch: int, loss: float) -> float:
"""
@@ -100,16 +87,16 @@ def step(self, epoch: int, loss: float) -> float:
The learning rate for the next epoch.
"""
- if epoch < self.num_warmup_steps:
- self.warmup_lr.step()
+ if epoch < self._num_warmup_steps:
+ self._warmup_lr.step()
else:
- self.reduce_lr_on_plateau.step(loss)
+ self._reduce_lr_on_plateau.step(loss)
def get_last_lr(self):
"""
Get the last learning rate.
"""
- return [self.optimizer.param_groups[0]["lr"]]
+ return [self._optimizer.param_groups[0]["lr"]]
class EarlyStopping:
@@ -119,11 +106,11 @@ class EarlyStopping:
"""
def __init__(self, patience: int = 5, margin: float = 0.01):
- self.patience = patience
- self.best_loss = np.inf
- self.last_loss = np.inf
- self.margin = margin
- self.counter = 0
+ self._patience = patience
+ self._best_loss = np.inf
+ self._last_loss = np.inf
+ self._margin = margin
+ self._counter = 0
def step(self, val_loss: float):
"""
@@ -139,27 +126,27 @@ def step(self, val_loss: float):
continue_training : bool
Whether to continue training or not based on the early stopping criteria.
"""
- if self.last_loss != np.inf:
+ if self._last_loss != np.inf:
if (
- val_loss > self.best_loss * (1 + self.margin)
- or abs(val_loss - self.last_loss) / self.last_loss < self.margin
+ val_loss > self._best_loss * (1 + self._margin)
+ or abs(val_loss - self._last_loss) / self._last_loss < self._margin
):
- self.counter += 1
- if self.counter >= self.patience:
+ self._counter += 1
+ if self._counter >= self._patience:
return False
else:
- self.best_loss = val_loss
- self.counter = 0
- self.last_loss = val_loss
+ self._best_loss = val_loss
+ self._counter = 0
+ self._last_loss = val_loss
return True
def reset(self):
"""
Reset the early stopping criteria.
"""
- self.best_loss = np.inf
- self.last_loss = np.inf
- self.counter = 0
+ self._best_loss = np.inf
+ self._last_loss = np.inf
+ self._counter = 0
class CustomCallbackHandler(CallbackHandler):
@@ -177,8 +164,8 @@ class CustomCallbackHandler(CallbackHandler):
def __init__(self, test_callback, **callback_args):
super().__init__()
- self.test_callback = test_callback
- self.callback_args = callback_args
+ self._test_callback = test_callback
+ self._callback_args = callback_args
def epoch_callback(self, epoch: int, epoch_loss: float):
"""
@@ -196,7 +183,7 @@ def epoch_callback(self, epoch: int, epoch_loss: float):
bool: continue_training
Whether to continue training or not based on the early stopping criteria.
"""
- return self.test_callback(epoch, epoch_loss, **self.callback_args)
+ return self._test_callback(epoch, epoch_loss, **self._callback_args)
class FinetuneManager(ModelManager):
@@ -219,21 +206,35 @@ def __init__(
self,
mask_modloss: bool = False,
device: str = "gpu",
- settings: dict | None = None,
+ lr_patience: int = 3,
+ test_interval: int = 1,
+ train_fraction: float = 0.7,
+ validation_fraction: float = 0.2,
+ test_fraction: float = 0.1,
+ epochs: int = 51,
+ warmup_epochs: int = 5,
+ batch_size: int = 1000,
+ max_lr: float = 0.0005,
+ nce: float = 25,
+ instrument: str = "Lumos",
):
- if settings is None:
- settings = {}
super().__init__(mask_modloss, device)
+ self._test_interval = test_interval
+ self._train_fraction = train_fraction
+ self._validation_fraction = validation_fraction
+ self._test_fraction = test_fraction
+ self._epochs = epochs
+ self._warmup_epochs = warmup_epochs
+ self._batch_size = batch_size
+ self._max_lr = max_lr
+ self.nce = nce
+ self.instrument = instrument
+
self.device = device
- self.settings = settings
- self.early_stopping = EarlyStopping(
- patience=(settings["lr_patience"] // settings["test_interval"]) * 4
- )
+ self.early_stopping = EarlyStopping(patience=(lr_patience // test_interval) * 4)
assert (
- settings["train_fraction"]
- + settings["validation_fraction"]
- + settings["test_fraction"]
+ self._train_fraction + self._validation_fraction + self._test_fraction
<= 1.0
), "The sum of the train, validation and test fractions should be less than or equal to 1.0"
@@ -299,6 +300,108 @@ def _order_intensities(
]
return reordered
+ def _accumulate_training_metrics(
+ self,
+ metric_accumulator: MetricManager,
+ epoch: int,
+ epoch_loss: float,
+ current_lr: float,
+ property_name: str,
+ ):
+ """
+ Accumulate the training metrics (training loss and learning rate) for the given property.
+
+ Parameters
+ ----------
+ metric_accumulator : MetricManager
+ The metric manager object.
+ epoch : int
+ The current epoch number.
+ epoch_loss : float
+ The training loss value of the current epoch.
+ current_lr : float
+ The current learning rate.
+ property_name : str
+ The property name to accumulate the metrics for.
+ """
+ loss_name = "ce_loss" if property_name == "charge" else "l1_loss"
+ metric_accumulator.accumulate_metrics(
+ epoch,
+ metric=epoch_loss,
+ metric_name=loss_name,
+ data_split="train",
+ property_name=property_name,
+ )
+ metric_accumulator.accumulate_metrics(
+ epoch,
+ metric=current_lr,
+ metric_name="lr",
+ data_split="train",
+ property_name=property_name,
+ )
+
+ def _evaluate_metrics(
+ self,
+ test_input: dict,
+ metric_accumulator: MetricManager,
+ epoch: int,
+ data_split: str,
+ property_name: str,
+ epoch_loss: float,
+ current_lr: float,
+ ) -> bool:
+ """
+ Evaluate the model using the test_input, accumulate the metrics and return the continue_training flag based on the early stopping criteria.
+
+ Parameters
+ ----------
+ test_input : dict
+ The input data for calculating the metrics.
+ metric_accumulator : MetricManager
+ The metric manager object.
+ epoch : int
+ The current epoch number.
+ data_split : str
+ The dataset label to test on e.g. "validation", "train"
+ property_name : str
+ The property name to accumulate the metrics for.
+ epoch_loss : float
+ The training loss value of the current epoch.
+ current_lr : float
+ The current learning rate.
+
+ Returns
+ -------
+ bool
+ Whether to continue training or not based on the early stopping criteria applied on the metrics.
+ """
+ continue_training = True
+ val_metrics = metric_accumulator.calculate_test_metric(
+ test_input, epoch, data_split=data_split, property_name=property_name
+ )
+ if epoch != -1 and data_split == "validation":
+ loss_name = "ce_loss" if property_name == "charge" else "l1_loss"
+ self._accumulate_training_metrics(
+ metric_accumulator, epoch, epoch_loss, current_lr, property_name
+ )
+ val_loss = val_metrics[val_metrics["metric_name"] == loss_name][
+ "value"
+ ].values[0]
+ continue_training = self.early_stopping.step(val_loss)
+ logger.progress(
+ f" Epoch {epoch:<3} Lr: {current_lr:.5f} Training loss: {epoch_loss:.4f} validation loss: {val_loss:.4f}"
+ )
+ else:
+ logger.progress(
+ f" Model tested on {data_split} dataset with the following metrics:"
+ )
+ for i in range(len(val_metrics)):
+ logger.progress(
+ f" {val_metrics['metric_name'].values[i]:<30}: {val_metrics['value'].values[i]:.4f}"
+ )
+
+ return continue_training
+
def _test_ms2(
self,
epoch: int,
@@ -339,56 +442,38 @@ def _test_ms2(
"""
continue_training = True
- if epoch % self.settings["test_interval"] == 0:
- self.ms2_model.model.eval()
- if "instrument" not in precursor_df.columns:
- precursor_df["instrument"] = default_instrument
- if "nce" not in precursor_df.columns:
- precursor_df["nce"] = default_nce
-
- precursor_copy = precursor_df.copy()
- pred_intensities = self.ms2_model.predict(precursor_copy)
-
- test_input = {
- "psm_df": precursor_df,
- "predicted": pred_intensities,
- "target": target_fragment_intensity_df,
- }
- val_metrics = metric_accumulator.calculate_test_metric(
- test_input, epoch, data_split=data_split, property_name="ms2"
- )
- if epoch != -1 and data_split == "validation": # A training epoch
- metric_accumulator.accumulate_metrics(
- epoch,
- metric=epoch_loss,
- metric_name="l1_loss",
- data_split="train",
- property_name="ms2",
- )
- current_lr = self.ms2_model.optimizer.param_groups[0]["lr"]
- metric_accumulator.accumulate_metrics(
- epoch,
- metric=current_lr,
- metric_name="lr",
- data_split="train",
- property_name="ms2",
- )
- val_loss = val_metrics[val_metrics["metric_name"] == "l1_loss"][
- "value"
- ].values[0]
- continue_training = self.early_stopping.step(val_loss)
- logger.progress(
- f" Epoch {epoch:<3} Lr: {current_lr:.5f} Training loss: {epoch_loss:.4f} validation loss: {val_loss:.4f}"
- )
- else:
- logger.progress(
- f" Ms2 model tested on {data_split} dataset with the following metrics:"
- )
- for i in range(len(val_metrics)):
- logger.progress(
- f" {val_metrics['metric_name'].values[i]:<30}: {val_metrics['value'].values[i]:.4f}"
- )
- self.ms2_model.model.train()
+ if epoch % self._test_interval and epoch != -1:
+ return continue_training
+
+ self.ms2_model.model.eval()
+ if "instrument" not in precursor_df.columns:
+ precursor_df["instrument"] = default_instrument
+ if "nce" not in precursor_df.columns:
+ precursor_df["nce"] = default_nce
+
+ precursor_copy = precursor_df.copy()
+ pred_intensities = self.ms2_model.predict(precursor_copy)
+
+ test_input = {
+ "psm_df": precursor_df,
+ "predicted": pred_intensities,
+ "target": target_fragment_intensity_df,
+ }
+
+ current_lr = (
+ self.ms2_model.optimizer.param_groups[0]["lr"] if epoch != -1 else 0
+ )
+ continue_training = self._evaluate_metrics(
+ test_input,
+ metric_accumulator,
+ epoch,
+ data_split,
+ "ms2",
+ epoch_loss,
+ current_lr,
+ )
+
+ self.ms2_model.model.train()
return continue_training
def _normalize_intensity(
@@ -441,13 +526,10 @@ def finetune_ms2(
self._normalize_intensity(psm_df, matched_intensity_df)
# Shuffle the psm_df and split it into train and test
- train_psm_df = psm_df.sample(frac=self.settings["train_fraction"]).copy()
+ train_psm_df = psm_df.sample(frac=self._train_fraction).copy()
val_psm_df = (
psm_df.drop(train_psm_df.index)
- .sample(
- frac=self.settings["validation_fraction"]
- / (1 - self.settings["train_fraction"])
- )
+ .sample(frac=self._validation_fraction / (1 - self._train_fraction))
.copy()
)
test_psm_df = psm_df.drop(train_psm_df.index).drop(val_psm_df.index).copy()
@@ -496,12 +578,10 @@ def finetune_ms2(
unordered_frag_df=test_intensity_df,
)
- # Create a metric manager
test_metric_manager = MetricManager(
test_metrics=[L1LossTestMetric(), Ms2SimilarityTestMetric()],
)
- # create a callback handler
callback_handler = CustomCallbackHandler(
self._test_ms2,
precursor_df=reordered_val_psm_df,
@@ -510,13 +590,10 @@ def finetune_ms2(
data_split="validation",
)
- # set the callback handler
self.ms2_model.set_callback_handler(callback_handler)
- # Change the learning rate scheduler
self.ms2_model.set_lr_scheduler_class(CustomScheduler)
- # Reset the early stopping
self.early_stopping.reset()
# Test the model before training
@@ -531,26 +608,26 @@ def finetune_ms2(
# Train the model
logger.progress(" Fine-tuning MS2 model with the following settings:")
logger.info(
- f" Train fraction: {self.settings['train_fraction']:3.2f} Train size: {len(train_psm_df):<10}"
+ f" Train fraction: {self._train_fraction:3.2f} Train size: {len(train_psm_df):<10}"
)
logger.info(
- f" Validation fraction: {self.settings['validation_fraction']:3.2f} Validation size: {len(val_psm_df):<10}"
+ f" Validation fraction: {self._validation_fraction:3.2f} Validation size: {len(val_psm_df):<10}"
)
logger.info(
- f" Test fraction: {self.settings['test_fraction']:3.2f} Test size: {len(test_psm_df):<10}"
+ f" Test fraction: {self._test_fraction:3.2f} Test size: {len(test_psm_df):<10}"
)
self.ms2_model.model.train()
self.ms2_model.train(
precursor_df=train_psm_df,
fragment_intensity_df=train_intensity_df,
- epoch=self.settings["epochs"],
- batch_size=self.settings["batch_size"],
- warmup_epoch=self.settings["warmup_epochs"],
- lr=settings["max_lr"],
+ epoch=self._epochs,
+ batch_size=self._batch_size,
+ warmup_epoch=self._warmup_epochs,
+ lr=self._max_lr,
)
self._test_ms2(
- self.settings["epochs"],
+ self._epochs,
0,
reordered_test_psm_df,
reordered_test_intensity_df,
@@ -591,50 +668,28 @@ def _test_rt(
Whether to continue training or not based on the early stopping criteria.
"""
continue_training = True
- if epoch % self.settings["test_interval"] == 0:
- self.rt_model.model.eval()
-
- pred = self.rt_model.predict(test_df)
- test_input = {
- "predicted": pred["rt_pred"].values,
- "target": test_df["rt_norm"].values,
- }
- val_metrics = metric_accumulator.calculate_test_metric(
- test_input, epoch, data_split=data_split, property_name="rt"
- )
- if epoch != -1 and data_split == "validation": # A training epoch
- metric_accumulator.accumulate_metrics(
- epoch,
- metric=epoch_loss,
- metric_name="l1_loss",
- data_split="train",
- property_name="rt",
- )
- current_lr = self.rt_model.optimizer.param_groups[0]["lr"]
- metric_accumulator.accumulate_metrics(
- epoch,
- metric=current_lr,
- metric_name="lr",
- data_split="train",
- property_name="rt",
- )
- val_loss = val_metrics[val_metrics["metric_name"] == "l1_loss"][
- "value"
- ].values[0]
- continue_training = self.early_stopping.step(val_loss)
- logger.progress(
- f" Epoch {epoch:<3} Lr: {current_lr:.5f} Training loss: {epoch_loss:.4f} validation loss: {val_loss:.4f}"
- )
- else:
- logger.progress(
- f" RT model tested on {data_split} dataset with the following metrics:"
- )
- for i in range(len(val_metrics)):
- logger.progress(
- f" {val_metrics['metric_name'].values[i]:<30}: {val_metrics['value'].values[i]:.4f}"
- )
+ if epoch % self._test_interval != 0 and epoch != -1:
+ return continue_training
+
+ self.rt_model.model.eval()
+
+ pred = self.rt_model.predict(test_df)
+ test_input = {
+ "predicted": pred["rt_pred"].values,
+ "target": test_df["rt_norm"].values,
+ }
+ current_lr = self.rt_model.optimizer.param_groups[0]["lr"] if epoch != -1 else 0
+ continue_training = self._evaluate_metrics(
+ test_input,
+ metric_accumulator,
+ epoch,
+ data_split,
+ "rt",
+ epoch_loss,
+ current_lr,
+ )
- self.rt_model.model.train()
+ self.rt_model.model.train()
return continue_training
@@ -654,14 +709,12 @@ def finetune_rt(self, psm_df: pd.DataFrame) -> pd.DataFrame:
"""
# Shuffle the psm_df and split it into train and test
- train_df = psm_df.sample(frac=self.settings["train_fraction"])
+ train_df = psm_df.sample(frac=self._train_fraction)
val_df = psm_df.drop(train_df.index).sample(
- frac=self.settings["validation_fraction"]
- / (1 - self.settings["train_fraction"])
+ frac=self._validation_fraction / (1 - self._train_fraction)
)
test_df = psm_df.drop(train_df.index).drop(val_df.index)
- # Create a test metric manager
test_metric_manager = MetricManager(
test_metrics=[
L1LossTestMetric(),
@@ -670,20 +723,16 @@ def finetune_rt(self, psm_df: pd.DataFrame) -> pd.DataFrame:
],
)
- # Create a callback handler
callback_handler = CustomCallbackHandler(
self._test_rt,
test_df=val_df,
metric_accumulator=test_metric_manager,
data_split="validation",
)
- # Set the callback handler
self.rt_model.set_callback_handler(callback_handler)
- # Change the learning rate scheduler
self.rt_model.set_lr_scheduler_class(CustomScheduler)
- # Reset the early stopping
self.early_stopping.reset()
# Test the model before training
@@ -691,26 +740,24 @@ def finetune_rt(self, psm_df: pd.DataFrame) -> pd.DataFrame:
# Train the model
logger.progress(" Fine-tuning RT model with the following settings:")
logger.info(
- f" Train fraction: {self.settings['train_fraction']:3.2f} Train size: {len(train_df):<10}"
+ f" Train fraction: {self._train_fraction:3.2f} Train size: {len(train_df):<10}"
)
logger.info(
- f" Validation fraction: {self.settings['validation_fraction']:3.2f} Validation size: {len(val_df):<10}"
+ f" Validation fraction: {self._validation_fraction:3.2f} Validation size: {len(val_df):<10}"
)
logger.info(
- f" Test fraction: {self.settings['test_fraction']:3.2f} Test size: {len(test_df):<10}"
+ f" Test fraction: {self._test_fraction:3.2f} Test size: {len(test_df):<10}"
)
self.rt_model.model.train()
self.rt_model.train(
train_df,
- batch_size=self.settings["batch_size"],
- epoch=self.settings["epochs"],
- warmup_epoch=self.settings["warmup_epochs"],
- lr=settings["max_lr"],
+ batch_size=self._batch_size,
+ epoch=self._epochs,
+ warmup_epoch=self._warmup_epochs,
+ lr=self._max_lr,
)
- self._test_rt(
- self.settings["epochs"], 0, test_df, test_metric_manager, data_split="test"
- )
+ self._test_rt(self._epochs, 0, test_df, test_metric_manager, data_split="test")
metrics = test_metric_manager.get_stats()
@@ -746,49 +793,29 @@ def _test_charge(
Whether to continue training or not based on the early stopping criteria.
"""
continue_training = True
- if epoch % self.settings["test_interval"] == 0:
- self.charge_model.model.eval()
-
- pred = self.charge_model.predict(test_df)
- test_input = {
- "target": np.array(test_df["charge_indicators"].values.tolist()),
- "predicted": np.array(pred["charge_probs"].values.tolist()),
- }
- val_metrics = metric_accumulator.calculate_test_metric(
- test_input, epoch, data_split=data_split, property_name="charge"
- )
- if epoch != -1 and data_split == "validation": # A training epoch
- metric_accumulator.accumulate_metrics(
- epoch,
- metric=epoch_loss,
- metric_name="ce_loss",
- data_split="train",
- property_name="charge",
- )
- current_lr = self.charge_model.optimizer.param_groups[0]["lr"]
- metric_accumulator.accumulate_metrics(
- epoch,
- metric=current_lr,
- metric_name="lr",
- data_split="train",
- property_name="charge",
- )
- val_loss = val_metrics[val_metrics["metric_name"] == "ce_loss"][
- "value"
- ].values[0]
- continue_training = self.early_stopping.step(val_loss)
- logger.progress(
- f" Epoch {epoch:<3} Lr: {current_lr:.5f} Training loss: {epoch_loss:.4f} validation loss: {val_loss:.4f}"
- )
- else:
- logger.progress(
- f" Charge model tested on {data_split} dataset with the following metrics: "
- )
- for i in range(len(val_metrics)):
- logger.progress(
- f" {val_metrics['metric_name'].values[i]:<30}: {val_metrics['value'].values[i]:.4f}"
- )
- self.charge_model.model.train()
+ if epoch % self._test_interval != 0 and epoch != -1:
+ return continue_training
+
+ self.charge_model.model.eval()
+
+ pred = self.charge_model.predict(test_df)
+ test_input = {
+ "target": np.array(test_df["charge_indicators"].values.tolist()),
+ "predicted": np.array(pred["charge_probs"].values.tolist()),
+ }
+ current_lr = (
+ self.charge_model.optimizer.param_groups[0]["lr"] if epoch != -1 else 0
+ )
+ continue_training = self._evaluate_metrics(
+ test_input,
+ metric_accumulator,
+ epoch,
+ data_split,
+ "charge",
+ epoch_loss,
+ current_lr,
+ )
+ self.charge_model.model.train()
return continue_training
def finetune_charge(self, psm_df: pd.DataFrame) -> pd.DataFrame:
@@ -834,14 +861,12 @@ def finetune_charge(self, psm_df: pd.DataFrame) -> pd.DataFrame:
)
# Shuffle the psm_df and split it into train and test
- train_df = psm_df.sample(frac=self.settings["train_fraction"])
+ train_df = psm_df.sample(frac=self._train_fraction)
val_df = psm_df.drop(train_df.index).sample(
- frac=self.settings["validation_fraction"]
- / (1 - self.settings["train_fraction"])
+ frac=self._validation_fraction / (1 - self._train_fraction)
)
test_df = psm_df.drop(train_df.index).drop(val_df.index)
- # Create a test metric manager
test_metric_manager = MetricManager(
test_metrics=[
CELossTestMetric(),
@@ -850,7 +875,6 @@ def finetune_charge(self, psm_df: pd.DataFrame) -> pd.DataFrame:
],
)
- # Create a callback handler
callback_handler = CustomCallbackHandler(
self._test_charge,
test_df=val_df,
@@ -858,13 +882,10 @@ def finetune_charge(self, psm_df: pd.DataFrame) -> pd.DataFrame:
data_split="validation",
)
- # Set the callback handler
self.charge_model.set_callback_handler(callback_handler)
- # Change the learning rate scheduler
self.charge_model.set_lr_scheduler_class(CustomScheduler)
- # Reset the early stopping
self.early_stopping.reset()
# Test the model before training
@@ -873,26 +894,162 @@ def finetune_charge(self, psm_df: pd.DataFrame) -> pd.DataFrame:
# Train the model
logger.progress(" Fine-tuning Charge model with following settings:")
logger.info(
- f" Train fraction: {self.settings['train_fraction']:3.2f} Train size: {len(train_df):<10}"
+ f" Train fraction: {self._train_fraction:3.2f} Train size: {len(train_df):<10}"
)
logger.info(
- f" Validation fraction: {self.settings['validation_fraction']:3.2f} Validation size: {len(val_df):<10}"
+ f" Validation fraction: {self._validation_fraction:3.2f} Validation size: {len(val_df):<10}"
)
logger.info(
- f" Test fraction: {self.settings['test_fraction']:3.2f} Test size: {len(test_df):<10}"
+ f" Test fraction: {self._test_fraction:3.2f} Test size: {len(test_df):<10}"
)
self.charge_model.model.train()
self.charge_model.train(
train_df,
- batch_size=self.settings["batch_size"],
- epoch=self.settings["epochs"],
- warmup_epoch=self.settings["warmup_epochs"],
- lr=settings["max_lr"],
+ batch_size=self._batch_size,
+ epoch=self._epochs,
+ warmup_epoch=self._warmup_epochs,
+ lr=self._max_lr,
)
self._test_charge(
- self.settings["epochs"], 0, test_df, test_metric_manager, data_split="test"
+ self._epochs, 0, test_df, test_metric_manager, data_split="test"
)
metrics = test_metric_manager.get_stats()
return metrics
+
+ def _test_ccs(
+ self,
+ epoch: int,
+ epoch_loss: float,
+ test_df: pd.DataFrame,
+ metric_accumulator: MetricManager,
+ data_split: str,
+ ) -> bool:
+ """
+ Test the CCS model using the PSM dataframe and accumulate both the training loss and test metrics.
+
+ Parameters
+ ----------
+ epoch : int
+ The current epoch number.
+ epoch_loss : float
+ The train loss value of the current epoch.
+ test_df : pd.DataFrame
+ The PSM dataframe.
+ metric_accumulator : MetricManager
+ The metric manager object.
+ data_split : str
+ The dataset label to test on. e.g. "validation", "train"
+ Returns
+ -------
+ bool
+ Whether to continue training or not based on the early stopping criteria.
+ """
+ continue_training = True
+ if epoch % self._test_interval != 0 and epoch != -1:
+ return continue_training
+
+ self.ccs_model.model.eval()
+
+ pred = self.ccs_model.predict(test_df)
+
+ test_input = {
+ "predicted": pred["ccs_pred"].values,
+ "target": test_df["ccs"].values,
+ }
+
+ current_lr = (
+ self.ccs_model.optimizer.param_groups[0]["lr"] if epoch != -1 else 0
+ )
+ continue_training = self._evaluate_metrics(
+ test_input,
+ metric_accumulator,
+ epoch,
+ data_split,
+ "ccs",
+ epoch_loss,
+ current_lr,
+ )
+
+ self.ccs_model.model.train()
+
+ return continue_training
+
+ def finetune_ccs(self, psm_df: pd.DataFrame) -> pd.DataFrame:
+ """
+ Fine tune the CCS model using the PSM dataframe.
+
+ Parameters
+ ----------
+ psm_df : pd.DataFrame
+ The PSM dataframe.
+
+ Returns
+ -------
+ pd.DataFrame
+ Accumulated metrics during the fine tuning process.
+ """
+ if "mobility" not in psm_df.columns and "ccs" not in psm_df.columns:
+ logger.error(
+ "Failed to finetune CCS model. PSM dataframe does not contain mobility or ccs columns."
+ )
+ return
+ if "ccs" not in psm_df.columns:
+ psm_df["ccs"] = mobility_to_ccs_for_df(psm_df, "mobility")
+ elif "mobility" not in psm_df.columns:
+ psm_df["mobility"] = ccs_to_mobility_for_df(psm_df, "ccs")
+
+ # Shuffle the psm_df and split it into train and test
+ train_df = psm_df.sample(frac=self._train_fraction)
+ val_df = psm_df.drop(train_df.index).sample(
+ frac=self._validation_fraction / (1 - self._train_fraction)
+ )
+ test_df = psm_df.drop(train_df.index).drop(val_df.index)
+
+ test_metric_manager = MetricManager(
+ test_metrics=[
+ L1LossTestMetric(),
+ LinearRegressionTestMetric(),
+ AbsErrorPercentileTestMetric(95),
+ ],
+ )
+ callback_handler = CustomCallbackHandler(
+ self._test_ccs,
+ test_df=val_df,
+ metric_accumulator=test_metric_manager,
+ data_split="validation",
+ )
+ self.ccs_model.set_callback_handler(callback_handler)
+
+ self.ccs_model.set_lr_scheduler_class(CustomScheduler)
+
+ self.early_stopping.reset()
+
+ # Test the model before training
+ self._test_ccs(-1, 0, psm_df, test_metric_manager, data_split="all")
+ # Train the model
+ logger.progress(" Fine-tuning CCS model with the following settings:")
+ logger.info(
+ f" Train fraction: {self._train_fraction:3.2f} Train size: {len(train_df):<10}"
+ )
+ logger.info(
+ f" Validation fraction: {self._validation_fraction:3.2f} Validation size: {len(val_df):<10}"
+ )
+ logger.info(
+ f" Test fraction: {self._test_fraction:3.2f} Test size: {len(test_df):<10}"
+ )
+ self.ccs_model.model.train()
+ self.ccs_model.train(
+ train_df,
+ batch_size=self._batch_size,
+ epoch=self._epochs,
+ warmup_epoch=self._warmup_epochs,
+ lr=self._max_lr,
+ )
+
+ self._test_ccs(self._epochs, 0, test_df, test_metric_manager, data_split="test")
+
+ metrics = test_metric_manager.get_stats()
+
+ return metrics
diff --git a/alphadia/workflow/base.py b/alphadia/workflow/base.py
index b06fc5d7..08b16143 100644
--- a/alphadia/workflow/base.py
+++ b/alphadia/workflow/base.py
@@ -4,6 +4,7 @@
# alpha family imports
from alphabase.spectral_library.base import SpecLibBase
+from alphabase.spectral_library.flat import SpecLibFlat
# alphadia imports
from alphadia.data import alpharaw, bruker
@@ -23,6 +24,7 @@ class WorkflowBase:
CALIBRATION_MANAGER_PATH = "calibration_manager.pkl"
OPTIMIZATION_MANAGER_PATH = "optimization_manager.pkl"
+ TIMING_MANAGER_PATH = "timing_manager.pkl"
FDR_MANAGER_PATH = "fdr_manager.pkl"
FIGURE_PATH = "figures"
@@ -45,13 +47,19 @@ def __init__(
Configuration for the workflow. This will be used to initialize the calibration manager and fdr manager
"""
- self._instance_name = instance_name
- self._parent_path = os.path.join(config["output"], TEMP_FOLDER)
- self._config = config
+ self._instance_name: str = instance_name
+ self._parent_path: str = os.path.join(config["output"], TEMP_FOLDER)
+ self._config: dict = config
+ self.reporter: reporting.Pipeline | None = None
+ self._dia_data: bruker.TimsTOFTranspose | alpharaw.AlphaRaw | None = None
+ self._spectral_library: SpecLibBase | None = None
+ self._calibration_manager: manager.CalibrationManager | None = None
+ self._optimization_manager: manager.OptimizationManager | None = None
+ self._timing_manager: manager.TimingManager | None = None
if not os.path.exists(self.parent_path):
logger.info(f"Creating parent folder for workflows at {self.parent_path}")
- os.mkdir(self.parent_path)
+ os.makedirs(self.parent_path)
if not os.path.exists(self.path):
logger.info(
@@ -96,13 +104,19 @@ def load(
# initialize the optimization manager
self._optimization_manager = manager.OptimizationManager(
- self.config["optimization_manager"],
+ self.config,
+ gradient_length=self.dia_data.rt_values.max(),
path=os.path.join(self.path, self.OPTIMIZATION_MANAGER_PATH),
load_from_file=self.config["general"]["reuse_calibration"],
figure_path=os.path.join(self.path, self.FIGURE_PATH),
reporter=self.reporter,
)
+ self._timing_manager = manager.TimingManager(
+ path=os.path.join(self.path, self.TIMING_MANAGER_PATH),
+ load_from_file=self.config["general"]["reuse_calibration"],
+ )
+
self.reporter.log_event("section_stop", {})
@property
@@ -126,17 +140,22 @@ def config(self) -> dict:
return self._config
@property
- def calibration_manager(self) -> str:
+ def calibration_manager(self) -> manager.CalibrationManager:
"""Calibration manager for the workflow. Owns the RT, IM, MZ calibration and the calibration data"""
return self._calibration_manager
@property
- def optimization_manager(self) -> str:
+ def optimization_manager(self) -> manager.OptimizationManager:
"""Optimization manager for the workflow. Owns the optimization data"""
return self._optimization_manager
@property
- def spectral_library(self) -> SpecLibBase:
+ def timing_manager(self) -> manager.TimingManager:
+ """Optimization manager for the workflow. Owns the timing data"""
+ return self._timing_manager
+
+ @property
+ def spectral_library(self) -> SpecLibFlat:
"""Spectral library for the workflow. Owns the spectral library data"""
return self._spectral_library
diff --git a/alphadia/workflow/config.py b/alphadia/workflow/config.py
index 4af78454..8b30904b 100644
--- a/alphadia/workflow/config.py
+++ b/alphadia/workflow/config.py
@@ -77,15 +77,7 @@ def print_w_style(
# Check what the config name in string inside the brackets ( )
# If the source is default, remove the brackets and set style to default
# Else set style to new
- if string.find("(") != -1:
- config_name = string[string.find("(") + 1 : string.find(")")]
- if config_name == "default":
- string = string[: string.find("(")] + string[string.find(")") + 1 :]
- style = "default"
- else:
- style = "new"
- else:
- style = "default"
+ style = "new" if "user defined" in string else "default"
if style == "update":
# Green color
@@ -362,32 +354,47 @@ def update_recursive(
)
return default_config
- for default_key, default_value in default_config.items():
- is_last_item = default_key == list(default_config.keys())[-1]
+ all_keys = list(default_config.keys())
+ for experiment_config in experiment_configs:
+ all_keys += [key for key in experiment_config if key not in all_keys]
+
+ for key in all_keys:
+ style = "auto"
+ if key not in default_config:
+ style = "new"
+ for experiment_config in experiment_configs:
+ if key in experiment_config:
+ default_config[key] = experiment_config[key]
+ break
+
+ default_value = default_config[key]
+
+ is_last_item = key == all_keys[-1]
if not isinstance(
default_value, tuple
): # If the default value is not a leaf node, print it's key on separate line
print_w_style(
- f"{default_key}",
- style="auto",
+ f"{key}",
+ style=style,
last_item_arr=last_item_arr + [is_last_item],
)
# Collect potential updates for this item
potential_config_updates = []
for experiment_config in experiment_configs:
- if default_key in experiment_config:
- potential_config_updates.append(experiment_config[default_key])
+ if key in experiment_config:
+ potential_config_updates.append(experiment_config[key])
- default_config[default_key] = update_recursive(
- {"key": default_key, "value": default_value},
+ default_config[key] = update_recursive(
+ {"key": key, "value": default_value},
potential_config_updates,
level + 1,
print_output,
is_last_item,
last_item_arr=last_item_arr + [is_last_item],
)
+
return default_config
@@ -537,25 +544,6 @@ def __repr__(self) -> str:
print_recursively(self.config, 0, "auto")
return ""
- def translate(self):
- """
- Translate the config dict so that every leaf node is a tuple (value, experiment_name), instead of just value
-
- and sets the translate_config attribute, uses the general translate_config function
- """
- temp = copy.deepcopy(self.config)
- self.translated_config = translate_config(temp, self.experiment_name)
- # Let's make sure that the main config was not translated
- return self.translated_config
-
- def align_config_w_translation(self) -> None:
- """
- Translate the config dict back so that every leaf node is a value, instead of a tuple (value, experiment_name)
- uses the general translate_config_back function
- """
- temp = copy.deepcopy(self.translated_config)
- self.config = translate_config_back(temp)
-
def update(self, experiments: list["Config"], print_modifications: bool = True):
"""
Updates the config with the experiment configs,
@@ -572,17 +560,26 @@ def update(self, experiments: list["Config"], print_modifications: bool = True):
contains updated and unmodifed values.
"""
- # Translate the config dict first.
- self.translate()
+ # The translated config contains the source of the modifications on leaf nodes
+ translated_config = translate_config(
+ copy.deepcopy(self.config), self.experiment_name
+ )
if len(experiments) > 0:
translated_experiments = []
for config in experiments:
- translated_experiments.append(config.translate())
- self.translated_config = update_recursive(
- {"key": "", "value": self.translated_config},
+ translated_experiments.append(
+ translate_config(
+ copy.deepcopy(config.config), config.experiment_name
+ )
+ )
+
+ # Update the config with the experiments iteratively
+ translated_config = update_recursive(
+ {"key": "", "value": translated_config},
translated_experiments,
print_output=print_modifications,
)
- # Translate the config dict back
- self.align_config_w_translation()
+
+ # Remove the source of modifications
+ self.config = translate_config_back(translated_config)
diff --git a/alphadia/workflow/manager.py b/alphadia/workflow/manager.py
index 109280f9..6dc2e3e8 100644
--- a/alphadia/workflow/manager.py
+++ b/alphadia/workflow/manager.py
@@ -3,6 +3,7 @@
import os
import pickle
import typing
+from collections import defaultdict
from copy import deepcopy
import numpy as np
@@ -259,6 +260,7 @@ def get_group_names(self):
-------
list of str
List of calibration group names
+
"""
return [x["name"] for x in self.estimator_groups]
@@ -300,6 +302,7 @@ def get_estimator_names(self, group_name: str):
-------
list of str
List of estimator names
+
"""
group = self.get_group(group_name)
@@ -451,7 +454,8 @@ def fit_predict(
class OptimizationManager(BaseManager):
def __init__(
self,
- initial_parameters: dict,
+ config: None | dict = None,
+ gradient_length: None | float = None,
path: None | str = None,
load_from_file: bool = True,
**kwargs,
@@ -461,6 +465,25 @@ def __init__(
self.reporter.log_event("initializing", {"name": f"{self.__class__.__name__}"})
if not self.is_loaded_from_file:
+ rt_error = (
+ config["search_initial"]["initial_rt_tolerance"]
+ if config["search_initial"]["initial_rt_tolerance"] > 1
+ else config["search_initial"]["initial_rt_tolerance"] * gradient_length
+ )
+ initial_parameters = {
+ "ms1_error": config["search_initial"]["initial_ms1_tolerance"],
+ "ms2_error": config["search_initial"]["initial_ms2_tolerance"],
+ "rt_error": rt_error,
+ "mobility_error": config["search_initial"][
+ "initial_mobility_tolerance"
+ ],
+ "column_type": "library",
+ "num_candidates": config["search_initial"]["initial_num_candidates"],
+ "classifier_version": -1,
+ "fwhm_rt": config["optimization_manager"]["fwhm_rt"],
+ "fwhm_mobility": config["optimization_manager"]["fwhm_mobility"],
+ "score_cutoff": config["optimization_manager"]["score_cutoff"],
+ }
self.__dict__.update(initial_parameters)
for key, value in initial_parameters.items():
@@ -470,7 +493,6 @@ def fit(self, update_dict):
"""Update the parameters dict with the values in update_dict."""
self.__dict__.update(update_dict)
self.is_fitted = True
- self.save()
def predict(self):
"""Return the parameters dict."""
@@ -491,15 +513,25 @@ def __init__(
load_from_file: bool = True,
**kwargs,
):
+ """Contains, updates and applies classifiers for target-decoy competitio-based false discovery rate (FDR) estimation.
+
+ Parameters
+ ----------
+ feature_columns: list
+ List of feature columns to use for the classifier
+ classifier_base: object
+ Base classifier object to use for the FDR estimation
+
+ """
super().__init__(path=path, load_from_file=load_from_file, **kwargs)
self.reporter.log_string(f"Initializing {self.__class__.__name__}")
self.reporter.log_event("initializing", {"name": f"{self.__class__.__name__}"})
if not self.is_loaded_from_file:
self.feature_columns = feature_columns
- self.classifier_store = {}
+ self.classifier_store = defaultdict(list)
self.classifier_base = classifier_base
-
+ self._current_version = -1
self.load_classifier_store()
def fit_predict(
@@ -512,8 +544,14 @@ def fit_predict(
df_fragments: None | pd.DataFrame = None,
dia_cycle: None | np.ndarray = None,
decoy_channel: int = -1,
+ version: int = -1,
):
- """Update the parameters dict with the values in update_dict."""
+ """Fit the classifier and perform FDR estimation.
+
+ Notes
+ -----
+ The classifier_hash must be identical for every call of fit_predict for self._current_version to give the right index in self.classifier_store.
+ """
available_columns = list(
set(features_df.columns).intersection(set(self.feature_columns))
)
@@ -559,7 +597,7 @@ def fit_predict(
self.reporter.log_string(f"Decoy channel: {decoy_channel}")
self.reporter.log_string(f"Competetive: {competetive}")
- classifier = self.get_classifier(available_columns)
+ classifier = self.get_classifier(available_columns, version)
if decoy_strategy == "precursor":
psm_df = fdr.perform_fdr(
classifier,
@@ -619,12 +657,25 @@ def fit_predict(
raise ValueError(f"Invalid decoy_strategy: {decoy_strategy}")
self.is_fitted = True
- self.classifier_store[column_hash(available_columns)] = classifier
+
+ self._current_version += 1
+ self.classifier_store[column_hash(available_columns)].append(classifier)
+
self.save()
return psm_df
- def save_classifier_store(self, path=None):
+ def save_classifier_store(self, path: None | str = None, version: int = -1):
+ """Saves the classifier store to disk.
+
+ Parameters
+ ----------
+ path: None | str
+ Where to save the classifier. Saves to alphadia/constants/classifier if None.
+ version: int
+ Version of the classifier to save. Takes the last classifier if -1 (default)
+
+ """
if path is None:
path = os.path.join(
os.path.dirname(alphadia.__file__), "constants", "classifier"
@@ -632,12 +683,21 @@ def save_classifier_store(self, path=None):
logger.info(f"Saving classifier store to {path}")
- for classifier_hash, classifier in self.classifier_store.items():
+ for classifier_hash, classifier_list in self.classifier_store.items():
torch.save(
- classifier.to_state_dict(), os.path.join(path, f"{classifier_hash}.pth")
+ classifier_list[version].to_state_dict(),
+ os.path.join(path, f"{classifier_hash}.pth"),
)
- def load_classifier_store(self, path=None):
+ def load_classifier_store(self, path: None | str = None):
+ """Loads the classifier store from disk.
+
+ Parameters
+ ----------
+ path: None | str
+ Location of the classifier to load. Loads from alphadia/constants/classifier if None.
+
+ """
if path is None:
path = os.path.join(
os.path.dirname(alphadia.__file__), "constants", "classifier"
@@ -650,20 +710,36 @@ def load_classifier_store(self, path=None):
classifier_hash = file.split(".")[0]
if classifier_hash not in self.classifier_store:
- self.classifier_store[classifier_hash] = deepcopy(
- self.classifier_base
- )
- self.classifier_store[classifier_hash].from_state_dict(
- torch.load(os.path.join(path, file))
- )
+ classifier = deepcopy(self.classifier_base)
+ classifier.from_state_dict(torch.load(os.path.join(path, file)))
+ self.classifier_store[classifier_hash].append(classifier)
+
+ def get_classifier(self, available_columns: list, version: int = -1):
+ """Gets the classifier for a given set of feature columns and version. If the classifier is not found in the store, gets the base classifier instead.
+
+ Parameters
+ ----------
+ available_columns: list
+ List of feature columns
+ version: int
+ Version of the classifier to get
+
+ Returns
+ ----------
+ object
+ Classifier object
- def get_classifier(self, available_columns):
+ """
classifier_hash = column_hash(available_columns)
if classifier_hash in self.classifier_store:
- classifier = self.classifier_store[classifier_hash]
+ classifier = self.classifier_store[classifier_hash][version]
else:
- classifier = deepcopy(self.classifier_base)
- return classifier
+ classifier = self.classifier_base
+ return deepcopy(classifier)
+
+ @property
+ def current_version(self):
+ return self._current_version
def predict(self):
"""Return the parameters dict."""
@@ -681,3 +757,41 @@ def fit(self, update_dict):
def column_hash(columns):
columns.sort()
return xxhash.xxh64_hexdigest("".join(columns))
+
+
+class TimingManager(BaseManager):
+ def __init__(
+ self,
+ path: None | str = None,
+ load_from_file: bool = True,
+ **kwargs,
+ ):
+ """Contains and updates timing information for the portions of the workflow."""
+ super().__init__(path=path, load_from_file=load_from_file, **kwargs)
+ self.reporter.log_string(f"Initializing {self.__class__.__name__}")
+ self.reporter.log_event("initializing", {"name": f"{self.__class__.__name__}"})
+ if not self.is_loaded_from_file:
+ self.timings = {}
+
+ def set_start_time(self, workflow_stage: str):
+ """Stores the start time of the given stage of the workflow in the timings attribute. Also saves the timing manager to disk.
+
+ Parameters
+ ----------
+ workflow_stage : str
+ The name under which the timing will be stored in the timings dict
+ """
+ self.timings.update({workflow_stage: {"start": pd.Timestamp.now()}})
+
+ def set_end_time(self, workflow_stage: str):
+ """Stores the end time of the given stage of the workflow in the timings attribute and calculates the duration. Also saves the timing manager to disk.
+ Parameters
+ ----------
+ workflow_stage : str
+ The name under which the timing will be stored in the timings dict
+
+ """
+ self.timings[workflow_stage]["end"] = pd.Timestamp.now()
+ self.timings[workflow_stage]["duration"] = (
+ self.timings[workflow_stage]["end"] - self.timings[workflow_stage]["start"]
+ ).total_seconds() / 60
diff --git a/alphadia/workflow/optimization.py b/alphadia/workflow/optimization.py
new file mode 100644
index 00000000..4fc61188
--- /dev/null
+++ b/alphadia/workflow/optimization.py
@@ -0,0 +1,942 @@
+# native imports
+from abc import ABC, abstractmethod
+
+import matplotlib.pyplot as plt
+import numpy as np
+
+# third party imports
+import pandas as pd
+import seaborn as sns
+
+# alpha family imports
+from alphabase.peptide.fragment import remove_unused_fragments
+from alphabase.spectral_library.flat import SpecLibFlat
+
+from alphadia.exceptions import NoOptimizationLockTargetError
+
+# alphadia imports
+from alphadia.workflow import reporting
+
+
+class BaseOptimizer(ABC):
+ def __init__(
+ self,
+ workflow,
+ reporter: None | reporting.Pipeline | reporting.Backend = None,
+ ):
+ """This class serves as a base class for the search parameter optimization process, which defines the parameters used for search.
+
+ Parameters
+ ----------
+
+ workflow: peptidecentric.PeptideCentricWorkflow
+ The workflow object, which includes the calibration, calibration_optimization and FDR managers which are used as part of optimization.
+
+ reporter: None | reporting.Pipeline | reporting.Backend
+ The reporter object used to log information about the optimization process. If None, a new LogBackend object is created.
+
+ """
+ self.workflow = workflow
+ self.reporter = reporting.LogBackend() if reporter is None else reporter
+ self._num_prev_optimizations = 0
+
+ @abstractmethod
+ def step(self, precursors_df: pd.DataFrame, fragments_df: pd.DataFrame):
+ """This method evaluates the progress of the optimization, and either concludes the optimization if it has converged or continues the optimization if it has not.
+ This method includes the update rule for the optimization.
+
+ Parameters
+ ----------
+
+ precursors_df: pd.DataFrame
+ The filtered precursor dataframe for the search (see peptidecentric.PeptideCentricWorkflow.filter_dfs).
+
+ fragments_df: pd.DataFrame
+ The filtered fragment dataframe for the search (see peptidecentric.PeptideCentricWorkflow.filter_dfs).
+
+
+ """
+
+ pass
+
+ @abstractmethod
+ def skip(self):
+ """Record skipping of optimization. Can be overwritten with an empty method if there is no need to record skips."""
+ pass
+
+ def proceed_with_insufficient_precursors(self, precursors_df, fragments_df):
+ self.workflow.reporter.log_string(
+ "No more batches to process. Will proceed to extraction using best parameters available in optimization manager.",
+ verbosity="warning",
+ )
+ self._update_history(precursors_df, fragments_df)
+ self._update_workflow()
+ self.workflow.reporter.log_string(
+ f"Using current optimal value for {self.parameter_name}: {self.workflow.optimization_manager.__dict__[self.parameter_name]:.2f}.",
+ verbosity="warning",
+ )
+
+ @abstractmethod
+ def plot(self):
+ """Plots the progress of the optimization. Can be overwritten with an empty method if there is no need to plot the progress."""
+ pass
+
+ @abstractmethod
+ def _update_workflow():
+ """This method updates the optimization manager with the results of the optimization, namely:
+ the classifier version,
+ the optimal parameter,
+ score cutoff,
+ FWHM_RT,
+ and FWHM_mobility
+
+ """
+ pass
+
+
+class AutomaticOptimizer(BaseOptimizer):
+ def __init__(
+ self,
+ initial_parameter: float,
+ workflow,
+ reporter: None | reporting.Pipeline | reporting.Backend = None,
+ ):
+ """This class automatically optimizes the search parameter and stores the progres of optimization in a dataframe, history_df.
+
+ Parameters
+ ----------
+ initial_parameter: float
+ The parameter used for search in the first round of optimization.
+
+ See base class for other parameters.
+
+ """
+ super().__init__(workflow, reporter)
+ self.history_df = pd.DataFrame()
+ self.workflow.optimization_manager.fit({self.parameter_name: initial_parameter})
+ self.has_converged = False
+ self._num_prev_optimizations = 0
+ self._num_consecutive_skips = 0
+ self.update_factor = workflow.config["optimization"][self.parameter_name][
+ "automatic_update_factor"
+ ]
+ self.update_percentile_range = workflow.config["optimization"][
+ self.parameter_name
+ ]["automatic_update_percentile_range"]
+
+ self._try_narrower_values = workflow.config["optimization"][
+ self.parameter_name
+ ]["try_narrower_values"]
+
+ self._maximal_decrease = (
+ workflow.config["optimization"][self.parameter_name]["maximal_decrease"]
+ if self._try_narrower_values
+ else None
+ )
+
+ self._favour_narrower_optimum = workflow.config["optimization"][
+ self.parameter_name
+ ]["favour_narrower_optimum"]
+
+ self._maximum_decrease_from_maximum = (
+ workflow.config["optimization"][self.parameter_name][
+ "maximum_decrease_from_maximum"
+ ]
+ if self._favour_narrower_optimum
+ else None
+ )
+
+ def step(
+ self,
+ precursors_df: pd.DataFrame,
+ fragments_df: pd.DataFrame,
+ ):
+ """See base class. The feature is used to track the progres of the optimization and determine whether it has converged.
+ It also resets the internal counter for the number of consecutive skips.
+ """
+ if self.has_converged:
+ self.reporter.log_string(
+ f"✅ {self.parameter_name:<15}: optimization complete. Optimal parameter {self.workflow.optimization_manager.__dict__[self.parameter_name]} found after {len(self.history_df)} searches.",
+ verbosity="progress",
+ )
+ return
+
+ self._num_consecutive_skips = 0
+ self._num_prev_optimizations += 1
+ self.reporter.log_string(
+ f"=== Optimization of {self.parameter_name} has been performed {self._num_prev_optimizations} time(s); minimum number is {self.workflow.config['calibration']['min_steps']} ===",
+ verbosity="progress",
+ )
+
+ self._update_history(precursors_df, fragments_df)
+
+ if self._just_converged:
+ self.has_converged = True
+
+ self._update_workflow()
+
+ self.reporter.log_string(
+ f"✅ {self.parameter_name:<15}: optimization complete. Optimal parameter {self.workflow.optimization_manager.__dict__[self.parameter_name]:.4f} found after {len(self.history_df)} searches.",
+ verbosity="progress",
+ )
+
+ else:
+ new_parameter = self._propose_new_parameter(
+ precursors_df
+ if self.estimator_group_name == "precursor"
+ else fragments_df
+ )
+
+ self.workflow.optimization_manager.fit({self.parameter_name: new_parameter})
+
+ self.reporter.log_string(
+ f"❌ {self.parameter_name:<15}: optimization incomplete after {len(self.history_df)} search(es). Will search with parameter {self.workflow.optimization_manager.__dict__[self.parameter_name]:.4f}.",
+ verbosity="progress",
+ )
+
+ def skip(self):
+ """Increments the internal counter for the number of consecutive skips and checks if the optimization should be stopped."""
+ self._num_consecutive_skips += 1
+ self.reporter.log_string(
+ f"=== Optimization of {self.parameter_name} has been skipped {self._num_consecutive_skips} time(s); maximum number is {self.workflow.config['calibration']['max_skips']} ===",
+ verbosity="progress",
+ )
+ if self._batch_substantially_bigger:
+ self.has_converged = True
+ self._update_workflow()
+ self.reporter.log_string(
+ f"✅ {self.parameter_name:<15}: optimization complete. Optimal parameter {self.workflow.optimization_manager.__dict__[self.parameter_name]:.4f} found after {len(self.history_df)} searches.",
+ verbosity="progress",
+ )
+
+ def plot(self):
+ """Plot the value of the feature used to assess optimization progress against the parameter value, for each value tested."""
+ fig, ax = plt.subplots()
+
+ ax.vlines(
+ x=self.workflow.optimization_manager.__dict__[self.parameter_name],
+ ymin=0,
+ ymax=self.history_df.loc[self._find_index_of_optimum(), self.feature_name],
+ color="red",
+ zorder=0,
+ label=f"Optimal {self.parameter_name}",
+ )
+
+ sns.lineplot(
+ data=self.history_df,
+ x="parameter",
+ y=self.feature_name,
+ ax=ax,
+ )
+ sns.scatterplot(
+ data=self.history_df,
+ x="parameter",
+ y=self.feature_name,
+ ax=ax,
+ )
+
+ ax.set_xlabel(self.parameter_name)
+ ax.xaxis.set_inverted(True)
+ ax.set_ylim(bottom=0, top=self.history_df[self.feature_name].max() * 1.1)
+ ax.legend(loc="upper left")
+
+ plt.show()
+
+ def _propose_new_parameter(self, df: pd.DataFrame):
+ """This method specifies the rule according to which the search parameter is updated between rounds of optimization. The update rule is
+ 1) calculate the deviation of the predicted mz values from the observed mz values,
+ 2) take the mean of the endpoints of the central interval
+ (determined by the self.update_percentile_range attribute, which determines the percentile taken expressed as a decimal) of these deviations, and
+ 3) multiply this value by self.update_factor.
+ This is implemented by the ci method for the estimator.
+
+ Parameters
+ ----------
+
+ df: pd.DataFrame
+ The dataframe used to update the parameter. This could be the precursor or fragment dataframe, depending on the search parameter being optimized.
+
+ Returns
+ -------
+ float
+ The proposed new value for the search parameter.
+
+ """
+ return self.update_factor * self.workflow.calibration_manager.get_estimator(
+ self.estimator_group_name, self.estimator_name
+ ).ci(df, self.update_percentile_range)
+
+ def _update_history(self, precursors_df: pd.DataFrame, fragments_df: pd.DataFrame):
+ """This method updates the history dataframe with relevant values.
+
+ Parameters
+ ----------
+ precursors_df: pd.DataFrame
+ The filtered precursor dataframe for the search.
+
+ fragments_df: pd.DataFrame
+ The filtered fragment dataframe for the search.
+
+ """
+ new_row = pd.DataFrame(
+ [
+ {
+ "parameter": self.workflow.optimization_manager.__dict__[
+ self.parameter_name
+ ],
+ self.feature_name: self._get_feature_value(
+ precursors_df, fragments_df
+ ),
+ "classifier_version": self.workflow.fdr_manager.current_version,
+ "score_cutoff": self.workflow.optimization_manager.score_cutoff,
+ "fwhm_rt": self.workflow.optimization_manager.fwhm_rt,
+ "fwhm_mobility": self.workflow.optimization_manager.fwhm_mobility,
+ "batch_idx": self.workflow.optlock.batch_idx,
+ }
+ ]
+ )
+ self.history_df = pd.concat([self.history_df, new_row], ignore_index=True)
+
+ @property
+ def _batch_substantially_bigger(self):
+ """This function checks if the optimization has already been optimized sufficiently many times and if it has been skipped too many times at the current parameter value.
+ (Being skipped indicates that the current parameter proposal significantly reduces the number of identified precursors and is unlikely to be optimal.)
+
+ Returns
+ -------
+ bool
+ True if the optimization has already been performed the minimum number of times and the maximum number of skips has been reached, False otherwise.
+
+ """
+ min_steps_reached = (
+ self._num_prev_optimizations
+ >= self.workflow.config["calibration"]["min_steps"]
+ )
+ max_skips_reached = (
+ self._num_consecutive_skips
+ > self.workflow.config["calibration"]["max_skips"]
+ )
+ return min_steps_reached and max_skips_reached
+
+ @property
+ def _just_converged(self):
+ """Optimization should stop if continued narrowing of the parameter is not improving the feature value.
+ If self._try_narrower_values is False:
+ 1) This function checks if the previous rounds of optimization have led to a meaningful improvement in the feature value.
+ 2) If so, it continues optimization and appends the proposed new parameter to the list of parameters. If not, it stops optimization and sets the optimal parameter attribute.
+ If self._try_narrower_values is True:
+ 1) This function checks if the previous rounds of optimization have led to a meaningful disimprovement in the feature value or if the parameter has not changed substantially.
+ 2) If not, it continues optimization and appends the proposed new parameter to the list of parameters. If so, it stops optimization and sets the optimal parameter attribute.
+
+ Notes
+ -----
+ Because the check for an increase in feature value requires two previous rounds, the function will also initialize for another round of optimization if there have been fewer than 3 rounds.
+ This function may be overwritten in child classes.
+
+ """
+ if len(self.history_df) < 3:
+ return False
+
+ feature_history = self.history_df[self.feature_name]
+ last_feature_value = feature_history.iloc[-1]
+ second_last_feature_value = feature_history.iloc[-2]
+ third_last_feature_value = feature_history.iloc[-3]
+
+ if self._try_narrower_values: # This setting can be useful for optimizing parameters for which many parameter values have similar feature values.
+ min_steps_reached = (
+ self._num_prev_optimizations
+ >= self.workflow.config["calibration"]["min_steps"]
+ )
+
+ feature_substantially_decreased = (
+ last_feature_value - second_last_feature_value
+ ) / np.abs(second_last_feature_value) < -self._maximal_decrease and (
+ last_feature_value - third_last_feature_value
+ ) / np.abs(third_last_feature_value) < -self._maximal_decrease
+
+ parameter_history = self.history_df["parameter"]
+
+ last_parameter_value = parameter_history.iloc[-1]
+ second_last_parameter_value = parameter_history.iloc[-2]
+ parameter_not_substantially_changed = (
+ np.abs(
+ (last_parameter_value - second_last_parameter_value)
+ / second_last_parameter_value
+ )
+ < 0.05
+ )
+
+ return min_steps_reached and (
+ feature_substantially_decreased or parameter_not_substantially_changed
+ )
+
+ else:
+ min_steps_reached = (
+ self._num_prev_optimizations
+ >= self.workflow.config["calibration"]["min_steps"]
+ )
+
+ feature_not_substantially_increased = (
+ last_feature_value - second_last_feature_value
+ ) / np.abs(second_last_feature_value) < 0.1 and (
+ last_feature_value - third_last_feature_value
+ ) / np.abs(third_last_feature_value) < 0.1
+
+ return min_steps_reached and feature_not_substantially_increased
+
+ def _find_index_of_optimum(self):
+ """Finds the index of the row in the history dataframe with the optimal value of the feature used for optimization.
+ if self._favour_narrower_parameter is False:
+ The index at optimum is the index of the parameter value that maximizes the feature.
+ if self._favour_narrower_parameter is True:
+ The index at optimum is the index of the minimal parameter value whose feature value is at least self._maximum_decrease_from_maximum of the maximum value of the feature.
+
+ Returns
+ -------
+ int
+ The index of the row with the optimal value of the feature used for optimization.
+
+ Notes
+ -----
+ This method may be overwritten in child classes.
+
+ """
+
+ if self._favour_narrower_optimum: # This setting can be useful for optimizing parameters for which many parameter values have similar feature values.
+ maximum_feature_value = self.history_df[self.feature_name].max()
+ rows_within_thresh_of_max = self.history_df.loc[
+ self.history_df[self.feature_name]
+ > (
+ maximum_feature_value
+ - self._maximum_decrease_from_maximum
+ * np.abs(maximum_feature_value)
+ )
+ ]
+ index_of_optimum = rows_within_thresh_of_max["parameter"].idxmin()
+ return index_of_optimum
+
+ else:
+ return self.history_df[self.feature_name].idxmax()
+
+ def _update_workflow(self):
+ """Updates the optimization manager with the results of the optimization, namely:
+ the classifier version,
+ the optimal parameter,
+ score cutoff,
+ FWHM_RT,
+ and FWHM_mobility
+ at the optimal parameter. Also updates the optlock with the batch index at the optimum.
+
+ """
+ index_of_optimum = self._find_index_of_optimum()
+
+ optimal_parameter = self.history_df["parameter"].loc[index_of_optimum]
+ self.workflow.optimization_manager.fit({self.parameter_name: optimal_parameter})
+
+ classifier_version_at_optimum = self.history_df["classifier_version"].loc[
+ index_of_optimum
+ ]
+ self.workflow.optimization_manager.fit(
+ {"classifier_version": classifier_version_at_optimum}
+ )
+
+ score_cutoff_at_optimum = self.history_df["score_cutoff"].loc[index_of_optimum]
+ self.workflow.optimization_manager.fit(
+ {"score_cutoff": score_cutoff_at_optimum}
+ )
+
+ fwhm_rt_at_optimum = self.history_df["fwhm_rt"].loc[index_of_optimum]
+ self.workflow.optimization_manager.fit({"fwhm_rt": fwhm_rt_at_optimum})
+
+ fwhm_mobility_at_optimum = self.history_df["fwhm_mobility"].loc[
+ index_of_optimum
+ ]
+ self.workflow.optimization_manager.fit(
+ {"fwhm_mobility": fwhm_mobility_at_optimum}
+ )
+
+ batch_index_at_optimum = self.history_df["batch_idx"].loc[index_of_optimum]
+ # Take the batch index of the optimum, at the cost of potentially getting the batch library twice if this is the same as the current batch index.
+ # The time impact of this is negligible and the benefits can be significant.
+ self.workflow.optlock.batch_idx = batch_index_at_optimum
+
+ @abstractmethod
+ def _get_feature_value(
+ self, precursors_df: pd.DataFrame, fragments_df: pd.DataFrame
+ ):
+ """Each parameter is optimized according to a particular feature. This method gets the value of that feature for a given round of optimization.
+
+ Parameters
+ ----------
+
+ precursors_df: pd.DataFrame
+ The precursor dataframe for the search
+
+ fragments_df: pd.DataFrame
+ The fragment dataframe for the search
+
+
+ """
+ pass
+
+
+class TargetedOptimizer(BaseOptimizer):
+ def __init__(
+ self,
+ initial_parameter: float,
+ target_parameter: float,
+ workflow,
+ reporter: None | reporting.Pipeline | reporting.Backend = None,
+ ):
+ """This class optimizes the search parameter until it reaches a user-specified target value.
+
+ Parameters
+ ----------
+
+ initial_parameter: float
+ The parameter used for search in the first round of optimization.
+
+ target_parameter: float
+ Optimization will stop when this parameter is reached.
+
+ See base class for other parameters.
+
+ """
+ super().__init__(workflow, reporter)
+ self.workflow.optimization_manager.fit({self.parameter_name: initial_parameter})
+ self.target_parameter = target_parameter
+ self.update_factor = workflow.config["optimization"][self.parameter_name][
+ "targeted_update_factor"
+ ]
+ self.update_percentile_range = workflow.config["optimization"][
+ self.parameter_name
+ ]["targeted_update_percentile_range"]
+ self.has_converged = False
+
+ def _check_convergence(self, proposed_parameter: float):
+ """The optimization has converged if the proposed parameter is equal to or less than the target parameter and the a sufficient number of steps has been taken.
+
+ Parameters
+ ----------
+ proposed_parameter: float
+ The proposed parameter for the next round of optimization.
+
+ Returns
+ -------
+ bool
+ True if proposed parameter less than target and the current step is greater than the minimum required, False otherwise.
+
+
+ """
+ min_steps_reached = (
+ self._num_prev_optimizations
+ >= self.workflow.config["calibration"]["min_steps"]
+ )
+ return proposed_parameter <= self.target_parameter and min_steps_reached
+
+ def _propose_new_parameter(self, df: pd.DataFrame):
+ """See base class. The update rule is
+ 1) calculate the deviation of the predicted mz values from the observed mz values,
+ 2) take the mean of the endpoints of the central 95% of these deviations, and
+ 3) take the maximum of this value and the target parameter.
+ This is implemented by the ci method for the estimator.
+ """
+ return self.update_factor * max(
+ self.workflow.calibration_manager.get_estimator(
+ self.estimator_group_name, self.estimator_name
+ ).ci(df, self.update_percentile_range),
+ self.target_parameter,
+ )
+
+ def step(
+ self,
+ precursors_df: pd.DataFrame,
+ fragments_df: pd.DataFrame,
+ ):
+ """See base class."""
+ if self.has_converged:
+ self.reporter.log_string(
+ f"✅ {self.parameter_name:<15}: {self.workflow.optimization_manager.__dict__[self.parameter_name]:.4f} <= {self.target_parameter:.4f}",
+ verbosity="progress",
+ )
+ return
+ self._num_prev_optimizations += 1
+ new_parameter = self._propose_new_parameter(
+ precursors_df if self.estimator_group_name == "precursor" else fragments_df
+ )
+ just_converged = self._check_convergence(new_parameter)
+ self.workflow.optimization_manager.fit({self.parameter_name: new_parameter})
+ self.workflow.optimization_manager.fit(
+ {"classifier_version": self.workflow.fdr_manager.current_version}
+ )
+
+ if just_converged:
+ self.has_converged = True
+ self.reporter.log_string(
+ f"✅ {self.parameter_name:<15}: {self.workflow.optimization_manager.__dict__[self.parameter_name]:.4f} <= {self.target_parameter:.4f}",
+ verbosity="progress",
+ )
+
+ else:
+ self.reporter.log_string(
+ f"❌ {self.parameter_name:<15}: {self.workflow.optimization_manager.__dict__[self.parameter_name]:.4f} > {self.target_parameter:.4f} or insufficient steps taken.",
+ verbosity="progress",
+ )
+
+ def skip(self):
+ """See base class."""
+ pass
+
+ def plot(self):
+ """See base class"""
+ pass
+
+ def _update_workflow(self, new_parameter: float):
+ pass
+
+
+class AutomaticRTOptimizer(AutomaticOptimizer):
+ def __init__(
+ self,
+ initial_parameter: float,
+ workflow,
+ reporter: None | reporting.Pipeline | reporting.Backend = None,
+ ):
+ """See base class. Optimizes retention time error."""
+ self.parameter_name = "rt_error"
+ self.estimator_group_name = "precursor"
+ self.estimator_name = "rt"
+ self.feature_name = "precursor_proportion_detected"
+ super().__init__(initial_parameter, workflow, reporter)
+
+ def _get_feature_value(
+ self, precursors_df: pd.DataFrame, fragments_df: pd.DataFrame
+ ):
+ return len(precursors_df) / self.workflow.optlock.total_elution_groups
+
+
+class AutomaticMS2Optimizer(AutomaticOptimizer):
+ def __init__(
+ self,
+ initial_parameter: float,
+ workflow,
+ reporter: None | reporting.Pipeline | reporting.Backend = None,
+ ):
+ """See base class. This class automatically optimizes the MS2 tolerance parameter by tracking the number of precursor identifications and stopping when further changes do not increase this number."""
+ self.parameter_name = "ms2_error"
+ self.estimator_group_name = "fragment"
+ self.estimator_name = "mz"
+ self.feature_name = "precursor_proportion_detected"
+ super().__init__(initial_parameter, workflow, reporter)
+
+ def _get_feature_value(
+ self, precursors_df: pd.DataFrame, fragments_df: pd.DataFrame
+ ):
+ return len(precursors_df) / self.workflow.optlock.total_elution_groups
+
+
+class AutomaticMS1Optimizer(AutomaticOptimizer):
+ def __init__(
+ self,
+ initial_parameter: float,
+ workflow,
+ reporter: None | reporting.Pipeline | reporting.Backend = None,
+ ):
+ """See base class. Optimizes MS1 error."""
+ self.parameter_name = "ms1_error"
+ self.estimator_group_name = "precursor"
+ self.estimator_name = "mz"
+ self.feature_name = "mean_isotope_intensity_correlation"
+ super().__init__(initial_parameter, workflow, reporter)
+
+ def _get_feature_value(
+ self, precursors_df: pd.DataFrame, fragments_df: pd.DataFrame
+ ):
+ return precursors_df.isotope_intensity_correlation.mean()
+
+
+class AutomaticMobilityOptimizer(AutomaticOptimizer):
+ def __init__(
+ self,
+ initial_parameter: float,
+ workflow,
+ reporter: None | reporting.Pipeline | reporting.Backend = None,
+ ):
+ """See base class. Optimizes mobility error."""
+ self.parameter_name = "mobility_error"
+ self.estimator_group_name = "precursor"
+ self.estimator_name = "mobility"
+ self.feature_name = "precursor_proportion_detected"
+ super().__init__(initial_parameter, workflow, reporter)
+
+ def _get_feature_value(
+ self, precursors_df: pd.DataFrame, fragments_df: pd.DataFrame
+ ):
+ return len(precursors_df) / self.workflow.optlock.total_elution_groups
+
+
+class TargetedRTOptimizer(TargetedOptimizer):
+ def __init__(
+ self,
+ initial_parameter: float,
+ target_parameter: float,
+ workflow,
+ reporter: None | reporting.Pipeline | reporting.Backend = None,
+ ):
+ """See base class."""
+ self.parameter_name = "rt_error"
+ self.estimator_group_name = "precursor"
+ self.estimator_name = "rt"
+ super().__init__(initial_parameter, target_parameter, workflow, reporter)
+
+
+class TargetedMS2Optimizer(TargetedOptimizer):
+ def __init__(
+ self,
+ initial_parameter: float,
+ target_parameter: float,
+ workflow,
+ reporter: None | reporting.Pipeline | reporting.Backend = None,
+ ):
+ """See base class."""
+ self.parameter_name = "ms2_error"
+ self.estimator_group_name = "fragment"
+ self.estimator_name = "mz"
+ super().__init__(initial_parameter, target_parameter, workflow, reporter)
+
+
+class TargetedMS1Optimizer(TargetedOptimizer):
+ def __init__(
+ self,
+ initial_parameter: float,
+ target_parameter: float,
+ workflow,
+ reporter: None | reporting.Pipeline | reporting.Backend = None,
+ ):
+ """See base class."""
+ self.parameter_name = "ms1_error"
+ self.estimator_group_name = "precursor"
+ self.estimator_name = "mz"
+ super().__init__(initial_parameter, target_parameter, workflow, reporter)
+
+
+class TargetedMobilityOptimizer(TargetedOptimizer):
+ def __init__(
+ self,
+ initial_parameter: float,
+ target_parameter: float,
+ workflow,
+ reporter: None | reporting.Pipeline | reporting.Backend = None,
+ ):
+ """See base class."""
+ self.parameter_name = "mobility_error"
+ self.estimator_group_name = "precursor"
+ self.estimator_name = "mobility"
+ super().__init__(initial_parameter, target_parameter, workflow, reporter)
+
+
+class OptimizationLock:
+ def __init__(self, library: SpecLibFlat, config: dict):
+ """Sets and updates the optimization lock, which is the data used for calibration and optimization of the search parameters.
+
+ Parameters
+ ----------
+ library: alphabase.spectral_library.flat.SpecLibFlat
+ The library object from the PeptideCentricWorkflow object, which includes the precursor and fragment library dataframes.
+
+ config: dict
+ The configuration dictionary from the PeptideCentricWorkflow object.
+ """
+ self._library = library
+ self._config = config
+
+ self.previously_calibrated = False
+ self.has_target_num_precursors = False
+
+ self.elution_group_order = library._precursor_df["elution_group_idx"].unique()
+ rng = np.random.default_rng(seed=772)
+ rng.shuffle(self.elution_group_order)
+
+ self.target_count = self._config["calibration"]["optimization_lock_target"]
+
+ self.batch_idx = 0
+ self.set_batch_plan()
+
+ eg_idxes = self.elution_group_order[self.start_idx : self.stop_idx]
+ self.set_batch_dfs(eg_idxes)
+
+ self.feature_dfs = []
+ self.fragment_dfs = []
+
+ def _get_exponential_batches(self, step):
+ """Get the number of batches for a given step
+ This plan has the shape:
+ 1, 2, 4, 8, 16, 32, 64, ...
+ """
+ return int(2**step)
+
+ def set_batch_plan(self):
+ """Gets an exponential batch plan based on the batch_size value in the config."""
+ n_eg = self._library._precursor_df["elution_group_idx"].nunique()
+
+ plan = []
+
+ batch_size = self._config["calibration"]["batch_size"]
+ step = 0
+ start_idx = 0
+
+ while start_idx < n_eg:
+ n_batches = self._get_exponential_batches(step)
+ stop_idx = min(start_idx + n_batches * batch_size, n_eg)
+ plan.append((start_idx, stop_idx))
+ step += 1
+ start_idx = stop_idx
+
+ self.batch_plan = plan
+
+ def update_with_extraction(
+ self, feature_df: pd.DataFrame, fragment_df: pd.DataFrame
+ ):
+ """Extract features and fragments from the current batch of the optimization lock.
+
+ Parameters
+ ----------
+ feature_df: pd.DataFrame
+ The feature dataframe for the current batch of the optimization lock (from workflow.extract_batch).
+
+ fragment_df: pd.DataFrame
+ The fragment dataframe for the current batch of the optimization lock (from workflow.extract_batch).
+ """
+
+ self.feature_dfs += [feature_df]
+ self.fragment_dfs += [fragment_df]
+
+ self.total_elution_groups = self.features_df.elution_group_idx.nunique()
+
+ def update_with_fdr(self, precursor_df: pd.DataFrame):
+ """Calculates the number of precursors at 1% FDR for the current optimization lock and determines if it is sufficient to perform calibration and optimization.
+
+ Parameters
+ ----------
+ precursor_df: pd.DataFrame
+ The precursor dataframe for the current batch of the optimization lock (from workflow.perform_fdr).
+ """
+
+ self.count = len(precursor_df[precursor_df["qval"] < 0.01])
+
+ self.has_target_num_precursors = self.count >= self.target_count
+
+ def update_with_calibration(self, calibration_manager):
+ """Updates the batch library with the current calibrated values using the calibration manager.
+
+ Parameters
+ ----------
+ calibration_manager: manager.CalibrationManager
+ The calibration manager object from the PeptideCentricWorkflow object.
+
+ """
+ calibration_manager.predict(
+ self.batch_library._precursor_df,
+ "precursor",
+ )
+
+ calibration_manager.predict(self.batch_library._fragment_df, "fragment")
+
+ def increase_batch_idx(self):
+ """If the optimization lock does not contain enough precursors at 1% FDR, the optimization lock proceeds to include the next step in the batch plan in the library attribute.
+ This is done by incrementing self.batch_idx.
+ """
+ self.batch_idx += 1
+
+ def decrease_batch_idx(self):
+ """If the optimization lock contains enough precursors at 1% FDR, checks if enough precursors can be obtained using a smaller library and updates the library attribute accordingly.
+ If not, the same library is used as before.
+ This is done by taking the smallest step in the batch plan which gives more precursors than the target number of precursors.
+ """
+
+ batch_plan_diff = np.array(
+ [
+ stop_at_given_idx - self.stop_idx * self.target_count / self.count
+ for _, stop_at_given_idx in self.batch_plan
+ ]
+ ) # Calculate the difference between the number of precursors expected at the given idx and the target number of precursors for each idx in the batch plan.
+ smallest_value = np.min(
+ batch_plan_diff[batch_plan_diff > 0]
+ ) # Take the smallest positive difference (i.e. the smallest idx that is expected to yield more than the target number of precursors).
+ self.batch_idx = np.where(batch_plan_diff == smallest_value)[0][
+ 0
+ ] # Set the batch idx to the index of the smallest positive difference.
+
+ def update(self):
+ """Updates the library to use for the next round of optimization, either adjusting it upwards or downwards depending on whether the target has been reached.
+ If the target has been reached, the feature and fragment dataframes are reset
+ """
+ if not self.has_target_num_precursors:
+ self.increase_batch_idx()
+
+ else:
+ self.decrease_batch_idx()
+ self.feature_dfs = []
+ self.fragment_dfs = []
+
+ eg_idxes = self.elution_group_order[self.start_idx : self.stop_idx]
+ self.set_batch_dfs(eg_idxes)
+
+ def reset_after_convergence(self, calibration_manager):
+ """Resets the optimization lock after all optimizers in a given round of optimization have converged.
+
+ Parameter
+ ---------
+ calibration_manager: manager.CalibrationManager
+ The calibration manager object from the PeptideCentricWorkflow object.
+
+ """
+ self.has_target_num_precursors = True
+ self.feature_dfs = []
+ self.fragment_dfs = []
+ self.set_batch_dfs()
+ self.update_with_calibration(calibration_manager)
+
+ def set_batch_dfs(self, eg_idxes: None | np.ndarray = None):
+ """
+ Sets the batch library to use for the next round of optimization, either adjusting it upwards or downwards depending on whether the target has been reached.
+
+ Parameters
+ ----------
+ eg_idxes: None | np.ndarray
+ The elution group indexes to use for the next round of optimization. If None, the eg_idxes for the current self.start_idx and self.stop_idx are used.
+ """
+ if eg_idxes is None:
+ eg_idxes = self.elution_group_order[self.start_idx : self.stop_idx]
+ self.batch_library = SpecLibFlat()
+ self.batch_library._precursor_df, (self.batch_library._fragment_df,) = (
+ remove_unused_fragments(
+ self._library._precursor_df[
+ self._library._precursor_df["elution_group_idx"].isin(eg_idxes)
+ ],
+ (self._library._fragment_df,),
+ frag_start_col="flat_frag_start_idx",
+ frag_stop_col="flat_frag_stop_idx",
+ )
+ )
+
+ @property
+ def features_df(self) -> pd.DataFrame:
+ return pd.concat(self.feature_dfs)
+
+ @property
+ def fragments_df(self) -> pd.DataFrame:
+ return pd.concat(self.fragment_dfs)
+
+ @property
+ def start_idx(self) -> int:
+ if self.has_target_num_precursors:
+ return 0
+ elif self.batch_idx >= len(self.batch_plan):
+ raise NoOptimizationLockTargetError() # This should never be triggered since introduction of the BaseOptimizer.proceed_with_insufficient_precursors method and associated code, and could be removed.
+ else:
+ return self.batch_plan[self.batch_idx][0]
+
+ @property
+ def stop_idx(self) -> int:
+ return self.batch_plan[self.batch_idx][1]
diff --git a/alphadia/workflow/peptidecentric.py b/alphadia/workflow/peptidecentric.py
index c07c6cdc..31b8ad1f 100644
--- a/alphadia/workflow/peptidecentric.py
+++ b/alphadia/workflow/peptidecentric.py
@@ -16,7 +16,7 @@
# alphadia imports
from alphadia import fragcomp, plexscoring, utils
from alphadia.peakgroup import search
-from alphadia.workflow import base, manager
+from alphadia.workflow import base, manager, optimization
logger = logging.getLogger()
@@ -98,12 +98,6 @@
)
-class CalibrationError(Exception):
- """Raised when calibration fails"""
-
- pass
-
-
class PeptideCentricWorkflow(base.WorkflowBase):
def __init__(
self,
@@ -114,6 +108,7 @@ def __init__(
instance_name,
config,
)
+ self.optlock = None
def load(
self,
@@ -129,46 +124,9 @@ def load(
f"Initializing workflow {self.instance_name}", verbosity="progress"
)
- self.init_calibration_optimization_manager()
self.init_fdr_manager()
self.init_spectral_library()
- @property
- def calibration_optimization_manager(self):
- """Is used during the iterative optimization of the calibration parameters.
- Should not be stored on disk.
- """
- return self._calibration_optimization_manager
-
- @property
- def com(self):
- """alias for calibration_optimization_manager"""
- return self.calibration_optimization_manager
-
- def init_calibration_optimization_manager(self):
- self._calibration_optimization_manager = manager.OptimizationManager(
- {
- "current_epoch": 0,
- "current_step": 0,
- "ms1_error": self.config["search_initial"]["initial_ms1_tolerance"],
- "ms2_error": self.config["search_initial"]["initial_ms2_tolerance"],
- "rt_error": self.config["search_initial"]["initial_rt_tolerance"],
- "mobility_error": self.config["search_initial"][
- "initial_mobility_tolerance"
- ],
- "column_type": "library",
- "num_candidates": self.config["search_initial"][
- "initial_num_candidates"
- ],
- "recalibration_target": self.config["calibration"][
- "recalibration_target"
- ],
- "accumulated_precursors": 0,
- "accumulated_precursors_01FDR": 0,
- "accumulated_precursors_001FDR": 0,
- }
- )
-
def init_fdr_manager(self):
self.fdr_manager = manager.FDRManager(
feature_columns=feature_columns,
@@ -299,318 +257,430 @@ def norm_to_rt(
else:
raise ValueError(f"Unknown norm_rt_mode {mode}")
- def get_exponential_batches(self, step):
- """Get the number of batches for a given step
- This plan has the shape:
- 1, 2, 4, 8, 16, 32, 64, ...
- """
- return int(2**step)
-
- def get_batch_plan(self):
- n_eg = self.spectral_library._precursor_df["elution_group_idx"].nunique()
-
- plan = []
-
- batch_size = self.config["calibration"]["batch_size"]
- step = 0
- start_index = 0
-
- while start_index < n_eg:
- n_batches = self.get_exponential_batches(step)
- stop_index = min(start_index + n_batches * batch_size, n_eg)
- plan.append((start_index, stop_index))
- step += 1
- start_index = stop_index
-
- return plan
+ def get_ordered_optimizers(self):
+ """Select appropriate optimizers. Targeted optimization is used if a valid target value (i.e. a number greater than 0) is specified in the config;
+ if a value less than or equal to 0 is supplied, automatic optimization is used.
+ Targeted optimizers are run simultaneously; automatic optimizers are run separately in the order MS2, RT, MS1, mobility.
+ This order is built into the structure of the returned list of lists, ordered_optimizers.
+ For MS1 and mobility, the relevant optimizer will be excluded from the returned list of lists if it is not present in the data.
- def start_of_calibration(self):
- self.batch_plan = self.get_batch_plan()
-
- def start_of_epoch(self, current_epoch):
- self.com.current_epoch = current_epoch
-
- # if self.neptune is not None:
- # self.neptune["eval/epoch"].log(current_epoch)
-
- self.elution_group_order = self.spectral_library.precursor_df[
- "elution_group_idx"
- ].unique()
- np.random.shuffle(self.elution_group_order)
-
- self.calibration_manager.predict(
- self.spectral_library._precursor_df, "precursor"
- )
- self.calibration_manager.predict(self.spectral_library._fragment_df, "fragment")
-
- # make updates to the progress dict depending on the epoch
- if self.com.current_epoch > 0:
- self.com.recalibration_target = self.config["calibration"][
- "recalibration_target"
- ] * (1 + current_epoch)
-
- def start_of_step(self, current_step, start_index, stop_index):
- self.com.current_step = current_step
- # if self.neptune is not None:
- # self.neptune["eval/step"].log(current_step)
-
- # for key, value in self.com.__dict__.items():
- # self.neptune[f"eval/{key}"].log(value)
-
- self.reporter.log_string(
- f"=== Epoch {self.com.current_epoch}, step {current_step}, extracting elution groups {start_index} to {stop_index} ===",
- verbosity="progress",
- )
-
- def check_epoch_conditions(self):
- continue_calibration = False
+ Returns
+ -------
+ ordered_optimizers : list
+ List of lists of optimizers
- self.reporter.log_string(
- "=== checking if epoch conditions were reached ===", verbosity="info"
- )
- if self.dia_data.has_ms1:
- if self.com.ms1_error > self.config["search"]["target_ms1_tolerance"]:
- self.reporter.log_string(
- f"❌ {'ms1_error':<15}: {self.com.ms1_error:.4f} > {self.config['search']['target_ms1_tolerance']}",
- verbosity="info",
- )
- continue_calibration = True
- else:
- self.reporter.log_string(
- f"✅ {'ms1_error':<15}: {self.com.ms1_error:.4f} <= {self.config['search']['target_ms1_tolerance']}",
- verbosity="info",
- )
+ """
+ config_search = self.config["search"]
- if self.com.ms2_error > self.config["search"]["target_ms2_tolerance"]:
- self.reporter.log_string(
- f"❌ {'ms2_error':<15}: {self.com.ms2_error:.4f} > {self.config['search']['target_ms2_tolerance']}",
- verbosity="info",
+ if config_search["target_ms2_tolerance"] > 0:
+ ms2_optimizer = optimization.TargetedMS2Optimizer(
+ self.optimization_manager.ms2_error,
+ config_search["target_ms2_tolerance"],
+ self,
)
- continue_calibration = True
else:
- self.reporter.log_string(
- f"✅ {'ms2_error':<15}: {self.com.ms2_error:.4f} <= {self.config['search']['target_ms2_tolerance']}",
- verbosity="info",
+ ms2_optimizer = optimization.AutomaticMS2Optimizer(
+ self.optimization_manager.ms2_error,
+ self,
)
- if self.com.rt_error > self.config["search"]["target_rt_tolerance"]:
- self.reporter.log_string(
- f"❌ {'rt_error':<15}: {self.com.rt_error:.4f} > {self.config['search']['target_rt_tolerance']}",
- verbosity="info",
+ if config_search["target_rt_tolerance"] > 0:
+ gradient_length = self.dia_data.rt_values.max()
+ target_rt_error = (
+ config_search["target_rt_tolerance"]
+ if config_search["target_rt_tolerance"] > 1
+ else config_search["target_rt_tolerance"] * gradient_length
+ )
+ rt_optimizer = optimization.TargetedRTOptimizer(
+ self.optimization_manager.rt_error,
+ target_rt_error,
+ self,
)
- continue_calibration = True
else:
- self.reporter.log_string(
- f"✅ {'rt_error':<15}: {self.com.rt_error:.4f} <= {self.config['search']['target_rt_tolerance']}",
- verbosity="info",
+ rt_optimizer = optimization.AutomaticRTOptimizer(
+ self.optimization_manager.rt_error,
+ self,
)
-
+ if self.dia_data.has_ms1:
+ if config_search["target_ms1_tolerance"] > 0:
+ ms1_optimizer = optimization.TargetedMS1Optimizer(
+ self.optimization_manager.ms1_error,
+ config_search["target_ms1_tolerance"],
+ self,
+ )
+ else:
+ ms1_optimizer = optimization.AutomaticMS1Optimizer(
+ self.optimization_manager.ms1_error,
+ self,
+ )
+ else:
+ ms1_optimizer = None
if self.dia_data.has_mobility:
- if (
- self.com.mobility_error
- > self.config["search"]["target_mobility_tolerance"]
- ):
- self.reporter.log_string(
- f"❌ {'mobility_error':<15}: {self.com.mobility_error:.4f} > {self.config['search']['target_mobility_tolerance']}",
- verbosity="info",
+ if config_search["target_mobility_tolerance"] > 0:
+ mobility_optimizer = optimization.TargetedMobilityOptimizer(
+ self.optimization_manager.mobility_error,
+ config_search["target_mobility_tolerance"],
+ self,
)
- continue_calibration = True
else:
- self.reporter.log_string(
- f"✅ {'mobility_error':<15}: {self.com.mobility_error:.4f} <= {self.config['search']['target_mobility_tolerance']}",
- verbosity="info",
+ mobility_optimizer = optimization.AutomaticMobilityOptimizer(
+ self.optimization_manager.mobility_error,
+ self,
)
+ else:
+ mobility_optimizer = None
+
+ if self.config["optimization"]["order_of_optimization"] is None:
+ optimizers = [
+ ms2_optimizer,
+ rt_optimizer,
+ ms1_optimizer,
+ mobility_optimizer,
+ ]
+ targeted_optimizers = [
+ [
+ optimizer
+ for optimizer in optimizers
+ if isinstance(optimizer, optimization.TargetedOptimizer)
+ ]
+ ]
+ automatic_optimizers = [
+ [optimizer]
+ for optimizer in optimizers
+ if isinstance(optimizer, optimization.AutomaticOptimizer)
+ ]
- if self.com.current_epoch < self.config["calibration"]["min_epochs"] - 1:
- self.reporter.log_string(
- f"❌ {'current_epoch':<15}: {self.com.current_epoch} < {self.config['calibration']['min_epochs']}",
- verbosity="info",
+ ordered_optimizers = (
+ targeted_optimizers + automatic_optimizers
+ if any(
+ targeted_optimizers
+ ) # This line is required so no empty list is added to the ordered_optimizers list
+ else automatic_optimizers
)
- continue_calibration = True
else:
- self.reporter.log_string(
- f"✅ {'current_epoch':<15}: {self.com.current_epoch} >= {self.config['calibration']['min_epochs']}",
- verbosity="info",
- )
+ opt_mapping = {
+ "ms2_error": ms2_optimizer,
+ "rt_error": rt_optimizer,
+ "ms1_error": ms1_optimizer,
+ "mobility_error": mobility_optimizer,
+ }
+ ordered_optimizers = []
+ for optimizers_in_ordering in self.config["optimization"][
+ "order_of_optimization"
+ ]:
+ ordered_optimizers += [
+ [
+ opt_mapping[opt]
+ for opt in optimizers_in_ordering
+ if opt_mapping[opt] is not None
+ ]
+ ]
- self.reporter.log_string(
- "==============================================", verbosity="info"
- )
- return continue_calibration
+ return ordered_optimizers
+
+ def search_parameter_optimization(self):
+ """Performs optimization of the search parameters. This occurs in two stages:
+ 1) Optimization lock: the data are searched to acquire a locked set of precursors which is used for search parameter optimization. The classifier is also trained during this stage.
+ 2) Optimization loop: the search parameters are optimized iteratively using the locked set of precursors.
+ In each iteration, the data are searched with the locked library from stage 1, and the properties -- m/z for both precursors and fragments (i.e. MS1 and MS2), RT and mobility -- are recalibrated.
+ The optimization loop is repeated for each list of optimizers in ordered_optimizers.
- def calibration(self):
+ """
+ log_string = self.reporter.log_string
+ # First check to see if the calibration has already been performed. Return if so.
if (
self.calibration_manager.is_fitted
and self.calibration_manager.is_loaded_from_file
):
- self.reporter.log_string(
+ log_string(
"Skipping calibration as existing calibration was found",
verbosity="progress",
)
return
- self.start_of_calibration()
- for current_epoch in range(self.config["calibration"]["max_epochs"]):
- if self.check_epoch_conditions():
- pass
- else:
+ # Get the order of optimization
+ ordered_optimizers = self.get_ordered_optimizers()
+
+ log_string(
+ "Starting initial search for precursors.",
+ verbosity="progress",
+ )
+
+ self.optlock = optimization.OptimizationLock(self.spectral_library, self.config)
+ insufficient_precursors_to_optimize = False
+ # Start of optimization/recalibration loop
+ for optimizers in ordered_optimizers:
+ if insufficient_precursors_to_optimize:
break
+ for current_step in range(
+ self.config["calibration"]["max_steps"]
+ ): # Note current_step here refers to a different step than the attribute of the same name in the optimizer -- this should be rectified
+ if np.all([optimizer.has_converged for optimizer in optimizers]):
+ log_string(
+ f"Optimization finished for {', '.join([optimizer.parameter_name for optimizer in optimizers])}.",
+ verbosity="progress",
+ )
- self.start_of_epoch(current_epoch)
+ self.optlock.reset_after_convergence(self.calibration_manager)
- features = []
- fragments = []
- for current_step, (start_index, stop_index) in enumerate(self.batch_plan):
- self.start_of_step(current_step, start_index, stop_index)
+ for optimizer in optimizers:
+ optimizer.plot()
- eg_idxes = self.elution_group_order[start_index:stop_index]
- batch_df = self.spectral_library._precursor_df[
- self.spectral_library._precursor_df["elution_group_idx"].isin(
- eg_idxes
+ break
+
+ log_string(f"Starting optimization step {current_step}.")
+
+ precursor_df = self._process_batch()
+
+ if not self.optlock.has_target_num_precursors:
+ if self.optlock.batch_idx + 1 >= len(self.optlock.batch_plan):
+ insufficient_precursors_to_optimize = True
+ break
+ self.optlock.update()
+
+ if self.optlock.previously_calibrated:
+ self.optlock.update_with_calibration(
+ self.calibration_manager
+ ) # This is needed so that the addition to the batch libary has the most recent calibration
+
+ self._skip_all_optimizers(optimizers)
+
+ else:
+ precursor_df_filtered, fragments_df_filtered = self.filter_dfs(
+ precursor_df, self.optlock.fragments_df
)
- ]
- feature_df, fragment_df = self.extract_batch(batch_df)
- features += [feature_df]
- fragments += [fragment_df]
- features_df = pd.concat(features)
- fragments_df = pd.concat(fragments)
+ self.optlock.update()
+ self.recalibration(precursor_df_filtered, fragments_df_filtered)
+ self.optlock.update_with_calibration(self.calibration_manager)
- self.reporter.log_string(
- f"=== Epoch {self.com.current_epoch}, step {current_step}, extracted {len(feature_df)} precursors and {len(fragment_df)} fragments ===",
- verbosity="progress",
+ if not self.optlock.previously_calibrated: # Updates classifier but does not optimize the first time the target is reached.
+ # Optimization is more stable when done with calibrated values.
+ self._initiate_search_parameter_optimization()
+ continue
+
+ self._step_all_optimizers(
+ optimizers, precursor_df_filtered, fragments_df_filtered
+ )
+
+ else:
+ log_string(
+ f"Optimization did not converge within the maximum number of steps, which is {self.config['calibration']['max_steps']}.",
+ verbosity="warning",
)
- precursor_df = self.fdr_correction(features_df, fragments_df)
- if self.check_recalibration(precursor_df):
- self.recalibration(precursor_df, fragments_df)
- break
- else:
- # check if last step has been reached
- if current_step == len(self.batch_plan) - 1:
- self.reporter.log_string(
- "Searched all data without finding recalibration target",
- verbosity="error",
- )
- raise CalibrationError(
- "Searched all data without finding recalibration target"
- )
-
- self.end_of_epoch()
-
- if self.config["calibration"].get("final_full_calibration", False):
- self.reporter.log_string(
- "Performing final calibration with all precursors",
- verbosity="progress",
- )
- features_df, fragments_df = self.extract_batch(
- self.spectral_library._precursor_df
+ log_string(
+ "Search parameter optimization finished. Values taken forward for search are:",
+ verbosity="progress",
+ )
+ log_string(
+ "==============================================", verbosity="progress"
+ )
+ if insufficient_precursors_to_optimize:
+ precursor_df_filtered, fragments_df_filtered = self.filter_dfs(
+ precursor_df, self.optlock.fragments_df
)
- precursor_df = self.fdr_correction(features_df, fragments_df)
- self.recalibration(precursor_df, fragments_df)
+ if precursor_df_filtered.shape[0] >= 6:
+ self.recalibration(precursor_df_filtered, fragments_df_filtered)
+ for optimizers in ordered_optimizers:
+ for optimizer in optimizers:
+ optimizer.proceed_with_insufficient_precursors(
+ precursor_df_filtered, self.optlock.fragments_df
+ )
- self.end_of_calibration()
+ for optimizers in ordered_optimizers:
+ for optimizer in optimizers:
+ log_string(
+ f"{optimizer.parameter_name:<15}: {self.optimization_manager.__dict__[optimizer.parameter_name]:.4f}",
+ verbosity="progress",
+ )
+ log_string(
+ "==============================================", verbosity="progress"
+ )
- def end_of_epoch(self):
- pass
+ self.save_managers()
- def end_of_calibration(self):
- # self.calibration_manager.predict(self.spectral_library._precursor_df, 'precursor')
- # self.calibration_manager.predict(self.spectral_library._fragment_df, 'fragment')
- self.calibration_manager.save()
- pass
+ def _process_batch(self):
+ """Extracts precursors and fragments from the spectral library, performs FDR correction and logs the precursor dataframe."""
+ self.reporter.log_string(
+ f"=== Extracting elution groups {self.optlock.start_idx} to {self.optlock.stop_idx} ===",
+ verbosity="progress",
+ )
- def recalibration(self, precursor_df, fragments_df):
- precursor_df_filtered = precursor_df[precursor_df["qval"] < 0.01]
- precursor_df_filtered = precursor_df_filtered[
- precursor_df_filtered["decoy"] == 0
- ]
+ feature_df, fragment_df = self.extract_batch(
+ self.optlock.batch_library.precursor_df,
+ self.optlock.batch_library.fragment_df,
+ )
+ self.optlock.update_with_extraction(feature_df, fragment_df)
- self.calibration_manager.fit(
- precursor_df_filtered,
- "precursor",
- plot=True,
- skip=["mz"] if not self.dia_data.has_ms1 else [],
- # neptune_run = self.neptune
+ self.reporter.log_string(
+ f"=== Extracted {len(self.optlock.features_df)} precursors and {len(self.optlock.fragments_df)} fragments ===",
+ verbosity="progress",
)
- rt_99 = self.calibration_manager.get_estimator("precursor", "rt").ci(
- precursor_df_filtered, 0.95
+ precursor_df = self.fdr_correction(
+ self.optlock.features_df,
+ self.optlock.fragments_df,
+ self.optimization_manager.classifier_version,
)
+ self.optlock.update_with_fdr(precursor_df)
+
+ self.reporter.log_string(
+ f"=== FDR correction performed with classifier version {self.optimization_manager.classifier_version} ===",
+ )
+
+ self.log_precursor_df(precursor_df)
+
+ return precursor_df
+
+ def _initiate_search_parameter_optimization(self):
+ """Saves the classifier version just before search parameter optimization begins and updates the optimization lock to show that calibration has been performed."""
+ self.optlock.previously_calibrated = True
+ self.optimization_manager.fit(
+ {"classifier_version": self.fdr_manager.current_version}
+ )
+ self.reporter.log_string(
+ "Required number of precursors found. Starting search parameter optimization.",
+ verbosity="progress",
+ )
+
+ def _step_all_optimizers(
+ self,
+ optimizers: list[optimization.BaseOptimizer],
+ precursor_df_filtered: pd.DataFrame,
+ fragments_df_filtered: pd.DataFrame,
+ ):
+ """All optimizers currently in use are stepped and their current state is logged.
+
+ Parameters
+ ----------
+ optimizers : list
+ List of optimizers to be stepped.
+
+ precursor_df_filtered : pd.DataFrame
+ Filtered precursor dataframe (see filter_dfs).
+
+ fragments_df_filtered : pd.DataFrame
+ Filtered fragment dataframe (see filter_dfs).
+ """
+ self.reporter.log_string(
+ "=== checking if optimization conditions were reached ===",
+ )
+
+ for optimizer in optimizers:
+ optimizer.step(precursor_df_filtered, fragments_df_filtered)
+
+ self.reporter.log_string(
+ "==============================================",
+ )
+
+ def _skip_all_optimizers(
+ self,
+ optimizers: list[optimization.BaseOptimizer],
+ ):
+ """All optimizers currently in use are stepped and their current state is logged.
+
+ Parameters
+ ----------
+ optimizers : list
+ List of optimizers to be stepped.
+
+ """
+ self.reporter.log_string(
+ "=== skipping optimization until target number of precursors are found ===",
+ )
+
+ for optimizer in optimizers:
+ optimizer.skip()
+
+ def filter_dfs(self, precursor_df, fragments_df):
+ """Filters precursor and fragment dataframes to extract the most reliable examples for calibration.
+
+ Parameters
+ ----------
+ precursor_df : pd.DataFrame
+ Precursor dataframe after FDR correction.
+
+ fragments_df : pd.DataFrame
+ Fragment dataframe.
+
+ Returns
+ -------
+ precursor_df_filtered : pd.DataFrame
+ Filtered precursor dataframe. Decoy precursors and those found at worse than 1% FDR are removed from the precursor dataframe.
+
+ fragments_df_filtered : pd.DataFrame
+ Filtered fragment dataframe. Retained fragments must either:
+ 1) have a correlation greater than 0.7 and belong to the top 5000 fragments sorted by correlation, if there are more than 500 with a correlation greater than 0.7, or
+ 2) belong to the top 500 fragments sorted by correlation otherwise.
+
+ """
+ precursor_df_filtered = precursor_df[precursor_df["qval"] < 0.01]
+ precursor_df_filtered = precursor_df_filtered[
+ precursor_df_filtered["decoy"] == 0
+ ]
+
fragments_df_filtered = fragments_df[
fragments_df["precursor_idx"].isin(precursor_df_filtered["precursor_idx"])
]
- min_fragments = 500
- max_fragments = 5000
- min_correlation = 0.7
fragments_df_filtered = fragments_df_filtered.sort_values(
- by=["correlation"], ascending=False
+ by="correlation", ascending=False
)
- stop_rank = min(
- max(
- np.searchsorted(
- fragments_df_filtered["correlation"].values, min_correlation
- ),
- min_fragments,
- ),
- max_fragments,
- )
- fragments_df_filtered = fragments_df_filtered.iloc[:stop_rank]
+ # Determine the number of fragments to keep
+ min_fragments, max_fragments = (
+ 500,
+ self.config["calibration"]["max_fragments"],
+ ) # TODO remove min_fragments as it seems to have no effect
+ min_correlation = self.config["calibration"]["min_correlation"]
+
+ high_corr_count = (fragments_df_filtered["correlation"] > min_correlation).sum()
+ stop_rank = min(max(high_corr_count, min_fragments), max_fragments)
+
+ # Select top fragments
+ fragments_df_filtered = fragments_df_filtered.head(stop_rank)
+
+ self.reporter.log_string(f"fragments_df_filtered: {len(fragments_df_filtered)}")
+
+ return precursor_df_filtered, fragments_df_filtered
+
+ def recalibration(self, precursor_df_filtered, fragments_df_filtered):
+ """Performs recalibration of the the MS1, MS2, RT and mobility properties. Also fits the convolution kernel and the score cutoff.
+ The calibration manager is used to fit the data and predict the calibrated values.
+
+ Parameters
+ ----------
+ precursor_df_filtered : pd.DataFrame
+ Filtered precursor dataframe (see filter_dfs)
- print(f"fragments_df_filtered: {len(fragments_df_filtered)}")
+ fragments_df_filtered : pd.DataFrame
+ Filtered fragment dataframe (see filter_dfs)
+ """
self.calibration_manager.fit(
- fragments_df_filtered,
- "fragment",
+ precursor_df_filtered,
+ "precursor",
plot=True,
+ skip=["mz"] if not self.dia_data.has_ms1 else [],
# neptune_run = self.neptune
)
- m2_99 = self.calibration_manager.get_estimator("fragment", "mz").ci(
- fragments_df_filtered, 0.95
+ self.calibration_manager.fit(
+ fragments_df_filtered,
+ "fragment",
+ plot=True,
+ # neptune_run = self.neptune
)
- self.com.fit(
+ self.optimization_manager.fit(
{
- "ms2_error": max(m2_99, self.config["search"]["target_ms2_tolerance"]),
- "rt_error": max(rt_99, self.config["search"]["target_rt_tolerance"]),
"column_type": "calibrated",
"num_candidates": self.config["search"]["target_num_candidates"],
}
)
- if self.dia_data.has_ms1:
- m1_99 = self.calibration_manager.get_estimator("precursor", "mz").ci(
- precursor_df_filtered, 0.95
- )
- self.com.fit(
- {
- "ms1_error": max(
- m1_99, self.config["search"]["target_ms1_tolerance"]
- ),
- }
- )
-
- if self.dia_data.has_mobility:
- mobility_99 = self.calibration_manager.get_estimator(
- "precursor", "mobility"
- ).ci(precursor_df_filtered, 0.95)
- self.com.fit(
- {
- "mobility_error": max(
- mobility_99, self.config["search"]["target_mobility_tolerance"]
- ),
- }
- )
-
- # if self.neptune is not None:
- # self.neptune['eval/99_mobility_error'].log(mobility_99)
-
percentile_001 = np.percentile(precursor_df_filtered["score"], 0.1)
- print("score cutoff", percentile_001)
-
self.optimization_manager.fit(
{
"fwhm_rt": precursor_df_filtered["cycle_fwhm"].median(),
@@ -619,34 +689,7 @@ def recalibration(self, precursor_df, fragments_df):
}
)
- # if self.neptune is not None:
- # precursor_df_fdr = precursor_df_filtered[precursor_df_filtered['qval'] < 0.01]
- # self.neptune["eval/precursors"].log(len(precursor_df_fdr))
- # self.neptune['eval/99_ms1_error'].log(m1_99)
- # self.neptune['eval/99_ms2_error'].log(m2_99)
- # self.neptune['eval/99_rt_error'].log(rt_99)
-
- def check_recalibration(self, precursor_df):
- self.com.accumulated_precursors = len(precursor_df)
- self.com.accumulated_precursors_01FDR = len(
- precursor_df[precursor_df["qval"] < 0.01]
- )
-
- self.reporter.log_string(
- f"=== checking if recalibration conditions were reached, target {self.com.recalibration_target} precursors ===",
- verbosity="progress",
- )
-
- self.log_precursor_df(precursor_df)
-
- perform_recalibration = False
-
- if self.com.accumulated_precursors_01FDR > self.com.recalibration_target:
- perform_recalibration = True
-
- return perform_recalibration
-
- def fdr_correction(self, features_df, df_fragments):
+ def fdr_correction(self, features_df, df_fragments, version=-1):
return self.fdr_manager.fit_predict(
features_df,
decoy_strategy="precursor_channel_wise"
@@ -657,12 +700,18 @@ def fdr_correction(self, features_df, df_fragments):
if self.config["search"]["compete_for_fragments"]
else None,
dia_cycle=self.dia_data.cycle,
+ version=version,
# neptune_run=self.neptune
)
- def extract_batch(self, batch_df, apply_cutoff=False):
+ def extract_batch(
+ self, batch_precursor_df, batch_fragment_df=None, apply_cutoff=False
+ ):
+ if batch_fragment_df is None:
+ batch_fragment_df = self.spectral_library._fragment_df
self.reporter.log_string(
- f"Extracting batch of {len(batch_df)} precursors", verbosity="progress"
+ f"Extracting batch of {len(batch_precursor_df)} precursors",
+ verbosity="progress",
)
config = search.HybridCandidateConfig()
@@ -670,29 +719,49 @@ def extract_batch(self, batch_df, apply_cutoff=False):
config.update(
{
"top_k_fragments": self.config["search_advanced"]["top_k_fragments"],
- "rt_tolerance": self.com.rt_error,
- "mobility_tolerance": self.com.mobility_error,
- "candidate_count": self.com.num_candidates,
- "precursor_mz_tolerance": self.com.ms1_error,
- "fragment_mz_tolerance": self.com.ms2_error,
+ "rt_tolerance": self.optimization_manager.rt_error,
+ "mobility_tolerance": self.optimization_manager.mobility_error,
+ "candidate_count": self.optimization_manager.num_candidates,
+ "precursor_mz_tolerance": self.optimization_manager.ms1_error,
+ "fragment_mz_tolerance": self.optimization_manager.ms2_error,
"exclude_shared_ions": self.config["search"]["exclude_shared_ions"],
"min_size_rt": self.config["search"]["quant_window"],
}
)
+ self.reporter.log_string("=== Search parameters used === ", verbosity="debug")
+ self.reporter.log_string(
+ f"{'rt_tolerance':<15}: {config.rt_tolerance}", verbosity="debug"
+ )
+ self.reporter.log_string(
+ f"{'mobility_tolerance':<15}: {config.mobility_tolerance}",
+ verbosity="debug",
+ )
+ self.reporter.log_string(
+ f"{'precursor_mz_tolerance':<15}: {config.precursor_mz_tolerance}",
+ verbosity="debug",
+ )
+ self.reporter.log_string(
+ f"{'fragment_mz_tolerance':<15}: {config.fragment_mz_tolerance}",
+ verbosity="debug",
+ )
+ self.reporter.log_string(
+ "==============================================", verbosity="debug"
+ )
+
extraction = search.HybridCandidateSelection(
self.dia_data.jitclass(),
- batch_df,
- self.spectral_library.fragment_df,
+ batch_precursor_df,
+ batch_fragment_df,
config.jitclass(),
- rt_column=f"rt_{self.com.column_type}",
- mobility_column=f"mobility_{self.com.column_type}"
+ rt_column=f"rt_{self.optimization_manager.column_type}",
+ mobility_column=f"mobility_{self.optimization_manager.column_type}"
if self.dia_data.has_mobility
else "mobility_library",
- precursor_mz_column=f"mz_{self.com.column_type}"
+ precursor_mz_column=f"mz_{self.optimization_manager.column_type}"
if self.dia_data.has_ms1
else "mz_library",
- fragment_mz_column=f"mz_{self.com.column_type}",
+ fragment_mz_column=f"mz_{self.optimization_manager.column_type}",
fwhm_rt=self.optimization_manager.fwhm_rt,
fwhm_mobility=self.optimization_manager.fwhm_mobility,
)
@@ -704,7 +773,6 @@ def extract_batch(self, batch_df, apply_cutoff=False):
num_before = len(candidates_df)
self.reporter.log_string(
f"Applying score cutoff of {self.optimization_manager.score_cutoff}",
- verbosity="info",
)
candidates_df = candidates_df[
candidates_df["score"] > self.optimization_manager.score_cutoff
@@ -713,7 +781,6 @@ def extract_batch(self, batch_df, apply_cutoff=False):
num_removed = num_before - num_after
self.reporter.log_string(
f"Removed {num_removed} precursors with score below cutoff",
- verbosity="info",
)
config = plexscoring.CandidateConfig()
@@ -721,8 +788,8 @@ def extract_batch(self, batch_df, apply_cutoff=False):
config.update(
{
"top_k_fragments": self.config["search_advanced"]["top_k_fragments"],
- "precursor_mz_tolerance": self.com.ms1_error,
- "fragment_mz_tolerance": self.com.ms2_error,
+ "precursor_mz_tolerance": self.optimization_manager.ms1_error,
+ "fragment_mz_tolerance": self.optimization_manager.ms2_error,
"exclude_shared_ions": self.config["search"]["exclude_shared_ions"],
"quant_window": self.config["search"]["quant_window"],
"quant_all": self.config["search"]["quant_all"],
@@ -731,17 +798,17 @@ def extract_batch(self, batch_df, apply_cutoff=False):
candidate_scoring = plexscoring.CandidateScoring(
self.dia_data.jitclass(),
- self.spectral_library._precursor_df,
- self.spectral_library._fragment_df,
+ batch_precursor_df,
+ batch_fragment_df,
config=config,
- rt_column=f"rt_{self.com.column_type}",
- mobility_column=f"mobility_{self.com.column_type}"
+ rt_column=f"rt_{self.optimization_manager.column_type}",
+ mobility_column=f"mobility_{self.optimization_manager.column_type}"
if self.dia_data.has_mobility
else "mobility_library",
- precursor_mz_column=f"mz_{self.com.column_type}"
+ precursor_mz_column=f"mz_{self.optimization_manager.column_type}"
if self.dia_data.has_ms1
else "mz_library",
- fragment_mz_column=f"mz_{self.com.column_type}",
+ fragment_mz_column=f"mz_{self.optimization_manager.column_type}",
)
features_df, fragments_df = candidate_scoring(
@@ -752,18 +819,15 @@ def extract_batch(self, batch_df, apply_cutoff=False):
return features_df, fragments_df
- def extraction(self):
- self.com.fit(
- {
- "num_candidates": self.config["search"]["target_num_candidates"],
- "ms1_error": self.config["search"]["target_ms1_tolerance"],
- "ms2_error": self.config["search"]["target_ms2_tolerance"],
- "rt_error": self.config["search"]["target_rt_tolerance"],
- "mobility_error": self.config["search"]["target_mobility_tolerance"],
- "column_type": "calibrated",
- }
- )
+ def save_managers(self):
+ """Saves the calibration, optimization and FDR managers to disk so that they can be reused if needed.
+ Note the timing manager is not saved at this point as it is saved with every call to it.
+ The FDR manager is not saved because it is not used in subsequent parts of the workflow.
+ """
+ self.calibration_manager.save()
+ self.optimization_manager.save() # this replaces the .save() call when the optimization manager is fitted, since there seems little point in saving an intermediate optimization manager.
+ def extraction(self):
self.calibration_manager.predict(
self.spectral_library._precursor_df, "precursor"
)
@@ -774,7 +838,13 @@ def extraction(self):
apply_cutoff=True,
)
- precursor_df = self.fdr_correction(features_df, fragments_df)
+ self.reporter.log_string(
+ f"=== FDR correction performed with classifier version {self.optimization_manager.classifier_version} ===",
+ )
+
+ precursor_df = self.fdr_correction(
+ features_df, fragments_df, self.optimization_manager.classifier_version
+ )
precursor_df = precursor_df[precursor_df["qval"] <= self.config["fdr"]["fdr"]]
@@ -1036,7 +1106,6 @@ def requantify_fragments(
self.reporter.log_string(
f"creating library for charged fragment types: {fragment_types}",
- verbosity="info",
)
candidate_speclib_flat, scored_candidates = _build_candidate_speclib_flat(
@@ -1045,7 +1114,6 @@ def requantify_fragments(
self.reporter.log_string(
"Calibrating library",
- verbosity="info",
)
# calibrate
@@ -1056,7 +1124,6 @@ def requantify_fragments(
self.reporter.log_string(
f"quantifying {len(scored_candidates):,} precursors with {len(candidate_speclib_flat.fragment_df):,} fragments",
- verbosity="info",
)
config = plexscoring.CandidateConfig()
diff --git a/alphadia/workflow/reporting.py b/alphadia/workflow/reporting.py
index d08ec45d..c45211db 100644
--- a/alphadia/workflow/reporting.py
+++ b/alphadia/workflow/reporting.py
@@ -21,6 +21,8 @@
# As soon as its instantiated the default logger will be configured with a path to save the log file
__is_initiated__ = False
+from alphadia.exceptions import CustomError
+
# Add a new logging level to the default logger, level 21 is just above INFO (20)
# This has to happen at load time to make the .progress() method available even if no logger is instantiated
PROGRESS_LEVELV_NUM = 21
@@ -221,6 +223,8 @@ def log_figure(
name: str,
figure: Figure | np.ndarray,
extension: str = "png",
+ *args,
+ **kwargs,
):
"""Log a figure to the figures folder.
@@ -361,7 +365,14 @@ def __exit__(
self.entered_context = False
self.start_time = 0
- def log_event(self, name: str, value: typing.Any):
+ def log_event(
+ self,
+ name: str,
+ value: typing.Any,
+ exception: Exception | None = None,
+ *args,
+ **kwargs,
+ ):
"""Log an event to the `events.jsonl` file.
Important: This method will only log events if the backend is in a context.
@@ -379,19 +390,21 @@ def log_event(self, name: str, value: typing.Any):
if not self.entered_context:
return
+ message = {
+ "absolute_time": self.absolute_time(),
+ "relative_time": self.relative_time(),
+ "type": "event",
+ "name": name,
+ "value": value,
+ "verbosity": 0,
+ }
+ if exception is not None and isinstance(exception, CustomError):
+ message["error_code"] = exception.error_code
with open(self.events_path, "a") as f:
- message = {
- "absolute_time": self.absolute_time(),
- "relative_time": self.relative_time(),
- "type": "event",
- "name": name,
- "value": value,
- "verbosity": 0,
- }
f.write(json.dumps(message) + "\n")
- def log_metric(self, name: str, value: float):
+ def log_metric(self, name: str, value: float, *args, **kwargs):
"""Log a metric to the `events.jsonl` file.
Important: This method will only log metrics if the backend is in a context.
@@ -421,7 +434,7 @@ def log_metric(self, name: str, value: float):
}
f.write(json.dumps(message) + "\n")
- def log_string(self, value: str, verbosity: int = "info"):
+ def log_string(self, value: str, verbosity: int = "info", *args, **kwargs):
"""Log a string to the `events.jsonl` file.
Important: This method will only log strings if the backend is in a context.
@@ -449,9 +462,10 @@ def log_string(self, value: str, verbosity: int = "info"):
"value": value,
"verbosity": verbosity,
}
+
f.write(json.dumps(message) + "\n")
- def log_figure(self, name: str, figure: typing.Any):
+ def log_figure(self, name: str, figure: typing.Any, *args, **kwargs):
"""Log a base64 image of a figure to the `events.jsonl` file.
Important: This method will only log figures if the backend is in a context.
@@ -506,7 +520,7 @@ def __init__(self, path: str = None) -> None:
self.logger = logging.getLogger()
super().__init__()
- def log_string(self, value: str, verbosity: str = "info"):
+ def log_string(self, value: str, verbosity: str = "info", *args, **kwargs):
if verbosity == "progress":
self.logger.progress(value)
elif verbosity == "info":
@@ -547,7 +561,7 @@ def __exit__(self, exc_type, exc_value, exc_traceback):
class Pipeline:
def __init__(
self,
- backends: list[type[Backend]] = None,
+ backends: list[Backend] = None,
):
"""Metric logger which allows to log metrics, plots and strings to multiple backends.
@@ -565,7 +579,7 @@ def __init__(
self.context = Context(self)
# instantiate backends
- self.backends = backends
+ self.backends: list[Backend] = backends
def __enter__(self):
for backend in self.backends:
diff --git a/docs/_static/images/methods_optimization.png b/docs/_static/images/methods_optimization.png
new file mode 100644
index 00000000..af613d8d
Binary files /dev/null and b/docs/_static/images/methods_optimization.png differ
diff --git a/docs/_static/images/methods_optimization_calibration.png b/docs/_static/images/methods_optimization_calibration.png
new file mode 100644
index 00000000..8b4203e6
Binary files /dev/null and b/docs/_static/images/methods_optimization_calibration.png differ
diff --git a/docs/contributing.md b/docs/contributing.md
deleted file mode 100644
index 43838d7e..00000000
--- a/docs/contributing.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# Contributing
-This document gathers information on how to contribute to the alphaDIA project.
-
-## Release process
-### Tagging of changes
-In order to have release notes automatically generated, changes need to be tagged with labels.
-The following labels are used (should be safe-explanatory):
-`breaking-change`, `bug`, `enhancement`.
-
-### Release a new version
-Note: Releases need to be done from the `main` branch.
-
-1. Bump the version locally to (e.g. to `X.Y.Z`) and merge the change to `main`.
-2. Create a new draft release on GitHub using the
-[Create Draft Release](https://github.com/MannLabs/alphadia/actions/workflows/create_release.yml) workflow.
-You need to specify the commit to release, and the release tag (e.g. `vX.Y.Z`).
-3. Test the release manually.
-4. Add release notes and publish the release on GitHub.
-5. Run the [Publish on PyPi](https://github.com/MannLabs/alphadia/actions/workflows/publish_on_pypi.yml) workflow,
-specifying the release tag (e.g. `vX.Y.Z`).
-6. Run the [Publish Docker Image](https://github.com/MannLabs/alphadia/actions/workflows/publish_docker_image.yml) workflow,
-specifying the release tag (e.g. `vX.Y.Z`).
-
-
-## Notes for developers
-### pre-commit hooks
-It is highly recommended to use the provided pre-commit hooks, as the CI pipeline enforces all checks therein to
-pass in order to merge a branch.
-
-The hooks need to be installed once by
-```bash
-pre-commit install
-```
-You can run the checks yourself using:
-```bash
-pre-commit run --all-files
-```
-Make sure you use the same version of pre-commit as defined in `requirements_development.txt`.
diff --git a/docs/developer_guide.md b/docs/developer_guide.md
new file mode 100644
index 00000000..74424fb0
--- /dev/null
+++ b/docs/developer_guide.md
@@ -0,0 +1,40 @@
+# Developer Guide
+This document gathers information on how to develop and contribute to the alphaDIA project.
+
+## Release process
+
+### Tagging of changes
+In order to have release notes automatically generated, changes need to be tagged with labels.
+The following labels are used (should be safe-explanatory):
+`breaking-change`, `bug`, `enhancement`.
+
+### Release a new version
+This package uses a shared release process defined in the
+[alphashared](https://github.com/MannLabs/alphashared) repository. Please see the instructions
+[there](https://github.com/MannLabs/alphashared/blob/reusable-release-workflow/.github/workflows/README.md#release-a-new-version)
+
+
+## Notes for developers
+### Debugging
+To debug e2e tests with PyCharm:
+1. Create a "Run/Debug configuration" with
+ - "module": `alphadia.cli`
+ - "script parameters": `--config /abs/path/to/tests/e2e_tests/basic/config.yaml`
+ - "working directory": `/abs/path/to/tests/e2e_tests`
+2. Uncomment the lines following the `uncomment for debugging` comment in `alphadia/cli.py`.
+3. Run the configuration.
+
+
+### pre-commit hooks
+It is highly recommended to use the provided pre-commit hooks, as the CI pipeline enforces all checks therein to
+pass in order to merge a branch.
+
+The hooks need to be installed once by
+```bash
+pre-commit install
+```
+You can run the checks yourself using:
+```bash
+pre-commit run --all-files
+```
+Make sure you use the same version of pre-commit as defined in `requirements_development.txt`.
diff --git a/docs/index.md b/docs/index.md
index f26ef4d4..818e5ed6 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,6 +1,6 @@
# AlphaDIA Documentation
-**Version:** 1.7.2 | [Github](https://github.com/MannLabs/alphadia)
+**Version:** 1.8.0 | [Github](https://github.com/MannLabs/alphadia)
Open-source DIA search engine built with the alphaX ecosystem. Built with [alpharaw](https://github.com/MannLabs/alpharaw) and [alphatims](https://github.com/MannLabs/alphatims) for raw file acces. Spectral libraries are predicted with [peptdeep](https://github.com/MannLabs/alphapeptdeep) and managed by [alphabase](https://github.com/MannLabs/alphabase). Quantification is powered by [directLFQ](https://github.com/MannLabs/directLFQ).
diff --git a/docs/installation.md b/docs/installation.md
index 5648c49d..6fada45c 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -39,7 +39,7 @@ A detailed guide to installing alphaRaw with mono can be found [here](https://gi
It is highly recommended to use a conda environment for the installation of alphaDIA.
This ensures that all dependencies are installed correctly and do not interfere with other packages.
```bash
-conda create -n alphadia python=3.11
+conda create -n alphadia python=3.11 -y
conda activate alphadia
```
@@ -65,7 +65,7 @@ AlphaDIA can be installed in editable (i.e. developer) mode. This allows to full
Make sure you have a conda environment and Mono installed for reading `.raw` files as described [here](https://github.com/MannLabs/alpharaw#installation).
-See also the [developer guide](contributing.md) for more information on how to contribute to alphaDIA.
+See also the [developer guide](developer_guide.md) for more information on how to contribute to alphaDIA.
### 1. Setting up the repository
@@ -165,33 +165,34 @@ docker run -v $DATA_FOLDER:/app/data/ --rm alphadia-docker
### 1. Set up environment
Check if conda is available on your cluster. If not, install it, or, if provided, load the corresponding module, e.g.
-`module load anaconda/3/2023.03`.
+`module load anaconda/3/2023.03`. You might be asked to run `conda init bash` to initialize conda.
Create and activate a new conda environment
```bash
-conda create -n alphadia python=3.11
+conda create -n alphadia -y
conda activate alphadia
```
### 2. Installing mono
Install mono to support reading proprietary vendor formats like Thermo `.raw` files.
-Please make sure you include the conda-forge channel
+
+Install python into your new environment
```bash
-conda install python=3.11 -c conda-forge
+conda install python=3.11 -y
```
Then install mono by
```bash
-conda install mono -c conda-forge
+conda install mono=6.12.0.182 -c anaconda -y
```
-Make sure mono is installed by running
+Test that mono is correctly installed by running
```bash
mono --version
```
-Make sure the output looks something like this:
+The output should look something like this:
```
-Mono JIT compiler version 6.12.0.90 (tarball Fri Mar 5 04:37:13 UTC 2021)
+Mono JIT compiler version 6.12.0.182 (tarball Mon Jun 26 17:39:19 UTC 2023)
Copyright (C) 2002-2014 Novell, Inc, Xamarin Inc and Contributors. www.mono-project.com
TLS: __thread
SIGSEGV: altstack
@@ -212,5 +213,15 @@ Install alphaDIA using pip:
```bash
pip install "alphadia[stable]"
```
-Afterwards, verify the alphaDIA installation by running:
+Afterward, verify the alphaDIA installation by running:
`alphadia --version` which should output the current version.
+
+### Notes on running alphaDIA as part of automated workflows
+AlphaDIA is designed to be run in a headless mode. In case of an error, a nonzero exit code is returned.
+A exit code of 127 indicates that there was an unknown error. All other nonzero exit codes pertain to
+'business errors', i.e. those caused most likely by user input (data and/or configuration).
+
+Further details on such errors can be found in the `events.jsonl` file in the `.progress` folder(s) of the output directory.
+
+### Slurm script example
+You can find an example of a Slurm script here: [./tests/e2e_tests/e2e_slurm.sh](./tests/e2e_tests/e2e_slurm.sh).
diff --git a/docs/methods.md b/docs/methods.md
index 762dd2e8..83e3ad8b 100644
--- a/docs/methods.md
+++ b/docs/methods.md
@@ -5,5 +5,6 @@
methods/command-line
methods/configuration
methods/calibration
+methods/transfer-learning
```
diff --git a/docs/methods/calibration.md b/docs/methods/calibration.md
deleted file mode 100644
index 3363735f..00000000
--- a/docs/methods/calibration.md
+++ /dev/null
@@ -1,102 +0,0 @@
-# Calibration
-
-## Calibration and optimization
-The first step of every alphaDIA run entails the calibration of the empirical or fully predicted spectral library to the observed values. This step has two main purposes: 1. Removing the systematic deviation of observed and library values and 2. Optimizing the size of the search space to reflect the expected deviation from the library values. For DIA search this means calibration and optimization of retention time, ion mobility, precursor m/z and fragment m/z.
-
-AlphaDIA uses a iterative calibration schedule consisting of epochs. Every epoch, a defined number of precursors need to be identified at 1% FDR before calibration and optimization is performed and the next epoch is started (dashed green line). The number of precursors for the `n`'th epoch is derived from the parameter `recalibration_target` (default: 200) as `n*recalibration_target`. To reach this target library entries are searched, scored and aggregated, following an exponential batch plan (blue).
-
-
-
-If one epoch has accumulated enough confident target precursors, they are calibrated to the observed values using locally estimated scatterplot smoothing (LOESS) regression. For calibration of fragment m/z values, a fixed number of up to 5000 (but at least 500) of the best fragments according to their XIC correlation are used. For precursor calibration all precursors passing 1% FDR are used.
-For optimizing the search space, tolerances like retention time, ion mobility and m/z ratios need to be reduced. The goal is to cover the expected spectrum space but reduce it as much as possible to accelerate search and gain statsitical power. Starting with initial tolerances defined in `search_initial`. At the end of every epoch the 95% deviation after calibration is adopted as new tolerance until the target tolerances defined in the `search` section are reached.
-
-The search is finished as soon as the minimum numberof epochs `min_epochs` has passed and all tolerances have been to the target tolerances defined in `search`.
-## Configuring the calibration schedule
-
-```yaml
-calibration:
- # minimum number of times (epochs) the updated calibration target has to been passed
- min_epochs: 3
-
- # Number of precursors searched and scored per batch
- batch_size: 8000
-
- # recalibration target for the first epoch. For subsequent epochs, the target will increase by this amount.
- recalibration_target: 200
-
-search_initial:
- # Number of peak groups identified in the convolution score to classify with target decoy comeptition
- initial_num_candidates: 1
-
- # initial ms1 tolerance in ppm
- initial_ms1_tolerance: 30
-
- # initial ms2 tolerance in ppm
- initial_ms2_tolerance: 30
-
- # initial ion mobility tolerance in 1/K_0
- initial_mobility_tolerance: 0.08
-
- # initial retention time tolerance in seconds
- initial_rt_tolerance: 240
-```
-
-## Calibration using LOESS
-Individual properties like the retention time deviate from their library values and need to be calibrated (a). As a nonlinear but stable method, locally estimated scatterplot smoothing (LOESS) using both density and uniformly distributed kernels is used. (b) A collection of polynomial kernels is fitted to uniformly distributed subregions of the data. These consist of first and second degree polynomials basis functions of the calibratable property. (c) The individual functions are combined and smoothed using tricubic weights. (d) Combining the kernels with their weighting functions allows to approximate the systematic deviation of the data locally. (e), The sum of the weighted kernels can then be used for continuous approximation and calibration of retention times. The architecture is built on the scikit-learn package and can be configured to use different hyperparameters and arbitrary predictors for calibration.
-
-
-
-## Configuring the LOESS model
-
-The type of model, the hyperparameters and the columns used as input and target for calibration can be set in the `calibration_manager` section of the configuration file.
-
-```yaml
-calibration_manager:
- - name: fragment
- estimators:
- - name: mz
- model: LOESSRegression
- model_args:
- n_kernels: 2
- input_columns:
- - mz_library
- target_columns:
- - mz_observed
- output_columns:
- - mz_calibrated
- transform_deviation: 1e6
- - name: precursor
- estimators:
- - name: mz
- model: LOESSRegression
- model_args:
- n_kernels: 2
- input_columns:
- - mz_library
- target_columns:
- - mz_observed
- output_columns:
- - mz_calibrated
- transform_deviation: 1e6
- - name: rt
- model: LOESSRegression
- model_args:
- n_kernels: 6
- input_columns:
- - rt_library
- target_columns:
- - rt_observed
- output_columns:
- - rt_calibrated
- - name: mobility
- model: LOESSRegression
- model_args:
- n_kernels: 2
- input_columns:
- - mobility_library
- target_columns:
- - mobility_observed
- output_columns:
- - mobility_calibrated
-
-```
diff --git a/docs/methods/finetuning_imgs/finetuning_11_0.png b/docs/methods/finetuning_imgs/finetuning_11_0.png
new file mode 100644
index 00000000..e4e151d1
Binary files /dev/null and b/docs/methods/finetuning_imgs/finetuning_11_0.png differ
diff --git a/docs/methods/finetuning_imgs/finetuning_17_3.png b/docs/methods/finetuning_imgs/finetuning_17_3.png
new file mode 100644
index 00000000..07ed9e03
Binary files /dev/null and b/docs/methods/finetuning_imgs/finetuning_17_3.png differ
diff --git a/docs/methods/finetuning_imgs/finetuning_19_3.png b/docs/methods/finetuning_imgs/finetuning_19_3.png
new file mode 100644
index 00000000..453b5bee
Binary files /dev/null and b/docs/methods/finetuning_imgs/finetuning_19_3.png differ
diff --git a/docs/methods/finetuning_imgs/finetuning_20_0.png b/docs/methods/finetuning_imgs/finetuning_20_0.png
new file mode 100644
index 00000000..9b5c2dd2
Binary files /dev/null and b/docs/methods/finetuning_imgs/finetuning_20_0.png differ
diff --git a/docs/methods/finetuning_imgs/finetuning_23_0.png b/docs/methods/finetuning_imgs/finetuning_23_0.png
new file mode 100644
index 00000000..92795868
Binary files /dev/null and b/docs/methods/finetuning_imgs/finetuning_23_0.png differ
diff --git a/docs/methods/finetuning_imgs/finetuning_27_4.png b/docs/methods/finetuning_imgs/finetuning_27_4.png
new file mode 100644
index 00000000..4d1fcdfd
Binary files /dev/null and b/docs/methods/finetuning_imgs/finetuning_27_4.png differ
diff --git a/docs/methods/finetuning_imgs/finetuning_29_4.png b/docs/methods/finetuning_imgs/finetuning_29_4.png
new file mode 100644
index 00000000..270a9229
Binary files /dev/null and b/docs/methods/finetuning_imgs/finetuning_29_4.png differ
diff --git a/docs/methods/finetuning_imgs/finetuning_30_0.png b/docs/methods/finetuning_imgs/finetuning_30_0.png
new file mode 100644
index 00000000..1acf0efe
Binary files /dev/null and b/docs/methods/finetuning_imgs/finetuning_30_0.png differ
diff --git a/docs/methods/finetuning_imgs/finetuning_7_1.png b/docs/methods/finetuning_imgs/finetuning_7_1.png
new file mode 100644
index 00000000..dc305c7c
Binary files /dev/null and b/docs/methods/finetuning_imgs/finetuning_7_1.png differ
diff --git a/docs/methods/search_parameter_optimization.md b/docs/methods/search_parameter_optimization.md
new file mode 100644
index 00000000..24519f69
--- /dev/null
+++ b/docs/methods/search_parameter_optimization.md
@@ -0,0 +1,125 @@
+# Search Parameter Optimization
+
+## Calibration and optimization
+### Overall process
+The first step of every AlphaDIA run is the optimization of search parameters and the calibration of the empirical or fully predicted spectral library to the observed values. This step has two main purposes: 1) removing the systematic deviation of observed and library values, and 2) optimizing the size of the search space to reflect the expected deviation from the library values. For DIA search this means calibration and optimization of certain parameters: retention time, ion mobility, precursor m/z and fragment m/z. The process of iterative calibration and optimization is illustrated below.
+
+
+
+Optimization can be performed in either a targeted or automatic manner. In targeted optimization, the search space is progressively narrowed until a target tolerance is reached for a given parameter. In automatic optimization, the search space is progressively narrowed until an internal algorithm detects that further narrowing will reduce the confident identification of precursors (either by directly assessing the proportion of the library which has been detected or using a surrogate metric, such as the mean isotope intensity correlation for precursor m/z tolerance), at which point the optimal value is selected for search. Automatic optimization can be triggered by setting the target tolerance to 0.0 (or a negative value). It is possible to use targeted optimization for some parameters and automatic optimization for others; currently, it is recommended to use targeted optimization for precursor m/z, fragment m/z and ion mobility, and automatic optimization for retention time.
+
+AlphaDIA iteratively performs calibration and optimization based on a subset of the spectral library used for search. The size of this subset is adjusted according to an exponential batch plan to balance accuracy and efficiency. A defined number of precursors, set by the ``optimization_lock_target`` (default: 200), need to be identified at 1% FDR before calibration and optimization are performed. If fewer precursors than the target number are identified using a given step of the batch plan, AlphaDIA will search for precursors from the next step of the batch plan in addition to those already searched. If more precursors than the target number are identified, AlphaDIA will check if any previous step of the batch plan is also likely to yield at least the target number, in which case it will use the smallest such step of the batch plan for the next iteration of calibration and optimization. In this way, AlphaDIA ensures that calibration is always performed on sufficient precursors to be reliable, while calibrating on the smallest-possible subset of the library to maximize efficiency.
+
+The process of choosing the best batch step for calibration and optimization is illustrated in the figure below, which shows optimization and calibration over seven iterations; the batch size is increased in the first iteration until sufficient precursors are detected, and subsequently reduced when the proportion is sufficiently high that a previous step should reach the target as well; if, however, fluctuations in the number of identifications mean that not enough precursors are actually identified, the next step in the batch plan will be searched as well to ensure that calibration is always performed on at least the target number of precursors.
+
+
+
+### Calibration
+If enough confident target precursors have been detected, they are calibrated to the observed values using locally estimated scatterplot smoothing (LOESS) regression. For calibration of fragment m/z values, a fixed number of up to 5000 of the best fragments according to their XIC correlation are used. For precursor calibration all precursors passing 1% FDR are used. Calibration is performed prior to every optimization.
+
+### Optimization
+For optimizing the search space, tolerances like retention time, ion mobility and m/z ratios need to be reduced. The goal is to cover the expected spectrum space but reduce it as much as possible to accelerate search and gain statistical power. Search starts with initial tolerances as defined in `search_initial`. For targeted optimization, the 95% deviation after calibration is adopted as the new tolerance until the target tolerances defined in the `search` section are reached. For automatic optimization, the 99% deviation plus 10% of the absolute value of the tolerance is adopted as the new tolerance, and search continues until parameter-specific convergence rules are met.
+
+The optimization is finished as soon as the minimum number of steps `min_steps` has passed and all tolerances have either 1) reached the target tolerances defined in `search` if using targeted optimization, or 2) have converged if using automatic optimization.
+
+## Configuring calibration and optimization
+The configuration below will perform targeted optimization of precursor m/z, fragment m/z and ion mobility, and automatic optimization of retention time.
+
+```yaml
+calibration:
+ # Number of precursors searched and scored per batch
+ batch_size: 8000
+
+ # minimum number of precursors to be found before search parameter optimization begins
+ optimization_lock_target: 200
+
+ # the maximum number of steps that a given optimizer is permitted to take
+ max_steps: 20
+
+ # the minimum number of steps that a given optimizer must take before it can be said to have converged
+ min_steps: 2
+
+search_initial:
+ # Number of peak groups identified in the convolution score to classify with target decoy competition
+ initial_num_candidates: 1
+
+ # initial ms1 tolerance in ppm
+ initial_ms1_tolerance: 30
+
+ # initial ms2 tolerance in ppm
+ initial_ms2_tolerance: 30
+
+ # initial ion mobility tolerance in 1/K_0
+ initial_mobility_tolerance: 0.08
+
+ # initial retention time tolerance in seconds
+ initial_rt_tolerance: 240
+
+search:
+ target_num_candidates: 2
+ target_ms1_tolerance: 15
+ target_ms2_tolerance: 15
+ target_mobility_tolerance: 0.04
+ target_rt_tolerance: 0
+
+```
+
+## Calibration using LOESS
+Individual properties like the retention time deviate from their library values and need to be calibrated (a). As a nonlinear but stable method, locally estimated scatterplot smoothing (LOESS) using both density and uniformly distributed kernels is used. (b) A collection of polynomial kernels is fitted to uniformly distributed subregions of the data. These consist of first and second degree polynomials basis functions of the calibratable property. (c) The individual functions are combined and smoothed using tricubic weights. (d) Combining the kernels with their weighting functions allows to approximate the systematic deviation of the data locally. (e), The sum of the weighted kernels can then be used for continuous approximation and calibration of retention times. The architecture is built on the scikit-learn package and can be configured to use different hyperparameters and arbitrary predictors for calibration.
+
+
+
+## Configuring the LOESS model
+
+The type of model, the hyperparameters and the columns used as input and target for calibration can be set in the `calibration_manager` section of the configuration file.
+
+```yaml
+calibration_manager:
+ - name: fragment
+ estimators:
+ - name: mz
+ model: LOESSRegression
+ model_args:
+ n_kernels: 2
+ input_columns:
+ - mz_library
+ target_columns:
+ - mz_observed
+ output_columns:
+ - mz_calibrated
+ transform_deviation: 1e6
+ - name: precursor
+ estimators:
+ - name: mz
+ model: LOESSRegression
+ model_args:
+ n_kernels: 2
+ input_columns:
+ - mz_library
+ target_columns:
+ - mz_observed
+ output_columns:
+ - mz_calibrated
+ transform_deviation: 1e6
+ - name: rt
+ model: LOESSRegression
+ model_args:
+ n_kernels: 6
+ input_columns:
+ - rt_library
+ target_columns:
+ - rt_observed
+ output_columns:
+ - rt_calibrated
+ - name: mobility
+ model: LOESSRegression
+ model_args:
+ n_kernels: 2
+ input_columns:
+ - mobility_library
+ target_columns:
+ - mobility_observed
+ output_columns:
+ - mobility_calibrated
+
+```
diff --git a/docs/methods/transfer-learning.md b/docs/methods/transfer-learning.md
new file mode 100644
index 00000000..5edaf684
--- /dev/null
+++ b/docs/methods/transfer-learning.md
@@ -0,0 +1,415 @@
+
+## Transfer Learning
+
+Generally, transfer learning refers to the process of leveraging the knowledge learned in one model for better performance on a different task. A task is a vague term, but it essentially includes learning a different objective, for example, transitioning from regression to classification. It can also involve learning the same objective with a different loss function or optimizer, or using the same loss and objective but with different data. In cases where the dataset is too small to train a model from scratch without overfitting, we start from a pretrained model that has good performance on a larger dataset. This last type is the transfer learning we are using in alphaDIA.
+
+## Transfer Learning in alphaDIA
+
+In alphaDIA, we use the output obtained after the first search run and introduce a signal from the identified peptides to fine-tune the predictions from PeptDeep models on the custom dataset we are interested in. We then explore how this affects a second search run with fine-tuned models used to predict the spectral library.
+
+Training neural networks, and specifically transformers (such as those used in MS2 prediction), usually requires a lot of hyperparameter fine-tuning. Users try out different parameters like learning rate and the number of epochs to see what works better. For users with limited experience, this may seem like a trial and error process that is very time-consuming. The goal of the transfer learning module in alphaDIA is to provide robustness with minimal intervention from users, thereby increasing the accessibility of such processes for users from all backgrounds with minimal experience in deep learning.
+
+
+In this notebook, we will be going over the transfer learning done in alphaDIA, starting with two components that help achieve the robustness we are targeting: **Learning Rate Schedulers** and **Early Stopping**. If you understand these concepts and want to jump straight into how to use our APIs to fine-tune the model, skip to the [finetuning section](#transfer-learning-in-alphadia)
+
+
+
+```python
+
+from alphadia.transferlearning.train import CustomScheduler
+
+```
+
+## Learning Rate Scheduler
+
+Learning rates are crucial parameters that define the magnitude of updates made to the model weights, essentially controlling "how fast we learn". While a higher learning rate might seem beneficial, it can cause the weights to converge quickly to sub-optimal values and oscillate around them. If the learning rate is too high, it can even cause the model to diverge by overshooting the weights. This is where learning rate schedulers come into play. A learning rate scheduler adjusts the learning rate of a neural network (or part of it) dynamically based on time/epochs or the loss of the model (more on that later).
+
+For alphaDIA, we use a custom learning rate scheduler with two phases:
+
+### 1) Warmup Phase
+In this phase, the learning rate starts small and gradually increases over a certain number of "warmup epochs". Our default is **5**. This technique significantly helps in training transformers when using optimizers like Adam or SGD ([https://arxiv.org/abs/2002.04745](https://arxiv.org/abs/2002.04745)). Since we are not training from scratch, we set the default number of warmup epochs to 5. The user only needs to define the maximum learning rate and the number of epochs for warm-up. During this phase, the learning rate lr(t) is calculated as:
+
+$$\text{lr}(t) = \text{max lr} \times \left( \frac{t}{\text{number of warmup epochs}} \right)$$
+
+### 2) Reduce on Plateau LR Schedule
+After the warmup phase, the learning rate reaches the maximum value set by the user and remains there until the training loss reaches a plateau. A plateau is defined as the training loss not significantly improving for a certain number of epochs, referred to as "patience". For this phase, we use the PyTorch implementation `torch.optim.lr_scheduler.ReduceLROnPlateau` with a default patience value of 3 epochs.
+
+This approach makes the fine-tuning process less sensitive to the user-defined learning rate. If the model is not learning for 3 epochs, it is likely that the learning rate is too high, and the scheduler will then reduce the learning rate to encourage further learning.
+
+
+
+
+```python
+"""
+To show how our LR scheduler works, we will use a dummy optimizer with a dummy model parameters.
+"""
+
+import torch
+
+NUM_WARMUP_STEPS = 5
+MAX_LR = 0.01
+
+class DummyModel(torch.nn.Module):
+ def __init__(self):
+ super(DummyModel, self).__init__()
+ self.fc = torch.nn.Linear(1, 1)
+
+ def forward(self, x):
+ return self.fc(x)
+
+model = DummyModel()
+optimizer = torch.optim.Adam(model.parameters(), lr=MAX_LR)
+
+```
+
+
+```python
+"""
+Now since our lr scheduler uses reduce_lr_on_plateau, we need to pass the training loss per each epoch to the scheduler.
+"""
+
+# Dummy training loss
+losses = [0.12,0.1, 0.09, 0.08, 0.07, 0.06, 0.06,0.06,0.06,0.06,0.06, 0.06]
+scheduler = CustomScheduler(optimizer, max_lr=MAX_LR, num_warmup_steps=NUM_WARMUP_STEPS)
+
+learning_rates = []
+for epoch, loss in enumerate(losses):
+ scheduler.step(epoch, loss)
+ learning_rates.append(optimizer.param_groups[0]['lr'])
+
+fig, ax1 = plt.subplots()
+
+color = 'tab:red'
+ax1.set_xlabel('Epoch')
+ax1.set_ylabel('Learning Rate', color=color)
+ax1.plot(learning_rates, color=color)
+ax1.tick_params(axis='y', labelcolor=color)
+
+ax2 = ax1.twinx()
+
+color = 'tab:blue'
+ax2.set_ylabel('Loss', color=color)
+ax2.plot(losses, color=color)
+ax2.tick_params(axis='y', labelcolor=color)
+
+fig.tight_layout()
+plt.show()
+```
+
+
+
+
+
+
+![png](finetuning_imgs/finetuning_7_1.png)
+
+
+
+Notice how in the first 5 epochs the learning rate started from
+$$
+\text{lr}(t) = \text{max\_lr} \times \left( \frac{t}{\text{number of warmup epochs}} \right)
+$$
+and increased till it reached $0.01$ in the 5th epoch.
+
+When the loss plateaus for more than 3 epochs (the value set for patience), the learning rate is halved. We will see how much this learning rate halving actually helps retention time (RT) and MS2 fine-tuning to consistently achieve much better performance without requiring extensive experimentation with hyperparameter changes.
+
+
+
+
+```python
+from alphadia.transferlearning.train import EarlyStopping
+```
+
+## Early Stopping
+
+With the learning scheduler we are using, we could theoretically keep training indefinitely, as the learning rate is reduced whenever the loss becomes steady until it reaches an infinitesimally small value. However, there are two issues with this approach:
+
+1. The performance gains when the learning rate is very small are often not significant enough to justify continued training.
+2. Longer training times increase the risk of overfitting on the small dataset we are fine-tuning on.
+
+To address these issues, we implement two measures:
+
+### 1) Maximum Number of Epochs
+We set the maximum number of epochs to 50. From our experiments, we find that 50 epochs are usually sufficient to achieve significant performance gains without spending unnecessary time/epochs on insignificant improvements.
+
+### 2) Early Stopping
+We use an Early Stopping implementation that monitors the validation loss and terminates the training if one of the following criteria is met for more than the patience epochs (this is different from the learning rate scheduler's patience value, but they are related, more on this later):
+
+a) The validation loss is increasing, which may indicate overfitting.
+
+b) The validation loss is not significantly improving, indicating no significant performance gains on the validation dataset.
+
+The early stopping patience value represents the number of epochs we allow the model to meet the criteria without taking any action. This is because training neural networks with smaller batches can be a bit unstable, so we allow for some volatility before intervening. We set the early stopping patience to be a multiple of the learning rate scheduler patience. The idea is to give the learning rate scheduler a chance to address the problem before terminating the training.
+
+It's important to note that there are many implementations of Early Stopping algorithms, some offering better performance against overfitting by monitoring the generalization gap (val_loss - train_loss). However, we found that the simple implementation we use is sufficient for our fine-tuning tasks.
+
+
+
+```python
+"""
+To illustrate how our early stopping works we will try it on simulated validation losses in differnet cases and see how and when the early stopping is triggered.
+"""
+simulated_losses = {
+ "diverging": [0.5, 0.3 ,0.2, 0.1, 0.125, 0.15, 0.2, 0.3, 0.5, 0.7, 1.0],
+ "converging": [0.5, 0.3 ,0.2, 0.1, 0.05, 0.03, 0.02, 0.01, 0.005, 0.002, 0.0005],
+ "not significantly improving" : [0.5, 0.3 ,0.2, 0.1, 0.07, 0.0695, 0.0689, 0.06883, 0.06878,0.06874, 0.06869],
+}
+
+stopped_at = {case: len(losses)-1 for case, losses in simulated_losses.items()}
+for case, losses in simulated_losses.items():
+ early_stopping = EarlyStopping(patience=3)
+ for epoch, loss in enumerate(losses):
+ continue_training = early_stopping.step(loss)
+ if not continue_training:
+ stopped_at[case] = epoch
+ break
+
+fig, ax = plt.subplots()
+for case, losses in simulated_losses.items():
+ ax.plot(losses, label="Loss "+case)
+ ax.scatter(stopped_at[case], losses[stopped_at[case]], color='red')
+ax.legend()
+ax.set_xlabel("Epoch")
+ax.set_ylabel("Validation Loss")
+# remove axis
+ax.spines['top'].set_visible(False)
+ax.spines['right'].set_visible(False)
+ax.spines['left'].set_visible(False)
+ax.spines['bottom'].set_visible(False)
+plt.show()
+
+```
+
+
+
+![png](finetuning_imgs/finetuning_11_0.png)
+
+
+
+## Transfer Learning in alphaDIA
+
+Our fine-tuning interface in the `FinetuneManager` has a method implemented for each model (RT, Charge, MS2). Each function fine-tunes the respective model and runs tests on the validation dataset every epoch. By the end of the fine-tuning process, the method returns a pandas DataFrame that includes all metrics accumulated throughout the epochs. This DataFrame contains the training loss, learning rate, and all test metrics for the model. Additionally, the method provides an evaluation of the model on the full dataset before fine-tuning, and another evaluation on a separate test dataset that is used only once after the fine-tuning. This is included to facilitate easier interpretation of how well the fine-tuning performed on the given dataset.
+
+The metrics accumulated are calculated as the average over all validation samples and are as follows:
+
+| Model | Metrics |
+|---------|---------|
+| **RT** | L1 loss, Linear regression analysis, 95th percentile of the absolute error |
+| **Charge** | Cross Entropy, Accuracy, Precision, Recall |
+| **MS2** | L1 loss, Pearson Correlation Coefficient, Cosine Similarity, Spectral Angle, Spearman Correlation |
+
+
+```python
+
+tune_mgr = FinetuneManager(
+ device="gpu",
+ settings=settings)
+tune_mgr.nce = 25
+tune_mgr.instrument = 'Lumos'
+```
+
+
+
+## RT Fine-tuning
+
+
+
+```python
+transfer_lib.precursor_df = tune_mgr.predict_rt(transfer_lib.precursor_df)
+plt.scatter(transfer_lib.precursor_df['rt_norm'], transfer_lib.precursor_df['rt_norm_pred'], s=1, alpha=0.1)
+plt.xlabel('RT observed')
+plt.ylabel('RT predicted')
+```
+
+![png](finetuning_imgs/finetuning_17_3.png)
+
+
+
+
+
+```python
+rt_stats = tune_mgr.finetune_rt(transfer_lib.precursor_df)
+
+transfer_lib.precursor_df = tune_mgr.predict_rt(transfer_lib.precursor_df)
+
+plt.scatter(transfer_lib.precursor_df['rt_norm'], transfer_lib.precursor_df['rt_norm_pred'], s=0.1, alpha=0.1)
+plt.xlabel('RT observed')
+plt.ylabel('RT predicted')
+
+```
+
+ 2024-07-05 11:11:20> RT model tested on all dataset with the following metrics:
+ 2024-07-05 11:11:20> l1_loss : 0.2398
+ 2024-07-05 11:11:20> r_square : 0.4829
+ 2024-07-05 11:11:20> r : 0.6949
+ 2024-07-05 11:11:20> slope : 0.5390
+ 2024-07-05 11:11:20> intercept : 0.1113
+ 2024-07-05 11:11:20> abs_error_95th_percentile : 0.4317
+ 2024-07-05 11:11:20> Fine-tuning RT model with the following settings:
+ 2024-07-05 11:11:20> Train fraction: 0.70 Train size: 20226
+ 2024-07-05 11:11:20> Validation fraction: 0.20 Validation size: 5779
+ 2024-07-05 11:11:20> Test fraction: 0.10 Test size: 2889
+ 2024-07-05 11:11:30> Epoch 0 Lr: 0.00020 Training loss: 0.1619 validation loss: 0.1270
+ 2024-07-05 11:11:38> Epoch 1 Lr: 0.00030 Training loss: 0.0657 validation loss: 0.0482
+ ...
+ 2024-07-05 11:17:52> Epoch 49 Lr: 0.00030 Training loss: 0.0128 validation loss: 0.0206
+ 2024-07-05 11:18:00> Epoch 50 Lr: 0.00030 Training loss: 0.0128 validation loss: 0.0196
+ 2024-07-05 11:18:01> RT model tested on test dataset with the following metrics:
+ 2024-07-05 11:18:01> l1_loss : 0.0203
+ 2024-07-05 11:18:01> r_square : 0.9229
+ 2024-07-05 11:18:01> r : 0.9607
+ 2024-07-05 11:18:01> slope : 0.9672
+ 2024-07-05 11:18:01> intercept : 0.0147
+ 2024-07-05 11:18:01> abs_error_95th_percentile : 0.0846
+
+![png](finetuning_imgs/finetuning_19_3.png)
+
+
+
+
+```python
+g = sns.relplot(data=rt_stats, x='epoch', y='value', hue='data_split', marker= 'o',dashes=False, col='metric_name', kind='line', col_wrap=2, facet_kws={'sharex': False, 'sharey': False, 'legend_out': False})
+g.set_titles("{col_name}")
+g.legend.set_title('Data split')
+
+```
+
+
+
+![png](finetuning_imgs/finetuning_20_0.png)
+
+
+
+## Charge Fine-tuning
+
+
+```python
+
+charge_stats = tune_mgr.finetune_charge(psm_df=transfer_lib.precursor_df)
+```
+
+ 2024-07-05 11:18:13> Charge model tested on all dataset with the following metrics:
+ 2024-07-05 11:18:13> ce_loss : 0.5444
+ 2024-07-05 11:18:13> accuracy : 0.6414
+ 2024-07-05 11:18:13> precision : 0.3108
+ 2024-07-05 11:18:13> recall : 0.2975
+ 2024-07-05 11:18:13> Fine-tuning Charge model with following settings:
+ 2024-07-05 11:18:13> Train fraction: 0.70 Train size: 20226
+ 2024-07-05 11:18:13> Validation fraction: 0.20 Validation size: 5779
+ 2024-07-05 11:18:13> Test fraction: 0.10 Test size: 2889
+ 2024-07-05 11:18:24> Epoch 0 Lr: 0.00020 Training loss: 0.6341 validation loss: 0.6309
+ 2024-07-05 11:18:35> Epoch 1 Lr: 0.00030 Training loss: 0.4453
+ ...
+ 2024-07-05 11:27:54> Epoch 50 Lr: 0.00015 Training loss: 0.0618 validation loss: 0.1364
+ 2024-07-05 11:27:55> Charge model tested on test dataset with the following metrics:
+ 2024-07-05 11:27:55> ce_loss : 0.1476
+ 2024-07-05 11:27:55> accuracy : 0.9515
+ 2024-07-05 11:27:55> precision : 0.9107
+ 2024-07-05 11:27:55> recall : 0.8994
+
+
+
+```python
+g = sns.relplot(data=charge_stats, x='epoch', y='value', hue='data_split', marker= 'o',dashes=False, col='metric_name', kind='line', col_wrap=2, facet_kws={'sharex': False, 'sharey': False, 'legend_out': False})
+g.set_titles("{col_name}")
+g.legend.set_title('Data split')
+
+```
+
+
+
+![png](finetuning_imgs/finetuning_23_0.png)
+
+
+
+## MS2 Fine-tuning
+
+
+
+
+```python
+res = tune_mgr.predict_all(transfer_lib.precursor_df.copy(), predict_items=['ms2'])
+
+precursor_after_df = res['precursor_df']
+fragment_mz_after_df = res['fragment_mz_df']
+fragment_intensity_after_df = res['fragment_intensity_df']
+similarity_after_df = calculate_similarity(precursor_after_df, transfer_lib.precursor_df, fragment_intensity_after_df, transfer_lib.fragment_intensity_df)
+print(similarity_after_df['similarity'].median())
+plt.scatter(similarity_after_df['index'], similarity_after_df['similarity'], s=0.1)
+plt.xlabel('Index')
+plt.ylabel('Similarity')
+plt.title('Similarity between observed and predicted MS2 spectra before fine-tuning')
+```
+
+
+![png](finetuning_imgs/finetuning_27_4.png)
+
+
+
+
+```python
+
+# Testing the ms2 finetuning on the transfer library
+ms2_stats = tune_mgr.finetune_ms2(psm_df=transfer_lib.precursor_df.copy(), matched_intensity_df=transfer_lib.fragment_intensity_df.copy())
+```
+
+ 100%|██████████| 5779/5779 [00:01<00:00, 3818.99it/s]
+ 100%|██████████| 2889/2889 [00:00<00:00, 4138.71it/s]
+
+
+ 2024-07-05 11:38:06> Ms2 model tested on validation dataset with the following metrics:
+ 2024-07-05 11:38:06> l1_loss : 0.0323
+ 2024-07-05 11:38:06> PCC-mean : 0.7480
+ 2024-07-05 11:38:06> COS-mean : 0.7649
+ 2024-07-05 11:38:06> SA-mean : 0.5544
+ 2024-07-05 11:38:06> SPC-mean : 0.6447
+ 2024-07-05 11:38:06> Fine-tuning MS2 model with the following settings:
+ 2024-07-05 11:38:06> Train fraction: 0.70 Train size: 20226
+ 2024-07-05 11:38:06> Validation fraction: 0.20 Validation size: 5779
+ 2024-07-05 11:38:06> Test fraction: 0.10 Test size: 2889
+ 2024-07-05 11:38:39> Epoch 0 Lr: 0.00020 Training loss: 0.0212 validation loss: 0.0249
+ ...
+ 2024-07-05 12:05:54> Epoch 50 Lr: 0.00030 Training loss: 0.0110 validation loss: 0.0176
+ 2024-07-05 12:05:56> Ms2 model tested on test dataset with the following metrics:
+ 2024-07-05 12:05:56> l1_loss : 0.0176
+ 2024-07-05 12:05:56> PCC-mean : 0.9302
+ 2024-07-05 12:05:56> COS-mean : 0.9348
+ 2024-07-05 12:05:56> SA-mean : 0.7688
+ 2024-07-05 12:05:56> SPC-mean : 0.7802
+
+
+
+```python
+res = tune_mgr.predict_all(transfer_lib.precursor_df.copy(), predict_items=["ms2"])
+
+precursor_after_df = res["precursor_df"]
+fragment_mz_after_df = res["fragment_mz_df"]
+fragment_intensity_after_df = res["fragment_intensity_df"]
+similarity_after_df = calculate_similarity(
+ precursor_after_df,
+ transfer_lib.precursor_df,
+ fragment_intensity_after_df,
+ transfer_lib.fragment_intensity_df,
+)
+print(similarity_after_df["similarity"].median())
+plt.scatter(similarity_after_df["index"], similarity_after_df["similarity"], s=0.1)
+plt.xlabel("Index")
+plt.ylabel("Similarity")
+plt.title("Similarity between observed and predicted MS2 spectra after fine-tuning")
+```
+ 0.9401921591393136
+![png](finetuning_imgs/finetuning_29_4.png)
+
+
+
+
+```python
+g = sns.relplot(data=ms2_stats, x='epoch', y='value', hue='data_split', marker= 'o',dashes=False, col='metric_name', kind='line', col_wrap=2, facet_kws={'sharex': False, 'sharey': False, 'legend_out': False})
+g.set_titles("{col_name}")
+g.legend.set_title('Data split')
+
+```
+
+
+
+![png](finetuning_imgs/finetuning_30_0.png)
diff --git a/gui/package.json b/gui/package.json
index 1e95ec91..4856a5c2 100644
--- a/gui/package.json
+++ b/gui/package.json
@@ -1,7 +1,7 @@
{
"name": "alphadia",
"productName": "alphadia-gui",
- "version": "1.7.2",
+ "version": "1.8.0",
"description": "Graphical user interface for DIA data analysis",
"main": "dist/electron.js",
"homepage": "./",
diff --git a/gui/src/main/modules/engine.js b/gui/src/main/modules/engine.js
index 7e2e92ce..4034e8cc 100644
--- a/gui/src/main/modules/engine.js
+++ b/gui/src/main/modules/engine.js
@@ -12,9 +12,12 @@ const path = require('path');
var kill = require('tree-kill');
function getAppRoot() {
+ console.log("getAppPath=" + app.getAppPath() + " platform=" + process.platform)
if ( process.platform === 'win32' ) {
return path.join( app.getAppPath(), '/../../' );
- } else {
+ } else if ( process.platform === 'linux' ) {
+ return path.join( app.getAppPath(), '/../../../' );
+ } else {
return path.join( app.getAppPath(), '/../../../../' );
}
}
@@ -260,7 +263,7 @@ class CMDExecutionEngine extends BaseExecutionEngine {
"--no-capture-output",
"alphadia",
"--config",
- path.join(workflow.output_directory.path, "config.yaml")
+ `"${path.join(workflow.output_directory.path, "config.yaml")}"`
] , { env:{...process.env, PATH}, shell: true});
run.pid = run.process.pid
@@ -420,7 +423,7 @@ class BundledExecutionEngine extends BaseExecutionEngine {
// use binary location as cwd and binary name as command
run.process = spawn(prefix + binaryName,
["--config",
- path.join(workflow.output_directory.path, "config.yaml")
+ `"${path.join(workflow.output_directory.path, "config.yaml")}"`
],
{
env:{...process.env, PATH},
diff --git a/gui/src/main/modules/profile.js b/gui/src/main/modules/profile.js
index d2d5f2f0..5b9c864a 100644
--- a/gui/src/main/modules/profile.js
+++ b/gui/src/main/modules/profile.js
@@ -3,7 +3,7 @@ const path = require("path")
const { app, shell, BrowserWindow} = require("electron")
const { dialog } = require('electron')
-const VERSION = "1.7.2"
+const VERSION = "1.8.0"
const Profile = class {
diff --git a/gui/src/renderer/components/ParameterInput.js b/gui/src/renderer/components/ParameterInput.js
index 25376942..bb1a63a8 100644
--- a/gui/src/renderer/components/ParameterInput.js
+++ b/gui/src/renderer/components/ParameterInput.js
@@ -16,8 +16,7 @@ const SingleFolderSelection = ({parameter, onChange = () => {}}) => {
console.log(err);
})
}
-
- const folderName = parameter.replace(/^.*[\\\/]/, '')
+ const folderName = parameter ? parameter.replace(/^.*[\\\/]/, '') : ''
return (
<>
diff --git a/gui/workflows/PeptideCentric.v1.json b/gui/workflows/PeptideCentric.v1.json
index f0c8f1bb..f9b459be 100644
--- a/gui/workflows/PeptideCentric.v1.json
+++ b/gui/workflows/PeptideCentric.v1.json
@@ -35,13 +35,6 @@
"description": "Number of threads to use for parallel processing.",
"type": "integer"
},
- {
- "id": "reuse_calibration",
- "name": "Reuse Calibration",
- "value": false,
- "description": "AlphaDIA will save the calibration parameters in the project file. If this option is enabled, the calibration parameters will be reused for subsequent searches of the same file.",
- "type": "boolean"
- },
{
"id": "reuse_quant",
"name": "Reuse Ion Quantities",
@@ -153,7 +146,7 @@
{
"id": "max_var_mod_num",
"name": "Maximum variable modifications",
- "value": 1,
+ "value": 2,
"description": "Variable modifications for in-silico digest. At the moment localisation is not supported. Semicolon separated list \n Example: Oxidation@M;Acetyl@ProteinN-term",
"type": "integer"
},
@@ -239,11 +232,23 @@
]
},
{
- "id": "checkpoint_folder_path",
- "name": "PeptDeep Model",
- "value": "",
+ "id": "peptdeep_model_path",
+ "name": "PeptDeep Model Path",
+ "value": null,
"description": "Select a custom PeptDeep model for library prediction. This can be a DDA or DIA trained model. Please make sure that you use the same instrument type and NCE for prediction as the model was trained on.",
"type": "singleFolderSelection"
+ },
+ {
+ "id": "peptdeep_model_type",
+ "name": "PeptDeep Model Type",
+ "value": "generic",
+ "description": "Select a custom pretrained PeptDeep model for library prediction. Possible values are 'generic', 'phospho' and 'digly'.",
+ "type": "dropdown",
+ "options": [
+ "generic",
+ "phospho",
+ "digly"
+ ]
}
]
@@ -253,6 +258,34 @@
"name": "Search",
"hidden": false,
"parameters": [
+ {
+ "id": "target_ms1_tolerance",
+ "name": "MS1 Tolerance",
+ "value": 5,
+ "description": "MS1 tolerance in ppm. Search windows are optimized and calibrated during processing. The window is reduced until this tolerance is reached.",
+ "type": "float"
+ },
+ {
+ "id": "target_ms2_tolerance",
+ "name": "MS2 Tolerance",
+ "value": 10,
+ "description": "MS2 tolerance in ppm. Search windows are optimized and calibrated during processing. The window is reduced until this tolerance is reached.",
+ "type": "float"
+ },
+ {
+ "id": "target_mobility_tolerance",
+ "name": "Mobility Tolerance",
+ "value": 0.0,
+ "description": "Mobility tolerance in 1/K_0. Search windows are optimized and calibrated during processing. The window is reduced until this tolerance is reached. Set to enable automatic optimization.",
+ "type": "float"
+ },
+ {
+ "id": "target_rt_tolerance",
+ "name": "RT Tolerance",
+ "value": 0.0,
+ "description": "Retention time tolerance in seconds if greater than 1 or as a proportion of the gradient length if less than 1. Search windows are optimized and calibrated during processing. The window is reduced until this tolerance is reached. Automatic optimization is enabled if set to 0.",
+ "type": "float"
+ },
{
"id": "channel_filter",
"name": "Channel Filter",
@@ -281,34 +314,7 @@
"description": "For every precursor in the library a number of top scoring candidates will be extracted. This number is the maximum number of candidates that will be extracted per precursor.",
"type": "integer"
},
- {
- "id": "target_ms1_tolerance",
- "name": "MS1 Tolerance",
- "value": 5,
- "description": "MS1 tolerance in ppm. Search windows are optimized and calibrated during processing. The window is reduced until this tolerance is reached.",
- "type": "float"
- },
- {
- "id": "target_ms2_tolerance",
- "name": "MS2 Tolerance",
- "value": 10,
- "description": "MS2 tolerance in ppm. Search windows are optimized and calibrated during processing. The window is reduced until this tolerance is reached.",
- "type": "float"
- },
- {
- "id": "target_mobility_tolerance",
- "name": "Mobility Tolerance",
- "value": 0.04,
- "description": "Mobility tolerance in 1/K_0. Search windows are optimized and calibrated during processing. The window is reduced until this tolerance is reached.",
- "type": "float"
- },
- {
- "id": "target_rt_tolerance",
- "name": "RT Tolerance",
- "value": 100,
- "description": "Retention time tolerance in seconds. Search windows are optimized and calibrated during processing. The window is reduced until this tolerance is reached.",
- "type": "float"
- },
+
{
"id": "quant_window",
"name": "Quant window",
@@ -320,7 +326,7 @@
{
"id": "quant_all",
"name": "Use all MS2 observations",
- "value": false,
+ "value": true,
"description": "Use all MS2 observations for quantification. If disabled only the best scoring observation is used for quantification. Recommended for synchro-PASEF data.",
"type": "boolean"
@@ -362,13 +368,6 @@
"heuristic"
]
},
- {
- "id": "competetive_scoring",
- "name": "Competetive Scoring",
- "value": true,
- "description": "If enabled, only the best scoring candidate per target decoy pair is retained.",
- "type": "boolean"
- },
{
"id": "channel_wise_fdr",
"name": "Channel wise FDR",
@@ -393,7 +392,7 @@
{
"id": "initial_num_candidates",
"name": "Number of Candidates",
- "value": 2,
+ "value": 1,
"description": "Initial number of candidates to extract per precursor.",
"type": "integer"
},
@@ -414,15 +413,15 @@
{
"id": "initial_mobility_tolerance",
"name": "Mobility Tolerance",
- "value": 0.08,
+ "value": 0.1,
"description": "Initial mobility tolerance in 1/K_0.",
"type": "float"
},
{
"id": "initial_rt_tolerance",
"name": "RT Tolerance",
- "value": 240,
- "description": "Initial retention time tolerance in seconds.",
+ "value": 0.5,
+ "description": "Initial retention time tolerance in seconds if greater than 1 or as a proportion of the gradient length if less than 1.",
"type": "float"
}
]
@@ -433,7 +432,7 @@
"hidden": true,
"parameters": [
{
- "id": "multiplexed_quant",
+ "id": "enabled",
"name": "Enable Multiplexing",
"value": false,
"description": "Quantify and score identification across non-isobaric labled channels.",
diff --git a/misc/.bumpversion.cfg b/misc/.bumpversion.cfg
index 528f949c..f51e7b54 100644
--- a/misc/.bumpversion.cfg
+++ b/misc/.bumpversion.cfg
@@ -1,9 +1,9 @@
[bumpversion]
-current_version = 1.7.2
+current_version = 1.8.0
commit = True
tag = True
parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+)(?P\d+))?
-serialize =
+serialize =
{major}.{minor}.{patch}
{major}.{minor}.{patch}
@@ -21,16 +21,26 @@ serialize =
[bumpversion:file:../release/macos/info.plist]
+[bumpversion:file:../release/linux/build_installer_linux.sh]
+
+[bumpversion:file:../release/linux/control]
+
+[bumpversion:file:../release/macos/build_installer_macos.sh]
+
+[bumpversion:file:../release/macos/build_package_macos.sh]
+
[bumpversion:file:../release/macos/build_backend_macos.sh]
[bumpversion:file:../release/macos/build_pkg_macos.sh]
-[bumpversion:file:../release/macos/build_zip_macos.sh]
-
[bumpversion:file:../release/macos/distribution.xml]
[bumpversion:file:../release/windows/alphadia_innoinstaller.iss]
+[bumpversion:file:../release/windows/alphadia_innoinstaller_old.iss]
+
+[bumpversion:file:../release/windows/build_installer_windows.ps1]
+
[bumpversion:file:../release/windows/build_backend.ps1]
search = {current_version}
replace = {new_version}
diff --git a/nbs/debug/debug_lvl1.ipynb b/nbs/debug/debug_lvl1.ipynb
index 73e18f1b..db0282cc 100644
--- a/nbs/debug/debug_lvl1.ipynb
+++ b/nbs/debug/debug_lvl1.ipynb
@@ -112,7 +112,7 @@
" plan.config,\n",
")\n",
"workflow.load(dia_path, speclib)\n",
- "workflow.calibration()"
+ "workflow.search_parameter_optimization()"
]
},
{
@@ -221,7 +221,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.11.7"
+ "version": "3.11.9"
},
"orig_nbformat": 4
},
diff --git a/nbs/tutorial_nbs/finetuning.ipynb b/nbs/tutorial_nbs/finetuning.ipynb
index c4c7e9d9..f303e5c6 100644
--- a/nbs/tutorial_nbs/finetuning.ipynb
+++ b/nbs/tutorial_nbs/finetuning.ipynb
@@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"metadata": {},
"outputs": [],
"source": [
@@ -13,6 +13,7 @@
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"from alphabase.spectral_library.base import SpecLibBase\n",
+ "from alphadia.workflow.reporting import *\n",
"from alphadia.transferlearning.train import *\n",
"\n",
"import seaborn as sns\n",
@@ -21,7 +22,7 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"metadata": {},
"outputs": [],
"source": [
@@ -31,28 +32,77 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"transfer_lib = SpecLibBase()\n",
- "transfer_lib.load_hdf('/Users/georgwallmann/Documents/data/alphadia_manuscript/2024_04_25_Dimethyl_GPF/transfer_learning_asms/speclib.transfer.hdf', load_mod_seq=True)"
+ "transfer_lib.load_hdf('/Users/georgwallmann/Documents/data/alphadia_manuscript/2024_04_25_Dimethyl_GPF/transfer_learning_asms/speclib.transfer.hdf', load_mod_seq=True)\n"
]
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"transfer_lib.precursor_df = transfer_lib.precursor_df[~transfer_lib.precursor_df['mods'].str.contains('Dimethyl@C')]"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "tune_mgr = FinetuneManager(\n",
+ " device=\"gpu\",\n",
+ " test_interval=3)\n"
+ ]
+ },
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "# Util function to plot the metrics"
+ "## CCS Fine-tuning\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "transfer_lib.precursor_df[\"mobility\"] = transfer_lib.precursor_df[\"mobility_observed\"]\n",
+ "transfer_lib.precursor_df = tune_mgr.predict_mobility(transfer_lib.precursor_df)\n",
+ "plt.scatter(transfer_lib.precursor_df['mobility'], transfer_lib.precursor_df['mobility_pred'], s=1, alpha=0.1)\n",
+ "plt.xlabel('mobility observed')\n",
+ "plt.ylabel('mobility predicted')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ccs_stats = tune_mgr.finetune_ccs(transfer_lib.precursor_df)\n",
+ "\n",
+ "transfer_lib.precursor_df = tune_mgr.ccs_model.predict(transfer_lib.precursor_df)\n",
+ "plt.scatter(transfer_lib.precursor_df['ccs'], transfer_lib.precursor_df['ccs_pred'], s=1, alpha=0.1)\n",
+ "plt.xlabel('ccs observed')\n",
+ "plt.ylabel('ccs predicted')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "g = sns.relplot(data=ccs_stats, x='epoch', y='value', hue='data_split', marker= 'o',dashes=False, col='metric_name', kind='line', col_wrap=2, facet_kws={'sharex': False, 'sharey': False, 'legend_out': False})\n",
+ "g.set_titles(\"{col_name}\")\n",
+ "g.legend.set_title('Data split')"
]
},
{
@@ -69,11 +119,6 @@
"outputs": [],
"source": [
"\n",
- "tune_mgr = FinetuneManager(\n",
- " device=\"gpu\",\n",
- " settings=settings)\n",
- "tune_mgr.nce = 25\n",
- "tune_mgr.instrument = 'Lumos'\n",
"transfer_lib.precursor_df = tune_mgr.predict_rt(transfer_lib.precursor_df)\n",
"plt.scatter(transfer_lib.precursor_df['rt_norm'], transfer_lib.precursor_df['rt_norm_pred'], s=1, alpha=0.1)\n",
"plt.xlabel('RT observed')\n",
@@ -103,7 +148,7 @@
"source": [
"g = sns.relplot(data=rt_stats, x='epoch', y='value', hue='data_split', marker= 'o',dashes=False, col='metric_name', kind='line', col_wrap=2, facet_kws={'sharex': False, 'sharey': False, 'legend_out': False})\n",
"g.set_titles(\"{col_name}\")\n",
- "g.legend.set_title('Data split')\n"
+ "g.legend.set_title('Data split')"
]
},
{
@@ -144,7 +189,7 @@
},
{
"cell_type": "code",
- "execution_count": 10,
+ "execution_count": null,
"metadata": {},
"outputs": [],
"source": [
@@ -154,7 +199,7 @@
},
{
"cell_type": "code",
- "execution_count": 11,
+ "execution_count": null,
"metadata": {},
"outputs": [],
"source": [
diff --git a/nbs/tutorial_nbs/optimization_simulation.ipynb b/nbs/tutorial_nbs/optimization_simulation.ipynb
new file mode 100644
index 00000000..0256f13d
--- /dev/null
+++ b/nbs/tutorial_nbs/optimization_simulation.ipynb
@@ -0,0 +1,792 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Search Parameter Optimization Simulation"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%reload_ext autoreload\n",
+ "%autoreload 2\n",
+ "\n",
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "import matplotlib.pyplot as plt\n",
+ "import seaborn as sns\n",
+ "import os\n",
+ "import matplotlib as mpl\n",
+ "from alphadia.workflow import manager\n",
+ "\n",
+ "os.environ[\"NUMBA_BOUNDSCHECK\"] = \"1\"\n",
+ "os.environ[\"NUMBA_DEVELOPER_MODE\"] = \"1\"\n",
+ "os.environ[\"NUMBA_FULL_TRACEBACKS\"] = \"1\""
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "mpl.rcParams['figure.dpi']= 150\n",
+ "\n",
+ "#%config InlineBackend.figure_format = 'svg'\n",
+ "\n",
+ "# set all axis and text colors to #4e4f51\n",
+ "mpl.rcParams['text.color'] = '#4e4f51'\n",
+ "mpl.rcParams['axes.labelcolor'] = '#4e4f51'\n",
+ "mpl.rcParams['axes.edgecolor'] = '#4e4f51'\n",
+ "mpl.rcParams['xtick.color'] = '#4e4f51'\n",
+ "mpl.rcParams['ytick.color'] = '#4e4f51'\n",
+ "\n",
+ "# change all default line widths\n",
+ "mpl.rcParams['lines.linewidth'] = 0.6\n",
+ "mpl.rcParams['axes.linewidth'] = 0.6\n",
+ "mpl.rcParams['xtick.major.width'] = 0.6\n",
+ "mpl.rcParams['ytick.major.width'] = 0.6\n",
+ "mpl.rcParams['xtick.minor.width'] = 0.6\n",
+ "mpl.rcParams['ytick.minor.width'] = 0.6"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Mock Calibration "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "0:01:42.320059 INFO: Initializing CalibrationManager\n",
+ "0:01:42.320571 INFO: Loading calibration config\n",
+ "0:01:42.320913 INFO: Calibration config: [{'name': 'precursor', 'estimators': [{'name': 'rt', 'model': 'LOESSRegression', 'model_args': {'n_kernels': 6}, 'input_columns': ['rt_library'], 'target_columns': ['rt_observed'], 'output_columns': ['rt_calibrated']}]}]\n",
+ "0:01:42.321170 INFO: Calibration group :precursor, found 1 estimator(s)\n"
+ ]
+ }
+ ],
+ "source": [
+ "config = [{'name': 'precursor',\n",
+ " 'estimators': [{'name': 'rt',\n",
+ " 'model': 'LOESSRegression',\n",
+ " 'model_args': {'n_kernels': 6},\n",
+ " 'input_columns': ['rt_library'],\n",
+ " 'target_columns': ['rt_observed'],\n",
+ " 'output_columns': ['rt_calibrated'],\n",
+ " },\n",
+ "]}]\n",
+ "\n",
+ "config_calib_rt = [{'name': 'precursor',\n",
+ " 'estimators': [{'name': 'rt',\n",
+ " 'model': 'CalibRT',\n",
+ " 'model_args': {},\n",
+ " 'input_columns': ['rt_library'],\n",
+ " 'target_columns': ['rt_observed'],\n",
+ " 'output_columns': ['rt_calibrated'],\n",
+ " },\n",
+ "]}]\n",
+ "\n",
+ "\n",
+ "calibration_manager = manager.CalibrationManager(config=config)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def find_precursors_at_01_FDR(rt_df, number_to_discover):\n",
+ "\n",
+ " # Only allow values within the tolerance to be discovered\n",
+ " rt_df['discoverable'] = False\n",
+ " rt_df[\"discoverable\"] = rt_df.apply(lambda row: row['rt_observed'] < row[\"upper_bound\"] and row['rt_observed'] > row['lower_bound'], axis=1)\n",
+ " rt_df.loc[rt_df['discoverable'] == False, \"discovered\"] = False\n",
+ "\n",
+ " # Take a random sample at appropriate FDR of the remaining discoverable precursors\n",
+ " target_indices = rt_df.loc[(rt_df['decoy'] == False) & (rt_df['discoverable'] == True) & (rt_df['discovered'] == False)].sample(n=int(0.99*number_to_discover)).index\n",
+ " decoy_indices = rt_df.loc[(rt_df['decoy'] == True) & (rt_df['discoverable'] == True) & (rt_df['discovered'] == False)].sample(n=int(0.01*number_to_discover)).index\n",
+ " rt_df.loc[target_indices, 'discovered'] = True\n",
+ " rt_df.loc[decoy_indices, 'discovered'] = True\n",
+ "\n",
+ " return rt_df\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "dataset_size = 3000\n",
+ "iterations = 15\n",
+ "update_rule = 0.99\n",
+ "\n",
+ "true_amplitude = 60\n",
+ "true_wavelength = 100\n",
+ "true_noise = 50\n",
+ "\n",
+ "rng = np.random.default_rng(42)\n",
+ "predicted_rt = rng.uniform(100, 1000, size=dataset_size)\n",
+ "observed_rt = predicted_rt + true_amplitude * np.sin(predicted_rt / true_wavelength) + rng.normal(0, true_noise, size=dataset_size)\n",
+ "true_rt = predicted_rt + true_amplitude * np.sin(predicted_rt / true_wavelength)\n",
+ "true_positive_95_bound = true_rt + 1.96 * true_noise\n",
+ "true_negative_95_bound = true_rt - 1.96 * true_noise"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "0:01:42.411905 INFO: calibration group: precursor, fitting rt estimator \n",
+ "0:01:42.415518 INFO: calibration group: precursor, predicting rt\n",
+ "0:01:42.442650 INFO: calibration group: precursor, fitting rt estimator \n",
+ "0:01:42.445316 INFO: calibration group: precursor, predicting rt\n",
+ "0:01:42.470872 INFO: calibration group: precursor, fitting rt estimator \n",
+ "0:01:42.473066 INFO: calibration group: precursor, predicting rt\n",
+ "0:01:42.499471 INFO: calibration group: precursor, fitting rt estimator \n",
+ "0:01:42.501854 INFO: calibration group: precursor, predicting rt\n",
+ "0:01:42.527652 INFO: calibration group: precursor, fitting rt estimator \n",
+ "0:01:42.530212 INFO: calibration group: precursor, predicting rt\n",
+ "0:01:42.566556 INFO: calibration group: precursor, fitting rt estimator \n",
+ "0:01:42.570127 INFO: calibration group: precursor, predicting rt\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "0\n",
+ "1\n",
+ "2\n",
+ "3\n",
+ "4\n",
+ "5\n",
+ "6\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "0:01:42.602170 INFO: calibration group: precursor, fitting rt estimator \n",
+ "0:01:42.604882 INFO: calibration group: precursor, predicting rt\n",
+ "0:01:42.631466 INFO: calibration group: precursor, fitting rt estimator \n",
+ "0:01:42.635726 INFO: calibration group: precursor, predicting rt\n",
+ "0:01:42.664146 INFO: calibration group: precursor, fitting rt estimator \n",
+ "0:01:42.666718 INFO: calibration group: precursor, predicting rt\n",
+ "0:01:42.692421 INFO: calibration group: precursor, fitting rt estimator \n",
+ "0:01:42.696517 INFO: calibration group: precursor, predicting rt\n",
+ "0:01:42.721915 INFO: calibration group: precursor, fitting rt estimator \n",
+ "0:01:42.724483 INFO: calibration group: precursor, predicting rt\n",
+ "0:01:42.751780 INFO: calibration group: precursor, fitting rt estimator \n",
+ "0:01:42.754533 INFO: calibration group: precursor, predicting rt\n",
+ "0:01:42.780657 INFO: calibration group: precursor, fitting rt estimator \n",
+ "0:01:42.783217 INFO: calibration group: precursor, predicting rt\n",
+ "0:01:42.807731 INFO: calibration group: precursor, fitting rt estimator \n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "7\n",
+ "8\n",
+ "9\n",
+ "10\n",
+ "11\n",
+ "12\n",
+ "13\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "0:01:42.810789 INFO: calibration group: precursor, predicting rt\n",
+ "0:01:42.836908 INFO: calibration group: precursor, fitting rt estimator \n",
+ "0:01:42.839858 INFO: calibration group: precursor, predicting rt\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "14\n"
+ ]
+ }
+ ],
+ "source": [
+ "rt_df_target = pd.DataFrame({\"rt_library\": predicted_rt, \n",
+ " \"rt_observed\": observed_rt, \n",
+ " \"rt_true\": true_rt, \n",
+ " \"rt_true_positive_95_bound\": true_positive_95_bound,\n",
+ " \"rt_true_negative_95_bound\": true_negative_95_bound,\n",
+ " \"decoy\": False, \n",
+ " \"discovered\": False})\n",
+ "rt_df_decoy = pd.DataFrame({\"rt_library\": predicted_rt, \"rt_observed\": rng.uniform(100, 1000, size=dataset_size), \"decoy\": True, \"discovered\": False})\n",
+ "rt_df = pd.concat([rt_df_target, rt_df_decoy]).reset_index(drop=True)\n",
+ "rt_df[\"lower_bound\"] = rt_df[\"rt_library\"] - 300\n",
+ "rt_df[\"upper_bound\"] = rt_df[\"rt_library\"] + 300\n",
+ "\n",
+ "dfs = [rt_df.copy()]\n",
+ "tolerances = [300]\n",
+ "\n",
+ "for i in range(iterations):\n",
+ " print(i)\n",
+ " \n",
+ " rt_df = find_precursors_at_01_FDR(rt_df, 100).copy()\n",
+ " \n",
+ " precursor_df = rt_df[rt_df[\"discovered\"]]\n",
+ " calibration_manager.fit(precursor_df, \"precursor\")\n",
+ "\n",
+ " # Get upper and lower bounds for next search\n",
+ " tolerance = calibration_manager.get_estimator(\n",
+ " \"precursor\", \"rt\"\n",
+ " ).ci(precursor_df, update_rule)\n",
+ "\n",
+ " calibration_manager.predict(rt_df, \"precursor\")\n",
+ "\n",
+ " rt_df['upper_bound'] = rt_df['rt_calibrated'] + tolerance\n",
+ " rt_df['lower_bound'] = rt_df['rt_calibrated'] - tolerance\n",
+ "\n",
+ " tolerances.append(tolerance)\n",
+ " dfs.append(rt_df.copy())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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nF5fLGeJSUkA1A52nmGxIPNi4snESuuTyCXtxhthwAD7fwcFwZw59v3IFJCJiIhINaGZsnKWm2NPQODl5TzboPfpBAQ5uTkunS8zM4usrCyH8oTT+y8lnQ4CRS5tNsjOBt9i1h8++RTUrAXTp0NICCQlV273REREREpQ8FxuOXfvVBcoKLvILVmyhC+/WlBs/WP/+W8l9kakiv338arugYiIiMh5paSkEBFRp1THKCgrZ94+9pmwvLw8p/W5p1N5+3j7OK0/18CBA+nZs6dDuclkIuHkSepG1MHd3aOMvZXKlJSUxGP/+S/PPjOT4ODgqu6OVBMaV1IRNK6komhsSUW4WMaV1Wrh1KkUmjZtUupjFZSVs5DTAyG5mNerkpPt5SGhIS6dz9/fD39/56901a+vpBqXouDgYMLCQqu6G1LNaFxJRdC4koqisSUV4WIYV3XqlG6GrIASfZSzqIZRABw4eNBp/YED9vKoBg0qq0siIiIiInIRU1BWzqJbtMDP15f4+HgOHjzkUL9m7VoAOnbsWNldExERERGRi5CCsnLm6elJ/xv6A/D2/PmFa8gAFi1azOHD/9C6VatqsXG0iIiIiIhcOK0pqwAjhg9j69at7N69h4mT7qFly2gSExPZu3cfQUGB3HvvPVXdRRERERERuUhopqwCGI1GZs54mhEjhuPl5cX69RtITEykZ88evPLyLMLDw6u6i1IF/Pz8GHnzCPz8itmLS6QMNK6kImhcSUXR2JKKUB3GlZvNZrNVdSdEREREREQuV5opExERERERqUIKykRERERERKqQgjIREREREZEqpKBMRERERESkCikoExERERERqUIKykRERERERKqQNo8WKUFeXh6bN29h48ZN7IqNJTExEXd3d+rUCadLly4MHjQQHx8fp8euXPkL/1u2jCNHjmIwGGjerBnDRwwjukWLYq8XGxvL119/w569ezGbzdSvX4/+/a+nZ48eFXWLcpFIT89gyr33kpaWTnh4OG/Pm1tsW40tcUVaWhrffvsdGzZuIikpCaPRSGhoCFdccQW3jR/n0H7Dho18v2gRBw8eAqBxo0YMGTKYDh06FHuNuLg4vvjyK3bs2Elubi51wsPp3bsXAwbcgLu7fvdb3ezbt4/vvlvErthY0tPT8fbyokGDBvTq3ZNePXvi5uZWpL3FYmHpDz+ycsVKTsTH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4EHnKfZd1a9fXwB++mk5K1asxN3dnV69el7QOc/m5+fHsJvs68y++eZbrFZruZ1bREScU1AmIiKXvV2xsaxYsbJI2edffEliYhJRUQ1o1aqly+eKOL0x9Lp164psvpyZmcWbs+dgNpsvqK9NmzalcaNGrN+wgYSEBDp0uIrapZjJc8V11/WlZs2aHD16jLVr15XruUVExJHWlImIyCVt2vSHi63r06e3Swkwru93HbPnzGXZT8upEx7O4X8OExd3BF9fX+6/794Sjz9b0yZNaNe2LVu2bmXS5Cm0bt0KgB07dhIYGEhMp06s37ChVOc8V79+1zFn7lsAXNe39Ak+SuLl5cVNNw3l3XffY+E339CtW9dyv4aIiJyhoExERC5pe/fuK7auILNgSbp168ZVV13F1wu/Yf2GDRg8PIjp1Imxt4whsn79Uvfpscce4euF37B69Wr+/nszQUFBXHPN1YwZPZoPPviw1Oc7V5s2bQD7K5LtXbzH0rqubx++++47Dh48xKZNf9Ghw1UVch0REQE3m7P0TyIiInLRWrjwGz759DNG3jyCUaNGVnV3RETkAmlNmYiIyCUkOzubpT/8iMFgoO91fau6OyIiUg70+qKIiMglYMXKlezcsYudu3aRkpLCwIEDyj3Bh4iIVA3NlImIiFwCdu7YxS+//kpubg79+1/PuFtvqeouiYhIOdGaMhERERERkSqkmTIREREREZEqpKBMRERERESkCikoExERERERqUIKykRERERERKqQgjIREREREZEqpKBMRERERESkCikoExERERERqUIKykRERERERKqQgjIREREREZEqpKBMRERERESkCikoExERERERqUIKykRERERERKqQgjIREREREZEqpKBMRERERESkCv0/8c7MDPPgg64AAAAASUVORK5CYII=",
+ "text/plain": [
+ "