Skip to content

Commit cb0d1b3

Browse files
authored
Merge pull request #588 from Steinbeck-Lab/development
fix: Landing page and structure input
2 parents 4fedc3e + abb0cc3 commit cb0d1b3

File tree

7 files changed

+280
-55
lines changed

7 files changed

+280
-55
lines changed

app/modules/tools/surge.py

Lines changed: 102 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -34,48 +34,121 @@ def get_heavy_atom_count(formula: str) -> int:
3434
return heavy_atom_count
3535

3636

37-
def generate_structures_SURGE(molecular_formula: str) -> Union[list, str]:
38-
"""Generate chemical structures using the surge tool based on the canonical.
39-
40-
generation path method.
41-
37+
def get_surge_count(molecular_formula: str) -> int:
38+
"""Get the number of structures generated by the surge tool.
4239
Args:
4340
molecular_formula (str): Molecular formula provided by the user.
44-
4541
Returns:
46-
list: List of SMILES strings representing generated chemical structures.
47-
If the molecular formula contains more than 10 heavy atoms, a message
48-
indicating the limitation is returned instead.
42+
int: The number of structures generated by the surge tool.
4943
"""
5044

51-
smiles = []
5245
if get_heavy_atom_count(molecular_formula) <= 10:
5346
try:
5447
process = Popen(
55-
[
56-
"surge",
57-
"-P",
58-
"-T",
59-
"-B1,2,3,4,5,7,9",
60-
"-t0",
61-
"-f0",
62-
"-S",
63-
molecular_formula,
64-
],
65-
stdout=PIPE,
66-
stderr=PIPE,
48+
["surge", "-u", molecular_formula], stdout=PIPE, stderr=PIPE
6749
)
6850
stdout, stderr = process.communicate()
6951

70-
if process.returncode == 0:
71-
output_lines = stdout.decode("utf-8").splitlines()
72-
smiles = [line.strip() for line in output_lines]
73-
return smiles
52+
stdout_text = stdout.decode("utf-8").strip()
53+
stderr_text = stderr.decode("utf-8").strip()
54+
55+
# Surge outputs to stderr, so check both stdout and stderr
56+
output = stdout_text if stdout_text else stderr_text
57+
58+
if not output:
59+
raise Exception(
60+
f"No output from surge command. Return code: {process.returncode}"
61+
)
62+
63+
# Parse the output to find the line with structure count
64+
# Pattern: ">Z generated X -> Y -> Z in N.NN sec"
65+
pattern = r">Z generated \d+ -> \d+ -> (\d+) in [\d\.]+ sec"
66+
match = re.search(pattern, output)
67+
68+
if match:
69+
structure_count = int(match.group(1))
70+
return structure_count
7471
else:
7572
raise Exception(
76-
f"Error running surge: {stderr.decode('utf-8')}",
73+
f"Could not parse structure count from surge output. Output was: '{output}'"
7774
)
78-
except Exception:
79-
raise Exception(f"Error running surge: {stderr.decode('utf-8')}")
75+
76+
except Exception as e:
77+
raise Exception(f"Error running surge: {str(e)}")
8078
else:
79+
raise Exception(
80+
f"Molecular formula {molecular_formula} has more than 10 heavy atoms"
81+
)
82+
83+
84+
def generate_structures_SURGE(molecular_formula: str) -> Union[dict, str]:
85+
"""Generate chemical structures using the surge tool based on the canonical.
86+
87+
generation path method.
88+
89+
Args:
90+
molecular_formula (str): Molecular formula provided by the user.
91+
92+
Returns:
93+
dict: Dictionary containing:
94+
- total_count: Total number of possible structures
95+
- generated_count: Number of structures actually generated
96+
- structures: List of SMILES strings (limited to 1000)
97+
- settings: Dictionary describing the surge settings used
98+
- formula: The input molecular formula
99+
- limit_applied: Whether a limit was applied to results
100+
str: Error message if molecular formula contains more than 10 heavy atoms.
101+
"""
102+
103+
if get_heavy_atom_count(molecular_formula) > 10:
81104
return "The molecular formula contains more heavy atoms than allowed (10 Heavy Atoms max)."
105+
106+
# Surge command settings
107+
surge_args = [
108+
"-P", # Require planarity
109+
"-T", # Disallow triple bonds
110+
"-B1,2,3,4,5,7,9", # Avoid various substructures
111+
"-t0", # Limit rings of length 3
112+
"-f0", # Limit cycles of length 4
113+
]
114+
115+
settings_description = {
116+
"-P": "Require planarity",
117+
"-T": "Disallow triple bonds",
118+
"-B1,2,3,4,5,7,9": "Avoid substructures: no triple bonds in small rings, Bredt's rule violations, cumulative double bonds, forbidden topologies",
119+
"-t0": "No rings of length 3 allowed",
120+
"-f0": "No cycles of length 4 allowed",
121+
"-S": "Output in SMILES format",
122+
}
123+
124+
try:
125+
# First, get the total count
126+
total_count = get_surge_count(molecular_formula)
127+
128+
# Then generate structures (limited to first 1000)
129+
process = Popen(
130+
["surge"] + surge_args + ["-S", molecular_formula],
131+
stdout=PIPE,
132+
stderr=PIPE,
133+
)
134+
stdout, stderr = process.communicate()
135+
136+
if process.returncode == 0:
137+
output_lines = stdout.decode("utf-8").splitlines()
138+
smiles = [line.strip() for line in output_lines if line.strip()]
139+
140+
# Limit to first 1000 structures
141+
limited_smiles = smiles[:1000]
142+
143+
return {
144+
"total_count": total_count,
145+
"generated_count": len(limited_smiles),
146+
"structures": limited_smiles,
147+
"settings": settings_description,
148+
"formula": molecular_formula,
149+
"limit_applied": len(smiles) > 1000,
150+
}
151+
else:
152+
raise Exception(f"Error running surge: {stderr.decode('utf-8')}")
153+
except Exception as e:
154+
raise Exception(f"Error running surge: {str(e)}")

app/routers/tools.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,13 @@ async def generate_structures(
9494
- **Molecular_Formula**: required (str): The molecular formula of the compound.
9595
9696
Returns:
97-
- List[str]: A list of generated structures.
97+
- Dict: A dictionary containing structure generation results with:
98+
- total_count: Total number of possible structures
99+
- generated_count: Number of structures actually generated
100+
- structures: List of SMILES strings (limited to 1000)
101+
- settings: Dictionary describing the surge settings used
102+
- formula: The input molecular formula
103+
- limit_applied: Whether a limit was applied to results
98104
99105
Raises:
100106
- HTTPException: If there was an error generating the structures.
@@ -104,11 +110,18 @@ async def generate_structures(
104110
105111
Note:
106112
- The maximum allowable count of heavy atoms is restricted to 10 to mitigate excessive utilization of this service.
113+
- Results are limited to the first 1000 structures when the total count exceeds this limit.
107114
"""
108115
try:
109-
structures = generate_structures_SURGE(molecular_formula)
110-
if structures:
111-
return structures
116+
result = generate_structures_SURGE(molecular_formula)
117+
if isinstance(result, str):
118+
# Error message returned
119+
raise HTTPException(status_code=400, detail=result)
120+
else:
121+
# Success - return the structured response
122+
return GenerateStructuresResponse(message="Success", output=result)
123+
except HTTPException:
124+
raise # Re-raise HTTP exceptions
112125
except Exception as e:
113126
raise HTTPException(status_code=500, detail=str(e))
114127

app/schemas/tools_schema.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22

3-
from typing import List
3+
from typing import List, Dict, Union
44

55
from pydantic import BaseModel
66

@@ -10,11 +10,17 @@ class GenerateStructuresResponse(BaseModel):
1010
1111
Attributes:
1212
message (str): A message indicating the success status (default: "Success").
13-
output (List[str]): A list of generated structures.
13+
output (Dict): A dictionary containing structure generation results with:
14+
- total_count: Total number of possible structures
15+
- generated_count: Number of structures actually generated
16+
- structures: List of SMILES strings (limited)
17+
- settings: Dictionary describing the surge settings used
18+
- formula: The input molecular formula
19+
- limit_applied: Whether a limit was applied to results
1420
"""
1521

1622
message: str = "Success"
17-
output: List[str]
23+
output: Dict[str, Union[int, List[str], Dict[str, str], str, bool]]
1824

1925
class Config:
2026
"""Pydantic model configuration.
@@ -26,9 +32,22 @@ class Config:
2632
json_schema_extra = {
2733
"examples": [
2834
{
29-
"input": "C4H8",
3035
"message": "Success",
31-
"output": ["CC(C)C", "CCCC"],
36+
"output": {
37+
"total_count": 24000,
38+
"generated_count": 1000,
39+
"structures": ["CC(C)C", "CCCC"],
40+
"settings": {
41+
"-P": "Require planarity",
42+
"-T": "Disallow triple bonds",
43+
"-B1,2,3,4,5,7,9": "Avoid substructures: no triple bonds in small rings, Bredt's rule violations, cumulative double bonds, forbidden topologies",
44+
"-t0": "No rings of length 3 allowed",
45+
"-f0": "No cycles of length 4 allowed",
46+
"-S": "Output in SMILES format",
47+
},
48+
"formula": "C10H16",
49+
"limit_applied": True,
50+
},
3251
},
3352
],
3453
}

frontend/src/App.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ const NotFoundPage = () => (
5151
const router = createBrowserRouter(
5252
createRoutesFromElements(
5353
<Route path="/" element={<Layout />}>
54-
<Route index element={<HomePage />} />
54+
<Route index element={<DepictPage />} />
55+
<Route path="/home" element={<HomePage />} />
5556
<Route path="/chem" element={<ChemPage />} />
5657
<Route path="/convert" element={<ConvertPage />} />
5758
<Route path="/depict" element={<DepictPage />} />

frontend/src/components/common/Navigation.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const StrobingIcon = ({ icon: Icon, className }) => {
2222
// Navigation links configuration
2323
const navLinks = [
2424
{
25-
to: '/',
25+
to: '/home',
2626
label: 'Home',
2727
icon: HiOutlineHome,
2828
exact: true,

0 commit comments

Comments
 (0)