Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 75 additions & 6 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,79 @@
# .github/dependabot.yml
version: 2

updates:
- package-ecosystem: "pip"
directory: "/" # location of requirements.txt or pyproject.toml
target-branch: "staging" # open PRs against staging instead of main
directory: "/"
target-branch: "staging"
schedule:
interval: "weekly" # check for updates once a week
open-pull-requests-limit: 5 # max concurrent Dependabot PRs
rebase-strategy: "auto" # auto-rebase PRs when they fall out of date
interval: "weekly"
day: "monday"
time: "04:00"
timezone: "Etc/UTC"
open-pull-requests-limit: 5
rebase-strategy: "auto"
labels:
- "dependencies"
- "python"
commit-message:
prefix: "deps"
include: "scope"
groups:
python-runtime:
patterns:
- "networkx"
- "pandas"
- "rdkit"
- "regex"
- "requests"
- "scikit-learn"
- "seaborn"
python-optional:
patterns:
- "numpy"
- "sympy"
- "torch"
docs:
patterns:
- "sphinx*"
- "pydata-sphinx-theme"
- "sphinx-rtd-theme"
- "graphviz"
- "myst-parser"

- package-ecosystem: "github-actions"
directory: "/"
target-branch: "staging"
schedule:
interval: "weekly"
day: "monday"
time: "04:30"
timezone: "Etc/UTC"
open-pull-requests-limit: 5
rebase-strategy: "auto"
labels:
- "dependencies"
- "github-actions"
commit-message:
prefix: "ci"
include: "scope"
groups:
github-actions:
patterns:
- "*"

- package-ecosystem: "docker"
directory: "/"
target-branch: "staging"
schedule:
interval: "weekly"
day: "monday"
time: "05:00"
timezone: "Etc/UTC"
open-pull-requests-limit: 2
rebase-strategy: "auto"
labels:
- "dependencies"
- "docker"
commit-message:
prefix: "deps"
include: "scope"
10 changes: 6 additions & 4 deletions .github/workflows/conda-forge-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup Miniconda
uses: conda-incubator/setup-miniconda@v2
uses: conda-incubator/setup-miniconda@v3
with:
miniconda-version: "latest"
channels: conda-forge
auto-update-conda: true
auto-activate-base: true
Expand All @@ -53,13 +54,14 @@ jobs:
pkg_paths: ${{ steps.build.outputs.paths }}
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup Miniconda
uses: conda-incubator/setup-miniconda@v2
uses: conda-incubator/setup-miniconda@v3
with:
miniconda-version: "latest"
channels: conda-forge
auto-update-conda: true
auto-activate-base: true
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@ jobs:

steps:
- name: Check out repository
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Set up QEMU (optional, for multi‑arch)
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3

- name: Log in to Docker Hub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/test-and-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Test & Lint

on:
push:
branches: [ "main", "staging", "fix_query" ]
branches: [ "main", "staging", "mech" ]
pull_request:
branches: [ "main" ]

Expand All @@ -20,11 +20,11 @@ jobs:

steps:
# 0) Check out the code
- uses: actions/checkout@v3
- uses: actions/checkout@v4

# 1) Install Miniconda (downloaded — the “bundled” version was removed)
- name: Set up Miniconda
uses: conda-incubator/setup-miniconda@v2
uses: conda-incubator/setup-miniconda@v3
with:
miniconda-version: "latest" # <<–‑‑ mandatory or the action fails
python-version: "3.11"
Expand Down
105 changes: 68 additions & 37 deletions .github/workflows/verify-pypi-install.yml
Original file line number Diff line number Diff line change
@@ -1,65 +1,96 @@
# .github/workflows/verify-synkit-pypi-install.yml
name: Verify SynKit PyPI install
name: Verify PyPI install

on:
workflow_dispatch:
inputs:
branches:
package-version:
description: "Optional exact SynKit version to install, for example 1.4.0"
required: false
type: string
required: true
default: refractor

# Scheduled test every Monday at 03:00 UTC
schedule:
- cron: '0 3 * * 1'
- cron: "0 3 * * 1"

permissions:
contents: read

concurrency:
group: verify-pypi-install-${{ github.event_name }}-${{ github.event.inputs['package-version'] || 'latest' }}
cancel-in-progress: false

jobs:
verify:
name: Verify PyPI install on ${{ matrix.os }}
name: ${{ matrix.os }} / Python ${{ matrix.python-version }}
runs-on: ${{ matrix.os }}

strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.11", "3.12"]

steps:
- name: Setup Python
uses: actions/setup-python@v4
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.x'
python-version: ${{ matrix.python-version }}
cache: "pip"

- name: Create & activate virtualenv, upgrade pip, install SynKit
- name: Install SynKit from PyPI
shell: bash
run: |
python -m venv venv
source venv/bin/activate
python -m pip install --upgrade pip
pip install synkit[all]

- name: Show installed SynKit version
version="${{ github.event.inputs['package-version'] }}"
if [ -n "$version" ]; then
python -m pip install "synkit==$version"
else
python -m pip install synkit
fi
python -m pip install packaging

- name: Show installed package metadata
shell: bash
run: |
source venv/bin/activate
python -c "import importlib.metadata as m; print('SynKit version:', m.version('synkit'))"
python - <<'PY'
import importlib.metadata as metadata
import sys

print("Python:", sys.version)
print("SynKit:", metadata.version("synkit"))
PY

- name: Write smoke-test script
- name: Run import and smoke tests
shell: bash
run: |
cat << 'EOF' > test_synkit.py
from synkit.IO import rsmi_to_rsmarts
python - <<'PY'
import importlib.metadata as metadata

template = (
'[C:2]=[O:3].[C:4]([H:7])[H:8]'
'>>'
'[C:2]=[C:4].[O:3]([H:7])[H:8]'
)
from packaging.version import Version

smart = rsmi_to_rsmarts(template)
print("Reaction SMARTS:", smart)
EOF
from synkit.IO import rsmi_to_its, rsmi_to_rsmarts

- name: Run smoke-test
run: |
source venv/bin/activate
python test_synkit.py
rsmi = "[CH3:1][Cl:2].[NH3:3]>>[CH3:1][NH3+:3].[Cl-:2]"
smarts = rsmi_to_rsmarts(rsmi)
assert ">>" in smarts

version = Version(metadata.version("synkit"))
if version >= Version("1.4.0"):
import networkx as nx

from synkit.Graph.MTG.mtg import MTG

its = rsmi_to_its(rsmi, core=False, format="tuple")
assert isinstance(its, nx.Graph)
assert not any("typesGH" in attrs for _, attrs in its.nodes(data=True))

mtg_steps = [
"[CH3:1][Cl:2].[NH3:3]>>[CH3:1][NH3+:3].[Cl-:2]",
"[CH3:1][NH3+:3].[Cl-:2]>>[CH3:1][NH2:3].[Cl:2][H]",
]
mtg = MTG(mtg_steps, mcs_mol=True)
graph = mtg.get_mtg()
assert mtg._tuple_its
assert graph.number_of_nodes() > 0

- name: Success message
run: echo "✅ synkit[all] installed and smoke-test passed"
print("PyPI SynKit smoke tests passed.")
PY
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,8 @@ synkit/Graph/dev/*
*.json
*.txt
*.log
sprint/
test_syn.py
.gitignore
measure_candidate_stages.py
run_valid_bug_cases.py
6 changes: 6 additions & 0 deletions Test/CRN/Props/test_dynamics.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@ def test_jacobian_pattern_bipartite(self) -> None:
self.assertTrue(B.has_edge("row:1", "col:1"))


@unittest.skipUnless(
dynamics._SYMPY_AVAILABLE, "sympy is required for symbolic dynamics tests"
)
class TestDynamicsMatrices(unittest.TestCase):
def test_symbolic_reactivity_matrix_cycle(self) -> None:
G = make_cycle_crn()
Expand Down Expand Up @@ -429,6 +432,9 @@ def test_to_dict_and_str(self) -> None:
self.assertIn("classification", text)


@unittest.skipUnless(
dynamics._SYMPY_AVAILABLE, "sympy is required for exact singularity tests"
)
class TestStructuralSingularitySummary(unittest.TestCase):
def test_single_step_is_singular_by_pattern(self) -> None:
G = make_single_step_crn()
Expand Down
1 change: 1 addition & 0 deletions Test/Graph/FG/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

24 changes: 24 additions & 0 deletions Test/Graph/FG/test_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import pytest

from synkit.Graph.FG import smiles_to_graph_and_functional_groups


def test_smiles_to_graph_and_functional_groups_supports_unmapped_smiles():
graph, groups = smiles_to_graph_and_functional_groups("CC(=O)O")

assert tuple(graph.nodes) == (1, 2, 3, 4)
assert groups == [("carboxylic_acid", (2, 3, 4))]


def test_smiles_to_graph_and_functional_groups_preserves_atom_map_node_ids():
graph, groups = smiles_to_graph_and_functional_groups(
"[CH3:10][C:20](=[O:30])[OH:40]"
)

assert tuple(graph.nodes) == (10, 20, 30, 40)
assert groups == [("carboxylic_acid", (20, 30, 40))]


def test_smiles_to_graph_and_functional_groups_rejects_invalid_smiles():
with pytest.raises(ValueError):
smiles_to_graph_and_functional_groups("not_smiles")
21 changes: 21 additions & 0 deletions Test/Graph/FG/test_audit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from synkit.Graph.FG.audit import audit_reaction_smiles


def test_audit_reaction_smiles_summarizes_small_corpus():
report = audit_reaction_smiles(
[
"CCO>>CC=O",
"c1ncnnc1>>c1ncnnc1",
]
)

assert report.reactions == 2
assert report.molecules == 4
assert report.parse_failures == 0
assert report.label_counts["primary_alcohol"] == 1
assert report.label_counts["aldehyde"] == 1
assert report.label_counts["heteroaromatic_ring"] == 2
assert report.label_counts["triazine"] == 2
assert report.heteroaromatic_systems == 2
assert report.named_heteroaromatic_systems == 2
assert report.unnamed_heteroaromatic_count == 0
Loading
Loading