Skip to content

Commit 3d898ee

Browse files
committed
prepare release
1 parent 3e0cf74 commit 3d898ee

23 files changed

Lines changed: 1546 additions & 113 deletions

.github/dependabot.yml

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,79 @@
1-
# .github/dependabot.yml
21
version: 2
2+
33
updates:
44
- package-ecosystem: "pip"
5-
directory: "/" # location of requirements.txt or pyproject.toml
6-
target-branch: "staging" # open PRs against staging instead of main
5+
directory: "/"
6+
target-branch: "staging"
77
schedule:
8-
interval: "weekly" # check for updates once a week
9-
open-pull-requests-limit: 5 # max concurrent Dependabot PRs
10-
rebase-strategy: "auto" # auto-rebase PRs when they fall out of date
8+
interval: "weekly"
9+
day: "monday"
10+
time: "04:00"
11+
timezone: "Etc/UTC"
12+
open-pull-requests-limit: 5
13+
rebase-strategy: "auto"
14+
labels:
15+
- "dependencies"
16+
- "python"
17+
commit-message:
18+
prefix: "deps"
19+
include: "scope"
20+
groups:
21+
python-runtime:
22+
patterns:
23+
- "networkx"
24+
- "pandas"
25+
- "rdkit"
26+
- "regex"
27+
- "requests"
28+
- "scikit-learn"
29+
- "seaborn"
30+
python-optional:
31+
patterns:
32+
- "numpy"
33+
- "sympy"
34+
- "torch"
35+
docs:
36+
patterns:
37+
- "sphinx*"
38+
- "pydata-sphinx-theme"
39+
- "sphinx-rtd-theme"
40+
- "graphviz"
41+
- "myst-parser"
42+
43+
- package-ecosystem: "github-actions"
44+
directory: "/"
45+
target-branch: "staging"
46+
schedule:
47+
interval: "weekly"
48+
day: "monday"
49+
time: "04:30"
50+
timezone: "Etc/UTC"
51+
open-pull-requests-limit: 5
52+
rebase-strategy: "auto"
53+
labels:
54+
- "dependencies"
55+
- "github-actions"
56+
commit-message:
57+
prefix: "ci"
58+
include: "scope"
59+
groups:
60+
github-actions:
61+
patterns:
62+
- "*"
63+
64+
- package-ecosystem: "docker"
65+
directory: "/"
66+
target-branch: "staging"
67+
schedule:
68+
interval: "weekly"
69+
day: "monday"
70+
time: "05:00"
71+
timezone: "Etc/UTC"
72+
open-pull-requests-limit: 2
73+
rebase-strategy: "auto"
74+
labels:
75+
- "dependencies"
76+
- "docker"
77+
commit-message:
78+
prefix: "deps"
79+
include: "scope"

.github/workflows/conda-forge-publish.yml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@ jobs:
2121

2222
steps:
2323
- name: Checkout code
24-
uses: actions/checkout@v3
24+
uses: actions/checkout@v4
2525
with:
2626
fetch-depth: 0
2727

2828
- name: Setup Miniconda
29-
uses: conda-incubator/setup-miniconda@v2
29+
uses: conda-incubator/setup-miniconda@v3
3030
with:
31+
miniconda-version: "latest"
3132
channels: conda-forge
3233
auto-update-conda: true
3334
auto-activate-base: true
@@ -53,13 +54,14 @@ jobs:
5354
pkg_paths: ${{ steps.build.outputs.paths }}
5455
steps:
5556
- name: Checkout code
56-
uses: actions/checkout@v3
57+
uses: actions/checkout@v4
5758
with:
5859
fetch-depth: 0
5960

6061
- name: Setup Miniconda
61-
uses: conda-incubator/setup-miniconda@v2
62+
uses: conda-incubator/setup-miniconda@v3
6263
with:
64+
miniconda-version: "latest"
6365
channels: conda-forge
6466
auto-update-conda: true
6567
auto-activate-base: true
Lines changed: 68 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,96 @@
1-
# .github/workflows/verify-synkit-pypi-install.yml
2-
name: Verify SynKit PyPI install
1+
name: Verify PyPI install
32

43
on:
54
workflow_dispatch:
65
inputs:
7-
branches:
6+
package-version:
7+
description: "Optional exact SynKit version to install, for example 1.4.0"
8+
required: false
89
type: string
9-
required: true
10-
default: refractor
11-
12-
# Scheduled test every Monday at 03:00 UTC
1310
schedule:
14-
- cron: '0 3 * * 1'
11+
- cron: "0 3 * * 1"
12+
13+
permissions:
14+
contents: read
15+
16+
concurrency:
17+
group: verify-pypi-install-${{ github.event_name }}-${{ github.event.inputs['package-version'] || 'latest' }}
18+
cancel-in-progress: false
1519

1620
jobs:
1721
verify:
18-
name: Verify PyPI install on ${{ matrix.os }}
22+
name: ${{ matrix.os }} / Python ${{ matrix.python-version }}
1923
runs-on: ${{ matrix.os }}
2024

2125
strategy:
2226
fail-fast: false
2327
matrix:
24-
os: [ubuntu-latest, macos-latest]
28+
os: [ubuntu-latest, macos-latest, windows-latest]
29+
python-version: ["3.11", "3.12"]
2530

2631
steps:
27-
- name: Setup Python
28-
uses: actions/setup-python@v4
32+
- name: Set up Python
33+
uses: actions/setup-python@v6
2934
with:
30-
python-version: '3.x'
35+
python-version: ${{ matrix.python-version }}
36+
cache: "pip"
3137

32-
- name: Create & activate virtualenv, upgrade pip, install SynKit
38+
- name: Install SynKit from PyPI
39+
shell: bash
3340
run: |
34-
python -m venv venv
35-
source venv/bin/activate
3641
python -m pip install --upgrade pip
37-
pip install synkit[all]
3842
39-
- name: Show installed SynKit version
43+
version="${{ github.event.inputs['package-version'] }}"
44+
if [ -n "$version" ]; then
45+
python -m pip install "synkit==$version"
46+
else
47+
python -m pip install synkit
48+
fi
49+
python -m pip install packaging
50+
51+
- name: Show installed package metadata
52+
shell: bash
4053
run: |
41-
source venv/bin/activate
42-
python -c "import importlib.metadata as m; print('SynKit version:', m.version('synkit'))"
54+
python - <<'PY'
55+
import importlib.metadata as metadata
56+
import sys
57+
58+
print("Python:", sys.version)
59+
print("SynKit:", metadata.version("synkit"))
60+
PY
4361
44-
- name: Write smoke-test script
62+
- name: Run import and smoke tests
63+
shell: bash
4564
run: |
46-
cat << 'EOF' > test_synkit.py
47-
from synkit.IO import rsmi_to_rsmarts
65+
python - <<'PY'
66+
import importlib.metadata as metadata
4867
49-
template = (
50-
'[C:2]=[O:3].[C:4]([H:7])[H:8]'
51-
'>>'
52-
'[C:2]=[C:4].[O:3]([H:7])[H:8]'
53-
)
68+
from packaging.version import Version
5469
55-
smart = rsmi_to_rsmarts(template)
56-
print("Reaction SMARTS:", smart)
57-
EOF
70+
from synkit.IO import rsmi_to_its, rsmi_to_rsmarts
5871
59-
- name: Run smoke-test
60-
run: |
61-
source venv/bin/activate
62-
python test_synkit.py
72+
rsmi = "[CH3:1][Cl:2].[NH3:3]>>[CH3:1][NH3+:3].[Cl-:2]"
73+
smarts = rsmi_to_rsmarts(rsmi)
74+
assert ">>" in smarts
75+
76+
version = Version(metadata.version("synkit"))
77+
if version >= Version("1.4.0"):
78+
import networkx as nx
79+
80+
from synkit.Graph.MTG.mtg import MTG
81+
82+
its = rsmi_to_its(rsmi, core=False, format="tuple")
83+
assert isinstance(its, nx.Graph)
84+
assert not any("typesGH" in attrs for _, attrs in its.nodes(data=True))
85+
86+
mtg_steps = [
87+
"[CH3:1][Cl:2].[NH3:3]>>[CH3:1][NH3+:3].[Cl-:2]",
88+
"[CH3:1][NH3+:3].[Cl-:2]>>[CH3:1][NH2:3].[Cl:2][H]",
89+
]
90+
mtg = MTG(mtg_steps, mcs_mol=True)
91+
graph = mtg.get_mtg()
92+
assert mtg._tuple_its
93+
assert graph.number_of_nodes() > 0
6394
64-
- name: Success message
65-
run: echo "✅ synkit[all] installed and smoke-test passed"
95+
print("PyPI SynKit smoke tests passed.")
96+
PY

Test/Graph/MTG/test_mtg_tuple.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,32 @@ def test_mech_fixture_round_trips_ordered_tuple_rsmi_steps(self):
299299
self.assertEqual(len(exported), len(steps))
300300
self.assertTrue(all(">>" in step for step in exported))
301301

302+
def test_string_sequences_default_to_lewis_state_graph(self):
303+
data = load_database("./Data/Testcase/mech.json.gz")
304+
mech = data[0]["mechanisms"][1]
305+
steps = [step["smart_string"] for step in mech["steps"]]
306+
307+
mtg = MTG(steps, mcs_mol=True)
308+
graph = mtg.get_mtg()
309+
310+
self.assertTrue(mtg._tuple_its)
311+
self.assertFalse(any("typesGH" in attrs for _, attrs in graph.nodes(data=True)))
312+
self.assertTrue(
313+
all("sigma_order" in attrs for _, _, attrs in graph.edges(data=True))
314+
)
315+
316+
def test_string_sequences_can_request_legacy_typesgh(self):
317+
data = load_database("./Data/Testcase/mech.json.gz")
318+
mech = data[0]["mechanisms"][1]
319+
steps = [step["smart_string"] for step in mech["steps"][:2]]
320+
321+
mtg = MTG(steps, mcs_mol=True, its_format="typesGH")
322+
323+
self.assertFalse(mtg._tuple_its)
324+
self.assertTrue(
325+
any("typesGH" in attrs for _, attrs in mtg.get_mtg().nodes(data=True))
326+
)
327+
302328
def test_mech_fixture_tuple_mtg_automatic_mapping_matches_identity_mapping(self):
303329
data = load_database("./Data/Testcase/mech.json.gz")
304330
mech = data[0]["mechanisms"][1]

Test/Vis/test_mtg_drawer.py

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77

88
from synkit.Graph.ITS.its_construction import ITSConstruction # noqa: E402
99
from synkit.Graph.MTG.mtg import MTG # noqa: E402
10-
from synkit.Vis.mtg_drawer import draw_mtg_graph, draw_mtg_steps # noqa: E402
10+
from synkit.IO import load_database # noqa: E402
11+
from synkit.Vis.mtg_drawer import ( # noqa: E402
12+
_mtg_display_graph,
13+
draw_mtg_graph,
14+
draw_mtg_steps,
15+
)
1116

1217

1318
class TestMTGDrawer(unittest.TestCase):
@@ -88,19 +93,73 @@ def test_draw_mtg_graph_accepts_raw_graph_without_mutation(self):
8893
self.assertEqual(dict(graph.nodes(data=True)), before_nodes)
8994
self.assertEqual(list(graph.edges(data=True)), before_edges)
9095

96+
def test_draw_mtg_graph_supports_3d_layout(self):
97+
mtg = self._mtg()
98+
99+
fig, ax = draw_mtg_graph(mtg, dimension="3d", layout="spring")
100+
101+
self.assertIs(fig, ax.figure)
102+
self.assertEqual(getattr(ax, "name", None), "3d")
103+
104+
def test_mtg_edge_labels_compress_by_default(self):
105+
graph = self._mtg().get_mtg()
106+
107+
compact = _mtg_display_graph(
108+
graph,
109+
mode="timeline",
110+
show_atom_map=True,
111+
show_node_badges=False,
112+
hydrogen_mode="changed",
113+
changed_only=True,
114+
compress=True,
115+
)
116+
full = _mtg_display_graph(
117+
graph,
118+
mode="timeline",
119+
show_atom_map=True,
120+
show_node_badges=False,
121+
hydrogen_mode="changed",
122+
changed_only=True,
123+
compress=False,
124+
)
125+
126+
self.assertEqual(compact.edges[1, 2]["label"], "1→1")
127+
self.assertEqual(full.edges[1, 2]["label"], "1→0→1")
128+
91129
def test_draw_mtg_steps_draws_ordered_its_panels_and_composed_panel(self):
92130
mtg = self._mtg()
93131

94132
fig, axes = draw_mtg_steps(mtg, include_composed=True, show_edge_labels=True)
95133

96134
self.assertIs(fig, axes[0].figure)
97135
self.assertEqual(len(axes), 3)
98-
self.assertEqual([ax.get_title() for ax in axes], ["Step 1", "Step 2", "Composed"])
136+
self.assertEqual(
137+
[ax.get_title() for ax in axes], ["Step 1", "Step 2", "Composed"]
138+
)
99139

100140
def test_draw_mtg_steps_validates_indices(self):
101141
with self.assertRaises(IndexError):
102142
draw_mtg_steps(self._mtg(), steps=[2])
103143

144+
def test_draw_mtg_graph_handles_real_neutral_mechanism(self):
145+
data = load_database("Data/Testcase/mech.json.gz")[0]
146+
neutral = data["mechanisms"][1]
147+
steps = [step["smart_string"] for step in neutral["steps"]]
148+
mtg = MTG(steps, mcs_mol=True)
149+
graph = mtg.get_mtg()
150+
151+
fig, ax = draw_mtg_graph(
152+
mtg,
153+
title=neutral["mech_name"],
154+
hydrogen_mode="changed",
155+
show_edge_labels=True,
156+
)
157+
158+
self.assertIs(fig, ax.figure)
159+
self.assertEqual(ax.get_title(), "Aldol reaction (neutral cat)")
160+
self.assertTrue(mtg._tuple_its)
161+
self.assertFalse(any("typesGH" in attrs for _, attrs in graph.nodes(data=True)))
162+
104163

105164
if __name__ == "__main__":
106165
unittest.main()

0 commit comments

Comments
 (0)