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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
MbedTLS_jll = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
PythonCall = "6099a3de-0909-46bc-b1f4-468b9a2dfc0d"
Expand All @@ -17,25 +18,24 @@ Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[compat]
Aqua = "0.8"
CondaPkg = "0.2"
Dates = "1"
Distributed = "1"
Graphs = "1.0"
Images = "0.20, 0.26"
LinearAlgebra = "1"
MbedTLS_jll = "2.28.6"
Printf = "1"
PythonCall = "0.9"
Reexport = "1.0"
Serialization = "1"
Statistics = "1"
Aqua = "0.8"
JuliaFormatter = "1.0"
Test = "1"
julia = "1"

[extras]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
Expand Down
2 changes: 2 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ makedocs(;
"Home" => "index.md",
"Getting Started" => "getting-started.md",
"Practical Examples" => "examples.md",
"For Developers" => "for-developers.md",
"API Reference" => [
"Basic I/O" => "api/io.md",
"Molecular Operations" => "api/operations.md",
Expand All @@ -33,6 +34,7 @@ makedocs(;
"Similarity" => "api/similarity.md",
"Standardization" => "api/standardization.md",
"Conformers" => "api/conformers.md",
"Alignment" => "api/alignment.md",
"Fragmentation" => "api/fragmentation.md",
"Graph Operations" => "api/graph.md",
"Progress Tracking" => "api/progress.md",
Expand Down
61 changes: 61 additions & 0 deletions docs/src/api/alignment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Molecular Alignment

## align_mol

```@docs
align_mol
```

## calc_rms

```@docs
calc_rms
```

## `get_best_rms`

```@docs
get_best_rms
```

## `get_alignment_transform`

```@docs
get_alignment_transform
```

## `apply_transform`

```@docs
apply_transform
```

## `random_transform`

```@docs
random_transform
```

## O3AResult

```@docs
O3AResult
```

## `get_o3a`

```@docs
get_o3a
```

## `get_crippen_o3a`

```@docs
get_crippen_o3a
```

## `o3a_align!`

```@docs
o3a_align!
```
130 changes: 130 additions & 0 deletions docs/src/for-developers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# For Developers

## Architecture Overview

MoleculeFlow.jl is built as a Julia wrapper around
[RDKit](https://github.com/rdkit/rdkit) cheminformatics library.
Rather than reimplementing complex
cheminformatics algorithms from scratch, we leverage RDKit's mature and
battle-tested implementations while providing a clean, Julia-native
interface, which also allows to build additional functionality on top.

!!! info
Knowledge of Python RDKit is assumed.
This background knowledge is essential because MoleculeFlow's functions are direct mappings to RDKit functionality.

## Development Pattern

### 1. Low-Level Caching Layer

All RDKit functionality is accessed through caching functions in `src/rdkit.jl`. This file contains functions that:
- Import and cache Python RDKit modules and functions using `@pyconst`
- Provide direct access to RDKit functions
- Handle the Python-Julia interface via PythonCall.jl

!!! info
The `@pyconst` macro from PythonCall.jl is used to cache Python function calls for better performance. This ensures that the Python import and function lookup only happens once, and subsequent calls reuse the cached function object.

Example from `rdkit.jl`:
```julia
# RDKit function caching with @pyconst
_mol_from_smiles(smiles::String) = @pyconst(pyimport("rdkit.Chem").MolFromSmiles)(smiles)
_mol_to_smiles(mol::Py) = @pyconst(pyimport("rdkit.Chem").MolToSmiles)(mol)

function _mol_wt(mol::Py)
@pyconst(pyimport("rdkit.Chem.Descriptors").MolWt)(mol)
end

function _heavy_atom_count(mol::Py)
@pyconst(pyimport("rdkit.Chem.Descriptors").HeavyAtomCount)(mol)
end
```

### 2. Higher-Level Julia Wrappers

The main API functions are implemented as Julia wrappers around the cached RDKit functions.

Example wrapper pattern:
```julia
function molecular_weight(mol::Molecule)
!mol.valid && return missing

try
result = _mol_wt(mol._rdkit_mol)
return pyconvert(Float64, result)
catch
return missing
end
end

function get_atom(mol::Molecule, idx::Int)
!mol.valid && throw(ArgumentError("Invalid molecule"))

# Convert indices (Julia 1-based → Python 0-based)
python_idx = idx - 1

rdkit_atom = mol._rdkit_mol.GetAtomWithIdx(python_idx)
return Atom(rdkit_atom, mol)
end
```

## Critical Index Conversion

!!! danger
Index Conversion is Critical: Julia uses 1-based indexing while Python uses 0-based indexing.

### Examples of Index Handling
```julia

function get_atom(mol::Molecule, idx::Int)
rdkit_mol = mol._rdkit_mol
rdkit_idx = idx - 1
return get_chem().GetAtomWithIdx(rdkit_mol, rdkit_idx)
end
```

## Error Handling Patterns

### Molecule Validation
Always check molecule validity:
```julia
function some_function(mol::Molecule)
!mol.valid && throw(ArgumentError("Invalid molecule"))
# ... proceed with function
end
```

### Graceful Degradation
For operations that may fail, return appropriate fallback values:
```julia
function calculate_descriptor(mol::Molecule)
!mol.valid && return missing

try
result = rdkit_calculation(mol._rdkit_mol)
return convert_result(result)
catch
return missing # Or appropriate fallback
end
end
```

## Contributing New Functions

When adding new functionality:

1. **Add RDKit module caching** to `src/rdkit.jl` if needed
2. **Implement the wrapper** in the appropriate module file
3. **Export the function** in `src/MoleculeFlow.jl`
4. **Add comprehensive tests** following existing patterns
5. **Document with examples** in the appropriate API docs file
6. **Handle index conversion** carefully
7. **Follow existing error handling patterns**

## Best Practices

1. **Follow existing naming conventions** (snake_case for functions)
2. **Use keyword arguments** for optional parameters
3. **Provide sensible defaults** where possible
4. **Handle missing/invalid inputs gracefully**
5. **Add type annotations** where helpful
6 changes: 6 additions & 0 deletions src/MoleculeFlow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,11 @@ export FeatureFactory, ChemicalFeature
export create_feature_factory, get_mol_features, pharmacophore_fingerprint
export get_feature_families, filter_features_by_family, get_pharmacophore_3d

# Molecular alignment functions
export align_mol, calc_rms, get_best_rms
export get_alignment_transform, random_transform, apply_transform
export O3AResult, get_o3a, get_crippen_o3a, o3a_align!

include("./config.jl")
include("./utils.jl")
include("./rdkit.jl")
Expand All @@ -197,6 +202,7 @@ include("./molecule/fingerprints.jl")
include("./molecule/similarity.jl")
include("./molecule/graph.jl")
include("./molecule/conformers.jl")
include("./molecule/alignment.jl")
include("./atom/atom.jl")
include("./atom/descriptors.jl")
include("./bond/bond.jl")
Expand Down
Loading
Loading