renkin 0.22.0

Ultra-fast retrosynthesis engine for computer-aided synthesis planning (CASP) — pure Rust, WASM-ready, Python bindings via PyO3
Documentation
name: CI

on:
  push:
    branches: [master]
  pull_request:

jobs:
  test:
    name: Test
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v7
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - run: cargo test --workspace

  lint:
    name: Lint
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v7
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt, clippy
      - uses: Swatinem/rust-cache@v2
      - run: cargo fmt --check
      - run: cargo clippy --workspace --all-targets -- -D warnings

  reranker-tests:
    name: Reranker training/eval script tests
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-python@v7
        with:
          python-version: "3.12"
      - run: pip install lightgbm scikit-learn
      - run: python3 scripts/train_reranker.py --self-test
      - run: python3 -m unittest discover -s scripts/tests -p "test_*.py"

  version-check:
    name: Version sync
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v7
      - run: |
          VER=$(grep '^version' Cargo.toml | head -1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
          echo "Checking version: $VER"
          if grep -rE 'renkin = "0\.[0-9]\."' docs/; then
            echo "ERROR: stale renkin version in docs/" && exit 1
          fi
          grep -q "version: \"${VER}\"" CITATION.cff || \
            { echo "ERROR: CITATION.cff version not updated to $VER"; exit 1; }
          echo "OK: all version references match $VER"

  python-smoke:
    name: Python smoke
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v7
      - uses: PyO3/maturin-action@v1
        with:
          command: build
          args: --release --features python --out dist
      - run: pip install dist/*.whl --force-reinstall
      - name: Smoke test from a non-repo directory
        # Deliberately NOT run from the repo root: find_routes with no
        # building_blocks argument looks for data/building_blocks.smi
        # relative to the cwd and falls back to the compiled-in
        # DEFAULT_BUILDING_BLOCKS set (152 compounds) if it's not found --
        # exactly what a real `pip install renkin` user gets when running
        # from anywhere that isn't a checkout of this repo. Running the
        # smoke test from repo root would silently test only the 402-file
        # path and never catch a regression in the fallback.
        run: |
          cd /tmp
          python -c "
          import renkin
          assert hasattr(renkin, '__version__'), '__version__ missing'
          assert callable(renkin.find_routes), 'find_routes missing'
          assert callable(renkin.predict_forward), 'predict_forward missing'
          assert callable(renkin.validate_forward), 'validate_forward missing'
          result = renkin.find_routes('c1ccccc1', 3, 1, 0)
          assert result, 'find_routes returned empty'
          result = renkin.find_routes('c1ccccc1', depth=3, max_routes=1, beam_width=0, template_metadata_path=None)
          assert result, 'find_routes with template_metadata_path=None returned empty'
          print('ok:', renkin.__version__)
          "

  # Prevents documentation from silently drifting from reality (regression:
  # docs once claimed 31 rules / 509 building blocks / a non-existent
  # renkin.version() function after the real API had already moved on).
  # Two-tier: (1) the example scripts embedded into docs/ via pymdownx.snippets
  # are actually executed/compiled here, not just eyeballed; (2) known-stale
  # figures are checked against live-derived facts rather than hardcoded.
  docs-check:
    name: Docs facts & examples
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v7
      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: wasm32-unknown-unknown
      - uses: Swatinem/rust-cache@v2
      - uses: actions/setup-python@v7
        with:
          python-version: "3.12"

      - name: Derive live facts (rule count, building block file/fallback counts)
        id: facts
        run: |
          FACTS=$(cargo run --quiet --example doc_facts)
          echo "$FACTS"
          echo "rule_count=$(echo "$FACTS" | grep -oP 'HAND_CRAFTED_RULE_COUNT=\K\d+')" >> "$GITHUB_OUTPUT"
          echo "bb_file_count=$(echo "$FACTS" | grep -oP 'BUILDING_BLOCK_FILE_COUNT=\K\d+')" >> "$GITHUB_OUTPUT"
          echo "bb_fallback_count=$(echo "$FACTS" | grep -oP 'BUILDING_BLOCK_FALLBACK_COUNT=\K\d+')" >> "$GITHUB_OUTPUT"

      - name: Check docs/README against live facts
        run: python3 scripts/check_docs_facts.py "${{ steps.facts.outputs.rule_count }}" "${{ steps.facts.outputs.bb_file_count }}" "${{ steps.facts.outputs.bb_fallback_count }}"

      - name: Compile and run the Rust quickstart example
        run: cargo run --example quickstart

      - uses: PyO3/maturin-action@v1
        with:
          command: build
          args: --release --features python --out dist
      - name: Run the Python quickstart example
        run: |
          pip install dist/*.whl --force-reinstall
          python3 examples/quickstart.py

      - name: Install wasm-pack
        run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
      - name: Build WASM (nodejs target) and run the JS quickstart example
        run: |
          wasm-pack build --target nodejs --no-default-features
          node examples/quickstart.mjs
          rm -rf pkg

      - name: Install Cairo (required by the social plugin for OG card images)
        run: sudo apt-get update && sudo apt-get install -y libcairo2-dev libfreetype6-dev libffi-dev libjpeg-dev libpng-dev libz-dev
      - name: Install MkDocs Material
        run: pip install "mkdocs-material[imaging]"
      - name: Build docs site (--strict fails on broken links/snippet includes/missing social-card deps)
        run: mkdocs build --strict -d /tmp/site

      - name: Check sitemap.xml includes key pages
        run: |
          for page in api/python api/rust api/wasm benchmark benchmark_ja getting_started/installation getting_started/quickstart examples/aspirin examples/druglike guides/python-retrosynthesis guides/rust-retrosynthesis guides/reaction-evidence guides/forward-prediction guides/forward-enumeration guides/forward-retrieval-hints guides/open-source-retrosynthesis-comparison comparison/open-source-retrosynthesis-tools comparison/askcos-feasibility-issue-66; do
            grep -q "$page/" /tmp/site/sitemap.xml || { echo "ERROR: $page missing from sitemap.xml"; exit 1; }
          done
          echo "OK: sitemap.xml includes all key pages"

      - name: Check social card images were generated for key pages
        run: |
          for page in index api/python api/rust api/wasm benchmark benchmark_ja getting_started/installation getting_started/quickstart examples/aspirin examples/druglike guides/python-retrosynthesis guides/rust-retrosynthesis guides/reaction-evidence guides/forward-prediction guides/forward-enumeration guides/forward-retrieval-hints guides/open-source-retrosynthesis-comparison comparison/open-source-retrosynthesis-tools comparison/askcos-feasibility-issue-66; do
            test -s "/tmp/site/assets/images/social/$page.png" || { echo "ERROR: social card missing for $page"; exit 1; }
          done
          echo "OK: social card images generated for all key pages"