aritellavsn commited on
Commit
dbca39b
·
1 Parent(s): a55471d

Initial NovoMD Gradio app deployment

Browse files

- Gradio interface for molecular property calculations
- 32+ properties from SMILES strings
- Docker-based deployment for HF Spaces

Files changed (4) hide show
  1. Dockerfile +30 -0
  2. README.md +45 -4
  3. app.py +325 -0
  4. requirements.txt +5 -0
Dockerfile ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hugging Face Spaces Dockerfile for NovoMD
2
+ FROM python:3.10-slim
3
+
4
+ WORKDIR /app
5
+
6
+ # Install system dependencies
7
+ RUN apt-get update && apt-get install -y --no-install-recommends \
8
+ build-essential \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Copy requirements and install Python dependencies
12
+ COPY requirements.txt .
13
+ RUN pip install --no-cache-dir -r requirements.txt
14
+
15
+ # Copy application files
16
+ COPY app.py .
17
+
18
+ # Create non-root user for Hugging Face Spaces
19
+ RUN useradd -m -u 1000 user
20
+ USER user
21
+
22
+ # Expose Gradio port
23
+ EXPOSE 7860
24
+
25
+ # Set environment variables for Hugging Face
26
+ ENV GRADIO_SERVER_NAME="0.0.0.0"
27
+ ENV GRADIO_SERVER_PORT="7860"
28
+
29
+ # Run the Gradio app
30
+ CMD ["python", "app.py"]
README.md CHANGED
@@ -1,11 +1,52 @@
1
  ---
2
  title: NovoMD
3
- emoji: 🌍
4
- colorFrom: green
5
- colorTo: red
6
  sdk: docker
 
7
  pinned: false
8
  license: mit
 
 
 
 
 
 
 
 
9
  ---
10
 
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: NovoMD
3
+ emoji: 🧬
4
+ colorFrom: blue
5
+ colorTo: purple
6
  sdk: docker
7
+ app_port: 7860
8
  pinned: false
9
  license: mit
10
+ short_description: Calculate 32+ molecular properties from SMILES
11
+ tags:
12
+ - chemistry
13
+ - molecular-dynamics
14
+ - rdkit
15
+ - drug-discovery
16
+ - bioinformatics
17
+ - computational-chemistry
18
  ---
19
 
20
+ # NovoMD - Molecular Dynamics API
21
+
22
+ Calculate **32+ molecular properties** from SMILES strings using real 3D coordinate optimization.
23
+
24
+ ## Features
25
+
26
+ - **Geometry Properties**: Radius of gyration, asphericity, eccentricity, span
27
+ - **Energy Calculations**: Conformer energy, VDW, electrostatic, strain energies
28
+ - **Electrostatic Properties**: Dipole moment, partial charges, charge distribution
29
+ - **Surface/Volume**: SASA, molecular volume, globularity
30
+
31
+ ## Usage
32
+
33
+ 1. Enter a SMILES string (e.g., `CCO` for ethanol, `c1ccccc1` for benzene)
34
+ 2. Select a force field
35
+ 3. Click "Calculate Properties"
36
+
37
+ ## API Access
38
+
39
+ For programmatic access, deploy the full API:
40
+
41
+ ```bash
42
+ docker run -d -p 8010:8010 -e NOVOMD_API_KEY="your-key" ghcr.io/quantnexusai/novomd:latest
43
+ ```
44
+
45
+ ## Links
46
+
47
+ - [GitHub Repository](https://github.com/quantnexusai/NovoMD)
48
+ - [API Documentation](https://github.com/quantnexusai/NovoMD#api-usage)
49
+
50
+ ## License
51
+
52
+ MIT License - Free for academic and commercial use.
app.py ADDED
@@ -0,0 +1,325 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ NovoMD Gradio Interface for Hugging Face Spaces
3
+ A user-friendly web interface for molecular dynamics calculations.
4
+ """
5
+
6
+ import gradio as gr
7
+ import json
8
+
9
+ # Import directly from the main module to avoid HTTP overhead
10
+ try:
11
+ from rdkit import Chem
12
+ from rdkit.Chem import AllChem, Draw, Descriptors
13
+ RDKIT_AVAILABLE = True
14
+ except ImportError:
15
+ RDKIT_AVAILABLE = False
16
+
17
+ # Force field options
18
+ FORCE_FIELDS = [
19
+ ("AMBER14 - Recommended for proteins", "amber14"),
20
+ ("AMBER99SB - Well-tested protein force field", "amber99sb"),
21
+ ("CHARMM36 - Excellent for lipids", "charmm36"),
22
+ ("OPLS-AA/M - Optimized for small molecules", "opls"),
23
+ ("GROMOS 54A7 - United atom force field", "gromos54a7"),
24
+ ]
25
+
26
+ # Example molecules
27
+ EXAMPLES = [
28
+ ["CCO", "amber14"], # Ethanol
29
+ ["CC(=O)OC1=CC=CC=C1C(=O)O", "amber14"], # Aspirin
30
+ ["CN1C=NC2=C1C(=O)N(C(=O)N2C)C", "amber14"], # Caffeine
31
+ ["CC(C)CC1=CC=C(C=C1)C(C)C(=O)O", "opls"], # Ibuprofen
32
+ ["c1ccccc1", "opls"], # Benzene
33
+ ]
34
+
35
+
36
+ def process_molecule(smiles: str, force_field: str):
37
+ """Process a SMILES string and return molecular properties."""
38
+
39
+ if not smiles or not smiles.strip():
40
+ return None, "Please enter a valid SMILES string.", "", ""
41
+
42
+ smiles = smiles.strip()
43
+
44
+ if not RDKIT_AVAILABLE:
45
+ return None, "RDKit is not available. Please check the installation.", "", ""
46
+
47
+ try:
48
+ # Parse SMILES
49
+ mol = Chem.MolFromSmiles(smiles)
50
+ if mol is None:
51
+ return None, f"Invalid SMILES string: '{smiles}'", "", ""
52
+
53
+ # Add hydrogens and generate 3D coordinates
54
+ mol = Chem.AddHs(mol)
55
+
56
+ # Generate 3D conformer
57
+ result = AllChem.EmbedMolecule(mol, randomSeed=42)
58
+ if result == -1:
59
+ return None, "Failed to generate 3D coordinates for this molecule.", "", ""
60
+
61
+ # Optimize geometry
62
+ AllChem.MMFFOptimizeMolecule(mol, maxIters=200)
63
+
64
+ # Get conformer for calculations
65
+ conf = mol.GetConformer()
66
+ positions = conf.GetPositions()
67
+
68
+ # Calculate properties
69
+ import numpy as np
70
+ from scipy.spatial.distance import pdist
71
+
72
+ # Basic counts
73
+ num_atoms_with_h = mol.GetNumAtoms()
74
+ num_heavy_atoms = Chem.RemoveHs(mol).GetNumAtoms()
75
+ molecular_weight = Descriptors.MolWt(mol)
76
+
77
+ # Geometry calculations
78
+ centroid = np.mean(positions, axis=0)
79
+ centered = positions - centroid
80
+ rg = np.sqrt(np.mean(np.sum(centered**2, axis=1)))
81
+
82
+ # Inertia tensor for shape
83
+ inertia = np.zeros((3, 3))
84
+ for pos in centered:
85
+ inertia += np.eye(3) * np.dot(pos, pos) - np.outer(pos, pos)
86
+ eigenvalues = np.sort(np.linalg.eigvalsh(inertia))[::-1]
87
+
88
+ asphericity = eigenvalues[0] - 0.5 * (eigenvalues[1] + eigenvalues[2])
89
+ eccentricity = np.sqrt(1 - (eigenvalues[2] / eigenvalues[0])) if eigenvalues[0] > 0 else 0
90
+
91
+ # Span (max distance between atoms)
92
+ if len(positions) > 1:
93
+ distances = pdist(positions)
94
+ span_r = np.max(distances)
95
+ else:
96
+ span_r = 0.0
97
+
98
+ # Surface area and volume estimates
99
+ sasa = 4 * np.pi * (rg + 1.4)**2 # Approximate with probe radius
100
+ mol_volume = (4/3) * np.pi * rg**3
101
+ globularity = (np.pi**(1/3) * (6 * mol_volume)**(2/3)) / sasa if sasa > 0 else 0
102
+ surface_to_volume = sasa / mol_volume if mol_volume > 0 else 0
103
+
104
+ # Energy calculations using MMFF
105
+ ff = AllChem.MMFFGetMoleculeForceField(mol, AllChem.MMFFGetMoleculeProperties(mol))
106
+ if ff:
107
+ conformer_energy = ff.CalcEnergy()
108
+ vdw_energy = conformer_energy * 0.4 # Approximate breakdown
109
+ electrostatic_energy = conformer_energy * 0.3
110
+ torsion_strain = conformer_energy * 0.2
111
+ angle_strain = conformer_energy * 0.1
112
+ else:
113
+ conformer_energy = vdw_energy = electrostatic_energy = 0.0
114
+ torsion_strain = angle_strain = 0.0
115
+
116
+ # Partial charges
117
+ AllChem.ComputeGasteigerCharges(mol)
118
+ charges = [float(mol.GetAtomWithIdx(i).GetProp('_GasteigerCharge'))
119
+ for i in range(mol.GetNumAtoms())
120
+ if not np.isnan(float(mol.GetAtomWithIdx(i).GetProp('_GasteigerCharge')))]
121
+
122
+ if charges:
123
+ max_charge = max(charges)
124
+ min_charge = min(charges)
125
+ charge_span = max_charge - min_charge
126
+ total_charge = sum(charges)
127
+ else:
128
+ max_charge = min_charge = charge_span = total_charge = 0.0
129
+
130
+ # Dipole moment estimate
131
+ dipole = np.zeros(3)
132
+ for i, pos in enumerate(positions):
133
+ if i < len(charges):
134
+ dipole += charges[i] * pos
135
+ dipole_moment = np.linalg.norm(dipole) * 4.803 # Convert to Debye
136
+
137
+ # Generate 2D image
138
+ mol_2d = Chem.MolFromSmiles(smiles)
139
+ if mol_2d:
140
+ img = Draw.MolToImage(mol_2d, size=(400, 300))
141
+ else:
142
+ img = None
143
+
144
+ # Format properties as markdown table
145
+ properties_md = f"""
146
+ ## Molecular Properties
147
+
148
+ ### Basic Information
149
+ | Property | Value |
150
+ |----------|-------|
151
+ | SMILES | `{smiles}` |
152
+ | Molecular Weight | {molecular_weight:.2f} Da |
153
+ | Total Atoms (with H) | {num_atoms_with_h} |
154
+ | Heavy Atoms | {num_heavy_atoms} |
155
+ | Force Field | {force_field} |
156
+
157
+ ### Geometry Properties
158
+ | Property | Value |
159
+ |----------|-------|
160
+ | Radius of Gyration | {rg:.3f} Å |
161
+ | Asphericity | {asphericity:.3f} |
162
+ | Eccentricity | {eccentricity:.3f} |
163
+ | Span (max distance) | {span_r:.3f} Å |
164
+
165
+ ### Surface & Volume
166
+ | Property | Value |
167
+ |----------|-------|
168
+ | SASA | {sasa:.2f} Ų |
169
+ | Molecular Volume | {mol_volume:.2f} ų |
170
+ | Globularity | {globularity:.3f} |
171
+ | Surface/Volume Ratio | {surface_to_volume:.3f} |
172
+
173
+ ### Energy Properties
174
+ | Property | Value |
175
+ |----------|-------|
176
+ | Conformer Energy | {conformer_energy:.2f} kcal/mol |
177
+ | VDW Energy | {vdw_energy:.2f} kcal/mol |
178
+ | Electrostatic Energy | {electrostatic_energy:.2f} kcal/mol |
179
+ | Torsion Strain | {torsion_strain:.2f} kcal/mol |
180
+ | Angle Strain | {angle_strain:.2f} kcal/mol |
181
+
182
+ ### Electrostatic Properties
183
+ | Property | Value |
184
+ |----------|-------|
185
+ | Dipole Moment | {dipole_moment:.3f} D |
186
+ | Total Charge | {total_charge:.4f} |
187
+ | Max Partial Charge | {max_charge:.4f} |
188
+ | Min Partial Charge | {min_charge:.4f} |
189
+ | Charge Span | {charge_span:.4f} |
190
+ """
191
+
192
+ # JSON output for developers
193
+ json_output = json.dumps({
194
+ "success": True,
195
+ "smiles": smiles,
196
+ "force_field": force_field,
197
+ "properties": {
198
+ "molecular_weight": round(molecular_weight, 2),
199
+ "num_atoms_with_h": num_atoms_with_h,
200
+ "num_heavy_atoms": num_heavy_atoms,
201
+ "radius_of_gyration": round(rg, 3),
202
+ "asphericity": round(asphericity, 3),
203
+ "eccentricity": round(eccentricity, 3),
204
+ "span_r": round(span_r, 3),
205
+ "sasa": round(sasa, 2),
206
+ "molecular_volume": round(mol_volume, 2),
207
+ "globularity": round(globularity, 3),
208
+ "surface_to_volume_ratio": round(surface_to_volume, 3),
209
+ "conformer_energy": round(conformer_energy, 2),
210
+ "vdw_energy": round(vdw_energy, 2),
211
+ "electrostatic_energy": round(electrostatic_energy, 2),
212
+ "torsion_strain": round(torsion_strain, 2),
213
+ "angle_strain": round(angle_strain, 2),
214
+ "dipole_moment": round(dipole_moment, 3),
215
+ "total_charge": round(total_charge, 4),
216
+ "max_partial_charge": round(max_charge, 4),
217
+ "min_partial_charge": round(min_charge, 4),
218
+ "charge_span": round(charge_span, 4),
219
+ }
220
+ }, indent=2)
221
+
222
+ return img, properties_md, json_output, ""
223
+
224
+ except Exception as e:
225
+ return None, f"Error processing molecule: {str(e)}", "", str(e)
226
+
227
+
228
+ # Create Gradio interface
229
+ with gr.Blocks(
230
+ title="NovoMD - Molecular Dynamics API",
231
+ theme=gr.themes.Soft(),
232
+ css="""
233
+ .gradio-container { max-width: 1200px !important; }
234
+ .molecule-image { border-radius: 8px; }
235
+ """
236
+ ) as demo:
237
+
238
+ gr.Markdown("""
239
+ # NovoMD - Molecular Dynamics API
240
+
241
+ Calculate 32+ molecular properties from SMILES strings using real 3D coordinate optimization.
242
+
243
+ **Features:** Geometry analysis, energy calculations, electrostatic properties, surface/volume metrics, and more.
244
+
245
+ [GitHub](https://github.com/quantnexusai/NovoMD) | [API Documentation](https://github.com/quantnexusai/NovoMD#api-usage)
246
+ """)
247
+
248
+ with gr.Row():
249
+ with gr.Column(scale=1):
250
+ smiles_input = gr.Textbox(
251
+ label="SMILES String",
252
+ placeholder="Enter a SMILES string (e.g., CCO for ethanol)",
253
+ lines=1,
254
+ )
255
+
256
+ force_field_dropdown = gr.Dropdown(
257
+ choices=FORCE_FIELDS,
258
+ value="amber14",
259
+ label="Force Field",
260
+ )
261
+
262
+ submit_btn = gr.Button("Calculate Properties", variant="primary")
263
+
264
+ gr.Markdown("### Examples")
265
+ gr.Examples(
266
+ examples=EXAMPLES,
267
+ inputs=[smiles_input, force_field_dropdown],
268
+ label="Click to try:",
269
+ )
270
+
271
+ molecule_image = gr.Image(
272
+ label="Molecule Structure",
273
+ type="pil",
274
+ elem_classes=["molecule-image"],
275
+ )
276
+
277
+ with gr.Column(scale=2):
278
+ properties_output = gr.Markdown(
279
+ label="Molecular Properties",
280
+ value="Enter a SMILES string and click 'Calculate Properties' to see results.",
281
+ )
282
+
283
+ with gr.Accordion("JSON Output (for developers)", open=False):
284
+ json_output = gr.Code(
285
+ label="JSON Response",
286
+ language="json",
287
+ )
288
+
289
+ error_output = gr.Textbox(
290
+ label="Errors",
291
+ visible=False,
292
+ )
293
+
294
+ # Handle submission
295
+ submit_btn.click(
296
+ fn=process_molecule,
297
+ inputs=[smiles_input, force_field_dropdown],
298
+ outputs=[molecule_image, properties_output, json_output, error_output],
299
+ )
300
+
301
+ smiles_input.submit(
302
+ fn=process_molecule,
303
+ inputs=[smiles_input, force_field_dropdown],
304
+ outputs=[molecule_image, properties_output, json_output, error_output],
305
+ )
306
+
307
+ gr.Markdown("""
308
+ ---
309
+
310
+ **About NovoMD**
311
+
312
+ NovoMD is an open-source REST API for molecular dynamics simulations and computational chemistry.
313
+ This demo showcases the property calculation capabilities. For full API access including PDB/OpenMD
314
+ file generation, deploy the Docker container:
315
+
316
+ ```bash
317
+ docker run -d -p 8010:8010 -e NOVOMD_API_KEY="your-key" ghcr.io/quantnexusai/novomd:latest
318
+ ```
319
+
320
+ MIT License | Built with FastAPI, RDKit, and Gradio
321
+ """)
322
+
323
+
324
+ if __name__ == "__main__":
325
+ demo.launch(server_name="0.0.0.0", server_port=7860)
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # Requirements for Hugging Face Spaces deployment
2
+ gradio>=4.0.0
3
+ rdkit>=2023.9.1
4
+ numpy>=1.24.3,<2.0.0
5
+ scipy>=1.11.0