Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
be64abc
Add reaction progression and detector stubs
janitha-mahanthe Jun 27, 2026
5b954e1
Merge branch 'main' into 0.3-ARXSession
janitha-mahanthe Jun 28, 2026
65d16df
Add PoolSpecies model and mol population helper
janitha-mahanthe Jun 28, 2026
a087fd2
Document PoolSpecies fields and mol population
janitha-mahanthe Jul 2, 2026
03ae6fb
Merge branch 'main' into 0.3-ARXSession
janitha-mahanthe Jul 6, 2026
4e04876
Merge branch 'main' into 0.3-ARXSession
janitha-mahanthe Jul 6, 2026
2f6d842
Add index-based functional group detector helper
janitha-mahanthe Jul 6, 2026
df45b60
Add index-based functional groups detector
janitha-mahanthe Jul 6, 2026
b537e59
Add DetectedChemistryFilter to collect chemistries
janitha-mahanthe Jul 6, 2026
986977e
Add epoxy/amine reaction rules and progression
janitha-mahanthe Jul 6, 2026
2489ddf
Add epoxy-amine FGs, atom indexes, and warning util
janitha-mahanthe Jul 7, 2026
a01caea
Add staged amine-epoxy reaction rules
janitha-mahanthe Jul 7, 2026
c2a93bc
WIP: add progressive chemistry rules skeleton
janitha-mahanthe Jul 7, 2026
7df0749
Add epoxy simulation example config
janitha-mahanthe Jul 9, 2026
8ab6dd8
Implement index-based functional group detection
janitha-mahanthe Jul 9, 2026
4b02a38
Clean reaction products in progression
janitha-mahanthe Jul 9, 2026
e6516ad
Add index-based reaction detection in progression
janitha-mahanthe Jul 9, 2026
5d86d4e
Add index-constrained reaction progression prep
janitha-mahanthe Jul 10, 2026
5004246
Refactor prepare_reactions.py code style
janitha-mahanthe Jul 10, 2026
1fa9111
Fix product idx remapping for multi-fragment products
janitha-mahanthe Jul 10, 2026
7f77e7e
Remove unused detectors and processor files
janitha-mahanthe Jul 10, 2026
fcfe4a0
Add DeduplicationDetector for reactions
janitha-mahanthe Jul 10, 2026
33adcd3
Add RDKit reaction dedup across progression
janitha-mahanthe Jul 13, 2026
9259d09
Update deduplication_detector.py
janitha-mahanthe Jul 13, 2026
c262190
Refactor reaction_progression for clarity
janitha-mahanthe Jul 14, 2026
4fb669d
Refactor reaction progression logic
janitha-mahanthe Jul 14, 2026
fb1d557
Refine reaction progression loop state handling
janitha-mahanthe Jul 14, 2026
5f350a7
Add uncoupled pair-based duplicate detection
janitha-mahanthe Jul 14, 2026
7d50276
Fix forced-reaction mode for same reactants
janitha-mahanthe Jul 14, 2026
1b4f574
Update test_epoxy.json
janitha-mahanthe Jul 14, 2026
b9f4ae7
Update functional_groups_detector.py
janitha-mahanthe Jul 14, 2026
fb47faa
Refine deduplication cache behavior
janitha-mahanthe Jul 14, 2026
2c8e153
Delete fragment_comparison.py
janitha-mahanthe Jul 14, 2026
03b9d07
Raise error when no reactions are active
janitha-mahanthe Jul 14, 2026
66bd8cf
Refine reaction progression loop
janitha-mahanthe Jul 14, 2026
61c0df2
Fix progression deduplication handling
janitha-mahanthe Jul 14, 2026
b25fe07
Remove noisy debug output
janitha-mahanthe Jul 14, 2026
339638c
Potential fix for pull request finding
janitha-mahanthe Jul 14, 2026
070cc31
Potential fix for pull request finding
janitha-mahanthe Jul 14, 2026
8497c38
Potential fix for pull request finding
janitha-mahanthe Jul 14, 2026
0a52366
Potential fix for pull request finding
janitha-mahanthe Jul 14, 2026
bbfa638
Potential fix for pull request finding
janitha-mahanthe Jul 14, 2026
0450d2f
Potential fix for pull request finding
janitha-mahanthe Jul 14, 2026
7ff392f
Potential fix for pull request finding
janitha-mahanthe Jul 14, 2026
fe39077
Remove unused numpy indices import
Copilot Jul 14, 2026
e865c75
Refactor forced index handling in prepare_reactions.py
janitha-mahanthe Jul 14, 2026
f828e69
Potential fix for pull request finding
janitha-mahanthe Jul 14, 2026
d488f58
Merge pull request #100 from NanoCIPHER-Lab/0.3-ARXSession
janitha-mahanthe Jul 14, 2026
f425265
Add vinyl polymerization reaction support
janitha-mahanthe Jul 14, 2026
8f9c279
Fix vinyl polymerization SMARTS and radical handling
janitha-mahanthe Jul 14, 2026
8c3f4ad
Create test_styrene.json
janitha-mahanthe Jul 14, 2026
ff25387
Improve 3D molecule prep and add repair step
janitha-mahanthe Jul 15, 2026
735c20a
Generalize vinyl radical SMARTS patterns
janitha-mahanthe Jul 15, 2026
e6cd09c
Make RDKit deduplication radical-aware
janitha-mahanthe Jul 15, 2026
584e6aa
Track radical atoms before reaction dedup
janitha-mahanthe Jul 15, 2026
e52f82f
Add loop controls for reaction progression
janitha-mahanthe Jul 15, 2026
3602aaa
Refine loop parsing and trim debug output
janitha-mahanthe Jul 15, 2026
80532a8
Fix vinyl radical type and expand parser tests
janitha-mahanthe Jul 15, 2026
6960e27
Remove legacy compatibility shim
janitha-mahanthe Jul 15, 2026
7f97a23
Refresh warning banner ASCII art
janitha-mahanthe Jul 15, 2026
91a5b7f
Refactor reaction progression and docs
janitha-mahanthe Jul 20, 2026
ef489e1
Refactor reaction preparation workflow
janitha-mahanthe Jul 20, 2026
50e1c67
Fix epoxy polymer reaction rules
janitha-mahanthe Jul 21, 2026
19abeb7
Add registries for groups and reactions
janitha-mahanthe Jul 21, 2026
8f85eae
Add package exports for detector libraries
janitha-mahanthe Jul 21, 2026
31e15b4
Add polyamide reaction library
janitha-mahanthe Jul 21, 2026
13b70d9
Add mixed AB groups and polymer reaction libraries
janitha-mahanthe Jul 21, 2026
90e9792
Add nitrogen functional groups library
janitha-mahanthe Jul 21, 2026
59c8200
Update registry.py
janitha-mahanthe Jul 21, 2026
92ad793
Add polycarbonate synthesis pathways
janitha-mahanthe Jul 21, 2026
3fa18ed
Fix phosgene SMARTS to prevent partial matches
janitha-mahanthe Jul 21, 2026
126dedc
Add polyester polycondensation reaction library
janitha-mahanthe Jul 22, 2026
0f8f49c
Add polysiloxane reactions, disable draft modules
janitha-mahanthe Jul 22, 2026
9f82c8d
Disable untested halide and polysulfide entries
janitha-mahanthe Jul 22, 2026
ead2824
Add polythioester reaction library entries
janitha-mahanthe Jul 22, 2026
38052b9
Add functional groups and polyurea reaction
janitha-mahanthe Jul 22, 2026
49bb79a
Add polyurethanes reactions library module
janitha-mahanthe Jul 22, 2026
a6fd6a2
Update polyurethanes.py
janitha-mahanthe Jul 22, 2026
c4de1ed
Improve embedding for congested polymers
janitha-mahanthe Jul 24, 2026
29da8c9
Add vinyl polymer reaction library definitions
janitha-mahanthe Jul 24, 2026
b12183f
Add new functional group library modules
janitha-mahanthe Jul 24, 2026
f49b0c3
Refactor reaction libraries into package modules
janitha-mahanthe Jul 24, 2026
6c418ee
Disable aromatic group registration
janitha-mahanthe Jul 24, 2026
ab4281f
Add ring/silicon groups and normalize defs
janitha-mahanthe Jul 24, 2026
99fc398
Normalize polymer reaction library entries
janitha-mahanthe Jul 24, 2026
f2fb2f6
Normalization
janitha-mahanthe Jul 24, 2026
d48858b
Create thiol_ene_polymers.py
janitha-mahanthe Jul 24, 2026
cd2f3c8
Potential fix for pull request finding
janitha-mahanthe Jul 24, 2026
adc3739
Potential fix for pull request finding
janitha-mahanthe Jul 24, 2026
8ae3698
Potential fix for pull request finding
janitha-mahanthe Jul 24, 2026
dc12f88
Potential fix for pull request finding
janitha-mahanthe Jul 24, 2026
188d51a
Potential fix for pull request finding
janitha-mahanthe Jul 24, 2026
b16c63f
Potential fix for pull request finding
janitha-mahanthe Jul 24, 2026
d7ae649
Merge pull request #102 from NanoCIPHER-Lab/0.3-vinyl-dev
janitha-mahanthe Jul 24, 2026
5f7d612
Add TFE vinyl polymerization support
janitha-mahanthe Jul 24, 2026
71b2a3b
Bump to v0.3 and update reaction workflows
janitha-mahanthe Jul 27, 2026
014e3e0
Add LAMMPS template deduplication step
janitha-mahanthe Jul 27, 2026
8cce4dd
Add s_m sulfone params to PCFF force field
janitha-mahanthe Jul 28, 2026
a5f5f80
Improve reaction/merge handling and PCFF params
janitha-mahanthe Jul 28, 2026
90f4a5d
Add changelog entry for v0.3
janitha-mahanthe Jul 28, 2026
5a330b8
Use full atom count (H included) for num_atoms
janitha-mahanthe Jul 28, 2026
7d37cff
Add input snapshot and polymerization updates
janitha-mahanthe Jul 29, 2026
5e2a153
Disable transesterification and tweak equilibration
janitha-mahanthe Jul 31, 2026
7a310fe
Validate reaction initiator atom maps
janitha-mahanthe Aug 4, 2026
18c3386
Add LAMMPS map-based template deduplication
janitha-mahanthe Aug 4, 2026
f1ca90a
Potential fix for pull request finding
janitha-mahanthe Aug 7, 2026
bb12d91
Potential fix for pull request finding
janitha-mahanthe Aug 7, 2026
73ea96a
Potential fix for pull request finding
janitha-mahanthe Aug 7, 2026
24837c0
Potential fix for pull request finding
janitha-mahanthe Aug 7, 2026
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
2 changes: 1 addition & 1 deletion AutoREACTER/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

AutoREACTER is a tool for automated reaction-based molecular system generation.
"""
__version__ = "0.2.3"
__version__ = "0.3"

__title__ = "AutoREACTER"
__author__ = "Janitha Mahanthe, Jacob Gissinger"
Expand Down
60 changes: 0 additions & 60 deletions AutoREACTER/_compat.py

This file was deleted.

16 changes: 14 additions & 2 deletions AutoREACTER/arx_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from contextlib import contextmanager
import os
from pathlib import Path
import shutil
import sys
import threading
from PIL import Image
Expand All @@ -30,6 +31,9 @@
from AutoREACTER.reaction_preparation.ff_wrapper.REACTER_files_builder import REACTERFilesBuilder
from AutoREACTER.sim_setup.simulation_setup import SimulationSetupManager

class NoReactionGenerated(Exception):
"""Custom exception raised when no reaction is generated in the pipeline."""
pass

class ErrorHandler:
"""
Expand Down Expand Up @@ -98,7 +102,8 @@ def __init__(self, input: Path) -> None:
self.img_dir = self.session.images_dir
# with open(self.session.output_dir / "AutoREACTER.log", 'w') as f:
# f.write("--- Starting AutoREACTER Session ---\n")

# Save a copy of the input JSON to the output directory
self._save_input_json(abs_path)
# Save an initial grid image of all monomers
self._save_rdkit_img(
InputParser().initial_molecules_image_grid(self.session),
Expand Down Expand Up @@ -314,6 +319,10 @@ def process(self):
# ------------------------------------------------------------------
# Internal helpers – lazy detection & image saving
# ------------------------------------------------------------------
def _save_input_json(self, abs_path: Path):
destination_file = "input.json"
destination_path = self.session.output_dir / destination_file
shutil.copy(abs_path, destination_path)

def _ensure_fg_detected(self):
"""
Expand Down Expand Up @@ -384,7 +393,10 @@ def _save_rdkit_img(self, img, path: Path, is_non_reactant: bool = False):
if img is None:
if is_non_reactant:
return
raise ValueError("No image was generated. Cannot save molecule image.")
raise NoReactionGenerated(
"No reaction was generated. This is an error from AutoREACTER. "
"Please file an issue on https://github.com/NanoCIPHER-Lab/AutoREACTER/issues to improve the software."
)

# Case 1: PIL image
if hasattr(img, "save"):
Expand Down
154 changes: 150 additions & 4 deletions AutoREACTER/detectors/functional_groups_detector.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, List

"""
* Monomer Functionality Detection Module
--------------------------------------
Expand Down Expand Up @@ -111,12 +112,14 @@
from AutoREACTER.input_parser import MonomerEntry

# Conditional import for FunctionalGroupsLibrary to support both installed and local usage.
from .functional_groups_library import FunctionalGroupsLibrary
from AutoREACTER.detectors.functional_groups_library.registry import FunctionalGroupsLibrary

logger = logging.getLogger(__name__) # Module-level logger for future diagnostics.
if TYPE_CHECKING:
from AutoREACTER.session import Session

from AutoREACTER.reaction_preparation.reaction_processor.reaction_progression import (
MonomerRoleforIndexBasedFGDetection,
)

@dataclass(slots=True)
class FunctionalGroupInfo:
Expand All @@ -127,19 +130,23 @@ class FunctionalGroupInfo:
functionality_type (str): Type of functionality (e.g., 'vinyl', 'mono', 'di_identical', 'di_different').
fg_name (str): Name of the functional group (e.g., 'acrylate').
fg_smarts_1 (str): Primary SMARTS pattern for matching.
fg_1_indexes (Optional[Tuple[int, ...]]): Atom indices for matches of fg_smarts_1.
fg_count_1 (int): Number of matches for fg_smarts_1.
fg_smarts_2 (Optional[str]): Secondary SMARTS pattern (for 'di_different' types).
fg_2_indexes (Optional[Tuple[int, ...]]): Atom indices for matches of fg_smarts_2.
fg_count_2 (Optional[int]): Number of matches for fg_smarts_2.
"""
functionality_type: str
fg_name: str
fg_smarts_1: str
fg_count_1: int
fg_1_indexes: Optional[Tuple[Tuple[int, ...], ...]] = None
fg_smarts_2: Optional[str] = None
fg_count_2: Optional[int] = None
fg_2_indexes: Optional[Tuple[Tuple[int, ...], ...]] = None


@dataclass(slots=True, frozen=True)
@dataclass(slots=True)
class MonomerRole:
"""
Immutable dataclass representing a monomer with its detected functional groups.
Expand All @@ -152,6 +159,10 @@ class MonomerRole:
smiles: str
name: str
functionalities: Tuple[FunctionalGroupInfo, ...] # Tuple of detected functionalities for the monomer
rdkit_mol: Optional[rdchem.Mol] = None # Optional RDKit molecule object for the monomer
indexes_in_template: List[int] = None # Optional list of atom indices in the template
is_monomer: bool = False # Flag indicating if the monomer is eligible for polymerization
is_looped: bool = False

@dataclass(slots=True)
class FunctionalGroupVisualization:
Expand Down Expand Up @@ -349,6 +360,7 @@ def functional_groups_detector(
smiles=smiles,
name=monomer.name,
functionalities=tuple(detected_functionalities),
is_monomer=True
)
)

Expand Down Expand Up @@ -401,6 +413,140 @@ def _functional_groups_detector_for_visualization(
)
)
return monomer_roles_visualization

def _detect_functional_groups_by_index(
self,
mol: Chem.Mol,
smarts: str,
atom_indices: list[int],
) -> bool:
"""Return True when any SMARTS match overlaps the supplied atom indices."""
target_indices = set(atom_indices)

patt = Chem.MolFromSmarts(smarts)
if patt is None:
logger.warning("Invalid SMARTS pattern: %s", smarts)
return False

matches = mol.GetSubstructMatches(patt, uniquify=True)
return any(target_indices.intersection(match) for match in matches)

def index_based_functional_groups_detector(
self,
monomer_roles_in: list[MonomerRoleforIndexBasedFGDetection],
) -> list[MonomerRole] | bool:
"""
Detect functional groups across a list of monomers and categorize them into roles,
restricted to a given set of atom indices per monomer.

Iterates over predefined monomer_types, matches each against the monomer's
rdkit_mol, and keeps only matches that overlap with the monomer's
`indexes_in_template`. Prints matches for debugging/user feedback.

Args:
monomer_roles_in (list[MonomerRoleforIndexBasedFGDetection]): List of monomer
roles to process, each carrying the atom indices of interest.

Returns:
list[MonomerRole] | bool: List of MonomerRole objects with index-filtered
functionalities, or False if none detected.

Notes:
- Index-based rule: at least ONE match overlapping the given indices is
enough to qualify, regardless of functionality_type. This intentionally
breaks the whole-molecule 'di_identical' (>=2 matches) rule, since here
we only care whether the given index sits inside a valid functional group,
not how many total sites exist on the monomer.
"""

monomer_roles_out = []

for monomer in monomer_roles_in:
if monomer.is_looped:
continue # Skip already processed monomers

mol = monomer.rdkit_mol

target_indices = set(monomer.indexes_in_template or [])
detected_functionalities = []
all_matches = []

# Check against each predefined functional group type.
for functional_group in self.monomer_types.values():
ftype = functional_group["functionality_type"]
smarts_1 = functional_group["smarts_1"]
smarts_2 = functional_group.get("smarts_2")

patt1 = Chem.MolFromSmarts(smarts_1)
if patt1 is None:
logger.warning(f"Invalid primary SMARTS: {smarts_1}")
continue

matches1 = mol.GetSubstructMatches(patt1, uniquify=True)
# Index-based filter: keep only matches touching at least one target index.
matches1_hit = [m for m in matches1 if target_indices.intersection(m)]
count_1 = len(matches1_hit)

count_2 = None
matches2_hit = []

if smarts_2:
patt2 = Chem.MolFromSmarts(smarts_2)
if patt2 is None:
logger.warning(f"Invalid secondary SMARTS: {smarts_2}")
continue

matches2 = mol.GetSubstructMatches(patt2, uniquify=True)
matches2_hit = [m for m in matches2 if target_indices.intersection(m)]
count_2 = len(matches2_hit)

# di_different: still need one overlapping hit on EACH pattern.
functionality_count = 2 if (count_1 >= 1 and count_2 >= 1) else 0
else:
# vinyl / mono / di_identical: ONE overlapping match is enough.
# (Breaks the normal di_identical >=2 rule on purpose for index-based detection.)
functionality_count = 1 if count_1 >= 1 else 0

if functionality_count > 0:
functional_matches = tuple(matches1_hit) + tuple(matches2_hit)
all_matches.extend(functional_matches)

# Log detected functionality for debugging/user feedback.
# print(f"{monomer.smiles} has functionality: {functional_group['group_name']}")

detected_functionalities.append(
FunctionalGroupInfo(
functionality_type=ftype,
fg_name=functional_group["group_name"],
fg_smarts_1=smarts_1,
fg_count_1=count_1,
fg_1_indexes=tuple(matches1_hit) if matches1_hit else None,
fg_smarts_2=smarts_2,
fg_count_2=count_2,
fg_2_indexes=tuple(matches2_hit) if matches2_hit else None,
)
)

# Add to roles if any functionalities detected.
if detected_functionalities:
monomer_roles_out.append(
MonomerRole(
smiles=monomer.smiles,
name=monomer.name,
rdkit_mol=monomer.rdkit_mol,
functionalities=tuple(detected_functionalities),
is_monomer=False, # This is a product, not an input monomer
is_looped=False, # Yet to be processed in the loop
indexes_in_template=monomer.indexes_in_template,
)
)

# Store results for potential downstream use.
if not monomer_roles_out:
return False # No functional groups detected; handle as needed
# first break condition: if no monomer roles are detected, return False to indicate no further processing is needed.

return monomer_roles_out # Return list of MonomerRole; visualization not considered here.

def functional_group_highlighted_molecules_image_grid(self, session: Session) -> Image:
"""Convert monomer roles with detected functionalities into visualizations.
Expand Down
Loading
Loading