diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6a5edc6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +name: CI + +on: + push: + pull_request: + +jobs: + test: + name: Python ${{ matrix.python-version }} + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11"] + + steps: + - name: Check out source + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: pip + + - name: Install package + run: python -m pip install -e ".[dev]" + + - name: Run tests + run: python -W error::ResourceWarning -m unittest discover -s tests + + - name: Compile sources + run: python -m compileall groupy tests scripts + + - name: Build Python package + run: python -m build + + - name: Check Windows app build command + run: python scripts/build_windows_app.py --dry-run diff --git a/.gitignore b/.gitignore index 7b6caf3..5267b91 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ __pycache__/ # Distribution / packaging .Python build/ +build_check_dist/ develop-eggs/ dist/ downloads/ diff --git a/Groupy.egg-info/PKG-INFO b/Groupy.egg-info/PKG-INFO deleted file mode 100644 index 986c57b..0000000 --- a/Groupy.egg-info/PKG-INFO +++ /dev/null @@ -1,21 +0,0 @@ -Metadata-Version: 2.2 -Name: Groupy -Version: 3.0.0 -Summary: Groupy -- A Useful Tool for Molecular Analysis -Home-page: https://github.com/47-5/Groupy -Author: Ruichen Liu -Author-email: 1197748182@qq.com -Requires-Python: >3.5 -Requires-Dist: rdkit -Requires-Dist: pandas -Requires-Dist: numpy -Requires-Dist: tqdm -Requires-Dist: ase -Requires-Dist: joblib -Requires-Dist: openpyxl -Dynamic: author -Dynamic: author-email -Dynamic: home-page -Dynamic: requires-dist -Dynamic: requires-python -Dynamic: summary diff --git a/Groupy.egg-info/SOURCES.txt b/Groupy.egg-info/SOURCES.txt deleted file mode 100644 index 249859f..0000000 --- a/Groupy.egg-info/SOURCES.txt +++ /dev/null @@ -1,19 +0,0 @@ -README.md -setup.py -Groupy.egg-info/PKG-INFO -Groupy.egg-info/SOURCES.txt -Groupy.egg-info/dependency_links.txt -Groupy.egg-info/entry_points.txt -Groupy.egg-info/requires.txt -Groupy.egg-info/top_level.txt -groupy/__init__.py -groupy/gp_calculator.py -groupy/gp_convertor.py -groupy/gp_counter.py -groupy/gp_generator.py -groupy/gp_loader.py -groupy/gp_tool.py -groupy/gp_viewer.py -groupy/group_contribution_parameters.xlsx -groupy/group_order.xlsx -groupy/groupy_main.py \ No newline at end of file diff --git a/Groupy.egg-info/dependency_links.txt b/Groupy.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/Groupy.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Groupy.egg-info/entry_points.txt b/Groupy.egg-info/entry_points.txt deleted file mode 100644 index df1c40c..0000000 --- a/Groupy.egg-info/entry_points.txt +++ /dev/null @@ -1,2 +0,0 @@ -[console_scripts] -Groupy = groupy.groupy_main:main diff --git a/Groupy.egg-info/requires.txt b/Groupy.egg-info/requires.txt deleted file mode 100644 index eef2c7e..0000000 --- a/Groupy.egg-info/requires.txt +++ /dev/null @@ -1,7 +0,0 @@ -rdkit -pandas -numpy -tqdm -ase -joblib -openpyxl diff --git a/Groupy.egg-info/top_level.txt b/Groupy.egg-info/top_level.txt deleted file mode 100644 index 3635b43..0000000 --- a/Groupy.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -groupy diff --git a/PACKAGING_SIZE_REPORT.md b/PACKAGING_SIZE_REPORT.md new file mode 100644 index 0000000..c9d4a56 --- /dev/null +++ b/PACKAGING_SIZE_REPORT.md @@ -0,0 +1,125 @@ +# Groupy Packaging Size Report + +Created: 2026-06-25 + +This report records the packaged Windows app size baseline and the result after rebuilding from a clean OpenBLAS-based packaging environment. + +## Current Baseline + +Measured artifact: + +```text +dist/Groupy/Groupy.exe +dist/Groupy/_internal +``` + +Overall size: + +| Path | Size | +| --- | ---: | +| `dist/Groupy` | 772.41 MB | +| `dist/Groupy/_internal` | 753.50 MB | +| `dist/Groupy/Groupy.exe` | 18.91 MB | + +Top component groups: + +| Component | Count | Size | +| --- | ---: | ---: | +| MKL DLLs | 25 | 538.61 MB | +| ICU DLLs | 12 | 46.96 MB | +| RDKit package and DLLs | 244 | 35.63 MB | +| Qt DLLs | 5 | 22.53 MB | +| PySide6 package | 121 | 22.34 MB | +| pandas package | 53 | 12.50 MB | +| matplotlib package | 211 | 11.22 MB | +| numpy package | 13 | 5.87 MB | +| Tcl/Tk files | 7 | 3.44 MB | + +Largest files: + +| File | Size | +| --- | ---: | +| `mkl_avx512.3.dll` | 71.92 MB | +| `mkl_core.3.dll` | 68.57 MB | +| `mkl_avx10.3.dll` | 64.55 MB | +| `mkl_avx2.3.dll` | 45.28 MB | +| `mkl_mc3.3.dll` | 44.96 MB | +| `mkl_intel_thread.3.dll` | 37.27 MB | +| `mkl_def.3.dll` | 35.34 MB | +| `icudt78.dll` | 31.58 MB | +| `mkl_tbb_thread.3.dll` | 28.46 MB | +| `mkl_rt.3.dll` | 26.60 MB | + +## OpenBLAS Rebuild Result + +Measured after rebuilding from a clean conda-forge packaging environment with OpenBLAS-linked numerical dependencies. + +Overall size: + +| Path | Size | +| --- | ---: | +| `dist/Groupy` | 228.43 MB | +| `dist/Groupy/_internal` | 213.04 MB | +| `dist/Groupy/Groupy.exe` | 15.39 MB | + +Top component groups: + +| Component | Count | Size | +| --- | ---: | ---: | +| ICU DLLs | 3 | 38.21 MB | +| RDKit package and DLLs | 244 | 35.63 MB | +| OpenBLAS/BLAS DLLs | 3 | 27.32 MB | +| Qt DLLs | 5 | 22.53 MB | +| PySide6 package | 120 | 22.24 MB | +| pandas package | 53 | 12.50 MB | +| numpy package | 13 | 5.87 MB | +| matplotlib package | 0 | 0.00 MB | +| MKL DLLs | 0 | 0.00 MB | + +Largest files: + +| File | Size | +| --- | ---: | +| `icudt78.dll` | 31.58 MB | +| `openblas.dll` | 27.04 MB | +| `Qt6Gui.dll` | 8.45 MB | +| `libcrypto-3-x64.dll` | 7.08 MB | +| `Qt6Widgets.dll` | 6.24 MB | +| `python311.dll` | 5.91 MB | +| `Qt6Core.dll` | 5.55 MB | + +Reduction compared with the MKL-linked baseline: + +| Path | Before | After | Reduction | +| --- | ---: | ---: | ---: | +| `dist/Groupy` | 772.41 MB | 228.43 MB | 543.98 MB | +| `dist/Groupy/_internal` | 753.50 MB | 213.04 MB | 540.46 MB | +| `dist/Groupy/Groupy.exe` | 18.91 MB | 15.39 MB | 3.52 MB | + +## Interpretation + +- MKL is the dominant size source, accounting for about 538.61 MB. +- Qt/PySide6 is required by the desktop GUI and accounts for about 44.87 MB before Qt transitive DLLs such as ICU. +- RDKit is required by property calculation, group counting, and 2D structure preview. +- `matplotlib` appears in the current `_internal` even though the build script excludes it by default. This likely means the measured `dist/Groupy` folder contains stale files from an older build or an over-collected RDKit subtree. +- Rebuilding from a clean OpenBLAS-based packaging environment removed MKL and stale `matplotlib` contents. The current optimized artifact is about 228.43 MB and has been confirmed to run normally by the user. + +## Optimization Order + +1. Keep using the clean conda-forge packaging environment for release builds. +2. Retest `dist/Groupy/Groupy.exe` after every packaging change. +3. If more reduction is needed, investigate ICU, RDKit, Qt/PySide6, and Tcl/Tk in that order. +4. If RDKit is still over-collected, replace broad `--collect-submodules=rdkit` with a narrower set of required RDKit modules and retest. + +## Commands + +Recommended clean packaging environment: + +```powershell +conda create -n groupy_package -c conda-forge python=3.11 rdkit pandas numpy openpyxl tqdm joblib pyside6 pyinstaller "libblas=*=*openblas" +conda activate groupy_package +python -m pip install -e . --no-deps +python scripts\build_windows_app.py +``` + +The build script removes `dist/Groupy` before a default onedir build. Use `--no-clean-dist` only when debugging PyInstaller behavior and stale output files are acceptable. diff --git a/README.md b/README.md index ba15664..e7706a6 100644 --- a/README.md +++ b/README.md @@ -12,18 +12,96 @@ One may create an environment using Anaconda: `conda activate groupy_env` -Install: +Install for development: -`pip install .\Groupy\dist\groupy-3.0.0.tar.gz` +`python -m pip install -e .` -`conda install -c conda-forge openbabel`(**Do not** use `pip install openbabel` ) +Install visualization support when needed: -Then one can enter `Groupy` in terminal to start the Groupy. +`python -m pip install -e ".[viewer]"` -### Manual and Documention -The user manual is in the manual folder, and the API documentation can be found in the doc folder. +Install desktop GUI support when needed: + +`python -m pip install -e ".[gui]"` + +Install Windows app packaging support when needed: + +`python -m pip install -e ".[gui,package]"` + +Install conversion and Gaussian input generation support when needed: + +`conda install -c conda-forge openbabel` (**Do not** use `pip install openbabel`.) + +Then one can enter `Groupy` in terminal to start Groupy. +OpenBabel is only required for conversion and Gaussian input generation workflows. +ASE is only required for molecular visualization workflows. + +For non-interactive use, one can run commands such as: + +`Groupy count --smiles C1CCCC1` + +`Groupy calculate --smiles C1CCCC1` + +`Groupy convert --input molecule.xyz --from xyz --to mol2 --output molecule.mol2` + +To launch the desktop GUI after installing GUI support: + +`Groupy-GUI` + +The GUI supports SMILES text input, SMILES file import, 2D structure preview, property calculation, group counting, calculation/counting options, and CSV export. + +To build a double-clickable Windows app folder: + +`python scripts/build_windows_app.py` + +The default build output is `dist/Groupy/Groupy.exe`. Use `--mode onefile` to build a single executable. + +For a smaller package, build from a clean packaging environment instead of a broad development environment: + +```powershell +conda create -n groupy_package -c conda-forge python=3.11 rdkit pandas numpy openpyxl tqdm joblib pyside6 pyinstaller +conda activate groupy_package +python -m pip install -e . --no-deps +python scripts\build_windows_app.py +``` + +The `_internal` folder contains bundled runtime libraries. Large MKL or BLAS DLLs usually come from the build environment and should not be deleted manually unless the packaged app is retested. Build from the clean conda-forge OpenBLAS packaging environment in `PACKAGING_SIZE_REPORT.md` to avoid bundling Intel MKL. +The build script removes the previous output folder before building so stale `_internal` files do not affect package-size checks. Use `--no-clean-dist` only for debugging. +Current packaging-size findings and optimization order are tracked in `PACKAGING_SIZE_REPORT.md`. + +Before distributing a packaged app to ordinary users, follow `RELEASE_CHECKLIST.md`. + +The desktop app is intended for ordinary users who need SMILES-based property calculation, group counting, and CSV export. Optional workflows have separate dependency requirements: + +- Conversion and Gaussian input generation require OpenBabel from conda-forge and are not part of the default GUI workflow. +- Molecular visualization requires `.[viewer]` and ASE. +- A packaged Windows app should be tested on a clean Windows machine before distribution. +- Package size optimization is intentionally deferred until the user-facing workflow is stable, because removing runtime DLLs without retesting can break the executable. + +For Python scripts or GUI integrations, use quiet batch calls: + +```python +from groupy.gp_calculator import Calculator +from groupy.gp_counter import Counter + +Calculator().calculate_mols("SMILES.txt", "calculate.csv", verbose=False) +Counter().count_mols("SMILES.txt", "count.csv", add_smiles=True, verbose=False) +``` + +Batch APIs keep processing failed molecules by default for backward compatibility. Pass `continue_on_error=False` when a script or GUI workflow should stop at the first invalid input. + +CI runs the smoke tests, source compilation check, `python -m build`, and a dry-run Windows app packaging command. + +### Manual and Documentation + +Current user manuals: + +- Chinese: `manual/USER_MANUAL_zh.md` +- English: `manual/USER_MANUAL_en.md` + +The older background-oriented manual remains in `manual/Groupy_manual.md`. Generated API documentation can be found in the doc folder and should be kept separate from source documentation. # Known limitation when calculating properties of molecules, *simultaneous* type parameters may lead to some mistake results, so we set the -default parameter type is *stepwise* \ No newline at end of file +default parameter type is *stepwise* diff --git a/REFACTORING_PLAN.md b/REFACTORING_PLAN.md new file mode 100644 index 0000000..9d2037d --- /dev/null +++ b/REFACTORING_PLAN.md @@ -0,0 +1,279 @@ +# Groupy Refactoring Plan + +Created: 2026-06-24 + +This document captures the agreed refactoring direction for Groupy. The guiding principle is to stabilize behavior first, then modernize structure in small, verifiable steps. + +## Goals + +- Make local development work with `python -m pip install -e .`. +- Keep existing scientific behavior stable while changing project structure. +- Separate library APIs from command-line interaction. +- Prepare for a desktop GUI that ordinary users can launch by double-clicking. +- Improve maintainability without rewriting the chemistry logic all at once. +- Build a minimal regression test suite before deeper refactors. + +## GUI Target + +The final user-facing application should be a desktop GUI, not a web dashboard. The preferred route is PySide6/Qt because it supports a native-feeling Windows application and can be packaged into a double-clickable executable. + +Recommended GUI scope: + +- Single-molecule property calculation from SMILES. +- Single-molecule group counting from SMILES. +- Batch calculation from `.txt`, `.csv`, or `.xlsx`. +- Batch group counting from `.txt`, `.csv`, or `.xlsx`. +- 2D structure preview for the selected or first SMILES. +- CSV export for tabular results. +- Clear dependency warnings for optional conversion/viewer features. + +Packaging target: + +- Windows executable or application folder built with PyInstaller or Nuitka. +- No requirement for end users to open a terminal. +- OpenBabel-dependent features should be optional or packaged only after dependency behavior is well understood. + +The GUI should call stable library APIs, not the legacy `input()` menu. + +## Phase 1: Packaging And Repository Hygiene + +- [x] Keep `pyproject.toml` as the canonical packaging metadata. +- [x] Keep `setup.py` only as a legacy compatibility shim, or remove it once no longer needed. +- [x] Remove generated artifacts from Git tracking: + - `build/` + - `dist/` + - `Groupy.egg-info/` +- [x] Confirm `.gitignore` continues to ignore those generated paths. +- [x] Verify: + - `python -m pip install -e .` + - `python -m pip show Groupy` + - `python -m compileall groupy` + +## Phase 2: Regression Test Baseline + +Add a small smoke-test suite before changing core behavior. + +- [x] Add a test baseline in `tests/test_smoke.py`. +- [x] Add `pytest` to the `dev` optional dependency group. +- [x] Test that `Loader` can load bundled Excel data. +- [x] Test that `Counter().count_a_mol("C1CCCC1")` returns a stable result. +- [x] Test that `Calculator().calculate_a_mol("C1CCCC1")` completes and returns expected keys. +- [x] Test `Tool.load_smiles_iterator()` for: + - `.txt` + - `.csv` + - `.xlsx` +- [x] Test that the console entry point starts and can exit cleanly. + +Current test command: + +```powershell +python -W error::ResourceWarning -m unittest discover -s tests +``` + +## Phase 3: Separate Core API From CLI + +The current `groupy_main.py` is a fully interactive menu. Preserve it initially, but introduce a modern CLI layer around the existing API. + +- [x] Add `groupy/cli.py`. +- [x] Add `groupy/api.py` for non-interactive workflows shared by CLI and future GUI code. +- [x] Move console entry point from `groupy.groupy_main:main` to the new CLI once ready. +- [x] Preserve the old interactive menu as a subcommand or compatibility path. +- [x] Add scriptable commands such as: + - [x] `Groupy count --smiles C1CCCC1` + - [x] `Groupy calculate --smiles C1CCCC1` + - [x] `Groupy calculate --input SMILES.txt --output result.csv` + - [x] `Groupy convert --input molecule.xyz --from xyz --to mol2 --output molecule.mol2` +- [x] Keep command-line parsing separate from chemistry logic. + +Current CLI behavior: + +- `Groupy` starts the legacy interactive menu. +- `Groupy interactive` starts the legacy interactive menu explicitly. +- `Groupy count --smiles C1CCCC1` prints nonzero group counts as JSON. +- `Groupy count --smiles C1CCCC1 --output count.csv` writes one-row CSV output. +- `Groupy calculate --smiles C1CCCC1` prints calculated properties as JSON. +- `Groupy calculate --input SMILES.txt --output calculate.csv` writes batch calculated properties to CSV. +- `Groupy convert --input molecule.xyz --from xyz --to mol2 --output molecule.mol2` converts one molecular file through OpenBabel. + +## Phase 4: File And Path Handling + +- [x] Replace scattered `os.path` logic with `pathlib.Path` where practical in active CLI/API, conversion, generation, viewer, and batch output paths. +- [x] Centralize SMILES file loading into one helper. +- [x] Replace `list(open(...))` with context-managed file reads for SMILES input. +- [x] Use explicit encodings for text SMILES files. +- [x] Avoid writing implicit side-effect files such as `error.txt`, `xyz_fail.txt`, and `gjf_fail.txt` to the current working directory unless requested. + - [x] `Calculator.calculate_mols()` no longer writes `error.txt` unless `error_file_path` is provided. + - [x] `Convertor.batch_smi_to_xyz()` no longer writes `xyz_fail.txt` or `xyz_succeed.txt` unless explicit paths are provided. + - [x] `Generator.batch_smi_to_gjf()` no longer writes `gjf_fail.txt` or `gjf_succeed.txt` unless explicit paths are provided. +- [x] Make batch output paths predictable and configurable. + +Current file handling status: + +- `groupy.io.load_smiles_file()` is the shared loader for `.txt`, `.csv`, and `.xlsx` SMILES inputs. +- `groupy.api`, `groupy.gp_tool.Tool`, `Calculator.calculate_mols*`, and `Counter.count_mols*` use the shared loader. +- `Calculator.calculate_mols()` now supports an explicit `error_file_path` for failed SMILES output. +- `Convertor.batch_smi_to_xyz()` now supports explicit `fail_file_path` and `succeed_file_path` outputs. +- `Generator.batch_smi_to_gjf()` now supports explicit `fail_file_path` and `succeed_file_path` outputs. +- Batch CSV and generated-file outputs create their parent directories when needed. +- Conversion batch methods process input files in sorted order for stable output names. + +## Phase 5: Exceptions, Logging, And Error Reporting + +- [x] Replace bare `except:` blocks with specific exceptions. + - [x] `Calculator.calculate_a_mol()` now catches expected calculation/input errors explicitly. + - [x] `Counter.count_a_mol()` now catches expected invalid-input errors explicitly. + - [x] `Convertor` file conversion paths now catch explicit conversion exceptions. + - [x] `Generator.smi_to_gjf()` now catches explicit generation/input exceptions. +- [x] Replace `raise NotImplemented(...)` with `NotImplementedError` or `ValueError`. +- [x] Move user-facing `print()` calls toward the CLI layer for non-interactive paths. + - [x] Core `Calculator.calculate_mols*()` batch methods now support `verbose=False`. + - [x] Core `Counter.count_mols*()` batch methods now support `verbose=False`. + - [x] Conversion `Convertor.batch_*()` methods now support `verbose=False`. + - [x] Gaussian input `Generator.batch_smi_to_gjf*()` methods now support `verbose=False`. +- [x] Use `logging` in library code. + - [x] Use module loggers for invalid SMILES in core calculator/counter paths. + - [x] Use module loggers for conversion and Gaussian input generation failures. +- [x] For batch processing, continue processing failed molecules only when configured to do so. +- [x] Preserve enough failure detail to debug invalid SMILES, unsupported formats, and dependency problems. + - [x] Add `InvalidSmilesError` for failed SMILES parsing. + - [x] Failed property calculation records now include an `error` field. + - [x] Batch calculation writes requested error SMILES from structured failed records. + +Current exception/error reporting status: + +- `groupy.chem.ensure_mol()` validates SMILES and suppresses low-level RDKit parse logs. +- `groupy.exceptions.InvalidSmilesError` represents expected SMILES parsing failures. +- `groupy.exceptions.ConversionError` represents expected molecule/file conversion failures. +- Invalid single-molecule calculations return the legacy placeholder values plus a diagnostic `error` field. +- Unexpected bugs in core calculation/counting are no longer hidden by bare `except:` blocks. +- Conversion and Gaussian generation no longer hide missing OpenBabel install errors. +- `Generator.smi_to_gjf()` cleans temporary xyz files in a `finally` block. +- Core batch calculation/counting methods keep legacy progress output by default, but `verbose=False` suppresses `print()` and `tqdm` output for GUI/programmatic use. +- Conversion and Gaussian generation batch methods also keep legacy progress output by default, with `verbose=False` available for GUI/programmatic use. +- Batch calculation, counting, conversion, and Gaussian generation methods default to legacy `continue_on_error=True`, with `continue_on_error=False` available for workflows that should stop on the first failure. + +## Phase 6: Dependency Boundaries And Lazy Imports + +- [x] Keep core counting and property calculation independent from OpenBabel. +- [x] Lazy-load OpenBabel only inside conversion functions that need it. +- [x] Lazy-load visualization dependencies only inside viewer functions that need them. +- [x] Consider optional dependency groups: + - `.[convert]` + - `.[viewer]` + - `.[dev]` +- [x] Document that OpenBabel should usually be installed from conda-forge. + +Current dependency boundary status: + +- `groupy.gp_convertor` and `groupy.gp_generator` can be imported without importing OpenBabel. +- OpenBabel is loaded through `groupy.gp_convertor._load_pybel()` only when a conversion feature needs Pybel. +- Missing OpenBabel now raises an install hint for the relevant conversion feature. +- `groupy.gp_viewer` can be imported without importing ASE. +- ASE is loaded through viewer helpers only when molecular visualization needs it. +- Core installation no longer depends on ASE or OpenBabel. +- `.[viewer]` installs ASE for molecular visualization workflows. +- `.[convert]` is intentionally empty because OpenBabel should usually be installed from conda-forge. +- `.[dev]` installs build and test tooling. + +## Phase 7: Data Loading And Performance + +- [x] Cache `Loader` results so Excel files are not repeatedly parsed. +- [x] Add regression coverage around cached loader reads. +- [x] Keep SMARTS/group-counting logic behaviorally unchanged until tests cover it better. +- [x] Rename or alias `*_mpi` APIs to `*_parallel`, since they use `joblib`, not MPI. +- [x] Preserve old method names during transition for backward compatibility. + +Current data loading status: + +- `Loader.load_parameters()` caches bundled Excel parameter tables per parameter type. +- `Loader.load_group_order()` caches bundled group order tables. +- `Loader.clear_cache()` is available for tests and advanced workflows that need to refresh cached internal data. +- Public loader methods return independent copies so callers cannot mutate the process-wide cache by accident. + +Current parallel API status: + +- `Calculator.calculate_mols_parallel()` calls `calculate_mols_mpi()`. +- `Counter.count_mols_parallel()` calls `count_mols_mpi()`. +- `Convertor.batch_smi_to_xyz_parallel()` calls `batch_smi_to_xyz_mpi()`. +- `Convertor.batch_convert_file_type_parallel()` calls `batch_convert_file_type_mpi()`. +- `Convertor.batch_file_to_smi_parallel()` calls `batch_file_to_smi_mpi()`. +- `Generator.batch_smi_to_gjf_parallel()` calls `batch_smi_to_gjf_mpi()`. + +## Phase 8: Documentation And CI + +- [x] Update README with: + - [x] editable install + - [x] conda/OpenBabel note + - [x] library API examples + - [x] CLI examples + - [x] GUI install and launch examples + - [x] packaged app release checklist link +- [x] Add a build check: + - `python -m build` +- [x] Add GitHub Actions for: + - [x] install + - [x] lint + - [x] tests + - [x] package build +- [x] Keep generated API documentation separate from source documentation. + +## Phase 9: Desktop GUI And Application Packaging + +- [x] Choose GUI framework: PySide6. +- [x] Add a minimal `groupy/gui/` package. +- [x] Add a `.[gui]` optional dependency group for PySide6. +- [x] Add a `Groupy-GUI` entry point. +- [x] Build first GUI screen for: + - [x] SMILES input + - [x] SMILES file import + - [x] 2D structure preview + - [x] property calculation + - [x] group counting + - [x] calculation and group-counting options + - [x] CSV export +- [x] Visually smoke-test the initial GUI runtime. +- [x] Add background worker handling so long calculations do not freeze the UI. +- [x] Add GUI smoke tests where practical. +- [x] Add packaging script for Windows executable builds. +- [x] Test the packaged app can launch and run. +- [x] Test the packaged app on a clean Windows environment. +- [x] Document limitations around OpenBabel and optional conversion features. +- [x] Add a release checklist for ordinary-user distribution. + +Current GUI status: + +- PySide6 is not installed in the current `groupy_dev` environment. +- `python -m groupy.gui --check` and `Groupy-GUI --check` report a clear install hint when PySide6 is missing. +- `groupy.gui` imports without importing PySide6, so core CLI/API usage remains lightweight. +- The user confirmed the first GUI can run normally in a GUI-capable environment. +- The GUI now supports SMILES file import, RDKit 2D structure preview, parameter type selection, hydrocarbon filtering, zero-count group display, and optional SMILES columns. +- The user confirmed the enhanced GUI layout and interaction are satisfactory. +- Property calculation and group counting now run in a `QThread` worker instead of the GUI thread. +- Worker-based GUI behavior has been visually checked in a PySide6 environment. +- `scripts/build_windows_app.py` builds a PyInstaller app folder by default at `dist/Groupy/Groupy.exe`. +- The build script supports `--dry-run` for command inspection without requiring PyInstaller/PySide6. +- The user confirmed `dist/Groupy/Groupy.exe` can run normally. +- The user confirmed packaged-app validation can run normally after the release checklist pass. +- The generated `_internal` folder is currently large, about 753.5 MB, mainly because Intel MKL DLLs are bundled from the build environment. +- `PACKAGING_SIZE_REPORT.md` records the current size baseline: `dist/Groupy` is about 772.41 MB, `_internal` is about 753.50 MB, and MKL DLLs account for about 538.61 MB. +- Rebuilding from a clean conda-forge OpenBLAS packaging environment reduced `dist/Groupy` to about 228.43 MB and `_internal` to about 213.04 MB; MKL DLLs are no longer present. +- The build script now excludes clearly unused optional modules by default and supports `--no-default-excludes` / `--exclude-module` for tuning. +- The build script now removes the previous output folder before PyInstaller runs, so stale files do not remain in `_internal` after a rebuild. +- README now recommends building from a clean conda-forge packaging environment before trying manual file exclusions. +- `_internal` size optimization has removed MKL by using a clean OpenBLAS-based packaging environment. Further reductions are optional and should be measured against `PACKAGING_SIZE_REPORT.md`. +- README documents that OpenBabel conversion, Gaussian input generation, and ASE visualization remain optional workflows outside the default GUI workflow. +- `RELEASE_CHECKLIST.md` captures the pre-release build checks, GUI smoke test, clean Windows validation, and packaged app distribution notes. + +## Current Known Review Findings + +- Further `_internal` size optimization is optional and should focus on ICU, RDKit, Qt/PySide6, and Tcl/Tk only after retesting the current OpenBLAS build. +- The legacy interactive menu is still present for compatibility, but new automation should use `groupy.api`, `Groupy count`, `Groupy calculate`, or `Groupy convert`. +- Advanced GUI pages for OpenBabel conversion, Gaussian input generation, and optional visualization are planned for a later phase. + +## Near-Term Next Step + +Continue with optional Phase 9 packaging optimization: + +1. Preserve the clean OpenBLAS packaging environment as the release build path. +2. If more size reduction is needed, inspect ICU, RDKit, Qt/PySide6, and Tcl/Tk. +3. Optimize remaining large components in small steps, retesting `dist/Groupy/Groupy.exe` after each change. diff --git a/RELEASE_CHECKLIST.md b/RELEASE_CHECKLIST.md new file mode 100644 index 0000000..81aca1f --- /dev/null +++ b/RELEASE_CHECKLIST.md @@ -0,0 +1,97 @@ +# Groupy Release Checklist + +Use this checklist before distributing a Windows build to ordinary users. + +## 1. Build Environment + +- Build from a clean packaging environment, not a broad development environment. +- Prefer conda-forge packages for binary scientific dependencies. +- Install the local project without pulling duplicate dependencies: + +```powershell +conda create -n groupy_package -c conda-forge python=3.11 rdkit pandas numpy openpyxl tqdm joblib pyside6 pyinstaller +conda activate groupy_package +python -m pip install -e . --no-deps +``` + +## 2. Pre-Build Checks + +- Run the full smoke-test suite: + +```powershell +python -W error::ResourceWarning -m unittest discover -s tests +``` + +- Confirm the CLI still works: + +```powershell +Groupy.exe count --smiles C1CCCC1 +Groupy.exe calculate --smiles C1CCCC1 +Groupy-GUI.exe --check +``` + +- Confirm the current release version and user-facing limitations are documented. + +## 3. Build The App + +- Build the default Windows app folder: + +```powershell +python scripts\build_windows_app.py +``` + +- The expected app entry point is: + +```text +dist/Groupy/Groupy.exe +``` + +- Distribute the complete `dist/Groupy` folder. Do not move only `Groupy.exe` away from `_internal`. +- Do not delete DLLs from `_internal` unless the packaged app is retested afterward. +- Check `PACKAGING_SIZE_REPORT.md` when comparing package-size changes across builds. + +## 4. GUI Smoke Test + +Run `dist/Groupy/Groupy.exe` and check: + +- The window opens by double-clicking. +- SMILES file import works for a small `.txt`, `.csv`, or `.xlsx` input. +- The 2D structure preview updates for `C1CCCC1`. +- Property calculation works for `C1CCCC1`. +- Group counting works for `C1CCCC1`. +- Parameter and group-counting options can be changed before running. +- CSV export creates a readable file. +- Invalid SMILES input produces an error row instead of crashing. +- Long-running calculation or counting does not freeze the UI. + +## 5. Optional Workflow Boundaries + +- The default GUI workflow covers SMILES-based property calculation, group counting, and CSV export. +- OpenBabel conversion and Gaussian input generation require OpenBabel from conda-forge. +- ASE visualization requires the viewer extra and is not part of the default GUI workflow. +- If optional workflows are added to a packaged app later, repeat the clean Windows validation. + +## 6. Clean Windows Validation + +Test the final artifact on a Windows machine without the development environment: + +- Unzip or copy the complete `dist/Groupy` folder. +- Launch `dist/Groupy/Groupy.exe` by double-clicking. +- Repeat the GUI smoke test. +- Test from a path with spaces, such as `C:\Users\\Desktop\Groupy Test`. +- Confirm CSV export works in a user-writable folder. +- Confirm the app still launches after a reboot. + +## 7. Release Artifact + +- Package the complete `dist/Groupy` folder as a zip archive. +- Include release notes that mention optional dependency boundaries. +- Record the build date, source commit, Python version, and build environment. +- Build from the clean OpenBLAS packaging environment when preparing release artifacts. +- Treat any further `_internal` size optimization as optional and retest the app after each change. + +## 8. Validation Record + +- 2026-06-25: User confirmed the packaged app validation can run normally. +- 2026-06-25: User confirmed the enhanced GUI with import, options, and 2D structure preview looks satisfactory. +- 2026-06-25: User confirmed the OpenBLAS-based rebuilt app can run normally and is much smaller. diff --git a/build/lib/groupy/__init__.py b/build/lib/groupy/__init__.py deleted file mode 100644 index b233fce..0000000 --- a/build/lib/groupy/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from . import gp_loader -from . import gp_tool -from . import gp_viewer -from . import gp_convertor -from . import gp_calculator -from . import gp_counter -from . import gp_generator diff --git a/build/lib/groupy/gp_3x_calculator.py b/build/lib/groupy/gp_3x_calculator.py deleted file mode 100644 index aed3992..0000000 --- a/build/lib/groupy/gp_3x_calculator.py +++ /dev/null @@ -1,288 +0,0 @@ -from rdkit import Chem -from math import log -import pandas as pd -from tqdm import tqdm -from joblib import Parallel, delayed - -from groupy.gp_loader import Loader -from groupy.gp_counter import Counter - - -class Calculator: - def __init__(self): - self.loader = Loader() - self.counter = Counter() - self.parameters_step_wise = self.loader.load_parameters(parameter_type='step_wise', split=False) - self.parameters_simultaneous = self.loader.load_parameters(parameter_type='simultaneous', split=False) - - def __repr__(self): - return '这是一个使用基团贡献法计算分子性质的计算器' - - @staticmethod - def Tm(group_number, parameters): - right_side_eq = 0.00 - for i in group_number: # 这样写是取了字典的键 - right_side_eq += group_number[i] * parameters[i]['Tm'] - Tm = parameters[1]['Tm0'] * log(max(right_side_eq, 1.0)) # 这里的1.0是为了上计算结果小于0K的都设置为0K - return round(Tm, 3) - - @staticmethod - def Tb(group_number, parameters): - right_side_eq = 0.00 - for i in group_number: - right_side_eq += group_number[i] * parameters[i]['Tb'] - Tb = parameters[1]['Tb0'] * log(max(right_side_eq, 1.0)) # 这里的1.0是为了上计算结果小于0K的都设置为0K - return round(Tb, 3) - - @staticmethod - def Tc(group_number, parameters): - right_side_eq = 0.00 - for i in group_number: - right_side_eq += group_number[i] * parameters[i]['Tc'] - Tc = parameters[1]['Tc0'] * log(max(right_side_eq, 1.0)) # 这里的1.0是为了上计算结果小于0K的都设置为0K - return round(Tc, 3) - - @staticmethod - def Pc(group_number, parameters): - right_side_eq = 0.00 - for i in group_number: - right_side_eq += group_number[i] * parameters[i]['Pc'] - Pc = parameters[1]['Pc1'] + (right_side_eq + parameters[1]['Pc2']) ** -2 - return round(Pc, 4) - - @staticmethod - def Vc(group_number, parameters): - right_side_eq = 0.00 - for i in group_number: - right_side_eq += group_number[i] * parameters[i]['Vc'] - Vc = parameters[1]['Vc0'] + right_side_eq - return round(Vc, 2) - - @staticmethod - def delta_Gf(group_number, parameters): - right_side_eq = 0.00 - for i in group_number: - right_side_eq += group_number[i] * parameters[i]['Gf'] - delta_Gf = parameters[1]['Gf0'] + right_side_eq - return round(delta_Gf, 3) - - @staticmethod - def delta_Hf(group_number, parameters): - right_side_eq = 0.00 - for i in group_number: - right_side_eq += group_number[i] * parameters[i]['Hf'] - delta_Hf = parameters[1]['Hf0'] + right_side_eq - return round(delta_Hf, 3) - - @staticmethod - def delta_Hv(group_number, parameters): - right_side_eq = 0.00 - for i in group_number: - right_side_eq += group_number[i] * parameters[i]['Hv'] - delta_Hv = parameters[1]['Hv0'] + right_side_eq - return round(delta_Hv, 3) - - @staticmethod - def delta_Hfus(group_number, parameters): - right_side_eq = 0.00 - for i in group_number: - right_side_eq += group_number[i] * parameters[i]['Hfus'] - delta_Hfus = parameters[1]['Hfus0'] + right_side_eq - return round(delta_Hfus, 3) - - @staticmethod - def flash_point(group_number, parameters): - right_side_eq = 0.00 - for i in group_number: - right_side_eq += group_number[i] * parameters[i]['Fp'] - Fp = parameters[1]['Fp0'] + right_side_eq - return round(Fp, 3) - - @staticmethod - def molar_volume(group_number, parameters): - right_side_eq = 0.00 - for i in group_number: - right_side_eq += group_number[i] * parameters[i]['Vm'] - Vm = parameters[1]['Vm0'] + right_side_eq - return round(Vm, 3) - - @staticmethod - def density(molar_mass, Vs): - return round(molar_mass / (1000 * Vs), 3) - - @staticmethod - def delta_Hc(C_number, H_number, delta_Hf): - delta_Hc = -(-395.51 * C_number - 241.83 * H_number / 2 - delta_Hf) - return round(delta_Hc, 3) - - @staticmethod - def q(delta_Hc, molar_mass): - return round(delta_Hc / molar_mass, 3) - - @staticmethod - def isp(C_number, H_number, q): - H_C_ratio = H_number / C_number - parameter = q * (11.91 + H_C_ratio) / (43.66 + 8.936 * H_C_ratio) - if parameter < 0: # todo这是无意中发现的错误,可能是因为算的不是碳氢分子(确实目前发现的出现错误的是含F原子的分子) - parameter = 0 - isp = (2 * 0.556 * parameter) ** 0.5 / 9.8 * 1000 - return round(isp, 3) - - @staticmethod - def C_number(mol): - C_atoms = [i for i in mol.GetAtoms() if i.GetAtomicNum() == 6] - return len(C_atoms) - - @staticmethod - def H_number(mol): - H_atoms = [i for i in Chem.AddHs(mol).GetAtoms() if i.GetAtomicNum() == 1] - return len(H_atoms) - - @staticmethod - def smiles(mol): - return Chem.MolToSmiles(mol) - - @staticmethod - def molar_mass(mol): - molar_mass = 0.000 - atoms = Chem.AddHs(mol).GetAtoms() - for i in atoms: - molar_mass += i.GetMass() - return molar_mass - - def calculate_a_mol(self, mol, parameter_type='step_wise', debug=False): - init_smi = mol - try: - if isinstance(mol, str): - mol = Chem.MolFromSmiles(mol) - group_number = self.counter.count_a_mol(mol, clear_mode=True, add_note=True) - if group_number.get('note', ''): - counter_note = group_number['note'] - del group_number['note'] - else: - counter_note = '' - - if parameter_type == 'step_wise': - parameters = self.parameters_step_wise # todo 这里是速度慢的原因,每算一个分子都要加载一遍参数,要修改 - elif parameter_type == 'simultaneous': - parameters = self.parameters_simultaneous - else: - raise NotImplemented('不可用的参数类型,只能使用step_wise或simultaneous') - - if debug: - print(group_number) - # print(parameters) - Tm = self.Tm(group_number=group_number, parameters=parameters) - Tb = self.Tb(group_number=group_number, parameters=parameters) - Tc = self.Tc(group_number=group_number, parameters=parameters) - Pc = self.Pc(group_number=group_number, parameters=parameters) - Vc = self.Vc(group_number=group_number, parameters=parameters) - delta_Gf = self.delta_Gf(group_number=group_number, parameters=parameters) - delta_Hf = self.delta_Hf(group_number=group_number, parameters=parameters) - delta_Hv = self.delta_Hv(group_number=group_number, parameters=parameters) - delta_Hfus = self.delta_Hfus(group_number=group_number, parameters=parameters) - C_number = self.C_number(mol) - H_number = self.H_number(mol) - molar_mass = self.molar_mass(mol) - flash_point = self.flash_point(group_number=group_number, parameters=parameters) - molar_volume = self.molar_volume(group_number=group_number, parameters=parameters) - density = self.density(molar_mass=molar_mass, Vs=molar_volume) - delta_Hc = self.delta_Hc(C_number=C_number, H_number=H_number, delta_Hf=delta_Hf) - q = self.q(delta_Hc=delta_Hc, molar_mass=molar_mass) - isp = self.isp(C_number=C_number, H_number=H_number, q=q) - smiles = self.smiles(mol) - return {'smiles': smiles, - 'molar_mass': molar_mass, - 'flash_point/K': flash_point, - 'Tm/K': Tm, 'Tb/K': Tb, 'Tc/K': Tc, - 'Pc/bar': Pc, 'Vc/(cm3/mol)': Vc, - 'density/(g/cm3)': density, - 'delta_G/(KJ/mol)': delta_Gf, - 'delta_Hf/(KJ/mol)': delta_Hf, - 'delta_Hvap/(KJ/mol)': delta_Hv, - 'delta_Hfus/(KJ/mol)': delta_Hfus, - 'molar_volume/(cm3/mol)(default298K)': molar_volume, - 'delta_Hc/(KJ/mol)': delta_Hc, - 'mass_calorific_value_h/(MJ/kg)': q, - 'ISP': isp, - 'note': counter_note + ' at 298K'} - except: - print(f'Error! There is something wrong when calculating {init_smi}, please check it.') - return {'smiles': init_smi, - 'molar_mass': '?', - 'flash_point/K': '?', - 'Tm/K': '?', 'Tb/K': '?', 'Tc/K': '?', - 'Pc/bar': '?', 'Vc/(cm3/mol)': '?', - 'density/(g/cm3)': '?', - 'delta_G/(KJ/mol)': '?', - 'delta_Hf/(KJ/mol)': '?', - 'delta_Hvap/(KJ/mol)': '?', - 'delta_Hfus/(KJ/mol)': '?', - 'molar_volume/(cm3/mol)(default298K)': '?', - 'delta_Hc/(KJ/mol)': '?', - 'mass_calorific_value_h/(MJ/kg)': '?', - 'ISP': '?', - 'note': 'There must be something wrong with this SMILES'} - - def calculate_mols(self, smiles_file_path, properties_file_path='gp_3x_result.csv', parameter_type='simultaneous'): # todo 还没实现不同步拟合的参数的使用 - print('reading input file...') - if smiles_file_path.endswith('.txt'): - smiles_iterator = list(open(smiles_file_path)) - elif smiles_file_path.endswith('.xlsx'): - smiles_iterator = pd.read_excel(smiles_file_path)['smiles'] - elif smiles_file_path.endswith('.csv'): - smiles_iterator = pd.read_csv(smiles_file_path)['smiles'] - else: - print('无法识别的文件类型,请以.txt/.xlsx/.csv类型的文件作为输入。') - return None - mol_number = len(smiles_iterator) - print('reading completed,A total of {} molecules detected, start calculating properties...'.format(mol_number)) - print('start calculating...') - properties_dict_list = [] - error_smi = [] - for i in tqdm(smiles_iterator): - try: - properties_dict_list.append(self.calculate_a_mol(i, parameter_type=parameter_type)) - except: - error_smi.append(i) - print('calculation completed!') - print('start to export result to {} ...'.format(properties_file_path)) - result = pd.DataFrame(properties_dict_list) - result.to_csv(properties_file_path, index_label='index') - with open('error.txt', 'w') as f: - for i in error_smi: - f.write(i + '\n') - print('Done!') - return result - - def calculate_mols_mpi(self, smiles_file_path, properties_file_path='gp_3x_result_mpi.csv', parameter_type='simultaneous', n_jobs=1, batch_size='auto'): - print('reading input file...') - if smiles_file_path.endswith('.txt'): - smiles_iterator = list(open(smiles_file_path)) - elif smiles_file_path.endswith('.xlsx'): - smiles_iterator = pd.read_excel(smiles_file_path)['smiles'] - elif smiles_file_path.endswith('.csv'): - smiles_iterator = pd.read_csv(smiles_file_path)['smiles'] - else: - print('无法识别的文件类型,请以.txt/.xlsx/.csv类型的文件作为输入。') - return None - mol_number = len(smiles_iterator) - print('reading completed,A total of {} molecules detected, start calculating properties...'.format(mol_number)) - print('start calculating...') - task = [delayed(self.calculate_a_mol)(i, parameter_type=parameter_type) for i in smiles_iterator] - properties_dict_list = Parallel(n_jobs=n_jobs, batch_size=batch_size)(task) - print('calculation completed!') - print('start to export result to {} ...'.format(properties_file_path)) - result = pd.DataFrame(properties_dict_list) - result.to_csv(properties_file_path, index_label='index') - print('Done!') - return result - - -# if __name__ == '__main__': -# import time -# t1 = time.time() -# c = Calculator() -# c.calculate_mols_mpi(smiles_file_path='gp_3x_test_mol/SMILES.txt', n_jobs=4) -# t2 = time.time() -# print(t2 - t1) \ No newline at end of file diff --git a/build/lib/groupy/gp_3x_convertor.py b/build/lib/groupy/gp_3x_convertor.py deleted file mode 100644 index 4d2960e..0000000 --- a/build/lib/groupy/gp_3x_convertor.py +++ /dev/null @@ -1,345 +0,0 @@ -import os -from rdkit import Chem -from rdkit.Chem import AllChem -from openbabel import pybel -import pandas as pd -from tqdm import tqdm -from joblib import Parallel, delayed - - -class Convertor: - def __init__(self): - pass - - def __repr__(self): - return ('This is a object implemented some functions that can convert SMILES to 3D chemical files,' - ' such as xyz, gro... or convert 3D chemical files to SMILES') - - @staticmethod - def load_smiles_iterator(smiles_file_path): - print('reading input file...') - if smiles_file_path.endswith('.txt'): - smiles_iterator = list(open(smiles_file_path)) - elif smiles_file_path.endswith('.xlsx'): - smiles_iterator = pd.read_excel(smiles_file_path)['smiles'] - elif smiles_file_path.endswith('.csv'): - smiles_iterator = pd.read_csv(smiles_file_path)['smiles'] - else: - raise NotImplemented('无法识别的文件类型,请以.txt/.xlsx/.csv类型的文件作为输入。') - smiles_iterator = [i.strip() for i in smiles_iterator] - return smiles_iterator - - @staticmethod - def smi_to_xyz(smi, xyz_path=None): - mol = Chem.MolFromSmiles(smi) - if mol is None: - print(f'can not read {smi}, please check your SMILES') - return False - - mol_with_h = Chem.AddHs(mol) - AllChem.EmbedMolecule(mol_with_h, useRandomCoords=True) - atom_number = len(mol_with_h.GetAtoms()) - try: - AllChem.MMFFOptimizeMolecule(mol_with_h) - opt = Chem.MolToMolBlock(mol_with_h) - except ValueError: - mol = pybel.readstring("smi", smi) - mol.addh() - if mol.make3D() is None: - opt = mol.write("mol") - else: - print(f'Error! There is something wrong when converting {smi} to xyz file, please check it.') - return False - - # Windows 下pybel有问题 - # try: - # mol = pybel.readstring("smi", smi) - # mol.addh() - # if mol.make3D() is None: - # opt = mol.write("mol") - # else: - # return False - # except: - # AllChem.MMFFOptimizeMolecule(mol_with_h) - # opt = Chem.MolToMolBlock(mol_with_h) - - if xyz_path is None: - xyz_path = smi + '.xyz' - with open(xyz_path, 'w') as file: - file.write('{}\n'.format(atom_number)) - file.write(smi + '\n') - for index, i in enumerate(opt.splitlines()[4:]): - if len(i.split()) >= 4: - if i.split()[3].isupper(): - file.write(i.split()[3] + ' ') - file.write(i.split()[0] + ' ') - file.write(i.split()[1] + ' ') - file.write(i.split()[2] + '\n') - return True - - def batch_smi_to_xyz(self, smiles_file_path, xyz_root_path): - smiles_iterator = self.load_smiles_iterator(smiles_file_path=smiles_file_path) - mol_number = len(smiles_iterator) - zfill_number = len(str(mol_number)) + 3 - print('reading completed,A total of {} molecules detected, start making xyz files...'.format(mol_number)) - # make xyz_root_path - if os.path.exists(xyz_root_path): - print('xyz_root_path "{}" has been detected!'.format(xyz_root_path)) - else: - print('xyz_root_path "{}" has not been detected, I will create it for you'.format(xyz_root_path)) - os.makedirs(xyz_root_path) - # end - - succeed = [] - fail = [] - for (index, smi) in tqdm(enumerate(smiles_iterator)): - smi = smi.strip() - out_name = os.path.join(xyz_root_path, '{}.xyz'.format(str(index).zfill(zfill_number))) - generate_success_flag = self.smi_to_xyz(smi=smi, xyz_path=out_name) - - if not generate_success_flag: - fail.append(smi) - else: - succeed.append(smi) - - with open('xyz_fail.txt', 'w') as f: - for i in fail: - f.write(i + '\n') - with open('xyz_succeed.txt', 'w') as f: - for i in succeed: - f.write(i + '\n') - - if len(fail) == 0: - print('done! all .xyz files has been saved in {}'.format(xyz_root_path)) - else: - print('Warning! The following SMILES fail to generate .xyz, please check...sorry(OTZ)') - print(fail) - return None - - def batch_smi_to_xyz_mpi(self, smiles_file_path, xyz_root_path, n_jobs=1, batch_size='auto'): - smiles_iterator = self.load_smiles_iterator(smiles_file_path=smiles_file_path) - mol_number = len(smiles_iterator) - zfill_number = len(str(mol_number)) + 3 - print('reading completed,A total of {} molecules detected, start making xyz files...'.format(mol_number)) - # make xyz_root_path - if os.path.exists(xyz_root_path): - print('xyz_root_path "{}" has been detected!'.format(xyz_root_path)) - else: - print('xyz_root_path "{}" has not been detected, I will create it for you'.format(xyz_root_path)) - os.makedirs(xyz_root_path) - # end - - task = [delayed(self.smi_to_xyz)(smi=smi, xyz_path=os.path.join(xyz_root_path, '{}.xyz'.format(str(index).zfill(zfill_number)))) for (index, smi) in enumerate(smiles_iterator)] - result = Parallel(n_jobs=n_jobs, batch_size=batch_size)(task) - print('done! all .xyz files has been saved in {}'.format(xyz_root_path)) - return result - - @staticmethod - def convert_file_type(in_format, in_path, out_format, out_path=None): - try: - mol = pybel.readfile(in_format, in_path).__next__() - # print('The SMILES of this system is :') - # print(mol.write('smi')) - - if out_path is None: - out_path = in_path.split('.') - out_path = out_path[0] + '.' + out_format - mol.write(out_format, out_path, overwrite=True) - return None - except: - print(f'Error! There is something wrong when converting {in_path}, please check it.') - return None - - def batch_convert_file_type(self, in_format, in_root_path, out_format, out_root_path=None): - if out_root_path is None: - out_root_path = in_root_path - else: - # make out_root_path - if os.path.exists(out_root_path): - print('out_root_path "{}" has been detected!'.format(out_root_path)) - else: - print('out_root_path "{}" has not been detected, I will create it for you'.format(out_root_path)) - os.makedirs(out_root_path) - # end - in_file_names = os.listdir(in_root_path) - - in_file_names = [i for i in in_file_names if i.endswith(in_format)] - out_file_names = [i.split('.')[0] + '.{}'.format(out_format) for i in in_file_names] - - in_file_path = [os.path.join(in_root_path, i) for i in in_file_names] - out_file_path = [os.path.join(out_root_path, i) for i in out_file_names] - - error_in_file_path = [] - for index in tqdm(range(len(in_file_path))): - try: - self.convert_file_type(in_format=in_format, in_path=in_file_path[index], - out_format=out_format, out_path=out_file_path[index]) - except: - # print('Warning!!!') - error_in_file_path.append(in_file_path[index]) - # print('There may something wrong in {}, please check it carefully!'.format(in_file_path[index])) - - # When there is something wrong, print some warning - if len(error_in_file_path) > 0: - print('Warning!Warning!Warning!') - for i in error_in_file_path: - print('There may something wrong in {}, please check it carefully!'.format(i)) - return None - - def batch_convert_file_type_mpi(self, in_format, in_root_path, out_format, out_root_path=None, n_jobs=1, batch_size='auto'): - if out_root_path is None: - out_root_path = in_root_path - else: - # make out_root_path - if os.path.exists(out_root_path): - print('out_root_path "{}" has been detected!'.format(out_root_path)) - else: - print('out_root_path "{}" has not been detected, I will create it for you'.format(out_root_path)) - os.makedirs(out_root_path) - # end - in_file_names = os.listdir(in_root_path) - - in_file_names = [i for i in in_file_names if i.endswith(in_format)] - out_file_names = [i.split('.')[0] + '.{}'.format(out_format) for i in in_file_names] - - in_file_path = [os.path.join(in_root_path, i) for i in in_file_names] - out_file_path = [os.path.join(out_root_path, i) for i in out_file_names] - - task = [delayed(self.convert_file_type)(in_format=in_format, in_path=in_file_path[index],out_format=out_format, out_path=out_file_path[index]) for index in range(len(in_file_path))] - result = Parallel(n_jobs=n_jobs, batch_size=batch_size)(task) - return result - - @staticmethod - def file_to_smi(file_path, format=None): - """ - {'abinit': 'ABINIT Output Format', 'acesout': 'ACES output format', 'acr': 'ACR format', - 'adfband': 'ADF Band output format', 'adfdftb': 'ADF DFTB output format', 'adfout': 'ADF output format', - 'alc': 'Alchemy format', 'aoforce': 'Turbomole AOFORCE output format', - 'arc': 'Accelrys/MSI Biosym/Insight II CAR format', 'axsf': 'XCrySDen Structure Format', - 'bgf': 'MSI BGF format', 'box': 'Dock 3.5 Box format', 'bs': 'Ball and Stick format', - 'c09out': 'Crystal 09 output format', 'c3d1': 'Chem3D Cartesian 1 format', 'c3d2': 'Chem3D Cartesian 2 format', - 'caccrt': 'Cacao Cartesian format', 'can': 'Canonical SMILES format', - 'car': 'Accelrys/MSI Biosym/Insight II CAR format', 'castep': 'CASTEP format', 'ccc': 'CCC format', - 'cdjson': 'ChemDoodle JSON', 'cdx': 'ChemDraw binary format', 'cdxml': 'ChemDraw CDXML format', - 'cif': 'Crystallographic Information File', 'ck': 'ChemKin format', 'cml': 'Chemical Markup Language', - 'cmlr': 'CML Reaction format', 'cof': 'Culgi object file format', 'CONFIG': 'DL-POLY CONFIG', - 'CONTCAR': 'VASP format', 'CONTFF': 'MDFF format', 'crk2d': 'Chemical Resource Kit diagram(2D)', - 'crk3d': 'Chemical Resource Kit 3D format', 'ct': 'ChemDraw Connection Table format', - 'cub': 'Gaussian cube format', 'cube': 'Gaussian cube format', 'dallog': 'DALTON output format', - 'dalmol': 'DALTON input format', 'dat': 'Generic Output file format', 'dmol': 'DMol3 coordinates format', - 'dx': 'OpenDX cube format for APBS', 'ent': 'Protein Data Bank format', - 'exyz': 'Extended XYZ cartesian coordinates format', 'fa': 'FASTA format', 'fasta': 'FASTA format', - 'fch': 'Gaussian formatted checkpoint file format', 'fchk': 'Gaussian formatted checkpoint file format', - 'fck': 'Gaussian formatted checkpoint file format', 'feat': 'Feature format', 'fhiaims': 'FHIaims XYZ format', - 'fract': 'Free Form Fractional format', 'fs': 'Fastsearch format', 'fsa': 'FASTA format', - 'g03': 'Gaussian Output', 'g09': 'Gaussian Output', 'g16': 'Gaussian Output', - 'g92': 'Gaussian Output', 'g94': 'Gaussian Output', 'g98': 'Gaussian Output', 'gal': 'Gaussian Output', - 'gam': 'GAMESS Output', 'gamess': 'GAMESS Output', 'gamin': 'GAMESS Input', 'gamout': 'GAMESS Output', - 'got': 'GULP format', 'gpr': 'Ghemical format', 'gro': 'GRO format', 'gukin': 'GAMESS-UK Input', - 'gukout': 'GAMESS-UK Output', 'gzmat': 'Gaussian Z-Matrix Input', 'hin': 'HyperChem HIN format', - 'HISTORY': 'DL-POLY HISTORY', 'inchi': 'InChI format', 'inp': 'GAMESS Input', 'ins': 'ShelX format', - 'jin': 'Jaguar input format', 'jout': 'Jaguar output format', 'log': 'Generic Output file format', - 'lpmd': 'LPMD format', 'mcdl': 'MCDL format', 'mcif': 'Macromolecular Crystallographic Info', - 'MDFF': 'MDFF format', 'mdl': 'MDL MOL format', 'ml2': 'Sybyl Mol2 format', - 'mmcif': 'Macromolecular Crystallographic Info', 'mmd': 'MacroModel format', 'mmod': 'MacroModel format', - 'mol': 'MDL MOL format', 'mol2': 'Sybyl Mol2 format', 'mold': 'Molden format', 'molden': 'Molden format', - 'molf': 'Molden format', 'moo': 'MOPAC Output format', 'mop': 'MOPAC Cartesian format', - 'mopcrt': 'MOPAC Cartesian format', 'mopin': 'MOPAC Internal', 'mopout': 'MOPAC Output format', - 'mpc': 'MOPAC Cartesian format', 'mpo': 'Molpro output format', 'mpqc': 'MPQC output format', - 'mrv': 'Chemical Markup Language', 'msi': 'Accelrys/MSI Cerius II MSI format', 'nwo': 'NWChem output format', - 'orca': 'ORCA output format', 'out': 'Generic Output file format', 'outmol': 'DMol3 coordinates format', - 'output': 'Generic Output file format', 'pc': 'PubChem format', 'pcjson': 'PubChem JSON', - 'pcm': 'PCModel Format', 'pdb': 'Protein Data Bank format', 'pdbqt': 'AutoDock PDBQT format', - 'png': 'PNG 2D depiction', 'pos': 'POS cartesian coordinates format', 'POSCAR': 'VASP format', - 'POSFF': 'MDFF format', 'pqr': 'PQR format', 'pqs': 'Parallel Quantum Solutions format', - 'prep': 'Amber Prep format', 'pwscf': 'PWscf format', 'qcout': 'Q-Chem output format', 'res': 'ShelX format', - 'rsmi': 'Reaction SMILES format', 'rxn': 'MDL RXN format', 'sd': 'MDL MOL format', 'sdf': 'MDL MOL format', - 'siesta': 'SIESTA format', 'smi': 'SMILES format', 'smiles': 'SMILES format', - 'smy': 'SMILES format using Smiley parser', 'sy2': 'Sybyl Mol2 format', 't41': 'ADF TAPE41 format', - 'tdd': 'Thermo format', 'text': 'Read and write raw text', 'therm': 'Thermo format', - 'tmol': 'TurboMole Coordinate format', 'txt': 'Title format', 'txyz': 'Tinker XYZ format', - 'unixyz': 'UniChem XYZ format', 'VASP': 'VASP format', 'vmol': 'ViewMol format', - 'wln': 'Wiswesser Line Notation', 'xml': 'General XML format', 'xsf': 'XCrySDen Structure Format', - 'xyz': 'XYZ cartesian coordinates format', 'yob': 'YASARA.org YOB format'} - """ - try: - atoms = next(pybel.readfile(format=format, filename=file_path)) - smi = atoms.write(format='smi').split('\t')[0] - # print(smi) - return smi - except: - print('There may something wrong in {}, please check it carefully!'.format(file_path)) - return 'There may something wrong in {}, please check it carefully!'.format(file_path) - - def batch_file_to_smi(self, in_format, in_root_path, out_root_path=None): - if out_root_path is None: - out_root_path = in_root_path - else: - # make out_root_path - if os.path.exists(out_root_path): - print('out_root_path "{}" has been detected!'.format(out_root_path)) - else: - print('out_root_path "{}" has not been detected, I will create it for you'.format(out_root_path)) - os.makedirs(out_root_path) - # end - in_file_names = os.listdir(in_root_path) - in_file_names = [i for i in in_file_names if i.endswith(in_format)] - in_file_path = [os.path.join(in_root_path, i) for i in in_file_names] - error_in_file_path = [] - smi_list = [] - for index in tqdm(range(len(in_file_path))): - try: - smi_list.append(self.file_to_smi(format=in_format, file_path=in_file_path[index])) - except: - # print('Warning!!!') - error_in_file_path.append(in_file_path[index]) - # print('There may something wrong in {}, please check it carefully!'.format(in_file_path[index])) - - # When there is something wrong, print some warning - if len(error_in_file_path) > 0: - print('Warning!Warning!Warning!') - for i in error_in_file_path: - print('There may something wrong in {}, please check it carefully!'.format(i)) - - with open(os.path.join(out_root_path, 'SMILES.txt'), 'w') as f: - for i in smi_list: - f.write(i + '\n') - return smi_list - - def batch_file_to_smi_mpi(self, in_format, in_root_path, out_root_path=None, n_jobs=1, batch_size='auto'): - if out_root_path is None: - out_root_path = in_root_path - else: - # make out_root_path - if os.path.exists(out_root_path): - print('out_root_path "{}" has been detected!'.format(out_root_path)) - else: - print('out_root_path "{}" has not been detected, I will create it for you'.format(out_root_path)) - os.makedirs(out_root_path) - # end - in_file_names = os.listdir(in_root_path) - in_file_names = [i for i in in_file_names if i.endswith(in_format)] - in_file_path = [os.path.join(in_root_path, i) for i in in_file_names] - - task = [delayed(self.file_to_smi)(format=in_format, file_path=in_file_path[index]) for index in range(len(in_file_path))] - smi_list = Parallel(n_jobs=n_jobs, batch_size=batch_size)(task) - - with open(os.path.join(out_root_path, 'SMILES.txt'), 'w') as f: - for i in smi_list: - f.write(i + '\n') - return smi_list - - -# if __name__ == '__main__': -# import time -# -# t1 = time.time() -# c = Convertor() -# # c.smi_to_xyz('C1CCCC1C', 'C1CCCC1C.xyz') -# # c.convert_file_type(in_format='xyz', in_path='C1CCCC1C.xyz', out_format='mol', out_path='C1CCCC1C.mol') -# # c.file_to_smi('C1CCCC1C.mol', format='mol') -# -# # c.batch_file_to_smi(in_format='mol2', in_root_path=os.path.join('gp_3x_test_mol', 'test_mol')) -# x = c.batch_file_to_smi_mpi(in_format='mol', in_root_path='./test', n_jobs=4, batch_size='auto') -# t2 = time.time() -# print(t2 - t1) \ No newline at end of file diff --git a/build/lib/groupy/gp_3x_counter.py b/build/lib/groupy/gp_3x_counter.py deleted file mode 100644 index 2d1e61c..0000000 --- a/build/lib/groupy/gp_3x_counter.py +++ /dev/null @@ -1,3380 +0,0 @@ -from tqdm import tqdm -from rdkit import Chem -import pandas as pd -from joblib import Parallel, delayed - -from groupy.gp_loader import Loader - - -# tool -def has_non_aromatic_neighbor(atom): - """判断原子周围是否有非芳香原子""" - flag = False - neighbors = atom.GetNeighbors() - for i in neighbors: - if not i.GetIsAromatic(): - flag = True - break - return flag - - -def find_ring_atoms(mol, atom_idxs): - """找出给定的原子索引元组中在环上的原子索引,并返回一个元组""" - return list((i for i in atom_idxs if mol.GetAtomWithIdx(i).IsInRing())) - - -def is_in_same_ring(mol, atom_idxs: tuple): - """判断一个元组中的原子索引是否在同一个环上""" - flag = False - all_rings = [list(i) for i in Chem.GetSymmSSSR(mol)] - # set(i).issubset(j) - for i in all_rings: - if set(atom_idxs).issubset(i): - flag = True - break - return flag - - -# f order -def f_001(mol): - """CH3""" - query = Chem.MolFromSmarts('[C;H3;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_002(mol): - """CH2""" - query = Chem.MolFromSmarts('[C;H2;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_003(mol): - """CH""" - query = Chem.MolFromSmarts('[C;H1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_004(mol): - """C""" - query = Chem.MolFromSmarts('[C;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_005(mol): - """CH2=CH""" - query = Chem.MolFromSmarts('[C;H2;!R]=[C;H1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_006(mol): - """CH=CH""" - query = Chem.MolFromSmarts('[C;H;!R]=[C;H1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_007(mol): - """CH2=C""" - query = Chem.MolFromSmarts('[C;H2;!R]=[C;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_008(mol): - """CH=C""" - query = Chem.MolFromSmarts('[C;H1;!R]=[C;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_009(mol): - """C=C""" - query = Chem.MolFromSmarts('[C;H0;!R]=[C;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_010(mol): - """CH2=C=CH""" - query = Chem.MolFromSmarts('[C;H2;!R]=[C;H0;!R;D2]=[C;H1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_011(mol): - """CH2=C=C""" - query = Chem.MolFromSmarts('[C;H2;!R]=[C;H0;!R;D2]=[C;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_012(mol): - """C=C=C - 2001年的论文12号基团是CH=C=CH,对应的SMART为[C;H1;!R;D2]=[C;H0;!R;D2]=[C;H1;!R;D2] - """ - query = Chem.MolFromSmarts('[C;H0;!R]=[C;H0;!R;D2]=[C;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_013(mol): - """CH#C""" - query = Chem.MolFromSmarts('[C;H1;!R;D1]#[C;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_014(mol): - """C#C""" - query = Chem.MolFromSmarts('[C;H0;!R;D2]#[C;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_015(mol): - """aCH""" - query = Chem.MolFromSmarts('[c;H1;R1;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_016(mol): # 麻烦且慢,但是应该可靠 - """aC fused with aromatic ring""" - query = Chem.MolFromSmarts('[c;H0;R&!R1;D3]') - match_list = mol.GetSubstructMatches(query) - real_match_list = [] - for i in match_list: - atom_i = mol.GetAtomWithIdx(i[0]) - if not has_non_aromatic_neighbor(atom_i): # 判断该原子是否有链接非芳香原子是为了防止把芳环上连接非芳香环也算进去 - real_match_list.append(i) - return len(real_match_list), tuple(real_match_list) - - -def f_017(mol): # 麻烦且慢,但是应该可靠 - """aC fused with nonaromatic subring""" - query = Chem.MolFromSmarts('[c;H0;R&!R1;D3]') - match_list = mol.GetSubstructMatches(query) - real_match_list = [] - for i in match_list: - atom_i = mol.GetAtomWithIdx(i[0]) - if has_non_aromatic_neighbor(atom_i): # 判断该原子是否有链接非芳香原子是为了取出芳环上连接非芳香环的,和上一个函数刚好相反 - real_match_list.append(i) - return len(real_match_list), tuple(real_match_list) - - -def f_018(mol): - """aC except as above - 这里这样写是因为我们给出了数基团的顺序,在那里会剔除重复的,所以不用担心重复 - """ - query = Chem.MolFromSmarts('[c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_019(mol): - """aN in aromatic ring""" - query = Chem.MolFromSmarts('[n;H0;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_020(mol): - """aC-CH3""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_021(mol): - """aC-CH2""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H2;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_022(mol): - """aC-CH""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_023(mol): - """aC-C""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_024(mol): - """aC-CH=CH2""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H1;!R;D2]=[C;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_025(mol): - """aC-CH=CH""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H1;!R;D2]=[C;H1;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_026(mol): - """aC-C=CH2""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H0;!R;D3]=[C;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_027(mol): - """aC-C#CH""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H0;!R;D2]#[C;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_028(mol): - """aC-C#C""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H0;!R;D2]#[C;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_029(mol): - """OH""" - query = Chem.MolFromSmarts('[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_030(mol): - """aC-OH""" - query = Chem.MolFromSmarts('[c;H0;R;D3][O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_031(mol): - """COOH""" - query = Chem.MolFromSmarts('[C;H0;!R;D3]([O;H1;!R;D1])=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_032(mol): - """aC-COOH""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H0;!R;D3]([O;H1;!R;D1])=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_033(mol): - """CH3CO""" - query = Chem.MolFromSmarts('[C;H3;!R;D1][C;H0;!R;D3](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_034(mol): - """CH2CO""" - query = Chem.MolFromSmarts('[C;H2;!R;D2][C;H0;!R;D3](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_035(mol): - """CHCO""" - query = Chem.MolFromSmarts('[C;H1;!R][C;H0;!R;D3](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_036(mol): - """CCO""" - query = Chem.MolFromSmarts('[C;H0;!R][C;H0;!R;D3](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_037(mol): - """aC-CO""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H0;!R;D3](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_038(mol): - """CHO""" - query = Chem.MolFromSmarts('[C;H1;!R;D2](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_039(mol): - """aC-CHO""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H1;!R;D2](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_040(mol): - """CH3COO""" - query = Chem.MolFromSmarts('[C;H3;!R;D1][C;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_041(mol): - """CH2COO""" - query = Chem.MolFromSmarts('[C;H2;!R;D2][C;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_042(mol): - """CHCOO""" - query = Chem.MolFromSmarts('[C;H1;!R;D3][C;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_043(mol): - """CCOO""" - query = Chem.MolFromSmarts('[C;H0;!R;D4][C;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_044(mol): - """HCOO""" - query = Chem.MolFromSmarts('[C;H1;!R;D2](=[O;H0;!R;D1])[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_045(mol): - """aC-COO""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_046(mol): - """aC-OOCH""" - query = Chem.MolFromSmarts('[C;H1;!R;D2](=[O;H0;!R;D1])[O;H0;!R;D2][c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_047(mol): - """aC-OOC""" - query = Chem.MolFromSmarts('[C;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2][c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_048(mol): - """COO except as above - 取消了是不是在环上的限制 - """ - query = Chem.MolFromSmarts('[C;H0;D3](=[O;H0;D1])[O;H0;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_049(mol): - """CH3O""" - query = Chem.MolFromSmarts('[C;H3;!R;D1]-[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_050(mol): - """CH2O""" - query = Chem.MolFromSmarts('[C;H2;!R;D2]-[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_051(mol): - """CH-O""" - query = Chem.MolFromSmarts('[C;H1;!R]-[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_052(mol): - """C-O""" - query = Chem.MolFromSmarts('[C;H0;!R]-[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_053(mol): - """aC-O""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_054(mol): - """CH2NH2""" - query = Chem.MolFromSmarts('[C;H2;!R;D2]-[N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_055(mol): - """CHNH2""" - query = Chem.MolFromSmarts('[C;H1;!R;D3]-[N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_056(mol): - """CNH2""" - query = Chem.MolFromSmarts('[C;H0;!R;D4]-[N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_057(mol): - """CH3NH""" - query = Chem.MolFromSmarts('[C;H3;!R;D1]-[N;H1;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_058(mol): - """CH2NH""" - query = Chem.MolFromSmarts('[C;H2;!R;D2]-[N;H1;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_059(mol): - """CHNH""" - query = Chem.MolFromSmarts('[C;H1;!R;D3]-[N;H1;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_060(mol): - """CH3N - 因为有f66(CH=N)、f67(C=N),所以这里还是要求N的度为3 - """ - query = Chem.MolFromSmarts('[C;H3;!R;D1]-[N;H0;!R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_061(mol): - """CH2N - 因为有f66(CH=N)、f67(C=N),所以这里还是要求N的度为3 - """ - query = Chem.MolFromSmarts('[C;H2;!R;D2]-[N;H0;!R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_062(mol): - """aC-NH2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_063(mol): - """aC-NH""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H1;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_064(mol): - """aC-N""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H0;!R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_065(mol): - """NH2 except as above""" - query = Chem.MolFromSmarts('[N;H2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_066(mol): - """CH=N""" - query = Chem.MolFromSmarts('[C;H1;!R;D2]=[N;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_067(mol): - """C=N""" - query = Chem.MolFromSmarts('[C;H0;!R]=[N;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_068(mol): - """CH2CN""" - query = Chem.MolFromSmarts('[C;H2;!R;D2][C;H0;!R;D2]#[N;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_069(mol): - """CHCN""" - query = Chem.MolFromSmarts('[C;H1;!R;D3][C;H0;!R;D2]#[N;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_070(mol): - """CCN""" - query = Chem.MolFromSmarts('[C;H0;!R;D4][C;H0;!R;D2]#[N;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_071(mol): - """aC-CN""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H0;!R;D2]#[N;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_072(mol): - """CN except as above - 不可能在环上 - """ - query = Chem.MolFromSmarts('[C;H0;!R;D2]#[N;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_073(mol): - """CH2NCO""" - query = Chem.MolFromSmarts('[C;H2;!R;D2]-[N;H0;!R;D2]=[C;H0;!R;D2]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_074(mol): - """CHNCO""" - query = Chem.MolFromSmarts('[C;H1;!R]-[N;H0;!R;D2]=[C;H0;!R;D2]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_075(mol): - """CNCO""" - query = Chem.MolFromSmarts('[C;H0;!R]-[N;H0;!R;D2]=[C;H0;!R;D2]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_076(mol): - """aC-NCO""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H0;!R;D2]=[C;H0;!R;D2]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_077(mol): - """CH2NO2""" - query = Chem.MolFromSmarts('[C;H2;!R;D2]-[N;+]([O;-])=[O]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_078(mol): - """CHNO2""" - query = Chem.MolFromSmarts('[C;H1;!R;D3]-[N;+]([O;-])=[O]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_079(mol): - """CNO2""" - query = Chem.MolFromSmarts('[C;H0;!R;D4]-[N;+]([O;-])=[O]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_080(mol): - """aC-NO2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;+]([O;-])=[O]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_081(mol): - """NO2 except as above""" - query = Chem.MolFromSmarts('[N;+]([O;-])=[O]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_082(mol): - """ONO""" - query = Chem.MolFromSmarts('[O;H0;!R;D2][N;H0;!R;D2]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_083(mol): - """ONO2""" - query = Chem.MolFromSmarts('[O;H0;!R;D2][N;+]([O;-1])=O') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_084(mol): - """HCON(CH2)2""" - query = Chem.MolFromSmarts('[O;H0;!R;D1]=[C;H1;!R;D2][N;H0;!R;D3]([C;H2;!R;D2])[C;H2;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_085(mol): - """HCONH(CH2)""" - query = Chem.MolFromSmarts('[O;H0;!R;D1]=[C;H1;!R;D2][N;H1;!R;D2][C;H2;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_086(mol): - """CONH2""" - query = Chem.MolFromSmarts('[O;H0;!R;D1]=[C;H0;!R;D3][N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_087(mol): - """CONHCH3""" - query = Chem.MolFromSmarts('[O;H0;!R;D1]=[C;H0;!R;D3][N;H1;!R;D2][C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_088(mol): - """CONHCH2""" - query = Chem.MolFromSmarts('[O;H0;!R;D1]=[C;H0;!R;D3][N;H1;!R;D2][C;H2;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_089(mol): - """CON(CH3)2""" - query = Chem.MolFromSmarts('[O;H0;!R;D1]=[C;H0;!R;D3][N;H0;!R;D3]([C;H3;!R;D1])[C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_090(mol): - """CONCH3CH2""" - query = Chem.MolFromSmarts('[O;H0;!R;D1]=[C;H0;!R;D3][N;H0;!R;D3]([C;H2;!R;D2])[C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_091(mol): - """CON(CH2)2""" - query = Chem.MolFromSmarts('[O;H0;!R;D1]=[C;H0;!R;D3][N;H0;!R;D3]([C;H2;!R;D2])[C;H2;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_092(mol): - """CONHCO""" - query = Chem.MolFromSmarts('[O;H0;!R;D1]=[C;H0;!R;D3][N;H1;!R;D2][C;H0;!R;D3]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_093(mol): - """CONCO""" - query = Chem.MolFromSmarts('[O;H0;!R;D1]=[C;H0;!R;D3][N;H0;!R;D3][C;H0;!R;D3]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_094(mol): - """aC-CONH2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H0;!R;D3](=[O;H0;!R;D1])[N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_095(mol): - """aC-NH(CO)H""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H1;!R;D2]-[C;H1;!R;D2](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_096(mol): - """aC-N(CO)H""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H0;!R;D3]-[C;H1;!R;D2](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_097(mol): - """aC-CONH""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H0;!R;D3](=[O;H0;!R;D1])[N;H1;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_098(mol): - """aC-NHCO""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H1;!R;D2]-[C;H0;!R;D3]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_099(mol): - """aC-NCO""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H0;!R;D3]-[C;H0;!R;D3]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_100(mol): - """NHCONH""" - query = Chem.MolFromSmarts('[N;H1;!R;D2]-[C;H0;!R;D3](=[O;H0;!R;D1])-[N;H1;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_101(mol): - """NH2CONH""" - query = Chem.MolFromSmarts('[N;H2;!R;D1]-[C;H0;!R;D3](=[O;H0;!R;D1])-[N;H1;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_102(mol): - """NH2CON""" - query = Chem.MolFromSmarts('[N;H2;!R;D1]-[C;H0;!R;D3](=[O;H0;!R;D1])-[N;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_103(mol): - """NHCON""" - query = Chem.MolFromSmarts('[N;H1;!R;D2]-[C;H0;!R;D3](=[O;H0;!R;D1])-[N;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_104(mol): - """NCON""" - query = Chem.MolFromSmarts('[N;H0;!R;D3]-[C;H0;!R;D3](=[O;H0;!R;D1])-[N;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_105(mol): - """aC-NHCONH2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H1;!R;D2]-[C;H0;!R;D3](=[O;H0;!R;D1])-[N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_106(mol): - """aC-NHCONH""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H1;!R;D2]-[C;H0;!R;D3](=[O;H0;!R;D1])-[N;H1;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_107(mol): - """NHCO except as above""" - query = Chem.MolFromSmarts('[N;H1;!R;D2]-[C;H0;!R;D3](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_108(mol): - """CH2Cl""" - query = Chem.MolFromSmarts('[C;H2;!R;D2]-[Cl]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_109(mol): - """CHCl""" - query = Chem.MolFromSmarts('[C;H1;!R][Cl]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_110(mol): - """CCl""" - query = Chem.MolFromSmarts('[C;H0;!R][Cl]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_111(mol): - """CHCl2""" - query = Chem.MolFromSmarts('[C;H1;!R;D3](-[Cl])-[Cl]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_112(mol): - """CCl2""" - query = Chem.MolFromSmarts('[C;H0;!R](-[Cl])-[Cl]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_113(mol): - """CCl3""" - query = Chem.MolFromSmarts('[C;H0;!R;D4](-[Cl])(-[Cl])-[Cl]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_114(mol): - """CH2F""" - query = Chem.MolFromSmarts('[C;H2;!R;D2]-[F]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_115(mol): - """CHF""" - query = Chem.MolFromSmarts('[C;H1;!R][F]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_116(mol): - """CF""" - query = Chem.MolFromSmarts('[C;H0;!R][F]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_117(mol): - """CHF2""" - query = Chem.MolFromSmarts('[C;H1;!R;D3](-[F])-[F]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_118(mol): - """CF2""" - query = Chem.MolFromSmarts('[C;H0;!R](-[F])-[F]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_119(mol): - """CF3""" - query = Chem.MolFromSmarts('[C;H0;!R;D4](-[F])(-[F])-[F]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_120(mol): - """CCl2F""" - query = Chem.MolFromSmarts('F[C;H0;!R;D4](Cl)Cl') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_121(mol): - """HCClF""" - query = Chem.MolFromSmarts('F[C;H1;!R;D3]Cl') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_122(mol): - """CClF2""" - query = Chem.MolFromSmarts('Cl[C;H0;!R;D4](F)F') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_123(mol): - """aC-Cl""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-Cl') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_124(mol): - """aC-F""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-F') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_125(mol): - """aC-I""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-I') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_126(mol): - """aC-Br""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-Br') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_127(mol): - """I except as above""" - query = Chem.MolFromSmarts('I') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_128(mol): - """Br except as above""" - query = Chem.MolFromSmarts('Br') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_129(mol): - """F except as above""" - query = Chem.MolFromSmarts('F') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_130(mol): - """Cl except as above""" - query = Chem.MolFromSmarts('Cl') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_131(mol): - """CHNOH""" - query = Chem.MolFromSmarts('[C;H1;!R;D2]=[N;H0;!R;D2]-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_132(mol): - """CNOH""" - query = Chem.MolFromSmarts('[C;H0;!R]=[N;H0;!R;D2]-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_133(mol): - """aC-CHNOH""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H1;!R;D2]=[N;H0;!R;D2]-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_134(mol): - """OCH2CH2OH""" - query = Chem.MolFromSmarts('[O;H0;!R;D2][C;H2;!R;D2][C;H2;!R;D2][O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_135(mol): - """OCHCH2OH""" - query = Chem.MolFromSmarts('[O;H0;!R;D2][C;H1;!R;D3][C;H2;!R;D2][O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_136(mol): - """OCH2CHOH""" - query = Chem.MolFromSmarts('[O;H0;!R;D2][C;H2;!R;D2][C;H1;!R;D3][O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_137(mol): - """O-OH""" - query = Chem.MolFromSmarts('[O;H0;!R;D2][O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_138(mol): - """CH2SH""" - query = Chem.MolFromSmarts('[C;H2;!R;D2]-[S;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_139(mol): - """CHSH - 这里要求C是D3,是因为后面又142(-SH(except as above)) - """ - query = Chem.MolFromSmarts('[C;H1;!R;D3]-[S;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_140(mol): - """CSH - 这里要求C是D4,是因为后面又142(-SH(except as above)) - """ - query = Chem.MolFromSmarts('[C;H0;!R;D4]-[S;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_141(mol): - """aC-SH""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[S;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_142(mol): - """SH except as above""" - query = Chem.MolFromSmarts('[S;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_143(mol): - """CH3S""" - query = Chem.MolFromSmarts('[C;H3;!R;D1]-[S;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_144(mol): - """CH2S""" - query = Chem.MolFromSmarts('[C;H2;!R;D2]-[S;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_145(mol): - """CHS""" - query = Chem.MolFromSmarts('[C;H1;!R]-[S;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_146(mol): - """CS""" - query = Chem.MolFromSmarts('[C;H0;!R]-[S;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_147(mol): - """aC-S-""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[S;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_148(mol): - """SO""" - query = Chem.MolFromSmarts('[S;H0;!R;D3]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_149(mol): - """SO2""" - query = Chem.MolFromSmarts('[S;H0;!R;D4](=[O;H0;!R;D1])(=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_150(mol): - """SO3(sulfite)""" - query = Chem.MolFromSmarts('[O;H0;!R;D2][S;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_151(mol): - """SO3(sulfonate)""" - query = Chem.MolFromSmarts('[O;H0;!R;D2][S;H0;!R;D4](=[O;H0;!R;D1])(=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_152(mol): - """SO4(sulfite)""" - query = Chem.MolFromSmarts('[O;H0;!R;D2][S;H0;!R;D4](=[O;H0;!R;D1])(=[O;H0;!R;D1])[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_153(mol): - """aC-SO""" - query = Chem.MolFromSmarts('[c;H0;R;D3][S;H0;!R;D3]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_154(mol): - """aC-SO2""" - query = Chem.MolFromSmarts('[c;H0;R;D3][S;H0;!R;D4](=[O;H0;!R;D1])(=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -# todo 含磷化合物太复杂了,以后再检查吧 -def f_155(mol): - """PH(phosphine)""" - query = Chem.MolFromSmarts('[P;H1;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_156(mol): - """P(phosphine)""" - query = Chem.MolFromSmarts('[P;H0;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_157(mol): - """PO3(phosphine)""" - query = Chem.MolFromSmarts('[O;D2;H0][P;H0;D3]([O;D2;H0])[O;D2;H0]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_158(mol): - """PHO3(phosphonate)""" - query = Chem.MolFromSmarts('[O;D2;H0][P;H1;D3](=[O;D1;H0])[O;D2;H0]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_159(mol): - """PO3(phosphonate)""" - query = Chem.MolFromSmarts('[O;D2;H0][P;H0;D4](=[O;D1;H0])[O;D2;H0]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_160(mol): - """PHO4(phosphate)""" - query = Chem.MolFromSmarts('[O;D2;H0][P;H0;D4]([O;H1;D1])(=[O;D1;H0])[O;D2;H0]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_161(mol): - """PO4(phosphate)""" - query = Chem.MolFromSmarts('[O;D2;H0][P;H0;D4]([O;H0;D2])(=[O;D1;H0])[O;D2;H0]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_162(mol): - """aC-PO4""" - query = Chem.MolFromSmarts('[c;H0;R][O;D2;H0][P;H0;D4]([O;H0;D2])(=[O;D1;H0])[O;D2;H0]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_163(mol): - """aC-P""" - query = Chem.MolFromSmarts('[c;H0;R][P;H0;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_164(mol): - """CO3(carbonate)""" - query = Chem.MolFromSmarts('[O;H0;!R;D2][C;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_165(mol): - """C2H3O""" - query = Chem.MolFromSmarts('[C;H2;R;D2]1[O;H0;R;D2][C;H1;R;D3]1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_166(mol): - """C2H2O""" - query = Chem.MolFromSmarts('[C;H2;R;D2]1[O;H0;R;D2][C;H0;R]1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_167(mol): - """C2O""" - query = Chem.MolFromSmarts('[C;H1;R][O;H0;R][C;H0;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_168(mol): - """CH2(cyclic)""" - query = Chem.MolFromSmarts('[C;H2;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_169(mol): - """CH(cyclic)""" - query = Chem.MolFromSmarts('[C;H1;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_170(mol): - """C(cyclic)""" - query = Chem.MolFromSmarts('[C;H0;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_171(mol): - """CH=CH(cyclic)""" - query = Chem.MolFromSmarts('[C;H1;D2]=[C;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_172(mol): - """CH=C(cyclic)""" - query = Chem.MolFromSmarts('[C;H1;D2]=[C;H0;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_173(mol): - """C=C(cyclic)""" - query = Chem.MolFromSmarts('[C;H0]=[C;H0;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_174(mol): - """CH2=C(cyclic)""" - query = Chem.MolFromSmarts('[C;H2;D1]=[C;H0;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_175(mol): - """NH(cyclic)""" - query = Chem.MolFromSmarts('[N;H1;D2;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_176(mol): - """N(cyclic)""" - query = Chem.MolFromSmarts('[N;H0;D3;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_177(mol): # todo 芳香性 - """CH=N(cyclic)""" - # '[C,c;H1]=,:[N,n;H0;D2;R]' - query1 = Chem.MolFromSmarts('[C;H1]=[N;H0;D2;R]') - match_list = mol.GetSubstructMatches(query1) - return len(match_list), match_list - - -def f_178(mol): # todo 芳香性 - """C=N(cyclic)""" - query = Chem.MolFromSmarts('[C;H0]=[N;H0;D2;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_179(mol): - """O(cyclic)""" - query = Chem.MolFromSmarts('[O;H0;D2;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_180(mol): - """CO(cyclic)""" - query = Chem.MolFromSmarts('[C;H0;R;D3]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_181(mol): # todo 芳香性 - """S(cyclic)""" - query = Chem.MolFromSmarts('[S,s;H0;D2;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_182(mol): # todo 芳香性 - """SO2(cyclic)""" - query = Chem.MolFromSmarts('[O;H0;D1;!R]=[S,s;H0;D4;R]=[O;H0;D1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -# todo 183-220都没有官方的例子 -def f_183(mol): - """>NH""" - query = Chem.MolFromSmarts('[N;H1;D3;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_184(mol): - """-O-""" - query = Chem.MolFromSmarts('[O;H0;D2;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_185(mol): - """-S-""" - query = Chem.MolFromSmarts('[S;H0;D2;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_186(mol): - """>CO""" - query = Chem.MolFromSmarts('[C;H0;D3;!R]=[O;H0;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_187(mol): - """PO2""" - query = Chem.MolFromSmarts('[O;H0;!R][P;!R][O;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_188(mol): - """CH-N""" - query = Chem.MolFromSmarts('[C;H1;!R]-[N;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_189(mol): - """SiHO""" - query = Chem.MolFromSmarts('[Si;H1;!R]=[O;H0;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_190(mol): - """SiO""" - query = Chem.MolFromSmarts('[Si;H0;!R]=[O;H0;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_191(mol): - """SiH2""" - query = Chem.MolFromSmarts('[Si;H2;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_192(mol): - """SiH1""" - query = Chem.MolFromSmarts('[Si;H1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_193(mol): - """Si""" - query = Chem.MolFromSmarts('[Si;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_194(mol): - """(CH3)3N""" - query = Chem.MolFromSmarts('[C;H3][N;!R]([C;H3])[C;H3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_195(mol): - """N=N""" - query = Chem.MolFromSmarts('[N;H0;!R]=[N;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_196(mol): - """Ccyc=N-""" - query = Chem.MolFromSmarts('[C;H0;R]=[N;H0;D2;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_197(mol): - """Ccyc=CH-""" - query = Chem.MolFromSmarts('[C;H0;R]=[C;H1;D2;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_198(mol): - """Ccyc=NH""" - query = Chem.MolFromSmarts('[C;H0;R]=[N;H1;D1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_199(mol): - """N=O""" - query = Chem.MolFromSmarts('[N;H0;!R]=[O;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_200(mol): - """Ccyc=C""" - query = Chem.MolFromSmarts('[C;H0;R]=[C;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_201(mol): - """P=O""" - query = Chem.MolFromSmarts('[P;H0;!R]=[O;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_202(mol): - """N=N""" - query = Chem.MolFromSmarts('[N;H0;!R]=[N;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_203(mol): - """C=NH""" - query = Chem.MolFromSmarts('[C;H0;!R]=[N;H1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_204(mol): - """>C=S""" - query = Chem.MolFromSmarts('[C;H0;!R;D3]=[S;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_205(mol): - """aC-CON""" - query = Chem.MolFromSmarts('[c]!@&-[C](=[O;H0;D1])[N;H0]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_206(mol): - """aC=O""" - query = Chem.MolFromSmarts('[c]!@&=[O;H0;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_207(mol): - """aN-""" - query = Chem.MolFromSmarts('[n;H0;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_208(mol): - """-Na""" - query = Chem.MolFromSmarts('[Na]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_209(mol): - """-K""" - query = Chem.MolFromSmarts('[K]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_210(mol): - """HCONH""" - query = Chem.MolFromSmarts('[C;H1;!R](=[O;H0;D1])[N;H1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_211(mol): - """CHOCH""" - query = Chem.MolFromSmarts('[C;H1;!R](=[O;H0;D1])[C;H1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_212(mol): - """C2O""" - query = Chem.MolFromSmarts('[C;H0;R]1[O;H0;R][C;H0;R]1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_213(mol): - """SiH3""" - query = Chem.MolFromSmarts('[Si;H3;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_214(mol): # todo ? - """SiH2O""" - query = Chem.MolFromSmarts('[Si;H2;!R][O]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_215(mol): - """CH=C=CH""" - query = Chem.MolFromSmarts('[C;H1;!R]=[C;H0;!R]=[C;H1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_216(mol): - """CH=C=C""" - query = Chem.MolFromSmarts('[C;H1;!R]=[C;H0;!R]=[C;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_217(mol): - """OP(=S)O""" - query = Chem.MolFromSmarts('[O;H0][P;H0](=[S;H0])[O;H0]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_218(mol): # todo ? - """R""" - query = Chem.MolFromSmarts('[*]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_219(mol): - """CF2cyc""" - query = Chem.MolFromSmarts('[F][C;H0;R][F]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_220(mol): - """CFcyc""" - query = Chem.MolFromSmarts('[F][C;H0;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -# s order -def s_001(mol): - """(CH3)2CH""" - query = Chem.MolFromSmarts('[C;H3;!R;D1][C;H1;!R;D3][C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_002(mol): - """(CH3)3C""" - query = Chem.MolFromSmarts('[C;H3;!R;D1][C;H0;!R;D4]([C;H3;!R;D1])[C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_003(mol): # todo 能不能把多余的原子去掉? 好像没有必要 - """CH(CH3)CH(CH3)""" - query = Chem.MolFromSmarts('[*]-[C;H1;!R;D3]([C;H3;!R;D1])[C;H1;!R;D3]([C;H3;!R;D1])-[*]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_004(mol): # todo 能不能把多余的原子去掉 - """CH(CH3)C(CH3)2""" - query = Chem.MolFromSmarts('[*]-[C;H1;!R;D3]([C;H3;!R;D1])[C;H0;!R;D4]([*])([C;H3;!R;D1])[C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_005(mol): # todo 能不能把多余的原子去掉 - """C(CH3)2C(CH3)2""" - query = Chem.MolFromSmarts('[*]-[C;H0;!R;D4]([C;H3;!R;D1])([C;H3;!R;D1])[C;H0;!R;D4]([*])([C;H3;!R;D1])[C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_006(mol): - """CHn=CHm-CHp=CHk k,m,n,p=0,1,2""" - query = Chem.MolFromSmarts('[C;!R]=[C;!R]-[C;!R]=[C;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_007(mol): - """CH3-CHm=CHn m,n=0,1,2""" - query = Chem.MolFromSmarts('[C;H3;!R;D1]-[C;!R]=[C;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_008(mol): - """CH2-CHm=CHn m,n=0,1,2""" - query = Chem.MolFromSmarts('[C;H2;!R;D2]-[C;!R]=[C;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_009(mol): - """CHp-CHm=CHn p=0,1 m,n=0,1,2""" - query = Chem.MolFromSmarts('[C;!H2;!R]-[C;!R]=[C;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_010(mol): - """CHCHO or CCHO""" - query1 = Chem.MolFromSmarts('[C;H1;!R][C;H1;!R;D2]=[O;H0;D1]') - query2 = Chem.MolFromSmarts('[C;H0;!R][C;H1;!R;D2]=[O;H0;D1]') - match_list = mol.GetSubstructMatches(query1) + mol.GetSubstructMatches(query2) - return len(match_list), match_list - - -def s_011(mol): - """CH3COCH2""" - query = Chem.MolFromSmarts('[C;H3;!R;D1]-[C;H0;!R;D3](=[O;H0;!R;D1])-[C;H2;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_012(mol): - """CH3COCH or CH3COC""" - query = Chem.MolFromSmarts('[C;H3;!R;D1]-[C;H0;!R;D3](=[O;H0;!R;D1])-[C;H1,H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_013(mol): - """CHCOOH or CCOOH""" - query = Chem.MolFromSmarts('[O;H1;!R;D1]-[C;H0;!R;D3](=[O;H0;!R;D1])-[C;H1,H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_014(mol): - """CH3COOCH or CH3COOC""" - query = Chem.MolFromSmarts('[C;H3;!R;D1]-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H0;!R;D2]-[C;H1,H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_015(mol): - """CO-O-CO""" - query = Chem.MolFromSmarts('[O;H0;!R;D1]=[C;H0;!R;D3]-[O;H0;!R;D2]-[C;H0;!R;D3]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_016(mol): - """CHOH""" - query = Chem.MolFromSmarts('[C;H1;!R]-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_017(mol): - """COH""" - query = Chem.MolFromSmarts('[C;H0;!R]-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_018(mol): - """CH3COCHnOH n=0,1,2""" - query = Chem.MolFromSmarts('[C;H3;!R;D1]-[C;H0;!R;D3](=[O;H0;!R;D1])[C;H0,H1,H2;!R]-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_019(mol): - """NCCHOH or NCCOH""" - query = Chem.MolFromSmarts('[N;H0;!R;D1]#[C;H0;!R;D2][C;H0,H1;!R]-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_020(mol): - """OH-CHn-COO n=0,1,2""" - query = Chem.MolFromSmarts('[O;H1;!R;D1]-[C;H0,H1,H2;!R]-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_021(mol): - """CHm(OH)CHn(OH) m,n=0,1,2""" - query = Chem.MolFromSmarts('[O;H1;!R;D1]-[C;H0,H1,H2;!R]-[C;H0,H1,H2;!R]-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_022(mol): - """CHm(OH)CHn(NHp) m,n,p=0,1,2""" - query = Chem.MolFromSmarts('[O;H1;!R;D1]-[C;H0,H1,H2;!R]-[C;H0,H1,H2;!R]-[N;H0,H1,H2;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_023(mol): - """CHm(NH2)CHn(NH2) m,n=0,1,2""" - query = Chem.MolFromSmarts('[N;H2;!R;D1]-[C;H0,H1,H2;!R]-[C;H0,H1,H2;!R]-[N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_024(mol): - """CHm(NH)CHn(NH2) m,n=0,1,2""" - query = Chem.MolFromSmarts('[N;H;!R;D2]-[C;H0,H1,H2;!R]-[C;H0,H1,H2;!R]-[N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_025(mol): - """H2NCOCHnCHmCONH2 m,n=0,1,2""" - query = Chem.MolFromSmarts( - '[N;H2;!R;D1]-[C;H0;!R;D3](=[O;H0;!R;D1])-[C;H0,H1,H2;!R][C;H0,H1,H2;!R]-[C;H0;!R;D3](=[O;H0;!R;D1])-[N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_026(mol): - """CHm(NHn)-COOH m,n=0,1,2""" - query = Chem.MolFromSmarts('[N;H0,H1,H2;!R]-[C;H0,H1,H2;!R]-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_027(mol): - """HOOC-CHn-COOH n=1,2""" - query = Chem.MolFromSmarts( - '[C;H1,H2;!R](-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1])-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_028(mol): - """HOOC-CHn-CHm-COOH m,n=1,2""" - query = Chem.MolFromSmarts( - '[C;H1,H2;!R](-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1])-[C;H1,H2;!R](-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_029(mol): - """HO-CHn-COOH n=0,1,2""" - query = Chem.MolFromSmarts('[O;H1;!R;D1][C;H0,H1,H2;!R]-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_030(mol): - """NH2-CHn-CHm-COOH m,n=1,2""" - query = Chem.MolFromSmarts('[C;H1,H2;!R]([N;H2;!R;D1])-[C;H1,H2;!R](-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_031(mol): - """CH3-O-CHn-COOH n=1,2""" - query = Chem.MolFromSmarts('[C;H3;!R;D1]-[O;H0;!R;D2]-[C;H1,H2;!R](-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_032(mol): - """HS-CH-COOH""" - query = Chem.MolFromSmarts('[S;H1;!R;D1][C;H1;!R](-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_033(mol): - """HS-CHn-CHm-COOH m,n=1,2""" - query = Chem.MolFromSmarts('[C;H1,H2;!R]([S;H1;!R;D1])-[C;H1,H2;!R](-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_034(mol): - """NC-CHn-CHm-CN m,n=1,2""" - query = Chem.MolFromSmarts('[C;H1,H2;!R]([C;H0;!R;D2]#[N;H0;!R;D1])-[C;H1,H2;!R]([C;H0;!R;D2]#[N;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_035(mol): - """OH-CHn-CHm-CN m,n=1,2""" - query = Chem.MolFromSmarts('[C;H1,H2;!R]([O;H1;!R;D1])-[C;H1,H2;!R]([C;H0;!R;D2]#[N;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_036(mol): - """HS-CHn-CHm-SH m,n=1,2""" - query = Chem.MolFromSmarts('[C;H1,H2;!R]([S;H1;!R;D1])-[C;H1,H2;!R]([S;H1;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_037(mol): - """COO-CHn-CHm-OOC m,n=1,2""" - query = Chem.MolFromSmarts( - '[C;H1,H2;!R]([O;H0;!R;D2][C;H0;!R;D3](=[O;H0;!R;D1]))-[C;H1,H2;!R]([O;H0;!R;D2][C;H0;!R;D3](=[O;H0;!R;D1]))') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_038(mol): - """OOC-CHn-CHm-COO m,n=1,2""" - query = Chem.MolFromSmarts( - '[C;H1,H2;!R]([C;H0;D3;!R](=[O;H0;!R;D1])[O;H0;!R;D2])-[C;H1,H2;!R]([C;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_039(mol): - """NC-CHn-COO n=1,2""" - query = Chem.MolFromSmarts('[C;H1,H2;!R]([C;H0;!R;D2]#[N;H0;!R;D1])([C;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_040(mol): - """COCHnCOO n=1,2""" - query = Chem.MolFromSmarts('[C;H1,H2;!R]([C;H0;!R;D3](=[O;H0;!R;D1]))([C;H0;D3;!R](=[O;H0;!R;D1])[O;H0;!R;D2])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_041(mol): - """CHm-O-CHn=CHp m,n,p=0,1,2,3""" - query = Chem.MolFromSmarts('[C;H0,H1,H2,H3;!R]-[O;H0;!R;D2]-[C;H0,H1,H2,H3;!R]=[C;H0,H1,H2,H3;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_042(mol): - """CHm=CHn-F m,n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;!R]=[C;H0,H1,H2;!R]-F') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_043(mol): - """CHm=CHn-Br m,n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;!R]=[C;H0,H1,H2;!R]-Br') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_044(mol): - """CHm=CHn-I m,n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;!R]=[C;H0,H1,H2;!R]-I') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_045(mol): - """CHm=CHn-Cl m,n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;!R]=[C;H0,H1,H2;!R]-Cl') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_046(mol): - """CHm=CHn-CN m,n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;!R]=[C;H0,H1,H2;!R]-[C;H0;!R;D2]#[N;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_047(mol): - """CHm=CHn-COO-CHp m,n,p=0,1,2,3""" - query = Chem.MolFromSmarts( - '[C;H0,H1,H2;!R]=[C;H0,H1,H2;!R]-[C;H0;!R](=[O;H0;D1;!R])[O;H0;!R;D2]-[C;H0,H1,H2,H3;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_048(mol): - """CHm=CHn-CHO m,n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;!R]=[C;H0,H1,H2;!R]-[C;H1;!R;D2](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_049(mol): - """CHm=CHn-COOH m,n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;!R]=[C;H0,H1,H2;!R](-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_050(mol): - """aC-CHn-X n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]-[F,Cl,Br,I]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_051(mol): - """aC-CHn-NHm n=1,2 m=0,1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]-[N;H0,H1,H2;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_052(mol): - """aC-CHn-O- n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]-[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_053(mol): - """aC-CHn-OH n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_054(mol): - """aC-CHn-CN n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]-[C;H0;!R;D2]#[N;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_055(mol): - """aC-CHn-CHO n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]-[C;H1;!R;D2](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_056(mol): - """aC-CHn-SH n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]-[S;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_057(mol): - """aC-CHn-COOH n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R](-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_058(mol): - """aC-CHn-CO- n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]-[C;H0;!R;D3](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_059(mol): - """aC-CHn-S- n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]-[S;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_060(mol): - """aC-CHn-OOC-H n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]([O;H0;!R;D2][C;H1;!R;D2](=[O;H0;!R;D1]))') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_061(mol): - """aC-CHn-NO2 n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]-[N;+]([O;-])=[O]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_062(mol): - """aC-CHn-CONH2 n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]-[C;H0;!R;D3](=[O;H0;!R;D1])[N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_063(mol): - """aC-CHn-OOC n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]([O;H0;!R;D2][C;H0;!R;D3](=[O;H0;!R;D1]))') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_064(mol): - """aC-CHn-COO n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]([C;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_065(mol): - """aC-SO2-OH n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[S;H0;!R;D4](=[O;H0;!R;D1])(=[O;H0;!R;D1])[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_066(mol): - """aC-CH(CH3)2""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H1;!R;D3]([C;H3;!R;D1])([C;H3;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_067(mol): - """aC-C(CH3)3""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H0;!R;D4]([C;H3;!R;D1])([C;H3;!R;D1])([C;H3;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_068(mol): - """aC-CF3""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H0;!R;D4](F)(F)F') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_069(mol): # todo 芳香性 - """(CHn=C)(cyclic)-CHO n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;R]=[C;H0;R;D3]-[C;H1;!R;D2](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_070(mol): # todo 芳香性 - """(CHn=C)(cyclic)-COO-CHm m,n=0,1,2,3""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;R]=[C;H0;R;D3]-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H0;!R;D2]-[C;H0,H1,H2,H3;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_071(mol): # todo 芳香性 - """(CHn=C)(cyclic)-CO- n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;R]=[C;H0;R;D3]-[C;H0;!R;D3](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_072(mol): # todo 芳香性 - """(CHn=C)cyc-CH3, n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;R]=[C;H0;R;D3]-[C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_073(mol): # todo 芳香性 - """(CHn=C)cyc-CH2, n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;R]=[C;H0;R;D3]-[C;H2;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_074(mol): # todo 芳香性 - """(CHn=C)(cyclic)-CN n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;R]=[C;H0;R;D3]-[C;H0;!R;D2]#[N;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_075(mol): # todo 芳香性 - """(CHn=C)(cyclic)-Cl n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;R]=[C;H0;R;D3]-Cl') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_076(mol): - """CHcyc-CH3""" - query = Chem.MolFromSmarts('[C;H1;R;D3][C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_077(mol): - """CHcyc-CH2""" - query = Chem.MolFromSmarts('[C;H1;R;D3][C;H2;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_078(mol): - """CHcyc-CH""" - query = Chem.MolFromSmarts('[C;H1;R;D3][C;H1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_079(mol): - """CHcyc-C""" - query = Chem.MolFromSmarts('[C;H1;R;D3][C;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_080(mol): - """CHcyc-CH=CHn n=1,2""" - query = Chem.MolFromSmarts('[C;H1;R;D3][C;H1;!R;D2]=[C;H1,H2;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_081(mol): - """CHcyc-C=CHn n=1,2""" - query = Chem.MolFromSmarts('[C;H1;R;D3][C;H0;!R;D3]=[C;H1,H2;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_082(mol): - """CHcyc-Cl""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-Cl') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_083(mol): - """CHcyc-F""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-F') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_084(mol): - """CHcyc-OH""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_085(mol): - """CHcyc-NH2""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-[N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_086(mol): - """CHcyc-NH-CHn n=0,1,2,3""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-[N;H1;!R;D2]-[C;H0,H1,H2,H3;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_087(mol): - """CHcyc-N-CHn n=0,1,2,3""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-[N;H0;!R;D3]-[C;H0,H1,H2,H3;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_088(mol): - """CHcyc-SH""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-[S;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_089(mol): - """CHcyc-CN""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-[C;H0;!R;D2]#[N;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_090(mol): - """CHcyc-COOH""" - query = Chem.MolFromSmarts('[C;H1;R;D3](-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_091(mol): - """CHcyc-CO-""" - query = Chem.MolFromSmarts('[C;H1;R;D3][C;H0;!R;D3](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_092(mol): - """CHcyc-NO2""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-[N;+]([O;-])=[O]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_093(mol): - """CHcyc-S-""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-[S;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_094(mol): - """CHcyc-CHO""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-[C;H1;!R;D2](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_095(mol): - """CHcyc-O-""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_096(mol): - """CHcyc-OOCH""" - query = Chem.MolFromSmarts('[C;H1;R;D3]([O;H0;!R;D2][C;H1;!R;D2](=[O;H0;!R;D1]))') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_097(mol): - """CHcyc-COO""" - query = Chem.MolFromSmarts('[C;H1;R;D3]([C;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_098(mol): - """CHcyc-OOC""" - query = Chem.MolFromSmarts('[C;H1;R;D3]([O;H0;!R;D2][C;H0;!R;D3](=[O;H0;!R;D1]))') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_099(mol): - """Ccyc-CH3 - 环上的C要求D4是因为前面有s72 - """ - query = Chem.MolFromSmarts('[C;H0;R;D4][C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_100(mol): - """Ccyc-CH2 - 环上的C要求D4是因为前面有s73 - """ - query = Chem.MolFromSmarts('[C;H0;R;D4][C;H2;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_101(mol): - """Ccyc-OH""" - query = Chem.MolFromSmarts('[C;H0;R;D4][O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_102(mol): - """>Ncyc-CH3""" - query = Chem.MolFromSmarts('[N,n;H0;D3;R]-[C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_103(mol): - """>Ncyc-CH2""" - query = Chem.MolFromSmarts('[N,n;H0;D3;R]-[C;H2;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_104(mol): - """AROMRINGs1s2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2][c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_105(mol): - """AROMRINGs1s3""" - query = Chem.MolFromSmarts('[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_106(mol): - """AROMRINGs1s4""" - query = Chem.MolFromSmarts('[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_107(mol): - """AROMRINGs1s2s3""" - query = Chem.MolFromSmarts('[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_108(mol): - """AROMRINGs1s2s4""" - query = Chem.MolFromSmarts('[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_109(mol): - """AROMRINGs1s3s5""" - query = Chem.MolFromSmarts('[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_110(mol): - """AROMRINGs1s2s3s4""" - query = Chem.MolFromSmarts( - '[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_111(mol): - """AROMRINGs1s2s3s5""" - query = Chem.MolFromSmarts( - '[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_112(mol): - """AROMRINGs1s2s4s5""" - query = Chem.MolFromSmarts( - '[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_113(mol): - """PYRIDINEs2""" - query = Chem.MolFromSmarts('[n;H0;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2][c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_114(mol): - """PYRIDINEs3""" - query = Chem.MolFromSmarts('[n;H0;R;D2][c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_115(mol): - """PYRIDINEs4""" - query = Chem.MolFromSmarts('[n;H0;R;D2][c;H1;R;D2][c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_116(mol): - """PYRIDINEs2s3""" - query = Chem.MolFromSmarts('[n;H0;R;D2][c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_117(mol): - """PYRIDINEs2s4""" - query = Chem.MolFromSmarts('[n;H0;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_118(mol): - """PYRIDINEs2s5""" - query = Chem.MolFromSmarts('[n;H0;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_119(mol): - """PYRIDINEs2s6""" - query = Chem.MolFromSmarts('[n;H0;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2][c;H1;R;D2][c;H0;R;D3]([!a;!R])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_120(mol): - """PYRIDINEs3s4""" - query = Chem.MolFromSmarts('[n;H0;R;D2][c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_121(mol): - """PYRIDINEs3s5""" - query = Chem.MolFromSmarts('[n;H0;R;D2][c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_122(mol): - """PYRIDINEs2s3s6""" - query = Chem.MolFromSmarts('[n;H0;R;D2][c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2][c;H0;R;D3]([!a;!R])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_123(mol): - """(CHn=CHm)cyc-COOH""" - query = Chem.MolFromSmarts('[C;R]@&=[C;R]!@&-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_124(mol): - """AROMRINGs1s2s3s4s5""" - # query = Chem.MolFromSmarts('[c]1[c](!@&-[*])[c](!@&-[*])[c](!@&-[*])[c](!@&-[*])[c]1(!@&-[*])') - query = Chem.MolFromSmarts( - '[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_125(mol): - """aC-NHCOCH2N""" - query = Chem.MolFromSmarts('[c;H0;R;D3]!@&-[N;H1]-[C;H0](=[O;H0;D1])[C;H2][N]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_126(mol): - """(N=C)cyc-CH3""" - query = Chem.MolFromSmarts('[N;R]@&=[C;R]!@&-[C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_127(mol): - """aC-CONH(CH2)2N""" - query = Chem.MolFromSmarts('[c]!@&-[C](=[O;H0;D1])[N;H1][C;H2][C;H2][N]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_128(mol): # todo 原文128、129、130完全一样,不知道怎么回事 - """aC-SO2NHn n=0,1,2""" - query = Chem.MolFromSmarts('[c]!@&-[S](=[O;H0;D1])(=[O;H0;D1])[N;H0,H1,H2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_129(mol): # todo 原文128、129、130完全一样,不知道怎么回事 - """aC-SO2NHn n=0,1,2""" - query = Chem.MolFromSmarts('[c]!@&-[S](=[O;H0;D1])(=[O;H0;D1])[N;H0,H1,H2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_130(mol): # todo 原文128、129、130完全一样,不知道怎么回事 - """aC-SO2NHn n=0,1,2""" - query = Chem.MolFromSmarts('[c]!@&-[S](=[O;H0;D1])(=[O;H0;D1])[N;H0,H1,H2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -# t order -def t_001(mol): - """HOOC-(CHn)m-COOH m>2,n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(3, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts( - '[C;H0;!R;D3](=[O;H0;!R;D1])(-[O;H1;!R;D1]){}-[C;H0;!R;D3](=[O;H0;!R;D1])(-[O;H1;!R;D1])'.format( - '-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_002(mol): - """NHn-(CHn)m-COOH m>2,n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(3, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts( - '[N;H0,H1,H2;!R]{}-[C;H0;!R;D3](=[O;H0;!R;D1])(-[O;H1;!R;D1])'.format('-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_003(mol): - """NH2-(CHn)m-OH m>2,n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(3, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts('[N;H2;!R;D1]{}-[O;H1;!R;D1]'.format('-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_004(mol): - """OH-(CHn)m-OH m>2,n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(3, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts('[O;H1;!R;D1]{}-[O;H1;!R;D1]'.format('-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_005(mol): - """OH-(CHp)k-O-(CHn)m-OH m,k>0;p,n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(1, len(not_in_ring_atoms) + 1): - for j in range(1, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts( - '[O;H1;!R;D1]{}-[O;H0;D2;!R]{}-[O;H1;!R;D1]'.format('-[C;H0,H1,H2;!R]' * i, '-[C;H0,H1,H2;!R]' * j)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_006(mol): - """OH-(CHp)k-S-(CHn)m-OH m,k>0;p,n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(1, len(not_in_ring_atoms) + 1): - for j in range(1, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts( - '[O;H1;!R;D1]{}-[S;H0;D2;!R]{}-[O;H1;!R;D1]'.format('-[C;H0,H1,H2;!R]' * i, '-[C;H0,H1,H2;!R]' * j)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_007(mol): - """OH-(CHp)k-NHx-(CHn)m-OH m,k>0;x,p,n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(1, len(not_in_ring_atoms) + 1): - for j in range(1, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts( - '[O;H1;!R;D1]{}-[N;H0,H1,H2;!R]{}-[O;H1;!R;D1]'.format('-[C;H0,H1,H2;!R]' * i, '-[C;H0,H1,H2;!R]' * j)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_008(mol): - """CHp-O-(CHn)m-OH m>2;p,n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(3, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts('[C;H0,H1,H2;!R][O;H0;!R;D2]{}-[O;H1;!R;D1]'.format('-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_009(mol): - """NH2-(CHn)m-NH2 m>2,n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(3, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts('[N;H2;!R;D1]{}-[N;H2;!R;D1]'.format('-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_010(mol): - """NHk-(CHn)m-NH2 m>2;n=0,1,2;k=0,1""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(3, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts('[N;H0,H1;!R]{}-[N;H2;!R;D1]'.format('-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_011(mol): - """SH-(CHn)m-SH m>2;n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(3, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts('[S;H1;!R;D1]{}-[S;H1;!R;D1]'.format('-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_012(mol): - """CN-(CHn)m-CN m>2;n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(3, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts('[C;H0;!R;D2](#[N;H0;!R;D1]){}-[C;H0;!R;D2](#[N;H0;!R;D1])'.format('-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_013(mol): - """COO-(CHn)m-OOC m>2;n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(3, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts( - '[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H0;!R;D2]{}-[O;H0;!R;D2]-[C;H0;!R;D3](=[O;H0;!R;D1])'.format( - '-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_014(mol): - """aC-(CHn=CHm)cyc (fused rings) m,n=0,1""" - query = Chem.MolFromSmarts('[c;H0;R2;D3]@&-[C;H1,H2;R]@&=[C;H1,H2;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_015(mol): - """aC-aC (different rings)""" - query = Chem.MolFromSmarts('[c;H0;R1;D3]!@&-[c;H0;R1;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_016(mol): - """aC-CHncyc (different rings) n=0,1""" - query = Chem.MolFromSmarts('[c;H0;R1;D3]!@&-[C;H0,H1;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_017(mol): - """aC-CHncyc (fused rings) n=0,1,2原文写的0,1应该是不对的""" - query = Chem.MolFromSmarts('[c;H0;R2;D3]@&-[C;H0,H1,H2;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_018(mol): - """aC-(CHn)m-aC m>1 n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(2, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts('[c;H0;R;D3]{}-[c;H0;R;D3]'.format('-[C;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_019(mol): - """aC-(CHn)m-CHcyc m>1 n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(1, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts('[c;H0;R;D3]{}-[C;H1;R;D3]'.format('-[C;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_020(mol): - """CHcyc-CHcyc (different rings)""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-&!@[C;H1;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_021(mol): - """CHcyc-(CHn)m-CHcyc (different rings) m>0, n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(1, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts('[C;H1;R;D3]{}!@&-[C;H1;R;D3]'.format('!@&-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_022(mol): - """CH multiring""" - query = Chem.MolFromSmarts('[C;H1;!R1&R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_023(mol): - """C multiring""" - query = Chem.MolFromSmarts('[C;H0;!R1&R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_024(mol): - """aC-CHm-aC (different rings) m=0,1,2""" - query = Chem.MolFromSmarts('[c;H0;R1]-[C;H0,H1,H2;!R]-[c;H0;R1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_025(mol): - """aC-(CHm=CHn)-aC (different rings) m,n=0,1,2""" - query = Chem.MolFromSmarts('[c;H0;R1;D3]-[C;H0,H1,H2;!R]=[C;H0,H1,H2;!R]-[c;H0;R1;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_026(mol): # todo 芳香性 - """(CHm=C)cyc-CH=CH-(C=CHn)cyc (different rings) m,n没有限制""" - query = Chem.MolFromSmarts('[C;R]=[C;H0;R]-[C;H1;!R]=[C;H1;!R]-[C;H0;R]=[C;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_027(mol): # todo 芳香性 - """(CHm=C)cyc-CHp-(C=CHn)cyc (different rings) m,n,p没有限制""" - query = Chem.MolFromSmarts('[C;R]=[C;H0;R]-[C;!R]-[C;H0;R]=[C;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_028(mol): - """aC-CO-aC (different rings)""" - query = Chem.MolFromSmarts('[c;H0;R1;D3]-[C;H0;!R;D3](=[O;H0;!R;D1])-[c;H0;R1;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_029(mol): - """aC-CHm-CO-aC (different rings) m=0,1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H0,H1,H2;!R]-[C;H0;!R;D3](=[O;H0;!R;D1])-[c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_030(mol): # todo 芳香性 - """aC-CO-(C=CHn)cyc (different rings)""" - query = Chem.MolFromSmarts('[c;H0;R1;D3]-[C;H0;!R;D3](=[O;H0;!R;D1])-[C;H0;R;D3]=[C;H0,H1;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_031(mol): - """aC-CO-CO-aC (different rings)""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H0;!R;D3](=[O;H0;!R;D1])-[C;H0;!R;D3](=[O;H0;!R;D1])-[c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_032(mol): - """aC-COcyc (fused rings)""" - query = Chem.MolFromSmarts('[c;H0;R2;D3]@&-[C;R;H0;D3](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_033(mol): - """aC-CO-(CHn)m-CO-aC (different rings) m>0;n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(1, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts( - '[c;H0;R;D3]-[C;H0;!R;D3](=[O;H0;!R;D1]){}-[C;H0;!R;D3](=[O;H0;!R;D1])-[c;H0;R;D3]'.format('-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_034(mol): - """aC-CO-CHn,cyc (different rings) n=0,1""" - query = Chem.MolFromSmarts('[c;H0;R1;D3]-[C;H0;!R;D3](=[O;H0;!R;D1])-[C;H0,H1;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_035(mol): - """aC-CO-NHn-aC (different rings) n=0,1""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H0;!R;D3](=[O;H0;!R;D1])-[N;H0,H1;!R]-[c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_036(mol): - """aC-NHnCONHm-aC (different rings) m,n=0,1""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H0,H1;!R]-[C;H0;!R;D3](=[O;H0;!R;D1])-[N;H0,H1;!R]-[c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_037(mol): - """aC-CO-Ncyc (different rings)""" - query = Chem.MolFromSmarts('[c;H0;R;D3]!@&-[C;H0;!R;D3](=[O;H0;!R;D1])!@&-[N;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_038(mol): - """aC-Scyc (fused rings)""" - query = Chem.MolFromSmarts('[c;H0;R2;D3]@[S,s;H0;D2;R1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_039(mol): - """aC-S-aC (different rings)""" - query = Chem.MolFromSmarts('[c;H0;R1;D3]!@&-[S;H0;D2;!R]!@&-[c;H0;R1;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_040(mol): # todo ? - """aC-POn-aC (different rings) n=0,1,2,3,4""" - query0 = Chem.MolFromSmarts('[c;H0;R1;D3]-[P;H0,H1;!R]-[c;H0;R1;D3]') - query1 = Chem.MolFromSmarts('[c;H0;R1;D3]-[O;H0;D2;!R]-[P;!R]-[O;H0;D2;!R]-[c;H0;R1;D3]') - match_list = mol.GetSubstructMatches(query0) + mol.GetSubstructMatches(query1) - return len(match_list), match_list - - -def t_041(mol): - """aC-SOn-aC (different rings) n=1,2,3,4""" - query1 = Chem.MolFromSmarts('[c;H0;R1;D3]-[S;H0;D3;!R](=[O;!R;H0;D1])-[c;H0;R1;D3]') - query2 = Chem.MolFromSmarts('[c;H0;R1;D3]-[S;H0;D4;!R](=[O;!R;H0;D1])(=[O;!R;H0;D1])-[c;H0;R1;D3]') - query3 = Chem.MolFromSmarts('[c;H0;R1;D3]-[O;H0;D2;!R]-[S;H0;D3;!R](=[O;!R;H0;D1])-[O;H0;D2;!R]-[c;H0;R1;D3]') - query4 = Chem.MolFromSmarts( - '[c;H0;R1;D3]-[O;H0;D2;!R]-[S;H0;D4;!R](=[O;!R;H0;D1])(=[O;!R;H0;D1])-[O;H0;D2;!R]-[c;H0;R1;D3]') - match_list = mol.GetSubstructMatches(query1) + mol.GetSubstructMatches(query2) + mol.GetSubstructMatches( - query3) + mol.GetSubstructMatches(query4) - return len(match_list), match_list - - -def t_042(mol): - """aC-NHncyc (fused rings) n=0,1""" - query = Chem.MolFromSmarts('[c;H0;R2;D3]@[N,n;H0,H1;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_043(mol): - """aC-NH-aC (different rings)""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H1;!R;D2]-[c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_044(mol): - """aC-(C=N)cyc (different rings)""" - query = Chem.MolFromSmarts('[c;H0;R;D3]!@&-[c,C;H0;R;D3]=,:[n,N;H0;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_045(mol): - """aC-(N=CHn)cyc (fused rings) n=0,1""" - # query = Chem.MolFromSmarts('[c][c;H0;R2]([c])-[N;H0;D2;R1]=[C;H0,H1;R]') - query = Chem.MolFromSmarts('[c;H0;R;D3]@[n,N;H0;R;D2]@,=,:[c,C;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_046(mol): - """aC-(N=CHn)cyc (fused rings) n=0,1""" - query = Chem.MolFromSmarts('[c;H0;R2;D3]@,=,:[c,C;H0,H1;R]@,=,:[n,N;H0;D2;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_047(mol): - """aC-O-CHn-aC (different rings) n=0,1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]!@,-[O;H0;!R;D2]!@,-[C;H0,H1,H2;!R]!@,-[c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_048(mol): - """aC-O-aC (different rings)""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[O;H0;!R;D2]-[c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_049(mol): - """aC-CHn-O-CHm-aC (different rings) m,n=0,1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H0,H1,H2;!R]-[O;H0;!R;D2]-[C;H0,H1,H2;!R]-[c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_050(mol): - """aC-Ocyc (fused rings)""" - query = Chem.MolFromSmarts('[c;H0;R2;D3]@[O,o;H0;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_051(mol): - """AROMFUSED[2]""" - query = Chem.MolFromSmarts('[c]:[c;R2](:[c]):[c]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_052(mol): - """AROMFUSED[2]s1""" - query = Chem.MolFromSmarts('[c;R2]([c;H1]):[c;R2]([c;H1])c([C,O,N,P,S,F,Cl,Br,I])[c;H1][c;H1][c;H1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_053(mol): - """AROMFUSED[2]s2""" - query = Chem.MolFromSmarts('[*]!@[c]1[c;H1][c;R2]([c])[c;R2]([c])[c;H1][c;H1]1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_054(mol): - """AROMFUSED[2]s2s3""" - query = Chem.MolFromSmarts('[*]!@[c]1[c](!@[*])[c;H1][c;R2]([c])[c;R2]([c])[c;H1]1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_055(mol): - """AROMFUSED[2]s1s4""" - query = Chem.MolFromSmarts('[c;H1]1[c](!@[*])[c;R2]([c])[c;R2]([c])[c](!@[*])[c;H1]1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_056(mol): - """AROMFUSED[2]s1s2""" - query = Chem.MolFromSmarts('[c;H1]1[c](!@[*])[c](!@[*])[c;R2]([c])[c;R2]([c])[c;H1]1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_057(mol): - """AROMFUSED[2]s1s3""" - query = Chem.MolFromSmarts('[c;H1]1[c](!@[*])[c;H1][c;R2]([c])[c;R2]([c])[c](!@[*])1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_058(mol): - """AROMFUSED[3]""" - query = Chem.MolFromSmarts('[c][c;R2]1[c;R3]([c])[c;R2]([c])ccc1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_059(mol): - """AROMFUSED[4a]""" - query = Chem.MolFromSmarts('[c;H1][c;R2]1[c;R2]([c])[c;H1][c;R2]([c])[c;R2]([c])[c;H1]1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_060(mol): - """AROMFUSED[4a]s1""" - query = Chem.MolFromSmarts('[*]!@[c]1[c;R2]([c])[c;R2]([c])[c;H1][c;R2]([c])[c;R2]1([c])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_061(mol): - """AROMFUSED[4a]s1s4""" - query = Chem.MolFromSmarts('[*]!@[c]1[c;R2]([c])[c;R2]([c])[c](!@[*])[c;R2]([c])[c;R2]1([c])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_062(mol): - """AROMFUSED[4p]""" - query = Chem.MolFromSmarts('[c;H1]1[c;!R1]([c])[c;!R1]([c])[c;!R1]([c])[c;!R1]([c])[c;H1]1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_063(mol): - """AROMFUSED[4p]s3s4""" - query = Chem.MolFromSmarts('[*]!@[c]1[c;!R1]([c])[c;!R1]([c])[c;!R1]([c])[c;!R1]([c])[c]1!@[*]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_064(mol): - """PYRIDINE.FUSED[2]""" - query = Chem.MolFromSmarts('[n;H0;R1;D2]1[c;H1;R1;D2][c;H1;R1;D2][c;H1;R1;D2][c;H0;R2;D3](a)[c;H0;R2;D3]1(a)') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_065(mol): - """PYRIDINE.FUSED[2-iso]""" - query = Chem.MolFromSmarts('[n;H0;R1;D2]1[c;H1;R1;D2][c;H1;R1;D2][c;H0;R2;D3](a)[c;H0;R2;D3](a)[c;H1;R1;D2]1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_066(mol): - """PYRIDINE.FUSED[4]""" - query = Chem.MolFromSmarts('[n;H0;R1;D2]1[c;H0;R2;D3](a)[c;H0;R2;D3](a)[c;H1;R1;D2][c;H0;R2;D3](a)[c;H0;R2;D3]1(a)') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_067(mol): - """aC-N-CHcyc(different rings)""" - query = Chem.MolFromSmarts('[c;H0;R;D3]!@&-[N]!@&-[C;H1;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_068(mol): - """N multiring""" - query = Chem.MolFromSmarts('[N;R&!R1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_069(mol): - """Ncyc-(CH2)3-Ncyc(different rings)""" - query = Chem.MolFromSmarts('[N;H0;R;D3]!@&-[C;H2][C;H2][C;H2]!@&-[N;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_070(mol): - """aC-COCH2CH2-aC(different rings)""" - query = Chem.MolFromSmarts('[c;H0;R;D3]!@&-[C;H0](=[O;H0;!R;D1])[C;H2][C;H2]!@&-[c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_071(mol): - """aC-O-(CH2)2-Ncyc(different rings)""" - query = Chem.MolFromSmarts('[c;H0;R;D3]!@&-[O;H0;D2]-[C;H2][C;H2]!@&-[N;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_072(mol): - """aC-CH(OH)(CH2)2-CHcyc(different rings)""" - query = Chem.MolFromSmarts('[c;H0;R;D3]!@&-[C;H1]([O;H1;!R;D1])-[C;H2][C;H2]!@&-[C;H1;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_073(mol): - """Ncyc-(CH2)2-CHcyc(different rings)""" - query = Chem.MolFromSmarts('[N;H0;R;D3]!@&-[C;H2][C;H2]!@&-[C;H1;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_074(mol): - """aC-CONHCH2-CHcyc(different rings)""" - query = Chem.MolFromSmarts('[c;H0;R;D3]!@&-[C;H0;D3](=[O;H0;!R;D1])[N;H1;D2][C;H2]!@&-[C;H1;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -class Counter: - def __init__(self): - self.loader = Loader() - - self.init_result = { - 'f_001': 0, 'f_002': 0, 'f_003': 0, 'f_004': 0, 'f_005': 0, 'f_006': 0, 'f_007': 0, 'f_008': 0, 'f_009': 0, - 'f_010': 0, 'f_011': 0, 'f_012': 0, 'f_013': 0, 'f_014': 0, 'f_015': 0, 'f_016': 0, 'f_017': 0, 'f_018': 0, - 'f_019': 0, 'f_020': 0, 'f_021': 0, 'f_022': 0, 'f_023': 0, 'f_024': 0, 'f_025': 0, 'f_026': 0, 'f_027': 0, - 'f_028': 0, 'f_029': 0, 'f_030': 0, 'f_031': 0, 'f_032': 0, 'f_033': 0, 'f_034': 0, 'f_035': 0, 'f_036': 0, - 'f_037': 0, 'f_038': 0, 'f_039': 0, 'f_040': 0, 'f_041': 0, 'f_042': 0, 'f_043': 0, 'f_044': 0, 'f_045': 0, - 'f_046': 0, 'f_047': 0, 'f_048': 0, 'f_049': 0, 'f_050': 0, 'f_051': 0, 'f_052': 0, 'f_053': 0, 'f_054': 0, - 'f_055': 0, 'f_056': 0, 'f_057': 0, 'f_058': 0, 'f_059': 0, 'f_060': 0, 'f_061': 0, 'f_062': 0, 'f_063': 0, - 'f_064': 0, 'f_065': 0, 'f_066': 0, 'f_067': 0, 'f_068': 0, 'f_069': 0, 'f_070': 0, 'f_071': 0, 'f_072': 0, - 'f_073': 0, 'f_074': 0, 'f_075': 0, 'f_076': 0, 'f_077': 0, 'f_078': 0, 'f_079': 0, 'f_080': 0, 'f_081': 0, - 'f_082': 0, 'f_083': 0, 'f_084': 0, 'f_085': 0, 'f_086': 0, 'f_087': 0, 'f_088': 0, 'f_089': 0, 'f_090': 0, - 'f_091': 0, 'f_092': 0, 'f_093': 0, 'f_094': 0, 'f_095': 0, 'f_096': 0, 'f_097': 0, 'f_098': 0, 'f_099': 0, - 'f_100': 0, 'f_101': 0, 'f_102': 0, 'f_103': 0, 'f_104': 0, 'f_105': 0, 'f_106': 0, 'f_107': 0, 'f_108': 0, - 'f_109': 0, 'f_110': 0, 'f_111': 0, 'f_112': 0, 'f_113': 0, 'f_114': 0, 'f_115': 0, 'f_116': 0, 'f_117': 0, - 'f_118': 0, 'f_119': 0, 'f_120': 0, 'f_121': 0, 'f_122': 0, 'f_123': 0, 'f_124': 0, 'f_125': 0, 'f_126': 0, - 'f_127': 0, 'f_128': 0, 'f_129': 0, 'f_130': 0, 'f_131': 0, 'f_132': 0, 'f_133': 0, 'f_134': 0, 'f_135': 0, - 'f_136': 0, 'f_137': 0, 'f_138': 0, 'f_139': 0, 'f_140': 0, 'f_141': 0, 'f_142': 0, 'f_143': 0, 'f_144': 0, - 'f_145': 0, 'f_146': 0, 'f_147': 0, 'f_148': 0, 'f_149': 0, 'f_150': 0, 'f_151': 0, 'f_152': 0, 'f_153': 0, - 'f_154': 0, 'f_155': 0, 'f_156': 0, 'f_157': 0, 'f_158': 0, 'f_159': 0, 'f_160': 0, 'f_161': 0, 'f_162': 0, - 'f_163': 0, 'f_164': 0, 'f_165': 0, 'f_166': 0, 'f_167': 0, 'f_168': 0, 'f_169': 0, 'f_170': 0, 'f_171': 0, - 'f_172': 0, 'f_173': 0, 'f_174': 0, 'f_175': 0, 'f_176': 0, 'f_177': 0, 'f_178': 0, 'f_179': 0, 'f_180': 0, - 'f_181': 0, 'f_182': 0, 'f_183': 0, 'f_184': 0, 'f_185': 0, 'f_186': 0, 'f_187': 0, 'f_188': 0, 'f_189': 0, - 'f_190': 0, 'f_191': 0, 'f_192': 0, 'f_193': 0, 'f_194': 0, 'f_195': 0, 'f_196': 0, 'f_197': 0, 'f_198': 0, - 'f_199': 0, 'f_200': 0, 'f_201': 0, 'f_202': 0, 'f_203': 0, 'f_204': 0, 'f_205': 0, 'f_206': 0, 'f_207': 0, - 'f_208': 0, 'f_209': 0, 'f_210': 0, 'f_211': 0, 'f_212': 0, 'f_213': 0, 'f_214': 0, 'f_215': 0, 'f_216': 0, - 'f_217': 0, 'f_218': 0, 'f_219': 0, 'f_220': 0, - - 's_001': 0, 's_002': 0, 's_003': 0, 's_004': 0, 's_005': 0, 's_006': 0, 's_007': 0, 's_008': 0, 's_009': 0, - 's_010': 0, 's_011': 0, 's_012': 0, 's_013': 0, 's_014': 0, 's_015': 0, 's_016': 0, 's_017': 0, 's_018': 0, - 's_019': 0, 's_020': 0, 's_021': 0, 's_022': 0, 's_023': 0, 's_024': 0, 's_025': 0, 's_026': 0, 's_027': 0, - 's_028': 0, 's_029': 0, 's_030': 0, 's_031': 0, 's_032': 0, 's_033': 0, 's_034': 0, 's_035': 0, 's_036': 0, - 's_037': 0, 's_038': 0, 's_039': 0, 's_040': 0, 's_041': 0, 's_042': 0, 's_043': 0, 's_044': 0, 's_045': 0, - 's_046': 0, 's_047': 0, 's_048': 0, 's_049': 0, 's_050': 0, 's_051': 0, 's_052': 0, 's_053': 0, 's_054': 0, - 's_055': 0, 's_056': 0, 's_057': 0, 's_058': 0, 's_059': 0, 's_060': 0, 's_061': 0, 's_062': 0, 's_063': 0, - 's_064': 0, 's_065': 0, 's_066': 0, 's_067': 0, 's_068': 0, 's_069': 0, 's_070': 0, 's_071': 0, 's_072': 0, - 's_073': 0, 's_074': 0, 's_075': 0, 's_076': 0, 's_077': 0, 's_078': 0, 's_079': 0, 's_080': 0, 's_081': 0, - 's_082': 0, 's_083': 0, 's_084': 0, 's_085': 0, 's_086': 0, 's_087': 0, 's_088': 0, 's_089': 0, 's_090': 0, - 's_091': 0, 's_092': 0, 's_093': 0, 's_094': 0, 's_095': 0, 's_096': 0, 's_097': 0, 's_098': 0, 's_099': 0, - 's_100': 0, 's_101': 0, 's_102': 0, 's_103': 0, 's_104': 0, 's_105': 0, 's_106': 0, 's_107': 0, 's_108': 0, - 's_109': 0, 's_110': 0, 's_111': 0, 's_112': 0, 's_113': 0, 's_114': 0, 's_115': 0, 's_116': 0, 's_117': 0, - 's_118': 0, 's_119': 0, 's_120': 0, 's_121': 0, 's_122': 0, 's_123': 0, 's_124': 0, 's_125': 0, 's_126': 0, - 's_127': 0, 's_128': 0, 's_129': 0, 's_130': 0, - - 't_001': 0, 't_002': 0, 't_003': 0, 't_004': 0, 't_005': 0, 't_006': 0, 't_007': 0, 't_008': 0, 't_009': 0, - 't_010': 0, 't_011': 0, 't_012': 0, 't_013': 0, 't_014': 0, 't_015': 0, 't_016': 0, 't_017': 0, 't_018': 0, - 't_019': 0, 't_020': 0, 't_021': 0, 't_022': 0, 't_023': 0, 't_024': 0, 't_025': 0, 't_026': 0, 't_027': 0, - 't_028': 0, 't_029': 0, 't_030': 0, 't_031': 0, 't_032': 0, 't_033': 0, 't_034': 0, 't_035': 0, 't_036': 0, - 't_037': 0, 't_038': 0, 't_039': 0, 't_040': 0, 't_041': 0, 't_042': 0, 't_043': 0, 't_044': 0, 't_045': 0, - 't_046': 0, 't_047': 0, 't_048': 0, 't_049': 0, 't_050': 0, 't_051': 0, 't_052': 0, 't_053': 0, 't_054': 0, - 't_055': 0, 't_056': 0, 't_057': 0, 't_058': 0, 't_059': 0, 't_060': 0, 't_061': 0, 't_062': 0, 't_063': 0, - 't_064': 0, 't_065': 0, 't_066': 0, 't_067': 0, 't_068': 0, 't_069': 0, 't_070': 0, 't_071': 0, 't_072': 0, - 't_073': 0, 't_074': 0, - } - self.result = None - - self.f_order_group_function = \ - [ - f_001, f_002, f_003, f_004, f_005, f_006, f_007, f_008, f_009, f_010, f_011, f_012, f_013, f_014, f_015, - f_016, f_017, f_018, f_019, f_020, f_021, f_022, f_023, f_024, f_025, f_026, f_027, f_028, f_029, f_030, - f_031, f_032, f_033, f_034, f_035, f_036, f_037, f_038, f_039, f_040, f_041, f_042, f_043, f_044, f_045, - f_046, f_047, f_048, f_049, f_050, f_051, f_052, f_053, f_054, f_055, f_056, f_057, f_058, f_059, f_060, - f_061, f_062, f_063, f_064, f_065, f_066, f_067, f_068, f_069, f_070, f_071, f_072, f_073, f_074, f_075, - f_076, f_077, f_078, f_079, f_080, f_081, f_082, f_083, f_084, f_085, f_086, f_087, f_088, f_089, f_090, - f_091, f_092, f_093, f_094, f_095, f_096, f_097, f_098, f_099, f_100, f_101, f_102, f_103, f_104, f_105, - f_106, f_107, f_108, f_109, f_110, f_111, f_112, f_113, f_114, f_115, f_116, f_117, f_118, f_119, f_120, - f_121, f_122, f_123, f_124, f_125, f_126, f_127, f_128, f_129, f_130, f_131, f_132, f_133, f_134, f_135, - f_136, f_137, f_138, f_139, f_140, f_141, f_142, f_143, f_144, f_145, f_146, f_147, f_148, f_149, f_150, - f_151, f_152, f_153, f_154, f_155, f_156, f_157, f_158, f_159, f_160, f_161, f_162, f_163, f_164, f_165, - f_166, f_167, f_168, f_169, f_170, f_171, f_172, f_173, f_174, f_175, f_176, f_177, f_178, f_179, f_180, - f_181, f_182, f_183, f_184, f_185, f_186, f_187, f_188, f_189, f_190, f_191, f_192, f_193, f_194, f_195, - f_196, f_197, f_198, f_199, f_200, f_201, f_202, f_203, f_204, f_205, f_206, f_207, f_208, f_209, f_210, - f_211, f_212, f_213, f_214, f_215, f_216, f_217, f_218, f_219, f_220, - ] - self.s_order_group_function = \ - [ - s_001, s_002, s_003, s_004, s_005, s_006, s_007, s_008, s_009, s_010, s_011, s_012, s_013, s_014, s_015, - s_016, s_017, s_018, s_019, s_020, s_021, s_022, s_023, s_024, s_025, s_026, s_027, s_028, s_029, s_030, - s_031, s_032, s_033, s_034, s_035, s_036, s_037, s_038, s_039, s_040, s_041, s_042, s_043, s_044, s_045, - s_046, s_047, s_048, s_049, s_050, s_051, s_052, s_053, s_054, s_055, s_056, s_057, s_058, s_059, s_060, - s_061, s_062, s_063, s_064, s_065, s_066, s_067, s_068, s_069, s_070, s_071, s_072, s_073, s_074, s_075, - s_076, s_077, s_078, s_079, s_080, s_081, s_082, s_083, s_084, s_085, s_086, s_087, s_088, s_089, s_090, - s_091, s_092, s_093, s_094, s_095, s_096, s_097, s_098, s_099, s_100, s_101, s_102, s_103, s_104, s_105, - s_106, s_107, s_108, s_109, s_110, s_111, s_112, s_113, s_114, s_115, s_116, s_117, s_118, s_119, s_120, - s_121, s_122, s_123, s_124, s_125, s_126, s_127, s_128, s_129, s_130, - ] - self.t_order_group_function = \ - [ - t_001, t_002, t_003, t_004, t_005, t_006, t_007, t_008, t_009, t_010, t_011, t_012, t_013, t_014, t_015, - t_016, t_017, t_018, t_019, t_020, t_021, t_022, t_023, t_024, t_025, t_026, t_027, t_028, t_029, t_030, - t_031, t_032, t_033, t_034, t_035, t_036, t_037, t_038, t_039, t_040, t_041, t_042, t_043, t_044, t_045, - t_046, t_047, t_048, t_049, t_050, t_051, t_052, t_053, t_054, t_055, t_056, t_057, t_058, t_059, t_060, - t_061, t_062, t_063, t_064, t_065, t_066, t_067, t_068, t_069, t_070, t_071, t_072, t_073, t_074, - ] - # basepath = os.path.abspath(__file__) - # folder = os.path.dirname(basepath) - # group_order_file_path = os.path.join(folder, 'group_order.xlsx') - # self.f_order_group_function_order = ( - # pd.read_excel(group_order_file_path, sheet_name='f')['index'] - 1).tolist() # 减1是为了基团序号和列表索引对上,因为python列表里的索引是从0开始的 - # self.s_order_group_function_order = ( - # pd.read_excel(group_order_file_path, sheet_name='s')['index'] - 1).tolist() # 减1是为了基团序号和列表索引对上 - # self.t_order_group_function_order = ( - # pd.read_excel(group_order_file_path, sheet_name='t')['index'] - 1).tolist() # 减1是为了基团序号和列表索引对上 - self.f_order_group_function_order, self.s_order_group_function_order, self.t_order_group_function_order = self.loader.load_group_order() - - def count_a_mol(self, mol, clear_mode=False, add_note=False, add_smiles=False): - init_smi = mol - try: - if isinstance(mol, str): - mol = Chem.MolFromSmiles(mol) - self.result = self.init_result.copy() - if add_note: - # self.result['note'] = '' - self.result['note'] = Chem.MolToSmiles(mol) - if add_smiles: - self.result['smiles'] = Chem.MolToSmiles(mol) - self.count_1st_order_groups(mol=mol, add_note=add_note) - self.count_2nd_order_groups(mol=mol) - self.count_3rd_order_groups(mol=mol) - if clear_mode: - # 清爽模式,不显示没有统计到的基团 - self.result = {k: v for k, v in self.result.items() if v} - return self.result - except: - print(f'Error! There is something wrong when counting {init_smi}, please check it.') - return self.init_result.copy() - - def count_mols(self, smiles_file_path, count_result_file_path='count_result.csv', add_note=False, add_smiles=False): # todo mpi 并行? - print('reading the input file...') - if smiles_file_path.endswith('.txt'): - smiles_iterator = list(open(smiles_file_path)) - elif smiles_file_path.endswith('.xlsx'): - smiles_iterator = pd.read_excel(smiles_file_path)['smiles'] - elif smiles_file_path.endswith('.csv'): - smiles_iterator = pd.read_csv(smiles_file_path)['smiles'] - else: - raise NotImplementedError( - 'ERROR: The file type cannot be read, use the.txt/.xlsx/.csv file as the input file.') - - mol_number = len(smiles_iterator) - print('Done, totally detected {} molecules, start counting...'.format(mol_number)) - count_result_dict_list = [] - for i in tqdm(smiles_iterator): - count_result_dict_list.append(self.count_a_mol(i, add_note=add_note, add_smiles=add_smiles)) - print('Done!') - print('writing to csv...') - result = pd.DataFrame(count_result_dict_list) - result.to_csv(count_result_file_path, index_label='index') - print('Done!') - return result - - def count_mols_mpi(self, smiles_file_path, count_result_file_path='count_result.csv', add_note=False, add_smiles=False, n_jobs=1, batch_size='auto'): - print('reading the input file...') - if smiles_file_path.endswith('.txt'): - smiles_iterator = list(open(smiles_file_path)) - elif smiles_file_path.endswith('.xlsx'): - smiles_iterator = pd.read_excel(smiles_file_path)['smiles'] - elif smiles_file_path.endswith('.csv'): - smiles_iterator = pd.read_csv(smiles_file_path)['smiles'] - else: - raise NotImplementedError( - 'ERROR: The file type cannot be read, use the.txt/.xlsx/.csv file as the input file.') - - mol_number = len(smiles_iterator) - print('Done, totally detected {} molecules, start counting...'.format(mol_number)) - task = [delayed(self.count_a_mol)(i, add_note=add_note, add_smiles=add_smiles) for i in smiles_iterator] - count_result_dict_list = Parallel(n_jobs=n_jobs, batch_size=batch_size)(task) - print('Done!') - print('writing to csv...') - result = pd.DataFrame(count_result_dict_list) - result.to_csv(count_result_file_path, index_label='index') - print('Done!') - return result - - def get_group_fingerprint(self, mol): - count_result = self.count_a_mol(mol=mol, clear_mode=False, add_note=False) - return [value for key, value in count_result.items()] - - def count_1st_order_groups(self, mol, add_note=False): - atoms_index = set([i for i in range(len(mol.GetAtoms()))]) # 所有原子序号的集合 - used_atoms_index = set() - for function_index in self.f_order_group_function_order: - function = self.f_order_group_function[function_index] - _, tuple_of_match_tuples = function(mol) - for matched_index in tuple_of_match_tuples: - if used_atoms_index.intersection(matched_index) == set(): - used_atoms_index = used_atoms_index.union(matched_index) - self.result[function.__name__] += 1 - if used_atoms_index == atoms_index: - # early stop 统计第一顺序基团时,当所有原子都被使用后提前停止 - break - - if used_atoms_index != atoms_index: - warning = 'WARING: For {}, The first order groups do not cover the whole molecule! The results may not be reliable!'.format( - Chem.MolToSmiles(mol)) - print(warning) - if add_note: - if self.result.get('note', 0): - self.result['note'] += '\t' - self.result['note'] += warning - else: - self.result['note'] = warning - return None - - def count_2nd_order_groups(self, mol): - s_order_group_in_mol = [] - for function_index in self.s_order_group_function_order: - function = self.s_order_group_function[function_index] - _, tuple_of_match_tuples = function(mol) - for matched_index in tuple_of_match_tuples: - matched_index = set(matched_index) - for used in s_order_group_in_mol: # for else 语句 - if matched_index.issubset(used): - break - else: - s_order_group_in_mol.append(matched_index) - self.result[function.__name__] += 1 - return None - - def count_3rd_order_groups(self, mol): # 跟count_2nd_order_groups几乎完全一样 - t_order_group_in_mol = [] - for function_index in self.t_order_group_function_order: - function = self.t_order_group_function[function_index] - _, tuple_of_match_tuples = function(mol) - for matched_index in tuple_of_match_tuples: - matched_index = set(matched_index) - for used in t_order_group_in_mol: # for else 语句 - if matched_index.issubset(used): - break - else: - t_order_group_in_mol.append(matched_index) - self.result[function.__name__] += 1 - return None - - -# if __name__ == '__main__': -# print('debug gp_counter.py ...') -# import time -# -# t1 = time.time() -# # m = Chem.MolFromSmiles('Cc1ncc[nH]1') -# c = Counter() -# # result = c.count_a_mol(m, clear_mode=True) -# # print(result) -# # -# # print(c.get_group_fingerprint(m)) -# # -# # # c.count_mols_mpi(smiles_file_path=os.path.join('gp_3x_test_mol', 'SMILES.txt'), count_result_file_path='count_result.csv', add_note=True, -# # # n_jobs=4, batch_size='auto') -# # # -# # # -# # # t2 = time.time() -# # # print(t2 - t1) -# # print(os.path.exists(r'C:\Users\tjulrc\Desktop\Groupy_test\groupy\gp_3x_internal_data\group_order.xlsx')) \ No newline at end of file diff --git a/build/lib/groupy/gp_3x_generator.py b/build/lib/groupy/gp_3x_generator.py deleted file mode 100644 index d60f6fd..0000000 --- a/build/lib/groupy/gp_3x_generator.py +++ /dev/null @@ -1,222 +0,0 @@ -from rdkit import Chem -import os -from tqdm import tqdm -from joblib import Parallel, delayed -import time -import random - -from groupy.gp_convertor import Convertor -from groupy.gp_tool import Tool - - -class Generator: - def __init__(self): - pass - - def calculate_charge(self, smi): - if isinstance(smi, str): - smi = Chem.MolFromSmiles(smi) - net_charge = 0 - all_charge = 0 - for i in smi.GetAtoms(): - net_charge += i.GetFormalCharge() - all_charge += i.GetAtomicNum() - - all_charge -= net_charge - - return net_charge, all_charge - - def calculate_multiplicity(self, smi): - if isinstance(smi, str): - smi = Chem.MolFromSmiles(smi) - net_charge, all_charge = self.calculate_charge(smi) - alpha_minus_beta = all_charge % 2 - multiplicity = alpha_minus_beta + 1 - return multiplicity - - def smi_to_gjf(self, smi, nproc='12', mem='12GB', chk_path=None, gjf_path=None, - gaussian_keywords=None, charge_and_multiplicity=None, - add_other_tasks=False, other_tasks:list=None): - try: - # default path of chk and gjf - if chk_path is None: - chk_path = '{}.chk'.format(smi) - if gjf_path is None: - gjf_path = '{}.gjf'.format(smi) - assert ('(' not in gjf_path) and (')' not in gjf_path), \ - 'gaussian dose not allow ( or ) in the name of .gjf and .chk files' - assert ('(' not in chk_path) and (')' not in chk_path), \ - 'gaussian dose not allow ( or ) in the name of .gjf and .chk files' - # default task - if gaussian_keywords is None: - gaussian_keywords = '#p opt freq b3lyp/6-31g*' - # default charge and multiplicity - if charge_and_multiplicity is None: - charge_and_multiplicity = f'{self.calculate_charge(smi)[0]} {self.calculate_multiplicity(smi)}' - # default other tasks - if other_tasks is None: - other_tasks = [ - '#p m062x/def2tzvp geom=check', - '#p m062x/def2tzvp scrf=solvent=water geom=check', - ] - - # read smi - c = Convertor() - temp_xyz_path = f'temp_{str(time.time()) + str(random.randint(0,1000000000000000000))}.xyz' - c.smi_to_xyz(smi=smi, xyz_path=temp_xyz_path) - - # write gjf - # 判断是否存在同名gjf - if os.path.exists(gjf_path): - os.remove(gjf_path) - self.write_gjf_link0_and_keyword(gjf_path=gjf_path, chk_path=chk_path, nproc=nproc, mem=mem, - gaussian_keywords=gaussian_keywords, - charge_and_multiplicity=charge_and_multiplicity, note=smi) - self.write_gjf_coord(gjf_path=gjf_path, xyz_path=temp_xyz_path) - - if add_other_tasks: - for task_index, task in enumerate(other_tasks): - i_chk_path = chk_path.split('.')[0] + f'_{task_index + 1}' + '.chk' - self.write_gjf_link0_and_keyword(gjf_path=gjf_path, chk_path=i_chk_path, nproc=nproc, mem=mem, - gaussian_keywords=task, charge_and_multiplicity=charge_and_multiplicity, - note=smi, old_chk_path=chk_path, add_link1=True) - self.write_gjf_blank_line(gjf_path=gjf_path, blank_line_number=2) - - # 删除临时xyz文件 - os.remove(temp_xyz_path) - return True - except: - print(f'Error! There is something wrong when converting {smi} to gjf file, please check it.') - return False - - def batch_smi_to_gjf(self, smiles_file_path, gjf_root_path=None, - nproc='12', mem='12GB', chk_path=None, gjf_path=None, - gaussian_keywords=None, charge_and_multiplicity=None, - add_other_tasks=False, other_tasks: list = None, - index_start=0, - ): - smiles_iterator = Tool.load_smiles_iterator(smiles_file_path=smiles_file_path) - mol_number = len(smiles_iterator) - zfill_number = len(str(mol_number)) + 5 - print('reading completed,A total of {} molecules detected, start calculating properties...'.format(mol_number)) - - # make gjf_root_path - if os.path.exists(gjf_root_path): - print('gjf_root_path "{}" has been detected!'.format(gjf_root_path)) - else: - print('gjf_root_path "{}" has not been detected, I will create it for you'.format(gjf_root_path)) - os.makedirs(gjf_root_path) - # end - - succeed = [] - fail = [] - for (index, smi) in tqdm(enumerate(smiles_iterator)): - smi = smi.strip() - index += index_start - chk_path = '{}.chk'.format(str(index).zfill(zfill_number)) - gjf_path = os.path.join(gjf_root_path, '{}.gjf'.format(str(index).zfill(zfill_number))) - generate_success_flag = self.smi_to_gjf(smi=smi, nproc=nproc, mem=mem, - chk_path=chk_path, gjf_path=gjf_path, - gaussian_keywords=gaussian_keywords, - charge_and_multiplicity=charge_and_multiplicity, - add_other_tasks=add_other_tasks, other_tasks=other_tasks, - ) - if not generate_success_flag: - fail.append(smi) - else: - succeed.append(smi) - - with open('gjf_fail.txt', 'w') as f: - for i in fail: - f.write(i + '\n') - with open('gjf_succeed.txt', 'w') as f: - for i in succeed: - f.write(i + '\n') - if len(fail) == 0: - print('done! all .gjf files has been saved in {}'.format(gjf_root_path)) - else: - print('Warning! The following SMILES fail to generate .gjf, please check...sorry(OTZ)') - print(fail) - return None - - def batch_smi_to_gjf_mpi(self, smiles_file_path, gjf_root_path=None, - nproc='12', mem='12GB', chk_path=None, gjf_path=None, - gaussian_keywords=None, charge_and_multiplicity=None, - add_other_tasks=False, other_tasks: list = None, - index_start=0, - n_jobs=1, batch_size='auto' - ): - smiles_iterator = Tool.load_smiles_iterator(smiles_file_path=smiles_file_path) - mol_number = len(smiles_iterator) - zfill_number = len(str(mol_number)) + 5 - print('reading completed,A total of {} molecules detected, start calculating properties...'.format(mol_number)) - - # make gjf_root_path - if os.path.exists(gjf_root_path): - print('gjf_root_path "{}" has been detected!'.format(gjf_root_path)) - else: - print('gjf_root_path "{}" has not been detected, I will create it for you'.format(gjf_root_path)) - os.makedirs(gjf_root_path) - - # task - task = [delayed(self.smi_to_gjf)(smi=smi, nproc=nproc, mem=mem, - chk_path='{}.chk'.format(str(index).zfill(zfill_number)), - gjf_path=os.path.join(gjf_root_path, '{}.gjf'.format(str(index).zfill(zfill_number))), - gaussian_keywords=gaussian_keywords, - charge_and_multiplicity=charge_and_multiplicity, - add_other_tasks=add_other_tasks, other_tasks=other_tasks,) - for index, smi in enumerate(smiles_iterator)] - result = Parallel(n_jobs=n_jobs, batch_size=batch_size)(task) - return result - - @staticmethod - def write_gjf_link0_and_keyword(gjf_path, chk_path, nproc, mem, gaussian_keywords, charge_and_multiplicity, note, - old_chk_path=None, add_link1=False): - with open(gjf_path, 'a') as gjf: - if add_link1: - gjf.write('--link1--' + '\n') - gjf.write(f'%nproc={nproc}' + '\n') - gjf.write(f'%mem={mem}' + '\n') - if old_chk_path is not None: - gjf.write(f'%oldchk={old_chk_path}' + '\n') - gjf.write(f'%chk={chk_path}' + '\n') - gjf.write(f'{gaussian_keywords}' + '\n') - gjf.write('\n') - gjf.write(f'{note}' + '\n') - gjf.write('\n') - gjf.write(f'{charge_and_multiplicity}' + '\n') - gjf.close() - return None - - @staticmethod - def write_gjf_coord(gjf_path, xyz_path): - xyz = open(xyz_path) - with open(gjf_path, 'a') as gjf: - for i in xyz.readlines()[2:]: - gjf.write(i) - gjf.write('\n\n') - return None - - @staticmethod - def write_gjf_blank_line(gjf_path, blank_line_number=1): - with open(gjf_path, 'a') as gjf: - gjf.write('\n' * blank_line_number) - return None - - - - - -# if __name__ == '__main__': -# -# import time -# g = Generator() -# t1 = time.time() -# # g.smi_to_gjf(smi='C1CCCC1', add_other_tasks=True) -# # g.batch_smi_to_gjf(smiles_file_path='gp_3x_test_mol/3018_with_error_smiles.txt', gjf_root_path='./test_gjf') -# g.batch_smi_to_gjf_mpi(smiles_file_path='gp_3x_test_mol/3018_with_error_smiles.txt', gjf_root_path='./test_gjf', -# add_other_tasks=True, -# n_jobs=8, batch_size='auto') -# -# t2 = time.time() -# print(t2 - t1) \ No newline at end of file diff --git a/build/lib/groupy/gp_3x_loader.py b/build/lib/groupy/gp_3x_loader.py deleted file mode 100644 index 13020aa..0000000 --- a/build/lib/groupy/gp_3x_loader.py +++ /dev/null @@ -1,72 +0,0 @@ -import pandas as pd -import os - - -class Loader: - def __init__(self, ): - basepath = os.path.abspath(__file__) - folder_path1 = os.path.dirname(basepath) - folder_path2 = os.path.join(folder_path1, '..', '..', '..', 'groupy_internal_data') - folder_path3 = os.path.join(folder_path1, '..', '..', '..', '..', 'groupy_internal_data') - - parameters_filename = os.path.join('group_contribution_parameters.xlsx') - group_order_filename = os.path.join('group_order.xlsx') - self.parameters_path = [ - os.path.join(folder_path1, parameters_filename), - os.path.join(folder_path2, parameters_filename), - os.path.join(folder_path3, parameters_filename), - ] - self.group_order_path = [ - os.path.join(folder_path1, group_order_filename), - os.path.join(folder_path2, group_order_filename), - os.path.join(folder_path3, group_order_filename) - ] - - def load_parameters(self, parameter_type='simultaneous', split=False): - assert parameter_type in ['simultaneous', 'step_wise'], '请确保参数类型为simultaneous或step_wise!' - for path in self.parameters_path: - try: - step_wise_first_order = pd.read_excel(path, sheet_name='{}_first_order'.format(parameter_type), index_col='index').T.to_dict() - step_wise_second_order = pd.read_excel(path, sheet_name='{}_second_order'.format(parameter_type), index_col='index').T.to_dict() - step_wise_third_order = pd.read_excel(path, sheet_name='{}_third_order'.format(parameter_type), index_col='index').T.to_dict() - step_wise_universal_constants = pd.read_excel(path, sheet_name='{}_constants'.format(parameter_type), index_col='index').T.to_dict() - break - except: - pass - else: - raise FileNotFoundError(f'Can not find group_contribution_parameters.xlsx in {self.parameters_path}') - - - if split: - return step_wise_first_order, step_wise_second_order, step_wise_third_order, step_wise_universal_constants - else: - return {**step_wise_first_order, **step_wise_second_order, **step_wise_third_order, **step_wise_universal_constants} - - def load_group_order(self): - for path in self.group_order_path: - try: - f_order_group_function_order = ( - pd.read_excel(path, sheet_name='f')[ - 'index'] - 1).tolist() # 减1是为了基团序号和列表索引对上,因为python列表里的索引是从0开始的 - s_order_group_function_order = ( - pd.read_excel(path, sheet_name='s')['index'] - 1).tolist() # 减1是为了基团序号和列表索引对上 - t_order_group_function_order = ( - pd.read_excel(path, sheet_name='t')['index'] - 1).tolist() # 减1是为了基团序号和列表索引对上 - break - except: - pass - else: - raise FileNotFoundError(f'Can not find group_order.xlsx in {self.parameters_path}') - return f_order_group_function_order, s_order_group_function_order, t_order_group_function_order - - -# if __name__ == '__main__': -# -# # debug -# loader = Loader() -# -# # d = loader.load_parameters() -# # for i in d: -# # print(i) -# # print(d[i]) - diff --git a/build/lib/groupy/gp_3x_tool.py b/build/lib/groupy/gp_3x_tool.py deleted file mode 100644 index a41bce3..0000000 --- a/build/lib/groupy/gp_3x_tool.py +++ /dev/null @@ -1,56 +0,0 @@ -import pandas as pd - - -class Tool: - def __init__(self): - pass - - def __repr__(self): - return 'This is a object implemented some useful tools' - - @staticmethod - def load_smiles_iterator(smiles_file_path): - print('reading input file...') - if smiles_file_path.endswith('.txt'): - smiles_iterator = list(open(smiles_file_path)) - elif smiles_file_path.endswith('.xlsx'): - smiles_iterator = pd.read_excel(smiles_file_path)['smiles'] - elif smiles_file_path.endswith('.csv'): - smiles_iterator = pd.read_csv(smiles_file_path)['smiles'] - else: - raise NotImplemented('无法识别的文件类型,请以.txt/.xlsx/.csv类型的文件作为输入。') - smiles_iterator = [i.strip() for i in smiles_iterator] - return smiles_iterator - - -def export_a_dict(result_dict, export_path='result.csv'): - df = pd.DataFrame([result_dict]) - df.to_csv(export_path, index_label='index') - return None - - -logo = \ -''' ---------------------------------------------------------------------------------- -Groupy -- A Useful Tool for Molecular Analysis -Developer: Ruichen Liu -Hint: Please feel easy to contact the developer if you have any problems in use. -E-mail1: liuruichen@tju.edu.cn -E-mail2: 1197748182@qq.com (may reply more quickly than E-mail1) ---------------------------------------------------------------------------------- -''' - - -# if __name__ == '__main__': -# print('debug gp_tool.py') -# -# t = Tool() -# -# t.smi_to_xyz('C1CCCC1') -# # t.batch_smi_to_xyz(smiles_file_path=r'gp_3x_test_mol\SMILES.txt', xyz_root_path='test_xyz') -# # t.convert_file_type(in_format='xyz', in_path='C1CCCC1.xyz', out_format='mol2') -# # t.batch_convert_file_type(in_format='xyz', in_root_path='test_xyz', out_format='mol2', out_root_path=None) -# # t.batch_convert_file_type(in_format='xyz', in_root_path='test_xyz', out_format='mol2', out_root_path='test_mol2') -# -# t.smi_to_gjf(smi='C1CCC1', add_other_std_tasks=True) -# t.batch_smi_to_gjf(smiles_file_path=r'gp_3x_test_mol\SMILES.txt', gjf_root_path='test_gjf', add_other_std_tasks=True) diff --git a/build/lib/groupy/gp_3x_viewer.py b/build/lib/groupy/gp_3x_viewer.py deleted file mode 100644 index e1b0740..0000000 --- a/build/lib/groupy/gp_3x_viewer.py +++ /dev/null @@ -1,96 +0,0 @@ -import ase -from ase.visualize import view -from ase.io import read -import os -from pprint import pprint - -from groupy.gp_convertor import Convertor - - -class Viewer: - def __init__(self): - pass - - def view_mol(self, mol, mol_type='smi'): - if isinstance(mol, str): - if mol_type in ['smi', 'smiles', 'SMILES']: - convertor = Convertor() - convertor.smi_to_xyz(smi=mol, xyz_path='temp.xyz') - mol = read(filename='temp.xyz', format='xyz') - os.remove('temp.xyz') - else: - try: - mol = read(filename=mol, format=mol_type) - except: - convertor = Convertor() - convertor.convert_file_type(in_format=mol_type, in_path=mol, out_format='xyz', out_path='temp.xyz') - mol = read(filename='temp.xyz', format='xyz') - os.remove('temp.xyz') - view(mol) - - def plot_supported_format(self): - ase_format = ase.io.formats.ioformats - openbabel_format = {'abinit': 'ABINIT Output Format', 'acesout': 'ACES output format', 'acr': 'ACR format', - 'adfband': 'ADF Band output format', 'adfdftb': 'ADF DFTB output format', 'adfout': 'ADF output format', - 'alc': 'Alchemy format', 'aoforce': 'Turbomole AOFORCE output format', - 'arc': 'Accelrys/MSI Biosym/Insight II CAR format', 'axsf': 'XCrySDen Structure Format', - 'bgf': 'MSI BGF format', 'box': 'Dock 3.5 Box format', 'bs': 'Ball and Stick format', - 'c09out': 'Crystal 09 output format', 'c3d1': 'Chem3D Cartesian 1 format', 'c3d2': 'Chem3D Cartesian 2 format', - 'caccrt': 'Cacao Cartesian format', 'can': 'Canonical SMILES format', - 'car': 'Accelrys/MSI Biosym/Insight II CAR format', 'castep': 'CASTEP format', 'ccc': 'CCC format', - 'cdjson': 'ChemDoodle JSON', 'cdx': 'ChemDraw binary format', 'cdxml': 'ChemDraw CDXML format', - 'cif': 'Crystallographic Information File', 'ck': 'ChemKin format', 'cml': 'Chemical Markup Language', - 'cmlr': 'CML Reaction format', 'cof': 'Culgi object file format', 'CONFIG': 'DL-POLY CONFIG', - 'CONTCAR': 'VASP format', 'CONTFF': 'MDFF format', 'crk2d': 'Chemical Resource Kit diagram(2D)', - 'crk3d': 'Chemical Resource Kit 3D format', 'ct': 'ChemDraw Connection Table format', - 'cub': 'Gaussian cube format', 'cube': 'Gaussian cube format', 'dallog': 'DALTON output format', - 'dalmol': 'DALTON input format', 'dat': 'Generic Output file format', 'dmol': 'DMol3 coordinates format', - 'dx': 'OpenDX cube format for APBS', 'ent': 'Protein Data Bank format', - 'exyz': 'Extended XYZ cartesian coordinates format', 'fa': 'FASTA format', 'fasta': 'FASTA format', - 'fch': 'Gaussian formatted checkpoint file format', 'fchk': 'Gaussian formatted checkpoint file format', - 'fck': 'Gaussian formatted checkpoint file format', 'feat': 'Feature format', 'fhiaims': 'FHIaims XYZ format', - 'fract': 'Free Form Fractional format', 'fs': 'Fastsearch format', 'fsa': 'FASTA format', - 'g03': 'Gaussian Output', 'g09': 'Gaussian Output', 'g16': 'Gaussian Output', - 'g92': 'Gaussian Output', 'g94': 'Gaussian Output', 'g98': 'Gaussian Output', 'gal': 'Gaussian Output', - 'gam': 'GAMESS Output', 'gamess': 'GAMESS Output', 'gamin': 'GAMESS Input', 'gamout': 'GAMESS Output', - 'got': 'GULP format', 'gpr': 'Ghemical format', 'gro': 'GRO format', 'gukin': 'GAMESS-UK Input', - 'gukout': 'GAMESS-UK Output', 'gzmat': 'Gaussian Z-Matrix Input', 'hin': 'HyperChem HIN format', - 'HISTORY': 'DL-POLY HISTORY', 'inchi': 'InChI format', 'inp': 'GAMESS Input', 'ins': 'ShelX format', - 'jin': 'Jaguar input format', 'jout': 'Jaguar output format', 'log': 'Generic Output file format', - 'lpmd': 'LPMD format', 'mcdl': 'MCDL format', 'mcif': 'Macromolecular Crystallographic Info', - 'MDFF': 'MDFF format', 'mdl': 'MDL MOL format', 'ml2': 'Sybyl Mol2 format', - 'mmcif': 'Macromolecular Crystallographic Info', 'mmd': 'MacroModel format', 'mmod': 'MacroModel format', - 'mol': 'MDL MOL format', 'mol2': 'Sybyl Mol2 format', 'mold': 'Molden format', 'molden': 'Molden format', - 'molf': 'Molden format', 'moo': 'MOPAC Output format', 'mop': 'MOPAC Cartesian format', - 'mopcrt': 'MOPAC Cartesian format', 'mopin': 'MOPAC Internal', 'mopout': 'MOPAC Output format', - 'mpc': 'MOPAC Cartesian format', 'mpo': 'Molpro output format', 'mpqc': 'MPQC output format', - 'mrv': 'Chemical Markup Language', 'msi': 'Accelrys/MSI Cerius II MSI format', 'nwo': 'NWChem output format', - 'orca': 'ORCA output format', 'out': 'Generic Output file format', 'outmol': 'DMol3 coordinates format', - 'output': 'Generic Output file format', 'pc': 'PubChem format', 'pcjson': 'PubChem JSON', - 'pcm': 'PCModel Format', 'pdb': 'Protein Data Bank format', 'pdbqt': 'AutoDock PDBQT format', - 'png': 'PNG 2D depiction', 'pos': 'POS cartesian coordinates format', 'POSCAR': 'VASP format', - 'POSFF': 'MDFF format', 'pqr': 'PQR format', 'pqs': 'Parallel Quantum Solutions format', - 'prep': 'Amber Prep format', 'pwscf': 'PWscf format', 'qcout': 'Q-Chem output format', 'res': 'ShelX format', - 'rsmi': 'Reaction SMILES format', 'rxn': 'MDL RXN format', 'sd': 'MDL MOL format', 'sdf': 'MDL MOL format', - 'siesta': 'SIESTA format', 'smi': 'SMILES format', 'smiles': 'SMILES format', - 'smy': 'SMILES format using Smiley parser', 'sy2': 'Sybyl Mol2 format', 't41': 'ADF TAPE41 format', - 'tdd': 'Thermo format', 'text': 'Read and write raw text', 'therm': 'Thermo format', - 'tmol': 'TurboMole Coordinate format', 'txt': 'Title format', 'txyz': 'Tinker XYZ format', - 'unixyz': 'UniChem XYZ format', 'VASP': 'VASP format', 'vmol': 'ViewMol format', - 'wln': 'Wiswesser Line Notation', 'xml': 'General XML format', 'xsf': 'XCrySDen Structure Format', - 'xyz': 'XYZ cartesian coordinates format', 'yob': 'YASARA.org YOB format'} - print('-' * 40) - pprint('ASE format:') - pprint(ase_format) - print('-' * 40) - pprint('OpenBabel format:') - pprint(openbabel_format) - return None - - - -# if __name__ == '__main__': -# -# viewer = Viewer() -# viewer.view_mol('gp_3x_test_mol/test_xyz/000000.mol2', mol_type='mol2') -# viewer.plot_supported_format() \ No newline at end of file diff --git a/build/lib/groupy/groupy.py b/build/lib/groupy/groupy.py deleted file mode 100644 index 8e976c0..0000000 --- a/build/lib/groupy/groupy.py +++ /dev/null @@ -1,559 +0,0 @@ -from rdkit import Chem - -from gp_3x_calculator import Calculator -from gp_3x_counter import Counter -from gp_3x_viewer import Viewer -from gp_3x_convertor import Convertor -from gp_3x_generator import Generator -from gp_3x_tool import Tool, export_a_dict, logo - - -def main_function_5(): - - while True: - flag_file = input( - '\n' - '--------------------------------------------------------------------------------- \n' - 'You are in main function 5 \n' - 'what to do? \n' - ' 0. return to main interface. \n' - ' 1. generate a .xyz file by input SMILES of a molecule. \n' - ' 2. generate a batch of .xyz files. -2. use mpi to accelerate.\n' - ' 3. convert a file to other format (e.g. xyz, mol, mol2, pdb...) \n' - ' 4. convert a batch of file to other format. -4. use mpi to accelerate.\n' - ' 5. generate .gjf(input file of gaussian) file by input SMILES of a molecule. \n' - ' 6. generate a batch of .gjf files. -6. use mpi to accelerate. \n' - '--------------------------------------------------------------------------------- \n' - ) - - - if flag_file == '0': - break - - elif flag_file == '1': - sub_function_1_of_main_function_5() - - elif flag_file == '2': - sub_function_2_of_main_function_5() - - elif flag_file == '-2': - sub_function_minus_2_of_main_function_5() - - elif flag_file == '3': - sub_function_3_of_main_function_5() - - elif flag_file == '4': - sub_function_4_of_main_function_5() - - elif flag_file == '-4': - sub_function_minus_4_of_main_function_5() - - elif flag_file == '5': - sub_function_5_of_main_function_5() - - elif flag_file == '6': - sub_function_6_of_main_function_5() - - elif flag_file == '-6': - sub_function_minus_6_of_main_function_5() - - return None - - -def sub_function_1_of_main_function_5(): - """ - 1. generate a .xyz file by input SMILES of a molecule. - """ - smiles = input('input the SMILES of a molecule. \n') - smiles = smiles.strip() - xyz_file = input( - 'please input the path of output .xyz file. If press Enter directly, {}.xyz will be used\n'.format(smiles)) - if not xyz_file: - xyz_file = '{}.xyz'.format(smiles) - convertor = Convertor() - convertor.smi_to_xyz(smi=smiles, xyz_path=xyz_file) - print('Done!') - print('\n\n\n') - return None - - -def sub_function_2_of_main_function_5(): - """ - 2. generate a batch of .xyz files. - """ - smiles_file_path = input('input a filepath of a file in which save SMILES of molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - xyz_root_path = input('input the root path of output xyz files, that is, ' - 'all the output xyz files will be make in this path. e.g. test_xyz \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n') - convertor = Convertor() - convertor.batch_smi_to_xyz(smiles_file_path=smiles_file_path, xyz_root_path=xyz_root_path) - print('\n\n\n') - return None - - -def sub_function_minus_2_of_main_function_5(): - """ - 2. generate a batch of .xyz files. use mpi - """ - smiles_file_path = input('input a filepath of a file in which save SMILES of molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - xyz_root_path = input('input the root path of output xyz files, that is, ' - 'all the output xyz files will be make in this path. e.g. test_xyz \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n') - n_jobs = int(input('input number of cores to use. e.g. 4 \n')) - batch_size = input('input batch size for task decomposition. e.g. 20, you can also enter "auto" \n') - try: - batch_size = int(batch_size) - except: - pass - convertor = Convertor() - convertor.batch_smi_to_xyz_mpi(smiles_file_path=smiles_file_path, xyz_root_path=xyz_root_path, - n_jobs=n_jobs, batch_size=batch_size) - print('\n\n\n') - return None - - -def sub_function_3_of_main_function_5(): - """ - 3. convert a file to other format (e.g. xyz, mol, mol2, pdb...) - """ - in_format = input('please input the format of your input file (e.g. xyz, pdb...) \n') - in_path = input('please input the path of input file, e.g. C1CCC1.xyz \n') - out_format = input('please input the format of output file you want (e.g. xyz, mol2...) \n') - out_path = input('please input the path of output file, e.g C1CCC1.mol2. \n') - convertor = Convertor() - convertor.convert_file_type(in_format=in_format, in_path=in_path, out_format=out_format, out_path=out_path) - print('Done!') - print('\n\n\n') - - -def sub_function_4_of_main_function_5(): - """ - 4. convert a batch of file to other format. - """ - in_format = input('please input the format of your input file (e.g. xyz, pdb...) \n') - in_root_path = input('please input the root path of input files, that is, ' - 'all input files you want to convert should be in there.' - 'e.g. test_xyz \n') - out_format = input('please input the format of output file you want (e.g. xyz, mol2...) \n') - default_out_root_path = in_root_path - out_root_path = input('please input the root path of output file, that is, ' - 'all the output files will be saved in there\n' - 'If press Enter directly, {} will be used \n'.format(default_out_root_path)) - if not out_root_path: - out_root_path = default_out_root_path - convertor = Convertor() - convertor.batch_convert_file_type(in_format=in_format, in_root_path=in_root_path, - out_format=out_format, out_root_path=out_root_path) - print('Done!') - print('\n\n\n') - return None - - -def sub_function_minus_4_of_main_function_5(): - """ - -4. convert a batch of file to other format. use mpi. - """ - in_format = input('please input the format of your input file (e.g. xyz, pdb...) \n') - in_root_path = input('please input the root path of input files, that is, ' - 'all input files you want to convert should be in there.' - 'e.g. test_xyz \n') - out_format = input('please input the format of output file you want (e.g. xyz, mol2...) \n') - default_out_root_path = in_root_path - out_root_path = input('please input the root path of output file, that is, ' - 'all the output files will be saved in there\n' - 'If press Enter directly, {} will be used \n'.format(default_out_root_path)) - if not out_root_path: - out_root_path = default_out_root_path - n_jobs = int(input('input number of cores to use. e.g. 4 \n')) - batch_size = input('input batch size for task decomposition. e.g. 20, you can also enter "auto" \n') - try: - batch_size = int(batch_size) - except: - pass - convertor = Convertor() - convertor.batch_convert_file_type_mpi(in_format=in_format, in_root_path=in_root_path, - out_format=out_format, out_root_path=out_root_path, - n_jobs=n_jobs, batch_size=batch_size) - print('Done!') - print('\n\n\n') - return None - - -def sub_function_5_of_main_function_5(): - """ - 5. generate .gjf(input file of gaussian) file by input SMILES of a molecule. - """ - smiles = input('input the SMILES of a molecule. \n') - smiles = smiles.strip() - - nproc = input('input the CPU cores you want to use. e.g. 12 \n') - if not nproc: - nproc = '12' - - mem = input('input the memory you want to use. e.g. 12GB \n') - if not mem: - mem = '12GB' - - chk_path = input('input the path of chk file. e.g. Cc1ccccc1.chk \n' - 'Hint1: If press Enter directly, {}.chk will be used. \n' - 'Hint2: Attention please! the symbol such as (, ), /, \\ and # should not appear in a filepath! \n' - .format(smiles)) - if not chk_path: - chk_path = '{}.chk'.format(smiles) - - gjf_path = input('input the path of gjf file. e.g. Cc1ccccc1.gjf \n' - 'Hint1: If press Enter directly, {}.gjf will be used. \n' - 'Hint2: Attention please! the symbol such as (, ), /, \\ and # should not appear in a filepath! \n' - .format(smiles)) - if not gjf_path: - gjf_path = '{}.gjf'.format(smiles) - - gaussian_keywords = input('input the keywords of Gaussian to define task you want to run.' - 'e.g. #p opt freq b3lyp/6-31g* \n' - 'Hint1: if press Enter directly, "#p opt freq b3lyp/6-31g*" will be used. \n') - if not gaussian_keywords: - gaussian_keywords = '#p opt freq b3lyp/6-31g*' - - charge_and_multiplicity = input('Input charge and multiplicity. e.g. 0 1 \n' - 'Hint: If press Enter directly, Groupy will automatically calculate them') - if not charge_and_multiplicity: - charge_and_multiplicity = None - - add_other_tasks = input('Weather to add some other tasks in this .gjf (y/n). \n') - if add_other_tasks in ['n', 'no', 'N']: - add_other_tasks = False - other_tasks = None - else: - other_tasks = input('Input keywords you want to add. ' - 'If there are more than one other tasks, Please separate them with commas (,) \n' - 'Hint: if press Enter directly, "#p m062x/def2tzvp geom=check,#p m062x/def2tzvp scrf=solvent=water geom=check" will be used \n') - if not other_tasks: - other_tasks = None - else: - other_tasks = other_tasks.split(',') - - generator = Generator() - generator.smi_to_gjf(smi=smiles, nproc=nproc, mem=mem, gaussian_keywords=gaussian_keywords, - charge_and_multiplicity=charge_and_multiplicity, - chk_path=chk_path, gjf_path=gjf_path, - add_other_tasks=add_other_tasks, other_tasks=other_tasks) - print('Done!') - print('\n\n\n') - return None - - -def sub_function_6_of_main_function_5(): - smiles_file_path = input('input the filepath of a file in which save molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - - gjf_root_path = input('Input the root path of output gjf files, that is, ' - 'all the output gjf files will be make in this path. e.g. test_gjf \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: if press Enter directly, test_gjf will be used. \n') - if not gjf_root_path: - gjf_root_path = 'test_gjf' - - nproc = input('input the CPU cores you want to use. e.g. 12 \n') - if not nproc: - nproc = '12' - - mem = input('input the memory you want to use. e.g. 12GB \n') - if not mem: - mem = '12GB' - - gaussian_keywords = input('input the keywords of Gaussian to define task you want to run.' - 'e.g. #p opt freq b3lyp/6-31g* \n' - 'Hint1: if press Enter directly, "#p opt freq b3lyp/6-31g*" will be used. \n') - if not gaussian_keywords: - gaussian_keywords = '#p opt freq b3lyp/6-31g*' - - charge_and_multiplicity = input('Input charge and multiplicity. e.g. 0 1 \n' - 'Hint: If press Enter directly, Groupy will automatically calculate them') - if not charge_and_multiplicity: - charge_and_multiplicity = None - - add_other_tasks = input('Weather to add some other tasks in this .gjf (y/n). \n') - if add_other_tasks in ['n', 'no', 'N']: - add_other_tasks = False - other_tasks = None - else: - other_tasks = input('Input keywords you want to add. ' - 'If there are more than one other tasks, Please separate them with commas (,) \n' - 'Hint: if press Enter directly, "#p m062x/def2tzvp geom=check,#p m062x/def2tzvp scrf=solvent=water geom=check" will be used \n') - if not other_tasks: - other_tasks = None - else: - other_tasks = other_tasks.split(',') - - generator = Generator() - generator.batch_smi_to_gjf(smiles_file_path=smiles_file_path, gjf_root_path=gjf_root_path, - nproc=nproc, mem=mem, gaussian_keywords=gaussian_keywords, - charge_and_multiplicity=charge_and_multiplicity, - add_other_tasks=add_other_tasks, other_tasks=other_tasks) - print('\n\n\n') - return None - - -def sub_function_minus_6_of_main_function_5(): - smiles_file_path = input('input the filepath of a file in which save molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - - gjf_root_path = input('Input the root path of output gjf files, that is, ' - 'all the output gjf files will be make in this path. e.g. test_gjf \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: if press Enter directly, test_gjf will be used. \n') - if not gjf_root_path: - gjf_root_path = 'test_gjf' - - nproc = input('input the CPU cores you want to use. e.g. 12 \n') - if not nproc: - nproc = '12' - - mem = input('input the memory you want to use. e.g. 12GB \n') - if not mem: - mem = '12GB' - - gaussian_keywords = input('input the keywords of Gaussian to define task you want to run.' - 'e.g. #p opt freq b3lyp/6-31g* \n' - 'Hint1: if press Enter directly, "#p opt freq b3lyp/6-31g*" will be used. \n') - if not gaussian_keywords: - gaussian_keywords = '#p opt freq b3lyp/6-31g*' - - charge_and_multiplicity = input('Input charge and multiplicity. e.g. 0 1 \n' - 'Hint: If press Enter directly, Groupy will automatically calculate them') - if not charge_and_multiplicity: - charge_and_multiplicity = None - - add_other_tasks = input('Weather to add some other tasks in this .gjf (y/n). \n') - if add_other_tasks in ['n', 'no', 'N']: - add_other_tasks = False - other_tasks = None - else: - other_tasks = input('Input keywords you want to add. ' - 'If there are more than one other tasks, Please separate them with commas (,) \n' - 'Hint: if press Enter directly, "#p m062x/def2tzvp geom=check,#p m062x/def2tzvp scrf=solvent=water geom=check" will be used \n') - if not other_tasks: - other_tasks = None - else: - other_tasks = other_tasks.split(',') - - n_jobs = int(input('input number of cores to use. e.g. 4 \n')) - batch_size = input('input batch size for task decomposition. e.g. 20, you can also enter "auto" \n') - try: - batch_size = int(batch_size) - except: - pass - - generator = Generator() - generator.batch_smi_to_gjf_mpi(smiles_file_path=smiles_file_path, gjf_root_path=gjf_root_path, - nproc=nproc, mem=mem, gaussian_keywords=gaussian_keywords, - charge_and_multiplicity=charge_and_multiplicity, - add_other_tasks=add_other_tasks, other_tasks=other_tasks, - n_jobs=n_jobs, batch_size=batch_size) - print('\n\n\n') - return None - - -def main_function_view(): - viewer = Viewer() - show_flag = input('show a SMILES (enter 1) or file (enter 2). \n(enter help to show supported file formats)\n') - if show_flag in ['1', 'SMILES', 'smiles']: - smiles = input('input the SMILES of a molecule. \n') - viewer.view_mol(mol=smiles, mol_type='smi') - - elif show_flag in ['2', 'file', 'FILE']: - file_path = input('input the file path you want to show. e.g. ./temporary.xyz \n') - file_type = input('input file format. e.g. xyz \n') - viewer.view_mol(mol=file_path, mol_type=file_type) - - elif show_flag in ['h', 'help', 'H', 'Help', 'HELP']: - viewer.plot_supported_format() - - else: - print('Unrecognized command!') - return None - - -def main_function_1(): - """ - 1. calculate properties of a molecule by input SMILES of this molecule. - """ - smiles = input('input the SMILES of a molecule. \n') - smiles = smiles.strip() - calculator = Calculator() - result = calculator.calculate_a_mol(smiles, debug=False) - print(result) - export_flag = input('Do you want to export results to a csv file? (y/n) \n') - if export_flag in ['y', 'Y', '1']: - export_a_dict(result_dict=result, export_path='{}_calculate.csv'.format(smiles)) - print('the results have been export to {}_calculate.csv! \n\n'.format(smiles)) - else: - print('\n\n\n') - return None - - -def main_function_2(): - """ - 2. count group number of a molecule by input SMILES of this molecule. - """ - smiles = input('input the SMILES of a molecule. \n') - smiles = smiles.strip() - mol = Chem.MolFromSmiles(smiles) - counter = Counter() - clear_mode_flag = input('clear mode? (y/n) \n') - if clear_mode_flag in ['y', 'Y', '1']: - result = counter.count_a_mol(mol, clear_mode=True) - else: - result = counter.count_a_mol(mol, clear_mode=False) - print(result) - export_flag = input('Do you want to export results to a file? (y/n) \n') - if export_flag in ['y', 'Y', '1']: - export_a_dict(result_dict=result, export_path='{}_count.csv'.format(smiles)) - print('the results have been export to {}_count.csv! \n\n'.format(smiles)) - else: - print('\n\n\n') - return None - - -def main_function_3(): - """ - 3. calculate properties of a batch of molecules by input filepath of a file in which save molecules (.txt, .csv, .xlsx). - """ - smiles_file_path = input('input the filepath of a file (.txt, .csv, .xlsx) in which save molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - calculator = Calculator() - calculator.calculate_mols(smiles_file_path=smiles_file_path, - properties_file_path='batch_calculate_results.csv') - print('\n\n\n') - return None - - -def main_function_minus_3(): - """ - -3. calculate properties of a batch of molecules by input filepath of a file in which save molecules (.txt, .csv, .xlsx). - use mpi - """ - smiles_file_path = input('input the filepath of a file (.txt, .csv, .xlsx) in which save molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - n_jobs = int(input('input number of cores to use. e.g. 4 \n')) - batch_size = input('input batch size for task decomposition. e.g. 20, you can also enter "auto" \n') - try: - batch_size = int(batch_size) - except: - pass - calculator = Calculator() - calculator.calculate_mols_mpi(smiles_file_path=smiles_file_path, - properties_file_path='batch_calculate_results_mpi.csv', - n_jobs=n_jobs, batch_size=batch_size) - print('\n\n\n') - return None - - -def main_function_4(): - """ - 4. count group number of a batch of molecules by input filepath of a file in which save molecules (.txt, .csv, .xlsx). - """ - smiles_file_path = input('input the filepath of a file in which save molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - counter = Counter() - counter.count_mols(smiles_file_path=smiles_file_path, - count_result_file_path='batch_count_result.csv', add_note=True, add_smiles=True) - print('\n\n\n') - return None - - -def main_function_minus_4(): - """ - -4. count group number of a batch of molecules by input filepath of a file in which save molecules (.txt, .csv, .xlsx). - use mpi - """ - smiles_file_path = input('input the filepath of a file (.txt, .csv, .xlsx) in which save molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - n_jobs = int(input('input number of cores to use. e.g. 4 \n')) - batch_size = input('input batch size for task decomposition. e.g. 20, you can also enter "auto" \n') - try: - batch_size = int(batch_size) - except: - pass - counter = Counter() - counter.count_mols_mpi(smiles_file_path=smiles_file_path, - count_result_file_path='batch_count_result_mpi.csv', add_note=True, add_smiles=True, - n_jobs=n_jobs, batch_size=batch_size) - print('\n\n\n') - return None - - -def main(): - print(logo) - - while True: - - flag_main = input( - '\n' - '--------------------------------------------------------------------------------- \n' - 'You are in main interface \n' - 'what to do? \n' - ' q. exit \n' - ' 0. show molecular structure by SMILES or file. \n' - ' 1. calculate properties of a molecule. \n' - ' 2. count group number of a molecule. \n' - ' 3. calculate properties of a batch of molecules. -3. use mpi to accelerate.\n' - ' 4. count group number of a batch of molecules. -4. use mpi to accelerate.\n' - ' 5. generate files or covert file format for MD, DFT, Visualization... \n' - '--------------------------------------------------------------------------------- \n' - ) - - if flag_main == 'q': - print('exit Groupy, have a nice day!') - break - - elif flag_main == '0': - main_function_view() - - elif flag_main == '1': - main_function_1() - - elif flag_main == '2': - main_function_2() - - elif flag_main == '3': - main_function_3() - - elif flag_main == '-3': - main_function_minus_3() - - elif flag_main == '4': - main_function_4() - - elif flag_main == '-4': - main_function_minus_4() - - elif flag_main == '5': - main_function_5() - - else: - print('Please input the right option. ' - 'For more information, you are supposed to read the manual.') - - -if __name__ == '__main__': - - main() diff --git a/build/lib/src/__init__.py b/build/lib/src/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/build/lib/src/gp_3x_calculator.py b/build/lib/src/gp_3x_calculator.py deleted file mode 100644 index 68dbecf..0000000 --- a/build/lib/src/gp_3x_calculator.py +++ /dev/null @@ -1,288 +0,0 @@ -from rdkit import Chem -from math import log -import pandas as pd -from tqdm import tqdm -from joblib import Parallel, delayed - -from gp_3x_loader import Loader -from gp_3x_counter import Counter - - -class Calculator: - def __init__(self): - self.loader = Loader() - self.counter = Counter() - self.parameters_step_wise = self.loader.load_parameters(parameter_type='step_wise', split=False) - self.parameters_simultaneous = self.loader.load_parameters(parameter_type='simultaneous', split=False) - - def __repr__(self): - return '这是一个使用基团贡献法计算分子性质的计算器' - - @staticmethod - def Tm(group_number, parameters): - right_side_eq = 0.00 - for i in group_number: # 这样写是取了字典的键 - right_side_eq += group_number[i] * parameters[i]['Tm'] - Tm = parameters[1]['Tm0'] * log(max(right_side_eq, 1.0)) # 这里的1.0是为了上计算结果小于0K的都设置为0K - return round(Tm, 3) - - @staticmethod - def Tb(group_number, parameters): - right_side_eq = 0.00 - for i in group_number: - right_side_eq += group_number[i] * parameters[i]['Tb'] - Tb = parameters[1]['Tb0'] * log(max(right_side_eq, 1.0)) # 这里的1.0是为了上计算结果小于0K的都设置为0K - return round(Tb, 3) - - @staticmethod - def Tc(group_number, parameters): - right_side_eq = 0.00 - for i in group_number: - right_side_eq += group_number[i] * parameters[i]['Tc'] - Tc = parameters[1]['Tc0'] * log(max(right_side_eq, 1.0)) # 这里的1.0是为了上计算结果小于0K的都设置为0K - return round(Tc, 3) - - @staticmethod - def Pc(group_number, parameters): - right_side_eq = 0.00 - for i in group_number: - right_side_eq += group_number[i] * parameters[i]['Pc'] - Pc = parameters[1]['Pc1'] + (right_side_eq + parameters[1]['Pc2']) ** -2 - return round(Pc, 4) - - @staticmethod - def Vc(group_number, parameters): - right_side_eq = 0.00 - for i in group_number: - right_side_eq += group_number[i] * parameters[i]['Vc'] - Vc = parameters[1]['Vc0'] + right_side_eq - return round(Vc, 2) - - @staticmethod - def delta_Gf(group_number, parameters): - right_side_eq = 0.00 - for i in group_number: - right_side_eq += group_number[i] * parameters[i]['Gf'] - delta_Gf = parameters[1]['Gf0'] + right_side_eq - return round(delta_Gf, 3) - - @staticmethod - def delta_Hf(group_number, parameters): - right_side_eq = 0.00 - for i in group_number: - right_side_eq += group_number[i] * parameters[i]['Hf'] - delta_Hf = parameters[1]['Hf0'] + right_side_eq - return round(delta_Hf, 3) - - @staticmethod - def delta_Hv(group_number, parameters): - right_side_eq = 0.00 - for i in group_number: - right_side_eq += group_number[i] * parameters[i]['Hv'] - delta_Hv = parameters[1]['Hv0'] + right_side_eq - return round(delta_Hv, 3) - - @staticmethod - def delta_Hfus(group_number, parameters): - right_side_eq = 0.00 - for i in group_number: - right_side_eq += group_number[i] * parameters[i]['Hfus'] - delta_Hfus = parameters[1]['Hfus0'] + right_side_eq - return round(delta_Hfus, 3) - - @staticmethod - def flash_point(group_number, parameters): - right_side_eq = 0.00 - for i in group_number: - right_side_eq += group_number[i] * parameters[i]['Fp'] - Fp = parameters[1]['Fp0'] + right_side_eq - return round(Fp, 3) - - @staticmethod - def molar_volume(group_number, parameters): - right_side_eq = 0.00 - for i in group_number: - right_side_eq += group_number[i] * parameters[i]['Vm'] - Vm = parameters[1]['Vm0'] + right_side_eq - return round(Vm, 3) - - @staticmethod - def density(molar_mass, Vs): - return round(molar_mass / (1000 * Vs), 3) - - @staticmethod - def delta_Hc(C_number, H_number, delta_Hf): - delta_Hc = -(-395.51 * C_number - 241.83 * H_number / 2 - delta_Hf) - return round(delta_Hc, 3) - - @staticmethod - def q(delta_Hc, molar_mass): - return round(delta_Hc / molar_mass, 3) - - @staticmethod - def isp(C_number, H_number, q): - H_C_ratio = H_number / C_number - parameter = q * (11.91 + H_C_ratio) / (43.66 + 8.936 * H_C_ratio) - if parameter < 0: # todo这是无意中发现的错误,可能是因为算的不是碳氢分子(确实目前发现的出现错误的是含F原子的分子) - parameter = 0 - isp = (2 * 0.556 * parameter) ** 0.5 / 9.8 * 1000 - return round(isp, 3) - - @staticmethod - def C_number(mol): - C_atoms = [i for i in mol.GetAtoms() if i.GetAtomicNum() == 6] - return len(C_atoms) - - @staticmethod - def H_number(mol): - H_atoms = [i for i in Chem.AddHs(mol).GetAtoms() if i.GetAtomicNum() == 1] - return len(H_atoms) - - @staticmethod - def smiles(mol): - return Chem.MolToSmiles(mol) - - @staticmethod - def molar_mass(mol): - molar_mass = 0.000 - atoms = Chem.AddHs(mol).GetAtoms() - for i in atoms: - molar_mass += i.GetMass() - return molar_mass - - def calculate_a_mol(self, mol, parameter_type='step_wise', debug=False): - init_smi = mol - try: - if isinstance(mol, str): - mol = Chem.MolFromSmiles(mol) - group_number = self.counter.count_a_mol(mol, clear_mode=True, add_note=True) - if group_number.get('note', ''): - counter_note = group_number['note'] - del group_number['note'] - else: - counter_note = '' - - if parameter_type == 'step_wise': - parameters = self.parameters_step_wise # todo 这里是速度慢的原因,每算一个分子都要加载一遍参数,要修改 - elif parameter_type == 'simultaneous': - parameters = self.parameters_simultaneous - else: - raise NotImplemented('不可用的参数类型,只能使用step_wise或simultaneous') - - if debug: - print(group_number) - # print(parameters) - Tm = self.Tm(group_number=group_number, parameters=parameters) - Tb = self.Tb(group_number=group_number, parameters=parameters) - Tc = self.Tc(group_number=group_number, parameters=parameters) - Pc = self.Pc(group_number=group_number, parameters=parameters) - Vc = self.Vc(group_number=group_number, parameters=parameters) - delta_Gf = self.delta_Gf(group_number=group_number, parameters=parameters) - delta_Hf = self.delta_Hf(group_number=group_number, parameters=parameters) - delta_Hv = self.delta_Hv(group_number=group_number, parameters=parameters) - delta_Hfus = self.delta_Hfus(group_number=group_number, parameters=parameters) - C_number = self.C_number(mol) - H_number = self.H_number(mol) - molar_mass = self.molar_mass(mol) - flash_point = self.flash_point(group_number=group_number, parameters=parameters) - molar_volume = self.molar_volume(group_number=group_number, parameters=parameters) - density = self.density(molar_mass=molar_mass, Vs=molar_volume) - delta_Hc = self.delta_Hc(C_number=C_number, H_number=H_number, delta_Hf=delta_Hf) - q = self.q(delta_Hc=delta_Hc, molar_mass=molar_mass) - isp = self.isp(C_number=C_number, H_number=H_number, q=q) - smiles = self.smiles(mol) - return {'smiles': smiles, - 'molar_mass': molar_mass, - 'flash_point/K': flash_point, - 'Tm/K': Tm, 'Tb/K': Tb, 'Tc/K': Tc, - 'Pc/bar': Pc, 'Vc/(cm3/mol)': Vc, - 'density/(g/cm3)': density, - 'delta_G/(KJ/mol)': delta_Gf, - 'delta_Hf/(KJ/mol)': delta_Hf, - 'delta_Hvap/(KJ/mol)': delta_Hv, - 'delta_Hfus/(KJ/mol)': delta_Hfus, - 'molar_volume/(cm3/mol)(default298K)': molar_volume, - 'delta_Hc/(KJ/mol)': delta_Hc, - 'mass_calorific_value_h/(MJ/kg)': q, - 'ISP': isp, - 'note': counter_note + ' at 298K'} - except: - print(f'Error! There is something wrong when calculating {init_smi}, please check it.') - return {'smiles': init_smi, - 'molar_mass': '?', - 'flash_point/K': '?', - 'Tm/K': '?', 'Tb/K': '?', 'Tc/K': '?', - 'Pc/bar': '?', 'Vc/(cm3/mol)': '?', - 'density/(g/cm3)': '?', - 'delta_G/(KJ/mol)': '?', - 'delta_Hf/(KJ/mol)': '?', - 'delta_Hvap/(KJ/mol)': '?', - 'delta_Hfus/(KJ/mol)': '?', - 'molar_volume/(cm3/mol)(default298K)': '?', - 'delta_Hc/(KJ/mol)': '?', - 'mass_calorific_value_h/(MJ/kg)': '?', - 'ISP': '?', - 'note': 'There must be something wrong with this SMILES'} - - def calculate_mols(self, smiles_file_path, properties_file_path='gp_3x_result.csv', parameter_type='simultaneous'): # todo 还没实现不同步拟合的参数的使用 - print('reading input file...') - if smiles_file_path.endswith('.txt'): - smiles_iterator = list(open(smiles_file_path)) - elif smiles_file_path.endswith('.xlsx'): - smiles_iterator = pd.read_excel(smiles_file_path)['smiles'] - elif smiles_file_path.endswith('.csv'): - smiles_iterator = pd.read_csv(smiles_file_path)['smiles'] - else: - print('无法识别的文件类型,请以.txt/.xlsx/.csv类型的文件作为输入。') - return None - mol_number = len(smiles_iterator) - print('reading completed,A total of {} molecules detected, start calculating properties...'.format(mol_number)) - print('start calculating...') - properties_dict_list = [] - error_smi = [] - for i in tqdm(smiles_iterator): - try: - properties_dict_list.append(self.calculate_a_mol(i, parameter_type=parameter_type)) - except: - error_smi.append(i) - print('calculation completed!') - print('start to export result to {} ...'.format(properties_file_path)) - result = pd.DataFrame(properties_dict_list) - result.to_csv(properties_file_path, index_label='index') - with open('error.txt', 'w') as f: - for i in error_smi: - f.write(i + '\n') - print('Done!') - return result - - def calculate_mols_mpi(self, smiles_file_path, properties_file_path='gp_3x_result_mpi.csv', parameter_type='simultaneous', n_jobs=1, batch_size='auto'): - print('reading input file...') - if smiles_file_path.endswith('.txt'): - smiles_iterator = list(open(smiles_file_path)) - elif smiles_file_path.endswith('.xlsx'): - smiles_iterator = pd.read_excel(smiles_file_path)['smiles'] - elif smiles_file_path.endswith('.csv'): - smiles_iterator = pd.read_csv(smiles_file_path)['smiles'] - else: - print('无法识别的文件类型,请以.txt/.xlsx/.csv类型的文件作为输入。') - return None - mol_number = len(smiles_iterator) - print('reading completed,A total of {} molecules detected, start calculating properties...'.format(mol_number)) - print('start calculating...') - task = [delayed(self.calculate_a_mol)(i, parameter_type=parameter_type) for i in smiles_iterator] - properties_dict_list = Parallel(n_jobs=n_jobs, batch_size=batch_size)(task) - print('calculation completed!') - print('start to export result to {} ...'.format(properties_file_path)) - result = pd.DataFrame(properties_dict_list) - result.to_csv(properties_file_path, index_label='index') - print('Done!') - return result - - -# if __name__ == '__main__': -# import time -# t1 = time.time() -# c = Calculator() -# c.calculate_mols_mpi(smiles_file_path='gp_3x_test_mol/SMILES.txt', n_jobs=4) -# t2 = time.time() -# print(t2 - t1) \ No newline at end of file diff --git a/build/lib/src/gp_3x_convertor.py b/build/lib/src/gp_3x_convertor.py deleted file mode 100644 index 4d2960e..0000000 --- a/build/lib/src/gp_3x_convertor.py +++ /dev/null @@ -1,345 +0,0 @@ -import os -from rdkit import Chem -from rdkit.Chem import AllChem -from openbabel import pybel -import pandas as pd -from tqdm import tqdm -from joblib import Parallel, delayed - - -class Convertor: - def __init__(self): - pass - - def __repr__(self): - return ('This is a object implemented some functions that can convert SMILES to 3D chemical files,' - ' such as xyz, gro... or convert 3D chemical files to SMILES') - - @staticmethod - def load_smiles_iterator(smiles_file_path): - print('reading input file...') - if smiles_file_path.endswith('.txt'): - smiles_iterator = list(open(smiles_file_path)) - elif smiles_file_path.endswith('.xlsx'): - smiles_iterator = pd.read_excel(smiles_file_path)['smiles'] - elif smiles_file_path.endswith('.csv'): - smiles_iterator = pd.read_csv(smiles_file_path)['smiles'] - else: - raise NotImplemented('无法识别的文件类型,请以.txt/.xlsx/.csv类型的文件作为输入。') - smiles_iterator = [i.strip() for i in smiles_iterator] - return smiles_iterator - - @staticmethod - def smi_to_xyz(smi, xyz_path=None): - mol = Chem.MolFromSmiles(smi) - if mol is None: - print(f'can not read {smi}, please check your SMILES') - return False - - mol_with_h = Chem.AddHs(mol) - AllChem.EmbedMolecule(mol_with_h, useRandomCoords=True) - atom_number = len(mol_with_h.GetAtoms()) - try: - AllChem.MMFFOptimizeMolecule(mol_with_h) - opt = Chem.MolToMolBlock(mol_with_h) - except ValueError: - mol = pybel.readstring("smi", smi) - mol.addh() - if mol.make3D() is None: - opt = mol.write("mol") - else: - print(f'Error! There is something wrong when converting {smi} to xyz file, please check it.') - return False - - # Windows 下pybel有问题 - # try: - # mol = pybel.readstring("smi", smi) - # mol.addh() - # if mol.make3D() is None: - # opt = mol.write("mol") - # else: - # return False - # except: - # AllChem.MMFFOptimizeMolecule(mol_with_h) - # opt = Chem.MolToMolBlock(mol_with_h) - - if xyz_path is None: - xyz_path = smi + '.xyz' - with open(xyz_path, 'w') as file: - file.write('{}\n'.format(atom_number)) - file.write(smi + '\n') - for index, i in enumerate(opt.splitlines()[4:]): - if len(i.split()) >= 4: - if i.split()[3].isupper(): - file.write(i.split()[3] + ' ') - file.write(i.split()[0] + ' ') - file.write(i.split()[1] + ' ') - file.write(i.split()[2] + '\n') - return True - - def batch_smi_to_xyz(self, smiles_file_path, xyz_root_path): - smiles_iterator = self.load_smiles_iterator(smiles_file_path=smiles_file_path) - mol_number = len(smiles_iterator) - zfill_number = len(str(mol_number)) + 3 - print('reading completed,A total of {} molecules detected, start making xyz files...'.format(mol_number)) - # make xyz_root_path - if os.path.exists(xyz_root_path): - print('xyz_root_path "{}" has been detected!'.format(xyz_root_path)) - else: - print('xyz_root_path "{}" has not been detected, I will create it for you'.format(xyz_root_path)) - os.makedirs(xyz_root_path) - # end - - succeed = [] - fail = [] - for (index, smi) in tqdm(enumerate(smiles_iterator)): - smi = smi.strip() - out_name = os.path.join(xyz_root_path, '{}.xyz'.format(str(index).zfill(zfill_number))) - generate_success_flag = self.smi_to_xyz(smi=smi, xyz_path=out_name) - - if not generate_success_flag: - fail.append(smi) - else: - succeed.append(smi) - - with open('xyz_fail.txt', 'w') as f: - for i in fail: - f.write(i + '\n') - with open('xyz_succeed.txt', 'w') as f: - for i in succeed: - f.write(i + '\n') - - if len(fail) == 0: - print('done! all .xyz files has been saved in {}'.format(xyz_root_path)) - else: - print('Warning! The following SMILES fail to generate .xyz, please check...sorry(OTZ)') - print(fail) - return None - - def batch_smi_to_xyz_mpi(self, smiles_file_path, xyz_root_path, n_jobs=1, batch_size='auto'): - smiles_iterator = self.load_smiles_iterator(smiles_file_path=smiles_file_path) - mol_number = len(smiles_iterator) - zfill_number = len(str(mol_number)) + 3 - print('reading completed,A total of {} molecules detected, start making xyz files...'.format(mol_number)) - # make xyz_root_path - if os.path.exists(xyz_root_path): - print('xyz_root_path "{}" has been detected!'.format(xyz_root_path)) - else: - print('xyz_root_path "{}" has not been detected, I will create it for you'.format(xyz_root_path)) - os.makedirs(xyz_root_path) - # end - - task = [delayed(self.smi_to_xyz)(smi=smi, xyz_path=os.path.join(xyz_root_path, '{}.xyz'.format(str(index).zfill(zfill_number)))) for (index, smi) in enumerate(smiles_iterator)] - result = Parallel(n_jobs=n_jobs, batch_size=batch_size)(task) - print('done! all .xyz files has been saved in {}'.format(xyz_root_path)) - return result - - @staticmethod - def convert_file_type(in_format, in_path, out_format, out_path=None): - try: - mol = pybel.readfile(in_format, in_path).__next__() - # print('The SMILES of this system is :') - # print(mol.write('smi')) - - if out_path is None: - out_path = in_path.split('.') - out_path = out_path[0] + '.' + out_format - mol.write(out_format, out_path, overwrite=True) - return None - except: - print(f'Error! There is something wrong when converting {in_path}, please check it.') - return None - - def batch_convert_file_type(self, in_format, in_root_path, out_format, out_root_path=None): - if out_root_path is None: - out_root_path = in_root_path - else: - # make out_root_path - if os.path.exists(out_root_path): - print('out_root_path "{}" has been detected!'.format(out_root_path)) - else: - print('out_root_path "{}" has not been detected, I will create it for you'.format(out_root_path)) - os.makedirs(out_root_path) - # end - in_file_names = os.listdir(in_root_path) - - in_file_names = [i for i in in_file_names if i.endswith(in_format)] - out_file_names = [i.split('.')[0] + '.{}'.format(out_format) for i in in_file_names] - - in_file_path = [os.path.join(in_root_path, i) for i in in_file_names] - out_file_path = [os.path.join(out_root_path, i) for i in out_file_names] - - error_in_file_path = [] - for index in tqdm(range(len(in_file_path))): - try: - self.convert_file_type(in_format=in_format, in_path=in_file_path[index], - out_format=out_format, out_path=out_file_path[index]) - except: - # print('Warning!!!') - error_in_file_path.append(in_file_path[index]) - # print('There may something wrong in {}, please check it carefully!'.format(in_file_path[index])) - - # When there is something wrong, print some warning - if len(error_in_file_path) > 0: - print('Warning!Warning!Warning!') - for i in error_in_file_path: - print('There may something wrong in {}, please check it carefully!'.format(i)) - return None - - def batch_convert_file_type_mpi(self, in_format, in_root_path, out_format, out_root_path=None, n_jobs=1, batch_size='auto'): - if out_root_path is None: - out_root_path = in_root_path - else: - # make out_root_path - if os.path.exists(out_root_path): - print('out_root_path "{}" has been detected!'.format(out_root_path)) - else: - print('out_root_path "{}" has not been detected, I will create it for you'.format(out_root_path)) - os.makedirs(out_root_path) - # end - in_file_names = os.listdir(in_root_path) - - in_file_names = [i for i in in_file_names if i.endswith(in_format)] - out_file_names = [i.split('.')[0] + '.{}'.format(out_format) for i in in_file_names] - - in_file_path = [os.path.join(in_root_path, i) for i in in_file_names] - out_file_path = [os.path.join(out_root_path, i) for i in out_file_names] - - task = [delayed(self.convert_file_type)(in_format=in_format, in_path=in_file_path[index],out_format=out_format, out_path=out_file_path[index]) for index in range(len(in_file_path))] - result = Parallel(n_jobs=n_jobs, batch_size=batch_size)(task) - return result - - @staticmethod - def file_to_smi(file_path, format=None): - """ - {'abinit': 'ABINIT Output Format', 'acesout': 'ACES output format', 'acr': 'ACR format', - 'adfband': 'ADF Band output format', 'adfdftb': 'ADF DFTB output format', 'adfout': 'ADF output format', - 'alc': 'Alchemy format', 'aoforce': 'Turbomole AOFORCE output format', - 'arc': 'Accelrys/MSI Biosym/Insight II CAR format', 'axsf': 'XCrySDen Structure Format', - 'bgf': 'MSI BGF format', 'box': 'Dock 3.5 Box format', 'bs': 'Ball and Stick format', - 'c09out': 'Crystal 09 output format', 'c3d1': 'Chem3D Cartesian 1 format', 'c3d2': 'Chem3D Cartesian 2 format', - 'caccrt': 'Cacao Cartesian format', 'can': 'Canonical SMILES format', - 'car': 'Accelrys/MSI Biosym/Insight II CAR format', 'castep': 'CASTEP format', 'ccc': 'CCC format', - 'cdjson': 'ChemDoodle JSON', 'cdx': 'ChemDraw binary format', 'cdxml': 'ChemDraw CDXML format', - 'cif': 'Crystallographic Information File', 'ck': 'ChemKin format', 'cml': 'Chemical Markup Language', - 'cmlr': 'CML Reaction format', 'cof': 'Culgi object file format', 'CONFIG': 'DL-POLY CONFIG', - 'CONTCAR': 'VASP format', 'CONTFF': 'MDFF format', 'crk2d': 'Chemical Resource Kit diagram(2D)', - 'crk3d': 'Chemical Resource Kit 3D format', 'ct': 'ChemDraw Connection Table format', - 'cub': 'Gaussian cube format', 'cube': 'Gaussian cube format', 'dallog': 'DALTON output format', - 'dalmol': 'DALTON input format', 'dat': 'Generic Output file format', 'dmol': 'DMol3 coordinates format', - 'dx': 'OpenDX cube format for APBS', 'ent': 'Protein Data Bank format', - 'exyz': 'Extended XYZ cartesian coordinates format', 'fa': 'FASTA format', 'fasta': 'FASTA format', - 'fch': 'Gaussian formatted checkpoint file format', 'fchk': 'Gaussian formatted checkpoint file format', - 'fck': 'Gaussian formatted checkpoint file format', 'feat': 'Feature format', 'fhiaims': 'FHIaims XYZ format', - 'fract': 'Free Form Fractional format', 'fs': 'Fastsearch format', 'fsa': 'FASTA format', - 'g03': 'Gaussian Output', 'g09': 'Gaussian Output', 'g16': 'Gaussian Output', - 'g92': 'Gaussian Output', 'g94': 'Gaussian Output', 'g98': 'Gaussian Output', 'gal': 'Gaussian Output', - 'gam': 'GAMESS Output', 'gamess': 'GAMESS Output', 'gamin': 'GAMESS Input', 'gamout': 'GAMESS Output', - 'got': 'GULP format', 'gpr': 'Ghemical format', 'gro': 'GRO format', 'gukin': 'GAMESS-UK Input', - 'gukout': 'GAMESS-UK Output', 'gzmat': 'Gaussian Z-Matrix Input', 'hin': 'HyperChem HIN format', - 'HISTORY': 'DL-POLY HISTORY', 'inchi': 'InChI format', 'inp': 'GAMESS Input', 'ins': 'ShelX format', - 'jin': 'Jaguar input format', 'jout': 'Jaguar output format', 'log': 'Generic Output file format', - 'lpmd': 'LPMD format', 'mcdl': 'MCDL format', 'mcif': 'Macromolecular Crystallographic Info', - 'MDFF': 'MDFF format', 'mdl': 'MDL MOL format', 'ml2': 'Sybyl Mol2 format', - 'mmcif': 'Macromolecular Crystallographic Info', 'mmd': 'MacroModel format', 'mmod': 'MacroModel format', - 'mol': 'MDL MOL format', 'mol2': 'Sybyl Mol2 format', 'mold': 'Molden format', 'molden': 'Molden format', - 'molf': 'Molden format', 'moo': 'MOPAC Output format', 'mop': 'MOPAC Cartesian format', - 'mopcrt': 'MOPAC Cartesian format', 'mopin': 'MOPAC Internal', 'mopout': 'MOPAC Output format', - 'mpc': 'MOPAC Cartesian format', 'mpo': 'Molpro output format', 'mpqc': 'MPQC output format', - 'mrv': 'Chemical Markup Language', 'msi': 'Accelrys/MSI Cerius II MSI format', 'nwo': 'NWChem output format', - 'orca': 'ORCA output format', 'out': 'Generic Output file format', 'outmol': 'DMol3 coordinates format', - 'output': 'Generic Output file format', 'pc': 'PubChem format', 'pcjson': 'PubChem JSON', - 'pcm': 'PCModel Format', 'pdb': 'Protein Data Bank format', 'pdbqt': 'AutoDock PDBQT format', - 'png': 'PNG 2D depiction', 'pos': 'POS cartesian coordinates format', 'POSCAR': 'VASP format', - 'POSFF': 'MDFF format', 'pqr': 'PQR format', 'pqs': 'Parallel Quantum Solutions format', - 'prep': 'Amber Prep format', 'pwscf': 'PWscf format', 'qcout': 'Q-Chem output format', 'res': 'ShelX format', - 'rsmi': 'Reaction SMILES format', 'rxn': 'MDL RXN format', 'sd': 'MDL MOL format', 'sdf': 'MDL MOL format', - 'siesta': 'SIESTA format', 'smi': 'SMILES format', 'smiles': 'SMILES format', - 'smy': 'SMILES format using Smiley parser', 'sy2': 'Sybyl Mol2 format', 't41': 'ADF TAPE41 format', - 'tdd': 'Thermo format', 'text': 'Read and write raw text', 'therm': 'Thermo format', - 'tmol': 'TurboMole Coordinate format', 'txt': 'Title format', 'txyz': 'Tinker XYZ format', - 'unixyz': 'UniChem XYZ format', 'VASP': 'VASP format', 'vmol': 'ViewMol format', - 'wln': 'Wiswesser Line Notation', 'xml': 'General XML format', 'xsf': 'XCrySDen Structure Format', - 'xyz': 'XYZ cartesian coordinates format', 'yob': 'YASARA.org YOB format'} - """ - try: - atoms = next(pybel.readfile(format=format, filename=file_path)) - smi = atoms.write(format='smi').split('\t')[0] - # print(smi) - return smi - except: - print('There may something wrong in {}, please check it carefully!'.format(file_path)) - return 'There may something wrong in {}, please check it carefully!'.format(file_path) - - def batch_file_to_smi(self, in_format, in_root_path, out_root_path=None): - if out_root_path is None: - out_root_path = in_root_path - else: - # make out_root_path - if os.path.exists(out_root_path): - print('out_root_path "{}" has been detected!'.format(out_root_path)) - else: - print('out_root_path "{}" has not been detected, I will create it for you'.format(out_root_path)) - os.makedirs(out_root_path) - # end - in_file_names = os.listdir(in_root_path) - in_file_names = [i for i in in_file_names if i.endswith(in_format)] - in_file_path = [os.path.join(in_root_path, i) for i in in_file_names] - error_in_file_path = [] - smi_list = [] - for index in tqdm(range(len(in_file_path))): - try: - smi_list.append(self.file_to_smi(format=in_format, file_path=in_file_path[index])) - except: - # print('Warning!!!') - error_in_file_path.append(in_file_path[index]) - # print('There may something wrong in {}, please check it carefully!'.format(in_file_path[index])) - - # When there is something wrong, print some warning - if len(error_in_file_path) > 0: - print('Warning!Warning!Warning!') - for i in error_in_file_path: - print('There may something wrong in {}, please check it carefully!'.format(i)) - - with open(os.path.join(out_root_path, 'SMILES.txt'), 'w') as f: - for i in smi_list: - f.write(i + '\n') - return smi_list - - def batch_file_to_smi_mpi(self, in_format, in_root_path, out_root_path=None, n_jobs=1, batch_size='auto'): - if out_root_path is None: - out_root_path = in_root_path - else: - # make out_root_path - if os.path.exists(out_root_path): - print('out_root_path "{}" has been detected!'.format(out_root_path)) - else: - print('out_root_path "{}" has not been detected, I will create it for you'.format(out_root_path)) - os.makedirs(out_root_path) - # end - in_file_names = os.listdir(in_root_path) - in_file_names = [i for i in in_file_names if i.endswith(in_format)] - in_file_path = [os.path.join(in_root_path, i) for i in in_file_names] - - task = [delayed(self.file_to_smi)(format=in_format, file_path=in_file_path[index]) for index in range(len(in_file_path))] - smi_list = Parallel(n_jobs=n_jobs, batch_size=batch_size)(task) - - with open(os.path.join(out_root_path, 'SMILES.txt'), 'w') as f: - for i in smi_list: - f.write(i + '\n') - return smi_list - - -# if __name__ == '__main__': -# import time -# -# t1 = time.time() -# c = Convertor() -# # c.smi_to_xyz('C1CCCC1C', 'C1CCCC1C.xyz') -# # c.convert_file_type(in_format='xyz', in_path='C1CCCC1C.xyz', out_format='mol', out_path='C1CCCC1C.mol') -# # c.file_to_smi('C1CCCC1C.mol', format='mol') -# -# # c.batch_file_to_smi(in_format='mol2', in_root_path=os.path.join('gp_3x_test_mol', 'test_mol')) -# x = c.batch_file_to_smi_mpi(in_format='mol', in_root_path='./test', n_jobs=4, batch_size='auto') -# t2 = time.time() -# print(t2 - t1) \ No newline at end of file diff --git a/build/lib/src/gp_3x_counter.py b/build/lib/src/gp_3x_counter.py deleted file mode 100644 index 2d2cd8b..0000000 --- a/build/lib/src/gp_3x_counter.py +++ /dev/null @@ -1,3374 +0,0 @@ -import os.path -from tqdm import tqdm -from rdkit import Chem -import pandas as pd -from joblib import Parallel, delayed - - -# tool -def has_non_aromatic_neighbor(atom): - """判断原子周围是否有非芳香原子""" - flag = False - neighbors = atom.GetNeighbors() - for i in neighbors: - if not i.GetIsAromatic(): - flag = True - break - return flag - - -def find_ring_atoms(mol, atom_idxs): - """找出给定的原子索引元组中在环上的原子索引,并返回一个元组""" - return list((i for i in atom_idxs if mol.GetAtomWithIdx(i).IsInRing())) - - -def is_in_same_ring(mol, atom_idxs: tuple): - """判断一个元组中的原子索引是否在同一个环上""" - flag = False - all_rings = [list(i) for i in Chem.GetSymmSSSR(mol)] - # set(i).issubset(j) - for i in all_rings: - if set(atom_idxs).issubset(i): - flag = True - break - return flag - - -# f order -def f_001(mol): - """CH3""" - query = Chem.MolFromSmarts('[C;H3;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_002(mol): - """CH2""" - query = Chem.MolFromSmarts('[C;H2;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_003(mol): - """CH""" - query = Chem.MolFromSmarts('[C;H1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_004(mol): - """C""" - query = Chem.MolFromSmarts('[C;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_005(mol): - """CH2=CH""" - query = Chem.MolFromSmarts('[C;H2;!R]=[C;H1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_006(mol): - """CH=CH""" - query = Chem.MolFromSmarts('[C;H;!R]=[C;H1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_007(mol): - """CH2=C""" - query = Chem.MolFromSmarts('[C;H2;!R]=[C;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_008(mol): - """CH=C""" - query = Chem.MolFromSmarts('[C;H1;!R]=[C;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_009(mol): - """C=C""" - query = Chem.MolFromSmarts('[C;H0;!R]=[C;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_010(mol): - """CH2=C=CH""" - query = Chem.MolFromSmarts('[C;H2;!R]=[C;H0;!R;D2]=[C;H1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_011(mol): - """CH2=C=C""" - query = Chem.MolFromSmarts('[C;H2;!R]=[C;H0;!R;D2]=[C;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_012(mol): - """C=C=C - 2001年的论文12号基团是CH=C=CH,对应的SMART为[C;H1;!R;D2]=[C;H0;!R;D2]=[C;H1;!R;D2] - """ - query = Chem.MolFromSmarts('[C;H0;!R]=[C;H0;!R;D2]=[C;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_013(mol): - """CH#C""" - query = Chem.MolFromSmarts('[C;H1;!R;D1]#[C;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_014(mol): - """C#C""" - query = Chem.MolFromSmarts('[C;H0;!R;D2]#[C;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_015(mol): - """aCH""" - query = Chem.MolFromSmarts('[c;H1;R1;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_016(mol): # 麻烦且慢,但是应该可靠 - """aC fused with aromatic ring""" - query = Chem.MolFromSmarts('[c;H0;R&!R1;D3]') - match_list = mol.GetSubstructMatches(query) - real_match_list = [] - for i in match_list: - atom_i = mol.GetAtomWithIdx(i[0]) - if not has_non_aromatic_neighbor(atom_i): # 判断该原子是否有链接非芳香原子是为了防止把芳环上连接非芳香环也算进去 - real_match_list.append(i) - return len(real_match_list), tuple(real_match_list) - - -def f_017(mol): # 麻烦且慢,但是应该可靠 - """aC fused with nonaromatic subring""" - query = Chem.MolFromSmarts('[c;H0;R&!R1;D3]') - match_list = mol.GetSubstructMatches(query) - real_match_list = [] - for i in match_list: - atom_i = mol.GetAtomWithIdx(i[0]) - if has_non_aromatic_neighbor(atom_i): # 判断该原子是否有链接非芳香原子是为了取出芳环上连接非芳香环的,和上一个函数刚好相反 - real_match_list.append(i) - return len(real_match_list), tuple(real_match_list) - - -def f_018(mol): - """aC except as above - 这里这样写是因为我们给出了数基团的顺序,在那里会剔除重复的,所以不用担心重复 - """ - query = Chem.MolFromSmarts('[c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_019(mol): - """aN in aromatic ring""" - query = Chem.MolFromSmarts('[n;H0;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_020(mol): - """aC-CH3""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_021(mol): - """aC-CH2""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H2;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_022(mol): - """aC-CH""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_023(mol): - """aC-C""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_024(mol): - """aC-CH=CH2""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H1;!R;D2]=[C;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_025(mol): - """aC-CH=CH""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H1;!R;D2]=[C;H1;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_026(mol): - """aC-C=CH2""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H0;!R;D3]=[C;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_027(mol): - """aC-C#CH""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H0;!R;D2]#[C;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_028(mol): - """aC-C#C""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H0;!R;D2]#[C;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_029(mol): - """OH""" - query = Chem.MolFromSmarts('[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_030(mol): - """aC-OH""" - query = Chem.MolFromSmarts('[c;H0;R;D3][O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_031(mol): - """COOH""" - query = Chem.MolFromSmarts('[C;H0;!R;D3]([O;H1;!R;D1])=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_032(mol): - """aC-COOH""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H0;!R;D3]([O;H1;!R;D1])=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_033(mol): - """CH3CO""" - query = Chem.MolFromSmarts('[C;H3;!R;D1][C;H0;!R;D3](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_034(mol): - """CH2CO""" - query = Chem.MolFromSmarts('[C;H2;!R;D2][C;H0;!R;D3](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_035(mol): - """CHCO""" - query = Chem.MolFromSmarts('[C;H1;!R][C;H0;!R;D3](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_036(mol): - """CCO""" - query = Chem.MolFromSmarts('[C;H0;!R][C;H0;!R;D3](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_037(mol): - """aC-CO""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H0;!R;D3](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_038(mol): - """CHO""" - query = Chem.MolFromSmarts('[C;H1;!R;D2](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_039(mol): - """aC-CHO""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H1;!R;D2](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_040(mol): - """CH3COO""" - query = Chem.MolFromSmarts('[C;H3;!R;D1][C;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_041(mol): - """CH2COO""" - query = Chem.MolFromSmarts('[C;H2;!R;D2][C;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_042(mol): - """CHCOO""" - query = Chem.MolFromSmarts('[C;H1;!R;D3][C;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_043(mol): - """CCOO""" - query = Chem.MolFromSmarts('[C;H0;!R;D4][C;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_044(mol): - """HCOO""" - query = Chem.MolFromSmarts('[C;H1;!R;D2](=[O;H0;!R;D1])[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_045(mol): - """aC-COO""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_046(mol): - """aC-OOCH""" - query = Chem.MolFromSmarts('[C;H1;!R;D2](=[O;H0;!R;D1])[O;H0;!R;D2][c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_047(mol): - """aC-OOC""" - query = Chem.MolFromSmarts('[C;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2][c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_048(mol): - """COO except as above - 取消了是不是在环上的限制 - """ - query = Chem.MolFromSmarts('[C;H0;D3](=[O;H0;D1])[O;H0;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_049(mol): - """CH3O""" - query = Chem.MolFromSmarts('[C;H3;!R;D1]-[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_050(mol): - """CH2O""" - query = Chem.MolFromSmarts('[C;H2;!R;D2]-[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_051(mol): - """CH-O""" - query = Chem.MolFromSmarts('[C;H1;!R]-[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_052(mol): - """C-O""" - query = Chem.MolFromSmarts('[C;H0;!R]-[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_053(mol): - """aC-O""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_054(mol): - """CH2NH2""" - query = Chem.MolFromSmarts('[C;H2;!R;D2]-[N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_055(mol): - """CHNH2""" - query = Chem.MolFromSmarts('[C;H1;!R;D3]-[N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_056(mol): - """CNH2""" - query = Chem.MolFromSmarts('[C;H0;!R;D4]-[N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_057(mol): - """CH3NH""" - query = Chem.MolFromSmarts('[C;H3;!R;D1]-[N;H1;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_058(mol): - """CH2NH""" - query = Chem.MolFromSmarts('[C;H2;!R;D2]-[N;H1;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_059(mol): - """CHNH""" - query = Chem.MolFromSmarts('[C;H1;!R;D3]-[N;H1;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_060(mol): - """CH3N - 因为有f66(CH=N)、f67(C=N),所以这里还是要求N的度为3 - """ - query = Chem.MolFromSmarts('[C;H3;!R;D1]-[N;H0;!R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_061(mol): - """CH2N - 因为有f66(CH=N)、f67(C=N),所以这里还是要求N的度为3 - """ - query = Chem.MolFromSmarts('[C;H2;!R;D2]-[N;H0;!R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_062(mol): - """aC-NH2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_063(mol): - """aC-NH""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H1;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_064(mol): - """aC-N""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H0;!R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_065(mol): - """NH2 except as above""" - query = Chem.MolFromSmarts('[N;H2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_066(mol): - """CH=N""" - query = Chem.MolFromSmarts('[C;H1;!R;D2]=[N;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_067(mol): - """C=N""" - query = Chem.MolFromSmarts('[C;H0;!R]=[N;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_068(mol): - """CH2CN""" - query = Chem.MolFromSmarts('[C;H2;!R;D2][C;H0;!R;D2]#[N;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_069(mol): - """CHCN""" - query = Chem.MolFromSmarts('[C;H1;!R;D3][C;H0;!R;D2]#[N;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_070(mol): - """CCN""" - query = Chem.MolFromSmarts('[C;H0;!R;D4][C;H0;!R;D2]#[N;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_071(mol): - """aC-CN""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H0;!R;D2]#[N;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_072(mol): - """CN except as above - 不可能在环上 - """ - query = Chem.MolFromSmarts('[C;H0;!R;D2]#[N;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_073(mol): - """CH2NCO""" - query = Chem.MolFromSmarts('[C;H2;!R;D2]-[N;H0;!R;D2]=[C;H0;!R;D2]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_074(mol): - """CHNCO""" - query = Chem.MolFromSmarts('[C;H1;!R]-[N;H0;!R;D2]=[C;H0;!R;D2]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_075(mol): - """CNCO""" - query = Chem.MolFromSmarts('[C;H0;!R]-[N;H0;!R;D2]=[C;H0;!R;D2]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_076(mol): - """aC-NCO""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H0;!R;D2]=[C;H0;!R;D2]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_077(mol): - """CH2NO2""" - query = Chem.MolFromSmarts('[C;H2;!R;D2]-[N;+]([O;-])=[O]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_078(mol): - """CHNO2""" - query = Chem.MolFromSmarts('[C;H1;!R;D3]-[N;+]([O;-])=[O]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_079(mol): - """CNO2""" - query = Chem.MolFromSmarts('[C;H0;!R;D4]-[N;+]([O;-])=[O]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_080(mol): - """aC-NO2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;+]([O;-])=[O]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_081(mol): - """NO2 except as above""" - query = Chem.MolFromSmarts('[N;+]([O;-])=[O]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_082(mol): - """ONO""" - query = Chem.MolFromSmarts('[O;H0;!R;D2][N;H0;!R;D2]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_083(mol): - """ONO2""" - query = Chem.MolFromSmarts('[O;H0;!R;D2][N;+]([O;-1])=O') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_084(mol): - """HCON(CH2)2""" - query = Chem.MolFromSmarts('[O;H0;!R;D1]=[C;H1;!R;D2][N;H0;!R;D3]([C;H2;!R;D2])[C;H2;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_085(mol): - """HCONH(CH2)""" - query = Chem.MolFromSmarts('[O;H0;!R;D1]=[C;H1;!R;D2][N;H1;!R;D2][C;H2;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_086(mol): - """CONH2""" - query = Chem.MolFromSmarts('[O;H0;!R;D1]=[C;H0;!R;D3][N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_087(mol): - """CONHCH3""" - query = Chem.MolFromSmarts('[O;H0;!R;D1]=[C;H0;!R;D3][N;H1;!R;D2][C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_088(mol): - """CONHCH2""" - query = Chem.MolFromSmarts('[O;H0;!R;D1]=[C;H0;!R;D3][N;H1;!R;D2][C;H2;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_089(mol): - """CON(CH3)2""" - query = Chem.MolFromSmarts('[O;H0;!R;D1]=[C;H0;!R;D3][N;H0;!R;D3]([C;H3;!R;D1])[C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_090(mol): - """CONCH3CH2""" - query = Chem.MolFromSmarts('[O;H0;!R;D1]=[C;H0;!R;D3][N;H0;!R;D3]([C;H2;!R;D2])[C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_091(mol): - """CON(CH2)2""" - query = Chem.MolFromSmarts('[O;H0;!R;D1]=[C;H0;!R;D3][N;H0;!R;D3]([C;H2;!R;D2])[C;H2;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_092(mol): - """CONHCO""" - query = Chem.MolFromSmarts('[O;H0;!R;D1]=[C;H0;!R;D3][N;H1;!R;D2][C;H0;!R;D3]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_093(mol): - """CONCO""" - query = Chem.MolFromSmarts('[O;H0;!R;D1]=[C;H0;!R;D3][N;H0;!R;D3][C;H0;!R;D3]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_094(mol): - """aC-CONH2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H0;!R;D3](=[O;H0;!R;D1])[N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_095(mol): - """aC-NH(CO)H""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H1;!R;D2]-[C;H1;!R;D2](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_096(mol): - """aC-N(CO)H""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H0;!R;D3]-[C;H1;!R;D2](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_097(mol): - """aC-CONH""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H0;!R;D3](=[O;H0;!R;D1])[N;H1;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_098(mol): - """aC-NHCO""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H1;!R;D2]-[C;H0;!R;D3]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_099(mol): - """aC-NCO""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H0;!R;D3]-[C;H0;!R;D3]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_100(mol): - """NHCONH""" - query = Chem.MolFromSmarts('[N;H1;!R;D2]-[C;H0;!R;D3](=[O;H0;!R;D1])-[N;H1;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_101(mol): - """NH2CONH""" - query = Chem.MolFromSmarts('[N;H2;!R;D1]-[C;H0;!R;D3](=[O;H0;!R;D1])-[N;H1;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_102(mol): - """NH2CON""" - query = Chem.MolFromSmarts('[N;H2;!R;D1]-[C;H0;!R;D3](=[O;H0;!R;D1])-[N;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_103(mol): - """NHCON""" - query = Chem.MolFromSmarts('[N;H1;!R;D2]-[C;H0;!R;D3](=[O;H0;!R;D1])-[N;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_104(mol): - """NCON""" - query = Chem.MolFromSmarts('[N;H0;!R;D3]-[C;H0;!R;D3](=[O;H0;!R;D1])-[N;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_105(mol): - """aC-NHCONH2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H1;!R;D2]-[C;H0;!R;D3](=[O;H0;!R;D1])-[N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_106(mol): - """aC-NHCONH""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H1;!R;D2]-[C;H0;!R;D3](=[O;H0;!R;D1])-[N;H1;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_107(mol): - """NHCO except as above""" - query = Chem.MolFromSmarts('[N;H1;!R;D2]-[C;H0;!R;D3](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_108(mol): - """CH2Cl""" - query = Chem.MolFromSmarts('[C;H2;!R;D2]-[Cl]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_109(mol): - """CHCl""" - query = Chem.MolFromSmarts('[C;H1;!R][Cl]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_110(mol): - """CCl""" - query = Chem.MolFromSmarts('[C;H0;!R][Cl]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_111(mol): - """CHCl2""" - query = Chem.MolFromSmarts('[C;H1;!R;D3](-[Cl])-[Cl]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_112(mol): - """CCl2""" - query = Chem.MolFromSmarts('[C;H0;!R](-[Cl])-[Cl]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_113(mol): - """CCl3""" - query = Chem.MolFromSmarts('[C;H0;!R;D4](-[Cl])(-[Cl])-[Cl]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_114(mol): - """CH2F""" - query = Chem.MolFromSmarts('[C;H2;!R;D2]-[F]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_115(mol): - """CHF""" - query = Chem.MolFromSmarts('[C;H1;!R][F]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_116(mol): - """CF""" - query = Chem.MolFromSmarts('[C;H0;!R][F]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_117(mol): - """CHF2""" - query = Chem.MolFromSmarts('[C;H1;!R;D3](-[F])-[F]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_118(mol): - """CF2""" - query = Chem.MolFromSmarts('[C;H0;!R](-[F])-[F]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_119(mol): - """CF3""" - query = Chem.MolFromSmarts('[C;H0;!R;D4](-[F])(-[F])-[F]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_120(mol): - """CCl2F""" - query = Chem.MolFromSmarts('F[C;H0;!R;D4](Cl)Cl') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_121(mol): - """HCClF""" - query = Chem.MolFromSmarts('F[C;H1;!R;D3]Cl') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_122(mol): - """CClF2""" - query = Chem.MolFromSmarts('Cl[C;H0;!R;D4](F)F') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_123(mol): - """aC-Cl""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-Cl') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_124(mol): - """aC-F""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-F') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_125(mol): - """aC-I""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-I') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_126(mol): - """aC-Br""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-Br') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_127(mol): - """I except as above""" - query = Chem.MolFromSmarts('I') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_128(mol): - """Br except as above""" - query = Chem.MolFromSmarts('Br') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_129(mol): - """F except as above""" - query = Chem.MolFromSmarts('F') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_130(mol): - """Cl except as above""" - query = Chem.MolFromSmarts('Cl') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_131(mol): - """CHNOH""" - query = Chem.MolFromSmarts('[C;H1;!R;D2]=[N;H0;!R;D2]-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_132(mol): - """CNOH""" - query = Chem.MolFromSmarts('[C;H0;!R]=[N;H0;!R;D2]-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_133(mol): - """aC-CHNOH""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H1;!R;D2]=[N;H0;!R;D2]-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_134(mol): - """OCH2CH2OH""" - query = Chem.MolFromSmarts('[O;H0;!R;D2][C;H2;!R;D2][C;H2;!R;D2][O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_135(mol): - """OCHCH2OH""" - query = Chem.MolFromSmarts('[O;H0;!R;D2][C;H1;!R;D3][C;H2;!R;D2][O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_136(mol): - """OCH2CHOH""" - query = Chem.MolFromSmarts('[O;H0;!R;D2][C;H2;!R;D2][C;H1;!R;D3][O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_137(mol): - """O-OH""" - query = Chem.MolFromSmarts('[O;H0;!R;D2][O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_138(mol): - """CH2SH""" - query = Chem.MolFromSmarts('[C;H2;!R;D2]-[S;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_139(mol): - """CHSH - 这里要求C是D3,是因为后面又142(-SH(except as above)) - """ - query = Chem.MolFromSmarts('[C;H1;!R;D3]-[S;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_140(mol): - """CSH - 这里要求C是D4,是因为后面又142(-SH(except as above)) - """ - query = Chem.MolFromSmarts('[C;H0;!R;D4]-[S;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_141(mol): - """aC-SH""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[S;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_142(mol): - """SH except as above""" - query = Chem.MolFromSmarts('[S;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_143(mol): - """CH3S""" - query = Chem.MolFromSmarts('[C;H3;!R;D1]-[S;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_144(mol): - """CH2S""" - query = Chem.MolFromSmarts('[C;H2;!R;D2]-[S;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_145(mol): - """CHS""" - query = Chem.MolFromSmarts('[C;H1;!R]-[S;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_146(mol): - """CS""" - query = Chem.MolFromSmarts('[C;H0;!R]-[S;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_147(mol): - """aC-S-""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[S;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_148(mol): - """SO""" - query = Chem.MolFromSmarts('[S;H0;!R;D3]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_149(mol): - """SO2""" - query = Chem.MolFromSmarts('[S;H0;!R;D4](=[O;H0;!R;D1])(=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_150(mol): - """SO3(sulfite)""" - query = Chem.MolFromSmarts('[O;H0;!R;D2][S;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_151(mol): - """SO3(sulfonate)""" - query = Chem.MolFromSmarts('[O;H0;!R;D2][S;H0;!R;D4](=[O;H0;!R;D1])(=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_152(mol): - """SO4(sulfite)""" - query = Chem.MolFromSmarts('[O;H0;!R;D2][S;H0;!R;D4](=[O;H0;!R;D1])(=[O;H0;!R;D1])[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_153(mol): - """aC-SO""" - query = Chem.MolFromSmarts('[c;H0;R;D3][S;H0;!R;D3]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_154(mol): - """aC-SO2""" - query = Chem.MolFromSmarts('[c;H0;R;D3][S;H0;!R;D4](=[O;H0;!R;D1])(=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -# todo 含磷化合物太复杂了,以后再检查吧 -def f_155(mol): - """PH(phosphine)""" - query = Chem.MolFromSmarts('[P;H1;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_156(mol): - """P(phosphine)""" - query = Chem.MolFromSmarts('[P;H0;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_157(mol): - """PO3(phosphine)""" - query = Chem.MolFromSmarts('[O;D2;H0][P;H0;D3]([O;D2;H0])[O;D2;H0]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_158(mol): - """PHO3(phosphonate)""" - query = Chem.MolFromSmarts('[O;D2;H0][P;H1;D3](=[O;D1;H0])[O;D2;H0]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_159(mol): - """PO3(phosphonate)""" - query = Chem.MolFromSmarts('[O;D2;H0][P;H0;D4](=[O;D1;H0])[O;D2;H0]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_160(mol): - """PHO4(phosphate)""" - query = Chem.MolFromSmarts('[O;D2;H0][P;H0;D4]([O;H1;D1])(=[O;D1;H0])[O;D2;H0]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_161(mol): - """PO4(phosphate)""" - query = Chem.MolFromSmarts('[O;D2;H0][P;H0;D4]([O;H0;D2])(=[O;D1;H0])[O;D2;H0]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_162(mol): - """aC-PO4""" - query = Chem.MolFromSmarts('[c;H0;R][O;D2;H0][P;H0;D4]([O;H0;D2])(=[O;D1;H0])[O;D2;H0]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_163(mol): - """aC-P""" - query = Chem.MolFromSmarts('[c;H0;R][P;H0;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_164(mol): - """CO3(carbonate)""" - query = Chem.MolFromSmarts('[O;H0;!R;D2][C;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_165(mol): - """C2H3O""" - query = Chem.MolFromSmarts('[C;H2;R;D2]1[O;H0;R;D2][C;H1;R;D3]1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_166(mol): - """C2H2O""" - query = Chem.MolFromSmarts('[C;H2;R;D2]1[O;H0;R;D2][C;H0;R]1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_167(mol): - """C2O""" - query = Chem.MolFromSmarts('[C;H1;R][O;H0;R][C;H0;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_168(mol): - """CH2(cyclic)""" - query = Chem.MolFromSmarts('[C;H2;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_169(mol): - """CH(cyclic)""" - query = Chem.MolFromSmarts('[C;H1;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_170(mol): - """C(cyclic)""" - query = Chem.MolFromSmarts('[C;H0;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_171(mol): - """CH=CH(cyclic)""" - query = Chem.MolFromSmarts('[C;H1;D2]=[C;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_172(mol): - """CH=C(cyclic)""" - query = Chem.MolFromSmarts('[C;H1;D2]=[C;H0;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_173(mol): - """C=C(cyclic)""" - query = Chem.MolFromSmarts('[C;H0]=[C;H0;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_174(mol): - """CH2=C(cyclic)""" - query = Chem.MolFromSmarts('[C;H2;D1]=[C;H0;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_175(mol): - """NH(cyclic)""" - query = Chem.MolFromSmarts('[N;H1;D2;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_176(mol): - """N(cyclic)""" - query = Chem.MolFromSmarts('[N;H0;D3;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_177(mol): # todo 芳香性 - """CH=N(cyclic)""" - # '[C,c;H1]=,:[N,n;H0;D2;R]' - query1 = Chem.MolFromSmarts('[C;H1]=[N;H0;D2;R]') - match_list = mol.GetSubstructMatches(query1) - return len(match_list), match_list - - -def f_178(mol): # todo 芳香性 - """C=N(cyclic)""" - query = Chem.MolFromSmarts('[C;H0]=[N;H0;D2;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_179(mol): - """O(cyclic)""" - query = Chem.MolFromSmarts('[O;H0;D2;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_180(mol): - """CO(cyclic)""" - query = Chem.MolFromSmarts('[C;H0;R;D3]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_181(mol): # todo 芳香性 - """S(cyclic)""" - query = Chem.MolFromSmarts('[S,s;H0;D2;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_182(mol): # todo 芳香性 - """SO2(cyclic)""" - query = Chem.MolFromSmarts('[O;H0;D1;!R]=[S,s;H0;D4;R]=[O;H0;D1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -# todo 183-220都没有官方的例子 -def f_183(mol): - """>NH""" - query = Chem.MolFromSmarts('[N;H1;D3;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_184(mol): - """-O-""" - query = Chem.MolFromSmarts('[O;H0;D2;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_185(mol): - """-S-""" - query = Chem.MolFromSmarts('[S;H0;D2;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_186(mol): - """>CO""" - query = Chem.MolFromSmarts('[C;H0;D3;!R]=[O;H0;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_187(mol): - """PO2""" - query = Chem.MolFromSmarts('[O;H0;!R][P;!R][O;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_188(mol): - """CH-N""" - query = Chem.MolFromSmarts('[C;H1;!R]-[N;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_189(mol): - """SiHO""" - query = Chem.MolFromSmarts('[Si;H1;!R]=[O;H0;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_190(mol): - """SiO""" - query = Chem.MolFromSmarts('[Si;H0;!R]=[O;H0;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_191(mol): - """SiH2""" - query = Chem.MolFromSmarts('[Si;H2;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_192(mol): - """SiH1""" - query = Chem.MolFromSmarts('[Si;H1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_193(mol): - """Si""" - query = Chem.MolFromSmarts('[Si;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_194(mol): - """(CH3)3N""" - query = Chem.MolFromSmarts('[C;H3][N;!R]([C;H3])[C;H3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_195(mol): - """N=N""" - query = Chem.MolFromSmarts('[N;H0;!R]=[N;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_196(mol): - """Ccyc=N-""" - query = Chem.MolFromSmarts('[C;H0;R]=[N;H0;D2;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_197(mol): - """Ccyc=CH-""" - query = Chem.MolFromSmarts('[C;H0;R]=[C;H1;D2;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_198(mol): - """Ccyc=NH""" - query = Chem.MolFromSmarts('[C;H0;R]=[N;H1;D1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_199(mol): - """N=O""" - query = Chem.MolFromSmarts('[N;H0;!R]=[O;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_200(mol): - """Ccyc=C""" - query = Chem.MolFromSmarts('[C;H0;R]=[C;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_201(mol): - """P=O""" - query = Chem.MolFromSmarts('[P;H0;!R]=[O;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_202(mol): - """N=N""" - query = Chem.MolFromSmarts('[N;H0;!R]=[N;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_203(mol): - """C=NH""" - query = Chem.MolFromSmarts('[C;H0;!R]=[N;H1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_204(mol): - """>C=S""" - query = Chem.MolFromSmarts('[C;H0;!R;D3]=[S;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_205(mol): - """aC-CON""" - query = Chem.MolFromSmarts('[c]!@&-[C](=[O;H0;D1])[N;H0]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_206(mol): - """aC=O""" - query = Chem.MolFromSmarts('[c]!@&=[O;H0;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_207(mol): - """aN-""" - query = Chem.MolFromSmarts('[n;H0;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_208(mol): - """-Na""" - query = Chem.MolFromSmarts('[Na]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_209(mol): - """-K""" - query = Chem.MolFromSmarts('[K]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_210(mol): - """HCONH""" - query = Chem.MolFromSmarts('[C;H1;!R](=[O;H0;D1])[N;H1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_211(mol): - """CHOCH""" - query = Chem.MolFromSmarts('[C;H1;!R](=[O;H0;D1])[C;H1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_212(mol): - """C2O""" - query = Chem.MolFromSmarts('[C;H0;R]1[O;H0;R][C;H0;R]1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_213(mol): - """SiH3""" - query = Chem.MolFromSmarts('[Si;H3;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_214(mol): # todo ? - """SiH2O""" - query = Chem.MolFromSmarts('[Si;H2;!R][O]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_215(mol): - """CH=C=CH""" - query = Chem.MolFromSmarts('[C;H1;!R]=[C;H0;!R]=[C;H1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_216(mol): - """CH=C=C""" - query = Chem.MolFromSmarts('[C;H1;!R]=[C;H0;!R]=[C;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_217(mol): - """OP(=S)O""" - query = Chem.MolFromSmarts('[O;H0][P;H0](=[S;H0])[O;H0]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_218(mol): # todo ? - """R""" - query = Chem.MolFromSmarts('[*]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_219(mol): - """CF2cyc""" - query = Chem.MolFromSmarts('[F][C;H0;R][F]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def f_220(mol): - """CFcyc""" - query = Chem.MolFromSmarts('[F][C;H0;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -# s order -def s_001(mol): - """(CH3)2CH""" - query = Chem.MolFromSmarts('[C;H3;!R;D1][C;H1;!R;D3][C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_002(mol): - """(CH3)3C""" - query = Chem.MolFromSmarts('[C;H3;!R;D1][C;H0;!R;D4]([C;H3;!R;D1])[C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_003(mol): # todo 能不能把多余的原子去掉? 好像没有必要 - """CH(CH3)CH(CH3)""" - query = Chem.MolFromSmarts('[*]-[C;H1;!R;D3]([C;H3;!R;D1])[C;H1;!R;D3]([C;H3;!R;D1])-[*]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_004(mol): # todo 能不能把多余的原子去掉 - """CH(CH3)C(CH3)2""" - query = Chem.MolFromSmarts('[*]-[C;H1;!R;D3]([C;H3;!R;D1])[C;H0;!R;D4]([*])([C;H3;!R;D1])[C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_005(mol): # todo 能不能把多余的原子去掉 - """C(CH3)2C(CH3)2""" - query = Chem.MolFromSmarts('[*]-[C;H0;!R;D4]([C;H3;!R;D1])([C;H3;!R;D1])[C;H0;!R;D4]([*])([C;H3;!R;D1])[C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_006(mol): - """CHn=CHm-CHp=CHk k,m,n,p=0,1,2""" - query = Chem.MolFromSmarts('[C;!R]=[C;!R]-[C;!R]=[C;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_007(mol): - """CH3-CHm=CHn m,n=0,1,2""" - query = Chem.MolFromSmarts('[C;H3;!R;D1]-[C;!R]=[C;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_008(mol): - """CH2-CHm=CHn m,n=0,1,2""" - query = Chem.MolFromSmarts('[C;H2;!R;D2]-[C;!R]=[C;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_009(mol): - """CHp-CHm=CHn p=0,1 m,n=0,1,2""" - query = Chem.MolFromSmarts('[C;!H2;!R]-[C;!R]=[C;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_010(mol): - """CHCHO or CCHO""" - query1 = Chem.MolFromSmarts('[C;H1;!R][C;H1;!R;D2]=[O;H0;D1]') - query2 = Chem.MolFromSmarts('[C;H0;!R][C;H1;!R;D2]=[O;H0;D1]') - match_list = mol.GetSubstructMatches(query1) + mol.GetSubstructMatches(query2) - return len(match_list), match_list - - -def s_011(mol): - """CH3COCH2""" - query = Chem.MolFromSmarts('[C;H3;!R;D1]-[C;H0;!R;D3](=[O;H0;!R;D1])-[C;H2;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_012(mol): - """CH3COCH or CH3COC""" - query = Chem.MolFromSmarts('[C;H3;!R;D1]-[C;H0;!R;D3](=[O;H0;!R;D1])-[C;H1,H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_013(mol): - """CHCOOH or CCOOH""" - query = Chem.MolFromSmarts('[O;H1;!R;D1]-[C;H0;!R;D3](=[O;H0;!R;D1])-[C;H1,H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_014(mol): - """CH3COOCH or CH3COOC""" - query = Chem.MolFromSmarts('[C;H3;!R;D1]-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H0;!R;D2]-[C;H1,H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_015(mol): - """CO-O-CO""" - query = Chem.MolFromSmarts('[O;H0;!R;D1]=[C;H0;!R;D3]-[O;H0;!R;D2]-[C;H0;!R;D3]=[O;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_016(mol): - """CHOH""" - query = Chem.MolFromSmarts('[C;H1;!R]-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_017(mol): - """COH""" - query = Chem.MolFromSmarts('[C;H0;!R]-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_018(mol): - """CH3COCHnOH n=0,1,2""" - query = Chem.MolFromSmarts('[C;H3;!R;D1]-[C;H0;!R;D3](=[O;H0;!R;D1])[C;H0,H1,H2;!R]-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_019(mol): - """NCCHOH or NCCOH""" - query = Chem.MolFromSmarts('[N;H0;!R;D1]#[C;H0;!R;D2][C;H0,H1;!R]-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_020(mol): - """OH-CHn-COO n=0,1,2""" - query = Chem.MolFromSmarts('[O;H1;!R;D1]-[C;H0,H1,H2;!R]-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_021(mol): - """CHm(OH)CHn(OH) m,n=0,1,2""" - query = Chem.MolFromSmarts('[O;H1;!R;D1]-[C;H0,H1,H2;!R]-[C;H0,H1,H2;!R]-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_022(mol): - """CHm(OH)CHn(NHp) m,n,p=0,1,2""" - query = Chem.MolFromSmarts('[O;H1;!R;D1]-[C;H0,H1,H2;!R]-[C;H0,H1,H2;!R]-[N;H0,H1,H2;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_023(mol): - """CHm(NH2)CHn(NH2) m,n=0,1,2""" - query = Chem.MolFromSmarts('[N;H2;!R;D1]-[C;H0,H1,H2;!R]-[C;H0,H1,H2;!R]-[N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_024(mol): - """CHm(NH)CHn(NH2) m,n=0,1,2""" - query = Chem.MolFromSmarts('[N;H;!R;D2]-[C;H0,H1,H2;!R]-[C;H0,H1,H2;!R]-[N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_025(mol): - """H2NCOCHnCHmCONH2 m,n=0,1,2""" - query = Chem.MolFromSmarts( - '[N;H2;!R;D1]-[C;H0;!R;D3](=[O;H0;!R;D1])-[C;H0,H1,H2;!R][C;H0,H1,H2;!R]-[C;H0;!R;D3](=[O;H0;!R;D1])-[N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_026(mol): - """CHm(NHn)-COOH m,n=0,1,2""" - query = Chem.MolFromSmarts('[N;H0,H1,H2;!R]-[C;H0,H1,H2;!R]-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_027(mol): - """HOOC-CHn-COOH n=1,2""" - query = Chem.MolFromSmarts( - '[C;H1,H2;!R](-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1])-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_028(mol): - """HOOC-CHn-CHm-COOH m,n=1,2""" - query = Chem.MolFromSmarts( - '[C;H1,H2;!R](-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1])-[C;H1,H2;!R](-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_029(mol): - """HO-CHn-COOH n=0,1,2""" - query = Chem.MolFromSmarts('[O;H1;!R;D1][C;H0,H1,H2;!R]-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_030(mol): - """NH2-CHn-CHm-COOH m,n=1,2""" - query = Chem.MolFromSmarts('[C;H1,H2;!R]([N;H2;!R;D1])-[C;H1,H2;!R](-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_031(mol): - """CH3-O-CHn-COOH n=1,2""" - query = Chem.MolFromSmarts('[C;H3;!R;D1]-[O;H0;!R;D2]-[C;H1,H2;!R](-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_032(mol): - """HS-CH-COOH""" - query = Chem.MolFromSmarts('[S;H1;!R;D1][C;H1;!R](-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_033(mol): - """HS-CHn-CHm-COOH m,n=1,2""" - query = Chem.MolFromSmarts('[C;H1,H2;!R]([S;H1;!R;D1])-[C;H1,H2;!R](-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_034(mol): - """NC-CHn-CHm-CN m,n=1,2""" - query = Chem.MolFromSmarts('[C;H1,H2;!R]([C;H0;!R;D2]#[N;H0;!R;D1])-[C;H1,H2;!R]([C;H0;!R;D2]#[N;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_035(mol): - """OH-CHn-CHm-CN m,n=1,2""" - query = Chem.MolFromSmarts('[C;H1,H2;!R]([O;H1;!R;D1])-[C;H1,H2;!R]([C;H0;!R;D2]#[N;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_036(mol): - """HS-CHn-CHm-SH m,n=1,2""" - query = Chem.MolFromSmarts('[C;H1,H2;!R]([S;H1;!R;D1])-[C;H1,H2;!R]([S;H1;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_037(mol): - """COO-CHn-CHm-OOC m,n=1,2""" - query = Chem.MolFromSmarts( - '[C;H1,H2;!R]([O;H0;!R;D2][C;H0;!R;D3](=[O;H0;!R;D1]))-[C;H1,H2;!R]([O;H0;!R;D2][C;H0;!R;D3](=[O;H0;!R;D1]))') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_038(mol): - """OOC-CHn-CHm-COO m,n=1,2""" - query = Chem.MolFromSmarts( - '[C;H1,H2;!R]([C;H0;D3;!R](=[O;H0;!R;D1])[O;H0;!R;D2])-[C;H1,H2;!R]([C;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_039(mol): - """NC-CHn-COO n=1,2""" - query = Chem.MolFromSmarts('[C;H1,H2;!R]([C;H0;!R;D2]#[N;H0;!R;D1])([C;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_040(mol): - """COCHnCOO n=1,2""" - query = Chem.MolFromSmarts('[C;H1,H2;!R]([C;H0;!R;D3](=[O;H0;!R;D1]))([C;H0;D3;!R](=[O;H0;!R;D1])[O;H0;!R;D2])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_041(mol): - """CHm-O-CHn=CHp m,n,p=0,1,2,3""" - query = Chem.MolFromSmarts('[C;H0,H1,H2,H3;!R]-[O;H0;!R;D2]-[C;H0,H1,H2,H3;!R]=[C;H0,H1,H2,H3;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_042(mol): - """CHm=CHn-F m,n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;!R]=[C;H0,H1,H2;!R]-F') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_043(mol): - """CHm=CHn-Br m,n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;!R]=[C;H0,H1,H2;!R]-Br') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_044(mol): - """CHm=CHn-I m,n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;!R]=[C;H0,H1,H2;!R]-I') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_045(mol): - """CHm=CHn-Cl m,n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;!R]=[C;H0,H1,H2;!R]-Cl') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_046(mol): - """CHm=CHn-CN m,n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;!R]=[C;H0,H1,H2;!R]-[C;H0;!R;D2]#[N;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_047(mol): - """CHm=CHn-COO-CHp m,n,p=0,1,2,3""" - query = Chem.MolFromSmarts( - '[C;H0,H1,H2;!R]=[C;H0,H1,H2;!R]-[C;H0;!R](=[O;H0;D1;!R])[O;H0;!R;D2]-[C;H0,H1,H2,H3;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_048(mol): - """CHm=CHn-CHO m,n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;!R]=[C;H0,H1,H2;!R]-[C;H1;!R;D2](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_049(mol): - """CHm=CHn-COOH m,n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;!R]=[C;H0,H1,H2;!R](-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_050(mol): - """aC-CHn-X n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]-[F,Cl,Br,I]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_051(mol): - """aC-CHn-NHm n=1,2 m=0,1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]-[N;H0,H1,H2;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_052(mol): - """aC-CHn-O- n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]-[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_053(mol): - """aC-CHn-OH n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_054(mol): - """aC-CHn-CN n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]-[C;H0;!R;D2]#[N;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_055(mol): - """aC-CHn-CHO n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]-[C;H1;!R;D2](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_056(mol): - """aC-CHn-SH n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]-[S;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_057(mol): - """aC-CHn-COOH n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R](-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_058(mol): - """aC-CHn-CO- n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]-[C;H0;!R;D3](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_059(mol): - """aC-CHn-S- n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]-[S;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_060(mol): - """aC-CHn-OOC-H n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]([O;H0;!R;D2][C;H1;!R;D2](=[O;H0;!R;D1]))') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_061(mol): - """aC-CHn-NO2 n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]-[N;+]([O;-])=[O]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_062(mol): - """aC-CHn-CONH2 n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]-[C;H0;!R;D3](=[O;H0;!R;D1])[N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_063(mol): - """aC-CHn-OOC n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]([O;H0;!R;D2][C;H0;!R;D3](=[O;H0;!R;D1]))') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_064(mol): - """aC-CHn-COO n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H1,H2;!R]([C;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_065(mol): - """aC-SO2-OH n=1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[S;H0;!R;D4](=[O;H0;!R;D1])(=[O;H0;!R;D1])[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_066(mol): - """aC-CH(CH3)2""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H1;!R;D3]([C;H3;!R;D1])([C;H3;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_067(mol): - """aC-C(CH3)3""" - query = Chem.MolFromSmarts('[c;H0;R;D3][C;H0;!R;D4]([C;H3;!R;D1])([C;H3;!R;D1])([C;H3;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_068(mol): - """aC-CF3""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H0;!R;D4](F)(F)F') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_069(mol): # todo 芳香性 - """(CHn=C)(cyclic)-CHO n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;R]=[C;H0;R;D3]-[C;H1;!R;D2](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_070(mol): # todo 芳香性 - """(CHn=C)(cyclic)-COO-CHm m,n=0,1,2,3""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;R]=[C;H0;R;D3]-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H0;!R;D2]-[C;H0,H1,H2,H3;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_071(mol): # todo 芳香性 - """(CHn=C)(cyclic)-CO- n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;R]=[C;H0;R;D3]-[C;H0;!R;D3](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_072(mol): # todo 芳香性 - """(CHn=C)cyc-CH3, n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;R]=[C;H0;R;D3]-[C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_073(mol): # todo 芳香性 - """(CHn=C)cyc-CH2, n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;R]=[C;H0;R;D3]-[C;H2;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_074(mol): # todo 芳香性 - """(CHn=C)(cyclic)-CN n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;R]=[C;H0;R;D3]-[C;H0;!R;D2]#[N;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_075(mol): # todo 芳香性 - """(CHn=C)(cyclic)-Cl n=0,1,2""" - query = Chem.MolFromSmarts('[C;H0,H1,H2;R]=[C;H0;R;D3]-Cl') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_076(mol): - """CHcyc-CH3""" - query = Chem.MolFromSmarts('[C;H1;R;D3][C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_077(mol): - """CHcyc-CH2""" - query = Chem.MolFromSmarts('[C;H1;R;D3][C;H2;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_078(mol): - """CHcyc-CH""" - query = Chem.MolFromSmarts('[C;H1;R;D3][C;H1;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_079(mol): - """CHcyc-C""" - query = Chem.MolFromSmarts('[C;H1;R;D3][C;H0;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_080(mol): - """CHcyc-CH=CHn n=1,2""" - query = Chem.MolFromSmarts('[C;H1;R;D3][C;H1;!R;D2]=[C;H1,H2;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_081(mol): - """CHcyc-C=CHn n=1,2""" - query = Chem.MolFromSmarts('[C;H1;R;D3][C;H0;!R;D3]=[C;H1,H2;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_082(mol): - """CHcyc-Cl""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-Cl') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_083(mol): - """CHcyc-F""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-F') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_084(mol): - """CHcyc-OH""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_085(mol): - """CHcyc-NH2""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-[N;H2;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_086(mol): - """CHcyc-NH-CHn n=0,1,2,3""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-[N;H1;!R;D2]-[C;H0,H1,H2,H3;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_087(mol): - """CHcyc-N-CHn n=0,1,2,3""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-[N;H0;!R;D3]-[C;H0,H1,H2,H3;!R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_088(mol): - """CHcyc-SH""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-[S;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_089(mol): - """CHcyc-CN""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-[C;H0;!R;D2]#[N;H0;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_090(mol): - """CHcyc-COOH""" - query = Chem.MolFromSmarts('[C;H1;R;D3](-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_091(mol): - """CHcyc-CO-""" - query = Chem.MolFromSmarts('[C;H1;R;D3][C;H0;!R;D3](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_092(mol): - """CHcyc-NO2""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-[N;+]([O;-])=[O]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_093(mol): - """CHcyc-S-""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-[S;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_094(mol): - """CHcyc-CHO""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-[C;H1;!R;D2](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_095(mol): - """CHcyc-O-""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-[O;H0;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_096(mol): - """CHcyc-OOCH""" - query = Chem.MolFromSmarts('[C;H1;R;D3]([O;H0;!R;D2][C;H1;!R;D2](=[O;H0;!R;D1]))') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_097(mol): - """CHcyc-COO""" - query = Chem.MolFromSmarts('[C;H1;R;D3]([C;H0;!R;D3](=[O;H0;!R;D1])[O;H0;!R;D2])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_098(mol): - """CHcyc-OOC""" - query = Chem.MolFromSmarts('[C;H1;R;D3]([O;H0;!R;D2][C;H0;!R;D3](=[O;H0;!R;D1]))') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_099(mol): - """Ccyc-CH3 - 环上的C要求D4是因为前面有s72 - """ - query = Chem.MolFromSmarts('[C;H0;R;D4][C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_100(mol): - """Ccyc-CH2 - 环上的C要求D4是因为前面有s73 - """ - query = Chem.MolFromSmarts('[C;H0;R;D4][C;H2;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_101(mol): - """Ccyc-OH""" - query = Chem.MolFromSmarts('[C;H0;R;D4][O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_102(mol): - """>Ncyc-CH3""" - query = Chem.MolFromSmarts('[N,n;H0;D3;R]-[C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_103(mol): - """>Ncyc-CH2""" - query = Chem.MolFromSmarts('[N,n;H0;D3;R]-[C;H2;!R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_104(mol): - """AROMRINGs1s2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2][c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_105(mol): - """AROMRINGs1s3""" - query = Chem.MolFromSmarts('[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_106(mol): - """AROMRINGs1s4""" - query = Chem.MolFromSmarts('[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_107(mol): - """AROMRINGs1s2s3""" - query = Chem.MolFromSmarts('[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_108(mol): - """AROMRINGs1s2s4""" - query = Chem.MolFromSmarts('[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_109(mol): - """AROMRINGs1s3s5""" - query = Chem.MolFromSmarts('[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_110(mol): - """AROMRINGs1s2s3s4""" - query = Chem.MolFromSmarts( - '[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_111(mol): - """AROMRINGs1s2s3s5""" - query = Chem.MolFromSmarts( - '[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_112(mol): - """AROMRINGs1s2s4s5""" - query = Chem.MolFromSmarts( - '[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_113(mol): - """PYRIDINEs2""" - query = Chem.MolFromSmarts('[n;H0;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2][c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_114(mol): - """PYRIDINEs3""" - query = Chem.MolFromSmarts('[n;H0;R;D2][c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_115(mol): - """PYRIDINEs4""" - query = Chem.MolFromSmarts('[n;H0;R;D2][c;H1;R;D2][c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_116(mol): - """PYRIDINEs2s3""" - query = Chem.MolFromSmarts('[n;H0;R;D2][c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_117(mol): - """PYRIDINEs2s4""" - query = Chem.MolFromSmarts('[n;H0;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_118(mol): - """PYRIDINEs2s5""" - query = Chem.MolFromSmarts('[n;H0;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_119(mol): - """PYRIDINEs2s6""" - query = Chem.MolFromSmarts('[n;H0;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2][c;H1;R;D2][c;H0;R;D3]([!a;!R])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_120(mol): - """PYRIDINEs3s4""" - query = Chem.MolFromSmarts('[n;H0;R;D2][c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_121(mol): - """PYRIDINEs3s5""" - query = Chem.MolFromSmarts('[n;H0;R;D2][c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H0;R;D3]([!a;!R])[c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_122(mol): - """PYRIDINEs2s3s6""" - query = Chem.MolFromSmarts('[n;H0;R;D2][c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H1;R;D2][c;H1;R;D2][c;H0;R;D3]([!a;!R])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_123(mol): - """(CHn=CHm)cyc-COOH""" - query = Chem.MolFromSmarts('[C;R]@&=[C;R]!@&-[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H1;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_124(mol): - """AROMRINGs1s2s3s4s5""" - # query = Chem.MolFromSmarts('[c]1[c](!@&-[*])[c](!@&-[*])[c](!@&-[*])[c](!@&-[*])[c]1(!@&-[*])') - query = Chem.MolFromSmarts( - '[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H0;R;D3]([!a;!R])[c;H1;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_125(mol): - """aC-NHCOCH2N""" - query = Chem.MolFromSmarts('[c;H0;R;D3]!@&-[N;H1]-[C;H0](=[O;H0;D1])[C;H2][N]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_126(mol): - """(N=C)cyc-CH3""" - query = Chem.MolFromSmarts('[N;R]@&=[C;R]!@&-[C;H3;!R;D1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_127(mol): - """aC-CONH(CH2)2N""" - query = Chem.MolFromSmarts('[c]!@&-[C](=[O;H0;D1])[N;H1][C;H2][C;H2][N]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_128(mol): # todo 原文128、129、130完全一样,不知道怎么回事 - """aC-SO2NHn n=0,1,2""" - query = Chem.MolFromSmarts('[c]!@&-[S](=[O;H0;D1])(=[O;H0;D1])[N;H0,H1,H2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_129(mol): # todo 原文128、129、130完全一样,不知道怎么回事 - """aC-SO2NHn n=0,1,2""" - query = Chem.MolFromSmarts('[c]!@&-[S](=[O;H0;D1])(=[O;H0;D1])[N;H0,H1,H2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def s_130(mol): # todo 原文128、129、130完全一样,不知道怎么回事 - """aC-SO2NHn n=0,1,2""" - query = Chem.MolFromSmarts('[c]!@&-[S](=[O;H0;D1])(=[O;H0;D1])[N;H0,H1,H2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -# t order -def t_001(mol): - """HOOC-(CHn)m-COOH m>2,n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(3, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts( - '[C;H0;!R;D3](=[O;H0;!R;D1])(-[O;H1;!R;D1]){}-[C;H0;!R;D3](=[O;H0;!R;D1])(-[O;H1;!R;D1])'.format( - '-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_002(mol): - """NHn-(CHn)m-COOH m>2,n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(3, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts( - '[N;H0,H1,H2;!R]{}-[C;H0;!R;D3](=[O;H0;!R;D1])(-[O;H1;!R;D1])'.format('-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_003(mol): - """NH2-(CHn)m-OH m>2,n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(3, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts('[N;H2;!R;D1]{}-[O;H1;!R;D1]'.format('-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_004(mol): - """OH-(CHn)m-OH m>2,n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(3, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts('[O;H1;!R;D1]{}-[O;H1;!R;D1]'.format('-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_005(mol): - """OH-(CHp)k-O-(CHn)m-OH m,k>0;p,n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(1, len(not_in_ring_atoms) + 1): - for j in range(1, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts( - '[O;H1;!R;D1]{}-[O;H0;D2;!R]{}-[O;H1;!R;D1]'.format('-[C;H0,H1,H2;!R]' * i, '-[C;H0,H1,H2;!R]' * j)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_006(mol): - """OH-(CHp)k-S-(CHn)m-OH m,k>0;p,n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(1, len(not_in_ring_atoms) + 1): - for j in range(1, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts( - '[O;H1;!R;D1]{}-[S;H0;D2;!R]{}-[O;H1;!R;D1]'.format('-[C;H0,H1,H2;!R]' * i, '-[C;H0,H1,H2;!R]' * j)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_007(mol): - """OH-(CHp)k-NHx-(CHn)m-OH m,k>0;x,p,n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(1, len(not_in_ring_atoms) + 1): - for j in range(1, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts( - '[O;H1;!R;D1]{}-[N;H0,H1,H2;!R]{}-[O;H1;!R;D1]'.format('-[C;H0,H1,H2;!R]' * i, '-[C;H0,H1,H2;!R]' * j)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_008(mol): - """CHp-O-(CHn)m-OH m>2;p,n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(3, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts('[C;H0,H1,H2;!R][O;H0;!R;D2]{}-[O;H1;!R;D1]'.format('-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_009(mol): - """NH2-(CHn)m-NH2 m>2,n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(3, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts('[N;H2;!R;D1]{}-[N;H2;!R;D1]'.format('-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_010(mol): - """NHk-(CHn)m-NH2 m>2;n=0,1,2;k=0,1""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(3, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts('[N;H0,H1;!R]{}-[N;H2;!R;D1]'.format('-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_011(mol): - """SH-(CHn)m-SH m>2;n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(3, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts('[S;H1;!R;D1]{}-[S;H1;!R;D1]'.format('-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_012(mol): - """CN-(CHn)m-CN m>2;n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(3, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts('[C;H0;!R;D2](#[N;H0;!R;D1]){}-[C;H0;!R;D2](#[N;H0;!R;D1])'.format('-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_013(mol): - """COO-(CHn)m-OOC m>2;n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(3, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts( - '[C;H0;!R;D3](=[O;H0;!R;D1])-[O;H0;!R;D2]{}-[O;H0;!R;D2]-[C;H0;!R;D3](=[O;H0;!R;D1])'.format( - '-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_014(mol): - """aC-(CHn=CHm)cyc (fused rings) m,n=0,1""" - query = Chem.MolFromSmarts('[c;H0;R2;D3]@&-[C;H1,H2;R]@&=[C;H1,H2;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_015(mol): - """aC-aC (different rings)""" - query = Chem.MolFromSmarts('[c;H0;R1;D3]!@&-[c;H0;R1;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_016(mol): - """aC-CHncyc (different rings) n=0,1""" - query = Chem.MolFromSmarts('[c;H0;R1;D3]!@&-[C;H0,H1;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_017(mol): - """aC-CHncyc (fused rings) n=0,1,2原文写的0,1应该是不对的""" - query = Chem.MolFromSmarts('[c;H0;R2;D3]@&-[C;H0,H1,H2;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_018(mol): - """aC-(CHn)m-aC m>1 n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(2, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts('[c;H0;R;D3]{}-[c;H0;R;D3]'.format('-[C;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_019(mol): - """aC-(CHn)m-CHcyc m>1 n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(1, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts('[c;H0;R;D3]{}-[C;H1;R;D3]'.format('-[C;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_020(mol): - """CHcyc-CHcyc (different rings)""" - query = Chem.MolFromSmarts('[C;H1;R;D3]-&!@[C;H1;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_021(mol): - """CHcyc-(CHn)m-CHcyc (different rings) m>0, n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(1, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts('[C;H1;R;D3]{}!@&-[C;H1;R;D3]'.format('!@&-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_022(mol): - """CH multiring""" - query = Chem.MolFromSmarts('[C;H1;!R1&R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_023(mol): - """C multiring""" - query = Chem.MolFromSmarts('[C;H0;!R1&R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_024(mol): - """aC-CHm-aC (different rings) m=0,1,2""" - query = Chem.MolFromSmarts('[c;H0;R1]-[C;H0,H1,H2;!R]-[c;H0;R1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_025(mol): - """aC-(CHm=CHn)-aC (different rings) m,n=0,1,2""" - query = Chem.MolFromSmarts('[c;H0;R1;D3]-[C;H0,H1,H2;!R]=[C;H0,H1,H2;!R]-[c;H0;R1;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_026(mol): # todo 芳香性 - """(CHm=C)cyc-CH=CH-(C=CHn)cyc (different rings) m,n没有限制""" - query = Chem.MolFromSmarts('[C;R]=[C;H0;R]-[C;H1;!R]=[C;H1;!R]-[C;H0;R]=[C;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_027(mol): # todo 芳香性 - """(CHm=C)cyc-CHp-(C=CHn)cyc (different rings) m,n,p没有限制""" - query = Chem.MolFromSmarts('[C;R]=[C;H0;R]-[C;!R]-[C;H0;R]=[C;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_028(mol): - """aC-CO-aC (different rings)""" - query = Chem.MolFromSmarts('[c;H0;R1;D3]-[C;H0;!R;D3](=[O;H0;!R;D1])-[c;H0;R1;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_029(mol): - """aC-CHm-CO-aC (different rings) m=0,1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H0,H1,H2;!R]-[C;H0;!R;D3](=[O;H0;!R;D1])-[c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_030(mol): # todo 芳香性 - """aC-CO-(C=CHn)cyc (different rings)""" - query = Chem.MolFromSmarts('[c;H0;R1;D3]-[C;H0;!R;D3](=[O;H0;!R;D1])-[C;H0;R;D3]=[C;H0,H1;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_031(mol): - """aC-CO-CO-aC (different rings)""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H0;!R;D3](=[O;H0;!R;D1])-[C;H0;!R;D3](=[O;H0;!R;D1])-[c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_032(mol): - """aC-COcyc (fused rings)""" - query = Chem.MolFromSmarts('[c;H0;R2;D3]@&-[C;R;H0;D3](=[O;H0;!R;D1])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_033(mol): - """aC-CO-(CHn)m-CO-aC (different rings) m>0;n=0,1,2""" - not_in_ring_atoms = [atom for atom in mol.GetAtoms() if not atom.IsInRing()] - match_list = () - for i in range(1, len(not_in_ring_atoms) + 1): - query = Chem.MolFromSmarts( - '[c;H0;R;D3]-[C;H0;!R;D3](=[O;H0;!R;D1]){}-[C;H0;!R;D3](=[O;H0;!R;D1])-[c;H0;R;D3]'.format('-[C;H0,H1,H2;!R]' * i)) - match_list += mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_034(mol): - """aC-CO-CHn,cyc (different rings) n=0,1""" - query = Chem.MolFromSmarts('[c;H0;R1;D3]-[C;H0;!R;D3](=[O;H0;!R;D1])-[C;H0,H1;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_035(mol): - """aC-CO-NHn-aC (different rings) n=0,1""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H0;!R;D3](=[O;H0;!R;D1])-[N;H0,H1;!R]-[c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_036(mol): - """aC-NHnCONHm-aC (different rings) m,n=0,1""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H0,H1;!R]-[C;H0;!R;D3](=[O;H0;!R;D1])-[N;H0,H1;!R]-[c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_037(mol): - """aC-CO-Ncyc (different rings)""" - query = Chem.MolFromSmarts('[c;H0;R;D3]!@&-[C;H0;!R;D3](=[O;H0;!R;D1])!@&-[N;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_038(mol): - """aC-Scyc (fused rings)""" - query = Chem.MolFromSmarts('[c;H0;R2;D3]@[S,s;H0;D2;R1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_039(mol): - """aC-S-aC (different rings)""" - query = Chem.MolFromSmarts('[c;H0;R1;D3]!@&-[S;H0;D2;!R]!@&-[c;H0;R1;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_040(mol): # todo ? - """aC-POn-aC (different rings) n=0,1,2,3,4""" - query0 = Chem.MolFromSmarts('[c;H0;R1;D3]-[P;H0,H1;!R]-[c;H0;R1;D3]') - query1 = Chem.MolFromSmarts('[c;H0;R1;D3]-[O;H0;D2;!R]-[P;!R]-[O;H0;D2;!R]-[c;H0;R1;D3]') - match_list = mol.GetSubstructMatches(query0) + mol.GetSubstructMatches(query1) - return len(match_list), match_list - - -def t_041(mol): - """aC-SOn-aC (different rings) n=1,2,3,4""" - query1 = Chem.MolFromSmarts('[c;H0;R1;D3]-[S;H0;D3;!R](=[O;!R;H0;D1])-[c;H0;R1;D3]') - query2 = Chem.MolFromSmarts('[c;H0;R1;D3]-[S;H0;D4;!R](=[O;!R;H0;D1])(=[O;!R;H0;D1])-[c;H0;R1;D3]') - query3 = Chem.MolFromSmarts('[c;H0;R1;D3]-[O;H0;D2;!R]-[S;H0;D3;!R](=[O;!R;H0;D1])-[O;H0;D2;!R]-[c;H0;R1;D3]') - query4 = Chem.MolFromSmarts( - '[c;H0;R1;D3]-[O;H0;D2;!R]-[S;H0;D4;!R](=[O;!R;H0;D1])(=[O;!R;H0;D1])-[O;H0;D2;!R]-[c;H0;R1;D3]') - match_list = mol.GetSubstructMatches(query1) + mol.GetSubstructMatches(query2) + mol.GetSubstructMatches( - query3) + mol.GetSubstructMatches(query4) - return len(match_list), match_list - - -def t_042(mol): - """aC-NHncyc (fused rings) n=0,1""" - query = Chem.MolFromSmarts('[c;H0;R2;D3]@[N,n;H0,H1;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_043(mol): - """aC-NH-aC (different rings)""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[N;H1;!R;D2]-[c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_044(mol): - """aC-(C=N)cyc (different rings)""" - query = Chem.MolFromSmarts('[c;H0;R;D3]!@&-[c,C;H0;R;D3]=,:[n,N;H0;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_045(mol): - """aC-(N=CHn)cyc (fused rings) n=0,1""" - # query = Chem.MolFromSmarts('[c][c;H0;R2]([c])-[N;H0;D2;R1]=[C;H0,H1;R]') - query = Chem.MolFromSmarts('[c;H0;R;D3]@[n,N;H0;R;D2]@,=,:[c,C;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_046(mol): - """aC-(N=CHn)cyc (fused rings) n=0,1""" - query = Chem.MolFromSmarts('[c;H0;R2;D3]@,=,:[c,C;H0,H1;R]@,=,:[n,N;H0;D2;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_047(mol): - """aC-O-CHn-aC (different rings) n=0,1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]!@,-[O;H0;!R;D2]!@,-[C;H0,H1,H2;!R]!@,-[c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_048(mol): - """aC-O-aC (different rings)""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[O;H0;!R;D2]-[c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_049(mol): - """aC-CHn-O-CHm-aC (different rings) m,n=0,1,2""" - query = Chem.MolFromSmarts('[c;H0;R;D3]-[C;H0,H1,H2;!R]-[O;H0;!R;D2]-[C;H0,H1,H2;!R]-[c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_050(mol): - """aC-Ocyc (fused rings)""" - query = Chem.MolFromSmarts('[c;H0;R2;D3]@[O,o;H0;R;D2]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_051(mol): - """AROMFUSED[2]""" - query = Chem.MolFromSmarts('[c]:[c;R2](:[c]):[c]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_052(mol): - """AROMFUSED[2]s1""" - query = Chem.MolFromSmarts('[c;R2]([c;H1]):[c;R2]([c;H1])c([C,O,N,P,S,F,Cl,Br,I])[c;H1][c;H1][c;H1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_053(mol): - """AROMFUSED[2]s2""" - query = Chem.MolFromSmarts('[*]!@[c]1[c;H1][c;R2]([c])[c;R2]([c])[c;H1][c;H1]1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_054(mol): - """AROMFUSED[2]s2s3""" - query = Chem.MolFromSmarts('[*]!@[c]1[c](!@[*])[c;H1][c;R2]([c])[c;R2]([c])[c;H1]1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_055(mol): - """AROMFUSED[2]s1s4""" - query = Chem.MolFromSmarts('[c;H1]1[c](!@[*])[c;R2]([c])[c;R2]([c])[c](!@[*])[c;H1]1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_056(mol): - """AROMFUSED[2]s1s2""" - query = Chem.MolFromSmarts('[c;H1]1[c](!@[*])[c](!@[*])[c;R2]([c])[c;R2]([c])[c;H1]1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_057(mol): - """AROMFUSED[2]s1s3""" - query = Chem.MolFromSmarts('[c;H1]1[c](!@[*])[c;H1][c;R2]([c])[c;R2]([c])[c](!@[*])1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_058(mol): - """AROMFUSED[3]""" - query = Chem.MolFromSmarts('[c][c;R2]1[c;R3]([c])[c;R2]([c])ccc1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_059(mol): - """AROMFUSED[4a]""" - query = Chem.MolFromSmarts('[c;H1][c;R2]1[c;R2]([c])[c;H1][c;R2]([c])[c;R2]([c])[c;H1]1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_060(mol): - """AROMFUSED[4a]s1""" - query = Chem.MolFromSmarts('[*]!@[c]1[c;R2]([c])[c;R2]([c])[c;H1][c;R2]([c])[c;R2]1([c])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_061(mol): - """AROMFUSED[4a]s1s4""" - query = Chem.MolFromSmarts('[*]!@[c]1[c;R2]([c])[c;R2]([c])[c](!@[*])[c;R2]([c])[c;R2]1([c])') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_062(mol): - """AROMFUSED[4p]""" - query = Chem.MolFromSmarts('[c;H1]1[c;!R1]([c])[c;!R1]([c])[c;!R1]([c])[c;!R1]([c])[c;H1]1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_063(mol): - """AROMFUSED[4p]s3s4""" - query = Chem.MolFromSmarts('[*]!@[c]1[c;!R1]([c])[c;!R1]([c])[c;!R1]([c])[c;!R1]([c])[c]1!@[*]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_064(mol): - """PYRIDINE.FUSED[2]""" - query = Chem.MolFromSmarts('[n;H0;R1;D2]1[c;H1;R1;D2][c;H1;R1;D2][c;H1;R1;D2][c;H0;R2;D3](a)[c;H0;R2;D3]1(a)') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_065(mol): - """PYRIDINE.FUSED[2-iso]""" - query = Chem.MolFromSmarts('[n;H0;R1;D2]1[c;H1;R1;D2][c;H1;R1;D2][c;H0;R2;D3](a)[c;H0;R2;D3](a)[c;H1;R1;D2]1') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_066(mol): - """PYRIDINE.FUSED[4]""" - query = Chem.MolFromSmarts('[n;H0;R1;D2]1[c;H0;R2;D3](a)[c;H0;R2;D3](a)[c;H1;R1;D2][c;H0;R2;D3](a)[c;H0;R2;D3]1(a)') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_067(mol): - """aC-N-CHcyc(different rings)""" - query = Chem.MolFromSmarts('[c;H0;R;D3]!@&-[N]!@&-[C;H1;R]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_068(mol): - """N multiring""" - query = Chem.MolFromSmarts('[N;R&!R1]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_069(mol): - """Ncyc-(CH2)3-Ncyc(different rings)""" - query = Chem.MolFromSmarts('[N;H0;R;D3]!@&-[C;H2][C;H2][C;H2]!@&-[N;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_070(mol): - """aC-COCH2CH2-aC(different rings)""" - query = Chem.MolFromSmarts('[c;H0;R;D3]!@&-[C;H0](=[O;H0;!R;D1])[C;H2][C;H2]!@&-[c;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_071(mol): - """aC-O-(CH2)2-Ncyc(different rings)""" - query = Chem.MolFromSmarts('[c;H0;R;D3]!@&-[O;H0;D2]-[C;H2][C;H2]!@&-[N;H0;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_072(mol): - """aC-CH(OH)(CH2)2-CHcyc(different rings)""" - query = Chem.MolFromSmarts('[c;H0;R;D3]!@&-[C;H1]([O;H1;!R;D1])-[C;H2][C;H2]!@&-[C;H1;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_073(mol): - """Ncyc-(CH2)2-CHcyc(different rings)""" - query = Chem.MolFromSmarts('[N;H0;R;D3]!@&-[C;H2][C;H2]!@&-[C;H1;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -def t_074(mol): - """aC-CONHCH2-CHcyc(different rings)""" - query = Chem.MolFromSmarts('[c;H0;R;D3]!@&-[C;H0;D3](=[O;H0;!R;D1])[N;H1;D2][C;H2]!@&-[C;H1;R;D3]') - match_list = mol.GetSubstructMatches(query) - return len(match_list), match_list - - -class Counter: - def __init__(self): - - self.init_result = { - 'f_001': 0, 'f_002': 0, 'f_003': 0, 'f_004': 0, 'f_005': 0, 'f_006': 0, 'f_007': 0, 'f_008': 0, 'f_009': 0, - 'f_010': 0, 'f_011': 0, 'f_012': 0, 'f_013': 0, 'f_014': 0, 'f_015': 0, 'f_016': 0, 'f_017': 0, 'f_018': 0, - 'f_019': 0, 'f_020': 0, 'f_021': 0, 'f_022': 0, 'f_023': 0, 'f_024': 0, 'f_025': 0, 'f_026': 0, 'f_027': 0, - 'f_028': 0, 'f_029': 0, 'f_030': 0, 'f_031': 0, 'f_032': 0, 'f_033': 0, 'f_034': 0, 'f_035': 0, 'f_036': 0, - 'f_037': 0, 'f_038': 0, 'f_039': 0, 'f_040': 0, 'f_041': 0, 'f_042': 0, 'f_043': 0, 'f_044': 0, 'f_045': 0, - 'f_046': 0, 'f_047': 0, 'f_048': 0, 'f_049': 0, 'f_050': 0, 'f_051': 0, 'f_052': 0, 'f_053': 0, 'f_054': 0, - 'f_055': 0, 'f_056': 0, 'f_057': 0, 'f_058': 0, 'f_059': 0, 'f_060': 0, 'f_061': 0, 'f_062': 0, 'f_063': 0, - 'f_064': 0, 'f_065': 0, 'f_066': 0, 'f_067': 0, 'f_068': 0, 'f_069': 0, 'f_070': 0, 'f_071': 0, 'f_072': 0, - 'f_073': 0, 'f_074': 0, 'f_075': 0, 'f_076': 0, 'f_077': 0, 'f_078': 0, 'f_079': 0, 'f_080': 0, 'f_081': 0, - 'f_082': 0, 'f_083': 0, 'f_084': 0, 'f_085': 0, 'f_086': 0, 'f_087': 0, 'f_088': 0, 'f_089': 0, 'f_090': 0, - 'f_091': 0, 'f_092': 0, 'f_093': 0, 'f_094': 0, 'f_095': 0, 'f_096': 0, 'f_097': 0, 'f_098': 0, 'f_099': 0, - 'f_100': 0, 'f_101': 0, 'f_102': 0, 'f_103': 0, 'f_104': 0, 'f_105': 0, 'f_106': 0, 'f_107': 0, 'f_108': 0, - 'f_109': 0, 'f_110': 0, 'f_111': 0, 'f_112': 0, 'f_113': 0, 'f_114': 0, 'f_115': 0, 'f_116': 0, 'f_117': 0, - 'f_118': 0, 'f_119': 0, 'f_120': 0, 'f_121': 0, 'f_122': 0, 'f_123': 0, 'f_124': 0, 'f_125': 0, 'f_126': 0, - 'f_127': 0, 'f_128': 0, 'f_129': 0, 'f_130': 0, 'f_131': 0, 'f_132': 0, 'f_133': 0, 'f_134': 0, 'f_135': 0, - 'f_136': 0, 'f_137': 0, 'f_138': 0, 'f_139': 0, 'f_140': 0, 'f_141': 0, 'f_142': 0, 'f_143': 0, 'f_144': 0, - 'f_145': 0, 'f_146': 0, 'f_147': 0, 'f_148': 0, 'f_149': 0, 'f_150': 0, 'f_151': 0, 'f_152': 0, 'f_153': 0, - 'f_154': 0, 'f_155': 0, 'f_156': 0, 'f_157': 0, 'f_158': 0, 'f_159': 0, 'f_160': 0, 'f_161': 0, 'f_162': 0, - 'f_163': 0, 'f_164': 0, 'f_165': 0, 'f_166': 0, 'f_167': 0, 'f_168': 0, 'f_169': 0, 'f_170': 0, 'f_171': 0, - 'f_172': 0, 'f_173': 0, 'f_174': 0, 'f_175': 0, 'f_176': 0, 'f_177': 0, 'f_178': 0, 'f_179': 0, 'f_180': 0, - 'f_181': 0, 'f_182': 0, 'f_183': 0, 'f_184': 0, 'f_185': 0, 'f_186': 0, 'f_187': 0, 'f_188': 0, 'f_189': 0, - 'f_190': 0, 'f_191': 0, 'f_192': 0, 'f_193': 0, 'f_194': 0, 'f_195': 0, 'f_196': 0, 'f_197': 0, 'f_198': 0, - 'f_199': 0, 'f_200': 0, 'f_201': 0, 'f_202': 0, 'f_203': 0, 'f_204': 0, 'f_205': 0, 'f_206': 0, 'f_207': 0, - 'f_208': 0, 'f_209': 0, 'f_210': 0, 'f_211': 0, 'f_212': 0, 'f_213': 0, 'f_214': 0, 'f_215': 0, 'f_216': 0, - 'f_217': 0, 'f_218': 0, 'f_219': 0, 'f_220': 0, - - 's_001': 0, 's_002': 0, 's_003': 0, 's_004': 0, 's_005': 0, 's_006': 0, 's_007': 0, 's_008': 0, 's_009': 0, - 's_010': 0, 's_011': 0, 's_012': 0, 's_013': 0, 's_014': 0, 's_015': 0, 's_016': 0, 's_017': 0, 's_018': 0, - 's_019': 0, 's_020': 0, 's_021': 0, 's_022': 0, 's_023': 0, 's_024': 0, 's_025': 0, 's_026': 0, 's_027': 0, - 's_028': 0, 's_029': 0, 's_030': 0, 's_031': 0, 's_032': 0, 's_033': 0, 's_034': 0, 's_035': 0, 's_036': 0, - 's_037': 0, 's_038': 0, 's_039': 0, 's_040': 0, 's_041': 0, 's_042': 0, 's_043': 0, 's_044': 0, 's_045': 0, - 's_046': 0, 's_047': 0, 's_048': 0, 's_049': 0, 's_050': 0, 's_051': 0, 's_052': 0, 's_053': 0, 's_054': 0, - 's_055': 0, 's_056': 0, 's_057': 0, 's_058': 0, 's_059': 0, 's_060': 0, 's_061': 0, 's_062': 0, 's_063': 0, - 's_064': 0, 's_065': 0, 's_066': 0, 's_067': 0, 's_068': 0, 's_069': 0, 's_070': 0, 's_071': 0, 's_072': 0, - 's_073': 0, 's_074': 0, 's_075': 0, 's_076': 0, 's_077': 0, 's_078': 0, 's_079': 0, 's_080': 0, 's_081': 0, - 's_082': 0, 's_083': 0, 's_084': 0, 's_085': 0, 's_086': 0, 's_087': 0, 's_088': 0, 's_089': 0, 's_090': 0, - 's_091': 0, 's_092': 0, 's_093': 0, 's_094': 0, 's_095': 0, 's_096': 0, 's_097': 0, 's_098': 0, 's_099': 0, - 's_100': 0, 's_101': 0, 's_102': 0, 's_103': 0, 's_104': 0, 's_105': 0, 's_106': 0, 's_107': 0, 's_108': 0, - 's_109': 0, 's_110': 0, 's_111': 0, 's_112': 0, 's_113': 0, 's_114': 0, 's_115': 0, 's_116': 0, 's_117': 0, - 's_118': 0, 's_119': 0, 's_120': 0, 's_121': 0, 's_122': 0, 's_123': 0, 's_124': 0, 's_125': 0, 's_126': 0, - 's_127': 0, 's_128': 0, 's_129': 0, 's_130': 0, - - 't_001': 0, 't_002': 0, 't_003': 0, 't_004': 0, 't_005': 0, 't_006': 0, 't_007': 0, 't_008': 0, 't_009': 0, - 't_010': 0, 't_011': 0, 't_012': 0, 't_013': 0, 't_014': 0, 't_015': 0, 't_016': 0, 't_017': 0, 't_018': 0, - 't_019': 0, 't_020': 0, 't_021': 0, 't_022': 0, 't_023': 0, 't_024': 0, 't_025': 0, 't_026': 0, 't_027': 0, - 't_028': 0, 't_029': 0, 't_030': 0, 't_031': 0, 't_032': 0, 't_033': 0, 't_034': 0, 't_035': 0, 't_036': 0, - 't_037': 0, 't_038': 0, 't_039': 0, 't_040': 0, 't_041': 0, 't_042': 0, 't_043': 0, 't_044': 0, 't_045': 0, - 't_046': 0, 't_047': 0, 't_048': 0, 't_049': 0, 't_050': 0, 't_051': 0, 't_052': 0, 't_053': 0, 't_054': 0, - 't_055': 0, 't_056': 0, 't_057': 0, 't_058': 0, 't_059': 0, 't_060': 0, 't_061': 0, 't_062': 0, 't_063': 0, - 't_064': 0, 't_065': 0, 't_066': 0, 't_067': 0, 't_068': 0, 't_069': 0, 't_070': 0, 't_071': 0, 't_072': 0, - 't_073': 0, 't_074': 0, - } - self.result = None - - self.f_order_group_function = \ - [ - f_001, f_002, f_003, f_004, f_005, f_006, f_007, f_008, f_009, f_010, f_011, f_012, f_013, f_014, f_015, - f_016, f_017, f_018, f_019, f_020, f_021, f_022, f_023, f_024, f_025, f_026, f_027, f_028, f_029, f_030, - f_031, f_032, f_033, f_034, f_035, f_036, f_037, f_038, f_039, f_040, f_041, f_042, f_043, f_044, f_045, - f_046, f_047, f_048, f_049, f_050, f_051, f_052, f_053, f_054, f_055, f_056, f_057, f_058, f_059, f_060, - f_061, f_062, f_063, f_064, f_065, f_066, f_067, f_068, f_069, f_070, f_071, f_072, f_073, f_074, f_075, - f_076, f_077, f_078, f_079, f_080, f_081, f_082, f_083, f_084, f_085, f_086, f_087, f_088, f_089, f_090, - f_091, f_092, f_093, f_094, f_095, f_096, f_097, f_098, f_099, f_100, f_101, f_102, f_103, f_104, f_105, - f_106, f_107, f_108, f_109, f_110, f_111, f_112, f_113, f_114, f_115, f_116, f_117, f_118, f_119, f_120, - f_121, f_122, f_123, f_124, f_125, f_126, f_127, f_128, f_129, f_130, f_131, f_132, f_133, f_134, f_135, - f_136, f_137, f_138, f_139, f_140, f_141, f_142, f_143, f_144, f_145, f_146, f_147, f_148, f_149, f_150, - f_151, f_152, f_153, f_154, f_155, f_156, f_157, f_158, f_159, f_160, f_161, f_162, f_163, f_164, f_165, - f_166, f_167, f_168, f_169, f_170, f_171, f_172, f_173, f_174, f_175, f_176, f_177, f_178, f_179, f_180, - f_181, f_182, f_183, f_184, f_185, f_186, f_187, f_188, f_189, f_190, f_191, f_192, f_193, f_194, f_195, - f_196, f_197, f_198, f_199, f_200, f_201, f_202, f_203, f_204, f_205, f_206, f_207, f_208, f_209, f_210, - f_211, f_212, f_213, f_214, f_215, f_216, f_217, f_218, f_219, f_220, - ] - self.s_order_group_function = \ - [ - s_001, s_002, s_003, s_004, s_005, s_006, s_007, s_008, s_009, s_010, s_011, s_012, s_013, s_014, s_015, - s_016, s_017, s_018, s_019, s_020, s_021, s_022, s_023, s_024, s_025, s_026, s_027, s_028, s_029, s_030, - s_031, s_032, s_033, s_034, s_035, s_036, s_037, s_038, s_039, s_040, s_041, s_042, s_043, s_044, s_045, - s_046, s_047, s_048, s_049, s_050, s_051, s_052, s_053, s_054, s_055, s_056, s_057, s_058, s_059, s_060, - s_061, s_062, s_063, s_064, s_065, s_066, s_067, s_068, s_069, s_070, s_071, s_072, s_073, s_074, s_075, - s_076, s_077, s_078, s_079, s_080, s_081, s_082, s_083, s_084, s_085, s_086, s_087, s_088, s_089, s_090, - s_091, s_092, s_093, s_094, s_095, s_096, s_097, s_098, s_099, s_100, s_101, s_102, s_103, s_104, s_105, - s_106, s_107, s_108, s_109, s_110, s_111, s_112, s_113, s_114, s_115, s_116, s_117, s_118, s_119, s_120, - s_121, s_122, s_123, s_124, s_125, s_126, s_127, s_128, s_129, s_130, - ] - self.t_order_group_function = \ - [ - t_001, t_002, t_003, t_004, t_005, t_006, t_007, t_008, t_009, t_010, t_011, t_012, t_013, t_014, t_015, - t_016, t_017, t_018, t_019, t_020, t_021, t_022, t_023, t_024, t_025, t_026, t_027, t_028, t_029, t_030, - t_031, t_032, t_033, t_034, t_035, t_036, t_037, t_038, t_039, t_040, t_041, t_042, t_043, t_044, t_045, - t_046, t_047, t_048, t_049, t_050, t_051, t_052, t_053, t_054, t_055, t_056, t_057, t_058, t_059, t_060, - t_061, t_062, t_063, t_064, t_065, t_066, t_067, t_068, t_069, t_070, t_071, t_072, t_073, t_074, - ] - group_order_file_path = os.path.join('gp_3x_internal_data', 'group_order.xlsx') - self.f_order_group_function_order = ( - pd.read_excel(group_order_file_path, sheet_name='f')['index'] - 1).tolist() # 减1是为了基团序号和列表索引对上,因为python列表里的索引是从0开始的 - self.s_order_group_function_order = ( - pd.read_excel(group_order_file_path, sheet_name='s')['index'] - 1).tolist() # 减1是为了基团序号和列表索引对上 - self.t_order_group_function_order = ( - pd.read_excel(group_order_file_path, sheet_name='t')['index'] - 1).tolist() # 减1是为了基团序号和列表索引对上 - - def count_a_mol(self, mol, clear_mode=False, add_note=False, add_smiles=False): - init_smi = mol - try: - if isinstance(mol, str): - mol = Chem.MolFromSmiles(mol) - self.result = self.init_result.copy() - if add_note: - # self.result['note'] = '' - self.result['note'] = Chem.MolToSmiles(mol) - if add_smiles: - self.result['smiles'] = Chem.MolToSmiles(mol) - self.count_1st_order_groups(mol=mol, add_note=add_note) - self.count_2nd_order_groups(mol=mol) - self.count_3rd_order_groups(mol=mol) - if clear_mode: - # 清爽模式,不显示没有统计到的基团 - self.result = {k: v for k, v in self.result.items() if v} - return self.result - except: - print(f'Error! There is something wrong when counting {init_smi}, please check it.') - return self.init_result.copy() - - def count_mols(self, smiles_file_path, count_result_file_path='count_result.csv', add_note=False, add_smiles=False): # todo mpi 并行? - print('reading the input file...') - if smiles_file_path.endswith('.txt'): - smiles_iterator = list(open(smiles_file_path)) - elif smiles_file_path.endswith('.xlsx'): - smiles_iterator = pd.read_excel(smiles_file_path)['smiles'] - elif smiles_file_path.endswith('.csv'): - smiles_iterator = pd.read_csv(smiles_file_path)['smiles'] - else: - raise NotImplementedError( - 'ERROR: The file type cannot be read, use the.txt/.xlsx/.csv file as the input file.') - - mol_number = len(smiles_iterator) - print('Done, totally detected {} molecules, start counting...'.format(mol_number)) - count_result_dict_list = [] - for i in tqdm(smiles_iterator): - count_result_dict_list.append(self.count_a_mol(i, add_note=add_note, add_smiles=add_smiles)) - print('Done!') - print('writing to csv...') - result = pd.DataFrame(count_result_dict_list) - result.to_csv(count_result_file_path, index_label='index') - print('Done!') - return result - - def count_mols_mpi(self, smiles_file_path, count_result_file_path='count_result.csv', add_note=False, add_smiles=False, n_jobs=1, batch_size='auto'): - print('reading the input file...') - if smiles_file_path.endswith('.txt'): - smiles_iterator = list(open(smiles_file_path)) - elif smiles_file_path.endswith('.xlsx'): - smiles_iterator = pd.read_excel(smiles_file_path)['smiles'] - elif smiles_file_path.endswith('.csv'): - smiles_iterator = pd.read_csv(smiles_file_path)['smiles'] - else: - raise NotImplementedError( - 'ERROR: The file type cannot be read, use the.txt/.xlsx/.csv file as the input file.') - - mol_number = len(smiles_iterator) - print('Done, totally detected {} molecules, start counting...'.format(mol_number)) - task = [delayed(self.count_a_mol)(i, add_note=add_note, add_smiles=add_smiles) for i in smiles_iterator] - count_result_dict_list = Parallel(n_jobs=n_jobs, batch_size=batch_size)(task) - print('Done!') - print('writing to csv...') - result = pd.DataFrame(count_result_dict_list) - result.to_csv(count_result_file_path, index_label='index') - print('Done!') - return result - - def get_group_fingerprint(self, mol): - count_result = self.count_a_mol(mol=mol, clear_mode=False, add_note=False) - return [value for key, value in count_result.items()] - - def count_1st_order_groups(self, mol, add_note=False): - atoms_index = set([i for i in range(len(mol.GetAtoms()))]) # 所有原子序号的集合 - used_atoms_index = set() - for function_index in self.f_order_group_function_order: - function = self.f_order_group_function[function_index] - _, tuple_of_match_tuples = function(mol) - for matched_index in tuple_of_match_tuples: - if used_atoms_index.intersection(matched_index) == set(): - used_atoms_index = used_atoms_index.union(matched_index) - self.result[function.__name__] += 1 - if used_atoms_index == atoms_index: - # early stop 统计第一顺序基团时,当所有原子都被使用后提前停止 - break - - if used_atoms_index != atoms_index: - warning = 'WARING: For {}, The first order groups do not cover the whole molecule! The results may not be reliable!'.format( - Chem.MolToSmiles(mol)) - print(warning) - if add_note: - if self.result.get('note', 0): - self.result['note'] += '\t' - self.result['note'] += warning - else: - self.result['note'] = warning - return None - - def count_2nd_order_groups(self, mol): - s_order_group_in_mol = [] - for function_index in self.s_order_group_function_order: - function = self.s_order_group_function[function_index] - _, tuple_of_match_tuples = function(mol) - for matched_index in tuple_of_match_tuples: - matched_index = set(matched_index) - for used in s_order_group_in_mol: # for else 语句 - if matched_index.issubset(used): - break - else: - s_order_group_in_mol.append(matched_index) - self.result[function.__name__] += 1 - return None - - def count_3rd_order_groups(self, mol): # 跟count_2nd_order_groups几乎完全一样 - t_order_group_in_mol = [] - for function_index in self.t_order_group_function_order: - function = self.t_order_group_function[function_index] - _, tuple_of_match_tuples = function(mol) - for matched_index in tuple_of_match_tuples: - matched_index = set(matched_index) - for used in t_order_group_in_mol: # for else 语句 - if matched_index.issubset(used): - break - else: - t_order_group_in_mol.append(matched_index) - self.result[function.__name__] += 1 - return None - - -# if __name__ == '__main__': -# print('debug gp_counter.py ...') -# import time -# -# t1 = time.time() -# # m = Chem.MolFromSmiles('Cc1ncc[nH]1') -# c = Counter() -# # result = c.count_a_mol(m, clear_mode=True) -# # print(result) -# # -# # print(c.get_group_fingerprint(m)) -# -# c.count_mols_mpi(smiles_file_path=os.path.join('gp_3x_test_mol', 'SMILES.txt'), count_result_file_path='count_result.csv', add_note=True, -# n_jobs=4, batch_size='auto') -# -# -# t2 = time.time() -# print(t2 - t1) \ No newline at end of file diff --git a/build/lib/src/gp_3x_generator.py b/build/lib/src/gp_3x_generator.py deleted file mode 100644 index 7902de2..0000000 --- a/build/lib/src/gp_3x_generator.py +++ /dev/null @@ -1,222 +0,0 @@ -from rdkit import Chem -import os -from tqdm import tqdm -from joblib import Parallel, delayed -import time -import random - -from gp_3x_convertor import Convertor -from gp_3x_tool import Tool - - -class Generator: - def __init__(self): - pass - - def calculate_charge(self, smi): - if isinstance(smi, str): - smi = Chem.MolFromSmiles(smi) - net_charge = 0 - all_charge = 0 - for i in smi.GetAtoms(): - net_charge += i.GetFormalCharge() - all_charge += i.GetAtomicNum() - - all_charge -= net_charge - - return net_charge, all_charge - - def calculate_multiplicity(self, smi): - if isinstance(smi, str): - smi = Chem.MolFromSmiles(smi) - net_charge, all_charge = self.calculate_charge(smi) - alpha_minus_beta = all_charge % 2 - multiplicity = alpha_minus_beta + 1 - return multiplicity - - def smi_to_gjf(self, smi, nproc='12', mem='12GB', chk_path=None, gjf_path=None, - gaussian_keywords=None, charge_and_multiplicity=None, - add_other_tasks=False, other_tasks:list=None): - try: - # default path of chk and gjf - if chk_path is None: - chk_path = '{}.chk'.format(smi) - if gjf_path is None: - gjf_path = '{}.gjf'.format(smi) - assert ('(' not in gjf_path) and (')' not in gjf_path), \ - 'gaussian dose not allow ( or ) in the name of .gjf and .chk files' - assert ('(' not in chk_path) and (')' not in chk_path), \ - 'gaussian dose not allow ( or ) in the name of .gjf and .chk files' - # default task - if gaussian_keywords is None: - gaussian_keywords = '#p opt freq b3lyp/6-31g*' - # default charge and multiplicity - if charge_and_multiplicity is None: - charge_and_multiplicity = f'{self.calculate_charge(smi)[0]} {self.calculate_multiplicity(smi)}' - # default other tasks - if other_tasks is None: - other_tasks = [ - '#p m062x/def2tzvp geom=check', - '#p m062x/def2tzvp scrf=solvent=water geom=check', - ] - - # read smi - c = Convertor() - temp_xyz_path = f'temp_{str(time.time()) + str(random.randint(0,1000000000000000000))}.xyz' - c.smi_to_xyz(smi=smi, xyz_path=temp_xyz_path) - - # write gjf - # 判断是否存在同名gjf - if os.path.exists(gjf_path): - os.remove(gjf_path) - self.write_gjf_link0_and_keyword(gjf_path=gjf_path, chk_path=chk_path, nproc=nproc, mem=mem, - gaussian_keywords=gaussian_keywords, - charge_and_multiplicity=charge_and_multiplicity, note=smi) - self.write_gjf_coord(gjf_path=gjf_path, xyz_path=temp_xyz_path) - - if add_other_tasks: - for task_index, task in enumerate(other_tasks): - i_chk_path = chk_path.split('.')[0] + f'_{task_index + 1}' + '.chk' - self.write_gjf_link0_and_keyword(gjf_path=gjf_path, chk_path=i_chk_path, nproc=nproc, mem=mem, - gaussian_keywords=task, charge_and_multiplicity=charge_and_multiplicity, - note=smi, old_chk_path=chk_path, add_link1=True) - self.write_gjf_blank_line(gjf_path=gjf_path, blank_line_number=2) - - # 删除临时xyz文件 - os.remove(temp_xyz_path) - return True - except: - print(f'Error! There is something wrong when converting {smi} to gjf file, please check it.') - return False - - def batch_smi_to_gjf(self, smiles_file_path, gjf_root_path=None, - nproc='12', mem='12GB', chk_path=None, gjf_path=None, - gaussian_keywords=None, charge_and_multiplicity=None, - add_other_tasks=False, other_tasks: list = None, - index_start=0, - ): - smiles_iterator = Tool.load_smiles_iterator(smiles_file_path=smiles_file_path) - mol_number = len(smiles_iterator) - zfill_number = len(str(mol_number)) + 5 - print('reading completed,A total of {} molecules detected, start calculating properties...'.format(mol_number)) - - # make gjf_root_path - if os.path.exists(gjf_root_path): - print('gjf_root_path "{}" has been detected!'.format(gjf_root_path)) - else: - print('gjf_root_path "{}" has not been detected, I will create it for you'.format(gjf_root_path)) - os.makedirs(gjf_root_path) - # end - - succeed = [] - fail = [] - for (index, smi) in tqdm(enumerate(smiles_iterator)): - smi = smi.strip() - index += index_start - chk_path = '{}.chk'.format(str(index).zfill(zfill_number)) - gjf_path = os.path.join(gjf_root_path, '{}.gjf'.format(str(index).zfill(zfill_number))) - generate_success_flag = self.smi_to_gjf(smi=smi, nproc=nproc, mem=mem, - chk_path=chk_path, gjf_path=gjf_path, - gaussian_keywords=gaussian_keywords, - charge_and_multiplicity=charge_and_multiplicity, - add_other_tasks=add_other_tasks, other_tasks=other_tasks, - ) - if not generate_success_flag: - fail.append(smi) - else: - succeed.append(smi) - - with open('gjf_fail.txt', 'w') as f: - for i in fail: - f.write(i + '\n') - with open('gjf_succeed.txt', 'w') as f: - for i in succeed: - f.write(i + '\n') - if len(fail) == 0: - print('done! all .gjf files has been saved in {}'.format(gjf_root_path)) - else: - print('Warning! The following SMILES fail to generate .gjf, please check...sorry(OTZ)') - print(fail) - return None - - def batch_smi_to_gjf_mpi(self, smiles_file_path, gjf_root_path=None, - nproc='12', mem='12GB', chk_path=None, gjf_path=None, - gaussian_keywords=None, charge_and_multiplicity=None, - add_other_tasks=False, other_tasks: list = None, - index_start=0, - n_jobs=1, batch_size='auto' - ): - smiles_iterator = Tool.load_smiles_iterator(smiles_file_path=smiles_file_path) - mol_number = len(smiles_iterator) - zfill_number = len(str(mol_number)) + 5 - print('reading completed,A total of {} molecules detected, start calculating properties...'.format(mol_number)) - - # make gjf_root_path - if os.path.exists(gjf_root_path): - print('gjf_root_path "{}" has been detected!'.format(gjf_root_path)) - else: - print('gjf_root_path "{}" has not been detected, I will create it for you'.format(gjf_root_path)) - os.makedirs(gjf_root_path) - - # task - task = [delayed(self.smi_to_gjf)(smi=smi, nproc=nproc, mem=mem, - chk_path='{}.chk'.format(str(index).zfill(zfill_number)), - gjf_path=os.path.join(gjf_root_path, '{}.gjf'.format(str(index).zfill(zfill_number))), - gaussian_keywords=gaussian_keywords, - charge_and_multiplicity=charge_and_multiplicity, - add_other_tasks=add_other_tasks, other_tasks=other_tasks,) - for index, smi in enumerate(smiles_iterator)] - result = Parallel(n_jobs=n_jobs, batch_size=batch_size)(task) - return result - - @staticmethod - def write_gjf_link0_and_keyword(gjf_path, chk_path, nproc, mem, gaussian_keywords, charge_and_multiplicity, note, - old_chk_path=None, add_link1=False): - with open(gjf_path, 'a') as gjf: - if add_link1: - gjf.write('--link1--' + '\n') - gjf.write(f'%nproc={nproc}' + '\n') - gjf.write(f'%mem={mem}' + '\n') - if old_chk_path is not None: - gjf.write(f'%oldchk={old_chk_path}' + '\n') - gjf.write(f'%chk={chk_path}' + '\n') - gjf.write(f'{gaussian_keywords}' + '\n') - gjf.write('\n') - gjf.write(f'{note}' + '\n') - gjf.write('\n') - gjf.write(f'{charge_and_multiplicity}' + '\n') - gjf.close() - return None - - @staticmethod - def write_gjf_coord(gjf_path, xyz_path): - xyz = open(xyz_path) - with open(gjf_path, 'a') as gjf: - for i in xyz.readlines()[2:]: - gjf.write(i) - gjf.write('\n\n') - return None - - @staticmethod - def write_gjf_blank_line(gjf_path, blank_line_number=1): - with open(gjf_path, 'a') as gjf: - gjf.write('\n' * blank_line_number) - return None - - - - - -# if __name__ == '__main__': -# -# import time -# g = Generator() -# t1 = time.time() -# # g.smi_to_gjf(smi='C1CCCC1', add_other_tasks=True) -# # g.batch_smi_to_gjf(smiles_file_path='gp_3x_test_mol/3018_with_error_smiles.txt', gjf_root_path='./test_gjf') -# g.batch_smi_to_gjf_mpi(smiles_file_path='gp_3x_test_mol/3018_with_error_smiles.txt', gjf_root_path='./test_gjf', -# add_other_tasks=True, -# n_jobs=8, batch_size='auto') -# -# t2 = time.time() -# print(t2 - t1) \ No newline at end of file diff --git a/build/lib/src/gp_3x_loader.py b/build/lib/src/gp_3x_loader.py deleted file mode 100644 index 31aa8a4..0000000 --- a/build/lib/src/gp_3x_loader.py +++ /dev/null @@ -1,31 +0,0 @@ -import pandas as pd -import os - - -class Loader: - def __init__(self, load_path=os.path.join('gp_3x_internal_data', 'group_contribution_parameters.xlsx')): - self.load_path = load_path - - def load_parameters(self, parameter_type='simultaneous', split=False): - assert parameter_type in ['simultaneous', 'step_wise'], '请确保参数类型为simultaneous或step_wise!' - step_wise_first_order = pd.read_excel(self.load_path, sheet_name='{}_first_order'.format(parameter_type), index_col='index').T.to_dict() - step_wise_second_order = pd.read_excel(self.load_path, sheet_name='{}_second_order'.format(parameter_type), index_col='index').T.to_dict() - step_wise_third_order = pd.read_excel(self.load_path, sheet_name='{}_third_order'.format(parameter_type), index_col='index').T.to_dict() - step_wise_universal_constants = pd.read_excel(self.load_path, sheet_name='{}_constants'.format(parameter_type), index_col='index').T.to_dict() - - if split: - return step_wise_first_order, step_wise_second_order, step_wise_third_order, step_wise_universal_constants - else: - return {**step_wise_first_order, **step_wise_second_order, **step_wise_third_order, **step_wise_universal_constants} - - -# if __name__ == '__main__': -# -# # debug -# loader = Loader() -# -# d = loader.load_parameters() -# for i in d: -# print(i) -# print(d[i]) - diff --git a/build/lib/src/gp_3x_tool.py b/build/lib/src/gp_3x_tool.py deleted file mode 100644 index a41bce3..0000000 --- a/build/lib/src/gp_3x_tool.py +++ /dev/null @@ -1,56 +0,0 @@ -import pandas as pd - - -class Tool: - def __init__(self): - pass - - def __repr__(self): - return 'This is a object implemented some useful tools' - - @staticmethod - def load_smiles_iterator(smiles_file_path): - print('reading input file...') - if smiles_file_path.endswith('.txt'): - smiles_iterator = list(open(smiles_file_path)) - elif smiles_file_path.endswith('.xlsx'): - smiles_iterator = pd.read_excel(smiles_file_path)['smiles'] - elif smiles_file_path.endswith('.csv'): - smiles_iterator = pd.read_csv(smiles_file_path)['smiles'] - else: - raise NotImplemented('无法识别的文件类型,请以.txt/.xlsx/.csv类型的文件作为输入。') - smiles_iterator = [i.strip() for i in smiles_iterator] - return smiles_iterator - - -def export_a_dict(result_dict, export_path='result.csv'): - df = pd.DataFrame([result_dict]) - df.to_csv(export_path, index_label='index') - return None - - -logo = \ -''' ---------------------------------------------------------------------------------- -Groupy -- A Useful Tool for Molecular Analysis -Developer: Ruichen Liu -Hint: Please feel easy to contact the developer if you have any problems in use. -E-mail1: liuruichen@tju.edu.cn -E-mail2: 1197748182@qq.com (may reply more quickly than E-mail1) ---------------------------------------------------------------------------------- -''' - - -# if __name__ == '__main__': -# print('debug gp_tool.py') -# -# t = Tool() -# -# t.smi_to_xyz('C1CCCC1') -# # t.batch_smi_to_xyz(smiles_file_path=r'gp_3x_test_mol\SMILES.txt', xyz_root_path='test_xyz') -# # t.convert_file_type(in_format='xyz', in_path='C1CCCC1.xyz', out_format='mol2') -# # t.batch_convert_file_type(in_format='xyz', in_root_path='test_xyz', out_format='mol2', out_root_path=None) -# # t.batch_convert_file_type(in_format='xyz', in_root_path='test_xyz', out_format='mol2', out_root_path='test_mol2') -# -# t.smi_to_gjf(smi='C1CCC1', add_other_std_tasks=True) -# t.batch_smi_to_gjf(smiles_file_path=r'gp_3x_test_mol\SMILES.txt', gjf_root_path='test_gjf', add_other_std_tasks=True) diff --git a/build/lib/src/gp_3x_viewer.py b/build/lib/src/gp_3x_viewer.py deleted file mode 100644 index 0d19b12..0000000 --- a/build/lib/src/gp_3x_viewer.py +++ /dev/null @@ -1,96 +0,0 @@ -import ase -from ase.visualize import view -from ase.io import read -import os -from pprint import pprint - -from gp_3x_convertor import Convertor - - -class Viewer: - def __init__(self): - pass - - def view_mol(self, mol, mol_type='smi'): - if isinstance(mol, str): - if mol_type in ['smi', 'smiles', 'SMILES']: - convertor = Convertor() - convertor.smi_to_xyz(smi=mol, xyz_path='temp.xyz') - mol = read(filename='temp.xyz', format='xyz') - os.remove('temp.xyz') - else: - try: - mol = read(filename=mol, format=mol_type) - except: - convertor = Convertor() - convertor.convert_file_type(in_format=mol_type, in_path=mol, out_format='xyz', out_path='temp.xyz') - mol = read(filename='temp.xyz', format='xyz') - os.remove('temp.xyz') - view(mol) - - def plot_supported_format(self): - ase_format = ase.io.formats.ioformats - openbabel_format = {'abinit': 'ABINIT Output Format', 'acesout': 'ACES output format', 'acr': 'ACR format', - 'adfband': 'ADF Band output format', 'adfdftb': 'ADF DFTB output format', 'adfout': 'ADF output format', - 'alc': 'Alchemy format', 'aoforce': 'Turbomole AOFORCE output format', - 'arc': 'Accelrys/MSI Biosym/Insight II CAR format', 'axsf': 'XCrySDen Structure Format', - 'bgf': 'MSI BGF format', 'box': 'Dock 3.5 Box format', 'bs': 'Ball and Stick format', - 'c09out': 'Crystal 09 output format', 'c3d1': 'Chem3D Cartesian 1 format', 'c3d2': 'Chem3D Cartesian 2 format', - 'caccrt': 'Cacao Cartesian format', 'can': 'Canonical SMILES format', - 'car': 'Accelrys/MSI Biosym/Insight II CAR format', 'castep': 'CASTEP format', 'ccc': 'CCC format', - 'cdjson': 'ChemDoodle JSON', 'cdx': 'ChemDraw binary format', 'cdxml': 'ChemDraw CDXML format', - 'cif': 'Crystallographic Information File', 'ck': 'ChemKin format', 'cml': 'Chemical Markup Language', - 'cmlr': 'CML Reaction format', 'cof': 'Culgi object file format', 'CONFIG': 'DL-POLY CONFIG', - 'CONTCAR': 'VASP format', 'CONTFF': 'MDFF format', 'crk2d': 'Chemical Resource Kit diagram(2D)', - 'crk3d': 'Chemical Resource Kit 3D format', 'ct': 'ChemDraw Connection Table format', - 'cub': 'Gaussian cube format', 'cube': 'Gaussian cube format', 'dallog': 'DALTON output format', - 'dalmol': 'DALTON input format', 'dat': 'Generic Output file format', 'dmol': 'DMol3 coordinates format', - 'dx': 'OpenDX cube format for APBS', 'ent': 'Protein Data Bank format', - 'exyz': 'Extended XYZ cartesian coordinates format', 'fa': 'FASTA format', 'fasta': 'FASTA format', - 'fch': 'Gaussian formatted checkpoint file format', 'fchk': 'Gaussian formatted checkpoint file format', - 'fck': 'Gaussian formatted checkpoint file format', 'feat': 'Feature format', 'fhiaims': 'FHIaims XYZ format', - 'fract': 'Free Form Fractional format', 'fs': 'Fastsearch format', 'fsa': 'FASTA format', - 'g03': 'Gaussian Output', 'g09': 'Gaussian Output', 'g16': 'Gaussian Output', - 'g92': 'Gaussian Output', 'g94': 'Gaussian Output', 'g98': 'Gaussian Output', 'gal': 'Gaussian Output', - 'gam': 'GAMESS Output', 'gamess': 'GAMESS Output', 'gamin': 'GAMESS Input', 'gamout': 'GAMESS Output', - 'got': 'GULP format', 'gpr': 'Ghemical format', 'gro': 'GRO format', 'gukin': 'GAMESS-UK Input', - 'gukout': 'GAMESS-UK Output', 'gzmat': 'Gaussian Z-Matrix Input', 'hin': 'HyperChem HIN format', - 'HISTORY': 'DL-POLY HISTORY', 'inchi': 'InChI format', 'inp': 'GAMESS Input', 'ins': 'ShelX format', - 'jin': 'Jaguar input format', 'jout': 'Jaguar output format', 'log': 'Generic Output file format', - 'lpmd': 'LPMD format', 'mcdl': 'MCDL format', 'mcif': 'Macromolecular Crystallographic Info', - 'MDFF': 'MDFF format', 'mdl': 'MDL MOL format', 'ml2': 'Sybyl Mol2 format', - 'mmcif': 'Macromolecular Crystallographic Info', 'mmd': 'MacroModel format', 'mmod': 'MacroModel format', - 'mol': 'MDL MOL format', 'mol2': 'Sybyl Mol2 format', 'mold': 'Molden format', 'molden': 'Molden format', - 'molf': 'Molden format', 'moo': 'MOPAC Output format', 'mop': 'MOPAC Cartesian format', - 'mopcrt': 'MOPAC Cartesian format', 'mopin': 'MOPAC Internal', 'mopout': 'MOPAC Output format', - 'mpc': 'MOPAC Cartesian format', 'mpo': 'Molpro output format', 'mpqc': 'MPQC output format', - 'mrv': 'Chemical Markup Language', 'msi': 'Accelrys/MSI Cerius II MSI format', 'nwo': 'NWChem output format', - 'orca': 'ORCA output format', 'out': 'Generic Output file format', 'outmol': 'DMol3 coordinates format', - 'output': 'Generic Output file format', 'pc': 'PubChem format', 'pcjson': 'PubChem JSON', - 'pcm': 'PCModel Format', 'pdb': 'Protein Data Bank format', 'pdbqt': 'AutoDock PDBQT format', - 'png': 'PNG 2D depiction', 'pos': 'POS cartesian coordinates format', 'POSCAR': 'VASP format', - 'POSFF': 'MDFF format', 'pqr': 'PQR format', 'pqs': 'Parallel Quantum Solutions format', - 'prep': 'Amber Prep format', 'pwscf': 'PWscf format', 'qcout': 'Q-Chem output format', 'res': 'ShelX format', - 'rsmi': 'Reaction SMILES format', 'rxn': 'MDL RXN format', 'sd': 'MDL MOL format', 'sdf': 'MDL MOL format', - 'siesta': 'SIESTA format', 'smi': 'SMILES format', 'smiles': 'SMILES format', - 'smy': 'SMILES format using Smiley parser', 'sy2': 'Sybyl Mol2 format', 't41': 'ADF TAPE41 format', - 'tdd': 'Thermo format', 'text': 'Read and write raw text', 'therm': 'Thermo format', - 'tmol': 'TurboMole Coordinate format', 'txt': 'Title format', 'txyz': 'Tinker XYZ format', - 'unixyz': 'UniChem XYZ format', 'VASP': 'VASP format', 'vmol': 'ViewMol format', - 'wln': 'Wiswesser Line Notation', 'xml': 'General XML format', 'xsf': 'XCrySDen Structure Format', - 'xyz': 'XYZ cartesian coordinates format', 'yob': 'YASARA.org YOB format'} - print('-' * 40) - pprint('ASE format:') - pprint(ase_format) - print('-' * 40) - pprint('OpenBabel format:') - pprint(openbabel_format) - return None - - - -# if __name__ == '__main__': -# -# viewer = Viewer() -# viewer.view_mol('gp_3x_test_mol/test_xyz/000000.mol2', mol_type='mol2') -# viewer.plot_supported_format() \ No newline at end of file diff --git a/build/lib/src/groupy.py b/build/lib/src/groupy.py deleted file mode 100644 index 8e976c0..0000000 --- a/build/lib/src/groupy.py +++ /dev/null @@ -1,559 +0,0 @@ -from rdkit import Chem - -from gp_3x_calculator import Calculator -from gp_3x_counter import Counter -from gp_3x_viewer import Viewer -from gp_3x_convertor import Convertor -from gp_3x_generator import Generator -from gp_3x_tool import Tool, export_a_dict, logo - - -def main_function_5(): - - while True: - flag_file = input( - '\n' - '--------------------------------------------------------------------------------- \n' - 'You are in main function 5 \n' - 'what to do? \n' - ' 0. return to main interface. \n' - ' 1. generate a .xyz file by input SMILES of a molecule. \n' - ' 2. generate a batch of .xyz files. -2. use mpi to accelerate.\n' - ' 3. convert a file to other format (e.g. xyz, mol, mol2, pdb...) \n' - ' 4. convert a batch of file to other format. -4. use mpi to accelerate.\n' - ' 5. generate .gjf(input file of gaussian) file by input SMILES of a molecule. \n' - ' 6. generate a batch of .gjf files. -6. use mpi to accelerate. \n' - '--------------------------------------------------------------------------------- \n' - ) - - - if flag_file == '0': - break - - elif flag_file == '1': - sub_function_1_of_main_function_5() - - elif flag_file == '2': - sub_function_2_of_main_function_5() - - elif flag_file == '-2': - sub_function_minus_2_of_main_function_5() - - elif flag_file == '3': - sub_function_3_of_main_function_5() - - elif flag_file == '4': - sub_function_4_of_main_function_5() - - elif flag_file == '-4': - sub_function_minus_4_of_main_function_5() - - elif flag_file == '5': - sub_function_5_of_main_function_5() - - elif flag_file == '6': - sub_function_6_of_main_function_5() - - elif flag_file == '-6': - sub_function_minus_6_of_main_function_5() - - return None - - -def sub_function_1_of_main_function_5(): - """ - 1. generate a .xyz file by input SMILES of a molecule. - """ - smiles = input('input the SMILES of a molecule. \n') - smiles = smiles.strip() - xyz_file = input( - 'please input the path of output .xyz file. If press Enter directly, {}.xyz will be used\n'.format(smiles)) - if not xyz_file: - xyz_file = '{}.xyz'.format(smiles) - convertor = Convertor() - convertor.smi_to_xyz(smi=smiles, xyz_path=xyz_file) - print('Done!') - print('\n\n\n') - return None - - -def sub_function_2_of_main_function_5(): - """ - 2. generate a batch of .xyz files. - """ - smiles_file_path = input('input a filepath of a file in which save SMILES of molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - xyz_root_path = input('input the root path of output xyz files, that is, ' - 'all the output xyz files will be make in this path. e.g. test_xyz \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n') - convertor = Convertor() - convertor.batch_smi_to_xyz(smiles_file_path=smiles_file_path, xyz_root_path=xyz_root_path) - print('\n\n\n') - return None - - -def sub_function_minus_2_of_main_function_5(): - """ - 2. generate a batch of .xyz files. use mpi - """ - smiles_file_path = input('input a filepath of a file in which save SMILES of molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - xyz_root_path = input('input the root path of output xyz files, that is, ' - 'all the output xyz files will be make in this path. e.g. test_xyz \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n') - n_jobs = int(input('input number of cores to use. e.g. 4 \n')) - batch_size = input('input batch size for task decomposition. e.g. 20, you can also enter "auto" \n') - try: - batch_size = int(batch_size) - except: - pass - convertor = Convertor() - convertor.batch_smi_to_xyz_mpi(smiles_file_path=smiles_file_path, xyz_root_path=xyz_root_path, - n_jobs=n_jobs, batch_size=batch_size) - print('\n\n\n') - return None - - -def sub_function_3_of_main_function_5(): - """ - 3. convert a file to other format (e.g. xyz, mol, mol2, pdb...) - """ - in_format = input('please input the format of your input file (e.g. xyz, pdb...) \n') - in_path = input('please input the path of input file, e.g. C1CCC1.xyz \n') - out_format = input('please input the format of output file you want (e.g. xyz, mol2...) \n') - out_path = input('please input the path of output file, e.g C1CCC1.mol2. \n') - convertor = Convertor() - convertor.convert_file_type(in_format=in_format, in_path=in_path, out_format=out_format, out_path=out_path) - print('Done!') - print('\n\n\n') - - -def sub_function_4_of_main_function_5(): - """ - 4. convert a batch of file to other format. - """ - in_format = input('please input the format of your input file (e.g. xyz, pdb...) \n') - in_root_path = input('please input the root path of input files, that is, ' - 'all input files you want to convert should be in there.' - 'e.g. test_xyz \n') - out_format = input('please input the format of output file you want (e.g. xyz, mol2...) \n') - default_out_root_path = in_root_path - out_root_path = input('please input the root path of output file, that is, ' - 'all the output files will be saved in there\n' - 'If press Enter directly, {} will be used \n'.format(default_out_root_path)) - if not out_root_path: - out_root_path = default_out_root_path - convertor = Convertor() - convertor.batch_convert_file_type(in_format=in_format, in_root_path=in_root_path, - out_format=out_format, out_root_path=out_root_path) - print('Done!') - print('\n\n\n') - return None - - -def sub_function_minus_4_of_main_function_5(): - """ - -4. convert a batch of file to other format. use mpi. - """ - in_format = input('please input the format of your input file (e.g. xyz, pdb...) \n') - in_root_path = input('please input the root path of input files, that is, ' - 'all input files you want to convert should be in there.' - 'e.g. test_xyz \n') - out_format = input('please input the format of output file you want (e.g. xyz, mol2...) \n') - default_out_root_path = in_root_path - out_root_path = input('please input the root path of output file, that is, ' - 'all the output files will be saved in there\n' - 'If press Enter directly, {} will be used \n'.format(default_out_root_path)) - if not out_root_path: - out_root_path = default_out_root_path - n_jobs = int(input('input number of cores to use. e.g. 4 \n')) - batch_size = input('input batch size for task decomposition. e.g. 20, you can also enter "auto" \n') - try: - batch_size = int(batch_size) - except: - pass - convertor = Convertor() - convertor.batch_convert_file_type_mpi(in_format=in_format, in_root_path=in_root_path, - out_format=out_format, out_root_path=out_root_path, - n_jobs=n_jobs, batch_size=batch_size) - print('Done!') - print('\n\n\n') - return None - - -def sub_function_5_of_main_function_5(): - """ - 5. generate .gjf(input file of gaussian) file by input SMILES of a molecule. - """ - smiles = input('input the SMILES of a molecule. \n') - smiles = smiles.strip() - - nproc = input('input the CPU cores you want to use. e.g. 12 \n') - if not nproc: - nproc = '12' - - mem = input('input the memory you want to use. e.g. 12GB \n') - if not mem: - mem = '12GB' - - chk_path = input('input the path of chk file. e.g. Cc1ccccc1.chk \n' - 'Hint1: If press Enter directly, {}.chk will be used. \n' - 'Hint2: Attention please! the symbol such as (, ), /, \\ and # should not appear in a filepath! \n' - .format(smiles)) - if not chk_path: - chk_path = '{}.chk'.format(smiles) - - gjf_path = input('input the path of gjf file. e.g. Cc1ccccc1.gjf \n' - 'Hint1: If press Enter directly, {}.gjf will be used. \n' - 'Hint2: Attention please! the symbol such as (, ), /, \\ and # should not appear in a filepath! \n' - .format(smiles)) - if not gjf_path: - gjf_path = '{}.gjf'.format(smiles) - - gaussian_keywords = input('input the keywords of Gaussian to define task you want to run.' - 'e.g. #p opt freq b3lyp/6-31g* \n' - 'Hint1: if press Enter directly, "#p opt freq b3lyp/6-31g*" will be used. \n') - if not gaussian_keywords: - gaussian_keywords = '#p opt freq b3lyp/6-31g*' - - charge_and_multiplicity = input('Input charge and multiplicity. e.g. 0 1 \n' - 'Hint: If press Enter directly, Groupy will automatically calculate them') - if not charge_and_multiplicity: - charge_and_multiplicity = None - - add_other_tasks = input('Weather to add some other tasks in this .gjf (y/n). \n') - if add_other_tasks in ['n', 'no', 'N']: - add_other_tasks = False - other_tasks = None - else: - other_tasks = input('Input keywords you want to add. ' - 'If there are more than one other tasks, Please separate them with commas (,) \n' - 'Hint: if press Enter directly, "#p m062x/def2tzvp geom=check,#p m062x/def2tzvp scrf=solvent=water geom=check" will be used \n') - if not other_tasks: - other_tasks = None - else: - other_tasks = other_tasks.split(',') - - generator = Generator() - generator.smi_to_gjf(smi=smiles, nproc=nproc, mem=mem, gaussian_keywords=gaussian_keywords, - charge_and_multiplicity=charge_and_multiplicity, - chk_path=chk_path, gjf_path=gjf_path, - add_other_tasks=add_other_tasks, other_tasks=other_tasks) - print('Done!') - print('\n\n\n') - return None - - -def sub_function_6_of_main_function_5(): - smiles_file_path = input('input the filepath of a file in which save molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - - gjf_root_path = input('Input the root path of output gjf files, that is, ' - 'all the output gjf files will be make in this path. e.g. test_gjf \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: if press Enter directly, test_gjf will be used. \n') - if not gjf_root_path: - gjf_root_path = 'test_gjf' - - nproc = input('input the CPU cores you want to use. e.g. 12 \n') - if not nproc: - nproc = '12' - - mem = input('input the memory you want to use. e.g. 12GB \n') - if not mem: - mem = '12GB' - - gaussian_keywords = input('input the keywords of Gaussian to define task you want to run.' - 'e.g. #p opt freq b3lyp/6-31g* \n' - 'Hint1: if press Enter directly, "#p opt freq b3lyp/6-31g*" will be used. \n') - if not gaussian_keywords: - gaussian_keywords = '#p opt freq b3lyp/6-31g*' - - charge_and_multiplicity = input('Input charge and multiplicity. e.g. 0 1 \n' - 'Hint: If press Enter directly, Groupy will automatically calculate them') - if not charge_and_multiplicity: - charge_and_multiplicity = None - - add_other_tasks = input('Weather to add some other tasks in this .gjf (y/n). \n') - if add_other_tasks in ['n', 'no', 'N']: - add_other_tasks = False - other_tasks = None - else: - other_tasks = input('Input keywords you want to add. ' - 'If there are more than one other tasks, Please separate them with commas (,) \n' - 'Hint: if press Enter directly, "#p m062x/def2tzvp geom=check,#p m062x/def2tzvp scrf=solvent=water geom=check" will be used \n') - if not other_tasks: - other_tasks = None - else: - other_tasks = other_tasks.split(',') - - generator = Generator() - generator.batch_smi_to_gjf(smiles_file_path=smiles_file_path, gjf_root_path=gjf_root_path, - nproc=nproc, mem=mem, gaussian_keywords=gaussian_keywords, - charge_and_multiplicity=charge_and_multiplicity, - add_other_tasks=add_other_tasks, other_tasks=other_tasks) - print('\n\n\n') - return None - - -def sub_function_minus_6_of_main_function_5(): - smiles_file_path = input('input the filepath of a file in which save molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - - gjf_root_path = input('Input the root path of output gjf files, that is, ' - 'all the output gjf files will be make in this path. e.g. test_gjf \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: if press Enter directly, test_gjf will be used. \n') - if not gjf_root_path: - gjf_root_path = 'test_gjf' - - nproc = input('input the CPU cores you want to use. e.g. 12 \n') - if not nproc: - nproc = '12' - - mem = input('input the memory you want to use. e.g. 12GB \n') - if not mem: - mem = '12GB' - - gaussian_keywords = input('input the keywords of Gaussian to define task you want to run.' - 'e.g. #p opt freq b3lyp/6-31g* \n' - 'Hint1: if press Enter directly, "#p opt freq b3lyp/6-31g*" will be used. \n') - if not gaussian_keywords: - gaussian_keywords = '#p opt freq b3lyp/6-31g*' - - charge_and_multiplicity = input('Input charge and multiplicity. e.g. 0 1 \n' - 'Hint: If press Enter directly, Groupy will automatically calculate them') - if not charge_and_multiplicity: - charge_and_multiplicity = None - - add_other_tasks = input('Weather to add some other tasks in this .gjf (y/n). \n') - if add_other_tasks in ['n', 'no', 'N']: - add_other_tasks = False - other_tasks = None - else: - other_tasks = input('Input keywords you want to add. ' - 'If there are more than one other tasks, Please separate them with commas (,) \n' - 'Hint: if press Enter directly, "#p m062x/def2tzvp geom=check,#p m062x/def2tzvp scrf=solvent=water geom=check" will be used \n') - if not other_tasks: - other_tasks = None - else: - other_tasks = other_tasks.split(',') - - n_jobs = int(input('input number of cores to use. e.g. 4 \n')) - batch_size = input('input batch size for task decomposition. e.g. 20, you can also enter "auto" \n') - try: - batch_size = int(batch_size) - except: - pass - - generator = Generator() - generator.batch_smi_to_gjf_mpi(smiles_file_path=smiles_file_path, gjf_root_path=gjf_root_path, - nproc=nproc, mem=mem, gaussian_keywords=gaussian_keywords, - charge_and_multiplicity=charge_and_multiplicity, - add_other_tasks=add_other_tasks, other_tasks=other_tasks, - n_jobs=n_jobs, batch_size=batch_size) - print('\n\n\n') - return None - - -def main_function_view(): - viewer = Viewer() - show_flag = input('show a SMILES (enter 1) or file (enter 2). \n(enter help to show supported file formats)\n') - if show_flag in ['1', 'SMILES', 'smiles']: - smiles = input('input the SMILES of a molecule. \n') - viewer.view_mol(mol=smiles, mol_type='smi') - - elif show_flag in ['2', 'file', 'FILE']: - file_path = input('input the file path you want to show. e.g. ./temporary.xyz \n') - file_type = input('input file format. e.g. xyz \n') - viewer.view_mol(mol=file_path, mol_type=file_type) - - elif show_flag in ['h', 'help', 'H', 'Help', 'HELP']: - viewer.plot_supported_format() - - else: - print('Unrecognized command!') - return None - - -def main_function_1(): - """ - 1. calculate properties of a molecule by input SMILES of this molecule. - """ - smiles = input('input the SMILES of a molecule. \n') - smiles = smiles.strip() - calculator = Calculator() - result = calculator.calculate_a_mol(smiles, debug=False) - print(result) - export_flag = input('Do you want to export results to a csv file? (y/n) \n') - if export_flag in ['y', 'Y', '1']: - export_a_dict(result_dict=result, export_path='{}_calculate.csv'.format(smiles)) - print('the results have been export to {}_calculate.csv! \n\n'.format(smiles)) - else: - print('\n\n\n') - return None - - -def main_function_2(): - """ - 2. count group number of a molecule by input SMILES of this molecule. - """ - smiles = input('input the SMILES of a molecule. \n') - smiles = smiles.strip() - mol = Chem.MolFromSmiles(smiles) - counter = Counter() - clear_mode_flag = input('clear mode? (y/n) \n') - if clear_mode_flag in ['y', 'Y', '1']: - result = counter.count_a_mol(mol, clear_mode=True) - else: - result = counter.count_a_mol(mol, clear_mode=False) - print(result) - export_flag = input('Do you want to export results to a file? (y/n) \n') - if export_flag in ['y', 'Y', '1']: - export_a_dict(result_dict=result, export_path='{}_count.csv'.format(smiles)) - print('the results have been export to {}_count.csv! \n\n'.format(smiles)) - else: - print('\n\n\n') - return None - - -def main_function_3(): - """ - 3. calculate properties of a batch of molecules by input filepath of a file in which save molecules (.txt, .csv, .xlsx). - """ - smiles_file_path = input('input the filepath of a file (.txt, .csv, .xlsx) in which save molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - calculator = Calculator() - calculator.calculate_mols(smiles_file_path=smiles_file_path, - properties_file_path='batch_calculate_results.csv') - print('\n\n\n') - return None - - -def main_function_minus_3(): - """ - -3. calculate properties of a batch of molecules by input filepath of a file in which save molecules (.txt, .csv, .xlsx). - use mpi - """ - smiles_file_path = input('input the filepath of a file (.txt, .csv, .xlsx) in which save molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - n_jobs = int(input('input number of cores to use. e.g. 4 \n')) - batch_size = input('input batch size for task decomposition. e.g. 20, you can also enter "auto" \n') - try: - batch_size = int(batch_size) - except: - pass - calculator = Calculator() - calculator.calculate_mols_mpi(smiles_file_path=smiles_file_path, - properties_file_path='batch_calculate_results_mpi.csv', - n_jobs=n_jobs, batch_size=batch_size) - print('\n\n\n') - return None - - -def main_function_4(): - """ - 4. count group number of a batch of molecules by input filepath of a file in which save molecules (.txt, .csv, .xlsx). - """ - smiles_file_path = input('input the filepath of a file in which save molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - counter = Counter() - counter.count_mols(smiles_file_path=smiles_file_path, - count_result_file_path='batch_count_result.csv', add_note=True, add_smiles=True) - print('\n\n\n') - return None - - -def main_function_minus_4(): - """ - -4. count group number of a batch of molecules by input filepath of a file in which save molecules (.txt, .csv, .xlsx). - use mpi - """ - smiles_file_path = input('input the filepath of a file (.txt, .csv, .xlsx) in which save molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - n_jobs = int(input('input number of cores to use. e.g. 4 \n')) - batch_size = input('input batch size for task decomposition. e.g. 20, you can also enter "auto" \n') - try: - batch_size = int(batch_size) - except: - pass - counter = Counter() - counter.count_mols_mpi(smiles_file_path=smiles_file_path, - count_result_file_path='batch_count_result_mpi.csv', add_note=True, add_smiles=True, - n_jobs=n_jobs, batch_size=batch_size) - print('\n\n\n') - return None - - -def main(): - print(logo) - - while True: - - flag_main = input( - '\n' - '--------------------------------------------------------------------------------- \n' - 'You are in main interface \n' - 'what to do? \n' - ' q. exit \n' - ' 0. show molecular structure by SMILES or file. \n' - ' 1. calculate properties of a molecule. \n' - ' 2. count group number of a molecule. \n' - ' 3. calculate properties of a batch of molecules. -3. use mpi to accelerate.\n' - ' 4. count group number of a batch of molecules. -4. use mpi to accelerate.\n' - ' 5. generate files or covert file format for MD, DFT, Visualization... \n' - '--------------------------------------------------------------------------------- \n' - ) - - if flag_main == 'q': - print('exit Groupy, have a nice day!') - break - - elif flag_main == '0': - main_function_view() - - elif flag_main == '1': - main_function_1() - - elif flag_main == '2': - main_function_2() - - elif flag_main == '3': - main_function_3() - - elif flag_main == '-3': - main_function_minus_3() - - elif flag_main == '4': - main_function_4() - - elif flag_main == '-4': - main_function_minus_4() - - elif flag_main == '5': - main_function_5() - - else: - print('Please input the right option. ' - 'For more information, you are supposed to read the manual.') - - -if __name__ == '__main__': - - main() diff --git a/build/scripts-3.11/groupy.py b/build/scripts-3.11/groupy.py deleted file mode 100644 index 8e976c0..0000000 --- a/build/scripts-3.11/groupy.py +++ /dev/null @@ -1,559 +0,0 @@ -from rdkit import Chem - -from gp_3x_calculator import Calculator -from gp_3x_counter import Counter -from gp_3x_viewer import Viewer -from gp_3x_convertor import Convertor -from gp_3x_generator import Generator -from gp_3x_tool import Tool, export_a_dict, logo - - -def main_function_5(): - - while True: - flag_file = input( - '\n' - '--------------------------------------------------------------------------------- \n' - 'You are in main function 5 \n' - 'what to do? \n' - ' 0. return to main interface. \n' - ' 1. generate a .xyz file by input SMILES of a molecule. \n' - ' 2. generate a batch of .xyz files. -2. use mpi to accelerate.\n' - ' 3. convert a file to other format (e.g. xyz, mol, mol2, pdb...) \n' - ' 4. convert a batch of file to other format. -4. use mpi to accelerate.\n' - ' 5. generate .gjf(input file of gaussian) file by input SMILES of a molecule. \n' - ' 6. generate a batch of .gjf files. -6. use mpi to accelerate. \n' - '--------------------------------------------------------------------------------- \n' - ) - - - if flag_file == '0': - break - - elif flag_file == '1': - sub_function_1_of_main_function_5() - - elif flag_file == '2': - sub_function_2_of_main_function_5() - - elif flag_file == '-2': - sub_function_minus_2_of_main_function_5() - - elif flag_file == '3': - sub_function_3_of_main_function_5() - - elif flag_file == '4': - sub_function_4_of_main_function_5() - - elif flag_file == '-4': - sub_function_minus_4_of_main_function_5() - - elif flag_file == '5': - sub_function_5_of_main_function_5() - - elif flag_file == '6': - sub_function_6_of_main_function_5() - - elif flag_file == '-6': - sub_function_minus_6_of_main_function_5() - - return None - - -def sub_function_1_of_main_function_5(): - """ - 1. generate a .xyz file by input SMILES of a molecule. - """ - smiles = input('input the SMILES of a molecule. \n') - smiles = smiles.strip() - xyz_file = input( - 'please input the path of output .xyz file. If press Enter directly, {}.xyz will be used\n'.format(smiles)) - if not xyz_file: - xyz_file = '{}.xyz'.format(smiles) - convertor = Convertor() - convertor.smi_to_xyz(smi=smiles, xyz_path=xyz_file) - print('Done!') - print('\n\n\n') - return None - - -def sub_function_2_of_main_function_5(): - """ - 2. generate a batch of .xyz files. - """ - smiles_file_path = input('input a filepath of a file in which save SMILES of molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - xyz_root_path = input('input the root path of output xyz files, that is, ' - 'all the output xyz files will be make in this path. e.g. test_xyz \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n') - convertor = Convertor() - convertor.batch_smi_to_xyz(smiles_file_path=smiles_file_path, xyz_root_path=xyz_root_path) - print('\n\n\n') - return None - - -def sub_function_minus_2_of_main_function_5(): - """ - 2. generate a batch of .xyz files. use mpi - """ - smiles_file_path = input('input a filepath of a file in which save SMILES of molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - xyz_root_path = input('input the root path of output xyz files, that is, ' - 'all the output xyz files will be make in this path. e.g. test_xyz \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n') - n_jobs = int(input('input number of cores to use. e.g. 4 \n')) - batch_size = input('input batch size for task decomposition. e.g. 20, you can also enter "auto" \n') - try: - batch_size = int(batch_size) - except: - pass - convertor = Convertor() - convertor.batch_smi_to_xyz_mpi(smiles_file_path=smiles_file_path, xyz_root_path=xyz_root_path, - n_jobs=n_jobs, batch_size=batch_size) - print('\n\n\n') - return None - - -def sub_function_3_of_main_function_5(): - """ - 3. convert a file to other format (e.g. xyz, mol, mol2, pdb...) - """ - in_format = input('please input the format of your input file (e.g. xyz, pdb...) \n') - in_path = input('please input the path of input file, e.g. C1CCC1.xyz \n') - out_format = input('please input the format of output file you want (e.g. xyz, mol2...) \n') - out_path = input('please input the path of output file, e.g C1CCC1.mol2. \n') - convertor = Convertor() - convertor.convert_file_type(in_format=in_format, in_path=in_path, out_format=out_format, out_path=out_path) - print('Done!') - print('\n\n\n') - - -def sub_function_4_of_main_function_5(): - """ - 4. convert a batch of file to other format. - """ - in_format = input('please input the format of your input file (e.g. xyz, pdb...) \n') - in_root_path = input('please input the root path of input files, that is, ' - 'all input files you want to convert should be in there.' - 'e.g. test_xyz \n') - out_format = input('please input the format of output file you want (e.g. xyz, mol2...) \n') - default_out_root_path = in_root_path - out_root_path = input('please input the root path of output file, that is, ' - 'all the output files will be saved in there\n' - 'If press Enter directly, {} will be used \n'.format(default_out_root_path)) - if not out_root_path: - out_root_path = default_out_root_path - convertor = Convertor() - convertor.batch_convert_file_type(in_format=in_format, in_root_path=in_root_path, - out_format=out_format, out_root_path=out_root_path) - print('Done!') - print('\n\n\n') - return None - - -def sub_function_minus_4_of_main_function_5(): - """ - -4. convert a batch of file to other format. use mpi. - """ - in_format = input('please input the format of your input file (e.g. xyz, pdb...) \n') - in_root_path = input('please input the root path of input files, that is, ' - 'all input files you want to convert should be in there.' - 'e.g. test_xyz \n') - out_format = input('please input the format of output file you want (e.g. xyz, mol2...) \n') - default_out_root_path = in_root_path - out_root_path = input('please input the root path of output file, that is, ' - 'all the output files will be saved in there\n' - 'If press Enter directly, {} will be used \n'.format(default_out_root_path)) - if not out_root_path: - out_root_path = default_out_root_path - n_jobs = int(input('input number of cores to use. e.g. 4 \n')) - batch_size = input('input batch size for task decomposition. e.g. 20, you can also enter "auto" \n') - try: - batch_size = int(batch_size) - except: - pass - convertor = Convertor() - convertor.batch_convert_file_type_mpi(in_format=in_format, in_root_path=in_root_path, - out_format=out_format, out_root_path=out_root_path, - n_jobs=n_jobs, batch_size=batch_size) - print('Done!') - print('\n\n\n') - return None - - -def sub_function_5_of_main_function_5(): - """ - 5. generate .gjf(input file of gaussian) file by input SMILES of a molecule. - """ - smiles = input('input the SMILES of a molecule. \n') - smiles = smiles.strip() - - nproc = input('input the CPU cores you want to use. e.g. 12 \n') - if not nproc: - nproc = '12' - - mem = input('input the memory you want to use. e.g. 12GB \n') - if not mem: - mem = '12GB' - - chk_path = input('input the path of chk file. e.g. Cc1ccccc1.chk \n' - 'Hint1: If press Enter directly, {}.chk will be used. \n' - 'Hint2: Attention please! the symbol such as (, ), /, \\ and # should not appear in a filepath! \n' - .format(smiles)) - if not chk_path: - chk_path = '{}.chk'.format(smiles) - - gjf_path = input('input the path of gjf file. e.g. Cc1ccccc1.gjf \n' - 'Hint1: If press Enter directly, {}.gjf will be used. \n' - 'Hint2: Attention please! the symbol such as (, ), /, \\ and # should not appear in a filepath! \n' - .format(smiles)) - if not gjf_path: - gjf_path = '{}.gjf'.format(smiles) - - gaussian_keywords = input('input the keywords of Gaussian to define task you want to run.' - 'e.g. #p opt freq b3lyp/6-31g* \n' - 'Hint1: if press Enter directly, "#p opt freq b3lyp/6-31g*" will be used. \n') - if not gaussian_keywords: - gaussian_keywords = '#p opt freq b3lyp/6-31g*' - - charge_and_multiplicity = input('Input charge and multiplicity. e.g. 0 1 \n' - 'Hint: If press Enter directly, Groupy will automatically calculate them') - if not charge_and_multiplicity: - charge_and_multiplicity = None - - add_other_tasks = input('Weather to add some other tasks in this .gjf (y/n). \n') - if add_other_tasks in ['n', 'no', 'N']: - add_other_tasks = False - other_tasks = None - else: - other_tasks = input('Input keywords you want to add. ' - 'If there are more than one other tasks, Please separate them with commas (,) \n' - 'Hint: if press Enter directly, "#p m062x/def2tzvp geom=check,#p m062x/def2tzvp scrf=solvent=water geom=check" will be used \n') - if not other_tasks: - other_tasks = None - else: - other_tasks = other_tasks.split(',') - - generator = Generator() - generator.smi_to_gjf(smi=smiles, nproc=nproc, mem=mem, gaussian_keywords=gaussian_keywords, - charge_and_multiplicity=charge_and_multiplicity, - chk_path=chk_path, gjf_path=gjf_path, - add_other_tasks=add_other_tasks, other_tasks=other_tasks) - print('Done!') - print('\n\n\n') - return None - - -def sub_function_6_of_main_function_5(): - smiles_file_path = input('input the filepath of a file in which save molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - - gjf_root_path = input('Input the root path of output gjf files, that is, ' - 'all the output gjf files will be make in this path. e.g. test_gjf \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: if press Enter directly, test_gjf will be used. \n') - if not gjf_root_path: - gjf_root_path = 'test_gjf' - - nproc = input('input the CPU cores you want to use. e.g. 12 \n') - if not nproc: - nproc = '12' - - mem = input('input the memory you want to use. e.g. 12GB \n') - if not mem: - mem = '12GB' - - gaussian_keywords = input('input the keywords of Gaussian to define task you want to run.' - 'e.g. #p opt freq b3lyp/6-31g* \n' - 'Hint1: if press Enter directly, "#p opt freq b3lyp/6-31g*" will be used. \n') - if not gaussian_keywords: - gaussian_keywords = '#p opt freq b3lyp/6-31g*' - - charge_and_multiplicity = input('Input charge and multiplicity. e.g. 0 1 \n' - 'Hint: If press Enter directly, Groupy will automatically calculate them') - if not charge_and_multiplicity: - charge_and_multiplicity = None - - add_other_tasks = input('Weather to add some other tasks in this .gjf (y/n). \n') - if add_other_tasks in ['n', 'no', 'N']: - add_other_tasks = False - other_tasks = None - else: - other_tasks = input('Input keywords you want to add. ' - 'If there are more than one other tasks, Please separate them with commas (,) \n' - 'Hint: if press Enter directly, "#p m062x/def2tzvp geom=check,#p m062x/def2tzvp scrf=solvent=water geom=check" will be used \n') - if not other_tasks: - other_tasks = None - else: - other_tasks = other_tasks.split(',') - - generator = Generator() - generator.batch_smi_to_gjf(smiles_file_path=smiles_file_path, gjf_root_path=gjf_root_path, - nproc=nproc, mem=mem, gaussian_keywords=gaussian_keywords, - charge_and_multiplicity=charge_and_multiplicity, - add_other_tasks=add_other_tasks, other_tasks=other_tasks) - print('\n\n\n') - return None - - -def sub_function_minus_6_of_main_function_5(): - smiles_file_path = input('input the filepath of a file in which save molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - - gjf_root_path = input('Input the root path of output gjf files, that is, ' - 'all the output gjf files will be make in this path. e.g. test_gjf \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: if press Enter directly, test_gjf will be used. \n') - if not gjf_root_path: - gjf_root_path = 'test_gjf' - - nproc = input('input the CPU cores you want to use. e.g. 12 \n') - if not nproc: - nproc = '12' - - mem = input('input the memory you want to use. e.g. 12GB \n') - if not mem: - mem = '12GB' - - gaussian_keywords = input('input the keywords of Gaussian to define task you want to run.' - 'e.g. #p opt freq b3lyp/6-31g* \n' - 'Hint1: if press Enter directly, "#p opt freq b3lyp/6-31g*" will be used. \n') - if not gaussian_keywords: - gaussian_keywords = '#p opt freq b3lyp/6-31g*' - - charge_and_multiplicity = input('Input charge and multiplicity. e.g. 0 1 \n' - 'Hint: If press Enter directly, Groupy will automatically calculate them') - if not charge_and_multiplicity: - charge_and_multiplicity = None - - add_other_tasks = input('Weather to add some other tasks in this .gjf (y/n). \n') - if add_other_tasks in ['n', 'no', 'N']: - add_other_tasks = False - other_tasks = None - else: - other_tasks = input('Input keywords you want to add. ' - 'If there are more than one other tasks, Please separate them with commas (,) \n' - 'Hint: if press Enter directly, "#p m062x/def2tzvp geom=check,#p m062x/def2tzvp scrf=solvent=water geom=check" will be used \n') - if not other_tasks: - other_tasks = None - else: - other_tasks = other_tasks.split(',') - - n_jobs = int(input('input number of cores to use. e.g. 4 \n')) - batch_size = input('input batch size for task decomposition. e.g. 20, you can also enter "auto" \n') - try: - batch_size = int(batch_size) - except: - pass - - generator = Generator() - generator.batch_smi_to_gjf_mpi(smiles_file_path=smiles_file_path, gjf_root_path=gjf_root_path, - nproc=nproc, mem=mem, gaussian_keywords=gaussian_keywords, - charge_and_multiplicity=charge_and_multiplicity, - add_other_tasks=add_other_tasks, other_tasks=other_tasks, - n_jobs=n_jobs, batch_size=batch_size) - print('\n\n\n') - return None - - -def main_function_view(): - viewer = Viewer() - show_flag = input('show a SMILES (enter 1) or file (enter 2). \n(enter help to show supported file formats)\n') - if show_flag in ['1', 'SMILES', 'smiles']: - smiles = input('input the SMILES of a molecule. \n') - viewer.view_mol(mol=smiles, mol_type='smi') - - elif show_flag in ['2', 'file', 'FILE']: - file_path = input('input the file path you want to show. e.g. ./temporary.xyz \n') - file_type = input('input file format. e.g. xyz \n') - viewer.view_mol(mol=file_path, mol_type=file_type) - - elif show_flag in ['h', 'help', 'H', 'Help', 'HELP']: - viewer.plot_supported_format() - - else: - print('Unrecognized command!') - return None - - -def main_function_1(): - """ - 1. calculate properties of a molecule by input SMILES of this molecule. - """ - smiles = input('input the SMILES of a molecule. \n') - smiles = smiles.strip() - calculator = Calculator() - result = calculator.calculate_a_mol(smiles, debug=False) - print(result) - export_flag = input('Do you want to export results to a csv file? (y/n) \n') - if export_flag in ['y', 'Y', '1']: - export_a_dict(result_dict=result, export_path='{}_calculate.csv'.format(smiles)) - print('the results have been export to {}_calculate.csv! \n\n'.format(smiles)) - else: - print('\n\n\n') - return None - - -def main_function_2(): - """ - 2. count group number of a molecule by input SMILES of this molecule. - """ - smiles = input('input the SMILES of a molecule. \n') - smiles = smiles.strip() - mol = Chem.MolFromSmiles(smiles) - counter = Counter() - clear_mode_flag = input('clear mode? (y/n) \n') - if clear_mode_flag in ['y', 'Y', '1']: - result = counter.count_a_mol(mol, clear_mode=True) - else: - result = counter.count_a_mol(mol, clear_mode=False) - print(result) - export_flag = input('Do you want to export results to a file? (y/n) \n') - if export_flag in ['y', 'Y', '1']: - export_a_dict(result_dict=result, export_path='{}_count.csv'.format(smiles)) - print('the results have been export to {}_count.csv! \n\n'.format(smiles)) - else: - print('\n\n\n') - return None - - -def main_function_3(): - """ - 3. calculate properties of a batch of molecules by input filepath of a file in which save molecules (.txt, .csv, .xlsx). - """ - smiles_file_path = input('input the filepath of a file (.txt, .csv, .xlsx) in which save molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - calculator = Calculator() - calculator.calculate_mols(smiles_file_path=smiles_file_path, - properties_file_path='batch_calculate_results.csv') - print('\n\n\n') - return None - - -def main_function_minus_3(): - """ - -3. calculate properties of a batch of molecules by input filepath of a file in which save molecules (.txt, .csv, .xlsx). - use mpi - """ - smiles_file_path = input('input the filepath of a file (.txt, .csv, .xlsx) in which save molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - n_jobs = int(input('input number of cores to use. e.g. 4 \n')) - batch_size = input('input batch size for task decomposition. e.g. 20, you can also enter "auto" \n') - try: - batch_size = int(batch_size) - except: - pass - calculator = Calculator() - calculator.calculate_mols_mpi(smiles_file_path=smiles_file_path, - properties_file_path='batch_calculate_results_mpi.csv', - n_jobs=n_jobs, batch_size=batch_size) - print('\n\n\n') - return None - - -def main_function_4(): - """ - 4. count group number of a batch of molecules by input filepath of a file in which save molecules (.txt, .csv, .xlsx). - """ - smiles_file_path = input('input the filepath of a file in which save molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - counter = Counter() - counter.count_mols(smiles_file_path=smiles_file_path, - count_result_file_path='batch_count_result.csv', add_note=True, add_smiles=True) - print('\n\n\n') - return None - - -def main_function_minus_4(): - """ - -4. count group number of a batch of molecules by input filepath of a file in which save molecules (.txt, .csv, .xlsx). - use mpi - """ - smiles_file_path = input('input the filepath of a file (.txt, .csv, .xlsx) in which save molecules. ' - 'e.g. ./gp_3x_test_mol/SMILES.txt \n' - 'Hint1: Pay attention to the difference of path format in Linux and Windows! \n' - 'Hint2: The file must not have blank line! \n') - n_jobs = int(input('input number of cores to use. e.g. 4 \n')) - batch_size = input('input batch size for task decomposition. e.g. 20, you can also enter "auto" \n') - try: - batch_size = int(batch_size) - except: - pass - counter = Counter() - counter.count_mols_mpi(smiles_file_path=smiles_file_path, - count_result_file_path='batch_count_result_mpi.csv', add_note=True, add_smiles=True, - n_jobs=n_jobs, batch_size=batch_size) - print('\n\n\n') - return None - - -def main(): - print(logo) - - while True: - - flag_main = input( - '\n' - '--------------------------------------------------------------------------------- \n' - 'You are in main interface \n' - 'what to do? \n' - ' q. exit \n' - ' 0. show molecular structure by SMILES or file. \n' - ' 1. calculate properties of a molecule. \n' - ' 2. count group number of a molecule. \n' - ' 3. calculate properties of a batch of molecules. -3. use mpi to accelerate.\n' - ' 4. count group number of a batch of molecules. -4. use mpi to accelerate.\n' - ' 5. generate files or covert file format for MD, DFT, Visualization... \n' - '--------------------------------------------------------------------------------- \n' - ) - - if flag_main == 'q': - print('exit Groupy, have a nice day!') - break - - elif flag_main == '0': - main_function_view() - - elif flag_main == '1': - main_function_1() - - elif flag_main == '2': - main_function_2() - - elif flag_main == '3': - main_function_3() - - elif flag_main == '-3': - main_function_minus_3() - - elif flag_main == '4': - main_function_4() - - elif flag_main == '-4': - main_function_minus_4() - - elif flag_main == '5': - main_function_5() - - else: - print('Please input the right option. ' - 'For more information, you are supposed to read the manual.') - - -if __name__ == '__main__': - - main() diff --git a/dist/Groupy-3.0.0-py3-none-any.whl b/dist/Groupy-3.0.0-py3-none-any.whl deleted file mode 100644 index 783d558..0000000 Binary files a/dist/Groupy-3.0.0-py3-none-any.whl and /dev/null differ diff --git a/dist/groupy-3.0.0.tar.gz b/dist/groupy-3.0.0.tar.gz deleted file mode 100644 index 9643379..0000000 Binary files a/dist/groupy-3.0.0.tar.gz and /dev/null differ diff --git a/groupy/__init__.py b/groupy/__init__.py index b233fce..eb75328 100644 --- a/groupy/__init__.py +++ b/groupy/__init__.py @@ -1,7 +1,23 @@ -from . import gp_loader -from . import gp_tool -from . import gp_viewer -from . import gp_convertor -from . import gp_calculator -from . import gp_counter -from . import gp_generator +"""Groupy package namespace.""" + +import logging + +__version__ = "3.0.0" + +logging.getLogger(__name__).addHandler(logging.NullHandler()) + +__all__ = [ + "api", + "chem", + "cli", + "exceptions", + "gui", + "io", + "gp_loader", + "gp_tool", + "gp_viewer", + "gp_convertor", + "gp_calculator", + "gp_counter", + "gp_generator", +] diff --git a/groupy/api.py b/groupy/api.py new file mode 100644 index 0000000..93c2589 --- /dev/null +++ b/groupy/api.py @@ -0,0 +1,105 @@ +"""Programmatic API helpers for non-interactive Groupy workflows.""" + +from __future__ import annotations + +from collections.abc import Iterable +from pathlib import Path +from typing import Any + +from groupy.exceptions import ConversionError +from groupy.gp_calculator import Calculator +from groupy.gp_counter import Counter +from groupy.io import load_smiles_file, write_records_csv + + +def count_smiles( + smiles: str, + *, + include_zero: bool = False, + include_smiles: bool = True, + counter: Counter | None = None, +) -> dict[str, Any]: + """Count molecular groups for one SMILES string.""" + active_counter = counter or Counter() + return active_counter.count_a_mol( + smiles, + clear_mode=not include_zero, + add_smiles=include_smiles, + ) + + +def count_many_smiles( + smiles_values: Iterable[str], + *, + include_zero: bool = False, + include_smiles: bool = True, +) -> list[dict[str, Any]]: + """Count molecular groups for many SMILES strings.""" + counter = Counter() + return [ + count_smiles( + smiles, + include_zero=include_zero, + include_smiles=include_smiles, + counter=counter, + ) + for smiles in smiles_values + ] + + +def calculate_smiles( + smiles: str, + *, + check_hydrocarbon: bool = True, + parameter_type: str = "step_wise", + calculator: Calculator | None = None, +) -> dict[str, Any]: + """Calculate properties for one SMILES string.""" + active_calculator = calculator or Calculator() + return active_calculator.calculate_a_mol( + smiles, + check_hydrocarbon=check_hydrocarbon, + parameter_type=parameter_type, + ) + + +def calculate_many_smiles( + smiles_values: Iterable[str], + *, + check_hydrocarbon: bool = True, + parameter_type: str = "step_wise", +) -> list[dict[str, Any]]: + """Calculate properties for many SMILES strings.""" + calculator = Calculator() + return [ + calculate_smiles( + smiles, + check_hydrocarbon=check_hydrocarbon, + parameter_type=parameter_type, + calculator=calculator, + ) + for smiles in smiles_values + ] + + +def convert_file( + input_path: str | Path, + from_format: str, + to_format: str, + output_path: str | Path | None = None, +) -> Path: + """Convert one molecular file and return the output path.""" + from groupy.gp_convertor import Convertor + + input_file = Path(input_path) + target_format = to_format.lstrip(".") + output_file = Path(output_path) if output_path is not None else input_file.with_suffix(f".{target_format}") + converted_path = Convertor.convert_file_type( + in_format=from_format.lstrip("."), + in_path=str(input_file), + out_format=target_format, + out_path=str(output_file), + ) + if converted_path is None or not output_file.exists(): + raise ConversionError(f"Failed to convert {input_file} to {output_file}.") + return Path(converted_path) diff --git a/groupy/chem.py b/groupy/chem.py new file mode 100644 index 0000000..44c1acf --- /dev/null +++ b/groupy/chem.py @@ -0,0 +1,18 @@ +"""Small chemistry helpers shared by Groupy modules.""" + +from rdkit import Chem, rdBase + +from groupy.exceptions import InvalidSmilesError + + +def ensure_mol(mol): + """Return an RDKit molecule from a SMILES string or molecule-like object.""" + if isinstance(mol, str): + with rdBase.BlockLogs(): + parsed = Chem.MolFromSmiles(mol) + if parsed is None: + raise InvalidSmilesError(f"Invalid SMILES: {mol}") + return parsed + if mol is None or not hasattr(mol, "GetAtoms"): + raise TypeError("Expected a SMILES string or an RDKit molecule.") + return mol diff --git a/groupy/cli.py b/groupy/cli.py new file mode 100644 index 0000000..576adbc --- /dev/null +++ b/groupy/cli.py @@ -0,0 +1,222 @@ +"""Command-line interface for Groupy. + +The legacy interactive menu is still available, but new subcommands should call +the library API directly so they can also be reused by future GUI code. +""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path +from typing import Sequence + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="Groupy", + description="Groupy command-line tools for molecular analysis.", + ) + subparsers = parser.add_subparsers(dest="command") + + subparsers.add_parser( + "interactive", + help="start the legacy interactive menu", + ) + + count_parser = subparsers.add_parser( + "count", + help="count molecular groups", + ) + count_input = count_parser.add_mutually_exclusive_group(required=True) + count_input.add_argument( + "--smiles", + help="SMILES string to analyze", + ) + count_input.add_argument( + "--input", + type=Path, + help="txt, csv, or xlsx file containing SMILES values", + ) + count_parser.add_argument( + "--include-zero", + action="store_true", + help="include groups with zero counts in the output", + ) + count_parser.add_argument( + "--no-smiles", + action="store_true", + help="omit the input SMILES from the output", + ) + count_parser.add_argument( + "--output", + type=Path, + help="optional CSV path for saving the result", + ) + + calculate_parser = subparsers.add_parser( + "calculate", + help="calculate molecular properties", + ) + calculate_input = calculate_parser.add_mutually_exclusive_group(required=True) + calculate_input.add_argument( + "--smiles", + help="SMILES string to analyze", + ) + calculate_input.add_argument( + "--input", + type=Path, + help="txt, csv, or xlsx file containing SMILES values", + ) + calculate_parser.add_argument( + "--parameter-type", + choices=["step_wise", "simultaneous"], + default="step_wise", + help="group contribution parameter type", + ) + calculate_parser.add_argument( + "--no-check-hydrocarbon", + action="store_true", + help="calculate combustion-related properties without hydrocarbon filtering", + ) + calculate_parser.add_argument( + "--output", + type=Path, + help="optional CSV path for saving the result", + ) + + convert_parser = subparsers.add_parser( + "convert", + help="convert one molecular file to another format", + ) + convert_parser.add_argument( + "--input", + type=Path, + required=True, + help="input molecule file", + ) + convert_parser.add_argument( + "--from", + dest="from_format", + required=True, + help="input file format, such as xyz or mol", + ) + convert_parser.add_argument( + "--to", + dest="to_format", + required=True, + help="output file format, such as mol2 or pdb", + ) + convert_parser.add_argument( + "--output", + type=Path, + help="optional output file path", + ) + + return parser + + +def _run_legacy_interactive() -> int: + from groupy.groupy_main import main as legacy_main + + legacy_main() + return 0 + + +def _run_count(args: argparse.Namespace) -> int: + from groupy.api import count_many_smiles, count_smiles, load_smiles_file, write_records_csv + + if args.smiles: + records = [ + count_smiles( + args.smiles, + include_zero=args.include_zero, + include_smiles=not args.no_smiles, + ) + ] + else: + records = count_many_smiles( + load_smiles_file(args.input), + include_zero=args.include_zero, + include_smiles=not args.no_smiles, + ) + + if args.output: + write_records_csv(records, args.output) + else: + print(_format_json_output(records, single=bool(args.smiles))) + + return 0 + + +def _run_calculate(args: argparse.Namespace) -> int: + from groupy.api import calculate_many_smiles, calculate_smiles, load_smiles_file, write_records_csv + + check_hydrocarbon = not args.no_check_hydrocarbon + if args.smiles: + records = [ + calculate_smiles( + args.smiles, + check_hydrocarbon=check_hydrocarbon, + parameter_type=args.parameter_type, + ) + ] + else: + records = calculate_many_smiles( + load_smiles_file(args.input), + check_hydrocarbon=check_hydrocarbon, + parameter_type=args.parameter_type, + ) + + if args.output: + write_records_csv(records, args.output) + else: + print(_format_json_output(records, single=bool(args.smiles))) + + return 0 + + +def _run_convert(args: argparse.Namespace) -> int: + from groupy.api import convert_file + from groupy.exceptions import ConversionError + + try: + output_path = convert_file( + args.input, + from_format=args.from_format, + to_format=args.to_format, + output_path=args.output, + ) + except (ConversionError, ImportError) as exc: + print(str(exc), file=sys.stderr) + return 1 + + print(_format_json_output([{"output": str(output_path)}], single=True)) + return 0 + + +def _format_json_output(records: list[dict], *, single: bool) -> str: + payload = records[0] if single else records + return json.dumps(payload, ensure_ascii=False, sort_keys=True) + + +def main(argv: Sequence[str] | None = None) -> int: + parser = build_parser() + args = parser.parse_args(argv) + + if args.command is None or args.command == "interactive": + return _run_legacy_interactive() + if args.command == "count": + return _run_count(args) + if args.command == "calculate": + return _run_calculate(args) + if args.command == "convert": + return _run_convert(args) + + parser.error(f"Unsupported command: {args.command}") + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/groupy/exceptions.py b/groupy/exceptions.py new file mode 100644 index 0000000..ea093d6 --- /dev/null +++ b/groupy/exceptions.py @@ -0,0 +1,13 @@ +"""Custom exceptions used by Groupy library APIs.""" + + +class GroupyError(Exception): + """Base class for expected Groupy runtime errors.""" + + +class InvalidSmilesError(GroupyError, ValueError): + """Raised when a SMILES string cannot be parsed into a molecule.""" + + +class ConversionError(GroupyError): + """Raised when a molecule or file conversion step fails.""" diff --git a/groupy/gp_calculator.py b/groupy/gp_calculator.py index fef4034..24b4344 100644 --- a/groupy/gp_calculator.py +++ b/groupy/gp_calculator.py @@ -1,11 +1,43 @@ +import logging +from pathlib import Path from rdkit import Chem from math import log import pandas as pd from tqdm import tqdm from joblib import Parallel, delayed +from groupy.chem import ensure_mol +from groupy.exceptions import InvalidSmilesError from groupy.gp_loader import Loader from groupy.gp_counter import Counter +from groupy.io import load_smiles_file, write_text_lines + +logger = logging.getLogger(__name__) + + +def _report_progress(message, verbose): + logger.info(message) + if verbose: + print(message) + + +def _failed_calculation_result(smiles, error): + return {'smiles': smiles, + 'molar_mass': '?', + 'flash_point/K': '?', + 'Tm/K': '?', 'Tb/K': '?', 'Tc/K': '?', + 'Pc/bar': '?', 'Vc/(cm3/mol)': '?', + 'density/(g/cm3)': '?', + 'delta_G/(KJ/mol)': '?', + 'delta_Hf/(KJ/mol)': '?', + 'delta_Hvap/(KJ/mol)': '?', + 'delta_Hfus/(KJ/mol)': '?', + 'molar_volume/(cm3/mol)(default298K)': '?', + 'delta_Hc/(KJ/mol)': '?', + 'mass_calorific_value_h/(MJ/kg)': '?', + 'ISP': '?', + 'note': 'There must be something wrong with this SMILES', + 'error': error} class Calculator: @@ -318,7 +350,8 @@ def is_hydrocarbon(self, mol): return False - def calculate_a_mol(self, mol, parameter_type='step_wise', check_hydrocarbon=True, debug=False): + def calculate_a_mol(self, mol, parameter_type='step_wise', check_hydrocarbon=True, debug=False, + raise_on_error=False): """ Calculating properties of a molecule. :param mol: instance of rdkit.Chem.rdchem.Mol or SMILES str which will be converter to rdkit.Chem.rdchem.Mol automatically. @@ -326,6 +359,7 @@ def calculate_a_mol(self, mol, parameter_type='step_wise', check_hydrocarbon=Tru :param check_hydrocarbon: bool. Since Calculator.delta_Hc() was designed for hydrocarbon, if set to True, Calculator will check whether the molecule is hydrocarbon. If the molecule is not hydrocarbon, Calculator will not calculate delta_Hc, q and ISP. If set to False, Calculator will calculate these properties no matter whether the molecule is hydrocarbon. Default=True. + :param raise_on_error: if True, re-raise expected input/calculation errors instead of returning placeholders. :return: dict like {'smiles': init_smi, 'molar_mass': value, 'flash_point/K': value, @@ -343,9 +377,11 @@ def calculate_a_mol(self, mol, parameter_type='step_wise', check_hydrocarbon=Tru 'note': value} """ init_smi = mol + if parameter_type not in ['step_wise', 'simultaneous']: + raise ValueError('parameter_type must be "step_wise" or "simultaneous".') + try: - if isinstance(mol, str): - mol = Chem.MolFromSmiles(mol) + mol = ensure_mol(mol) group_number = self.counter.count_a_mol(mol, clear_mode=True, add_note=True) if group_number.get('note', ''): counter_note = group_number['note'] @@ -357,8 +393,6 @@ def calculate_a_mol(self, mol, parameter_type='step_wise', check_hydrocarbon=Tru parameters = self.parameters_step_wise elif parameter_type == 'simultaneous': parameters = self.parameters_simultaneous - else: - raise NotImplemented('不可用的参数类型,只能使用step_wise或simultaneous') if debug: print(group_number) @@ -409,25 +443,15 @@ def calculate_a_mol(self, mol, parameter_type='step_wise', check_hydrocarbon=Tru 'mass_calorific_value_h/(MJ/kg)': q, 'ISP': isp, 'note': counter_note + ' at 298K'} - except: - print(f'Error! There is something wrong when calculating {init_smi}, please check it.') - return {'smiles': init_smi, - 'molar_mass': '?', - 'flash_point/K': '?', - 'Tm/K': '?', 'Tb/K': '?', 'Tc/K': '?', - 'Pc/bar': '?', 'Vc/(cm3/mol)': '?', - 'density/(g/cm3)': '?', - 'delta_G/(KJ/mol)': '?', - 'delta_Hf/(KJ/mol)': '?', - 'delta_Hvap/(KJ/mol)': '?', - 'delta_Hfus/(KJ/mol)': '?', - 'molar_volume/(cm3/mol)(default298K)': '?', - 'delta_Hc/(KJ/mol)': '?', - 'mass_calorific_value_h/(MJ/kg)': '?', - 'ISP': '?', - 'note': 'There must be something wrong with this SMILES'} - - def calculate_mols(self, smiles_file_path, properties_file_path='gp_3x_result.csv', check_hydrocarbon=True, parameter_type='step_wise'): + except (InvalidSmilesError, TypeError, ValueError, KeyError, AttributeError, ArithmeticError) as exc: + if raise_on_error: + raise + logger.warning("Failed to calculate properties for %r: %s", init_smi, exc) + return _failed_calculation_result(init_smi, str(exc)) + + def calculate_mols(self, smiles_file_path, properties_file_path='gp_3x_result.csv', + check_hydrocarbon=True, parameter_type='step_wise', + error_file_path=None, verbose=True, continue_on_error=True): """ Calculating properties of a batch of molecules. :param smiles_file_path: path of the file(.txt, .xlsx, .csv) in which saved SMILES. @@ -435,39 +459,51 @@ def calculate_mols(self, smiles_file_path, properties_file_path='gp_3x_result.cs :param check_hydrocarbon: bool. Since Calculator.delta_Hc() was designed for hydrocarbon, if set to True, Calculator will check whether the molecule is hydrocarbon. If the molecule is not hydrocarbon, Calculator will not calculate delta_Hc, q and ISP. If set to False, Calculator will calculate these properties no matter whether the molecule is hydrocarbon. Default=True. + :param error_file_path: optional path for writing SMILES strings that failed during batch calculation. + :param verbose: if True, print progress messages and progress bars. Set False for programmatic or GUI use. + :param continue_on_error: if True, keep legacy batch behavior and write placeholder rows for failed SMILES. :return: instance of pandas.DataFrame """ - print('reading input file...') - if smiles_file_path.endswith('.txt'): - smiles_iterator = list(open(smiles_file_path)) - elif smiles_file_path.endswith('.xlsx'): - smiles_iterator = pd.read_excel(smiles_file_path)['smiles'] - elif smiles_file_path.endswith('.csv'): - smiles_iterator = pd.read_csv(smiles_file_path)['smiles'] - else: - print('无法识别的文件类型,请以.txt/.xlsx/.csv类型的文件作为输入。') + _report_progress('reading input file...', verbose) + try: + smiles_iterator = load_smiles_file(smiles_file_path) + except ValueError: + logger.warning('Unable to read SMILES input file %s; expected .txt, .xlsx, or .csv.', smiles_file_path) + if verbose: + print('无法识别的文件类型,请以.txt/.xlsx/.csv类型的文件作为输入。') return None mol_number = len(smiles_iterator) - print('reading completed,A total of {} molecules detected, start calculating properties...'.format(mol_number)) - print('start calculating...') + _report_progress( + 'reading completed,A total of {} molecules detected, start calculating properties...'.format(mol_number), + verbose, + ) + _report_progress('start calculating...', verbose) properties_dict_list = [] error_smi = [] - for i in tqdm(smiles_iterator): - try: - properties_dict_list.append(self.calculate_a_mol(i, check_hydrocarbon=check_hydrocarbon, parameter_type=parameter_type)) - except: + for i in tqdm(smiles_iterator, disable=not verbose): + result = self.calculate_a_mol( + i, + check_hydrocarbon=check_hydrocarbon, + parameter_type=parameter_type, + raise_on_error=not continue_on_error, + ) + properties_dict_list.append(result) + if result.get('error'): error_smi.append(i) - print('calculation completed!') - print('start to export result to {} ...'.format(properties_file_path)) + _report_progress('calculation completed!', verbose) + _report_progress('start to export result to {} ...'.format(properties_file_path), verbose) result = pd.DataFrame(properties_dict_list) - result.to_csv(properties_file_path, index_label='index') - with open('error.txt', 'w') as f: - for i in error_smi: - f.write(i + '\n') - print('Done!') + output_path = Path(properties_file_path) + output_path.parent.mkdir(parents=True, exist_ok=True) + result.to_csv(output_path, index_label='index') + if error_file_path is not None: + write_text_lines(error_smi, error_file_path) + _report_progress('Done!', verbose) return result - def calculate_mols_mpi(self, smiles_file_path, properties_file_path='gp_3x_result_mpi.csv', check_hydrocarbon=True, parameter_type='step_wise', n_jobs=1, batch_size='auto'): + def calculate_mols_mpi(self, smiles_file_path, properties_file_path='gp_3x_result_mpi.csv', + check_hydrocarbon=True, parameter_type='step_wise', + n_jobs=1, batch_size='auto', verbose=True, continue_on_error=True): """ Calculating properties of a batch of molecules with MPI acceleration. :param smiles_file_path: path of the file(.txt, .xlsx, .csv) in which saved SMILES. @@ -477,30 +513,52 @@ def calculate_mols_mpi(self, smiles_file_path, properties_file_path='gp_3x_resul Default=True. :param n_jobs: int. Number of cores. Default=1 :param batch_size: int. Task number per core. default='auto' + :param verbose: if True, print progress messages. Set False for programmatic or GUI use. + :param continue_on_error: if True, keep legacy batch behavior and write placeholder rows for failed SMILES. :return: instance of pandas.DataFrame """ - print('reading input file...') - if smiles_file_path.endswith('.txt'): - smiles_iterator = list(open(smiles_file_path)) - elif smiles_file_path.endswith('.xlsx'): - smiles_iterator = pd.read_excel(smiles_file_path)['smiles'] - elif smiles_file_path.endswith('.csv'): - smiles_iterator = pd.read_csv(smiles_file_path)['smiles'] - else: - print('无法识别的文件类型,请以.txt/.xlsx/.csv类型的文件作为输入。') + _report_progress('reading input file...', verbose) + try: + smiles_iterator = load_smiles_file(smiles_file_path) + except ValueError: + logger.warning('Unable to read SMILES input file %s; expected .txt, .xlsx, or .csv.', smiles_file_path) + if verbose: + print('无法识别的文件类型,请以.txt/.xlsx/.csv类型的文件作为输入。') return None mol_number = len(smiles_iterator) - print('reading completed,A total of {} molecules detected, start calculating properties...'.format(mol_number)) - print('start calculating...') - task = [delayed(self.calculate_a_mol)(i, parameter_type=parameter_type, check_hydrocarbon=check_hydrocarbon) for i in smiles_iterator] + _report_progress( + 'reading completed,A total of {} molecules detected, start calculating properties...'.format(mol_number), + verbose, + ) + _report_progress('start calculating...', verbose) + task = [ + delayed(self.calculate_a_mol)( + i, + parameter_type=parameter_type, + check_hydrocarbon=check_hydrocarbon, + raise_on_error=not continue_on_error, + ) + for i in smiles_iterator + ] properties_dict_list = Parallel(n_jobs=n_jobs, batch_size=batch_size)(task) - print('calculation completed!') - print('start to export result to {} ...'.format(properties_file_path)) + _report_progress('calculation completed!', verbose) + _report_progress('start to export result to {} ...'.format(properties_file_path), verbose) result = pd.DataFrame(properties_dict_list) - result.to_csv(properties_file_path, index_label='index') - print('Done!') + output_path = Path(properties_file_path) + output_path.parent.mkdir(parents=True, exist_ok=True) + result.to_csv(output_path, index_label='index') + _report_progress('Done!', verbose) return result + def calculate_mols_parallel(self, *args, **kwargs): + """ + Calculating properties of a batch of molecules with joblib parallelism. + + This is the preferred name for new code. It calls calculate_mols_mpi() + for backward compatibility with the original API. + """ + return self.calculate_mols_mpi(*args, **kwargs) + # if __name__ == '__main__': # import time @@ -508,4 +566,4 @@ def calculate_mols_mpi(self, smiles_file_path, properties_file_path='gp_3x_resul # c = Calculator() # c.calculate_mols_mpi(smiles_file_path='../gp_3x_test_mol/SMILES.txt', n_jobs=4, check_hydrocarbon=False) # t2 = time.time() -# print(t2 - t1) \ No newline at end of file +# print(t2 - t1) diff --git a/groupy/gp_convertor.py b/groupy/gp_convertor.py index bff4457..4c36a60 100644 --- a/groupy/gp_convertor.py +++ b/groupy/gp_convertor.py @@ -1,13 +1,34 @@ -import os +import logging +from pathlib import Path from rdkit import Chem from rdkit.Chem import AllChem -from openbabel import pybel -import pandas as pd from tqdm import tqdm from joblib import Parallel, delayed from pprint import pprint -from groupy.gp_tool import Tool +from groupy.chem import ensure_mol +from groupy.exceptions import ConversionError, InvalidSmilesError +from groupy.io import load_smiles_file, write_text_lines + +logger = logging.getLogger(__name__) +CONVERSION_EXCEPTIONS = (OSError, RuntimeError, StopIteration, ValueError) + + +def _report_progress(message, verbose): + logger.info(message) + if verbose: + print(message) + + +def _load_pybel(): + try: + from openbabel import pybel + except ImportError as exc: + raise ImportError( + "OpenBabel is required for this conversion feature. " + "Install it with `conda install -c conda-forge openbabel`." + ) from exc + return pybel class Convertor: @@ -30,9 +51,10 @@ def smi_to_xyz(smi, xyz_path=None): :param xyz_path: str. Path of xyz file you want to generate. Default={smi}.xyz :return: bool. True if the xyz file is successfully generated. """ - mol = Chem.MolFromSmiles(smi) - if mol is None: - print(f'can not read {smi}, please check your SMILES') + try: + mol = ensure_mol(smi) + except InvalidSmilesError as exc: + logger.warning("Failed to parse SMILES %r for xyz conversion: %s", smi, exc) return False mol_with_h = Chem.AddHs(mol) @@ -42,29 +64,20 @@ def smi_to_xyz(smi, xyz_path=None): AllChem.MMFFOptimizeMolecule(mol_with_h) opt = Chem.MolToMolBlock(mol_with_h) except ValueError: + pybel = _load_pybel() mol = pybel.readstring("smi", smi) mol.addh() if mol.make3D() is None: opt = mol.write("mol") else: - print(f'Error! There is something wrong when converting {smi} to xyz file, please check it.') + logger.warning("OpenBabel failed to generate 3D coordinates for %r", smi) return False - # Windows 下pybel有问题 - # try: - # mol = pybel.readstring("smi", smi) - # mol.addh() - # if mol.make3D() is None: - # opt = mol.write("mol") - # else: - # return False - # except: - # AllChem.MMFFOptimizeMolecule(mol_with_h) - # opt = Chem.MolToMolBlock(mol_with_h) - if xyz_path is None: xyz_path = smi + '.xyz' - with open(xyz_path, 'w') as file: + xyz_output_path = Path(xyz_path) + xyz_output_path.parent.mkdir(parents=True, exist_ok=True) + with xyz_output_path.open('w', encoding='utf-8') as file: file.write('{}\n'.format(atom_number)) file.write(smi + '\n') for index, i in enumerate(opt.splitlines()[4:]): @@ -76,77 +89,121 @@ def smi_to_xyz(smi, xyz_path=None): file.write(i.split()[2] + '\n') return True - def batch_smi_to_xyz(self, smiles_file_path, xyz_root_path): + def batch_smi_to_xyz(self, smiles_file_path, xyz_root_path, + fail_file_path=None, succeed_file_path=None, verbose=True, + continue_on_error=True): """ Converting a batch of SMILES to xyz files. :param smiles_file_path: str. Path of the file in which saved SMILES. :param xyz_root_path: str. The folder path where all generated xyz files are saved. + :param fail_file_path: optional path for writing SMILES strings that failed to generate xyz files. + :param succeed_file_path: optional path for writing SMILES strings that successfully generated xyz files. + :param verbose: if True, print progress messages and progress bars. Set False for programmatic or GUI use. + :param continue_on_error: if True, continue converting later SMILES after one conversion fails. :return: None. """ - smiles_iterator = Tool.load_smiles_iterator(smiles_file_path=smiles_file_path) + _report_progress('reading input file...', verbose) + smiles_iterator = load_smiles_file(smiles_file_path) mol_number = len(smiles_iterator) zfill_number = len(str(mol_number)) + 3 - print('reading completed,A total of {} molecules detected, start making xyz files...'.format(mol_number)) - # make xyz_root_path - if os.path.exists(xyz_root_path): - print('xyz_root_path "{}" has been detected!'.format(xyz_root_path)) + _report_progress( + 'reading completed,A total of {} molecules detected, start making xyz files...'.format(mol_number), + verbose, + ) + xyz_root = Path(xyz_root_path) + if xyz_root.exists(): + _report_progress('xyz_root_path "{}" has been detected!'.format(xyz_root), verbose) else: - print('xyz_root_path "{}" has not been detected, I will create it for you'.format(xyz_root_path)) - os.makedirs(xyz_root_path) - # end + _report_progress( + 'xyz_root_path "{}" has not been detected, I will create it for you'.format(xyz_root), + verbose, + ) + xyz_root.mkdir(parents=True, exist_ok=True) succeed = [] fail = [] - for (index, smi) in tqdm(enumerate(smiles_iterator)): + for (index, smi) in tqdm(enumerate(smiles_iterator), disable=not verbose): smi = smi.strip() - out_name = os.path.join(xyz_root_path, '{}.xyz'.format(str(index).zfill(zfill_number))) - generate_success_flag = self.smi_to_xyz(smi=smi, xyz_path=out_name) + out_name = xyz_root / '{}.xyz'.format(str(index).zfill(zfill_number)) + generate_success_flag = self.smi_to_xyz(smi=smi, xyz_path=str(out_name)) if not generate_success_flag: fail.append(smi) + if not continue_on_error: + if fail_file_path is not None: + write_text_lines(fail, fail_file_path) + if succeed_file_path is not None: + write_text_lines(succeed, succeed_file_path) + raise ConversionError(f'Failed to generate xyz for {smi!r}.') else: succeed.append(smi) - with open('xyz_fail.txt', 'w') as f: - for i in fail: - f.write(i + '\n') - with open('xyz_succeed.txt', 'w') as f: - for i in succeed: - f.write(i + '\n') + if fail_file_path is not None: + write_text_lines(fail, fail_file_path) + if succeed_file_path is not None: + write_text_lines(succeed, succeed_file_path) if len(fail) == 0: - print('done! all .xyz files has been saved in {}'.format(xyz_root_path)) + _report_progress('done! all .xyz files has been saved in {}'.format(xyz_root), verbose) else: - print('Warning! The following SMILES fail to generate .xyz, please check...sorry(OTZ)') - print(fail) + logger.warning("Failed to generate xyz files for SMILES: %s", fail) + if verbose: + print('Warning! The following SMILES fail to generate .xyz, please check...sorry(OTZ)') + print(fail) return None - def batch_smi_to_xyz_mpi(self, smiles_file_path, xyz_root_path, n_jobs=1, batch_size='auto'): + def batch_smi_to_xyz_mpi(self, smiles_file_path, xyz_root_path, + n_jobs=1, batch_size='auto', verbose=True, continue_on_error=True): """ Converting a batch of SMILES to xyz files with MPI acceleration. :param smiles_file_path: str. Path of the file in which saved SMILES. :param xyz_root_path: str. The folder path where all generated xyz files are saved. :param n_jobs: int. number of CPU cores you want to use when generating xyz file. :param batch_size: int or str. Number of tasks per CPU core you want to use when generating xyz file. Default='auto'. + :param verbose: if True, print progress messages. Set False for programmatic or GUI use. + :param continue_on_error: if True, return all task results even when some conversions fail. :return: None. """ - smiles_iterator = Tool.load_smiles_iterator(smiles_file_path=smiles_file_path) + _report_progress('reading input file...', verbose) + smiles_iterator = load_smiles_file(smiles_file_path) mol_number = len(smiles_iterator) zfill_number = len(str(mol_number)) + 3 - print('reading completed,A total of {} molecules detected, start making xyz files...'.format(mol_number)) - # make xyz_root_path - if os.path.exists(xyz_root_path): - print('xyz_root_path "{}" has been detected!'.format(xyz_root_path)) + _report_progress( + 'reading completed,A total of {} molecules detected, start making xyz files...'.format(mol_number), + verbose, + ) + xyz_root = Path(xyz_root_path) + if xyz_root.exists(): + _report_progress('xyz_root_path "{}" has been detected!'.format(xyz_root), verbose) else: - print('xyz_root_path "{}" has not been detected, I will create it for you'.format(xyz_root_path)) - os.makedirs(xyz_root_path) - # end - - task = [delayed(self.smi_to_xyz)(smi=smi, xyz_path=os.path.join(xyz_root_path, '{}.xyz'.format(str(index).zfill(zfill_number)))) for (index, smi) in enumerate(smiles_iterator)] + _report_progress( + 'xyz_root_path "{}" has not been detected, I will create it for you'.format(xyz_root), + verbose, + ) + xyz_root.mkdir(parents=True, exist_ok=True) + + task = [ + delayed(self.smi_to_xyz)( + smi=smi, + xyz_path=str(xyz_root / '{}.xyz'.format(str(index).zfill(zfill_number))), + ) + for (index, smi) in enumerate(smiles_iterator) + ] result = Parallel(n_jobs=n_jobs, batch_size=batch_size)(task) - print('done! all .xyz files has been saved in {}'.format(xyz_root_path)) + if not continue_on_error and not all(result): + raise ConversionError('Failed to generate one or more xyz files.') + _report_progress('done! all .xyz files has been saved in {}'.format(xyz_root), verbose) return result + def batch_smi_to_xyz_parallel(self, *args, **kwargs): + """ + Converting a batch of SMILES to xyz files with joblib parallelism. + + This is the preferred name for new code. It calls batch_smi_to_xyz_mpi() + for backward compatibility with the original API. + """ + return self.batch_smi_to_xyz_mpi(*args, **kwargs) + @staticmethod def convert_file_type(in_format, in_path, out_format, out_path=None): """ @@ -156,64 +213,80 @@ def convert_file_type(in_format, in_path, out_format, out_path=None): :param out_format: str. Target format. :param out_path: str. Target file path. If set to None, out_path will be same as in_path except its suffix. """ + pybel = _load_pybel() try: - mol = pybel.readfile(in_format, in_path).__next__() + input_path = Path(in_path) + mol = pybel.readfile(in_format, str(input_path)).__next__() # print('The SMILES of this system is :') # print(mol.write('smi')) - if out_path is None: - out_path = in_path.split('.') - out_path = out_path[0] + '.' + out_format - mol.write(out_format, out_path, overwrite=True) - return None - except: - print(f'Error! There is something wrong when converting {in_path}, please check it.') + output_path = Path(out_path) if out_path is not None else input_path.with_suffix(f'.{out_format}') + output_path.parent.mkdir(parents=True, exist_ok=True) + mol.write(out_format, str(output_path), overwrite=True) + return str(output_path) + except CONVERSION_EXCEPTIONS as exc: + logger.warning("Failed to convert %s from %s to %s: %s", in_path, in_format, out_format, exc) return None - def batch_convert_file_type(self, in_format, in_root_path, out_format, out_root_path=None): + def batch_convert_file_type(self, in_format, in_root_path, out_format, out_root_path=None, verbose=True, + continue_on_error=True): """ Converting format of a batch of files. :param in_format: str. Init format. :param in_root_path: str. Path of the folder in which save files that you want to change format. :param out_format: str. Target format. :param out_root_path: str. Path of the folder where all new files are saved. If set to None, out_root_path will be same as in_root_path. + :param verbose: if True, print progress messages and progress bars. Set False for programmatic or GUI use. + :param continue_on_error: if True, continue converting later files after one conversion fails. """ + input_root = Path(in_root_path) if out_root_path is None: - out_root_path = in_root_path + output_root = input_root else: - # make out_root_path - if os.path.exists(out_root_path): - print('out_root_path "{}" has been detected!'.format(out_root_path)) + output_root = Path(out_root_path) + if output_root.exists(): + _report_progress('out_root_path "{}" has been detected!'.format(output_root), verbose) else: - print('out_root_path "{}" has not been detected, I will create it for you'.format(out_root_path)) - os.makedirs(out_root_path) - # end - in_file_names = os.listdir(in_root_path) - - in_file_names = [i for i in in_file_names if i.endswith(in_format)] - out_file_names = [i.split('.')[0] + '.{}'.format(out_format) for i in in_file_names] - - in_file_path = [os.path.join(in_root_path, i) for i in in_file_names] - out_file_path = [os.path.join(out_root_path, i) for i in out_file_names] + _report_progress( + 'out_root_path "{}" has not been detected, I will create it for you'.format(output_root), + verbose, + ) + output_root.mkdir(parents=True, exist_ok=True) + + normalized_in_format = in_format.lower().lstrip('.') + normalized_out_format = out_format.lstrip('.') + in_file_path = sorted( + path for path in input_root.iterdir() + if path.is_file() and path.suffix.lower() == f'.{normalized_in_format}' + ) + out_file_path = [output_root / f'{path.stem}.{normalized_out_format}' for path in in_file_path] error_in_file_path = [] - for index in tqdm(range(len(in_file_path))): - try: - self.convert_file_type(in_format=in_format, in_path=in_file_path[index], - out_format=out_format, out_path=out_file_path[index]) - except: - # print('Warning!!!') + for index in tqdm(range(len(in_file_path)), disable=not verbose): + converted_path = self.convert_file_type( + in_format=normalized_in_format, + in_path=str(in_file_path[index]), + out_format=normalized_out_format, + out_path=str(out_file_path[index]), + ) + if converted_path is None: error_in_file_path.append(in_file_path[index]) + if not continue_on_error: + raise ConversionError(f'Failed to convert {in_file_path[index]}.') # print('There may something wrong in {}, please check it carefully!'.format(in_file_path[index])) # When there is something wrong, print some warning if len(error_in_file_path) > 0: - print('Warning!Warning!Warning!') - for i in error_in_file_path: - print('There may something wrong in {}, please check it carefully!'.format(i)) + logger.warning("Failed to convert files: %s", error_in_file_path) + if verbose: + print('Warning!Warning!Warning!') + for i in error_in_file_path: + print('There may something wrong in {}, please check it carefully!'.format(i)) return None - def batch_convert_file_type_mpi(self, in_format, in_root_path, out_format, out_root_path=None, n_jobs=1, batch_size='auto'): + def batch_convert_file_type_mpi(self, in_format, in_root_path, out_format, + out_root_path=None, n_jobs=1, batch_size='auto', verbose=True, + continue_on_error=True): """ Converting format of a batch of files with MPI acceleration. :param in_format: str. Init format. @@ -222,89 +295,139 @@ def batch_convert_file_type_mpi(self, in_format, in_root_path, out_format, out_r :param out_root_path: str. Path of the folder where all new files are saved. If set to None, out_root_path will be same as in_root_path. :param n_jobs: int. number of CPU cores you want to use when converting file format. :param batch_size: int or str. Number of tasks per CPU core you want to use when converting file format. Default='auto'. + :param verbose: if True, print progress messages. Set False for programmatic or GUI use. + :param continue_on_error: if True, return all task results even when some conversions fail. """ + input_root = Path(in_root_path) if out_root_path is None: - out_root_path = in_root_path + output_root = input_root else: - # make out_root_path - if os.path.exists(out_root_path): - print('out_root_path "{}" has been detected!'.format(out_root_path)) + output_root = Path(out_root_path) + if output_root.exists(): + _report_progress('out_root_path "{}" has been detected!'.format(output_root), verbose) else: - print('out_root_path "{}" has not been detected, I will create it for you'.format(out_root_path)) - os.makedirs(out_root_path) - # end - in_file_names = os.listdir(in_root_path) - - in_file_names = [i for i in in_file_names if i.endswith(in_format)] - out_file_names = [i.split('.')[0] + '.{}'.format(out_format) for i in in_file_names] - - in_file_path = [os.path.join(in_root_path, i) for i in in_file_names] - out_file_path = [os.path.join(out_root_path, i) for i in out_file_names] - - task = [delayed(self.convert_file_type)(in_format=in_format, in_path=in_file_path[index],out_format=out_format, out_path=out_file_path[index]) for index in range(len(in_file_path))] + _report_progress( + 'out_root_path "{}" has not been detected, I will create it for you'.format(output_root), + verbose, + ) + output_root.mkdir(parents=True, exist_ok=True) + + normalized_in_format = in_format.lower().lstrip('.') + normalized_out_format = out_format.lstrip('.') + in_file_path = sorted( + path for path in input_root.iterdir() + if path.is_file() and path.suffix.lower() == f'.{normalized_in_format}' + ) + out_file_path = [output_root / f'{path.stem}.{normalized_out_format}' for path in in_file_path] + + task = [ + delayed(self.convert_file_type)( + in_format=normalized_in_format, + in_path=str(in_file_path[index]), + out_format=normalized_out_format, + out_path=str(out_file_path[index]), + ) + for index in range(len(in_file_path)) + ] result = Parallel(n_jobs=n_jobs, batch_size=batch_size)(task) + if not continue_on_error and any(item is None for item in result): + raise ConversionError('Failed to convert one or more files.') return result + def batch_convert_file_type_parallel(self, *args, **kwargs): + """ + Converting format of a batch of files with joblib parallelism. + + This is the preferred name for new code. It calls batch_convert_file_type_mpi() + for backward compatibility with the original API. + """ + return self.batch_convert_file_type_mpi(*args, **kwargs) + @staticmethod - def file_to_smi(file_path, format=None): + def file_to_smi(file_path, format=None, raise_on_error=False): """ Converting a file into SMILES. :param file_path: str. Path of the file. :param format: str. Format of the file. + :param raise_on_error: if True, raise ConversionError when the file cannot be read. :return: SMILES """ + pybel = _load_pybel() try: atoms = next(pybel.readfile(format=format, filename=file_path)) smi = atoms.write(format='smi').split('\t')[0] # print(smi) return smi - except: - print('There may something wrong in {}, please check it carefully!'.format(file_path)) - return 'There may something wrong in {}, please check it carefully!'.format(file_path) - - def batch_file_to_smi(self, in_format, in_root_path, out_root_path=None): + except CONVERSION_EXCEPTIONS as exc: + message = 'There may something wrong in {}, please check it carefully!'.format(file_path) + logger.warning("Failed to read SMILES from %s as %s: %s", file_path, format, exc) + if raise_on_error: + raise ConversionError(message) from exc + return message + + def batch_file_to_smi(self, in_format, in_root_path, out_root_path=None, verbose=True, + continue_on_error=True): """ Converting a batch of files into SMILES. :param in_format: str. Format of the file. :param in_root_path: str. Path of the folder in which save files that you want to convert to SMILES. :param out_root_path: str. Path of the folder in which save the file that save SMILES of molecules. If set to None, out_root_path will be same as in_root_path. + :param verbose: if True, print progress messages and progress bars. Set False for programmatic or GUI use. + :param continue_on_error: if True, continue converting later files after one conversion fails. """ + input_root = Path(in_root_path) if out_root_path is None: - out_root_path = in_root_path + output_root = input_root else: - # make out_root_path - if os.path.exists(out_root_path): - print('out_root_path "{}" has been detected!'.format(out_root_path)) + output_root = Path(out_root_path) + if output_root.exists(): + _report_progress('out_root_path "{}" has been detected!'.format(output_root), verbose) else: - print('out_root_path "{}" has not been detected, I will create it for you'.format(out_root_path)) - os.makedirs(out_root_path) - # end - in_file_names = os.listdir(in_root_path) - in_file_names = [i for i in in_file_names if i.endswith(in_format)] - in_file_path = [os.path.join(in_root_path, i) for i in in_file_names] + _report_progress( + 'out_root_path "{}" has not been detected, I will create it for you'.format(output_root), + verbose, + ) + output_root.mkdir(parents=True, exist_ok=True) + normalized_in_format = in_format.lower().lstrip('.') + in_file_path = sorted( + path for path in input_root.iterdir() + if path.is_file() and path.suffix.lower() == f'.{normalized_in_format}' + ) error_in_file_path = [] smi_list = [] - for index in tqdm(range(len(in_file_path))): + for index in tqdm(range(len(in_file_path)), disable=not verbose): try: - smi_list.append(self.file_to_smi(format=in_format, file_path=in_file_path[index])) - except: - # print('Warning!!!') + smi_list.append( + self.file_to_smi( + format=normalized_in_format, + file_path=str(in_file_path[index]), + raise_on_error=not continue_on_error, + ) + ) + except ConversionError as exc: + logger.warning("Failed to read SMILES from %s: %s", in_file_path[index], exc) error_in_file_path.append(in_file_path[index]) + if not continue_on_error: + raise # print('There may something wrong in {}, please check it carefully!'.format(in_file_path[index])) # When there is something wrong, print some warning if len(error_in_file_path) > 0: - print('Warning!Warning!Warning!') - for i in error_in_file_path: - print('There may something wrong in {}, please check it carefully!'.format(i)) - - with open(os.path.join(out_root_path, 'SMILES.txt'), 'w') as f: + logger.warning("Failed to read SMILES from files: %s", error_in_file_path) + if verbose: + print('Warning!Warning!Warning!') + for i in error_in_file_path: + print('There may something wrong in {}, please check it carefully!'.format(i)) + + smiles_output_path = output_root / 'SMILES.txt' + with smiles_output_path.open('w', encoding='utf-8') as f: for i in smi_list: f.write(i + '\n') return smi_list - def batch_file_to_smi_mpi(self, in_format, in_root_path, out_root_path=None, n_jobs=1, batch_size='auto'): + def batch_file_to_smi_mpi(self, in_format, in_root_path, out_root_path=None, + n_jobs=1, batch_size='auto', verbose=True, continue_on_error=True): """ Converting a batch of files into SMILES with MPI acceleration. :param in_format: str. Format of the file. @@ -313,29 +436,53 @@ def batch_file_to_smi_mpi(self, in_format, in_root_path, out_root_path=None, n_j If set to None, out_root_path will be same as in_root_path. :param n_jobs: int. number of CPU cores you want to use when converting file to SMILES. :param batch_size: int or str. Number of tasks per CPU core you want to use when converting file to SMILES. Default='auto'. + :param verbose: if True, print progress messages. Set False for programmatic or GUI use. + :param continue_on_error: if True, return all task results even when some conversions fail. """ + input_root = Path(in_root_path) if out_root_path is None: - out_root_path = in_root_path + output_root = input_root else: - # make out_root_path - if os.path.exists(out_root_path): - print('out_root_path "{}" has been detected!'.format(out_root_path)) + output_root = Path(out_root_path) + if output_root.exists(): + _report_progress('out_root_path "{}" has been detected!'.format(output_root), verbose) else: - print('out_root_path "{}" has not been detected, I will create it for you'.format(out_root_path)) - os.makedirs(out_root_path) - # end - in_file_names = os.listdir(in_root_path) - in_file_names = [i for i in in_file_names if i.endswith(in_format)] - in_file_path = [os.path.join(in_root_path, i) for i in in_file_names] - - task = [delayed(self.file_to_smi)(format=in_format, file_path=in_file_path[index]) for index in range(len(in_file_path))] + _report_progress( + 'out_root_path "{}" has not been detected, I will create it for you'.format(output_root), + verbose, + ) + output_root.mkdir(parents=True, exist_ok=True) + normalized_in_format = in_format.lower().lstrip('.') + in_file_path = sorted( + path for path in input_root.iterdir() + if path.is_file() and path.suffix.lower() == f'.{normalized_in_format}' + ) + + task = [ + delayed(self.file_to_smi)( + format=normalized_in_format, + file_path=str(in_file_path[index]), + raise_on_error=not continue_on_error, + ) + for index in range(len(in_file_path)) + ] smi_list = Parallel(n_jobs=n_jobs, batch_size=batch_size)(task) - with open(os.path.join(out_root_path, 'SMILES.txt'), 'w') as f: + smiles_output_path = output_root / 'SMILES.txt' + with smiles_output_path.open('w', encoding='utf-8') as f: for i in smi_list: f.write(i + '\n') return smi_list + def batch_file_to_smi_parallel(self, *args, **kwargs): + """ + Converting a batch of files into SMILES with joblib parallelism. + + This is the preferred name for new code. It calls batch_file_to_smi_mpi() + for backward compatibility with the original API. + """ + return self.batch_file_to_smi_mpi(*args, **kwargs) + def plot_supported_format(self): """ print all supported file formats. @@ -409,4 +556,4 @@ def plot_supported_format(self): # # c.batch_file_to_smi(in_format='mol2', in_root_path=os.path.join('gp_3x_test_mol', 'test_mol')) # x = c.batch_file_to_smi_mpi(in_format='mol', in_root_path='./test', n_jobs=4, batch_size='auto') # t2 = time.time() -# print(t2 - t1) \ No newline at end of file +# print(t2 - t1) diff --git a/groupy/gp_counter.py b/groupy/gp_counter.py index b1249c5..8929ca8 100644 --- a/groupy/gp_counter.py +++ b/groupy/gp_counter.py @@ -1,12 +1,25 @@ +import logging +from pathlib import Path + from tqdm import tqdm from rdkit import Chem import pandas as pd from joblib import Parallel, delayed +from groupy.chem import ensure_mol +from groupy.exceptions import InvalidSmilesError from groupy.gp_loader import Loader +from groupy.io import load_smiles_file __all__ = ["Counter", ] +logger = logging.getLogger(__name__) + + +def _report_progress(message, verbose): + logger.info(message) + if verbose: + print(message) # tool @@ -3226,19 +3239,19 @@ def __init__(self): ] self.f_order_group_function_order, self.s_order_group_function_order, self.t_order_group_function_order = self.loader.load_group_order() - def count_a_mol(self, mol, clear_mode=False, add_note=False, add_smiles=False): + def count_a_mol(self, mol, clear_mode=False, add_note=False, add_smiles=False, raise_on_error=False): """ Counting number of different groups of a molecule. :param mol: instance of rdkit.Chem.rdchem.Mol or SMILES str which will be converter to rdkit.Chem.rdchem.Mol automatically. :param clear_mode: bool. If set to True, The dictionary that stores the results will only retain groups with a count that is not zero. Default=False. :param add_note: bool. If set to True, a note(SMILES of current molecule) will be added to the dictionary that stores the results, i.e. {note: SMILES}. Default=False. :param add_smiles: bool. If set to True, The SMILES of current molecule will be added to the dictionary that stores the results, i.e. {smiles: SMILES}. Default=False. + :param raise_on_error: if True, re-raise expected input errors instead of returning zero counts. :return: Dict. A dictionary that stores the results. """ init_smi = mol try: - if isinstance(mol, str): - mol = Chem.MolFromSmiles(mol) + mol = ensure_mol(mol) self.result = self.init_result.copy() if add_note: # self.result['note'] = '' @@ -3252,43 +3265,55 @@ def count_a_mol(self, mol, clear_mode=False, add_note=False, add_smiles=False): # 清爽模式,不显示没有统计到的基团 self.result = {k: v for k, v in self.result.items() if v} return self.result - except: - print(f'Error! There is something wrong when counting {init_smi}, please check it.') + except (InvalidSmilesError, TypeError) as exc: + if raise_on_error: + raise + logger.warning("Failed to count groups for %r: %s", init_smi, exc) return self.init_result.copy() - def count_mols(self, smiles_file_path, count_result_file_path='count_result.csv', add_note=False, add_smiles=False): + def count_mols(self, smiles_file_path, count_result_file_path='count_result.csv', + add_note=False, add_smiles=False, verbose=True, continue_on_error=True): """ Counting number of different groups of a batch of molecules. :param smiles_file_path: str. Path of the file(.txt, .xlsx, .csv) in which saved SMILES of molecules. :param count_result_file_path: str. path of result file. Default='count_result.csv'. :param add_note: bool. If set to True, a note(SMILES of current molecule) will be added to the dictionary that stores the results, i.e. {note: SMILES}. Default=False. :param add_smiles: bool. If set to True, The SMILES of current molecule will be added to the dictionary that stores the results, i.e. {smiles: SMILES}. Default=False. + :param verbose: if True, print progress messages and progress bars. Set False for programmatic or GUI use. + :param continue_on_error: if True, keep legacy batch behavior and write zero rows for invalid SMILES. :return: pandas Dataframe. A dictionary that stores the results. """ - print('reading the input file...') - if smiles_file_path.endswith('.txt'): - smiles_iterator = list(open(smiles_file_path)) - elif smiles_file_path.endswith('.xlsx'): - smiles_iterator = pd.read_excel(smiles_file_path)['smiles'] - elif smiles_file_path.endswith('.csv'): - smiles_iterator = pd.read_csv(smiles_file_path)['smiles'] - else: + _report_progress('reading the input file...', verbose) + try: + smiles_iterator = load_smiles_file(smiles_file_path) + except ValueError as exc: raise NotImplementedError( - 'ERROR: The file type cannot be read, use the.txt/.xlsx/.csv file as the input file.') + 'ERROR: The file type cannot be read, use the.txt/.xlsx/.csv file as the input file.') from exc mol_number = len(smiles_iterator) - print('Done, totally detected {} molecules, start counting...'.format(mol_number)) + _report_progress('Done, totally detected {} molecules, start counting...'.format(mol_number), verbose) count_result_dict_list = [] - for i in tqdm(smiles_iterator): - count_result_dict_list.append(self.count_a_mol(i, add_note=add_note, add_smiles=add_smiles)) - print('Done!') - print('writing to csv...') + for i in tqdm(smiles_iterator, disable=not verbose): + count_result_dict_list.append( + self.count_a_mol( + i, + add_note=add_note, + add_smiles=add_smiles, + raise_on_error=not continue_on_error, + ) + ) + _report_progress('Done!', verbose) + _report_progress('writing to csv...', verbose) result = pd.DataFrame(count_result_dict_list) - result.to_csv(count_result_file_path, index_label='index') - print('Done!') + output_path = Path(count_result_file_path) + output_path.parent.mkdir(parents=True, exist_ok=True) + result.to_csv(output_path, index_label='index') + _report_progress('Done!', verbose) return result - def count_mols_mpi(self, smiles_file_path, count_result_file_path='count_result.csv', add_note=False, add_smiles=False, n_jobs=1, batch_size='auto'): + def count_mols_mpi(self, smiles_file_path, count_result_file_path='count_result.csv', + add_note=False, add_smiles=False, n_jobs=1, batch_size='auto', verbose=True, + continue_on_error=True): """ Counting number of different groups of a batch of molecules with MPI acceleration. :param smiles_file_path: str. Path of the file(.txt, .xlsx, .csv) in which saved SMILES of molecules. @@ -3297,30 +3322,47 @@ def count_mols_mpi(self, smiles_file_path, count_result_file_path='count_result. :param add_smiles: bool. If set to True, The SMILES of current molecule will be added to the dictionary that stores the results, i.e. {smiles: SMILES}. Default=False. :param n_jobs: int. number of CPU cores you want to use when counting groups. :param batch_size: int or str. Number of tasks per CPU core you want to use when counting groups. Default='auto'. + :param verbose: if True, print progress messages. Set False for programmatic or GUI use. + :param continue_on_error: if True, keep legacy batch behavior and write zero rows for invalid SMILES. :return: pandas Dataframe. A dictionary that stores the results. """ - print('reading the input file...') - if smiles_file_path.endswith('.txt'): - smiles_iterator = list(open(smiles_file_path)) - elif smiles_file_path.endswith('.xlsx'): - smiles_iterator = pd.read_excel(smiles_file_path)['smiles'] - elif smiles_file_path.endswith('.csv'): - smiles_iterator = pd.read_csv(smiles_file_path)['smiles'] - else: + _report_progress('reading the input file...', verbose) + try: + smiles_iterator = load_smiles_file(smiles_file_path) + except ValueError as exc: raise NotImplementedError( - 'ERROR: The file type cannot be read, use the.txt/.xlsx/.csv file as the input file.') + 'ERROR: The file type cannot be read, use the.txt/.xlsx/.csv file as the input file.') from exc mol_number = len(smiles_iterator) - print('Done, totally detected {} molecules, start counting...'.format(mol_number)) - task = [delayed(self.count_a_mol)(i, add_note=add_note, add_smiles=add_smiles) for i in smiles_iterator] + _report_progress('Done, totally detected {} molecules, start counting...'.format(mol_number), verbose) + task = [ + delayed(self.count_a_mol)( + i, + add_note=add_note, + add_smiles=add_smiles, + raise_on_error=not continue_on_error, + ) + for i in smiles_iterator + ] count_result_dict_list = Parallel(n_jobs=n_jobs, batch_size=batch_size)(task) - print('Done!') - print('writing to csv...') + _report_progress('Done!', verbose) + _report_progress('writing to csv...', verbose) result = pd.DataFrame(count_result_dict_list) - result.to_csv(count_result_file_path, index_label='index') - print('Done!') + output_path = Path(count_result_file_path) + output_path.parent.mkdir(parents=True, exist_ok=True) + result.to_csv(output_path, index_label='index') + _report_progress('Done!', verbose) return result + def count_mols_parallel(self, *args, **kwargs): + """ + Counting number of different groups of a batch of molecules with joblib parallelism. + + This is the preferred name for new code. It calls count_mols_mpi() + for backward compatibility with the original API. + """ + return self.count_mols_mpi(*args, **kwargs) + def get_group_fingerprint(self, mol): """ Getting group style fingerprint of a molecule. In fact, this method just call Counter.count_a_mol() and convert its result to a list. @@ -3415,4 +3457,4 @@ def count_3rd_order_groups(self, mol): # # # # # # t2 = time.time() # # # print(t2 - t1) -# # print(os.path.exists(r'C:\Users\tjulrc\Desktop\Groupy_test\groupy\gp_3x_internal_data\group_order.xlsx')) \ No newline at end of file +# # print(os.path.exists(r'C:\Users\tjulrc\Desktop\Groupy_test\groupy\gp_3x_internal_data\group_order.xlsx')) diff --git a/groupy/gp_generator.py b/groupy/gp_generator.py index 87d8fee..82966ea 100644 --- a/groupy/gp_generator.py +++ b/groupy/gp_generator.py @@ -1,12 +1,24 @@ +import logging from rdkit import Chem -import os +from pathlib import Path from tqdm import tqdm from joblib import Parallel, delayed import time import random +from groupy.chem import ensure_mol +from groupy.exceptions import ConversionError, InvalidSmilesError from groupy.gp_convertor import Convertor -from groupy.gp_tool import Tool +from groupy.io import load_smiles_file, write_text_lines + +logger = logging.getLogger(__name__) +GENERATION_EXCEPTIONS = (ConversionError, InvalidSmilesError, TypeError, OSError, RuntimeError, ValueError) + + +def _report_progress(message, verbose): + logger.info(message) + if verbose: + print(message) class Generator: @@ -25,8 +37,7 @@ def calculate_charge(self, smi): :param smi: SMILES str or instance of rdkit.Chem.rdchem.Mol :return: (int, int). (net charge, all charge) """ - if isinstance(smi, str): - smi = Chem.MolFromSmiles(smi) + smi = ensure_mol(smi) smi = Chem.AddHs(smi) # add H net_charge = 0 all_charge = 0 @@ -48,8 +59,7 @@ def calculate_multiplicity(self, smi): :param smi: SMILES str or instance of rdkit.Chem.rdchem.Mol :return: (int, int). (net charge, all charge) """ - if isinstance(smi, str): - smi = Chem.MolFromSmiles(smi) + smi = ensure_mol(smi) net_charge, all_charge = self.calculate_charge(smi) alpha_minus_beta = all_charge % 2 multiplicity = alpha_minus_beta + 1 @@ -86,25 +96,30 @@ def smi_to_gjf(self, smi, nproc='12', mem='12GB', chk_path=None, gjf_path=None, # default task if gaussian_keywords is None: gaussian_keywords = '#p opt freq b3lyp/6-31g*' - # default charge and multiplicity - if charge_and_multiplicity is None: - charge_and_multiplicity = f'{self.calculate_charge(smi)[0]} {self.calculate_multiplicity(smi)}' # default other tasks if other_tasks is None: other_tasks = [ '#p m062x/def2tzvp geom=check', '#p m062x/def2tzvp scrf=solvent=water geom=check', ] + temp_xyz_path = None try: + # default charge and multiplicity + if charge_and_multiplicity is None: + charge_and_multiplicity = f'{self.calculate_charge(smi)[0]} {self.calculate_multiplicity(smi)}' + # read smi c = Convertor() temp_xyz_path = f'temp_{str(time.time()) + str(random.randint(0,1000000000000000000))}.xyz' - c.smi_to_xyz(smi=smi, xyz_path=temp_xyz_path) + if not c.smi_to_xyz(smi=smi, xyz_path=temp_xyz_path): + raise ConversionError(f'Failed to convert {smi} to xyz coordinates.') # write gjf # 判断是否存在同名gjf - if os.path.exists(gjf_path): - os.remove(gjf_path) + gjf_output_path = Path(gjf_path) + gjf_output_path.parent.mkdir(parents=True, exist_ok=True) + if gjf_output_path.exists(): + gjf_output_path.unlink() self.write_gjf_link0_and_keyword(gjf_path=gjf_path, chk_path=chk_path, nproc=nproc, mem=mem, gaussian_keywords=gaussian_keywords, charge_and_multiplicity=charge_and_multiplicity, note=smi) @@ -118,18 +133,22 @@ def smi_to_gjf(self, smi, nproc='12', mem='12GB', chk_path=None, gjf_path=None, note=smi, old_chk_path=chk_path, add_link1=True) self.write_gjf_blank_line(gjf_path=gjf_path, blank_line_number=2) - # 删除临时xyz文件 - os.remove(temp_xyz_path) return True - except: - print(f'Error! There is something wrong when converting {smi} to gjf file, please check it.') + except GENERATION_EXCEPTIONS as exc: + logger.warning("Failed to generate gjf for %r: %s", smi, exc) return False + finally: + if temp_xyz_path is not None: + Path(temp_xyz_path).unlink(missing_ok=True) def batch_smi_to_gjf(self, smiles_file_path, gjf_root_path=None, nproc='12', mem='12GB', gaussian_keywords=None, charge_and_multiplicity=None, add_other_tasks=False, other_tasks: list = None, index_start=0, + fail_file_path=None, succeed_file_path=None, + verbose=True, + continue_on_error=True, ): """ Generating some gjf files based on a file in which saved some SMILES. @@ -144,57 +163,75 @@ def batch_smi_to_gjf(self, smiles_file_path, gjf_root_path=None, :param add_other_tasks: bool. Whether to add other job into you gjf file. Default=False :param other_tasks: list. Jobs you want to add into you gjf file. Default = ['#p m062x/def2tzvp geom=check', '#p m062x/def2tzvp scrf=solvent=water geom=check',]. Note that this parameter will only be used if add_other_tasks=True. + :param fail_file_path: optional path for writing SMILES strings that failed to generate gjf files. + :param succeed_file_path: optional path for writing SMILES strings that successfully generated gjf files. + :param verbose: if True, print progress messages and progress bars. Set False for programmatic or GUI use. + :param continue_on_error: if True, continue generating later SMILES after one generation fails. :return: None """ - smiles_iterator = Tool.load_smiles_iterator(smiles_file_path=smiles_file_path) + if gjf_root_path is None: + gjf_root_path = 'gjf' + _report_progress('reading input file...', verbose) + smiles_iterator = load_smiles_file(smiles_file_path) mol_number = len(smiles_iterator) zfill_number = len(str(mol_number)) + 5 - print('reading completed,A total of {} molecules detected, start calculating properties...'.format(mol_number)) + _report_progress( + 'reading completed,A total of {} molecules detected, start calculating properties...'.format(mol_number), + verbose, + ) - # make gjf_root_path - if os.path.exists(gjf_root_path): - print('gjf_root_path "{}" has been detected!'.format(gjf_root_path)) + gjf_root = Path(gjf_root_path) + if gjf_root.exists(): + _report_progress('gjf_root_path "{}" has been detected!'.format(gjf_root), verbose) else: - print('gjf_root_path "{}" has not been detected, I will create it for you'.format(gjf_root_path)) - os.makedirs(gjf_root_path) - # end + _report_progress( + 'gjf_root_path "{}" has not been detected, I will create it for you'.format(gjf_root), + verbose, + ) + gjf_root.mkdir(parents=True, exist_ok=True) succeed = [] fail = [] - for (index, smi) in tqdm(enumerate(smiles_iterator)): + for (index, smi) in tqdm(enumerate(smiles_iterator), disable=not verbose): smi = smi.strip() index += index_start chk_path = '{}.chk'.format(str(index).zfill(zfill_number)) - gjf_path = os.path.join(gjf_root_path, '{}.gjf'.format(str(index).zfill(zfill_number))) + gjf_path = gjf_root / '{}.gjf'.format(str(index).zfill(zfill_number)) generate_success_flag = self.smi_to_gjf(smi=smi, nproc=nproc, mem=mem, - chk_path=chk_path, gjf_path=gjf_path, + chk_path=chk_path, gjf_path=str(gjf_path), gaussian_keywords=gaussian_keywords, charge_and_multiplicity=charge_and_multiplicity, add_other_tasks=add_other_tasks, other_tasks=other_tasks, ) if not generate_success_flag: fail.append(smi) + if not continue_on_error: + if fail_file_path is not None: + write_text_lines(fail, fail_file_path) + if succeed_file_path is not None: + write_text_lines(succeed, succeed_file_path) + raise ConversionError(f'Failed to generate gjf for {smi!r}.') else: succeed.append(smi) - with open('gjf_fail.txt', 'w') as f: - for i in fail: - f.write(i + '\n') - with open('gjf_succeed.txt', 'w') as f: - for i in succeed: - f.write(i + '\n') + if fail_file_path is not None: + write_text_lines(fail, fail_file_path) + if succeed_file_path is not None: + write_text_lines(succeed, succeed_file_path) if len(fail) == 0: - print('done! all .gjf files has been saved in {}'.format(gjf_root_path)) + _report_progress('done! all .gjf files has been saved in {}'.format(gjf_root), verbose) else: - print('Warning! The following SMILES fail to generate .gjf, please check...sorry(OTZ)') - print(fail) + logger.warning("Failed to generate gjf files for SMILES: %s", fail) + if verbose: + print('Warning! The following SMILES fail to generate .gjf, please check...sorry(OTZ)') + print(fail) return None def batch_smi_to_gjf_mpi(self, smiles_file_path, gjf_root_path=None, nproc='12', mem='12GB', gaussian_keywords=None, charge_and_multiplicity=None, add_other_tasks=False, other_tasks: list = None, - n_jobs=1, batch_size='auto' + n_jobs=1, batch_size='auto', verbose=True, continue_on_error=True ): """ Generating some gjf files based on a file in which saved some SMILES with MPI acceleration. @@ -211,38 +248,60 @@ def batch_smi_to_gjf_mpi(self, smiles_file_path, gjf_root_path=None, '#p m062x/def2tzvp scrf=solvent=water geom=check',]. Note that this parameter will only be used if add_other_tasks=True. :param n_jobs: int. number of CPU cores you want to use when generating gjf file. :param batch_size: int or str. Number of tasks per CPU core you want to use when generating gjf file. Default='auto'. + :param verbose: if True, print progress messages. Set False for programmatic or GUI use. + :param continue_on_error: if True, return all task results even when some generations fail. :return: None """ - smiles_iterator = Tool.load_smiles_iterator(smiles_file_path=smiles_file_path) + if gjf_root_path is None: + gjf_root_path = 'gjf' + _report_progress('reading input file...', verbose) + smiles_iterator = load_smiles_file(smiles_file_path) mol_number = len(smiles_iterator) zfill_number = len(str(mol_number)) + 5 - print('reading completed,A total of {} molecules detected, start calculating properties...'.format(mol_number)) + _report_progress( + 'reading completed,A total of {} molecules detected, start calculating properties...'.format(mol_number), + verbose, + ) - # make gjf_root_path - if os.path.exists(gjf_root_path): - print('gjf_root_path "{}" has been detected!'.format(gjf_root_path)) + gjf_root = Path(gjf_root_path) + if gjf_root.exists(): + _report_progress('gjf_root_path "{}" has been detected!'.format(gjf_root), verbose) else: - print('gjf_root_path "{}" has not been detected, I will create it for you'.format(gjf_root_path)) - os.makedirs(gjf_root_path) + _report_progress( + 'gjf_root_path "{}" has not been detected, I will create it for you'.format(gjf_root), + verbose, + ) + gjf_root.mkdir(parents=True, exist_ok=True) # task task = [delayed(self.smi_to_gjf)(smi=smi, nproc=nproc, mem=mem, chk_path='{}.chk'.format(str(index).zfill(zfill_number)), - gjf_path=os.path.join(gjf_root_path, '{}.gjf'.format(str(index).zfill(zfill_number))), + gjf_path=str(gjf_root / '{}.gjf'.format(str(index).zfill(zfill_number))), gaussian_keywords=gaussian_keywords, charge_and_multiplicity=charge_and_multiplicity, add_other_tasks=add_other_tasks, other_tasks=other_tasks,) for index, smi in enumerate(smiles_iterator)] result = Parallel(n_jobs=n_jobs, batch_size=batch_size)(task) + if not continue_on_error and not all(result): + raise ConversionError('Failed to generate one or more gjf files.') return result + def batch_smi_to_gjf_parallel(self, *args, **kwargs): + """ + Generating Gaussian job files with joblib parallelism. + + This is the preferred name for new code. It calls batch_smi_to_gjf_mpi() + for backward compatibility with the original API. + """ + return self.batch_smi_to_gjf_mpi(*args, **kwargs) + @staticmethod def write_gjf_link0_and_keyword(gjf_path, chk_path, nproc, mem, gaussian_keywords, charge_and_multiplicity, note, old_chk_path=None, add_link1=False): """ Only used in Generator.smi_to_gjf() """ - with open(gjf_path, 'a') as gjf: + with Path(gjf_path).open('a', encoding='utf-8') as gjf: if add_link1: gjf.write('--link1--' + '\n') gjf.write(f'%nproc={nproc}' + '\n') @@ -263,9 +322,10 @@ def write_gjf_coord(gjf_path, xyz_path): """ Only used in Generator.smi_to_gjf() """ - xyz = open(xyz_path) - with open(gjf_path, 'a') as gjf: - for i in xyz.readlines()[2:]: + with Path(xyz_path).open(encoding='utf-8') as xyz: + coord_lines = xyz.readlines()[2:] + with Path(gjf_path).open('a', encoding='utf-8') as gjf: + for i in coord_lines: gjf.write(i) gjf.write('\n\n') return None @@ -275,7 +335,7 @@ def write_gjf_blank_line(gjf_path, blank_line_number=1): """ Only used in Generator.smi_to_gjf() """ - with open(gjf_path, 'a') as gjf: + with Path(gjf_path).open('a', encoding='utf-8') as gjf: gjf.write('\n' * blank_line_number) return None @@ -293,4 +353,3 @@ def write_gjf_blank_line(gjf_path, blank_line_number=1): # # g.batch_smi_to_gjf_mpi(smiles_file_path='gp_3x_test_mol/3018_with_error_smiles.txt', gjf_root_path='./test_gjf', # # add_other_tasks=True, # # n_jobs=8, batch_size='auto') - diff --git a/groupy/gp_loader.py b/groupy/gp_loader.py index e37081e..321ed94 100644 --- a/groupy/gp_loader.py +++ b/groupy/gp_loader.py @@ -1,5 +1,63 @@ +from copy import deepcopy +from functools import lru_cache +from importlib.resources import as_file, files + import pandas as pd -import os + + +def _data_paths(): + data_root = files('groupy') + return { + 'parameters': [data_root / 'group_contribution_parameters.xlsx'], + 'group_order': [data_root / 'group_order.xlsx'], + } + + +@lru_cache(maxsize=None) +def _load_parameter_tables(parameter_type): + parameters_path = _data_paths()['parameters'] + for path in parameters_path: + if not path.is_file(): + continue + first_order = Loader._read_excel( + path, + sheet_name=f'{parameter_type}_first_order', + index_col='index', + ).T.to_dict() + second_order = Loader._read_excel( + path, + sheet_name=f'{parameter_type}_second_order', + index_col='index', + ).T.to_dict() + third_order = Loader._read_excel( + path, + sheet_name=f'{parameter_type}_third_order', + index_col='index', + ).T.to_dict() + universal_constants = Loader._read_excel( + path, + sheet_name=f'{parameter_type}_constants', + index_col='index', + ).T.to_dict() + return first_order, second_order, third_order, universal_constants + raise FileNotFoundError(f'Can not find group_contribution_parameters.xlsx in {parameters_path}') + + +@lru_cache(maxsize=None) +def _load_group_order_tables(): + group_order_path = _data_paths()['group_order'] + for path in group_order_path: + if not path.is_file(): + continue + f_order_group_function_order = ( + Loader._read_excel(path, sheet_name='f')[ + 'index'] - 1).tolist() # 减1是为了基团序号和列表索引对上,因为python列表里的索引是从0开始的 + s_order_group_function_order = ( + Loader._read_excel(path, sheet_name='s')['index'] - 1).tolist() # 减1是为了基团序号和列表索引对上 + t_order_group_function_order = ( + Loader._read_excel(path, sheet_name='t')['index'] - 1).tolist() # 减1是为了基团序号和列表索引对上 + return f_order_group_function_order, s_order_group_function_order, t_order_group_function_order + raise FileNotFoundError(f'Can not find group_order.xlsx in {group_order_path}') class Loader: @@ -7,66 +65,40 @@ class Loader: A class for loading internal date of Groupy. Usually, users do not need to utilize this Python class. """ def __init__(self, ): - basepath = os.path.abspath(__file__) - folder_path1 = os.path.dirname(basepath) - folder_path2 = os.path.join(folder_path1, '..', '..', '..', 'groupy_internal_data') - folder_path3 = os.path.join(folder_path1, '..', '..', '..', '..', 'groupy_internal_data') - - parameters_filename = os.path.join('group_contribution_parameters.xlsx') - group_order_filename = os.path.join('group_order.xlsx') - self.parameters_path = [ - os.path.join(folder_path1, parameters_filename), - os.path.join(folder_path2, parameters_filename), - os.path.join(folder_path3, parameters_filename), - ] - self.group_order_path = [ - os.path.join(folder_path1, group_order_filename), - os.path.join(folder_path2, group_order_filename), - os.path.join(folder_path3, group_order_filename) - ] + paths = _data_paths() + self.parameters_path = paths['parameters'] + self.group_order_path = paths['group_order'] + + @staticmethod + def _read_excel(data_file, **kwargs): + with as_file(data_file) as path: + return pd.read_excel(path, **kwargs) + + @staticmethod + def clear_cache(): + _load_parameter_tables.cache_clear() + _load_group_order_tables.cache_clear() def load_parameters(self, parameter_type='simultaneous', split=False): """ Loading parameters of group contribution method for groupy.gp_calculator.Calculator. """ assert parameter_type in ['simultaneous', 'step_wise'], '请确保参数类型为simultaneous或step_wise!' - for path in self.parameters_path: - try: - step_wise_first_order = pd.read_excel(path, sheet_name='{}_first_order'.format(parameter_type), index_col='index').T.to_dict() - step_wise_second_order = pd.read_excel(path, sheet_name='{}_second_order'.format(parameter_type), index_col='index').T.to_dict() - step_wise_third_order = pd.read_excel(path, sheet_name='{}_third_order'.format(parameter_type), index_col='index').T.to_dict() - step_wise_universal_constants = pd.read_excel(path, sheet_name='{}_constants'.format(parameter_type), index_col='index').T.to_dict() - break - except: - pass - else: - raise FileNotFoundError(f'Can not find group_contribution_parameters.xlsx in {self.parameters_path}') - + parameter_tables = _load_parameter_tables(parameter_type) if split: - return step_wise_first_order, step_wise_second_order, step_wise_third_order, step_wise_universal_constants - else: - return {**step_wise_first_order, **step_wise_second_order, **step_wise_third_order, **step_wise_universal_constants} + return deepcopy(parameter_tables) + + parameters = {} + for table in parameter_tables: + parameters.update(deepcopy(table)) + return parameters def load_group_order(self): """ Loading order of group for groupy.gp_counter.Counter """ - for path in self.group_order_path: - try: - f_order_group_function_order = ( - pd.read_excel(path, sheet_name='f')[ - 'index'] - 1).tolist() # 减1是为了基团序号和列表索引对上,因为python列表里的索引是从0开始的 - s_order_group_function_order = ( - pd.read_excel(path, sheet_name='s')['index'] - 1).tolist() # 减1是为了基团序号和列表索引对上 - t_order_group_function_order = ( - pd.read_excel(path, sheet_name='t')['index'] - 1).tolist() # 减1是为了基团序号和列表索引对上 - break - except: - pass - else: - raise FileNotFoundError(f'Can not find group_order.xlsx in {self.parameters_path}') - return f_order_group_function_order, s_order_group_function_order, t_order_group_function_order + return deepcopy(_load_group_order_tables()) # if __name__ == '__main__': @@ -78,4 +110,3 @@ def load_group_order(self): # # for i in d: # # print(i) # # print(d[i]) - diff --git a/groupy/gp_tool.py b/groupy/gp_tool.py index de412da..9e88187 100644 --- a/groupy/gp_tool.py +++ b/groupy/gp_tool.py @@ -1,5 +1,6 @@ import pandas as pd -from PIL.ImageSequence import Iterator + +from groupy.io import load_smiles_file class Tool: @@ -20,16 +21,10 @@ def load_smiles_iterator(smiles_file_path): :return: Iterator. """ print('reading input file...') - if smiles_file_path.endswith('.txt'): - smiles_iterator = list(open(smiles_file_path)) - elif smiles_file_path.endswith('.xlsx'): - smiles_iterator = pd.read_excel(smiles_file_path)['smiles'] - elif smiles_file_path.endswith('.csv'): - smiles_iterator = pd.read_csv(smiles_file_path)['smiles'] - else: - raise NotImplemented('无法识别的文件类型,请以.txt/.xlsx/.csv类型的文件作为输入。') - smiles_iterator = [i.strip() for i in smiles_iterator] - return smiles_iterator + try: + return load_smiles_file(smiles_file_path) + except ValueError as exc: + raise NotImplementedError('无法识别的文件类型,请以.txt/.xlsx/.csv类型的文件作为输入。') from exc def export_a_dict(result_dict, export_path='result.csv'): diff --git a/groupy/gp_viewer.py b/groupy/gp_viewer.py index 680dd6b..ff134f0 100644 --- a/groupy/gp_viewer.py +++ b/groupy/gp_viewer.py @@ -1,11 +1,42 @@ -import ase -from ase.visualize import view -from ase.io import read -import os +import logging +from pathlib import Path from pprint import pprint +import tempfile from groupy.gp_convertor import Convertor +logger = logging.getLogger(__name__) +VIEWER_READ_EXCEPTIONS = (OSError, RuntimeError, ValueError) + +_ASE_INSTALL_HINT = ( + "ASE is required for molecular visualization. " + "Install it with `pip install ase` or `conda install -c conda-forge ase`." +) + + +def _load_ase_read(): + try: + from ase.io import read + except ImportError as exc: + raise ImportError(_ASE_INSTALL_HINT) from exc + return read + + +def _load_ase_view(): + try: + from ase.visualize import view + except ImportError as exc: + raise ImportError(_ASE_INSTALL_HINT) from exc + return view + + +def _load_ase_formats(): + try: + from ase.io.formats import ioformats + except ImportError as exc: + raise ImportError(_ASE_INSTALL_HINT) from exc + return ioformats + class Viewer: """ @@ -23,20 +54,37 @@ def view_mol(self, mol, mol_type='smi'): :param mol_type: str. :return: None. """ + read = _load_ase_read() + view = _load_ase_view() if isinstance(mol, str): + temp_xyz_path = None if mol_type in ['smi', 'smiles', 'SMILES']: convertor = Convertor() - convertor.smi_to_xyz(smi=mol, xyz_path='temp.xyz') - mol = read(filename='temp.xyz', format='xyz') - os.remove('temp.xyz') + with tempfile.NamedTemporaryFile(suffix='.xyz', delete=False) as temp_xyz: + temp_xyz_path = Path(temp_xyz.name) + try: + convertor.smi_to_xyz(smi=mol, xyz_path=str(temp_xyz_path)) + mol = read(filename=str(temp_xyz_path), format='xyz') + finally: + temp_xyz_path.unlink(missing_ok=True) else: try: mol = read(filename=mol, format=mol_type) - except: + except VIEWER_READ_EXCEPTIONS as exc: + logger.info("ASE could not read %s as %s directly: %s", mol, mol_type, exc) convertor = Convertor() - convertor.convert_file_type(in_format=mol_type, in_path=mol, out_format='xyz', out_path='temp.xyz') - mol = read(filename='temp.xyz', format='xyz') - os.remove('temp.xyz') + with tempfile.NamedTemporaryFile(suffix='.xyz', delete=False) as temp_xyz: + temp_xyz_path = Path(temp_xyz.name) + try: + convertor.convert_file_type( + in_format=mol_type, + in_path=mol, + out_format='xyz', + out_path=str(temp_xyz_path), + ) + mol = read(filename=str(temp_xyz_path), format='xyz') + finally: + temp_xyz_path.unlink(missing_ok=True) view(mol) return None @@ -45,7 +93,7 @@ def plot_supported_format(self): print all supported file formats. :return: None. """ - ase_format = ase.io.formats.ioformats + ase_format = _load_ase_formats() openbabel_format = {'abinit': 'ABINIT Output Format', 'acesout': 'ACES output format', 'acr': 'ACR format', 'adfband': 'ADF Band output format', 'adfdftb': 'ADF DFTB output format', 'adfout': 'ADF output format', 'alc': 'Alchemy format', 'aoforce': 'Turbomole AOFORCE output format', @@ -109,4 +157,4 @@ def plot_supported_format(self): # # viewer = Viewer() # viewer.view_mol('gp_3x_test_mol/test_xyz/000000.mol2', mol_type='mol2') -# viewer.plot_supported_format() \ No newline at end of file +# viewer.plot_supported_format() diff --git a/groupy/groupy_main.py b/groupy/groupy_main.py index ca993db..c389434 100644 --- a/groupy/groupy_main.py +++ b/groupy/groupy_main.py @@ -124,7 +124,7 @@ def sub_function_minus_2_of_main_function_5(): batch_size = input('input batch size for task decomposition. e.g. 20, you can also enter "auto" \n') try: batch_size = int(batch_size) - except: + except ValueError: pass convertor = Convertor() convertor.batch_smi_to_xyz_mpi(smiles_file_path=smiles_file_path, xyz_root_path=xyz_root_path, @@ -189,7 +189,7 @@ def sub_function_minus_4_of_main_function_5(): batch_size = input('input batch size for task decomposition. e.g. 20, you can also enter "auto" \n') try: batch_size = int(batch_size) - except: + except ValueError: pass convertor = Convertor() convertor.batch_convert_file_type_mpi(in_format=in_format, in_root_path=in_root_path, @@ -249,7 +249,7 @@ def sub_function_minus_6_of_main_function_5(): batch_size = input('input batch size for task decomposition. e.g. 20, you can also enter "auto" \n') try: batch_size = int(batch_size) - except: + except ValueError: pass convertor = Convertor() smi_list = convertor.batch_file_to_smi_mpi(in_format=in_format, in_root_path=in_root_path, @@ -429,7 +429,7 @@ def sub_function_minus_8_of_main_function_5(): batch_size = input('input batch size for task decomposition. e.g. 20, you can also enter "auto" \n') try: batch_size = int(batch_size) - except: + except ValueError: pass generator = Generator() @@ -531,7 +531,7 @@ def main_function_minus_3(): batch_size = input('input batch size for task decomposition. e.g. 20, you can also enter "auto" \n') try: batch_size = int(batch_size) - except: + except ValueError: pass calculator = Calculator() calculator.calculate_mols_mpi(smiles_file_path=smiles_file_path, @@ -569,7 +569,7 @@ def main_function_minus_4(): batch_size = input('input batch size for task decomposition. e.g. 20, you can also enter "auto" \n') try: batch_size = int(batch_size) - except: + except ValueError: pass counter = Counter() counter.count_mols_mpi(smiles_file_path=smiles_file_path, diff --git a/groupy/gui/__init__.py b/groupy/gui/__init__.py new file mode 100644 index 0000000..86cd8be --- /dev/null +++ b/groupy/gui/__init__.py @@ -0,0 +1,5 @@ +"""Desktop GUI entry points for Groupy.""" + +from groupy.gui.app import INSTALL_HINT, is_pyside6_available, main + +__all__ = ["INSTALL_HINT", "is_pyside6_available", "main"] diff --git a/groupy/gui/__main__.py b/groupy/gui/__main__.py new file mode 100644 index 0000000..4b4dcb8 --- /dev/null +++ b/groupy/gui/__main__.py @@ -0,0 +1,6 @@ +"""Run the Groupy desktop GUI with `python -m groupy.gui`.""" + +from groupy.gui import main + + +raise SystemExit(main()) diff --git a/groupy/gui/app.py b/groupy/gui/app.py new file mode 100644 index 0000000..13ff646 --- /dev/null +++ b/groupy/gui/app.py @@ -0,0 +1,467 @@ +"""Minimal PySide6 desktop application for Groupy.""" + +from __future__ import annotations + +import argparse +from io import BytesIO +import importlib.util +import sys +from pathlib import Path +from typing import Any + +from groupy.chem import ensure_mol +from groupy.api import calculate_smiles, count_smiles +from groupy.io import load_smiles_file, write_records_csv + +INSTALL_HINT = ( + "PySide6 is required for the Groupy desktop GUI. " + "Install it with `python -m pip install -e \".[gui]\"`." +) + + +def is_pyside6_available() -> bool: + """Return whether PySide6 can be imported in the current environment.""" + return importlib.util.find_spec("PySide6") is not None + + +def calculate_records( + smiles_values: list[str], + *, + parameter_type: str = "step_wise", + check_hydrocarbon: bool = True, +) -> list[dict[str, Any]]: + """Calculate properties for a list of SMILES strings.""" + return [ + calculate_smiles( + smiles, + parameter_type=parameter_type, + check_hydrocarbon=check_hydrocarbon, + ) + for smiles in smiles_values + ] + + +def count_records( + smiles_values: list[str], + *, + include_zero: bool = False, + include_smiles: bool = True, +) -> list[dict[str, Any]]: + """Count groups for a list of SMILES strings.""" + return [ + count_smiles( + smiles, + include_zero=include_zero, + include_smiles=include_smiles, + ) + for smiles in smiles_values + ] + + +def load_smiles_text(path: str | Path) -> str: + """Load a SMILES file and return text suitable for the GUI editor.""" + return "\n".join(load_smiles_file(path)) + + +def render_smiles_png(smiles: str, *, width: int = 360, height: int = 280) -> bytes: + """Render one SMILES string to PNG bytes using RDKit.""" + from rdkit.Chem import Draw + + molecule = ensure_mol(smiles) + image = Draw.MolToImage(molecule, size=(width, height)) + buffer = BytesIO() + image.save(buffer, format="PNG") + return buffer.getvalue() + + +def main(argv: list[str] | None = None) -> int: + """Run the desktop GUI.""" + parser = argparse.ArgumentParser(prog="Groupy-GUI", description="Launch the Groupy desktop GUI.") + parser.add_argument( + "--check", + action="store_true", + help="Check whether GUI dependencies are installed without launching a window.", + ) + args = parser.parse_args(argv) + + if not is_pyside6_available(): + print(INSTALL_HINT, file=sys.stderr) + return 1 + + if args.check: + print("PySide6 is available.") + return 0 + + from PySide6 import QtCore, QtGui, QtWidgets + + app = QtWidgets.QApplication.instance() or QtWidgets.QApplication(sys.argv[:1]) + window = _create_main_window(QtCore, QtGui, QtWidgets) + window.show() + return app.exec() + + +def _create_main_window(QtCore: Any, QtGui: Any, QtWidgets: Any): + class BatchWorker(QtCore.QObject): + finished = QtCore.Signal(object, str) + failed = QtCore.Signal(str) + + def __init__(self, smiles_values, operation, message): + super().__init__() + self._smiles_values = smiles_values + self._operation = operation + self._message = message + + @QtCore.Slot() + def run(self): + try: + records = self._operation(self._smiles_values) + except Exception as exc: + self.failed.emit(str(exc)) + return + self.finished.emit(records, self._message) + + class GroupyMainWindow(QtWidgets.QMainWindow): + def __init__(self): + super().__init__() + self._records: list[dict[str, Any]] = [] + self._record_smiles_values: list[str] = [] + self._pending_smiles_values: list[str] = [] + self._structure_pixmap = None + self._worker = None + self._worker_thread = None + self.setWindowTitle("Groupy") + self.resize(1180, 720) + + central = QtWidgets.QWidget() + self.setCentralWidget(central) + root_layout = QtWidgets.QVBoxLayout(central) + root_layout.setContentsMargins(14, 14, 14, 14) + root_layout.setSpacing(10) + + content_splitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal) + root_layout.addWidget(content_splitter, stretch=1) + + work_panel = QtWidgets.QWidget() + work_layout = QtWidgets.QVBoxLayout(work_panel) + work_layout.setContentsMargins(0, 0, 10, 0) + work_layout.setSpacing(10) + content_splitter.addWidget(work_panel) + + preview_panel = QtWidgets.QWidget() + preview_layout = QtWidgets.QVBoxLayout(preview_panel) + preview_layout.setContentsMargins(10, 0, 0, 0) + preview_layout.setSpacing(8) + content_splitter.addWidget(preview_panel) + + input_label = QtWidgets.QLabel("SMILES") + work_layout.addWidget(input_label) + + self.smiles_input = QtWidgets.QPlainTextEdit() + self.smiles_input.setPlaceholderText("Enter one SMILES per line") + self.smiles_input.setPlainText("C1CCCC1") + self.smiles_input.setMinimumHeight(96) + self.smiles_input.textChanged.connect(self._preview_first_input_smiles) + work_layout.addWidget(self.smiles_input) + + action_layout = QtWidgets.QHBoxLayout() + work_layout.addLayout(action_layout) + + self.import_button = QtWidgets.QPushButton("Import File") + self.import_button.clicked.connect(self._import_smiles_file) + action_layout.addWidget(self.import_button) + + self.calculate_button = QtWidgets.QPushButton("Calculate Properties") + self.calculate_button.clicked.connect(self._calculate_properties) + action_layout.addWidget(self.calculate_button) + + self.count_button = QtWidgets.QPushButton("Count Groups") + self.count_button.clicked.connect(self._count_groups) + action_layout.addWidget(self.count_button) + + self.export_button = QtWidgets.QPushButton("Export CSV") + self.export_button.setEnabled(False) + self.export_button.clicked.connect(self._export_csv) + action_layout.addWidget(self.export_button) + + action_layout.addStretch(1) + + options_layout = QtWidgets.QHBoxLayout() + work_layout.addLayout(options_layout) + + options_layout.addWidget(QtWidgets.QLabel("Parameters")) + self.parameter_type_combo = QtWidgets.QComboBox() + self.parameter_type_combo.addItem("Stepwise", "step_wise") + self.parameter_type_combo.addItem("Simultaneous", "simultaneous") + options_layout.addWidget(self.parameter_type_combo) + + self.check_hydrocarbon_box = QtWidgets.QCheckBox("Hydrocarbon check") + self.check_hydrocarbon_box.setChecked(True) + options_layout.addWidget(self.check_hydrocarbon_box) + + self.include_zero_box = QtWidgets.QCheckBox("Show zero groups") + options_layout.addWidget(self.include_zero_box) + + self.include_smiles_box = QtWidgets.QCheckBox("Include SMILES") + self.include_smiles_box.setChecked(True) + options_layout.addWidget(self.include_smiles_box) + + options_layout.addStretch(1) + + self.result_table = QtWidgets.QTableWidget() + self.result_table.setAlternatingRowColors(True) + self.result_table.setSortingEnabled(True) + self.result_table.horizontalHeader().setStretchLastSection(True) + self.result_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + self.result_table.itemSelectionChanged.connect(self._preview_selected_record) + work_layout.addWidget(self.result_table, stretch=1) + + preview_label = QtWidgets.QLabel("Structure") + preview_layout.addWidget(preview_label) + + self.structure_view = QtWidgets.QLabel() + self.structure_view.setAlignment(QtCore.Qt.AlignCenter) + self.structure_view.setMinimumSize(320, 260) + self.structure_view.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.structure_view.setText("No structure") + preview_layout.addWidget(self.structure_view, stretch=1) + + preview_layout.addStretch(1) + content_splitter.setStretchFactor(0, 4) + content_splitter.setStretchFactor(1, 2) + + self._busy_controls = [ + self.import_button, + self.calculate_button, + self.count_button, + self.smiles_input, + self.parameter_type_combo, + self.check_hydrocarbon_box, + self.include_zero_box, + self.include_smiles_box, + ] + self._preview_first_input_smiles() + + self.statusBar().showMessage("Ready") + + def resizeEvent(self, event): + super().resizeEvent(event) + self._refresh_structure_pixmap() + + def _smiles_values(self) -> list[str]: + values = [line.strip() for line in self.smiles_input.toPlainText().splitlines() if line.strip()] + if not values: + raise ValueError("Enter at least one SMILES string.") + return values + + def _calculate_properties(self) -> None: + parameter_type = self.parameter_type_combo.currentData() + check_hydrocarbon = self.check_hydrocarbon_box.isChecked() + self._start_worker( + lambda smiles_values: calculate_records( + smiles_values, + parameter_type=parameter_type, + check_hydrocarbon=check_hydrocarbon, + ), + "Calculated properties", + "Calculating properties...", + ) + + def _count_groups(self) -> None: + include_zero = self.include_zero_box.isChecked() + include_smiles = self.include_smiles_box.isChecked() + self._start_worker( + lambda smiles_values: count_records( + smiles_values, + include_zero=include_zero, + include_smiles=include_smiles, + ), + "Counted groups", + "Counting groups...", + ) + + def _start_worker(self, operation, done_message: str, progress_message: str) -> None: + if self._worker_thread is not None: + return + try: + smiles_values = self._smiles_values() + except Exception as exc: + self._show_error(str(exc)) + return + + self._pending_smiles_values = smiles_values + self._set_busy(True, progress_message) + thread = QtCore.QThread(self) + worker = BatchWorker(smiles_values, operation, done_message) + worker.moveToThread(thread) + + thread.started.connect(worker.run) + worker.finished.connect(self._finish_worker) + worker.failed.connect(self._fail_worker) + worker.finished.connect(worker.deleteLater) + worker.failed.connect(worker.deleteLater) + worker.finished.connect(thread.quit) + worker.failed.connect(thread.quit) + thread.finished.connect(thread.deleteLater) + thread.finished.connect(self._clear_worker) + + self._worker = worker + self._worker_thread = thread + thread.start() + + def _finish_worker(self, records: list[dict[str, Any]], message: str) -> None: + self._set_busy(False) + self._show_records(records, message) + + def _fail_worker(self, message: str) -> None: + self._set_busy(False) + self._show_error(message) + + def _clear_worker(self) -> None: + self._worker = None + self._worker_thread = None + + def _set_busy(self, busy: bool, message: str | None = None) -> None: + for control in self._busy_controls: + control.setEnabled(not busy) + self.export_button.setEnabled((not busy) and bool(self._records)) + if busy: + QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) + self.statusBar().showMessage(message or "Working...") + else: + QtWidgets.QApplication.restoreOverrideCursor() + + def _show_records(self, records: list[dict[str, Any]], message: str) -> None: + self._records = records + self._record_smiles_values = list(self._pending_smiles_values) + columns = _record_columns(records) + self.result_table.setSortingEnabled(False) + self.result_table.clear() + self.result_table.setRowCount(len(records)) + self.result_table.setColumnCount(len(columns)) + self.result_table.setHorizontalHeaderLabels(columns) + + for row_index, record in enumerate(records): + for column_index, column in enumerate(columns): + value = record.get(column, "") + item = QtWidgets.QTableWidgetItem("" if value is None else str(value)) + item.setFlags(item.flags() & ~QtCore.Qt.ItemIsEditable) + self.result_table.setItem(row_index, column_index, item) + + self.result_table.resizeColumnsToContents() + self.result_table.setSortingEnabled(True) + self.export_button.setEnabled(bool(records)) + if records: + self.result_table.selectRow(0) + self._preview_record(0) + else: + self._preview_first_input_smiles() + self.statusBar().showMessage(f"{message}: {len(records)} molecule(s)") + + def _import_smiles_file(self) -> None: + path, _ = QtWidgets.QFileDialog.getOpenFileName( + self, + "Import SMILES", + str(Path.cwd()), + "SMILES files (*.txt *.csv *.xlsx);;All files (*.*)", + ) + if not path: + return + try: + text = load_smiles_text(path) + except Exception as exc: + self._show_error(str(exc)) + return + self.smiles_input.setPlainText(text) + count = len([line for line in text.splitlines() if line.strip()]) + self.statusBar().showMessage(f"Imported {count} molecule(s) from {path}") + + def _export_csv(self) -> None: + if not self._records: + self._show_error("No results to export.") + return + path, _ = QtWidgets.QFileDialog.getSaveFileName( + self, + "Export CSV", + str(Path.cwd() / "groupy_results.csv"), + "CSV files (*.csv)", + ) + if not path: + return + write_records_csv(self._records, path) + self.statusBar().showMessage(f"Exported {path}") + + def _show_error(self, message: str) -> None: + QtWidgets.QMessageBox.warning(self, "Groupy", message) + self.statusBar().showMessage(message) + + def _preview_first_input_smiles(self) -> None: + values = [line.strip() for line in self.smiles_input.toPlainText().splitlines() if line.strip()] + if not values: + self._clear_structure("No structure") + return + self._show_structure(values[0]) + + def _preview_selected_record(self) -> None: + selected_rows = self.result_table.selectionModel().selectedRows() + if not selected_rows: + return + self._preview_record(selected_rows[0].row()) + + def _preview_record(self, row: int) -> None: + if row < 0 or row >= len(self._records): + return + smiles = _record_smiles(self._records[row]) + if smiles is None and row < len(self._record_smiles_values): + smiles = self._record_smiles_values[row] + if not smiles: + self._clear_structure("No SMILES") + return + self._show_structure(smiles) + + def _show_structure(self, smiles: str) -> None: + try: + image_bytes = render_smiles_png(smiles) + except Exception: + self._clear_structure("Invalid SMILES") + return + pixmap = QtGui.QPixmap() + pixmap.loadFromData(image_bytes, "PNG") + self._structure_pixmap = pixmap + self._refresh_structure_pixmap() + + def _refresh_structure_pixmap(self) -> None: + if self._structure_pixmap is None: + return + scaled = self._structure_pixmap.scaled( + self.structure_view.size(), + QtCore.Qt.KeepAspectRatio, + QtCore.Qt.SmoothTransformation, + ) + self.structure_view.setPixmap(scaled) + + def _clear_structure(self, message: str) -> None: + self._structure_pixmap = None + self.structure_view.clear() + self.structure_view.setText(message) + + return GroupyMainWindow() + + +def _record_columns(records: list[dict[str, Any]]) -> list[str]: + columns: list[str] = [] + for preferred in ("smiles", "note", "error"): + if any(preferred in record for record in records): + columns.append(preferred) + for record in records: + for key in record: + if key not in columns: + columns.append(key) + return columns + + +def _record_smiles(record: dict[str, Any]) -> str | None: + for key in ("smiles", "note"): + value = record.get(key) + if isinstance(value, str) and value: + return value.split()[0] + return None diff --git a/groupy/io.py b/groupy/io.py new file mode 100644 index 0000000..0a67a81 --- /dev/null +++ b/groupy/io.py @@ -0,0 +1,48 @@ +"""Shared file I/O helpers for Groupy.""" + +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import pandas as pd + + +def load_smiles_file(path: str | Path) -> list[str]: + """Load SMILES strings from a txt, csv, or xlsx file.""" + input_path = Path(path) + suffix = input_path.suffix.lower() + + if suffix == ".txt": + with input_path.open(encoding="utf-8") as file: + smiles = [line.strip() for line in file] + elif suffix == ".csv": + smiles = _load_smiles_column(pd.read_csv(input_path), input_path) + elif suffix == ".xlsx": + smiles = _load_smiles_column(pd.read_excel(input_path), input_path) + else: + raise ValueError("Input file must use .txt, .csv, or .xlsx format.") + + return [item for item in smiles if item] + + +def write_records_csv(records: list[dict[str, Any]], path: str | Path) -> None: + """Write result records to a CSV file.""" + output_path = Path(path) + output_path.parent.mkdir(parents=True, exist_ok=True) + pd.DataFrame(records).to_csv(output_path, index=False) + + +def write_text_lines(lines: list[str], path: str | Path) -> None: + """Write one text item per line.""" + output_path = Path(path) + output_path.parent.mkdir(parents=True, exist_ok=True) + with output_path.open("w", encoding="utf-8") as file: + for line in lines: + file.write(f"{line}\n") + + +def _load_smiles_column(dataframe: pd.DataFrame, path: Path) -> list[str]: + if "smiles" not in dataframe.columns: + raise ValueError(f"{path} must contain a 'smiles' column.") + return [str(value).strip() for value in dataframe["smiles"].dropna()] diff --git a/manual/USER_MANUAL_en.md b/manual/USER_MANUAL_en.md new file mode 100644 index 0000000..d7943ce --- /dev/null +++ b/manual/USER_MANUAL_en.md @@ -0,0 +1,475 @@ +# Groupy User Manual + +Last updated: 2026-06-25 + +This manual describes the current refactored Groupy workflow, including the desktop GUI, command-line interface, Python API, input formats, optional advanced features, and Windows packaging. The older background-oriented manual remains available at `manual/Groupy_manual.md`. + +## 1. What Groupy Does + +Groupy is a SMILES-based molecular analysis toolkit. It can: + +- Calculate molecular properties from SMILES. +- Count group-contribution groups from SMILES. +- Export tabular results to CSV. +- Show a 2D structure preview in the desktop GUI. +- Import SMILES from `.txt`, `.csv`, and `.xlsx` files. +- Be used as a Python package in scripts or other applications. +- Optionally use OpenBabel for file conversion and Gaussian input generation. + +Ordinary users should start with the desktop GUI. Script users and developers can use the CLI or Python API. + +## 2. Recommended Usage + +### 2.1 Ordinary Users + +If you received a packaged Windows app, open: + +```text +dist/Groupy/Groupy.exe +``` + +Double-click `Groupy.exe` to start the app. Do not copy only `Groupy.exe`; keep the entire `dist/Groupy` folder because `_internal` contains the Python runtime, RDKit, Qt, OpenBLAS, and other required libraries. + +### 2.2 Developers And Script Users + +If you run from source, create an isolated environment: + +```powershell +conda create -n groupy_env -c conda-forge python=3.11 rdkit pandas numpy openpyxl tqdm joblib +conda activate groupy_env +python -m pip install -e . +``` + +For GUI support: + +```powershell +python -m pip install -e ".[gui]" +``` + +For Windows app packaging: + +```powershell +python -m pip install -e ".[gui,package]" +``` + +For OpenBabel-dependent features: + +```powershell +conda install -c conda-forge openbabel +``` + +Avoid `pip install openbabel`, especially on Windows. + +## 3. Desktop GUI + +Start the GUI with: + +```powershell +Groupy-GUI +``` + +Or double-click the packaged `dist/Groupy/Groupy.exe`. + +### 3.1 Enter SMILES + +Enter one or more SMILES strings in the SMILES editor, one per line: + +```text +C1CCCC1 +CCO +CC(C)C +``` + +The first valid SMILES in the editor is shown in the 2D structure preview. + +### 3.2 Import SMILES From A File + +Click `Import File` and choose a SMILES input file. Supported formats: + +- `.txt` +- `.csv` +- `.xlsx` + +A `.txt` file should contain one SMILES per line: + +```text +C1CCCC1 +CCO +CC(C)C +``` + +`.csv` and `.xlsx` files must contain a column named `smiles`: + +```text +smiles +C1CCCC1 +CCO +CC(C)C +``` + +After import, the SMILES values are inserted into the editor. + +### 3.3 2D Structure Preview + +The right-hand preview panel shows the 2D structure for a SMILES string. + +- While editing input, it previews the first valid SMILES. +- After calculation or group counting, selecting a table row updates the preview to that molecule. +- If a SMILES string cannot be parsed, the preview shows `Invalid SMILES`. + +This feature uses RDKit only. It does not require OpenBabel or ASE. + +### 3.4 Calculate Molecular Properties + +Click `Calculate Properties` to calculate properties. Results appear in the table and can be exported to CSV. + +Calculation options: + +- `Parameters` + - `Stepwise`: recommended default. + - `Simultaneous`: available for compatibility, but may be unreliable for some molecules. +- `Hydrocarbon check` + - Enabled: combustion-related properties are calculated only for hydrocarbons. + - Disabled: Groupy attempts to calculate those properties for non-hydrocarbons too, but the results may not be physically meaningful. + +### 3.5 Count Groups + +Click `Count Groups` to count group-contribution groups. + +Counting options: + +- `Show zero groups` + - Enabled: include all groups, including zero-count groups. + - Disabled: include only nonzero groups. +- `Include SMILES` + - Enabled: include a SMILES column in the output. + +### 3.6 Export CSV + +After calculation or group counting, click `Export CSV` to save the current table. + +The exported CSV can be opened with Excel, WPS, LibreOffice, or Python/pandas. + +### 3.7 GUI Troubleshooting + +If running from source and PySide6 is missing: + +```powershell +python -m pip install -e ".[gui]" +``` + +If the packaged executable does not start: + +- Make sure the whole `dist/Groupy` folder is present. +- Make sure `_internal` is next to `Groupy.exe`. +- Try copying `dist/Groupy` to a simple path without unusual characters. +- Run `dist\Groupy\Groupy.exe` from a terminal to see error messages. + +## 4. Command-Line Interface + +After installation, the `Groupy` command is available. + +### 4.1 Help + +```powershell +Groupy --help +Groupy count --help +Groupy calculate --help +Groupy convert --help +``` + +### 4.2 Count Groups For One SMILES + +```powershell +Groupy count --smiles C1CCCC1 +``` + +Example JSON output: + +```json +{"f_168": 5, "smiles": "C1CCCC1"} +``` + +Write CSV output: + +```powershell +Groupy count --smiles C1CCCC1 --output count.csv +``` + +Include zero-count groups: + +```powershell +Groupy count --smiles C1CCCC1 --include-zero --output count_full.csv +``` + +### 4.3 Calculate Properties For One SMILES + +```powershell +Groupy calculate --smiles C1CCCC1 +``` + +Write CSV output: + +```powershell +Groupy calculate --smiles C1CCCC1 --output calculate.csv +``` + +Select parameter type: + +```powershell +Groupy calculate --smiles C1CCCC1 --parameter-type step_wise +Groupy calculate --smiles C1CCCC1 --parameter-type simultaneous +``` + +Disable hydrocarbon filtering: + +```powershell +Groupy calculate --smiles CCO --no-check-hydrocarbon +``` + +### 4.4 Batch Calculation + +Input files can be `.txt`, `.csv`, or `.xlsx`: + +```powershell +Groupy calculate --input SMILES.txt --output calculate.csv +Groupy count --input SMILES.txt --output count.csv +``` + +`.csv` and `.xlsx` files must contain a `smiles` column. + +### 4.5 File Conversion + +Single-file conversion: + +```powershell +Groupy convert --input molecule.xyz --from xyz --to mol2 --output molecule.mol2 +``` + +This requires OpenBabel: + +```powershell +conda install -c conda-forge openbabel +``` + +Conversion is an optional advanced workflow and is not part of the default GUI workflow. + +### 4.6 Legacy Interactive Menu + +Running: + +```powershell +Groupy +``` + +starts the legacy interactive menu. New scripts and automation should prefer: + +- `Groupy count` +- `Groupy calculate` +- `Groupy convert` +- `groupy.api` + +## 5. Python API + +Groupy can be used as a Python library. + +### 5.1 Single-Molecule Calls + +```python +from groupy.api import calculate_smiles, count_smiles + +properties = calculate_smiles("C1CCCC1") +groups = count_smiles("C1CCCC1") + +print(properties) +print(groups) +``` + +### 5.2 Batch Calls + +```python +from groupy.api import calculate_many_smiles, count_many_smiles, write_records_csv + +smiles_values = ["C1CCCC1", "CCO", "CC(C)C"] + +properties = calculate_many_smiles(smiles_values) +groups = count_many_smiles(smiles_values) + +write_records_csv(properties, "calculate.csv") +write_records_csv(groups, "count.csv") +``` + +### 5.3 Load SMILES From A File + +```python +from groupy.io import load_smiles_file + +smiles_values = load_smiles_file("SMILES.xlsx") +``` + +`.csv` and `.xlsx` files must contain a `smiles` column. + +### 5.4 Lower-Level Classes + +For compatibility or finer control, use the lower-level classes: + +```python +from groupy.gp_calculator import Calculator +from groupy.gp_counter import Counter + +calculator = Calculator() +counter = Counter() + +calculator.calculate_mols( + "SMILES.txt", + properties_file_path="calculate.csv", + verbose=False, +) + +counter.count_mols( + "SMILES.txt", + count_result_file_path="count.csv", + add_smiles=True, + verbose=False, +) +``` + +Batch APIs continue after invalid SMILES by default for backward compatibility. To stop on the first error: + +```python +calculator.calculate_mols( + "SMILES.txt", + properties_file_path="calculate.csv", + continue_on_error=False, + verbose=False, +) +``` + +## 6. Optional Advanced Features + +### 6.1 OpenBabel Conversion + +OpenBabel-dependent features include: + +- Generating `.xyz` files from SMILES. +- Converting between common molecular file formats. +- Extracting SMILES from structure files. +- Supporting coordinate generation for Gaussian input files. + +Install OpenBabel with: + +```powershell +conda install -c conda-forge openbabel +``` + +### 6.2 Gaussian Input Generation + +`groupy.gp_generator.Generator` can generate Gaussian `.gjf` files from SMILES. This workflow usually depends on OpenBabel for 3D coordinate generation. + +This is currently an advanced scripting workflow, not part of the default GUI workflow. + +### 6.3 Molecular Visualization + +The legacy Viewer depends on ASE: + +```powershell +python -m pip install -e ".[viewer]" +``` + +Or: + +```powershell +conda install -c conda-forge ase +``` + +The current GUI 2D structure preview does not require ASE. + +## 7. Build A Windows App + +To distribute a double-clickable Windows app, use a clean conda-forge OpenBLAS packaging environment: + +```powershell +conda create -n groupy_package -c conda-forge python=3.11 rdkit pandas numpy openpyxl tqdm joblib pyside6 pyinstaller "libblas=*=*openblas" +conda activate groupy_package +python -m pip install -e . --no-deps +python scripts\build_windows_app.py +``` + +Default output: + +```text +dist/Groupy/Groupy.exe +``` + +Distribute the entire `dist/Groupy` folder, not just the executable. + +The build script removes the previous `dist/Groupy` output by default so stale `_internal` files do not affect package size. Use this only for PyInstaller debugging: + +```powershell +python scripts\build_windows_app.py --no-clean-dist +``` + +Package size notes: + +```text +PACKAGING_SIZE_REPORT.md +``` + +Release checklist: + +```text +RELEASE_CHECKLIST.md +``` + +## 8. Known Limitations + +- The `simultaneous` parameter type may be unreliable for some molecules. The recommended default is `step_wise`. +- Combustion enthalpy, heat value, and specific impulse are designed mainly for hydrocarbons. Interpret non-hydrocarbon results carefully. +- OpenBabel conversion, Gaussian input generation, and ASE visualization are optional advanced workflows and are not part of the default GUI workflow. +- The packaged `_internal` folder contains runtime dependencies. Do not delete DLLs manually unless the executable is fully retested. +- More advanced GUI pages, such as OpenBabel conversion and Gaussian input generation, can be added in a later phase. + +## 9. Troubleshooting + +### 9.1 SMILES Cannot Be Parsed + +Check: + +- Extra spaces or invisible characters. +- Ring closure digits. +- Atom symbol capitalization. +- Correct lowercase aromatic atoms. + +### 9.2 CSV Or XLSX Import Fails + +Make sure the file contains a lowercase `smiles` column: + +```text +smiles +``` + +### 9.3 Packaged App Is Too Large + +Use the OpenBLAS packaging environment: + +```powershell +conda create -n groupy_package -c conda-forge python=3.11 rdkit pandas numpy openpyxl tqdm joblib pyside6 pyinstaller "libblas=*=*openblas" +``` + +If `_internal` contains many `mkl_*.dll` files, the build environment is still linked against Intel MKL. + +### 9.4 GUI Does Not Start + +When running from source: + +```powershell +Groupy-GUI --check +``` + +If PySide6 is missing: + +```powershell +python -m pip install -e ".[gui]" +``` + +For packaged apps, first check that the entire `dist/Groupy` folder is complete. diff --git a/manual/USER_MANUAL_zh.md b/manual/USER_MANUAL_zh.md new file mode 100644 index 0000000..80f25a7 --- /dev/null +++ b/manual/USER_MANUAL_zh.md @@ -0,0 +1,475 @@ +# Groupy 用户手册 + +更新日期:2026-06-25 + +本手册面向当前重构后的 Groupy,重点介绍普通用户的桌面 GUI、命令行、Python API、文件格式和 Windows 打包发布方式。旧版背景说明和更详细的算法介绍仍保留在 `manual/Groupy_manual.md`。 + +## 1. Groupy 可以做什么 + +Groupy 是一个基于 SMILES 的分子分析工具,主要功能包括: + +- 根据 SMILES 计算分子性质。 +- 根据 SMILES 统计基团贡献方法中的基团数量。 +- 将结果导出为 CSV 文件。 +- 在桌面 GUI 中查看 SMILES 对应的 2D 分子结构。 +- 从 `.txt`、`.csv`、`.xlsx` 文件批量导入 SMILES。 +- 作为 Python 包被其他脚本或 GUI 调用。 +- 可选地使用 OpenBabel 做格式转换和 Gaussian 输入文件生成。 + +普通用户推荐使用桌面 GUI;脚本用户和开发者可以使用命令行或 Python API。 + +## 2. 推荐使用方式 + +### 2.1 普通用户 + +如果你拿到的是打包后的 Windows 程序,请进入: + +```text +dist/Groupy/Groupy.exe +``` + +双击 `Groupy.exe` 启动。不要只复制 `Groupy.exe`,必须保留整个 `dist/Groupy` 文件夹,因为 `_internal` 中包含运行所需的 Python、RDKit、Qt、OpenBLAS 等库。 + +### 2.2 开发者或脚本用户 + +如果你从源码运行,推荐创建独立环境: + +```powershell +conda create -n groupy_env -c conda-forge python=3.11 rdkit pandas numpy openpyxl tqdm joblib +conda activate groupy_env +python -m pip install -e . +``` + +如果需要 GUI: + +```powershell +python -m pip install -e ".[gui]" +``` + +如果需要打包 Windows 应用: + +```powershell +python -m pip install -e ".[gui,package]" +``` + +如果需要 OpenBabel 相关功能: + +```powershell +conda install -c conda-forge openbabel +``` + +不要使用 `pip install openbabel`,它在 Windows 上通常不可靠。 + +## 3. 桌面 GUI 使用说明 + +启动 GUI: + +```powershell +Groupy-GUI +``` + +或者双击打包后的 `dist/Groupy/Groupy.exe`。 + +### 3.1 输入 SMILES + +在左上方 SMILES 输入框中输入一个或多个 SMILES,每行一个。例如: + +```text +C1CCCC1 +CCO +CC(C)C +``` + +输入框中第一个有效 SMILES 会显示在右侧 2D 结构预览区域。 + +### 3.2 从文件导入 SMILES + +点击 `Import File` 选择输入文件。支持: + +- `.txt` +- `.csv` +- `.xlsx` + +`.txt` 文件每行一个 SMILES: + +```text +C1CCCC1 +CCO +CC(C)C +``` + +`.csv` 和 `.xlsx` 文件必须包含名为 `smiles` 的列: + +```text +smiles +C1CCCC1 +CCO +CC(C)C +``` + +导入后,文件中的 SMILES 会填入输入框。 + +### 3.3 2D 结构预览 + +GUI 右侧会显示 SMILES 对应的 2D 结构图。 + +- 输入框改变时,预览第一个有效 SMILES。 +- 计算或统计后,点击结果表中的某一行,预览会切换到该行对应的分子。 +- 如果 SMILES 无法解析,预览区会显示 `Invalid SMILES`。 + +该功能使用 RDKit 生成 2D 图,不需要 OpenBabel 或 ASE。 + +### 3.4 计算分子性质 + +点击 `Calculate Properties` 计算性质。结果会显示在表格中,并可导出为 CSV。 + +计算选项: + +- `Parameters` + - `Stepwise`:默认推荐选项。 + - `Simultaneous`:保留为可选参数类型;当前已知可能在部分分子上产生不可靠结果。 +- `Hydrocarbon check` + - 勾选时,燃烧焓、热值、比冲等仅在烃类分子上计算。 + - 取消勾选时,会尝试对非烃类分子也计算这些性质,但结果可能没有物理意义。 + +### 3.5 统计基团数量 + +点击 `Count Groups` 统计基团数量。 + +统计选项: + +- `Show zero groups` + - 勾选后,结果中会显示所有基团,包括数量为 0 的基团。 + - 不勾选时,只显示非零基团。 +- `Include SMILES` + - 勾选后,结果中包含原始或规范化后的 SMILES 列。 + +### 3.6 导出 CSV + +计算或统计完成后,点击 `Export CSV` 保存结果。 + +CSV 文件可用 Excel、WPS、LibreOffice 或 Python/pandas 打开。 + +### 3.7 GUI 常见问题 + +如果从源码运行 GUI 时出现 PySide6 缺失提示: + +```powershell +python -m pip install -e ".[gui]" +``` + +如果双击打包后的 exe 无反应: + +- 确认没有只复制 `Groupy.exe`。 +- 确认 `_internal` 文件夹仍在 `Groupy.exe` 同级目录。 +- 尝试把整个 `dist/Groupy` 文件夹复制到没有中文或特殊字符的路径下测试。 +- 从命令行运行 `dist\Groupy\Groupy.exe` 查看错误信息。 + +## 4. 命令行使用 + +安装后会提供 `Groupy` 命令。 + +### 4.1 查看帮助 + +```powershell +Groupy --help +Groupy count --help +Groupy calculate --help +Groupy convert --help +``` + +### 4.2 统计单个 SMILES 的基团 + +```powershell +Groupy count --smiles C1CCCC1 +``` + +输出为 JSON,例如: + +```json +{"f_168": 5, "smiles": "C1CCCC1"} +``` + +导出 CSV: + +```powershell +Groupy count --smiles C1CCCC1 --output count.csv +``` + +显示零值基团: + +```powershell +Groupy count --smiles C1CCCC1 --include-zero --output count_full.csv +``` + +### 4.3 计算单个 SMILES 的性质 + +```powershell +Groupy calculate --smiles C1CCCC1 +``` + +导出 CSV: + +```powershell +Groupy calculate --smiles C1CCCC1 --output calculate.csv +``` + +指定参数类型: + +```powershell +Groupy calculate --smiles C1CCCC1 --parameter-type step_wise +Groupy calculate --smiles C1CCCC1 --parameter-type simultaneous +``` + +取消烃类检查: + +```powershell +Groupy calculate --smiles CCO --no-check-hydrocarbon +``` + +### 4.4 批量计算 + +输入文件可以是 `.txt`、`.csv` 或 `.xlsx`。 + +```powershell +Groupy calculate --input SMILES.txt --output calculate.csv +Groupy count --input SMILES.txt --output count.csv +``` + +`.csv` 和 `.xlsx` 文件必须包含 `smiles` 列。 + +### 4.5 文件格式转换 + +单文件转换命令: + +```powershell +Groupy convert --input molecule.xyz --from xyz --to mol2 --output molecule.mol2 +``` + +该功能需要 OpenBabel: + +```powershell +conda install -c conda-forge openbabel +``` + +转换功能不是默认 GUI 主流程的一部分。 + +### 4.6 旧交互菜单 + +直接运行: + +```powershell +Groupy +``` + +会进入旧版交互菜单。新脚本和自动化流程建议优先使用: + +- `Groupy count` +- `Groupy calculate` +- `Groupy convert` +- `groupy.api` + +## 5. Python API + +Groupy 可以作为 Python 包使用。 + +### 5.1 单分子计算 + +```python +from groupy.api import calculate_smiles, count_smiles + +properties = calculate_smiles("C1CCCC1") +groups = count_smiles("C1CCCC1") + +print(properties) +print(groups) +``` + +### 5.2 批量计算 + +```python +from groupy.api import calculate_many_smiles, count_many_smiles, write_records_csv + +smiles_values = ["C1CCCC1", "CCO", "CC(C)C"] + +properties = calculate_many_smiles(smiles_values) +groups = count_many_smiles(smiles_values) + +write_records_csv(properties, "calculate.csv") +write_records_csv(groups, "count.csv") +``` + +### 5.3 从文件读取 SMILES + +```python +from groupy.io import load_smiles_file + +smiles_values = load_smiles_file("SMILES.xlsx") +``` + +`.csv` 和 `.xlsx` 文件必须包含 `smiles` 列。 + +### 5.4 使用底层类 + +如果需要旧 API 或更细控制,可以直接使用底层类: + +```python +from groupy.gp_calculator import Calculator +from groupy.gp_counter import Counter + +calculator = Calculator() +counter = Counter() + +calculator.calculate_mols( + "SMILES.txt", + properties_file_path="calculate.csv", + verbose=False, +) + +counter.count_mols( + "SMILES.txt", + count_result_file_path="count.csv", + add_smiles=True, + verbose=False, +) +``` + +默认情况下,批处理遇到坏 SMILES 会继续执行并记录失败信息。如果希望遇到第一个错误就停止: + +```python +calculator.calculate_mols( + "SMILES.txt", + properties_file_path="calculate.csv", + continue_on_error=False, + verbose=False, +) +``` + +## 6. 可选进阶功能 + +### 6.1 OpenBabel 转换 + +OpenBabel 相关功能包括: + +- SMILES 生成 `.xyz`。 +- 常见分子文件格式转换。 +- 从结构文件提取 SMILES。 +- Gaussian 输入文件生成中的结构转换步骤。 + +安装: + +```powershell +conda install -c conda-forge openbabel +``` + +### 6.2 Gaussian 输入文件生成 + +`groupy.gp_generator.Generator` 可根据 SMILES 生成 Gaussian `.gjf` 文件。该流程通常依赖 OpenBabel 生成三维坐标。 + +这部分目前属于进阶脚本功能,不是默认 GUI 主流程。 + +### 6.3 分子可视化 + +旧版 Viewer 依赖 ASE: + +```powershell +python -m pip install -e ".[viewer]" +``` + +或: + +```powershell +conda install -c conda-forge ase +``` + +当前 GUI 的 2D 结构预览不依赖 ASE。 + +## 7. 打包 Windows 应用 + +如果需要给普通用户发布双击运行的 Windows 应用,推荐使用干净的 conda-forge + OpenBLAS 打包环境: + +```powershell +conda create -n groupy_package -c conda-forge python=3.11 rdkit pandas numpy openpyxl tqdm joblib pyside6 pyinstaller "libblas=*=*openblas" +conda activate groupy_package +python -m pip install -e . --no-deps +python scripts\build_windows_app.py +``` + +默认输出: + +```text +dist/Groupy/Groupy.exe +``` + +发布时请压缩并分发整个 `dist/Groupy` 文件夹,不要只分发 exe。 + +打包脚本默认会删除旧的 `dist/Groupy` 输出目录,以避免旧 `_internal` 文件残留影响体积。只有调试 PyInstaller 时才建议使用: + +```powershell +python scripts\build_windows_app.py --no-clean-dist +``` + +体积优化记录见: + +```text +PACKAGING_SIZE_REPORT.md +``` + +发布前检查清单见: + +```text +RELEASE_CHECKLIST.md +``` + +## 8. 已知限制 + +- `simultaneous` 参数类型在部分分子上可能产生不可靠结果,默认建议使用 `step_wise`。 +- 燃烧焓、热值、比冲等性质主要针对烃类分子设计;非烃分子的结果需要谨慎解释。 +- OpenBabel 转换、Gaussian 输入文件生成、ASE 可视化属于可选进阶功能,不是默认 GUI 主流程。 +- 打包后的 `_internal` 是运行时依赖目录,不应手动删除其中 DLL,除非重新完整测试 exe。 +- 进一步的 GUI 高级页面,例如 OpenBabel 转换和 Gaussian 输入生成,可以作为后续阶段开发。 + +## 9. 故障排查 + +### 9.1 无法解析 SMILES + +检查: + +- 是否有空格或不可见字符。 +- 环闭合数字是否成对。 +- 原子符号大小写是否正确。 +- 芳香原子是否使用正确小写形式。 + +### 9.2 CSV 或 XLSX 无法导入 + +检查文件中是否有名为 `smiles` 的列。列名区分大小写,建议使用小写: + +```text +smiles +``` + +### 9.3 打包后体积过大 + +优先确认是否使用了 OpenBLAS 打包环境: + +```powershell +conda create -n groupy_package -c conda-forge python=3.11 rdkit pandas numpy openpyxl tqdm joblib pyside6 pyinstaller "libblas=*=*openblas" +``` + +如果 `_internal` 中存在大量 `mkl_*.dll`,说明构建环境仍然链接了 Intel MKL。 + +### 9.4 GUI 启动失败 + +源码运行时检查: + +```powershell +Groupy-GUI --check +``` + +如果提示缺少 PySide6: + +```powershell +python -m pip install -e ".[gui]" +``` + +打包应用启动失败时,优先确认整个 `dist/Groupy` 文件夹是否完整。 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..a12e3d0 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,50 @@ +[build-system] +requires = ["setuptools>=64", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "Groupy" +version = "3.0.0" +description = "Groupy -- A Useful Tool for Molecular Analysis" +readme = "README.md" +requires-python = ">=3.10" +authors = [ + { name = "Ruichen Liu", email = "1197748182@qq.com" }, +] +dependencies = [ + "joblib", + "numpy", + "openpyxl", + "pandas", + "rdkit", + "tqdm", +] + +[project.optional-dependencies] +convert = [] +gui = [ + "PySide6", +] +package = [ + "PyInstaller", +] +viewer = [ + "ase", +] +dev = [ + "build", + "pytest", +] + +[project.scripts] +Groupy = "groupy.cli:main" +Groupy-GUI = "groupy.gui:main" + +[project.urls] +Homepage = "https://github.com/47-5/Groupy" + +[tool.setuptools.packages.find] +include = ["groupy*"] + +[tool.setuptools.package-data] +groupy = ["*.xlsx"] diff --git a/scripts/build_windows_app.py b/scripts/build_windows_app.py new file mode 100644 index 0000000..3b7b066 --- /dev/null +++ b/scripts/build_windows_app.py @@ -0,0 +1,180 @@ +"""Build a double-clickable Windows desktop app for Groupy with PyInstaller.""" + +from __future__ import annotations + +import argparse +import importlib.util +import platform +import shlex +import shutil +import subprocess +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +ENTRY_SCRIPT = ROOT / "scripts" / "groupy_gui_entry.py" +DEFAULT_EXCLUDES = [ + "IPython", + "PyQt5", + "PyQt6", + "PySide2", + "jupyter", + "matplotlib", + "notebook", + "pytest", + "sphinx", + "tkinter", +] + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--name", default="Groupy", help="Application name. Default: Groupy.") + parser.add_argument( + "--mode", + choices=("onedir", "onefile"), + default="onedir", + help="Build an app folder or a single executable. Default: onedir.", + ) + parser.add_argument("--dist-path", type=Path, default=ROOT / "dist", help="PyInstaller dist directory.") + parser.add_argument( + "--work-path", + type=Path, + default=ROOT / "build" / "pyinstaller", + help="PyInstaller temporary work directory.", + ) + parser.add_argument( + "--spec-path", + type=Path, + default=ROOT / "build" / "pyinstaller", + help="Directory for generated PyInstaller spec files.", + ) + parser.add_argument("--console", action="store_true", help="Keep a console window for debugging.") + parser.add_argument("--no-clean", action="store_true", help="Do not clear PyInstaller cache before building.") + parser.add_argument( + "--no-clean-dist", + action="store_true", + help="Do not remove the existing output app folder or executable before building.", + ) + parser.add_argument( + "--no-default-excludes", + action="store_true", + help="Do not exclude the default list of unused optional modules.", + ) + parser.add_argument( + "--exclude-module", + action="append", + default=[], + help="Additional module to exclude from the PyInstaller build. Can be used more than once.", + ) + parser.add_argument("--dry-run", action="store_true", help="Print the command without running PyInstaller.") + args = parser.parse_args(argv) + + command = build_command(args) + expected_output = expected_output_path(args) + + if args.dry_run: + print("PyInstaller command:") + print(format_command(command)) + print(f"Expected output: {expected_output}") + if not args.no_clean_dist: + print(f"Clean output before build: {output_cleanup_path(args)}") + return 0 + + missing = [ + module + for module in ("PyInstaller", "PySide6") + if importlib.util.find_spec(module) is None + ] + if missing: + print( + "Missing build dependencies: {}. Install them with " + '`python -m pip install -e ".[gui,package]"`.'.format(", ".join(missing)), + file=sys.stderr, + ) + return 1 + + if platform.system() != "Windows": + print( + "Warning: PyInstaller builds for the current platform only. " + "Run this script on Windows to produce a Windows .exe.", + file=sys.stderr, + ) + + if not args.no_clean_dist: + clean_existing_output(args) + + completed = subprocess.run(command, cwd=ROOT, check=False) + if completed.returncode != 0: + return completed.returncode + + print(f"Built Groupy desktop app: {expected_output}") + return 0 + + +def build_command(args: argparse.Namespace) -> list[str]: + command = [ + sys.executable, + "-m", + "PyInstaller", + "--noconfirm", + f"--name={args.name}", + f"--distpath={args.dist_path}", + f"--workpath={args.work_path}", + f"--specpath={args.spec_path}", + "--collect-data=groupy", + "--collect-data=rdkit", + "--collect-submodules=rdkit", + f"--{args.mode}", + ] + + excludes = [] + if not args.no_default_excludes: + excludes.extend(DEFAULT_EXCLUDES) + excludes.extend(args.exclude_module) + for module in excludes: + command.append(f"--exclude-module={module}") + + if args.console: + command.append("--console") + else: + command.append("--windowed") + + if not args.no_clean: + command.append("--clean") + + command.append(str(ENTRY_SCRIPT)) + return command + + +def expected_output_path(args: argparse.Namespace) -> Path: + if args.mode == "onefile": + return args.dist_path / f"{args.name}.exe" + return args.dist_path / args.name / f"{args.name}.exe" + + +def output_cleanup_path(args: argparse.Namespace) -> Path: + if args.mode == "onefile": + return args.dist_path / f"{args.name}.exe" + return args.dist_path / args.name + + +def clean_existing_output(args: argparse.Namespace) -> None: + target = output_cleanup_path(args) + resolved_target = target.resolve() + resolved_dist = args.dist_path.resolve() + if resolved_target == resolved_dist or resolved_dist not in resolved_target.parents: + raise RuntimeError(f"Refusing to clean unsafe output path: {target}") + if target.is_dir(): + shutil.rmtree(target) + elif target.exists(): + target.unlink() + + +def format_command(command: list[str]) -> str: + return shlex.join(str(part) for part in command) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/groupy_gui_entry.py b/scripts/groupy_gui_entry.py new file mode 100644 index 0000000..20ab179 --- /dev/null +++ b/scripts/groupy_gui_entry.py @@ -0,0 +1,6 @@ +"""PyInstaller entry script for the Groupy desktop GUI.""" + +from groupy.gui import main + + +raise SystemExit(main()) diff --git a/setup.py b/setup.py index 09c9f07..63294de 100644 --- a/setup.py +++ b/setup.py @@ -1,33 +1,9 @@ -from setuptools import setup, find_packages -from os.path import join +"""Compatibility shim for legacy tooling. +Project metadata lives in pyproject.toml. +""" -VERSION = '3.0.0' -DESCRIPTION = 'Groupy -- A Useful Tool for Molecular Analysis' +from setuptools import setup -setup( - name="Groupy", - version=VERSION, - author="Ruichen Liu", - author_email="1197748182@qq.com", - description=DESCRIPTION, - url='https://github.com/47-5/Groupy', - packages=find_packages(), - - install_requires=['rdkit', 'pandas', 'numpy', 'tqdm', 'ase', 'joblib', 'openpyxl'], - python_requires='>3.5', - - data_files=[(join('.', 'groupy_internal_data'), [join('.', 'groupy', 'group_contribution_parameters.xlsx'), - join('.', 'groupy', 'group_order.xlsx') - ])], - - - entry_points={ - 'console_scripts': [ - 'Groupy = groupy.groupy_main:main' - ] - }, - scripts=['groupy/groupy_main.py'], - -) \ No newline at end of file +setup() diff --git a/tests/test_smoke.py b/tests/test_smoke.py new file mode 100644 index 0000000..91b711e --- /dev/null +++ b/tests/test_smoke.py @@ -0,0 +1,1241 @@ +import importlib.util +import builtins +import io +import json +import os +import subprocess +import sys +import tempfile +import unittest +from contextlib import redirect_stderr, redirect_stdout +from pathlib import Path +from types import SimpleNamespace + +import pandas as pd + +from groupy.gp_calculator import Calculator +from groupy.gp_counter import Counter +from groupy.gp_loader import Loader +from groupy.gp_tool import Tool +from groupy.io import load_smiles_file + + +class LoaderSmokeTests(unittest.TestCase): + def tearDown(self): + Loader.clear_cache() + + def test_loader_reads_bundled_data(self): + loader = Loader() + + parameters = loader.load_parameters() + group_orders = loader.load_group_order() + + self.assertEqual(len(parameters), 425) + self.assertEqual(group_orders[0][:3], [104, 105, 93]) + self.assertTrue(all(group_orders)) + + def test_loader_caches_bundled_excel_reads(self): + Loader.clear_cache() + read_calls = [] + original_read_excel = Loader._read_excel + + def counting_read_excel(data_file, **kwargs): + read_calls.append(kwargs["sheet_name"]) + return original_read_excel(data_file, **kwargs) + + Loader._read_excel = staticmethod(counting_read_excel) + try: + loader = Loader() + loader.load_parameters(parameter_type="simultaneous") + Loader().load_parameters(parameter_type="simultaneous") + loader.load_group_order() + Loader().load_group_order() + finally: + Loader._read_excel = staticmethod(original_read_excel) + + self.assertEqual( + read_calls, + [ + "simultaneous_first_order", + "simultaneous_second_order", + "simultaneous_third_order", + "simultaneous_constants", + "f", + "s", + "t", + ], + ) + + def test_loader_returns_independent_copies_from_cache(self): + Loader.clear_cache() + loader = Loader() + + parameters = loader.load_parameters() + parameter_key = next(iter(parameters)) + property_key = next(iter(parameters[parameter_key])) + original_value = parameters[parameter_key][property_key] + parameters[parameter_key][property_key] = "changed" + + group_orders = loader.load_group_order() + group_orders[0].append(999999) + + fresh_parameters = Loader().load_parameters() + fresh_group_orders = Loader().load_group_order() + + self.assertEqual(fresh_parameters[parameter_key][property_key], original_value) + self.assertNotIn(999999, fresh_group_orders[0]) + + +class PackagingSmokeTests(unittest.TestCase): + @unittest.skipUnless(importlib.util.find_spec("tomllib"), "tomllib is available on Python 3.11+") + def test_optional_dependency_groups_keep_viewer_out_of_core(self): + import tomllib + + pyproject_path = Path(__file__).resolve().parents[1] / "pyproject.toml" + project = tomllib.loads(pyproject_path.read_text(encoding="utf-8"))["project"] + + dependencies = project["dependencies"] + optional_dependencies = project["optional-dependencies"] + + self.assertNotIn("ase", dependencies) + self.assertEqual(optional_dependencies["viewer"], ["ase"]) + self.assertEqual(optional_dependencies["gui"], ["PySide6"]) + self.assertEqual(optional_dependencies["package"], ["PyInstaller"]) + self.assertEqual(optional_dependencies["convert"], []) + self.assertIn("pytest", optional_dependencies["dev"]) + + def test_windows_package_script_dry_run_uses_gui_entry(self): + script_path = Path(__file__).resolve().parents[1] / "scripts" / "build_windows_app.py" + completed = subprocess.run( + [sys.executable, str(script_path), "--dry-run"], + text=True, + capture_output=True, + timeout=20, + check=False, + ) + + self.assertEqual(completed.returncode, 0, completed.stderr) + self.assertIn("PyInstaller command:", completed.stdout) + self.assertIn("--windowed", completed.stdout) + self.assertIn("--onedir", completed.stdout) + self.assertIn("--collect-data=groupy", completed.stdout) + self.assertIn("--collect-submodules=rdkit", completed.stdout) + self.assertIn("--exclude-module=matplotlib", completed.stdout) + self.assertIn("--exclude-module=IPython", completed.stdout) + self.assertIn("groupy_gui_entry.py", completed.stdout) + self.assertIn("dist", completed.stdout) + self.assertIn("Clean output before build:", completed.stdout) + + def test_windows_package_script_accepts_custom_excludes(self): + script_path = Path(__file__).resolve().parents[1] / "scripts" / "build_windows_app.py" + completed = subprocess.run( + [ + sys.executable, + str(script_path), + "--dry-run", + "--no-default-excludes", + "--exclude-module", + "example_unused_module", + ], + text=True, + capture_output=True, + timeout=20, + check=False, + ) + + self.assertEqual(completed.returncode, 0, completed.stderr) + self.assertIn("--exclude-module=example_unused_module", completed.stdout) + self.assertNotIn("--exclude-module=matplotlib", completed.stdout) + + def test_windows_package_script_cleanup_targets_current_output(self): + from scripts.build_windows_app import output_cleanup_path + + onedir_args = SimpleNamespace(mode="onedir", dist_path=Path("dist"), name="Groupy") + onefile_args = SimpleNamespace(mode="onefile", dist_path=Path("dist"), name="Groupy") + + self.assertEqual(output_cleanup_path(onedir_args), Path("dist") / "Groupy") + self.assertEqual(output_cleanup_path(onefile_args), Path("dist") / "Groupy.exe") + + def test_readme_documents_packaged_app_limitations(self): + readme_path = Path(__file__).resolve().parents[1] / "README.md" + readme = readme_path.read_text(encoding="utf-8") + + self.assertIn("OpenBabel from conda-forge", readme) + self.assertIn("not part of the default GUI workflow", readme) + self.assertIn("clean Windows machine", readme) + self.assertIn("Package size optimization is intentionally deferred", readme) + self.assertIn("RELEASE_CHECKLIST.md", readme) + + def test_release_checklist_documents_double_click_distribution(self): + checklist_path = Path(__file__).resolve().parents[1] / "RELEASE_CHECKLIST.md" + checklist = checklist_path.read_text(encoding="utf-8") + + self.assertIn("dist/Groupy/Groupy.exe", checklist) + self.assertIn("dist/Groupy", checklist) + self.assertIn("_internal", checklist) + self.assertIn("clean Windows", checklist) + self.assertIn("C1CCCC1", checklist) + self.assertIn("CSV export", checklist) + self.assertIn("OpenBabel from conda-forge", checklist) + + def test_packaging_size_report_documents_current_baseline(self): + report_path = Path(__file__).resolve().parents[1] / "PACKAGING_SIZE_REPORT.md" + report = report_path.read_text(encoding="utf-8") + + self.assertIn("dist/Groupy/_internal", report) + self.assertIn("MKL DLLs", report) + self.assertIn("538.61 MB", report) + self.assertIn("213.04 MB", report) + self.assertIn("MKL DLLs | 0 | 0.00 MB", report) + self.assertIn("OpenBLAS Rebuild Result", report) + self.assertIn("conda-forge packaging environment", report) + self.assertIn("--no-clean-dist", report) + + def test_user_manuals_document_current_gui_and_packaging_workflows(self): + root = Path(__file__).resolve().parents[1] + zh_manual = (root / "manual" / "USER_MANUAL_zh.md").read_text(encoding="utf-8") + en_manual = (root / "manual" / "USER_MANUAL_en.md").read_text(encoding="utf-8") + readme = (root / "README.md").read_text(encoding="utf-8") + + for manual in (zh_manual, en_manual): + self.assertIn("Groupy-GUI", manual) + self.assertIn("Import File", manual) + self.assertIn("2D", manual) + self.assertIn("Groupy calculate --smiles C1CCCC1", manual) + self.assertIn("PACKAGING_SIZE_REPORT.md", manual) + self.assertIn("libblas=*=*openblas", manual) + self.assertIn("OpenBabel", manual) + + self.assertIn("manual/USER_MANUAL_zh.md", readme) + self.assertIn("manual/USER_MANUAL_en.md", readme) + + def test_ci_workflow_runs_build_and_package_checks(self): + workflow_path = Path(__file__).resolve().parents[1] / ".github" / "workflows" / "ci.yml" + workflow = workflow_path.read_text(encoding="utf-8") + + self.assertIn("python -W error::ResourceWarning -m unittest discover -s tests", workflow) + self.assertIn("python -m compileall groupy tests scripts", workflow) + self.assertIn("python -m build", workflow) + self.assertIn("python scripts/build_windows_app.py --dry-run", workflow) + + +class CoreChemistrySmokeTests(unittest.TestCase): + def test_counter_counts_cyclopentane(self): + result = Counter().count_a_mol("C1CCCC1", clear_mode=True, add_smiles=True) + + self.assertEqual(result, {"f_168": 5, "smiles": "C1CCCC1"}) + + def test_calculator_calculates_cyclopentane(self): + result = Calculator().calculate_a_mol("C1CCCC1") + + self.assertEqual(result["smiles"], "C1CCCC1") + self.assertAlmostEqual(result["molar_mass"], 70.135) + self.assertAlmostEqual(result["Tb/K"], 308.65) + self.assertAlmostEqual(result["Pc/bar"], 42.659) + self.assertEqual(result["note"], "C1CCCC1 at 298K") + + def test_calculator_reports_invalid_smiles(self): + with self.assertLogs("groupy.gp_calculator", level="WARNING") as log_context: + result = Calculator().calculate_a_mol("not-a-smiles") + + self.assertEqual(result["smiles"], "not-a-smiles") + self.assertEqual(result["molar_mass"], "?") + self.assertIn("Invalid SMILES", result["error"]) + self.assertIn("Failed to calculate properties", log_context.output[0]) + + def test_calculator_rejects_unknown_parameter_type(self): + with self.assertRaisesRegex(ValueError, "parameter_type"): + Calculator().calculate_a_mol("C1CCCC1", parameter_type="unknown") + + def test_counter_logs_invalid_smiles(self): + counter = Counter() + + with self.assertLogs("groupy.gp_counter", level="WARNING") as log_context: + result = counter.count_a_mol("not-a-smiles") + + self.assertEqual(result, counter.init_result) + self.assertIn("Failed to count groups", log_context.output[0]) + + def test_counter_batch_uses_shared_smiles_loader(self): + with tempfile.TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + input_path = tmp_path / "smiles.txt" + output_path = tmp_path / "count.csv" + input_path.write_text("C1CCCC1\n", encoding="utf-8") + + result = Counter().count_mols( + str(input_path), + count_result_file_path=str(output_path), + add_smiles=True, + ) + + self.assertEqual(result.loc[0, "smiles"], "C1CCCC1") + self.assertEqual(result.loc[0, "f_168"], 5) + self.assertTrue(output_path.exists()) + + def test_counter_batch_can_run_quietly_for_gui_use(self): + with tempfile.TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + input_path = tmp_path / "smiles.txt" + output_path = tmp_path / "count.csv" + input_path.write_text("C1CCCC1\n", encoding="utf-8") + stdout = io.StringIO() + stderr = io.StringIO() + + with redirect_stdout(stdout), redirect_stderr(stderr): + result = Counter().count_mols( + str(input_path), + count_result_file_path=str(output_path), + add_smiles=True, + verbose=False, + ) + + self.assertEqual(stdout.getvalue(), "") + self.assertEqual(stderr.getvalue(), "") + self.assertEqual(result.loc[0, "smiles"], "C1CCCC1") + self.assertTrue(output_path.exists()) + + def test_calculator_batch_uses_shared_smiles_loader(self): + with tempfile.TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + input_path = tmp_path / "smiles.txt" + output_path = tmp_path / "calculate.csv" + input_path.write_text("C1CCCC1\n", encoding="utf-8") + + original_cwd = Path.cwd() + os.chdir(tmp_path) + try: + result = Calculator().calculate_mols( + str(input_path), + properties_file_path=str(output_path), + ) + finally: + os.chdir(original_cwd) + + self.assertEqual(result.loc[0, "smiles"], "C1CCCC1") + self.assertAlmostEqual(result.loc[0, "molar_mass"], 70.135) + self.assertTrue(output_path.exists()) + self.assertFalse((tmp_path / "error.txt").exists()) + + def test_calculator_batch_can_run_quietly_for_gui_use(self): + with tempfile.TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + input_path = tmp_path / "smiles.txt" + output_path = tmp_path / "calculate.csv" + input_path.write_text("C1CCCC1\n", encoding="utf-8") + stdout = io.StringIO() + stderr = io.StringIO() + + with redirect_stdout(stdout), redirect_stderr(stderr): + result = Calculator().calculate_mols( + str(input_path), + properties_file_path=str(output_path), + verbose=False, + ) + + self.assertEqual(stdout.getvalue(), "") + self.assertEqual(stderr.getvalue(), "") + self.assertEqual(result.loc[0, "smiles"], "C1CCCC1") + self.assertAlmostEqual(result.loc[0, "molar_mass"], 70.135) + self.assertTrue(output_path.exists()) + + def test_calculator_batch_writes_error_file_only_when_requested(self): + with tempfile.TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + input_path = tmp_path / "smiles.txt" + output_path = tmp_path / "calculate.csv" + error_path = tmp_path / "errors.txt" + input_path.write_text("not-a-smiles\n", encoding="utf-8") + + with self.assertLogs("groupy.gp_calculator", level="WARNING"): + result = Calculator().calculate_mols( + str(input_path), + properties_file_path=str(output_path), + error_file_path=str(error_path), + ) + + self.assertEqual(result.loc[0, "smiles"], "not-a-smiles") + self.assertEqual(result.loc[0, "molar_mass"], "?") + self.assertIn("Invalid SMILES", result.loc[0, "error"]) + self.assertEqual(error_path.read_text(encoding="utf-8"), "not-a-smiles\n") + self.assertTrue(output_path.exists()) + + def test_calculator_batch_can_stop_on_invalid_smiles(self): + from groupy.exceptions import InvalidSmilesError + + with tempfile.TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + input_path = tmp_path / "smiles.txt" + output_path = tmp_path / "calculate.csv" + input_path.write_text("not-a-smiles\n", encoding="utf-8") + + with self.assertRaises(InvalidSmilesError): + Calculator().calculate_mols( + str(input_path), + properties_file_path=str(output_path), + continue_on_error=False, + verbose=False, + ) + + self.assertFalse(output_path.exists()) + + def test_counter_batch_can_stop_on_invalid_smiles(self): + from groupy.exceptions import InvalidSmilesError + + with tempfile.TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + input_path = tmp_path / "smiles.txt" + output_path = tmp_path / "count.csv" + input_path.write_text("not-a-smiles\n", encoding="utf-8") + + with self.assertRaises(InvalidSmilesError): + Counter().count_mols( + str(input_path), + count_result_file_path=str(output_path), + continue_on_error=False, + verbose=False, + ) + + self.assertFalse(output_path.exists()) + + def test_parallel_aliases_for_counter_and_calculator(self): + with tempfile.TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + input_path = tmp_path / "smiles.txt" + count_output = tmp_path / "count_parallel.csv" + calculate_output = tmp_path / "calculate_parallel.csv" + input_path.write_text("C1CCCC1\n", encoding="utf-8") + + count_result = Counter().count_mols_parallel( + str(input_path), + count_result_file_path=str(count_output), + add_smiles=True, + n_jobs=1, + ) + calculate_result = Calculator().calculate_mols_parallel( + str(input_path), + properties_file_path=str(calculate_output), + n_jobs=1, + ) + + self.assertEqual(count_result.loc[0, "smiles"], "C1CCCC1") + self.assertEqual(count_result.loc[0, "f_168"], 5) + self.assertEqual(calculate_result.loc[0, "smiles"], "C1CCCC1") + self.assertAlmostEqual(calculate_result.loc[0, "molar_mass"], 70.135) + + +class SmilesFileSmokeTests(unittest.TestCase): + def test_load_smiles_iterator_from_txt_csv_and_xlsx(self): + expected = ["C1CCCC1", "CCO"] + + with tempfile.TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + txt_path = tmp_path / "smiles.txt" + csv_path = tmp_path / "smiles.csv" + xlsx_path = tmp_path / "smiles.xlsx" + + txt_path.write_text("C1CCCC1\nCCO\n", encoding="utf-8") + pd.DataFrame({"smiles": expected}).to_csv(csv_path, index=False) + pd.DataFrame({"smiles": expected}).to_excel(xlsx_path, index=False) + + self.assertEqual(Tool.load_smiles_iterator(str(txt_path)), expected) + self.assertEqual(Tool.load_smiles_iterator(str(csv_path)), expected) + self.assertEqual(Tool.load_smiles_iterator(str(xlsx_path)), expected) + self.assertEqual(load_smiles_file(txt_path), expected) + self.assertEqual(load_smiles_file(csv_path), expected) + self.assertEqual(load_smiles_file(xlsx_path), expected) + + +class ConvertorSmokeTests(unittest.TestCase): + def test_conversion_modules_do_not_import_openbabel_at_import_time(self): + command = [ + sys.executable, + "-c", + ( + "import sys; " + "import groupy.gp_convertor; " + "import groupy.gp_generator; " + "print('openbabel' in sys.modules)" + ), + ] + + completed = subprocess.run( + command, + text=True, + capture_output=True, + timeout=20, + check=False, + ) + + self.assertEqual(completed.returncode, 0, completed.stderr) + self.assertEqual(completed.stdout.strip(), "False") + + def test_lazy_pybel_import_reports_install_hint_when_missing(self): + from groupy.gp_convertor import _load_pybel + + original_import = builtins.__import__ + + def block_openbabel(name, globals=None, locals=None, fromlist=(), level=0): + if name == "openbabel": + raise ImportError("blocked for test") + return original_import(name, globals, locals, fromlist, level) + + builtins.__import__ = block_openbabel + try: + with self.assertRaisesRegex(ImportError, "conda install -c conda-forge openbabel"): + _load_pybel() + finally: + builtins.__import__ = original_import + + def test_convert_file_api_uses_predictable_default_output(self): + from groupy.api import convert_file + from groupy.gp_convertor import Convertor + + with tempfile.TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + input_path = tmp_path / "molecule.xyz" + input_path.write_text("fake xyz\n", encoding="utf-8") + original_convert_file_type = Convertor.convert_file_type + + def fake_convert_file_type(in_format, in_path, out_format, out_path=None): + Path(out_path).write_text(f"converted from {in_format} to {out_format}\n", encoding="utf-8") + return str(out_path) + + Convertor.convert_file_type = staticmethod(fake_convert_file_type) + try: + output_path = convert_file(input_path, "xyz", "mol2") + finally: + Convertor.convert_file_type = original_convert_file_type + + self.assertEqual(output_path, tmp_path / "molecule.mol2") + self.assertTrue(output_path.exists()) + + def test_convertor_reports_invalid_smiles_for_xyz(self): + from groupy.gp_convertor import Convertor + + with tempfile.TemporaryDirectory() as tmpdir: + xyz_path = Path(tmpdir) / "invalid.xyz" + + with self.assertLogs("groupy.gp_convertor", level="WARNING") as log_context: + result = Convertor.smi_to_xyz("not-a-smiles", str(xyz_path)) + + self.assertFalse(result) + self.assertFalse(xyz_path.exists()) + self.assertIn("Failed to parse SMILES", log_context.output[0]) + + def test_convert_file_type_surfaces_missing_openbabel_hint(self): + from groupy.gp_convertor import Convertor + + original_import = builtins.__import__ + + def block_openbabel(name, globals=None, locals=None, fromlist=(), level=0): + if name == "openbabel": + raise ImportError("blocked for test") + return original_import(name, globals, locals, fromlist, level) + + builtins.__import__ = block_openbabel + try: + with self.assertRaisesRegex(ImportError, "conda install -c conda-forge openbabel"): + Convertor.convert_file_type("xyz", "missing.xyz", "mol") + finally: + builtins.__import__ = original_import + + @unittest.skipUnless(importlib.util.find_spec("openbabel"), "OpenBabel is required by gp_convertor") + def test_batch_smi_to_xyz_does_not_write_logs_by_default(self): + from groupy.gp_convertor import Convertor + + with tempfile.TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + input_path = tmp_path / "smiles.txt" + xyz_root = tmp_path / "xyz" + input_path.write_text("C1CCCC1\ninvalid\n", encoding="utf-8") + + convertor = Convertor() + + def fake_smi_to_xyz(smi, xyz_path=None): + if smi == "invalid": + return False + Path(xyz_path).write_text("fake xyz\n", encoding="utf-8") + return True + + convertor.smi_to_xyz = fake_smi_to_xyz + + original_cwd = Path.cwd() + os.chdir(tmp_path) + try: + convertor.batch_smi_to_xyz(str(input_path), str(xyz_root)) + finally: + os.chdir(original_cwd) + + self.assertFalse((tmp_path / "xyz_fail.txt").exists()) + self.assertFalse((tmp_path / "xyz_succeed.txt").exists()) + + @unittest.skipUnless(importlib.util.find_spec("openbabel"), "OpenBabel is required by gp_convertor") + def test_batch_smi_to_xyz_writes_logs_when_requested(self): + from groupy.gp_convertor import Convertor + + with tempfile.TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + input_path = tmp_path / "smiles.txt" + xyz_root = tmp_path / "xyz" + fail_path = tmp_path / "logs" / "xyz_fail.txt" + succeed_path = tmp_path / "logs" / "xyz_succeed.txt" + input_path.write_text("C1CCCC1\ninvalid\n", encoding="utf-8") + + convertor = Convertor() + + def fake_smi_to_xyz(smi, xyz_path=None): + if smi == "invalid": + return False + Path(xyz_path).write_text("fake xyz\n", encoding="utf-8") + return True + + convertor.smi_to_xyz = fake_smi_to_xyz + convertor.batch_smi_to_xyz( + str(input_path), + str(xyz_root), + fail_file_path=str(fail_path), + succeed_file_path=str(succeed_path), + ) + + self.assertEqual(fail_path.read_text(encoding="utf-8"), "invalid\n") + self.assertEqual(succeed_path.read_text(encoding="utf-8"), "C1CCCC1\n") + + def test_batch_smi_to_xyz_can_run_quietly_for_gui_use(self): + from groupy.gp_convertor import Convertor + + with tempfile.TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + input_path = tmp_path / "smiles.txt" + xyz_root = tmp_path / "xyz" + input_path.write_text("C1CCCC1\n", encoding="utf-8") + + convertor = Convertor() + + def fake_smi_to_xyz(smi, xyz_path=None): + Path(xyz_path).write_text("fake xyz\n", encoding="utf-8") + return True + + convertor.smi_to_xyz = fake_smi_to_xyz + stdout = io.StringIO() + stderr = io.StringIO() + + with redirect_stdout(stdout), redirect_stderr(stderr): + convertor.batch_smi_to_xyz(str(input_path), str(xyz_root), verbose=False) + + self.assertEqual(stdout.getvalue(), "") + self.assertEqual(stderr.getvalue(), "") + self.assertTrue((xyz_root / "0000.xyz").exists()) + + def test_batch_smi_to_xyz_can_stop_on_failure(self): + from groupy.exceptions import ConversionError + from groupy.gp_convertor import Convertor + + with tempfile.TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + input_path = tmp_path / "smiles.txt" + xyz_root = tmp_path / "xyz" + input_path.write_text("invalid\n", encoding="utf-8") + + convertor = Convertor() + convertor.smi_to_xyz = lambda smi, xyz_path=None: False + + with self.assertRaises(ConversionError): + convertor.batch_smi_to_xyz( + str(input_path), + str(xyz_root), + verbose=False, + continue_on_error=False, + ) + + def test_batch_convert_file_type_can_run_quietly_for_gui_use(self): + from groupy.gp_convertor import Convertor + + with tempfile.TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + input_root = tmp_path / "input" + output_root = tmp_path / "output" + input_root.mkdir() + (input_root / "molecule.xyz").write_text("fake xyz\n", encoding="utf-8") + + convertor = Convertor() + + def fake_convert_file_type(in_format, in_path, out_format, out_path=None): + Path(out_path).write_text(f"fake {out_format}\n", encoding="utf-8") + + convertor.convert_file_type = fake_convert_file_type + stdout = io.StringIO() + stderr = io.StringIO() + + with redirect_stdout(stdout), redirect_stderr(stderr): + convertor.batch_convert_file_type( + "xyz", + str(input_root), + "mol", + str(output_root), + verbose=False, + ) + + self.assertEqual(stdout.getvalue(), "") + self.assertEqual(stderr.getvalue(), "") + self.assertTrue((output_root / "molecule.mol").exists()) + + def test_batch_file_to_smi_can_run_quietly_for_gui_use(self): + from groupy.gp_convertor import Convertor + + with tempfile.TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + input_root = tmp_path / "input" + output_root = tmp_path / "output" + input_root.mkdir() + (input_root / "molecule.mol").write_text("fake mol\n", encoding="utf-8") + + convertor = Convertor() + convertor.file_to_smi = lambda file_path, format=None, **kwargs: "C1CCCC1" + stdout = io.StringIO() + stderr = io.StringIO() + + with redirect_stdout(stdout), redirect_stderr(stderr): + result = convertor.batch_file_to_smi( + "mol", + str(input_root), + str(output_root), + verbose=False, + ) + + self.assertEqual(stdout.getvalue(), "") + self.assertEqual(stderr.getvalue(), "") + self.assertEqual(result, ["C1CCCC1"]) + self.assertEqual((output_root / "SMILES.txt").read_text(encoding="utf-8"), "C1CCCC1\n") + + @unittest.skipUnless(importlib.util.find_spec("openbabel"), "OpenBabel is required by gp_convertor") + def test_convertor_parallel_aliases_call_legacy_methods(self): + from groupy.gp_convertor import Convertor + + convertor = Convertor() + calls = [] + + def fake_batch_smi_to_xyz_mpi(*args, **kwargs): + calls.append(("xyz", args, kwargs)) + return [True] + + def fake_batch_convert_file_type_mpi(*args, **kwargs): + calls.append(("convert", args, kwargs)) + return ["converted"] + + def fake_batch_file_to_smi_mpi(*args, **kwargs): + calls.append(("smi", args, kwargs)) + return ["C1CCCC1"] + + convertor.batch_smi_to_xyz_mpi = fake_batch_smi_to_xyz_mpi + convertor.batch_convert_file_type_mpi = fake_batch_convert_file_type_mpi + convertor.batch_file_to_smi_mpi = fake_batch_file_to_smi_mpi + + self.assertEqual( + convertor.batch_smi_to_xyz_parallel("smiles.txt", "xyz", n_jobs=1), + [True], + ) + self.assertEqual( + convertor.batch_convert_file_type_parallel("xyz", "in", "mol2", n_jobs=1), + ["converted"], + ) + self.assertEqual( + convertor.batch_file_to_smi_parallel("xyz", "in", n_jobs=1), + ["C1CCCC1"], + ) + self.assertEqual([call[0] for call in calls], ["xyz", "convert", "smi"]) + + +class GeneratorSmokeTests(unittest.TestCase): + def test_smi_to_gjf_returns_false_when_xyz_conversion_fails(self): + from groupy import gp_generator + from groupy.gp_generator import Generator + + original_convertor = gp_generator.Convertor + + class FailingConvertor: + def smi_to_xyz(self, smi, xyz_path=None): + return False + + gp_generator.Convertor = FailingConvertor + try: + with tempfile.TemporaryDirectory() as tmpdir: + gjf_path = Path(tmpdir) / "molecule.gjf" + + with self.assertLogs("groupy.gp_generator", level="WARNING") as log_context: + result = Generator().smi_to_gjf( + "C1CCCC1", + gjf_path=str(gjf_path), + chk_path=str(Path(tmpdir) / "molecule.chk"), + charge_and_multiplicity="0 1", + ) + + self.assertFalse(result) + self.assertFalse(gjf_path.exists()) + self.assertIn("Failed to generate gjf", log_context.output[0]) + finally: + gp_generator.Convertor = original_convertor + + def test_generator_rejects_invalid_smiles_for_charge(self): + from groupy.exceptions import InvalidSmilesError + from groupy.gp_generator import Generator + + with self.assertRaises(InvalidSmilesError): + Generator().calculate_charge("not-a-smiles") + + @unittest.skipUnless(importlib.util.find_spec("openbabel"), "OpenBabel is required by gp_generator") + def test_batch_smi_to_gjf_does_not_write_logs_by_default(self): + from groupy.gp_generator import Generator + + with tempfile.TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + input_path = tmp_path / "smiles.txt" + gjf_root = tmp_path / "gjf" + input_path.write_text("C1CCCC1\ninvalid\n", encoding="utf-8") + + generator = Generator() + + def fake_smi_to_gjf(smi, **kwargs): + if smi == "invalid": + return False + Path(kwargs["gjf_path"]).write_text("fake gjf\n", encoding="utf-8") + return True + + generator.smi_to_gjf = fake_smi_to_gjf + + original_cwd = Path.cwd() + os.chdir(tmp_path) + try: + generator.batch_smi_to_gjf(str(input_path), str(gjf_root)) + finally: + os.chdir(original_cwd) + + self.assertFalse((tmp_path / "gjf_fail.txt").exists()) + self.assertFalse((tmp_path / "gjf_succeed.txt").exists()) + + @unittest.skipUnless(importlib.util.find_spec("openbabel"), "OpenBabel is required by gp_generator") + def test_batch_smi_to_gjf_writes_logs_when_requested(self): + from groupy.gp_generator import Generator + + with tempfile.TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + input_path = tmp_path / "smiles.txt" + gjf_root = tmp_path / "gjf" + fail_path = tmp_path / "logs" / "gjf_fail.txt" + succeed_path = tmp_path / "logs" / "gjf_succeed.txt" + input_path.write_text("C1CCCC1\ninvalid\n", encoding="utf-8") + + generator = Generator() + + def fake_smi_to_gjf(smi, **kwargs): + if smi == "invalid": + return False + Path(kwargs["gjf_path"]).write_text("fake gjf\n", encoding="utf-8") + return True + + generator.smi_to_gjf = fake_smi_to_gjf + generator.batch_smi_to_gjf( + str(input_path), + str(gjf_root), + fail_file_path=str(fail_path), + succeed_file_path=str(succeed_path), + ) + + self.assertEqual(fail_path.read_text(encoding="utf-8"), "invalid\n") + self.assertEqual(succeed_path.read_text(encoding="utf-8"), "C1CCCC1\n") + + def test_batch_smi_to_gjf_can_run_quietly_for_gui_use(self): + from groupy.gp_generator import Generator + + with tempfile.TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + input_path = tmp_path / "smiles.txt" + gjf_root = tmp_path / "gjf" + input_path.write_text("C1CCCC1\n", encoding="utf-8") + + generator = Generator() + + def fake_smi_to_gjf(smi, **kwargs): + Path(kwargs["gjf_path"]).write_text("fake gjf\n", encoding="utf-8") + return True + + generator.smi_to_gjf = fake_smi_to_gjf + stdout = io.StringIO() + stderr = io.StringIO() + + with redirect_stdout(stdout), redirect_stderr(stderr): + generator.batch_smi_to_gjf(str(input_path), str(gjf_root), verbose=False) + + self.assertEqual(stdout.getvalue(), "") + self.assertEqual(stderr.getvalue(), "") + self.assertTrue((gjf_root / "000000.gjf").exists()) + + @unittest.skipUnless(importlib.util.find_spec("openbabel"), "OpenBabel is required by gp_generator") + def test_generator_parallel_alias_calls_legacy_method(self): + from groupy.gp_generator import Generator + + generator = Generator() + calls = [] + + def fake_batch_smi_to_gjf_mpi(*args, **kwargs): + calls.append((args, kwargs)) + return [True] + + generator.batch_smi_to_gjf_mpi = fake_batch_smi_to_gjf_mpi + + self.assertEqual( + generator.batch_smi_to_gjf_parallel("smiles.txt", "gjf", n_jobs=1), + [True], + ) + self.assertEqual(calls[0][0], ("smiles.txt", "gjf")) + self.assertEqual(calls[0][1], {"n_jobs": 1}) + + +class ViewerSmokeTests(unittest.TestCase): + def test_viewer_source_has_no_bare_except(self): + source_path = Path(__file__).resolve().parents[1] / "groupy" / "gp_viewer.py" + source = source_path.read_text(encoding="utf-8") + + self.assertNotIn("except:", source) + + def test_viewer_module_does_not_import_ase_at_import_time(self): + command = [ + sys.executable, + "-c", + "import sys; import groupy.gp_viewer; print('ase' in sys.modules)", + ] + + completed = subprocess.run( + command, + text=True, + capture_output=True, + timeout=20, + check=False, + ) + + self.assertEqual(completed.returncode, 0, completed.stderr) + self.assertEqual(completed.stdout.strip(), "False") + + def test_lazy_ase_import_reports_install_hint_when_missing(self): + from groupy.gp_viewer import _load_ase_read + + original_import = builtins.__import__ + + def block_ase(name, globals=None, locals=None, fromlist=(), level=0): + if name == "ase" or name.startswith("ase."): + raise ImportError("blocked for test") + return original_import(name, globals, locals, fromlist, level) + + builtins.__import__ = block_ase + try: + with self.assertRaisesRegex(ImportError, "conda install -c conda-forge ase"): + _load_ase_read() + finally: + builtins.__import__ = original_import + + +class GuiSmokeTests(unittest.TestCase): + def test_gui_module_does_not_import_pyside6_at_import_time(self): + command = [ + sys.executable, + "-c", + "import sys; import groupy.gui; print('PySide6' in sys.modules)", + ] + + completed = subprocess.run( + command, + text=True, + capture_output=True, + timeout=20, + check=False, + ) + + self.assertEqual(completed.returncode, 0, completed.stderr) + self.assertEqual(completed.stdout.strip(), "False") + + def test_gui_check_reports_dependency_status(self): + command = [ + sys.executable, + "-m", + "groupy.gui", + "--check", + ] + + completed = subprocess.run( + command, + text=True, + capture_output=True, + timeout=20, + check=False, + ) + + if importlib.util.find_spec("PySide6"): + self.assertEqual(completed.returncode, 0, completed.stderr) + self.assertIn("PySide6 is available.", completed.stdout) + else: + self.assertEqual(completed.returncode, 1) + self.assertIn("python -m pip install -e", completed.stderr) + self.assertIn(".[gui]", completed.stderr) + + def test_gui_record_helpers_use_public_api(self): + from groupy.gui.app import calculate_records, count_records + + calculation = calculate_records(["C1CCCC1"]) + counts = count_records(["C1CCCC1"]) + + self.assertEqual(calculation[0]["smiles"], "C1CCCC1") + self.assertAlmostEqual(calculation[0]["molar_mass"], 70.135) + self.assertEqual(counts[0], {"f_168": 5, "smiles": "C1CCCC1"}) + + def test_gui_record_helpers_accept_user_options(self): + from groupy.gui.app import calculate_records, count_records + + calculation = calculate_records(["CCO"], check_hydrocarbon=True) + calculation_without_filter = calculate_records(["CCO"], check_hydrocarbon=False) + counts = count_records(["C1CCCC1"], include_zero=True, include_smiles=False) + + self.assertIsNone(calculation[0]["delta_Hc/(KJ/mol)"]) + self.assertIsNotNone(calculation_without_filter[0]["delta_Hc/(KJ/mol)"]) + self.assertIn("f_001", counts[0]) + self.assertNotIn("smiles", counts[0]) + + def test_gui_can_load_smiles_text_from_file(self): + from groupy.gui.app import load_smiles_text + + with tempfile.TemporaryDirectory() as tmpdir: + input_path = Path(tmpdir) / "smiles.txt" + input_path.write_text("C1CCCC1\nCCO\n", encoding="utf-8") + + self.assertEqual(load_smiles_text(input_path), "C1CCCC1\nCCO") + + def test_gui_can_render_smiles_structure_png(self): + from groupy.exceptions import InvalidSmilesError + from groupy.gui.app import render_smiles_png + + image = render_smiles_png("C1CCCC1", width=220, height=160) + + self.assertTrue(image.startswith(b"\x89PNG\r\n\x1a\n")) + self.assertGreater(len(image), 1000) + with self.assertRaises(InvalidSmilesError): + render_smiles_png("not-a-smiles") + + +class CliSmokeTests(unittest.TestCase): + def test_cli_import_does_not_load_openbabel_conversion_stack(self): + command = [ + sys.executable, + "-c", + "import sys; import groupy.cli; print('groupy.gp_convertor' in sys.modules)", + ] + + completed = subprocess.run( + command, + text=True, + capture_output=True, + timeout=20, + check=False, + ) + + self.assertEqual(completed.returncode, 0, completed.stderr) + self.assertEqual(completed.stdout.strip(), "False") + + def test_count_cli_outputs_json(self): + command = [ + sys.executable, + "-m", + "groupy.cli", + "count", + "--smiles", + "C1CCCC1", + ] + + completed = subprocess.run( + command, + text=True, + capture_output=True, + timeout=20, + check=False, + ) + + self.assertEqual(completed.returncode, 0, completed.stderr) + self.assertEqual(json.loads(completed.stdout), {"f_168": 5, "smiles": "C1CCCC1"}) + + def test_count_cli_writes_csv(self): + with tempfile.TemporaryDirectory() as tmpdir: + output_path = Path(tmpdir) / "count.csv" + command = [ + sys.executable, + "-m", + "groupy.cli", + "count", + "--smiles", + "C1CCCC1", + "--output", + str(output_path), + ] + + completed = subprocess.run( + command, + text=True, + capture_output=True, + timeout=20, + check=False, + ) + + self.assertEqual(completed.returncode, 0, completed.stderr) + result = pd.read_csv(output_path) + self.assertEqual(result.to_dict(orient="records"), [{"f_168": 5, "smiles": "C1CCCC1"}]) + + def test_calculate_cli_outputs_json(self): + command = [ + sys.executable, + "-m", + "groupy.cli", + "calculate", + "--smiles", + "C1CCCC1", + ] + + completed = subprocess.run( + command, + text=True, + capture_output=True, + timeout=20, + check=False, + ) + + self.assertEqual(completed.returncode, 0, completed.stderr) + result = json.loads(completed.stdout) + self.assertEqual(result["smiles"], "C1CCCC1") + self.assertAlmostEqual(result["molar_mass"], 70.135) + self.assertAlmostEqual(result["Tb/K"], 308.65) + self.assertEqual(result["note"], "C1CCCC1 at 298K") + + def test_calculate_cli_reports_invalid_smiles_as_json(self): + command = [ + sys.executable, + "-m", + "groupy.cli", + "calculate", + "--smiles", + "not-a-smiles", + ] + + completed = subprocess.run( + command, + text=True, + capture_output=True, + timeout=20, + check=False, + ) + + self.assertEqual(completed.returncode, 0, completed.stderr) + self.assertEqual(completed.stderr, "") + result = json.loads(completed.stdout) + self.assertEqual(result["smiles"], "not-a-smiles") + self.assertEqual(result["molar_mass"], "?") + self.assertIn("Invalid SMILES", result["error"]) + + def test_calculate_cli_writes_csv_from_input_file(self): + with tempfile.TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + input_path = tmp_path / "smiles.txt" + output_path = tmp_path / "calculate.csv" + input_path.write_text("C1CCCC1\nCCO\n", encoding="utf-8") + + command = [ + sys.executable, + "-m", + "groupy.cli", + "calculate", + "--input", + str(input_path), + "--output", + str(output_path), + ] + + completed = subprocess.run( + command, + text=True, + capture_output=True, + timeout=30, + check=False, + ) + + self.assertEqual(completed.returncode, 0, completed.stderr) + result = pd.read_csv(output_path) + self.assertEqual(result["smiles"].tolist(), ["C1CCCC1", "CCO"]) + self.assertIn("molar_mass", result.columns) + + def test_convert_cli_uses_api_and_outputs_json(self): + from groupy import api + from groupy import cli + + with tempfile.TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + input_path = tmp_path / "molecule.xyz" + output_path = tmp_path / "molecule.mol2" + input_path.write_text("fake xyz\n", encoding="utf-8") + calls = [] + original_convert_file = api.convert_file + + def fake_convert_file(input_path, from_format, to_format, output_path=None): + calls.append(SimpleNamespace( + input_path=Path(input_path), + from_format=from_format, + to_format=to_format, + output_path=Path(output_path), + )) + return Path(output_path) + + api.convert_file = fake_convert_file + stdout = io.StringIO() + try: + with redirect_stdout(stdout): + exit_code = cli.main( + [ + "convert", + "--input", + str(input_path), + "--from", + "xyz", + "--to", + "mol2", + "--output", + str(output_path), + ] + ) + finally: + api.convert_file = original_convert_file + + self.assertEqual(exit_code, 0) + self.assertEqual(calls[0].input_path, input_path) + self.assertEqual(calls[0].from_format, "xyz") + self.assertEqual(calls[0].to_format, "mol2") + self.assertEqual(calls[0].output_path, output_path) + self.assertEqual(json.loads(stdout.getvalue()), {"output": str(output_path)}) + + @unittest.skipUnless(importlib.util.find_spec("openbabel"), "OpenBabel is required by the legacy interactive CLI") + def test_legacy_cli_can_start_and_exit(self): + command = [ + sys.executable, + "-m", + "groupy.cli", + "interactive", + ] + + completed = subprocess.run( + command, + input="q\n", + text=True, + capture_output=True, + timeout=20, + check=False, + ) + + self.assertEqual(completed.returncode, 0, completed.stderr) + self.assertIn("exit Groupy", completed.stdout) + + +if __name__ == "__main__": + unittest.main()