From 762e068041e0d870063ad1c17fc9cdd6ef413602 Mon Sep 17 00:00:00 2001 From: DJ Leamen Date: Thu, 18 Jun 2026 19:02:57 -0400 Subject: [PATCH] Replace lookup-table parser with PubChem structure parsing - Replace hardcoded molecule parsing tables with PubChem CID and record-based parsing\n- Use real atom/bond coordinates from PubChem records with explicit parse errors\n- Keep a small offline fallback set for common molecules\n- Fix SMILES lookup and parser cache key handling to preserve case sensitivity\n- Update README to document the new parsing approach and limitations\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 33 +- src/utils/identifierLookup.ts | 16 +- src/utils/moleculeParser.ts | 1744 ++++++--------------------------- 3 files changed, 345 insertions(+), 1448 deletions(-) diff --git a/README.md b/README.md index 570edbe..9ff0762 100644 --- a/README.md +++ b/README.md @@ -85,10 +85,10 @@ InputForm → identifierLookup → Local DB → PubChem API → moleculeParser - **Confidence Scoring**: Quality assessment based on data completeness #### **2. Molecular Structure Parser (`moleculeParser.ts`)** -- **SMILES Parser**: Pattern matching for common molecular structures -- **3D Coordinate Generation**: Hand-crafted coordinates for accurate visualization -- **Fallback System**: Multiple parsing strategies for unknown compounds -- **Structure Library**: 70+ pre-defined molecular structures +- **PubChem-Backed Parsing**: Resolves identifiers to PubChem CIDs, then loads real structure records +- **3D-First Coordinates**: Uses PubChem 3D conformers when available, with 2D coordinate fallback +- **Cross-Identifier Resolution**: Supports SMILES, InChI, IUPAC names, CAS numbers, and more through CID lookup +- **Explicit Failure Behavior**: Throws parse errors instead of silently substituting an unrelated molecule #### **3. 3D Rendering Engine (`Molecule3DComponent.tsx`)** - **Atomic Representation**: Spheres with CPK coloring and van der Waals radii @@ -145,11 +145,10 @@ npm run preview ## 🎯 Current Capabilities -### **Molecule Library** (70+ Structures) -- **Simple Molecules**: Water, methane, ethanol, benzene -- **Pharmaceuticals**: Caffeine, aspirin, glucose -- **Organic Compounds**: Alkanes, alkenes, alcohols, acids -- **Complex Structures**: Purine derivatives, sugar molecules +### **Structure Coverage** +- **Database-Driven**: Renders any molecule that PubChem can resolve with coordinate data +- **Large Coverage**: Handles a far broader set of molecules than fixed hand-coded tables +- **Offline Safety Net**: Keeps a small built-in fallback set for common molecules when network lookup fails ### **Database Integration** - **Local Database**: 50+ common compounds for instant lookup @@ -160,15 +159,11 @@ npm run preview ## ⚠️ Current Issues & Limitations ### **Known Issues** -1. **Limited SMILES Parser**: Only supports ~20 common SMILES patterns - - Complex molecules default to fallback structures - - Stereochemistry information is not preserved +1. **Network Dependency**: External structure retrieval requires PubChem availability + - Some identifiers may resolve to compounds that do not have coordinate records + - Offline fallback intentionally covers only common molecules -2. **Static 3D Coordinates**: Most structures use hand-crafted coordinates - - No automatic 3D structure generation from SMILES - - Some molecules may have suboptimal geometries - -3. **API Rate Limiting**: No rate limiting implemented for external APIs +2. **API Rate Limiting**: No rate limiting implemented for external APIs - Potential for hitting PubChem/NCI rate limits - No retry strategies for failed requests @@ -189,8 +184,8 @@ npm run preview ## 🚧 Future Enhancements ### **High Priority** -- [ ] **Advanced SMILES Parser**: Implement comprehensive SMILES parsing library -- [ ] **Automatic 3D Generation**: Integrate RDKit or similar for structure generation +- [ ] **Local Structure Generation**: Add openchemlib or RDKit-JS for fully offline parsing/generation +- [ ] **Coordinate Robustness**: Add alternate conformer sources when PubChem lacks 3D records - [ ] **Search History**: Local storage for recently viewed molecules - [ ] **Export Features**: PNG/SVG export, 3D model download - [ ] **Performance Optimization**: Level-of-detail rendering for large molecules diff --git a/src/utils/identifierLookup.ts b/src/utils/identifierLookup.ts index 3c3e1c6..461c3d2 100644 --- a/src/utils/identifierLookup.ts +++ b/src/utils/identifierLookup.ts @@ -570,7 +570,9 @@ export function getLocalIdentifiers( sourceType: keyof ChemicalIdentifiers, sourceValue: string ): Partial | null { - const lowerValue = sourceValue.toLowerCase().trim(); + const trimmedValue = sourceValue.trim(); + const lowerValue = trimmedValue.toLowerCase(); + const lookupValue = sourceType === 'smiles' ? trimmedValue : lowerValue; // Comprehensive database of common compounds const commonCompounds: { [key: string]: Partial } = { @@ -1080,7 +1082,7 @@ export function getLocalIdentifiers( }; // Try direct lookup first - const compound = commonCompounds[lowerValue]; + const compound = commonCompounds[lookupValue]; if (compound) { return compound; } @@ -1088,7 +1090,15 @@ export function getLocalIdentifiers( // Try to find by any matching identifier field for (const [, identifiers] of Object.entries(commonCompounds)) { for (const [idType, idValue] of Object.entries(identifiers)) { - if (idType === sourceType && idValue?.toLowerCase() === lowerValue) { + if (idType !== sourceType) { + continue; + } + + if (sourceType === 'smiles') { + if (idValue?.trim() === trimmedValue) { + return identifiers; + } + } else if (idValue?.toLowerCase() === lowerValue) { return identifiers; } } diff --git a/src/utils/moleculeParser.ts b/src/utils/moleculeParser.ts index 12666f8..2d3f6af 100644 --- a/src/utils/moleculeParser.ts +++ b/src/utils/moleculeParser.ts @@ -1,492 +1,341 @@ -import { ChemicalIdentifiers, Molecule3D, Atom, Bond } from '../types/molecule'; +import { ChemicalIdentifiers, Molecule3D } from '../types/molecule'; + +type IdentifierInput = { + type: keyof ChemicalIdentifiers; + value: string; +}; + +type PubChemCompound = { + atoms?: { + aid?: number[]; + element?: number[]; + }; + bonds?: { + aid1?: number[]; + aid2?: number[]; + order?: number[]; + }; + coords?: Array<{ + aid?: number[]; + conformers?: Array<{ + x?: number[]; + y?: number[]; + z?: number[]; + }>; + }>; +}; + +const PUBCHEM_BASE_URL = 'https://pubchem.ncbi.nlm.nih.gov/rest/pug'; +const parseCache = new Map>(); + +const COMMON_OFFLINE_FALLBACKS: Record = { + 'smiles:o': createWater(), + 'smiles:c': createMethane(), + 'smiles:n': createAmmonia(), + 'smiles:o=c=o': createCarbonDioxide(), + 'smiles:c1ccccc1': createBenzene(), + 'smiles:cco': createEthanol(), + 'iupacname:water': createWater(), + 'iupacname:methane': createMethane(), + 'iupacname:ammonia': createAmmonia(), + 'iupacname:carbon dioxide': createCarbonDioxide(), + 'iupacname:benzene': createBenzene(), + 'iupacname:ethanol': createEthanol(), + 'casnumber:7732-18-5': createWater(), + 'casnumber:74-82-8': createMethane(), + 'casnumber:7664-41-7': createAmmonia(), + 'casnumber:124-38-9': createCarbonDioxide(), + 'casnumber:71-43-2': createBenzene(), + 'casnumber:64-17-5': createEthanol() +}; + +const ATOMIC_NUMBER_TO_SYMBOL = new Map([ + [1, 'H'], [2, 'He'], [3, 'Li'], [4, 'Be'], [5, 'B'], [6, 'C'], [7, 'N'], [8, 'O'], [9, 'F'], [10, 'Ne'], + [11, 'Na'], [12, 'Mg'], [13, 'Al'], [14, 'Si'], [15, 'P'], [16, 'S'], [17, 'Cl'], [18, 'Ar'], [19, 'K'], [20, 'Ca'], + [21, 'Sc'], [22, 'Ti'], [23, 'V'], [24, 'Cr'], [25, 'Mn'], [26, 'Fe'], [27, 'Co'], [28, 'Ni'], [29, 'Cu'], [30, 'Zn'], + [31, 'Ga'], [32, 'Ge'], [33, 'As'], [34, 'Se'], [35, 'Br'], [36, 'Kr'], [37, 'Rb'], [38, 'Sr'], [39, 'Y'], [40, 'Zr'], + [41, 'Nb'], [42, 'Mo'], [43, 'Tc'], [44, 'Ru'], [45, 'Rh'], [46, 'Pd'], [47, 'Ag'], [48, 'Cd'], [49, 'In'], [50, 'Sn'], + [51, 'Sb'], [52, 'Te'], [53, 'I'], [54, 'Xe'], [55, 'Cs'], [56, 'Ba'], [57, 'La'], [58, 'Ce'], [59, 'Pr'], [60, 'Nd'], + [61, 'Pm'], [62, 'Sm'], [63, 'Eu'], [64, 'Gd'], [65, 'Tb'], [66, 'Dy'], [67, 'Ho'], [68, 'Er'], [69, 'Tm'], [70, 'Yb'], + [71, 'Lu'], [72, 'Hf'], [73, 'Ta'], [74, 'W'], [75, 'Re'], [76, 'Os'], [77, 'Ir'], [78, 'Pt'], [79, 'Au'], [80, 'Hg'], + [81, 'Tl'], [82, 'Pb'], [83, 'Bi'], [84, 'Po'], [85, 'At'], [86, 'Rn'], [87, 'Fr'], [88, 'Ra'], [89, 'Ac'], [90, 'Th'], + [91, 'Pa'], [92, 'U'], [93, 'Np'], [94, 'Pu'], [95, 'Am'], [96, 'Cm'], [97, 'Bk'], [98, 'Cf'], [99, 'Es'], [100, 'Fm'], + [101, 'Md'], [102, 'No'], [103, 'Lr'], [104, 'Rf'], [105, 'Db'], [106, 'Sg'], [107, 'Bh'], [108, 'Hs'], [109, 'Mt'], + [110, 'Ds'], [111, 'Rg'], [112, 'Cn'], [113, 'Nh'], [114, 'Fl'], [115, 'Mc'], [116, 'Lv'], [117, 'Ts'], [118, 'Og'] +]); -// Multi-format molecule parser with support for various chemical identifiers -// This is a simplified implementation - in production you'd want to use RDKit or similar export async function parseMolecule( type: keyof ChemicalIdentifiers, value: string, allIdentifiers?: Partial ): Promise { const cleanValue = value.trim(); - if (!cleanValue) { throw new Error('Empty identifier provided'); } - try { - switch (type) { - case 'smiles': - return parseSMILES(cleanValue); - - case 'iupacName': - return await parseIUPACName(cleanValue, allIdentifiers); - - case 'casNumber': - return parseCASNumber(cleanValue, allIdentifiers); - - case 'pubchemCID': - return parsePubChemCID(cleanValue, allIdentifiers); - - case 'inchi': - return parseInChI(cleanValue); - - case 'chemSpider': - return parseChemSpiderID(cleanValue, allIdentifiers); - - case 'unii': - return parseUNII(cleanValue, allIdentifiers); - - case 'ecNumber': - case 'eNumber': - case 'echaInfoCard': - case 'rtecsNumber': - case 'compToxDashboard': - return parseOtherIdentifiers(type, cleanValue, allIdentifiers); - - default: - throw new Error(`Parsing for ${type} is not yet implemented`); - } - } catch (error) { - // If primary parsing fails, try fallback using SMILES if available - if (allIdentifiers?.smiles && type !== 'smiles') { - console.warn(`Primary parsing failed for ${type}, falling back to SMILES:`, error); - return parseSMILES(allIdentifiers.smiles); + const candidates = buildCandidateInputs(type, cleanValue, allIdentifiers); + const failureMessages: string[] = []; + + for (const candidate of candidates) { + try { + return await parseCandidate(candidate); + } catch (error) { + failureMessages.push(error instanceof Error ? error.message : String(error)); } - throw error; } -} -// Helper function for partial name matching -function tryPartialMatches(lowerName: string): Molecule3D | null { - if (lowerName.includes('methyl') && lowerName.includes('benzene')) { - return createToluene(); - } - if (lowerName.includes('alcohol') || lowerName.includes('ol')) { - return createEthanol(); // Default alcohol + const offlineFallback = findOfflineFallback(candidates); + if (offlineFallback) { + return offlineFallback; } - if (lowerName.includes('acid')) { - return createAceticAcid(); // Default acid - } - return null; -} -// Helper function for alkene pattern matching -function tryMatchAlkene(lowerName: string): Molecule3D | null { - if (!lowerName.includes('ene')) return null; - - if (lowerName.includes('hex')) return createHexene(); - if (lowerName.includes('but')) return createButene(); - if (lowerName.includes('prop')) return createPropene(); - if (lowerName.includes('eth')) return createEthene(); - return createEthene(); // Default alkene -} - -// Helper function for alkane pattern matching -function tryMatchAlkane(lowerName: string): Molecule3D | null { - if (!lowerName.includes('ane')) return null; - - if (lowerName.includes('hex')) return createHexane(); - if (lowerName.includes('pent')) return createPentane(); - if (lowerName.includes('but')) return createButane(); - if (lowerName.includes('prop')) return createPropane(); - if (lowerName.includes('eth')) return createEthane(); - if (lowerName.includes('meth')) return createMethane(); - return null; + throw new Error( + `Unable to parse molecule "${cleanValue}". No structure data was found for the provided identifiers.` + + (failureMessages.length > 0 ? ` Last error: ${failureMessages[0]}` : '') + ); } -// IUPAC Name parser - converts common IUPAC names to 3D structures -async function parseIUPACName(iupacName: string, _allIdentifiers?: Partial): Promise { - const lowerName = iupacName.toLowerCase().trim(); - - const nameToMolecule: { [key: string]: () => Molecule3D } = { - // Basic alkanes - 'methane': createMethane, - 'ethane': createEthane, - 'propane': createPropane, - 'butane': createButane, - 'pentane': createPentane, - 'hexane': createHexane, - - // Alkenes - 'ethene': createEthene, - 'ethylene': createEthene, - 'propene': createPropene, - 'propylene': createPropene, - '1-butene': createButene, - '2-butene': createButene, - 'butene': createButene, - '1-hexene': createHexene, - '2-hexene': createHexene, - 'hexene': createHexene, - - // Branched alkanes - 'isobutane': createIsobutane, - '2-methylpropane': createIsobutane, - 'isopentane': createIsopentane, - '2-methylbutane': createIsopentane, - - // Alcohols - 'methanol': createMethanol, - 'ethanol': createEthanol, - 'propanol': createPropanol, - '1-propanol': createPropanol, - '2-propanol': createIsopropanol, - 'isopropanol': createIsopropanol, - - // Aromatic compounds - 'benzene': createBenzene, - 'toluene': createToluene, - 'methylbenzene': createToluene, - 'xylene': createXylene, - '1,2-dimethylbenzene': createXylene, - - // Acids - 'acetic acid': createAceticAcid, - 'ethanoic acid': createAceticAcid, - 'formic acid': createFormicAcid, - 'methanoic acid': createFormicAcid, - - // Simple inorganics - 'water': createWater, - 'carbon dioxide': createCarbonDioxide, - 'ammonia': createAmmonia, - 'hydrogen': createHydrogen, - 'oxygen': createOxygen, - 'nitrogen': createNitrogen, - }; - - const moleculeBuilder = nameToMolecule[lowerName]; - if (moleculeBuilder) { - return moleculeBuilder(); - } +function buildCandidateInputs( + type: keyof ChemicalIdentifiers, + value: string, + allIdentifiers?: Partial +): IdentifierInput[] { + const primary: IdentifierInput = { type, value }; + const preferredOrder: Array = [ + 'pubchemCID', + 'smiles', + 'inchi', + 'iupacName', + 'casNumber', + 'chemSpider', + 'unii', + 'ecNumber', + 'eNumber', + 'echaInfoCard', + 'rtecsNumber', + 'compToxDashboard' + ]; - // Try to get SMILES from PubChem for complex molecules - try { - const smiles = await getSmilesFromIUPAC(iupacName); - if (smiles) { - return parseSMILES(smiles); + const fromIdentifiers = preferredOrder + .map((identifierType) => ({ + type: identifierType, + value: allIdentifiers?.[identifierType]?.trim() ?? '' + })) + .filter((entry) => entry.value.length > 0); + + const unique = new Map(); + for (const entry of [primary, ...fromIdentifiers]) { + const key = getIdentifierKey(entry.type, entry.value); + if (!unique.has(key)) { + unique.set(key, entry); } - } catch (error) { - console.warn('Failed to lookup SMILES from PubChem:', error); } - // Try partial matches for common patterns - const partialMatch = tryPartialMatches(lowerName); - if (partialMatch) return partialMatch; - - // Pattern matching for alkenes - const alkeneMatch = tryMatchAlkene(lowerName); - if (alkeneMatch) return alkeneMatch; - - // Pattern matching for alkanes - const alkaneMatch = tryMatchAlkane(lowerName); - if (alkaneMatch) return alkaneMatch; + return [...unique.values()]; +} - // For very complex molecules, return a placeholder - if (lowerName.length > 20 || lowerName.includes('lambda') || lowerName.includes('phosphane')) { - return createComplexMoleculePlaceholder(); +async function parseCandidate(candidate: IdentifierInput): Promise { + const cacheKey = getIdentifierKey(candidate.type, candidate.value); + const cachedPromise = parseCache.get(cacheKey); + if (cachedPromise) { + return cachedPromise; } - throw new Error(`Unable to parse molecule "${iupacName}". Please check the spelling or try a different identifier type (SMILES, CAS number, etc.).`); -} + const parsePromise = parseCandidateUncached(candidate); + parseCache.set(cacheKey, parsePromise); -// Helper function to get SMILES from IUPAC name using PubChem -async function getSmilesFromIUPAC(iupacName: string): Promise { try { - const url = `https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/name/${encodeURIComponent(iupacName)}/property/CanonicalSMILES/JSON`; - const response = await fetch(url); - - if (!response.ok) { - return null; - } - - const data = await response.json(); - return data.PropertyTable?.Properties?.[0]?.CanonicalSMILES || null; + return await parsePromise; } catch (error) { - console.error('Error fetching SMILES from PubChem:', error); - return null; + parseCache.delete(cacheKey); + throw error; } } -// CAS Number parser - maps known CAS numbers to structures -function parseCASNumber(casNumber: string, _allIdentifiers?: Partial): Molecule3D { - const cleanCAS = casNumber.replaceAll(' ', ''); - - const casToMolecule: { [key: string]: () => Molecule3D } = { - // Common molecules with their CAS numbers - '74-82-8': createMethane, // Methane - '74-84-0': createEthane, // Ethane - '74-98-6': createPropane, // Propane - '106-97-8': createButane, // Butane - '75-28-5': createIsobutane, // Isobutane - '64-17-5': createEthanol, // Ethanol - '67-56-1': createMethanol, // Methanol - '71-43-2': createBenzene, // Benzene - '108-88-3': createToluene, // Toluene - '64-19-7': createAceticAcid, // Acetic acid - '7732-18-5': createWater, // Water - '124-38-9': createCarbonDioxide, // Carbon dioxide - '7664-41-7': createAmmonia, // Ammonia - '1333-74-0': createHydrogen, // Hydrogen - '7782-44-7': createOxygen, // Oxygen - '7727-37-9': createNitrogen, // Nitrogen - }; - - const moleculeBuilder = casToMolecule[cleanCAS]; - if (moleculeBuilder) { - return moleculeBuilder(); +function getIdentifierKey(type: keyof ChemicalIdentifiers, value: string): string { + const trimmed = value.trim(); + if (type === 'smiles') { + return `${type}:${trimmed}`; } - - throw new Error(`Unknown CAS number: ${casNumber}`); + return `${type}:${trimmed.toLowerCase()}`; } -// PubChem CID parser - maps compound IDs to structures -function parsePubChemCID(cid: string, allIdentifiers?: Partial): Molecule3D { - const cleanCID = cid.trim(); - - const cidToMolecule: { [key: string]: () => Molecule3D } = { - '280': createCarbonDioxide, // CO2 - '962': createWater, // H2O - '6344': createIsobutane, // Isobutane (2-methylpropane) - '702': createEthanol, // Ethanol - '887': createMethanol, // Methanol - '241': createBenzene, // Benzene - '1140': createToluene, // Toluene - '176': createAceticAcid, // Acetic acid - '297': createMethane, // Methane - '6324': createEthane, // Ethane - '6334': createPropane, // Propane - '7843': createButane, // Butane - '222': createAmmonia, // Ammonia - '284': createFormicAcid, // Formic acid - '5793': createGlucose, // D-glucose - '2519': createCaffeine, // Caffeine - '2244': createAspirin, // Aspirin - '634': createRibose, // D-ribose (CID 634) - }; +async function parseCandidateUncached(candidate: IdentifierInput): Promise { + const cid = await resolveCid(candidate); + const compound = await fetchCompoundWithCoordinates(cid); + return compoundToMolecule3D(compound); +} - const moleculeBuilder = cidToMolecule[cleanCID]; - if (moleculeBuilder) { - return moleculeBuilder(); +async function resolveCid(candidate: IdentifierInput): Promise { + if (candidate.type === 'pubchemCID') { + const cid = Number.parseInt(candidate.value, 10); + if (!Number.isNaN(cid) && cid > 0) { + return cid; + } + throw new Error(`Invalid PubChem CID: ${candidate.value}`); } - // If CID not found, try using SMILES from allIdentifiers - if (allIdentifiers?.smiles) { - console.log(`Unknown CID ${cid}, falling back to SMILES: ${allIdentifiers.smiles}`); - return parseSMILES(allIdentifiers.smiles); - } + const encodedValue = encodeURIComponent(candidate.value); + const cidLookupUrls = getCidLookupUrls(candidate.type, encodedValue); - // If no SMILES available, try using InChI - if (allIdentifiers?.inchi) { - console.log(`Unknown CID ${cid}, falling back to InChI: ${allIdentifiers.inchi}`); - return parseInChI(allIdentifiers.inchi); + for (const url of cidLookupUrls) { + try { + const json = await fetchJson(url) as { IdentifierList?: { CID?: number[] } }; + const cid = json.IdentifierList?.CID?.[0]; + if (typeof cid === 'number' && cid > 0) { + return cid; + } + } catch { + // Continue trying the next endpoint. + } } - throw new Error(`Unknown PubChem CID: ${cid}. No SMILES or InChI available for fallback.`); + throw new Error(`No PubChem CID found for ${candidate.type}: ${candidate.value}`); +} + +function getCidLookupUrls(type: keyof ChemicalIdentifiers, encodedValue: string): string[] { + const nameUrl = `${PUBCHEM_BASE_URL}/compound/name/${encodedValue}/cids/JSON`; + + switch (type) { + case 'smiles': + return [ + `${PUBCHEM_BASE_URL}/compound/smiles/${encodedValue}/cids/JSON`, + nameUrl + ]; + case 'inchi': + return [ + `${PUBCHEM_BASE_URL}/compound/inchi/${encodedValue}/cids/JSON`, + nameUrl + ]; + case 'casNumber': + return [ + `${PUBCHEM_BASE_URL}/compound/xref/RN/${encodedValue}/cids/JSON`, + nameUrl + ]; + case 'unii': + return [ + `${PUBCHEM_BASE_URL}/compound/xref/RegistryID/${encodedValue}/cids/JSON`, + nameUrl + ]; + case 'chemSpider': + return [ + `${PUBCHEM_BASE_URL}/compound/xref/ExternalID/${encodedValue}/cids/JSON`, + nameUrl + ]; + default: + return [nameUrl]; + } } -// InChI parser - basic InChI string parsing -function parseInChI(inchi: string): Molecule3D { - const cleanInChI = inchi.trim(); - - // Simple InChI patterns for common molecules - const inchiPatterns: { [key: string]: () => Molecule3D } = { - 'InChI=1S/CH4/h1H4': createMethane, - 'InChI=1S/C2H6/c1-2/h1-2H3': createEthane, - 'InChI=1S/C3H8/c1-2-3/h2H2,1,3H3': createPropane, - 'InChI=1S/C4H10/c1-3-4-2/h3-4H2,1-2H3': createButane, - 'InChI=1S/C2H6O/c1-2-3/h3H,2H2,1H3': createEthanol, - 'InChI=1S/CH4O/c1-2/h2H,1H3': createMethanol, - 'InChI=1S/C6H6/c1-2-4-6-5-3-1/h1-6H': createBenzene, - 'InChI=1S/H2O/h1H2': createWater, - 'InChI=1S/CO2/c2-1-3': createCarbonDioxide, - 'InChI=1S/H3N/h1H3': createAmmonia, - }; - - const moleculeBuilder = inchiPatterns[cleanInChI]; - if (moleculeBuilder) { - return moleculeBuilder(); - } +async function fetchCompoundWithCoordinates(cid: number): Promise { + const urls = [ + `${PUBCHEM_BASE_URL}/compound/cid/${cid}/record/JSON?record_type=3d`, + `${PUBCHEM_BASE_URL}/compound/cid/${cid}/record/JSON` + ]; - // Try to parse molecular formula from InChI - const formulaRegex = /InChI=1S\/([^/]+)/; - const formulaMatch = formulaRegex.exec(cleanInChI); - if (formulaMatch) { - const formula = formulaMatch[1]; - return parseByFormula(formula); + for (const url of urls) { + try { + const json = await fetchJson(url) as { PC_Compounds?: PubChemCompound[] }; + const compound = json.PC_Compounds?.[0]; + if (compound && hasUsableCoordinates(compound)) { + return compound; + } + } catch { + // Continue to the next record type. + } } - throw new Error(`Cannot parse InChI: ${inchi}`); + throw new Error(`No coordinate data found for PubChem CID ${cid}`); } -// ChemSpider ID parser -function parseChemSpiderID(chemSpiderID: string, _allIdentifiers?: Partial): Molecule3D { - const cleanID = chemSpiderID.trim(); - - const chemSpiderToMolecule: { [key: string]: () => Molecule3D } = { - '6091': createMethane, // Methane - '6089': createEthane, // Ethane - '6095': createPropane, // Propane - '6309': createIsobutane, // Isobutane - '682': createEthanol, // Ethanol - '864': createMethanol, // Methanol - '236': createBenzene, // Benzene - '1108': createToluene, // Toluene - '171': createAceticAcid, // Acetic acid - '937': createWater, // Water - '274': createCarbonDioxide, // Carbon dioxide - '217': createAmmonia, // Ammonia - }; - - const moleculeBuilder = chemSpiderToMolecule[cleanID]; - if (moleculeBuilder) { - return moleculeBuilder(); - } - - throw new Error(`Unknown ChemSpider ID: ${chemSpiderID}`); +function hasUsableCoordinates(compound: PubChemCompound): boolean { + const conformer = compound.coords?.find((entry) => entry.conformers?.[0]?.x?.length)?.conformers?.[0]; + return Boolean(conformer?.x?.length && conformer?.y?.length); } -// UNII parser -function parseUNII(unii: string, _allIdentifiers?: Partial): Molecule3D { - const cleanUNII = unii.trim().toUpperCase(); - - const uniiToMolecule: { [key: string]: () => Molecule3D } = { - 'OP0UW79H66': createMethane, // Methane - 'L99N5N533T': createEthane, // Ethane - 'T75W9KEF2D': createPropane, // Propane - 'BF27H04Q8B': createIsobutane, // Isobutane - '3K9958V90M': createEthanol, // Ethanol - 'Y4S76JWI15': createMethanol, // Methanol - 'J64922108F': createBenzene, // Benzene - '3FPU23BG52': createToluene, // Toluene - 'Q40Q9N063P': createAceticAcid, // Acetic acid - '059QF0KO0R': createWater, // Water - '142M471B3J': createCarbonDioxide, // Carbon dioxide - '5138Q19F1X': createAmmonia, // Ammonia - }; +function compoundToMolecule3D(compound: PubChemCompound): Molecule3D { + const atomIds = compound.atoms?.aid ?? []; + const atomicNumbers = compound.atoms?.element ?? []; + const coordinateSet = compound.coords?.find((entry) => entry.conformers?.[0]?.x?.length); + const conformer = coordinateSet?.conformers?.[0]; + const coordinateAtomIds = coordinateSet?.aid ?? atomIds; + const xValues = conformer?.x ?? []; + const yValues = conformer?.y ?? []; + const zValues = conformer?.z ?? []; - const moleculeBuilder = uniiToMolecule[cleanUNII]; - if (moleculeBuilder) { - return moleculeBuilder(); + if (atomIds.length === 0 || atomicNumbers.length !== atomIds.length || xValues.length === 0 || yValues.length === 0) { + throw new Error('PubChem structure payload is missing required atom or coordinate data'); } - throw new Error(`Unknown UNII: ${unii}`); -} + const coordinateIndexByAid = new Map(); + coordinateAtomIds.forEach((aid, index) => { + coordinateIndexByAid.set(aid, index); + }); -// Parser for other identifier types -function parseOtherIdentifiers(type: keyof ChemicalIdentifiers, value: string, _allIdentifiers?: Partial): Molecule3D { - // For demonstration, we'll map some common values for each identifier type - - if (type === 'ecNumber') { - const ecToMolecule: { [key: string]: () => Molecule3D } = { - '200-812-7': createMethane, - '200-816-9': createEthane, - '200-827-9': createPropane, - '200-857-2': createIsobutane, - '200-578-6': createEthanol, - '200-659-6': createMethanol, - '200-753-7': createBenzene, + const atoms = atomIds.map((aid, index) => { + const coordinateIndex = coordinateIndexByAid.get(aid) ?? index; + const atomicNumber = atomicNumbers[index]; + return { + id: index, + element: toElementSymbol(atomicNumber), + x: xValues[coordinateIndex] ?? 0, + y: yValues[coordinateIndex] ?? 0, + z: zValues[coordinateIndex] ?? 0 }; - - const builder = ecToMolecule[value.trim()]; - if (builder) return builder(); - } - - if (type === 'eNumber') { - const eToMolecule: { [key: string]: () => Molecule3D } = { - 'E290': createCarbonDioxide, - 'E948': createOxygen, - 'E941': createNitrogen, - }; - - const builder = eToMolecule[value.trim().toUpperCase()]; - if (builder) return builder(); - } + }); - // For other types, return a default molecule - console.warn(`Limited support for ${type}, returning default molecule`); - return createMethane(); -} + const aidToAtomIndex = new Map(); + atomIds.forEach((aid, index) => { + aidToAtomIndex.set(aid, index); + }); -// Helper function to parse by molecular formula -function parseByFormula(formula: string): Molecule3D { - const formulaToMolecule: { [key: string]: () => Molecule3D } = { - 'CH4': createMethane, - 'C2H6': createEthane, - 'C3H8': createPropane, - 'C4H10': createButane, - 'C2H6O': createEthanol, - 'CH4O': createMethanol, - 'C6H6': createBenzene, - 'C7H8': createToluene, - 'C2H4O2': createAceticAcid, - 'H2O': createWater, - 'CO2': createCarbonDioxide, - 'NH3': createAmmonia, - 'H2': createHydrogen, - 'O2': createOxygen, - 'N2': createNitrogen, - }; + const bondAid1 = compound.bonds?.aid1 ?? []; + const bondAid2 = compound.bonds?.aid2 ?? []; + const bondOrders = compound.bonds?.order ?? []; + const bonds = bondAid1.map((aid1, index) => { + const aid2 = bondAid2[index]; + const atomIndex1 = aidToAtomIndex.get(aid1); + const atomIndex2 = aidToAtomIndex.get(aid2); - const moleculeBuilder = formulaToMolecule[formula]; - if (moleculeBuilder) { - return moleculeBuilder(); - } + if (atomIndex1 === undefined || atomIndex2 === undefined) { + return null; + } + + const order = bondOrders[index] ?? 1; + return { + atomIndex1, + atomIndex2, + bondType: order === 2 || order === 3 ? order : 1 + }; + }).filter((bond): bond is NonNullable => bond !== null); - // Default fallback - return createMethane(); + return { atoms, bonds }; } -// Very simplified SMILES parser for demonstration -function parseSMILES(smiles: string): Molecule3D { - // Clean the SMILES string - preserve case for complex molecules - const cleanSmiles = smiles.trim(); - - // Simple patterns for common molecules - const patterns: { [key: string]: Molecule3D } = { - 'CCO': createEthanol(), - 'C1CCCCC1': createBenzene(), - 'CC(=O)O': createAceticAcid(), - 'C(=O)O': createFormicAcid(), - 'O=C=O': createCarbonDioxide(), - 'O': createWater(), - 'CC': createEthane(), - 'CCC': createPropane(), - 'CCCC': createButane(), - 'C(C)(C)C': createIsobutane(), - 'CC(C)CC': createIsopentane(), - 'CO': createMethanol(), - 'CCL4': createCarbonTetrachloride(), - 'N': createAmmonia(), - 'C': createMethane(), - // Complex molecules - 'CN1C=NC2=C1C(=O)N(C(=O)N2C)C': createCaffeineFromSMILES(), - 'CC(=O)OC1=CC=CC=C1C(=O)O': createAspirinFromSMILES(), - 'C([C@@H]1[C@H]([C@@H]([C@H]([C@H](O1)O)O)O)O)O': createGlucoseFromSMILES(), - }; +function toElementSymbol(atomicNumber: number): string { + return ATOMIC_NUMBER_TO_SYMBOL.get(atomicNumber) ?? 'X'; +} - // Check for exact matches first (case-sensitive for complex molecules) - if (patterns[cleanSmiles]) { - return patterns[cleanSmiles]; - } - - // Try uppercase patterns for simple molecules - const upperSmiles = cleanSmiles.toUpperCase(); - if (patterns[upperSmiles]) { - return patterns[upperSmiles]; +async function fetchJson(url: string): Promise { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`PubChem request failed (${response.status})`); } + return response.json(); +} - // If no pattern match, try to parse simple alkanes - if (/^C+$/i.test(cleanSmiles)) { - return createSimpleAlkane(cleanSmiles.length); +function findOfflineFallback(candidates: IdentifierInput[]): Molecule3D | null { + for (const candidate of candidates) { + const key = `${candidate.type}:${candidate.value.trim().toLowerCase()}`; + const fallback = COMMON_OFFLINE_FALLBACKS[key]; + if (fallback) { + return fallback; + } } - - // Default fallback - create a simple methane molecule - return createMethane(); + return null; } -// Helper functions to create 3D coordinates for common molecules - function createMethane(): Molecule3D { return { atoms: [ @@ -494,114 +343,13 @@ function createMethane(): Molecule3D { { id: 1, element: 'H', x: 1.09, y: 0, z: 0 }, { id: 2, element: 'H', x: -0.36, y: 1.03, z: 0 }, { id: 3, element: 'H', x: -0.36, y: -0.51, z: 0.89 }, - { id: 4, element: 'H', x: -0.36, y: -0.51, z: -0.89 }, + { id: 4, element: 'H', x: -0.36, y: -0.51, z: -0.89 } ], bonds: [ { atomIndex1: 0, atomIndex2: 1, bondType: 1 }, { atomIndex1: 0, atomIndex2: 2, bondType: 1 }, { atomIndex1: 0, atomIndex2: 3, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 4, bondType: 1 }, - ] - }; -} - -function createEthanol(): Molecule3D { - return { - atoms: [ - { id: 0, element: 'C', x: -1.2, y: 0, z: 0 }, - { id: 1, element: 'C', x: 0, y: 0, z: 0 }, - { id: 2, element: 'O', x: 1.4, y: 0, z: 0 }, - { id: 3, element: 'H', x: -1.6, y: 1.03, z: 0 }, - { id: 4, element: 'H', x: -1.6, y: -0.51, z: 0.89 }, - { id: 5, element: 'H', x: -1.6, y: -0.51, z: -0.89 }, - { id: 6, element: 'H', x: 0, y: 1.03, z: 0 }, - { id: 7, element: 'H', x: 0, y: -0.51, z: 0.89 }, - { id: 8, element: 'H', x: 1.8, y: 0, z: 0 }, - ], - bonds: [ - { atomIndex1: 0, atomIndex2: 1, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 2, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 3, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 4, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 5, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 6, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 7, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 8, bondType: 1 }, - ] - }; -} - -function createBenzene(): Molecule3D { - const radius = 1.4; - const atoms: Atom[] = []; - const bonds: Bond[] = []; - - // Create hexagonal ring - for (let i = 0; i < 6; i++) { - const angle = (i * Math.PI) / 3; - // Add carbon and hydrogen atoms together - atoms.push( - { - id: i, - element: 'C', - x: radius * Math.cos(angle), - y: radius * Math.sin(angle), - z: 0 - }, - { - id: i + 6, - element: 'H', - x: (radius + 1.1) * Math.cos(angle), - y: (radius + 1.1) * Math.sin(angle), - z: 0 - } - ); - - // Add C-H bond and C-C bond together - const nextCarbon = (i + 1) % 6; - bonds.push( - { atomIndex1: i, atomIndex2: i + 6, bondType: 1 }, - { atomIndex1: i, atomIndex2: nextCarbon, bondType: i % 2 === 0 ? 2 : 1 } - ); - } - - return { atoms, bonds }; -} - -function createAceticAcid(): Molecule3D { - return { - atoms: [ - { id: 0, element: 'C', x: -1.3, y: 0, z: 0 }, - { id: 1, element: 'C', x: 0, y: 0, z: 0 }, - { id: 2, element: 'O', x: 0.7, y: 1.1, z: 0 }, - { id: 3, element: 'O', x: 0.7, y: -1.1, z: 0 }, - { id: 4, element: 'H', x: -1.7, y: 1.03, z: 0 }, - { id: 5, element: 'H', x: -1.7, y: -0.51, z: 0.89 }, - { id: 6, element: 'H', x: -1.7, y: -0.51, z: -0.89 }, - { id: 7, element: 'H', x: 1.6, y: -1.1, z: 0 }, - ], - bonds: [ - { atomIndex1: 0, atomIndex2: 1, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 2, bondType: 2 }, - { atomIndex1: 1, atomIndex2: 3, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 4, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 5, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 6, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 7, bondType: 1 }, - ] - }; -} - -function createCarbonDioxide(): Molecule3D { - return { - atoms: [ - { id: 0, element: 'O', x: -1.2, y: 0, z: 0 }, - { id: 1, element: 'C', x: 0, y: 0, z: 0 }, - { id: 2, element: 'O', x: 1.2, y: 0, z: 0 }, - ], - bonds: [ - { atomIndex1: 0, atomIndex2: 1, bondType: 2 }, - { atomIndex1: 1, atomIndex2: 2, bondType: 2 }, + { atomIndex1: 0, atomIndex2: 4, bondType: 1 } ] }; } @@ -610,948 +358,92 @@ function createWater(): Molecule3D { return { atoms: [ { id: 0, element: 'O', x: 0, y: 0, z: 0 }, - { id: 1, element: 'H', x: 0.96, y: 0, z: 0 }, - { id: 2, element: 'H', x: -0.24, y: 0.93, z: 0 }, + { id: 1, element: 'H', x: 0.76, y: 0.59, z: 0 }, + { id: 2, element: 'H', x: -0.76, y: 0.59, z: 0 } ], bonds: [ { atomIndex1: 0, atomIndex2: 1, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 2, bondType: 1 }, + { atomIndex1: 0, atomIndex2: 2, bondType: 1 } ] }; } -function createEthane(): Molecule3D { +function createAmmonia(): Molecule3D { return { atoms: [ - { id: 0, element: 'C', x: -0.75, y: 0, z: 0 }, - { id: 1, element: 'C', x: 0.75, y: 0, z: 0 }, - { id: 2, element: 'H', x: -1.15, y: 1.03, z: 0 }, - { id: 3, element: 'H', x: -1.15, y: -0.51, z: 0.89 }, - { id: 4, element: 'H', x: -1.15, y: -0.51, z: -0.89 }, - { id: 5, element: 'H', x: 1.15, y: 1.03, z: 0 }, - { id: 6, element: 'H', x: 1.15, y: -0.51, z: 0.89 }, - { id: 7, element: 'H', x: 1.15, y: -0.51, z: -0.89 }, + { id: 0, element: 'N', x: 0, y: 0, z: 0.2 }, + { id: 1, element: 'H', x: 0.94, y: 0, z: -0.2 }, + { id: 2, element: 'H', x: -0.47, y: 0.81, z: -0.2 }, + { id: 3, element: 'H', x: -0.47, y: -0.81, z: -0.2 } ], bonds: [ { atomIndex1: 0, atomIndex2: 1, bondType: 1 }, { atomIndex1: 0, atomIndex2: 2, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 3, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 4, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 5, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 6, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 7, bondType: 1 }, + { atomIndex1: 0, atomIndex2: 3, bondType: 1 } ] }; } -function createPropane(): Molecule3D { +function createCarbonDioxide(): Molecule3D { return { atoms: [ - { id: 0, element: 'C', x: -1.5, y: 0, z: 0 }, + { id: 0, element: 'O', x: -1.16, y: 0, z: 0 }, { id: 1, element: 'C', x: 0, y: 0, z: 0 }, - { id: 2, element: 'C', x: 1.5, y: 0, z: 0 }, - { id: 3, element: 'H', x: -1.9, y: 1.03, z: 0 }, - { id: 4, element: 'H', x: -1.9, y: -0.51, z: 0.89 }, - { id: 5, element: 'H', x: -1.9, y: -0.51, z: -0.89 }, - { id: 6, element: 'H', x: 0, y: 1.03, z: 0 }, - { id: 7, element: 'H', x: 0, y: -0.51, z: 0.89 }, - { id: 8, element: 'H', x: 1.9, y: 1.03, z: 0 }, - { id: 9, element: 'H', x: 1.9, y: -0.51, z: 0.89 }, - { id: 10, element: 'H', x: 1.9, y: -0.51, z: -0.89 }, - ], - bonds: [ - { atomIndex1: 0, atomIndex2: 1, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 2, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 3, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 4, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 5, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 6, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 7, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 8, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 9, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 10, bondType: 1 }, - ] - }; -} - -function createButane(): Molecule3D { - return { - atoms: [ - { id: 0, element: 'C', x: -2.25, y: 0, z: 0 }, - { id: 1, element: 'C', x: -0.75, y: 0, z: 0 }, - { id: 2, element: 'C', x: 0.75, y: 0, z: 0 }, - { id: 3, element: 'C', x: 2.25, y: 0, z: 0 }, - { id: 4, element: 'H', x: -2.65, y: 1.03, z: 0 }, - { id: 5, element: 'H', x: -2.65, y: -0.51, z: 0.89 }, - { id: 6, element: 'H', x: -2.65, y: -0.51, z: -0.89 }, - { id: 7, element: 'H', x: -0.75, y: 1.03, z: 0 }, - { id: 8, element: 'H', x: -0.75, y: -0.51, z: 0.89 }, - { id: 9, element: 'H', x: 0.75, y: 1.03, z: 0 }, - { id: 10, element: 'H', x: 0.75, y: -0.51, z: 0.89 }, - { id: 11, element: 'H', x: 2.65, y: 1.03, z: 0 }, - { id: 12, element: 'H', x: 2.65, y: -0.51, z: 0.89 }, - { id: 13, element: 'H', x: 2.65, y: -0.51, z: -0.89 }, - ], - bonds: [ - { atomIndex1: 0, atomIndex2: 1, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 2, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 3, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 4, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 5, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 6, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 7, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 8, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 9, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 10, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 11, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 12, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 13, bondType: 1 }, - ] - }; -} - -function createIsobutane(): Molecule3D { - return { - atoms: [ - { id: 0, element: 'C', x: 0, y: 0, z: 0 }, - { id: 1, element: 'C', x: 1.5, y: 0, z: 0 }, - { id: 2, element: 'C', x: -0.75, y: 1.3, z: 0 }, - { id: 3, element: 'C', x: -0.75, y: -1.3, z: 0 }, - { id: 4, element: 'H', x: 0, y: 0, z: 1.09 }, - { id: 5, element: 'H', x: 1.9, y: 1.03, z: 0 }, - { id: 6, element: 'H', x: 1.9, y: -0.51, z: 0.89 }, - { id: 7, element: 'H', x: 1.9, y: -0.51, z: -0.89 }, - { id: 8, element: 'H', x: -1.15, y: 1.73, z: 0.89 }, - { id: 9, element: 'H', x: -1.15, y: 1.73, z: -0.89 }, - { id: 10, element: 'H', x: -0.35, y: 2.33, z: 0 }, - { id: 11, element: 'H', x: -1.15, y: -1.73, z: 0.89 }, - { id: 12, element: 'H', x: -1.15, y: -1.73, z: -0.89 }, - { id: 13, element: 'H', x: -0.35, y: -2.33, z: 0 }, - ], - bonds: [ - { atomIndex1: 0, atomIndex2: 1, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 2, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 3, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 4, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 5, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 6, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 7, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 8, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 9, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 10, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 11, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 12, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 13, bondType: 1 }, - ] - }; -} - -function createMethanol(): Molecule3D { - return { - atoms: [ - { id: 0, element: 'C', x: 0, y: 0, z: 0 }, - { id: 1, element: 'O', x: 1.4, y: 0, z: 0 }, - { id: 2, element: 'H', x: -0.36, y: 1.03, z: 0 }, - { id: 3, element: 'H', x: -0.36, y: -0.51, z: 0.89 }, - { id: 4, element: 'H', x: -0.36, y: -0.51, z: -0.89 }, - { id: 5, element: 'H', x: 1.8, y: 0, z: 0 }, - ], - bonds: [ - { atomIndex1: 0, atomIndex2: 1, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 2, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 3, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 4, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 5, bondType: 1 }, - ] - }; -} - -function createCarbonTetrachloride(): Molecule3D { - return { - atoms: [ - { id: 0, element: 'C', x: 0, y: 0, z: 0 }, - { id: 1, element: 'Cl', x: 1.8, y: 0, z: 0 }, - { id: 2, element: 'Cl', x: -0.6, y: 1.7, z: 0 }, - { id: 3, element: 'Cl', x: -0.6, y: -0.85, z: 1.47 }, - { id: 4, element: 'Cl', x: -0.6, y: -0.85, z: -1.47 }, + { id: 2, element: 'O', x: 1.16, y: 0, z: 0 } ], bonds: [ - { atomIndex1: 0, atomIndex2: 1, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 2, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 3, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 4, bondType: 1 }, + { atomIndex1: 0, atomIndex2: 1, bondType: 2 }, + { atomIndex1: 1, atomIndex2: 2, bondType: 2 } ] }; } -function createAmmonia(): Molecule3D { - return { - atoms: [ - { id: 0, element: 'N', x: 0, y: 0, z: 0 }, - { id: 1, element: 'H', x: 1.01, y: 0, z: 0 }, - { id: 2, element: 'H', x: -0.33, y: 0.94, z: 0 }, - { id: 3, element: 'H', x: -0.33, y: -0.47, z: 0.82 }, - ], - bonds: [ - { atomIndex1: 0, atomIndex2: 1, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 2, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 3, bondType: 1 }, - ] - }; -} +function createBenzene(): Molecule3D { + const atoms: Molecule3D['atoms'] = []; + const bonds: Molecule3D['bonds'] = []; + const carbonRadius = 1.4; + const hydrogenRadius = 2.45; -function createSimpleAlkane(length: number): Molecule3D { - const atoms: Atom[] = []; - const bonds: Bond[] = []; - - // Create carbon chain - for (let i = 0; i < length; i++) { - atoms.push({ - id: i, - element: 'C', - x: i * 1.5, - y: 0, - z: 0 - }); - - if (i > 0) { - bonds.push({ atomIndex1: i - 1, atomIndex2: i, bondType: 1 }); - } - } - - // Add hydrogens - let hydrogenId = length; - for (let i = 0; i < length; i++) { - const numHydrogens = (i === 0 || i === length - 1) ? 3 : 2; - - for (let j = 0; j < numHydrogens; j++) { - const angle = (j * 2 * Math.PI) / numHydrogens; - atoms.push({ - id: hydrogenId, - element: 'H', - x: i * 1.5 + 1.1 * Math.cos(angle), - y: 1.1 * Math.sin(angle), - z: 0 - }); - - bonds.push({ atomIndex1: i, atomIndex2: hydrogenId, bondType: 1 }); - hydrogenId++; - } + for (let i = 0; i < 6; i += 1) { + const angle = (i * Math.PI) / 3; + atoms.push( + { id: i, element: 'C', x: carbonRadius * Math.cos(angle), y: carbonRadius * Math.sin(angle), z: 0 }, + { id: i + 6, element: 'H', x: hydrogenRadius * Math.cos(angle), y: hydrogenRadius * Math.sin(angle), z: 0 } + ); } - - return { atoms, bonds }; -} - -// Additional molecule creation functions for expanded parser support - -function createPentane(): Molecule3D { - return createSimpleAlkane(5); -} - -function createHexane(): Molecule3D { - return createSimpleAlkane(6); -} - -function createIsopentane(): Molecule3D { - // 2-methylbutane structure - return { - atoms: [ - { id: 0, element: 'C', x: 0, y: 0, z: 0 }, // Central carbon - { id: 1, element: 'C', x: 1.5, y: 0, z: 0 }, // C2 - { id: 2, element: 'C', x: 3, y: 0, z: 0 }, // C3 - { id: 3, element: 'C', x: 4.5, y: 0, z: 0 }, // C4 - { id: 4, element: 'C', x: 1.5, y: 1.5, z: 0 }, // Methyl branch - // Hydrogens for C1 - { id: 5, element: 'H', x: -0.5, y: 0.5, z: 0.5 }, - { id: 6, element: 'H', x: -0.5, y: -0.5, z: 0.5 }, - { id: 7, element: 'H', x: -0.5, y: 0, z: -1 }, - // Hydrogens for C2 (has branch) - { id: 8, element: 'H', x: 1.5, y: -1, z: 0.5 }, - // Hydrogens for C3 - { id: 9, element: 'H', x: 3, y: 0.5, z: 0.5 }, - { id: 10, element: 'H', x: 3, y: -0.5, z: 0.5 }, - // Hydrogens for C4 - { id: 11, element: 'H', x: 5, y: 0.5, z: 0.5 }, - { id: 12, element: 'H', x: 5, y: -0.5, z: 0.5 }, - { id: 13, element: 'H', x: 5, y: 0, z: -1 }, - // Hydrogens for methyl branch - { id: 14, element: 'H', x: 1, y: 2, z: 0.5 }, - { id: 15, element: 'H', x: 2, y: 2, z: 0.5 }, - { id: 16, element: 'H', x: 1.5, y: 2, z: -1 }, - ], - bonds: [ - { atomIndex1: 0, atomIndex2: 1, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 2, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 3, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 4, bondType: 1 }, - // C1 hydrogens - { atomIndex1: 0, atomIndex2: 5, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 6, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 7, bondType: 1 }, - // C2 hydrogen - { atomIndex1: 1, atomIndex2: 8, bondType: 1 }, - // C3 hydrogens - { atomIndex1: 2, atomIndex2: 9, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 10, bondType: 1 }, - // C4 hydrogens - { atomIndex1: 3, atomIndex2: 11, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 12, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 13, bondType: 1 }, - // Methyl branch hydrogens - { atomIndex1: 4, atomIndex2: 14, bondType: 1 }, - { atomIndex1: 4, atomIndex2: 15, bondType: 1 }, - { atomIndex1: 4, atomIndex2: 16, bondType: 1 }, - ] - }; -} - -function createPropanol(): Molecule3D { - // 1-propanol structure - return { - atoms: [ - { id: 0, element: 'C', x: 0, y: 0, z: 0 }, - { id: 1, element: 'C', x: 1.5, y: 0, z: 0 }, - { id: 2, element: 'C', x: 3, y: 0, z: 0 }, - { id: 3, element: 'O', x: 4, y: 0, z: 0 }, - // Hydrogens - { id: 4, element: 'H', x: -0.5, y: 0.5, z: 0.5 }, - { id: 5, element: 'H', x: -0.5, y: -0.5, z: 0.5 }, - { id: 6, element: 'H', x: -0.5, y: 0, z: -1 }, - { id: 7, element: 'H', x: 1.5, y: 0.5, z: 0.5 }, - { id: 8, element: 'H', x: 1.5, y: -0.5, z: 0.5 }, - { id: 9, element: 'H', x: 3, y: 0.5, z: 0.5 }, - { id: 10, element: 'H', x: 3, y: -0.5, z: 0.5 }, - { id: 11, element: 'H', x: 4.5, y: 0, z: 0 }, - ], - bonds: [ - { atomIndex1: 0, atomIndex2: 1, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 2, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 3, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 4, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 5, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 6, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 7, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 8, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 9, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 10, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 11, bondType: 1 }, - ] - }; -} - -function createIsopropanol(): Molecule3D { - // 2-propanol structure - return { - atoms: [ - { id: 0, element: 'C', x: 0, y: 0, z: 0 }, - { id: 1, element: 'C', x: 1.5, y: 0, z: 0 }, - { id: 2, element: 'C', x: 3, y: 0, z: 0 }, - { id: 3, element: 'O', x: 1.5, y: 1.2, z: 0 }, - // Hydrogens - { id: 4, element: 'H', x: -0.5, y: 0.5, z: 0.5 }, - { id: 5, element: 'H', x: -0.5, y: -0.5, z: 0.5 }, - { id: 6, element: 'H', x: -0.5, y: 0, z: -1 }, - { id: 7, element: 'H', x: 1.5, y: -1, z: 0 }, - { id: 8, element: 'H', x: 3.5, y: 0.5, z: 0.5 }, - { id: 9, element: 'H', x: 3.5, y: -0.5, z: 0.5 }, - { id: 10, element: 'H', x: 3.5, y: 0, z: -1 }, - { id: 11, element: 'H', x: 1.5, y: 1.8, z: 0 }, - ], - bonds: [ - { atomIndex1: 0, atomIndex2: 1, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 2, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 3, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 4, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 5, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 6, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 7, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 8, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 9, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 10, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 11, bondType: 1 }, - ] - }; -} - -function createToluene(): Molecule3D { - // Toluene (methylbenzene) structure - return { - atoms: [ - // Benzene ring - { id: 0, element: 'C', x: 0, y: 1.2, z: 0 }, - { id: 1, element: 'C', x: 1.04, y: 0.6, z: 0 }, - { id: 2, element: 'C', x: 1.04, y: -0.6, z: 0 }, - { id: 3, element: 'C', x: 0, y: -1.2, z: 0 }, - { id: 4, element: 'C', x: -1.04, y: -0.6, z: 0 }, - { id: 5, element: 'C', x: -1.04, y: 0.6, z: 0 }, - // Methyl group - { id: 6, element: 'C', x: 0, y: 2.7, z: 0 }, - // Hydrogens on benzene ring - { id: 7, element: 'H', x: 1.85, y: 1.07, z: 0 }, - { id: 8, element: 'H', x: 1.85, y: -1.07, z: 0 }, - { id: 9, element: 'H', x: 0, y: -2.14, z: 0 }, - { id: 10, element: 'H', x: -1.85, y: -1.07, z: 0 }, - { id: 11, element: 'H', x: -1.85, y: 1.07, z: 0 }, - // Hydrogens on methyl group - { id: 12, element: 'H', x: 0.5, y: 3.2, z: 0.5 }, - { id: 13, element: 'H', x: -0.5, y: 3.2, z: 0.5 }, - { id: 14, element: 'H', x: 0, y: 3.2, z: -1 }, - ], - bonds: [ - // Benzene ring bonds - { atomIndex1: 0, atomIndex2: 1, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 2, bondType: 2 }, - { atomIndex1: 2, atomIndex2: 3, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 4, bondType: 2 }, - { atomIndex1: 4, atomIndex2: 5, bondType: 1 }, - { atomIndex1: 5, atomIndex2: 0, bondType: 2 }, - // Methyl group - { atomIndex1: 0, atomIndex2: 6, bondType: 1 }, - // Hydrogen bonds - { atomIndex1: 1, atomIndex2: 7, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 8, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 9, bondType: 1 }, - { atomIndex1: 4, atomIndex2: 10, bondType: 1 }, - { atomIndex1: 5, atomIndex2: 11, bondType: 1 }, - { atomIndex1: 6, atomIndex2: 12, bondType: 1 }, - { atomIndex1: 6, atomIndex2: 13, bondType: 1 }, - { atomIndex1: 6, atomIndex2: 14, bondType: 1 }, - ] - }; -} - -function createXylene(): Molecule3D { - // o-Xylene (1,2-dimethylbenzene) structure - return { - atoms: [ - // Benzene ring - { id: 0, element: 'C', x: 0, y: 1.2, z: 0 }, - { id: 1, element: 'C', x: 1.04, y: 0.6, z: 0 }, - { id: 2, element: 'C', x: 1.04, y: -0.6, z: 0 }, - { id: 3, element: 'C', x: 0, y: -1.2, z: 0 }, - { id: 4, element: 'C', x: -1.04, y: -0.6, z: 0 }, - { id: 5, element: 'C', x: -1.04, y: 0.6, z: 0 }, - // First methyl group - { id: 6, element: 'C', x: 0, y: 2.7, z: 0 }, - // Second methyl group - { id: 7, element: 'C', x: 2.5, y: 1.2, z: 0 }, - // Hydrogens on benzene ring - { id: 8, element: 'H', x: 1.85, y: -1.07, z: 0 }, - { id: 9, element: 'H', x: 0, y: -2.14, z: 0 }, - { id: 10, element: 'H', x: -1.85, y: -1.07, z: 0 }, - { id: 11, element: 'H', x: -1.85, y: 1.07, z: 0 }, - // Hydrogens on first methyl group - { id: 12, element: 'H', x: 0.5, y: 3.2, z: 0.5 }, - { id: 13, element: 'H', x: -0.5, y: 3.2, z: 0.5 }, - { id: 14, element: 'H', x: 0, y: 3.2, z: -1 }, - // Hydrogens on second methyl group - { id: 15, element: 'H', x: 3, y: 1.7, z: 0.5 }, - { id: 16, element: 'H', x: 3, y: 0.7, z: 0.5 }, - { id: 17, element: 'H', x: 3, y: 1.2, z: -1 }, - ], - bonds: [ - // Benzene ring bonds - { atomIndex1: 0, atomIndex2: 1, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 2, bondType: 2 }, - { atomIndex1: 2, atomIndex2: 3, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 4, bondType: 2 }, - { atomIndex1: 4, atomIndex2: 5, bondType: 1 }, - { atomIndex1: 5, atomIndex2: 0, bondType: 2 }, - // Methyl groups - { atomIndex1: 0, atomIndex2: 6, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 7, bondType: 1 }, - // Hydrogen bonds - { atomIndex1: 2, atomIndex2: 8, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 9, bondType: 1 }, - { atomIndex1: 4, atomIndex2: 10, bondType: 1 }, - { atomIndex1: 5, atomIndex2: 11, bondType: 1 }, - { atomIndex1: 6, atomIndex2: 12, bondType: 1 }, - { atomIndex1: 6, atomIndex2: 13, bondType: 1 }, - { atomIndex1: 6, atomIndex2: 14, bondType: 1 }, - { atomIndex1: 7, atomIndex2: 15, bondType: 1 }, - { atomIndex1: 7, atomIndex2: 16, bondType: 1 }, - { atomIndex1: 7, atomIndex2: 17, bondType: 1 }, - ] - }; -} - -function createFormicAcid(): Molecule3D { - // HCOOH structure - return { - atoms: [ - { id: 0, element: 'C', x: 0, y: 0, z: 0 }, - { id: 1, element: 'O', x: 1.2, y: 0, z: 0 }, - { id: 2, element: 'O', x: -0.6, y: 1.04, z: 0 }, - { id: 3, element: 'H', x: 0, y: -1, z: 0 }, - { id: 4, element: 'H', x: -1.5, y: 1.04, z: 0 }, - ], - bonds: [ - { atomIndex1: 0, atomIndex2: 1, bondType: 2 }, - { atomIndex1: 0, atomIndex2: 2, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 3, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 4, bondType: 1 }, - ] - }; -} -function createHydrogen(): Molecule3D { - return { - atoms: [ - { id: 0, element: 'H', x: 0, y: 0, z: 0 }, - { id: 1, element: 'H', x: 0.74, y: 0, z: 0 }, - ], - bonds: [ - { atomIndex1: 0, atomIndex2: 1, bondType: 1 }, - ] - }; -} - -// Create a placeholder molecule for complex structures that can't be parsed -function createComplexMoleculePlaceholder(): Molecule3D { - // Create a simple representation showing this is a complex molecule - const atoms = []; - const bonds: Array<{ atomIndex1: number; atomIndex2: number; bondType: number }> = []; - - // Central carbon with surrounding atoms to indicate complexity - atoms.push({ id: 0, element: 'C', x: 0, y: 0, z: 0 }); - - // Add surrounding atoms in a tetrahedral arrangement - const positions = [ - { x: 1.2, y: 0.8, z: 0.8 }, - { x: -1.2, y: -0.8, z: 0.8 }, - { x: 1.2, y: -0.8, z: -0.8 }, - { x: -1.2, y: 0.8, z: -0.8 } - ]; - - positions.forEach((pos, i) => { - atoms.push({ id: i + 1, element: 'X', x: pos.x, y: pos.y, z: pos.z }); - bonds.push({ atomIndex1: 0, atomIndex2: i + 1, bondType: 1 }); - }); - - return { atoms, bonds }; -} + for (let i = 0; i < 6; i += 1) { + bonds.push( + { atomIndex1: i, atomIndex2: (i + 1) % 6, bondType: i % 2 === 0 ? 2 : 1 }, + { atomIndex1: i, atomIndex2: i + 6, bondType: 1 } + ); + } -// D-ribose (CID 634) - simplified ring structure -function createRibose(): Molecule3D { - const atoms = []; - const bonds: Array<{ atomIndex1: number; atomIndex2: number; bondType: number }> = []; - - // Simplified ribose ring (furanose form) - // Add all atoms at once - atoms.push( - { id: 0, element: 'C', x: 0, y: 0, z: 0 }, // C1 - { id: 1, element: 'C', x: 1.4, y: 0.8, z: 0 }, // C2 - { id: 2, element: 'C', x: 2.2, y: -0.5, z: 0 }, // C3 - { id: 3, element: 'C', x: 1.2, y: -1.5, z: 0 }, // C4 - { id: 4, element: 'O', x: 0.2, y: -1.2, z: 0.8 }, // Ring oxygen - { id: 5, element: 'O', x: -0.8, y: 0.8, z: 0 }, // OH on C1 - { id: 6, element: 'H', x: -1.5, y: 0.5, z: 0 }, // H on OH - { id: 7, element: 'O', x: 1.8, y: 1.8, z: 0 }, // OH on C2 - { id: 8, element: 'H', x: 2.5, y: 2.2, z: 0 }, // H on OH - { id: 9, element: 'O', x: 3.2, y: -0.2, z: 0 }, // OH on C3 - { id: 10, element: 'H', x: 3.8, y: -0.8, z: 0 }, // H on OH - { id: 11, element: 'C', x: 1.5, y: -2.8, z: 0 }, // CH2 - { id: 12, element: 'O', x: 2.5, y: -3.5, z: 0 }, // OH - { id: 13, element: 'H', x: 3.2, y: -3.2, z: 0 }, // H on OH - { id: 14, element: 'H', x: 0.2, y: 0.2, z: 1 }, // H on C1 - { id: 15, element: 'H', x: 1, y: 1, z: -1 }, // H on C2 - { id: 16, element: 'H', x: 2.5, y: -0.8, z: -1 }, // H on C3 - { id: 17, element: 'H', x: 0.8, y: -1.8, z: -1 }, // H on C4 - { id: 18, element: 'H', x: 0.8, y: -3.2, z: 0 }, // H on CH2 - { id: 19, element: 'H', x: 1.8, y: -3.2, z: -0.8 } // H on CH2 - ); - - // Add all bonds at once - bonds.push( - { atomIndex1: 0, atomIndex2: 1, bondType: 1 }, // C1-C2 - { atomIndex1: 1, atomIndex2: 2, bondType: 1 }, // C2-C3 - { atomIndex1: 2, atomIndex2: 3, bondType: 1 }, // C3-C4 - { atomIndex1: 3, atomIndex2: 4, bondType: 1 }, // C4-O - { atomIndex1: 4, atomIndex2: 0, bondType: 1 }, // O-C1 - { atomIndex1: 0, atomIndex2: 5, bondType: 1 }, // C1-OH - { atomIndex1: 5, atomIndex2: 6, bondType: 1 }, // OH-H - { atomIndex1: 1, atomIndex2: 7, bondType: 1 }, // C2-OH - { atomIndex1: 7, atomIndex2: 8, bondType: 1 }, // OH-H - { atomIndex1: 2, atomIndex2: 9, bondType: 1 }, // C3-OH - { atomIndex1: 9, atomIndex2: 10, bondType: 1 }, // OH-H - { atomIndex1: 3, atomIndex2: 11, bondType: 1 }, // C4-CH2 - { atomIndex1: 11, atomIndex2: 12, bondType: 1 }, // CH2-OH - { atomIndex1: 12, atomIndex2: 13, bondType: 1 }, // OH-H - { atomIndex1: 0, atomIndex2: 14, bondType: 1 }, // C1-H - { atomIndex1: 1, atomIndex2: 15, bondType: 1 }, // C2-H - { atomIndex1: 2, atomIndex2: 16, bondType: 1 }, // C3-H - { atomIndex1: 3, atomIndex2: 17, bondType: 1 }, // C4-H - { atomIndex1: 11, atomIndex2: 18, bondType: 1 }, // CH2-H - { atomIndex1: 11, atomIndex2: 19, bondType: 1 } // CH2-H - ); - return { atoms, bonds }; } -// Placeholder functions for complex molecules (will use SMILES fallback) -function createGlucose(): Molecule3D { - // For now, create a placeholder - the SMILES fallback will handle the real structure - return createComplexMoleculePlaceholder(); -} - -function createCaffeine(): Molecule3D { - // For now, create a placeholder - the SMILES fallback will handle the real structure - return createComplexMoleculePlaceholder(); -} - -function createAspirin(): Molecule3D { - // For now, create a placeholder - the SMILES fallback will handle the real structure - return createComplexMoleculePlaceholder(); -} - -function createOxygen(): Molecule3D { - return { - atoms: [ - { id: 0, element: 'O', x: 0, y: 0, z: 0 }, - { id: 1, element: 'O', x: 1.21, y: 0, z: 0 }, - ], - bonds: [ - { atomIndex1: 0, atomIndex2: 1, bondType: 2 }, - ] - }; -} - -// Alkene creation functions -function createEthene(): Molecule3D { - return { - atoms: [ - { id: 0, element: 'C', x: 0, y: 0, z: 0 }, - { id: 1, element: 'C', x: 1.34, y: 0, z: 0 }, - { id: 2, element: 'H', x: -0.5, y: 0.8, z: 0 }, - { id: 3, element: 'H', x: -0.5, y: -0.8, z: 0 }, - { id: 4, element: 'H', x: 1.84, y: 0.8, z: 0 }, - { id: 5, element: 'H', x: 1.84, y: -0.8, z: 0 }, - ], - bonds: [ - { atomIndex1: 0, atomIndex2: 1, bondType: 2 }, // C=C double bond - { atomIndex1: 0, atomIndex2: 2, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 3, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 4, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 5, bondType: 1 }, - ] - }; -} - -function createPropene(): Molecule3D { +function createEthanol(): Molecule3D { return { atoms: [ - { id: 0, element: 'C', x: 0, y: 0, z: 0 }, - { id: 1, element: 'C', x: 1.34, y: 0, z: 0 }, - { id: 2, element: 'C', x: 2.2, y: 1, z: 0 }, - { id: 3, element: 'H', x: -0.5, y: 0.8, z: 0 }, - { id: 4, element: 'H', x: -0.5, y: -0.8, z: 0 }, - { id: 5, element: 'H', x: 1.84, y: -0.8, z: 0 }, - { id: 6, element: 'H', x: 1.7, y: 1.8, z: 0 }, - { id: 7, element: 'H', x: 2.7, y: 1.3, z: 0.8 }, - { id: 8, element: 'H', x: 2.7, y: 1.3, z: -0.8 }, + { id: 0, element: 'C', x: -1.2, y: 0, z: 0 }, + { id: 1, element: 'C', x: 0, y: 0, z: 0 }, + { id: 2, element: 'O', x: 1.3, y: 0, z: 0 }, + { id: 3, element: 'H', x: -1.6, y: 1.03, z: 0 }, + { id: 4, element: 'H', x: -1.6, y: -0.51, z: 0.89 }, + { id: 5, element: 'H', x: -1.6, y: -0.51, z: -0.89 }, + { id: 6, element: 'H', x: 0, y: 1.03, z: 0 }, + { id: 7, element: 'H', x: 0, y: -0.51, z: 0.89 }, + { id: 8, element: 'H', x: 1.7, y: 0, z: 0 } ], bonds: [ - { atomIndex1: 0, atomIndex2: 1, bondType: 2 }, // C=C double bond + { atomIndex1: 0, atomIndex2: 1, bondType: 1 }, { atomIndex1: 1, atomIndex2: 2, bondType: 1 }, { atomIndex1: 0, atomIndex2: 3, bondType: 1 }, { atomIndex1: 0, atomIndex2: 4, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 5, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 6, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 7, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 8, bondType: 1 }, - ] - }; -} - -function createButene(): Molecule3D { - return { - atoms: [ - { id: 0, element: 'C', x: 0, y: 0, z: 0 }, - { id: 1, element: 'C', x: 1.34, y: 0, z: 0 }, - { id: 2, element: 'C', x: 2.2, y: 1, z: 0 }, - { id: 3, element: 'C', x: 3.5, y: 0.8, z: 0 }, - { id: 4, element: 'H', x: -0.5, y: 0.8, z: 0 }, - { id: 5, element: 'H', x: -0.5, y: -0.8, z: 0 }, - { id: 6, element: 'H', x: 1.84, y: -0.8, z: 0 }, - { id: 7, element: 'H', x: 1.7, y: 1.8, z: 0 }, - { id: 8, element: 'H', x: 3.8, y: 1.6, z: 0 }, - { id: 9, element: 'H', x: 4.2, y: 0.3, z: 0.8 }, - { id: 10, element: 'H', x: 4.2, y: 0.3, z: -0.8 }, - ], - bonds: [ - { atomIndex1: 0, atomIndex2: 1, bondType: 2 }, // C=C double bond - { atomIndex1: 1, atomIndex2: 2, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 3, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 4, bondType: 1 }, { atomIndex1: 0, atomIndex2: 5, bondType: 1 }, { atomIndex1: 1, atomIndex2: 6, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 7, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 8, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 9, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 10, bondType: 1 }, - ] - }; -} - -function createHexene(): Molecule3D { - return { - atoms: [ - { id: 0, element: 'C', x: 0, y: 0, z: 0 }, - { id: 1, element: 'C', x: 1.34, y: 0, z: 0 }, - { id: 2, element: 'C', x: 2.2, y: 1, z: 0 }, - { id: 3, element: 'C', x: 3.5, y: 0.8, z: 0 }, - { id: 4, element: 'C', x: 4.3, y: 1.8, z: 0 }, - { id: 5, element: 'C', x: 5.6, y: 1.6, z: 0 }, - { id: 6, element: 'H', x: -0.5, y: 0.8, z: 0 }, - { id: 7, element: 'H', x: -0.5, y: -0.8, z: 0 }, - { id: 8, element: 'H', x: 1.84, y: -0.8, z: 0 }, - { id: 9, element: 'H', x: 1.7, y: 1.8, z: 0 }, - { id: 10, element: 'H', x: 3.8, y: 1.6, z: 0 }, - { id: 11, element: 'H', x: 3.2, y: 0, z: 0 }, - { id: 12, element: 'H', x: 3.9, y: 2.6, z: 0 }, - { id: 13, element: 'H', x: 4.6, y: 2, z: 0.8 }, - { id: 14, element: 'H', x: 6, y: 2.4, z: 0 }, - { id: 15, element: 'H', x: 6.2, y: 1, z: 0.8 }, - { id: 16, element: 'H', x: 6.2, y: 1, z: -0.8 }, - ], - bonds: [ - { atomIndex1: 0, atomIndex2: 1, bondType: 2 }, // C=C double bond (2-hexene) - { atomIndex1: 1, atomIndex2: 2, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 3, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 4, bondType: 1 }, - { atomIndex1: 4, atomIndex2: 5, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 6, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 7, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 8, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 9, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 10, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 11, bondType: 1 }, - { atomIndex1: 4, atomIndex2: 12, bondType: 1 }, - { atomIndex1: 4, atomIndex2: 13, bondType: 1 }, - { atomIndex1: 5, atomIndex2: 14, bondType: 1 }, - { atomIndex1: 5, atomIndex2: 15, bondType: 1 }, - { atomIndex1: 5, atomIndex2: 16, bondType: 1 }, - ] - }; -} - -function createNitrogen(): Molecule3D { - return { - atoms: [ - { id: 0, element: 'N', x: 0, y: 0, z: 0 }, - { id: 1, element: 'N', x: 1.1, y: 0, z: 0 }, - ], - bonds: [ - { atomIndex1: 0, atomIndex2: 1, bondType: 3 }, - ] - }; -} - -// SMILES-based structure creators for complex molecules -function createCaffeineFromSMILES(): Molecule3D { - // Caffeine structure: CN1C=NC2=C1C(=O)N(C(=O)N2C)C - // A purine alkaloid with two fused rings - return { - atoms: [ - // Purine ring system atoms - { id: 0, element: 'C', x: 0, y: 0, z: 0 }, // methyl group on N1 - { id: 1, element: 'N', x: 1.5, y: 0, z: 0 }, // N1 - { id: 2, element: 'C', x: 2.2, y: 1.2, z: 0 }, // C2 - { id: 3, element: 'N', x: 3.7, y: 1.2, z: 0 }, // N3 - { id: 4, element: 'C', x: 4.4, y: 0, z: 0 }, // C4 - { id: 5, element: 'C', x: 3.7, y: -1.2, z: 0 }, // C5 - { id: 6, element: 'C', x: 5.9, y: 0, z: 0 }, // C6 (carbonyl) - { id: 7, element: 'O', x: 6.6, y: 1.2, z: 0 }, // O6 - { id: 8, element: 'N', x: 6.6, y: -1.2, z: 0 }, // N7 - { id: 9, element: 'C', x: 8.1, y: -1.2, z: 0 }, // methyl on N7 - { id: 10, element: 'C', x: 5.9, y: -2.4, z: 0 }, // C8 (carbonyl) - { id: 11, element: 'O', x: 6.6, y: -3.6, z: 0 }, // O8 - { id: 12, element: 'N', x: 4.4, y: -2.4, z: 0 }, // N9 - { id: 13, element: 'C', x: 3.7, y: -3.6, z: 0 }, // methyl on N9 - // Ring closure - { id: 14, element: 'C', x: 2.2, y: -1.2, z: 0 }, // C6 ring closure - // Hydrogens - { id: 15, element: 'H', x: -0.5, y: 0.8, z: 0 }, - { id: 16, element: 'H', x: -0.5, y: -0.8, z: 0 }, - { id: 17, element: 'H', x: 0.5, y: 0, z: 0.8 }, - { id: 18, element: 'H', x: 2.2, y: 2.2, z: 0 }, - { id: 19, element: 'H', x: 8.6, y: -0.4, z: 0 }, - { id: 20, element: 'H', x: 8.6, y: -2, z: 0 }, - { id: 21, element: 'H', x: 8.4, y: -1.2, z: 0.8 }, - { id: 22, element: 'H', x: 3.2, y: -4.4, z: 0 }, - { id: 23, element: 'H', x: 4.2, y: -3.6, z: 0.8 }, - { id: 24, element: 'H', x: 4.2, y: -3.6, z: -0.8 }, - ], - bonds: [ - // Main purine ring bonds - { atomIndex1: 0, atomIndex2: 1, bondType: 1 }, // methyl-N1 - { atomIndex1: 1, atomIndex2: 2, bondType: 1 }, // N1-C2 - { atomIndex1: 2, atomIndex2: 3, bondType: 2 }, // C2=N3 - { atomIndex1: 3, atomIndex2: 4, bondType: 1 }, // N3-C4 - { atomIndex1: 4, atomIndex2: 5, bondType: 2 }, // C4=C5 - { atomIndex1: 5, atomIndex2: 14, bondType: 1 }, // C5-C6_ring - { atomIndex1: 14, atomIndex2: 1, bondType: 1 }, // C6_ring-N1 - - // 6-membered ring completion - { atomIndex1: 4, atomIndex2: 6, bondType: 1 }, // C4-C6 - { atomIndex1: 6, atomIndex2: 7, bondType: 2 }, // C6=O - { atomIndex1: 6, atomIndex2: 8, bondType: 1 }, // C6-N7 - { atomIndex1: 8, atomIndex2: 9, bondType: 1 }, // N7-methyl - { atomIndex1: 8, atomIndex2: 10, bondType: 1 }, // N7-C8 - { atomIndex1: 10, atomIndex2: 11, bondType: 2 }, // C8=O - { atomIndex1: 10, atomIndex2: 12, bondType: 1 }, // C8-N9 - { atomIndex1: 12, atomIndex2: 13, bondType: 1 }, // N9-methyl - { atomIndex1: 12, atomIndex2: 5, bondType: 1 }, // N9-C5 - - // Hydrogen bonds - { atomIndex1: 0, atomIndex2: 15, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 16, bondType: 1 }, - { atomIndex1: 0, atomIndex2: 17, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 18, bondType: 1 }, - { atomIndex1: 9, atomIndex2: 19, bondType: 1 }, - { atomIndex1: 9, atomIndex2: 20, bondType: 1 }, - { atomIndex1: 9, atomIndex2: 21, bondType: 1 }, - { atomIndex1: 13, atomIndex2: 22, bondType: 1 }, - { atomIndex1: 13, atomIndex2: 23, bondType: 1 }, - { atomIndex1: 13, atomIndex2: 24, bondType: 1 }, - ] - }; -} - -function createAspirinFromSMILES(): Molecule3D { - // Aspirin: CC(=O)OC1=CC=CC=C1C(=O)O - // Simplified benzene ring with acetyl and carboxyl groups - return { - atoms: [ - // Benzene ring - { id: 0, element: 'C', x: 0, y: 0, z: 0 }, - { id: 1, element: 'C', x: 1.4, y: 0, z: 0 }, - { id: 2, element: 'C', x: 2.1, y: 1.2, z: 0 }, - { id: 3, element: 'C', x: 1.4, y: 2.4, z: 0 }, - { id: 4, element: 'C', x: 0, y: 2.4, z: 0 }, - { id: 5, element: 'C', x: -0.7, y: 1.2, z: 0 }, - - // Carboxyl group on C1 - { id: 6, element: 'C', x: -2.2, y: 1.2, z: 0 }, - { id: 7, element: 'O', x: -2.9, y: 2.4, z: 0 }, - { id: 8, element: 'O', x: -2.9, y: 0, z: 0 }, - { id: 9, element: 'H', x: -3.9, y: 0, z: 0 }, - - // Acetyl ester on C2 - { id: 10, element: 'O', x: 2.1, y: -1.2, z: 0 }, - { id: 11, element: 'C', x: 3.6, y: -1.2, z: 0 }, - { id: 12, element: 'O', x: 4.3, y: 0, z: 0 }, - { id: 13, element: 'C', x: 4.3, y: -2.4, z: 0 }, - - // Hydrogens - { id: 14, element: 'H', x: 3.1, y: 1.2, z: 0 }, - { id: 15, element: 'H', x: 1.9, y: 3.3, z: 0 }, - { id: 16, element: 'H', x: -0.5, y: 3.3, z: 0 }, - { id: 17, element: 'H', x: 3.8, y: -3.3, z: 0 }, - { id: 18, element: 'H', x: 5.3, y: -2.4, z: 0 }, - { id: 19, element: 'H', x: 4.3, y: -2.4, z: 0.8 }, - ], - bonds: [ - // Benzene ring - { atomIndex1: 0, atomIndex2: 1, bondType: 2 }, - { atomIndex1: 1, atomIndex2: 2, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 3, bondType: 2 }, - { atomIndex1: 3, atomIndex2: 4, bondType: 1 }, - { atomIndex1: 4, atomIndex2: 5, bondType: 2 }, - { atomIndex1: 5, atomIndex2: 0, bondType: 1 }, - - // Carboxyl group - { atomIndex1: 5, atomIndex2: 6, bondType: 1 }, - { atomIndex1: 6, atomIndex2: 7, bondType: 2 }, - { atomIndex1: 6, atomIndex2: 8, bondType: 1 }, - { atomIndex1: 8, atomIndex2: 9, bondType: 1 }, - - // Acetyl ester - { atomIndex1: 1, atomIndex2: 10, bondType: 1 }, - { atomIndex1: 10, atomIndex2: 11, bondType: 1 }, - { atomIndex1: 11, atomIndex2: 12, bondType: 2 }, - { atomIndex1: 11, atomIndex2: 13, bondType: 1 }, - - // Hydrogens - { atomIndex1: 2, atomIndex2: 14, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 15, bondType: 1 }, - { atomIndex1: 4, atomIndex2: 16, bondType: 1 }, - { atomIndex1: 13, atomIndex2: 17, bondType: 1 }, - { atomIndex1: 13, atomIndex2: 18, bondType: 1 }, - { atomIndex1: 13, atomIndex2: 19, bondType: 1 }, - ] - }; -} - -function createGlucoseFromSMILES(): Molecule3D { - // Glucose in chair conformation (simplified) - // C([C@@H]1[C@H]([C@@H]([C@H]([C@H](O1)O)O)O)O)O - return { - atoms: [ - // Ring atoms (chair conformation) - { id: 0, element: 'C', x: 0, y: 0, z: 0 }, // C1 - { id: 1, element: 'C', x: 1.4, y: 0.8, z: 0 }, // C2 - { id: 2, element: 'C', x: 2.8, y: 0, z: 0 }, // C3 - { id: 3, element: 'C', x: 2.8, y: -1.6, z: 0 }, // C4 - { id: 4, element: 'C', x: 1.4, y: -2.4, z: 0 }, // C5 - { id: 5, element: 'O', x: 0, y: -1.6, z: 0 }, // O ring - - // CH2OH group on C5 - { id: 6, element: 'C', x: 1.4, y: -3.9, z: 0 }, - { id: 7, element: 'O', x: 0, y: -4.7, z: 0 }, - - // OH groups - { id: 8, element: 'O', x: -1.4, y: 0.8, z: 0 }, // OH on C1 - { id: 9, element: 'O', x: 1.4, y: 2.3, z: 0 }, // OH on C2 - { id: 10, element: 'O', x: 4.2, y: 0.8, z: 0 }, // OH on C3 - { id: 11, element: 'O', x: 4.2, y: -2.4, z: 0 }, // OH on C4 - - // Hydrogens - { id: 12, element: 'H', x: 0, y: 0, z: 1.1 }, // H on C1 - { id: 13, element: 'H', x: 1.4, y: 0.8, z: -1.1 }, // H on C2 - { id: 14, element: 'H', x: 2.8, y: 0, z: 1.1 }, // H on C3 - { id: 15, element: 'H', x: 2.8, y: -1.6, z: -1.1 }, // H on C4 - { id: 16, element: 'H', x: 1.4, y: -2.4, z: 1.1 }, // H on C5 - { id: 17, element: 'H', x: 2.3, y: -4.3, z: 0 }, // H on CH2 - { id: 18, element: 'H', x: 1.4, y: -3.9, z: 1.1 }, // H on CH2 - { id: 19, element: 'H', x: -1.8, y: 0.8, z: 0 }, // H on OH1 - { id: 20, element: 'H', x: 1.8, y: 2.7, z: 0 }, // H on OH2 - { id: 21, element: 'H', x: 4.6, y: 0.8, z: 0 }, // H on OH3 - { id: 22, element: 'H', x: 4.6, y: -2.4, z: 0 }, // H on OH4 - { id: 23, element: 'H', x: -0.4, y: -5.1, z: 0 }, // H on OH6 - ], - bonds: [ - // Ring bonds - { atomIndex1: 0, atomIndex2: 1, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 2, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 3, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 4, bondType: 1 }, - { atomIndex1: 4, atomIndex2: 5, bondType: 1 }, - { atomIndex1: 5, atomIndex2: 0, bondType: 1 }, - - // CH2OH group - { atomIndex1: 4, atomIndex2: 6, bondType: 1 }, - { atomIndex1: 6, atomIndex2: 7, bondType: 1 }, - - // OH groups - { atomIndex1: 0, atomIndex2: 8, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 9, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 10, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 11, bondType: 1 }, - - // Hydrogens - { atomIndex1: 0, atomIndex2: 12, bondType: 1 }, - { atomIndex1: 1, atomIndex2: 13, bondType: 1 }, - { atomIndex1: 2, atomIndex2: 14, bondType: 1 }, - { atomIndex1: 3, atomIndex2: 15, bondType: 1 }, - { atomIndex1: 4, atomIndex2: 16, bondType: 1 }, - { atomIndex1: 6, atomIndex2: 17, bondType: 1 }, - { atomIndex1: 6, atomIndex2: 18, bondType: 1 }, - { atomIndex1: 8, atomIndex2: 19, bondType: 1 }, - { atomIndex1: 9, atomIndex2: 20, bondType: 1 }, - { atomIndex1: 10, atomIndex2: 21, bondType: 1 }, - { atomIndex1: 11, atomIndex2: 22, bondType: 1 }, - { atomIndex1: 7, atomIndex2: 23, bondType: 1 }, + { atomIndex1: 1, atomIndex2: 7, bondType: 1 }, + { atomIndex1: 2, atomIndex2: 8, bondType: 1 } ] }; }