Skip to content

Molecular Descriptors

The fr5.descriptors module provides comprehensive tools for generating molecular descriptors for various chemical systems. These descriptors serve as numerical representations of molecular structures and properties, enabling machine learning models to work with chemical data.

Overview

Molecular descriptors are essential for:

  • Structure-property relationships (QSPR/QSAR modeling)
  • Chemical similarity analysis and clustering
  • Machine learning feature engineering
  • Virtual screening of chemical libraries
  • Property prediction and optimization

Supported Systems

🧪 Ionic Liquids (ILs)

Generate descriptors for ionic liquid systems consisting of cations and anions:

from fr5.descriptors import generate_il_descriptors

# Single ionic liquid
il_descriptors = generate_il_descriptors(
    cation='EMIM',      # 1-ethyl-3-methylimidazolium
    anion='BF4',        # tetrafluoroborate
    temperature=298.15  # Optional: temperature-dependent descriptors
)

print(f"Generated {len(il_descriptors)} descriptors")
print(f"Descriptor names: {list(il_descriptors.keys())[:5]}...")  # First 5

Available IL Descriptor Types: - Fragment-based descriptors: Functional group counts and connectivity - Property-based descriptors: Estimated physicochemical properties - Topological descriptors: Molecular graph properties - Electronic descriptors: Charge distribution and electronic properties

🏗️ Metal-Organic Frameworks (MOFs)

Generate descriptors for MOF structures:

from fr5.descriptors import generate_mof_descriptors

# From MOF structure file
mof_descriptors = generate_mof_descriptors(
    structure_file="HKUST-1.cif",      # CIF file path
    include_pore_descriptors=True,     # Include pore analysis
    include_electronic=True           # Include electronic properties
)

print(f"MOF descriptors: {list(mof_descriptors.keys())[:10]}...")

Available MOF Descriptor Types: - Geometric descriptors: Pore size, surface area, volume - Topological descriptors: Network topology and connectivity - Chemical descriptors: Elemental composition and bonding - Electronic descriptors: Band gap, density of states

🧬 General Molecules

Generate RDKit-based descriptors for arbitrary molecules:

from fr5.descriptors import generate_molecular_descriptors

# From SMILES string
mol_descriptors = generate_molecular_descriptors(
    smiles="CCO",                    # Ethanol
    descriptor_types=['morgan', 'rdkit', 'physicochemical']
)

# From molecule file
mol_descriptors = generate_molecular_descriptors(
    mol_file="molecule.sdf",
    descriptor_types=['all']         # Generate all available descriptors
)

Descriptor Types in Detail

Fragment-Based Descriptors

Count occurrences of structural fragments:

from fr5.descriptors import FragmentDescriptors

descriptor_gen = FragmentDescriptors()

# IL fragments
il_fragments = descriptor_gen.get_il_fragments(
    cation="EMIM",
    anion="BF4"
)

# Print fragment counts
for fragment, count in il_fragments.items():
    if count > 0:
        print(f"{fragment}: {count}")

Common IL Fragments: - aromatic_rings: Number of aromatic rings - alkyl_chains: Alkyl chain lengths - functional_groups: OH, NH2, COOH, etc. - heteroatoms: N, O, S, P counts

Property-Based Descriptors

Estimate physicochemical properties:

from fr5.descriptors import PropertyDescriptors

prop_gen = PropertyDescriptors()

# Calculate molecular properties
properties = prop_gen.calculate_properties(
    cation_smiles="CCn1ccnc1",      # EMIM cation
    anion_smiles="[B-](F)(F)(F)F",  # BF4 anion
    temperature=298.15
)

# Common properties
print(f"Molecular weight: {properties['mw']:.2f}")
print(f"LogP: {properties['logp']:.2f}")
print(f"Polar surface area: {properties['psa']:.2f}")

Topological Descriptors

Analyze molecular connectivity:

from fr5.descriptors import TopologicalDescriptors

topo_gen = TopologicalDescriptors()

# Calculate topological indices
topo_descriptors = topo_gen.calculate_indices(
    smiles="CCn1ccnc1"  # EMIM cation
)

# Wiener index, Zagreb indices, etc.
for descriptor, value in topo_descriptors.items():
    print(f"{descriptor}: {value}")

Advanced Features

Autoencoder-Based Descriptors

Generate low-dimensional latent representations:

from fr5.descriptors import AutoencoderDescriptors

# Load pre-trained autoencoder
ae_gen = AutoencoderDescriptors.load_pretrained(
    model_path="models/il_autoencoder.pth"
)

# Generate latent descriptors
latent_descriptors = ae_gen.encode(
    molecular_descriptors=high_dim_descriptors,
    latent_dim=32  # Compressed to 32 dimensions
)

print(f"Compressed from {len(high_dim_descriptors)} to {len(latent_descriptors)} dimensions")

Custom Descriptor Sets

Define custom descriptor combinations:

from fr5.descriptors import DescriptorSet

# Create custom descriptor set for specific application
custom_set = DescriptorSet(
    name="viscosity_prediction",
    descriptors=[
        "fragment_aromatic_rings",
        "fragment_alkyl_chains", 
        "property_mw",
        "property_logp",
        "topological_wiener"
    ]
)

# Generate only selected descriptors
selected_descriptors = custom_set.generate(
    cation="BMIM",
    anion="Tf2N"
)

Batch Processing

Process multiple molecules efficiently:

from fr5.descriptors import batch_generate_descriptors

# List of ionic liquids
il_list = [
    {"cation": "EMIM", "anion": "BF4"},
    {"cation": "BMIM", "anion": "PF6"},
    {"cation": "HMIM", "anion": "Tf2N"}
]

# Batch generation with parallel processing
all_descriptors = batch_generate_descriptors(
    molecules=il_list,
    descriptor_types=["fragment", "property"],
    n_jobs=4,  # Parallel workers
    verbose=True
)

print(f"Generated descriptors for {len(all_descriptors)} molecules")

Data Management

Descriptor Databases

Store and manage descriptor datasets:

from fr5.descriptors import DescriptorDatabase

# Create database
db = DescriptorDatabase("descriptors.db")

# Store descriptors
db.store_descriptors(
    molecule_id="EMIM_BF4",
    descriptors=il_descriptors,
    metadata={
        "cation": "EMIM",
        "anion": "BF4", 
        "temperature": 298.15,
        "generated_date": "2024-08-12"
    }
)

# Query descriptors
stored_descriptors = db.query_descriptors(
    filters={"cation": "EMIM", "temperature": 298.15}
)

Export and Import

Save descriptors in various formats:

from fr5.descriptors import export_descriptors, import_descriptors

# Export to CSV
export_descriptors(
    descriptors_dict=all_descriptors,
    output_path="descriptors.csv",
    format="csv",
    include_metadata=True
)

# Export to HDF5 for large datasets
export_descriptors(
    descriptors_dict=all_descriptors,
    output_path="descriptors.h5",
    format="hdf5",
    compression="gzip"
)

# Import back
loaded_descriptors = import_descriptors("descriptors.csv")

Performance Optimization

Caching

Enable caching for expensive calculations:

from fr5.descriptors import enable_caching

# Enable persistent caching
enable_caching(
    cache_dir="./descriptor_cache",
    cache_size_mb=1000,  # 1GB cache
    compression=True
)

# Subsequent calls will use cached results
descriptors1 = generate_il_descriptors("EMIM", "BF4")  # Calculated
descriptors2 = generate_il_descriptors("EMIM", "BF4")  # From cache

Memory Management

Handle large descriptor sets efficiently:

from fr5.descriptors import DescriptorStream

# Stream processing for large datasets
descriptor_stream = DescriptorStream(
    molecule_list=large_molecule_list,
    batch_size=100,  # Process 100 at a time
    descriptor_types=["fragment", "property"]
)

# Process in batches
for batch_idx, batch_descriptors in enumerate(descriptor_stream):
    print(f"Processing batch {batch_idx}: {len(batch_descriptors)} molecules")
    # Process batch...

Validation and Quality Control

Descriptor Validation

Check descriptor quality and consistency:

from fr5.descriptors import validate_descriptors

validation_report = validate_descriptors(
    descriptors=all_descriptors,
    checks=[
        "missing_values",
        "constant_features", 
        "correlated_features",
        "outliers"
    ]
)

print(f"Validation results:")
for check, result in validation_report.items():
    print(f"- {check}: {result['status']}")
    if result['warnings']:
        for warning in result['warnings']:
            print(f"  Warning: {warning}")

Feature Selection

Select most informative descriptors:

from fr5.descriptors import select_descriptors

# Select descriptors based on importance
selected_descriptors = select_descriptors(
    X=descriptor_matrix,
    y=target_values,
    method="mutual_info",  # mutual_info, f_regression, rfe
    n_features=50,         # Select top 50 features
    cv_folds=5            # Cross-validation folds
)

print(f"Selected {len(selected_descriptors)} most informative descriptors")

Integration with Machine Learning

Scikit-learn Compatibility

Descriptors work seamlessly with scikit-learn:

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import r2_score

# Prepare data
X = np.array([list(desc.values()) for desc in all_descriptors])
y = target_properties

# Train-test split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Train model
model = RandomForestRegressor(n_estimators=100, random_state=42)
model.fit(X_train, y_train)

# Evaluate
y_pred = model.predict(X_test)
r2 = r2_score(y_test, y_pred)
print(f"Model R²: {r2:.3f}")

Feature Importance Analysis

Analyze descriptor importance:

from fr5.descriptors import analyze_feature_importance

# Get feature names
feature_names = list(all_descriptors[0].keys())

# Analyze importance
importance_analysis = analyze_feature_importance(
    model=trained_model,
    feature_names=feature_names,
    X_test=X_test,
    y_test=y_test
)

# Plot top features
importance_analysis.plot_top_features(n_features=20)

Best Practices

1. Descriptor Selection

  • Start with domain knowledge to select relevant descriptor types
  • Use feature selection methods to reduce dimensionality
  • Validate descriptor importance for your specific application

2. Data Preprocessing

  • Check for missing values and outliers
  • Scale descriptors appropriately for your ML algorithm
  • Remove constant and highly correlated features

3. Validation

  • Always validate descriptors on held-out test sets
  • Use cross-validation for robust performance estimates
  • Check for data leakage between train and test sets

4. Performance

  • Use caching for repeated calculations
  • Consider batch processing for large datasets
  • Profile code to identify bottlenecks

Troubleshooting

Common Issues

Memory errors with large datasets: Use streaming or batch processing

Slow descriptor generation: Enable caching and parallel processing

Missing values: Check input molecule validity and descriptor implementation

Poor model performance: Try feature selection and different descriptor types

Debugging Tools

from fr5.descriptors import debug_descriptors

# Debug descriptor generation
debug_info = debug_descriptors(
    molecule="problematic_molecule",
    descriptor_types=["fragment", "property"],
    verbose=True
)

print(f"Debug information: {debug_info}")