Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#311 sgroup invert bugfix #331

Merged
merged 5 commits into from
Feb 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/ketcher-react/src/script/chem/molfile/molfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ class Molfile {
this.writePaddedNumber(q, 3)
this.writeCR()

const parentId = this.molecule.sGroupForest.parent.get(id)
const parentId = this.molecule.sGroupForest.parent.get(id) as number
if (parentId >= 0) {
this.write('M SPL')
this.writePaddedNumber(1, 3)
Expand Down
1 change: 1 addition & 0 deletions packages/ketcher-react/src/script/chem/struct/atom.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ function Atom(params) {
ifDef(this, params, 'stereoParity', def('stereoParity')) // {string | null} "<abs|and|or>-<group>"

this.atomList = params.atomList ? new AtomList(params.atomList) : null
/** @type {number[]} */
this.neighbors = [] // set of half-bonds having this atom as their origin
this.badConn = false
}
Expand Down
3 changes: 2 additions & 1 deletion packages/ketcher-react/src/script/chem/struct/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ function Struct() {
this.atoms = new Pool()
this.bonds = new Pool()
this.sgroups = new Pool()
/** @type {Pool<HalfBond>} */
this.halfBonds = new Pool()
this.loops = new Pool()
this.isReaction = false
Expand Down Expand Up @@ -464,7 +465,7 @@ Struct.prototype.simpleObjectSetPos = function (id, pos) {
}

/**
* @param atomSet { Pile<number> }
* @param [atomSet] { Pile<number> }
* @returns {*}
*/
Struct.prototype.getCoordBoundingBox = function (atomSet) {
Expand Down
242 changes: 129 additions & 113 deletions packages/ketcher-react/src/script/chem/struct/sgforest.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,138 +17,154 @@
import Pile from '../../util/pile'
import SGroup from './sgroup'

function SGroupForest() {
this.parent = new Map() // child id -> parent id
this.children = new Map() // parent id -> list of child ids
this.children.set(-1, []) // extra root node
this.atomSets = new Map()
}

// returns an array or s-group ids in the order of breadth-first search
SGroupForest.prototype.getSGroupsBFS = function () {
const order = []
let id = -1
let queue = Array.from(this.children.get(-1))
while (queue.length > 0) {
id = queue.shift()
queue = queue.concat(this.children.get(id))
order.push(id)
class SGroupForest {
constructor() {
/** node id -> parent id
* @type {Map<number, number>}
* */
this.parent = new Map()
/** node id -> list of child ids
* @type {Map<number, number[]>}
* */
this.children = new Map()

this.children.set(-1, []) // extra root node
this.atomSets = new Map()
}
return order
}

export function checkOverlapping(struct, atoms) {
const sgroups = atoms.reduce((res, aid) => {
const atom = struct.atoms.get(aid)
return res.union(atom.sgs)
}, new Pile())

return Array.from(sgroups).some(sid => {
const sg = struct.sgroups.get(sid)
if (sg.type === 'DAT') return false
const sgAtoms = SGroup.getAtoms(struct, sg)
// returns an array or s-group ids in the order of breadth-first search
getSGroupsBFS() {
const order = []
let id = -1
let queue = Array.from(this.children.get(-1))
while (queue.length > 0) {
id = queue.shift()
queue = queue.concat(this.children.get(id))
order.push(id)
}
return order
}

return sgAtoms.length < atoms.length
? sgAtoms.findIndex(aid => atoms.indexOf(aid) === -1) >= 0
: atoms.findIndex(aid => sgAtoms.indexOf(aid) === -1) >= 0
})
}
getAtomSetRelations(newId, atoms) {
// find the lowest superset in the hierarchy
const isStrictSuperset = new Map()
const isSubset = new Map()

this.atomSets.delete(newId)

this.atomSets.forEach((atomSet, id) => {
isSubset.set(id, atomSet.isSuperset(atoms))
isStrictSuperset.set(
id,
atoms.isSuperset(atomSet) && !atomSet.equals(atoms)
)
})

const parents = Array.from(this.atomSets.keys()).filter(sgid => {
if (!isSubset.get(sgid)) return false
return (
this.children.get(sgid).findIndex(childId => isSubset.get(childId)) < 0
)
})

const children = Array.from(this.atomSets.keys()).filter(
id =>
isStrictSuperset.get(id) && !isStrictSuperset.get(this.parent.get(id))
)

SGroupForest.prototype.getAtomSetRelations = function (newId, atoms) {
// find the lowest superset in the hierarchy
const isStrictSuperset = new Map()
const isSubset = new Map()
return {
children,
parent: parents.length === 0 ? -1 : parents[0]
}
}

this.atomSets.delete(newId)
getPathToRoot(sgid) {
const path = []
for (let id = sgid; id >= 0; id = this.parent.get(id)) {
console.assert(path.indexOf(id) < 0, 'SGroupForest: loop detected')
path.push(id)
}
return path
}

this.atomSets.forEach((atomSet, id) => {
isSubset.set(id, atomSet.isSuperset(atoms))
isStrictSuperset.set(
id,
atoms.isSuperset(atomSet) && !atomSet.equals(atoms)
)
})
/**
* @param {number} [parent]
* @param {number[]} [children]
* */
insert({ id, atoms }, parent, children) {
console.assert(!this.parent.has(id), 'sgid already present in the forest')
console.assert(!this.children.has(id), 'sgid already present in the forest')

if (!parent || !children) {
// if these are not provided, deduce automatically
const guess = this.getAtomSetRelations(id, new Pile(atoms))
parent = guess.parent
children = guess.children
}

// TODO: make children Map<int, Pile> instead of Map<int, []>?
children.forEach(childId => {
this.resetParentLink(childId, id)
})
this.children.set(id, children)
this.parent.set(id, parent)
this.children.get(parent).push(id)
this.atomSets.set(id, new Pile(atoms))

return { parent, children }
}

const parents = Array.from(this.atomSets.keys()).filter(sgid => {
if (!isSubset.get(sgid)) return false
return (
this.children.get(sgid).findIndex(childId => isSubset.get(childId)) < 0
)
})
resetParentLink(childId, id) {
const parentId = this.parent.get(childId)
if (typeof parentId === 'undefined') {
return
}

const children = Array.from(this.atomSets.keys()).filter(
id => isStrictSuperset.get(id) && !isStrictSuperset.get(this.parent.get(id))
)
const childs = this.children.get(parentId)
if (!childs) {
return
}

return {
children,
parent: parents.length === 0 ? -1 : parents[0]
const childIndex = childs.indexOf(childId)
childs.splice(childIndex, 1)
this.parent.set(childId, id)
}
}

SGroupForest.prototype.getPathToRoot = function (sgid) {
const path = []
for (let id = sgid; id >= 0; id = this.parent.get(id)) {
console.assert(path.indexOf(id) < 0, 'SGroupForest: loop detected')
path.push(id)
}
return path
}
remove(id) {
console.assert(this.parent.has(id), 'sgid is not in the forest')
console.assert(this.children.has(id), 'sgid is not in the forest')

SGroupForest.prototype.insert = function (
{ id, atoms },
parent /* int, optional */,
children /* [int], optional */
) {
console.assert(!this.parent.has(id), 'sgid already present in the forest')
console.assert(!this.children.has(id), 'sgid already present in the forest')

if (!parent || !children) {
// if these are not provided, deduce automatically
const guess = this.getAtomSetRelations(id, new Pile(atoms))
parent = guess.parent
children = guess.children
}
const parentId = this.parent.get(id)
const childs = this.children.get(parentId)
this.children.get(id).forEach(childId => {
this.parent.set(childId, parentId)
this.children.get(parentId).push(childId)
})

// TODO: make children Map<int, Pile> instead of Map<int, []>?
children.forEach(childId => {
// reset parent links
var childs = this.children.get(this.parent.get(childId))
var i = childs.indexOf(childId)
console.assert(
i >= 0 && childs.indexOf(childId, i + 1) < 0,
'Assertion failed'
) // one element
const i = childs.indexOf(id)
childs.splice(i, 1)
this.parent.set(childId, id)
})
this.children.set(id, children)
this.parent.set(id, parent)
this.children.get(parent).push(id)
this.atomSets.set(id, new Pile(atoms))

return { parent, children }
this.children.delete(id)
this.parent.delete(id)
this.atomSets.delete(id)
}
}

SGroupForest.prototype.remove = function (id) {
console.assert(this.parent.has(id), 'sgid is not in the forest')
console.assert(this.children.has(id), 'sgid is not in the forest')

const parentId = this.parent.get(id)
this.children.get(id).forEach(childId => {
// reset parent links
this.parent.set(childId, parentId)
this.children.get(parentId).push(childId)
})
export function checkOverlapping(struct, atoms) {
const sgroups = atoms.reduce((res, aid) => {
const atom = struct.atoms.get(aid)
return res.union(atom.sgs)
}, new Pile())

const childs = this.children.get(parentId)
const i = childs.indexOf(id)
console.assert(i >= 0 && childs.indexOf(id, i + 1) < 0, 'Assertion failed') // one element
childs.splice(i, 1)
return Array.from(sgroups).some(sid => {
const sg = struct.sgroups.get(sid)
if (sg.type === 'DAT') return false
const sgAtoms = SGroup.getAtoms(struct, sg)

this.children.delete(id)
this.parent.delete(id)
this.atomSets.delete(id)
return sgAtoms.length < atoms.length
? sgAtoms.findIndex(aid => atoms.indexOf(aid) === -1) >= 0
: atoms.findIndex(aid => sgAtoms.indexOf(aid) === -1) >= 0
})
}

export default SGroupForest
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import molfile from '../../chem/molfile'
import { Bond } from '../../chem/struct'

import op from '../operations/op'
import { BondAttr } from '../operations'
import Action from '../shared/action'

/**
Expand Down Expand Up @@ -109,7 +109,7 @@ function fromAromatize(restruct, astruct, bondMap) {
astruct.bonds.forEach((bond, bid) => {
if (bond.type !== Bond.PATTERN.TYPE.AROMATIC) return
action.addOp(
new op.BondAttr(
new BondAttr(
bondMap.get(bid),
'type',
Bond.PATTERN.TYPE.AROMATIC
Expand All @@ -131,7 +131,7 @@ function fromDearomatize(restruct, dastruct, bondMap) {

dastruct.bonds.forEach((bond, bid) => {
action.addOp(
new op.BondAttr(bondMap.get(bid), 'type', bond.type).perform(restruct)
new BondAttr(bondMap.get(bid), 'type', bond.type).perform(restruct)
)
})

Expand Down
Loading