diff --git a/app/tools/dim-red/page.tsx b/app/tools/dim-red/page.tsx index 9d03df1..a8910ef 100644 --- a/app/tools/dim-red/page.tsx +++ b/app/tools/dim-red/page.tsx @@ -7,9 +7,9 @@ import Scatterplot from "../../../components/tools/toolViz/ScatterPlot"; import { useSearchParams } from "next/navigation"; import Loader from "../../../components/ui-comps/Loader"; -export default function DimRed(){ +export default function DimRed() { - const {ligand} = useContext(LigandContext); + const { ligand, setLigand } = useContext(LigandContext); const { pyodide } = useContext(PyodideContext); const [pca, setPCA] = useState([]) const [whatDimRed, setWhatDimRef] = useState(window.location.href.split("#")[1]) @@ -23,17 +23,40 @@ export default function DimRed(){ setWhatDimRef(window.location.href.split("#")[1]); if (window.location.href.split("#")[1] === "pca") { globalThis.opts = 1 - } else if (window.location.href.split("#")[1] === "tsne"){ - if (pcaCorrectTSNE){ + } else if (window.location.href.split("#")[1] === "tsne") { + if (pcaCorrectTSNE) { globalThis.opts = 2 } else { globalThis.opts = 3 } } else { throw new Error("opts is not properly set."); - } - setLoaded(false); - setTimeout(() => {runDimRed()}, 1000); + } + setLoaded(false); + setTimeout(() => { + const previousDataExistsPCA = ligand.some(obj => obj.tsne); + const previousDataExiststSNE = ligand.some(obj => obj.tsne); + const dimRedType = window.location.href.split("#")[1]; + + if (!previousDataExistsPCA && !previousDataExiststSNE) { + runDimRed(); + } else { + if (dimRedType === "pca") { + if (previousDataExistsPCA) { + setPCA(ligand.map((obj) => obj.pca)); + } else { + runDimRed(); + } + } else if (dimRedType === "tsne") { + if (previousDataExistsPCA) { + setPCA(ligand.map((obj) => obj.tsne)); + } else { + runDimRed(); + } + } + setLoaded(true); + } + }, 100); }, [useSearchParams()]) var data = ligand.map((obj) => obj.neg_log_activity_column); @@ -41,8 +64,8 @@ export default function DimRed(){ var id = ligand.map((obj) => obj.id); globalThis.fp = ligand.map((obj) => obj.fingerprint); - async function runDimRed(){ - setLoaded(false); + async function runDimRed() { + setLoaded(false); await pyodide.runPython(` from sklearn.decomposition import PCA from sklearn.manifold import TSNE @@ -66,33 +89,44 @@ export default function DimRed(){ `) const pca_result = (globalThis.pca).toJs();; const pca_data_in = pca_result.map(([x, y]) => ({ x, y })); - setPCA(pca_data_in) + let new_ligand = ligand.map((obj, index) => { + if (globalThis.opts === 1) { + return { ...obj, pca: pca_data_in[index] }; + } else { + return { ...obj, tsne: pca_data_in[index] }; + } + }); + setLigand(new_ligand); + setPCA(pca_data_in); setLoaded(true) } if (loaded) { - return( + return (
{whatDimRed === "pca" && ( <> )} {whatDimRed === "tsne" && ( -
+
tSNE settings
- {setPCACorrectTSNE(e.target.checked)}}> + + { setPCACorrectTSNE(e.target.checked) }}> +
- + +
- + +
-
+
- )} - {pca.length > 0 && - 0 && + }
- ) + ) } else { return (
- +
) } diff --git a/components/tools/toolViz/BarChart.tsx b/components/tools/toolViz/BarChart.tsx index 65e3d1c..9964cd4 100644 --- a/components/tools/toolViz/BarChart.tsx +++ b/components/tools/toolViz/BarChart.tsx @@ -26,7 +26,7 @@ const GroupedBarChart = ({ mae, r2 }) => { if (dimensions.width === 0 && dimensions.height === 0){ getSvgContainerSize(); } - const margin = { top: 10, right: 30, bottom: 10, left: 70 }, + const margin = { top: 40, right: 60, bottom: 40, left: 70 }, width = dimensions.width - margin.left - margin.right, height = dimensions.height - margin.top - margin.bottom; @@ -54,6 +54,7 @@ const GroupedBarChart = ({ mae, r2 }) => { .padding([0.2]); svg.append("g") .attr("transform", `translate(0, ${height})`) + .style('font-size', '1.2em') .call(d3.axisBottom(x).tickSize(0)); // Add Y axis @@ -61,6 +62,7 @@ const GroupedBarChart = ({ mae, r2 }) => { .domain([0, d3.max([...mae, ...r2])]) .range([height, 0]); svg.append("g") + .style('font-size', '1.2em') .call(d3.axisLeft(y)); // Another scale for subgroup position @@ -93,7 +95,7 @@ const GroupedBarChart = ({ mae, r2 }) => { // Add legend const legend = svg.append("g") - .attr("transform", `translate(${width - 30}, 0)`); + .attr("transform", `translate(${width - 15}, 0)`); legend.selectAll("rect") .data(subgroups) diff --git a/components/tools/toolViz/Histogram.tsx b/components/tools/toolViz/Histogram.tsx index dd1e7f5..4ae3824 100644 --- a/components/tools/toolViz/Histogram.tsx +++ b/components/tools/toolViz/Histogram.tsx @@ -4,7 +4,7 @@ import ModalComponent from "../../ui-comps/ModalComponent"; import Card from "./Card"; import MoleculeStructure from "../toolComp/MoleculeStructure"; -const MARGIN = { top: 30, right: 30, bottom: 40, left: 50 }; +const MARGIN = { top: 30, right: 30, bottom: 50, left: 80 }; const BUCKET_NUMBER = 70; const BUCKET_PADDING = 1; @@ -91,16 +91,19 @@ export default function Histogram({ svgElement .append("g") .attr("transform", `translate(${MARGIN.left},${height - MARGIN.bottom})`) - .call(xAxisGenerator); + .call(xAxisGenerator) + .selectAll('text') // Select all the text elements for x-axis ticks + .style('font-size', '1.5em'); // Add X-axis label svgElement .append("text") .attr( "transform", - `translate(${width / 2},${height - MARGIN.bottom + 30})`, + `translate(${width / 2},${height - MARGIN.bottom + 45})`, ) .style("text-anchor", "middle") + .style('font-size', '1.5em') .text(xLabel); // Create Y-axis and position it @@ -108,16 +111,19 @@ export default function Histogram({ svgElement .append("g") .attr("transform", `translate(${MARGIN.left},${MARGIN.top})`) - .call(yAxisGenerator); + .call(yAxisGenerator) + .selectAll('text') // Select all the text elements for x-axis ticks + .style('font-size', '1.5em'); // Add Y-axis label svgElement .append("text") .attr("transform", "rotate(-90)") - .attr("y", MARGIN.left - 40) + .attr("y", MARGIN.left - 70) .attr("x", 0 - height / 2) .attr("dy", "1em") .style("text-anchor", "middle") + .style('font-size', '1.5em') .text(yLabel); svgElement diff --git a/components/tools/toolViz/ScatterPlot.tsx b/components/tools/toolViz/ScatterPlot.tsx index 7fab9d1..753679b 100644 --- a/components/tools/toolViz/ScatterPlot.tsx +++ b/components/tools/toolViz/ScatterPlot.tsx @@ -7,7 +7,7 @@ import MoleculeStructure from '../toolComp/MoleculeStructure'; import { randomInt } from 'mathjs'; const Scatterplot = ({ data, colorProperty = [], hoverProp = [], xAxisTitle, yAxisTitle, id = [] }) => { - const margin = { top: 10, right: 30, bottom: 30, left: 60 }; + const margin = { top: 10, right: 20, bottom: 60, left: 70 }; const parentRef = useRef(null); const svgRef = useRef(); @@ -52,7 +52,7 @@ const Scatterplot = ({ data, colorProperty = [], hoverProp = [], xAxisTitle, yAx }, []); useEffect(() => { - if (dimensions.width === 0 && dimensions.height === 0){ + if (dimensions.width === 0 && dimensions.height === 0) { getSvgContainerSize(); } const width = dimensions.width - margin.left - margin.right; @@ -100,6 +100,12 @@ const Scatterplot = ({ data, colorProperty = [], hoverProp = [], xAxisTitle, yAx xAxis.call(d3.axisBottom(newX)); yAxis.call(d3.axisLeft(newY)); + yAxis.selectAll('text') // Select all the text elements for x-axis ticks + .style('font-size', '1.5em'); + + xAxis.selectAll('text') // Select all the text elements for x-axis ticks + .style('font-size', '1.5em'); + scatter .selectAll('circle') .attr('cx', (d) => newX(d.x)) @@ -111,13 +117,19 @@ const Scatterplot = ({ data, colorProperty = [], hoverProp = [], xAxisTitle, yAx .range([0, width]); const xAxis = svg.append('g') .attr('transform', `translate(0,${height})`) - .call(d3.axisBottom(x)); + .call(d3.axisBottom(x)) + + xAxis.selectAll('text') // Select all the text elements for x-axis ticks + .style('font-size', '1.5em'); const y = d3.scaleLinear() .domain([d3.min(data, d => d.y), d3.max(data, d => d.y)]) .range([height, 0]); const yAxis = svg.append('g') - .call(d3.axisLeft(y)); + .call(d3.axisLeft(y)) + + yAxis.selectAll('text') // Select all the text elements for x-axis ticks + .style('font-size', '1.5em'); svg.append('defs').append('clipPath') .attr('id', 'clip') @@ -143,18 +155,20 @@ const Scatterplot = ({ data, colorProperty = [], hoverProp = [], xAxisTitle, yAx .on('click', hoverProp.length > 0 ? (_, d) => findModalDetails(event, d) : null); // Conditionally attach click handler svg.append('text') - .attr('transform', `translate(${width / 2},${height + margin.top + 20})`) + .attr('transform', `translate(${width / 2},${height + margin.top + 40})`) .style('text-anchor', 'middle') .style('fill', 'var(--text-color)') + .style('font-size', '1.2em') .text(xAxisTitle); svg.append('text') .attr('transform', 'rotate(-90)') .attr('y', 0 - margin.left) .attr('x', 0 - height / 2) - .attr('dy', '1em') + .attr('dy', '1.4em') .style('text-anchor', 'middle') .style('fill', 'var(--text-color)') + .style('font-size', '1.2em') .text(yAxisTitle); }, [data, dimensions, selectedColorScale, bubbleSize]); diff --git a/components/utils/gb_ga/crossover.py b/components/utils/gb_ga/crossover.py new file mode 100644 index 0000000..4a51dc8 --- /dev/null +++ b/components/utils/gb_ga/crossover.py @@ -0,0 +1,190 @@ +''' +Written by Jan H. Jensen 2018 +''' +from rdkit import Chem +from rdkit.Chem import AllChem + +import random +import numpy as np + +from rdkit import rdBase +rdBase.DisableLog('rdApp.error') + +def cut(mol): + if not mol.HasSubstructMatch(Chem.MolFromSmarts('[*]-;!@[*]')): + return None + bis = random.choice(mol.GetSubstructMatches(Chem.MolFromSmarts('[*]-;!@[*]'))) #single bond not in ring + #print bis,bis[0],bis[1] + bs = [mol.GetBondBetweenAtoms(bis[0],bis[1]).GetIdx()] + + fragments_mol = Chem.FragmentOnBonds(mol,bs,addDummies=True,dummyLabels=[(1, 1)]) + + try: + fragments = Chem.GetMolFrags(fragments_mol,asMols=True) + return fragments + except: + return None + + +def cut_ring(mol): + for i in range(10): + if random.random() < 0.5: + if not mol.HasSubstructMatch(Chem.MolFromSmarts('[R]@[R]@[R]@[R]')): + return None + bis = random.choice(mol.GetSubstructMatches(Chem.MolFromSmarts('[R]@[R]@[R]@[R]'))) + bis = ((bis[0],bis[1]),(bis[2],bis[3]),) + else: + if not mol.HasSubstructMatch(Chem.MolFromSmarts('[R]@[R;!D2]@[R]')): + return None + bis = random.choice(mol.GetSubstructMatches(Chem.MolFromSmarts('[R]@[R;!D2]@[R]'))) + bis = ((bis[0],bis[1]),(bis[1],bis[2]),) + + #print bis + bs = [mol.GetBondBetweenAtoms(x,y).GetIdx() for x,y in bis] + + fragments_mol = Chem.FragmentOnBonds(mol,bs,addDummies=True,dummyLabels=[(1, 1),(1,1)]) + + try: + fragments = Chem.GetMolFrags(fragments_mol,asMols=True) + except: + return None + + if len(fragments) == 2: + return fragments + + return None + +def ring_OK(mol): + if not mol.HasSubstructMatch(Chem.MolFromSmarts('[R]')): + return True + + ring_allene = mol.HasSubstructMatch(Chem.MolFromSmarts('[R]=[R]=[R]')) + + cycle_list = mol.GetRingInfo().AtomRings() + max_cycle_length = max([ len(j) for j in cycle_list ]) + macro_cycle = max_cycle_length > 6 + + double_bond_in_small_ring = mol.HasSubstructMatch(Chem.MolFromSmarts('[r3,r4]=[r3,r4]')) + + return not ring_allene and not macro_cycle and not double_bond_in_small_ring + +def mol_OK(mol): + try: + Chem.SanitizeMol(mol) + test_mol = Chem.MolFromSmiles(Chem.MolToSmiles(mol)) + if test_mol == None: + return None + target_size = size_stdev*np.random.randn() + average_size #parameters set in GA_mol + if mol.GetNumAtoms() > 5 and mol.GetNumAtoms() < target_size: + return True + else: + return False + except: + return False + + +def crossover_ring(parent_A,parent_B): + ring_smarts = Chem.MolFromSmarts('[R]') + if not parent_A.HasSubstructMatch(ring_smarts) and not parent_B.HasSubstructMatch(ring_smarts): + return None + + rxn_smarts1 = ['[*:1]~[1*].[1*]~[*:2]>>[*:1]-[*:2]','[*:1]~[1*].[1*]~[*:2]>>[*:1]=[*:2]'] + rxn_smarts2 = ['([*:1]~[1*].[1*]~[*:2])>>[*:1]-[*:2]','([*:1]~[1*].[1*]~[*:2])>>[*:1]=[*:2]'] + for i in range(10): + fragments_A = cut_ring(parent_A) + fragments_B = cut_ring(parent_B) + #print [Chem.MolToSmiles(x) for x in list(fragments_A)+list(fragments_B)] + if fragments_A == None or fragments_B == None: + return None + + new_mol_trial = [] + for rs in rxn_smarts1: + rxn1 = AllChem.ReactionFromSmarts(rs) + new_mol_trial = [] + for fa in fragments_A: + for fb in fragments_B: + new_mol_trial.append(rxn1.RunReactants((fa,fb))[0]) + + new_mols = [] + for rs in rxn_smarts2: + rxn2 = AllChem.ReactionFromSmarts(rs) + for m in new_mol_trial: + m = m[0] + if mol_OK(m): + new_mols += list(rxn2.RunReactants((m,))) + + new_mols2 = [] + for m in new_mols: + m = m[0] + if mol_OK(m) and ring_OK(m): + new_mols2.append(m) + + if len(new_mols2) > 0: + return random.choice(new_mols2) + + return None + +def crossover_non_ring(parent_A,parent_B): + for i in range(10): + fragments_A = cut(parent_A) + fragments_B = cut(parent_B) + if fragments_A == None or fragments_B == None: + return None + rxn = AllChem.ReactionFromSmarts('[*:1]-[1*].[1*]-[*:2]>>[*:1]-[*:2]') + new_mol_trial = [] + for fa in fragments_A: + for fb in fragments_B: + new_mol_trial.append(rxn.RunReactants((fa,fb))[0]) + + new_mols = [] + for mol in new_mol_trial: + mol = mol[0] + if mol_OK(mol): + new_mols.append(mol) + + if len(new_mols) > 0: + return random.choice(new_mols) + + return None + +def crossover(parent_A,parent_B): + parent_smiles = [Chem.MolToSmiles(parent_A),Chem.MolToSmiles(parent_B)] + try: + Chem.Kekulize(parent_A,clearAromaticFlags=True) + Chem.Kekulize(parent_B,clearAromaticFlags=True) + except: + pass + for i in range(10): + if random.random() <= 0.5: + #print 'non-ring crossover' + new_mol = crossover_non_ring(parent_A,parent_B) + if new_mol != None: + new_smiles = Chem.MolToSmiles(new_mol) + if new_mol != None and new_smiles not in parent_smiles: + return new_mol + else: + #print 'ring crossover' + new_mol = crossover_ring(parent_A,parent_B) + if new_mol != None: + new_smiles = Chem.MolToSmiles(new_mol) + if new_mol != None and new_smiles not in parent_smiles: + return new_mol + + return None + +if __name__ == "__main__": + smiles1 = 'CC(C)(C)c1ccc2occ(CC(=O)Nc3ccccc3F)c2c1' + smiles2 = 'C[C@@H]1CC(Nc2cncc(-c3nncn3C)c2)C[C@@H](C)C1' + + smiles1 = 'Cc1ccc(S(=O)(=O)N2C(N)=C(C#N)C(c3ccc(Cl)cc3)C2C(=O)c2ccccc2)cc1' + smiles2 = 'CC(C#N)CNC(=O)c1cccc(Oc2cccc(C(F)(F)F)c2)c1' + + mol1 = Chem.MolFromSmiles(smiles1) + mol2 = Chem.MolFromSmiles(smiles2) + + child = crossover(mol1,mol2) + mutation_rate = 1.0 + #mutated_child = mutate(child,mutation_rate) + + for i in range(100): + child = crossover(mol1,mol2) \ No newline at end of file diff --git a/components/utils/gb_ga/crossover.ts b/components/utils/gb_ga/crossover.ts new file mode 100644 index 0000000..1709f7e --- /dev/null +++ b/components/utils/gb_ga/crossover.ts @@ -0,0 +1,218 @@ +import _ from 'lodash'; +import { initRDKit } from '../rdkit_loader'; +const rdkit = await initRDKit(); + +function cut(mol){ + if (!mol.HasSubstructMatch(('[*]-;!@[*]'))) { + return null; + } + const bis = _.choice(mol.GetSubstructMatches(rdkit.get_mol('[*]-;!@[*]'))); + const bs = [mol.GetBondBetweenAtoms(bis[0], bis[1]).GetIdx()]; + + const fragments_mol = Chem.FragmentOnBonds(mol, bs, true, [(1, 1)]); + + try { + const fragments = Chem.GetMolFrags(fragments_mol, true); + return fragments; + } catch { + return null; + } +} + +function cut_ring(mol: Mol): Mol[] | null { + for (let i = 0; i < 10; i++) { + if (random.random() < 0.5) { + if (!mol.HasSubstructMatch(Chem.MolFromSmarts('[R]@[R]@[R]@[R]'))) { + return null; + } + const bis = random.choice(mol.GetSubstructMatches(Chem.MolFromSmarts('[R]@[R]@[R]@[R]'))); + bis = ((bis[0], bis[1]), (bis[2], bis[3])); + } else { + if (!mol.HasSubstructMatch(Chem.MolFromSmarts('[R]@[R;!D2]@[R]'))) { + return null; + } + const bis = random.choice(mol.GetSubstructMatches(Chem.MolFromSmarts('[R]@[R;!D2]@[R]'))); + bis = ((bis[0], bis[1]), (bis[1], bis[2])); + } + + const bs = [mol.GetBondBetweenAtoms(x, y).GetIdx() for x, y in bis]; + + const fragments_mol = Chem.FragmentOnBonds(mol, bs, true, [(1, 1), (1, 1)]); + + try { + const fragments = Chem.GetMolFrags(fragments_mol, true); + return fragments; + } catch { + return null; + } + + if (fragments.length === 2) { + return fragments; + } + } + + return null; +} + +function ring_OK(mol: Mol): boolean { + if (!mol.HasSubstructMatch(Chem.MolFromSmarts('[R]'))) { + return true; + } + + const ring_allene = mol.HasSubstructMatch(Chem.MolFromSmarts('[R]=[R]=[R]')); + + const cycle_list = mol.GetRingInfo().AtomRings(); + const max_cycle_length = Math.max(...cycle_list.map((j) => j.length)); + const macro_cycle = max_cycle_length > 6; + + const double_bond_in_small_ring = mol.HasSubstructMatch(Chem.MolFromSmarts('[r3,r4]=[r3,r4]')); + + return !ring_allene && !macro_cycle && !double_bond_in_small_ring; +} + +function mol_OK(mol: Mol): boolean { + try { + Chem.SanitizeMol(mol); + const test_mol = Chem.MolFromSmiles(Chem.MolToSmiles(mol)); + if (test_mol === null) { + return false; + } + const target_size = size_stdev * np.random.randn() + average_size; + if (mol.GetNumAtoms() > 5 && mol.GetNumAtoms() < target_size) { + return true; + } else { + return false; + } + } catch { + return false; + } +} + +function crossover_ring(parent_A: Mol, parent_B: Mol): Mol | null { + const ring_smarts = Chem.MolFromSmarts('[R]'); + if (!parent_A.HasSubstructMatch(ring_smarts) && !parent_B.HasSubstructMatch(ring_smarts)) { + return null; + } + + const rxn_smarts1 = ['[*:1]~[1*].[1*]~[*:2]>>[*:1]-[*:2]', '[*:1]~[1*].[1*]~[*:2]>>[*:1]=[*:2]']; + const rxn_smarts2 = ['([*:1]~[1*].[1*]~[*:2])>>[*:1]-[*:2]', '([*:1]~[1*].[1*]~[*:2])>>[*:1]=[*:2]']; + for (let i = 0; i < 10; i++) { + const fragments_A = cut_ring(parent_A); + const fragments_B = cut_ring(parent_B); + if (fragments_A === null || fragments_B === null) { + return null; + } + + let new_mol_trial = []; + for (const rs of rxn_smarts1) { + const rxn1 = AllChem.ReactionFromSmarts(rs); + new_mol_trial = []; + for (const fa of fragments_A) { + for (const fb of fragments_B) { + new_mol_trial.push(rxn1.RunReactants((fa, fb))[0]); + } + } + } + + const new_mols = []; + for (const rs of rxn_smarts2) { + const rxn2 = AllChem.ReactionFromSmarts(rs); + for (const m of new_mol_trial) { + const mol = m[0]; + if (mol_OK(mol)) { + new_mols += list(rxn2.RunReactants((mol,))); + } + } + } + + const new_mols2 = []; + for (const m of new_mols) { + const mol = m[0]; + if (mol_OK(mol) && ring_OK(mol)) { + new_mols2.push(mol); + } + } + + if (new_mols2.length > 0) { + return random.choice(new_mols2); + } + } + + return null; +} + +function crossover_non_ring(parent_A: Mol, parent_B: Mol): Mol | null { + for (let i = 0; i < 10; i++) { + const fragments_A = cut(parent_A); + const fragments_B = cut(parent_B); + if (fragments_A === null || fragments_B === null) { + return null; + } + const rxn = AllChem.ReactionFromSmarts('[*:1]-[1*].[1*]-[*:2]>>[*:1]-[*:2]'); + const new_mol_trial = []; + for (const fa of fragments_A) { + for (const fb of fragments_B) { + new_mol_trial.push(rxn.RunReactants((fa, fb))[0]); + } + } + + const new_mols = []; + for (const mol of new_mol_trial) { + const m = mol[0]; + if (mol_OK(m)) { + new_mols.push(m); + } + } + + if (new_mols.length > 0) { + return random.choice(new_mols); + } + } + + return null; +} + +function crossover(parent_A: Mol, parent_B: Mol): Mol | null { + const parent_smiles = [Chem.MolToSmiles(parent_A), Chem.MolToSmiles(parent_B)]; + try { + Chem.Kekulize(parent_A, true); + Chem.Kekulize(parent_B, true); + } catch { + // pass + } + for (let i = 0; i < 10; i++) { + if (random.random() <= 0.5) { + const new_mol = crossover_non_ring(parent_A, parent_B); + if (new_mol !== null) { + const new_smiles = Chem.MolToSmiles(new_mol); + if (new_mol !== null && !parent_smiles.includes(new_smiles)) { + return new_mol; + } + } + } else { + const new_mol = crossover_ring(parent_A, parent_B); + if (new_mol !== null) { + const new_smiles = Chem.MolToSmiles(new_mol); + if (new_mol !== null && !parent_smiles.includes(new_smiles)) { + return new_mol; + } + } + } + } + + return null; +} + +// const smiles1 = 'CC(C)(C)c1ccc2occ(CC(=O)Nc3ccccc3F)c2c1'; +// const smiles2 = 'C[C@@H]1CC(Nc2cncc(-c3nncn3C)c2)C[C@@H](C)C1'; + +// const mol1 = Chem.MolFromSmiles(smiles1); +// const mol2 = Chem.MolFromSmiles(smiles2); + +// const child = crossover(mol1, mol2); +// const mutation_rate = 1.0; +// // const mutated_child = mutate(child, mutation_rate); + +// for (let i = 0; i < 100; i++) { +// const child = crossover(mol1, mol2); +// } \ No newline at end of file diff --git a/components/utils/gb_ga/mutate.ts b/components/utils/gb_ga/mutate.ts new file mode 100644 index 0000000..a36c3de --- /dev/null +++ b/components/utils/gb_ga/mutate.ts @@ -0,0 +1,155 @@ +import _ from "lodash"; + +function selectChoice(choices: any[], probabilities: any[]) { + let randomNumber = Math.random(); + let cumulativeProbability = 0; + + for (let i = 0; i < choices.length; i++) { + cumulativeProbability += probabilities[i]; + if (randomNumber <= cumulativeProbability) { + return choices[i]; + } + } +} + +const range = n => Array.from({ length: n }, (_, i) => i); + +function deleteAtom() { + const choices = ['[*:1]~[D1]>>[*:1]', '[*:1]~[D2]~[*:2]>>[*:1]-[*:2]', + '[*:1]~[D3](~[*;!H0:2])~[*:3]>>[*:1]-[*:2]-[*:3]', + '[*:1]~[D4](~[*;!H0:2])(~[*;!H0:3])~[*:4]>>[*:1]-[*:2]-[*:3]-[*:4]', + '[*:1]~[D4](~[*;!H0;!H1:2])(~[*:3])~[*:4]>>[*:1]-[*:2](-[*:3])-[*:4]']; + const p = [0.25, 0.25, 0.25, 0.1875, 0.0625]; + return selectChoice(choices, p); +} + +function appendAtom() { + const choices = [['single', ['C', 'N', 'O', 'F', 'S', 'Cl', 'Br'], 7 * (1.0 / 7.0)], + ['double', ['C', 'N', 'O'], 3 * (1.0 / 3.0)], + ['triple', ['C', 'N'], 2 * (1.0 / 2.0)]]; + const p_BO = [0.60, 0.35, 0.05]; + + const index = selectChoice(range(3), p_BO); + const [BO, atom_list, p] = choices[index]; + + const newAtom = selectChoice([atom_list], [p]); + + let rxn_smarts; + + if (BO == 'single') { + rxn_smarts = '[*;!H0:1]>>[*:1]X'.replace('X', '-' + newAtom) + } + if (BO == 'double') { + rxn_smarts = '[*;!H0;!H1:1]>>[*:1]X'.replace('X', '=' + newAtom) + } + + if (BO == 'triple') { + rxn_smarts = '[*;H3:1]>>[*:1]X'.replace('X', '#' + newAtom) + } + return rxn_smarts; +} + +function insertAtom() { + const choices = [['single', ['C', 'N', 'O', 'S'], [1.0 / 4.0]], + ['double', ['C', 'N'], [1.0 / 2.0]], + ['triple', ['C'], [1.0]]]; + const p_BO = [0.60, 0.35, 0.05]; + + const index = selectChoice(range(3), p_BO); + const [BO, atom_list, p] = choices[index]; + + const newAtom = selectChoice([atom_list], [p]); + + let rxn_smarts; + + if (BO == 'single') { + rxn_smarts = '[*:1]~[*:2]>>[*:1]X[*:2]'.replace('X', newAtom) + } + if (BO == 'double') { + rxn_smarts = '[*;!H0:1]~[*:2]>>[*:1]=X-[*:2]'.replace('X', newAtom) + } + + if (BO == 'triple') { + rxn_smarts = '[*;!R;!H1;!H0:1]~[*:2]>>[*:1]#X-[*:2]'.replace('X', newAtom) + } + return rxn_smarts; +} + +function changeBondOrder() { + const choices = ['[*:1]!-[*:2]>>[*:1]-[*:2]', '[*;!H0:1]-[*;!H0:2]>>[*:1]=[*:2]', + '[*:1]#[*:2]>>[*:1]=[*:2]', '[*;!R;!H1;!H0:1]~[*:2]>>[*:1]#[*:2]'] + const p = [0.45, 0.45, 0.05, 0.05] + return selectChoice(choices, p) +} + +function deleteCyclicBond() { + return '[*:1]@[*:2]>>([*:1].[*:2])' +} + +function addRing() { + const choices = ['[*;!r;!H0:1]~[*;!r:2]~[*;!r;!H0:3]>>[*:1]1~[*:2]~[*:3]1', + '[*;!r;!H0:1]~[*!r:2]~[*!r:3]~[*;!r;!H0:4]>>[*:1]1~[*:2]~[*:3]~[*:4]1', + '[*;!r;!H0:1]~[*!r:2]~[*:3]~[*:4]~[*;!r;!H0:5]>>[*:1]1~[*:2]~[*:3]~[*:4]~[*:5]1', + '[*;!r;!H0:1]~[*!r:2]~[*:3]~[*:4]~[*!r:5]~[*;!r;!H0:6]>>[*:1]1~[*:2]~[*:3]~[*:4]~[*:5]~[*:6]1'] + const p = [0.05, 0.05, 0.45, 0.45] + return selectChoice(choices, p) +} + +function changeAtom(mol: any, rdkit: any) { + const choices = ['#6', '#7', '#8', '#9', '#16', '#17', '#35'] + const p = [0.15, 0.15, 0.14, 0.14, 0.14, 0.14, 0.14] + + let X = selectChoice(choices, p); + let Y + + while (!mol.get_substruct_match(rdkit.get_mol('[' + X + ']'))){ + X = selectChoice(choices, p) + Y = selectChoice(choices, p) + } + + while (Y == X){ + Y = selectChoice(choices, p) + } + + return '[X:1]>>[Y:1]'.replace('X', X).replace('Y', Y) +} + +function mutate(mol: any, mutation_rate: number, rdkit: any) { + if (Math.random() > mutation_rate) { + return mol; + } + mol = rdkit.get_mol(mol.get_smiles(), JSON.stringify({ kekulize: false })); + const p = [0.15, 0.14, 0.14, 0.14, 0.14, 0.14, 0.15]; + for (let i = 0; i < 10; i++) { + const rxn_smarts_list = ['', '', '', '', '', '', '']; + rxn_smarts_list[0] = insertAtom(); + rxn_smarts_list[1] = changeBondOrder(); + rxn_smarts_list[2] = deleteCyclicBond(); + rxn_smarts_list[3] = addRing(); + rxn_smarts_list[4] = deleteAtom(); + rxn_smarts_list[5] = changeAtom(mol, rdkit); + rxn_smarts_list[6] = appendAtom(); + const rxn_smarts = selectChoice(rxn_smarts_list, p); + + //console.log('mutation', rxn_smarts); + + const rxn = rdkit.get_rxn(rxn_smarts); + + const new_mol_trial = rxn.RunReactants([mol]); + + const new_mols = []; + for (const m of new_mol_trial) { + const new_mol = m[0]; + //console.log Chem.MolToSmiles(mol),mol_OK(mol); + if (mol_OK(new_mol) && ring_OK(new_mol)) { + new_mols.push(new_mol); + } + } + + if (new_mols.length > 0) { + return _.sample(new_mols); + } + } + + return null; +} \ No newline at end of file