NGL@1.0.0-beta.7 Home Manual Reference Source Gallery

src/selection/selection-test.js

/**
 * @file Selection Test
 * @author Alexander Rose <alexander.rose@weirdbyte.de>
 * @private
 */

import { binarySearchIndexOf, rangeInSortedArray } from '../utils.js'
import { kwd, AtomOnlyKeywords, ChainKeywords } from './selection-constants.js'

function atomTestFn (a, s) {
  // returning -1 means the rule is not applicable
  if (s.atomname === undefined && s.element === undefined &&
    s.altloc === undefined && s.atomindex === undefined &&
    s.keyword === undefined && s.inscode === undefined &&
    s.resname === undefined && s.sstruc === undefined &&
    s.resno === undefined && s.chainname === undefined &&
    s.model === undefined
  ) return -1

  if (s.keyword !== undefined) {
    if (s.keyword === kwd.BACKBONE && !a.isBackbone()) return false
    if (s.keyword === kwd.SIDECHAIN && !a.isSidechain()) return false
    if (s.keyword === kwd.BONDED && !a.isBonded()) return false
    if (s.keyword === kwd.RING && !a.isRing()) return false

    if (s.keyword === kwd.HETERO && !a.isHetero()) return false
    if (s.keyword === kwd.PROTEIN && !a.isProtein()) return false
    if (s.keyword === kwd.NUCLEIC && !a.isNucleic()) return false
    if (s.keyword === kwd.RNA && !a.isRna()) return false
    if (s.keyword === kwd.DNA && !a.isDna()) return false
    if (s.keyword === kwd.POLYMER && !a.isPolymer()) return false
    if (s.keyword === kwd.WATER && !a.isWater()) return false
    if (s.keyword === kwd.HELIX && !a.isHelix()) return false
    if (s.keyword === kwd.SHEET && !a.isSheet()) return false
    if (s.keyword === kwd.TURN && !a.isTurn()) return false
    if (s.keyword === kwd.ION && !a.isIon()) return false
    if (s.keyword === kwd.SACCHARIDE && !a.isSaccharide()) return false
  }

  if (s.atomname !== undefined && s.atomname !== a.atomname) return false
  if (s.element !== undefined && s.element !== a.element) return false
  if (s.altloc !== undefined && s.altloc !== a.altloc) return false

  if (s.atomindex !== undefined &&
      binarySearchIndexOf(s.atomindex, a.index) < 0
  ) return false

  if (s.resname !== undefined) {
    if (Array.isArray(s.resname)) {
      if (!s.resname.includes(a.resname)) return false
    } else {
      if (s.resname !== a.resname) return false
    }
  }
  if (s.sstruc !== undefined && s.sstruc !== a.sstruc) return false
  if (s.resno !== undefined) {
    if (Array.isArray(s.resno) && s.resno.length === 2) {
      if (s.resno[0] > a.resno || s.resno[1] < a.resno) return false
    } else {
      if (s.resno !== a.resno) return false
    }
  }
  if (s.inscode !== undefined && s.inscode !== a.inscode) return false

  if (s.chainname !== undefined && s.chainname !== a.chainname) return false
  if (s.model !== undefined && s.model !== a.modelIndex) return false

  return true
}

function residueTestFn (r, s) {
  // returning -1 means the rule is not applicable
  if (s.resname === undefined && s.resno === undefined && s.inscode === undefined &&
      s.sstruc === undefined && s.model === undefined && s.chainname === undefined &&
      s.atomindex === undefined &&
      (s.keyword === undefined || AtomOnlyKeywords.includes(s.keyword))
  ) return -1

  if (s.keyword !== undefined) {
    if (s.keyword === kwd.HETERO && !r.isHetero()) return false
    if (s.keyword === kwd.PROTEIN && !r.isProtein()) return false
    if (s.keyword === kwd.NUCLEIC && !r.isNucleic()) return false
    if (s.keyword === kwd.RNA && !r.isRna()) return false
    if (s.keyword === kwd.DNA && !r.isDna()) return false
    if (s.keyword === kwd.POLYMER && !r.isPolymer()) return false
    if (s.keyword === kwd.WATER && !r.isWater()) return false
    if (s.keyword === kwd.HELIX && !r.isHelix()) return false
    if (s.keyword === kwd.SHEET && !r.isSheet()) return false
    if (s.keyword === kwd.TURN && !r.isTurn()) return false
    if (s.keyword === kwd.ION && !r.isIon()) return false
    if (s.keyword === kwd.SACCHARIDE && !r.isSaccharide()) return false
  }

  if (s.atomindex !== undefined &&
      rangeInSortedArray(s.atomindex, r.atomOffset, r.atomEnd) === 0
  ) return false

  if (s.resname !== undefined) {
    if (Array.isArray(s.resname)) {
      if (!s.resname.includes(r.resname)) return false
    } else {
      if (s.resname !== r.resname) return false
    }
  }
  if (s.sstruc !== undefined && s.sstruc !== r.sstruc) return false
  if (s.resno !== undefined) {
    if (Array.isArray(s.resno) && s.resno.length === 2) {
      if (s.resno[0] > r.resno || s.resno[1] < r.resno) return false
    } else {
      if (s.resno !== r.resno) return false
    }
  }
  if (s.inscode !== undefined && s.inscode !== r.inscode) return false

  if (s.chainname !== undefined && s.chainname !== r.chainname) return false
  if (s.model !== undefined && s.model !== r.modelIndex) return false

  return true
}

function chainTestFn (c, s) {
  // returning -1 means the rule is not applicable
  if (s.chainname === undefined && s.model === undefined && s.atomindex === undefined &&
      (s.keyword === undefined || !ChainKeywords.includes(s.keyword) || !c.entity)
  ) return -1

  if (s.keyword !== undefined) {
    if (s.keyword === kwd.POLYMER && !c.entity.isPolymer()) return false
    if (s.keyword === kwd.WATER && !c.entity.isWater()) return false
  }

  if (s.atomindex !== undefined &&
      rangeInSortedArray(s.atomindex, c.atomOffset, c.atomEnd) === 0
  ) return false

  if (s.chainname !== undefined && s.chainname !== c.chainname) return false

  if (s.model !== undefined && s.model !== c.modelIndex) return false

  return true
}

function modelTestFn (m, s) {
  // returning -1 means the rule is not applicable
  if (s.model === undefined && s.atomindex === undefined) return -1

  if (s.atomindex !== undefined &&
      rangeInSortedArray(s.atomindex, m.atomOffset, m.atomEnd) === 0
  ) return false

  if (s.model !== undefined && s.model !== m.index) return false

  return true
}

function makeTest (selection, fn) {
  if (selection === null) return false
  if (selection.error) return false

  const n = selection.rules.length
  if (n === 0) return false

  const t = !selection.negate
  const f = !!selection.negate

  const subTests = []
  for (let i = 0; i < n; ++i) {
    const s = selection.rules[ i ]
    if (s.hasOwnProperty('operator')) {
      subTests[ i ] = makeTest(s, fn)
    }
  }

  // ( x and y ) can short circuit on false
  // ( x or y ) can short circuit on true
  // not ( x and y )

  return function test (entity) {
    const and = selection.operator === 'AND'
    let na = false

    for (let i = 0; i < n; ++i) {
      const s = selection.rules[ i ]
      let ret

      if (s.hasOwnProperty('operator')) {
        if (subTests[ i ]) {
          ret = subTests[ i ](entity)
        } else {
          ret = -1
        }

        if (ret === -1) {
          na = true
          continue
        } else if (ret === true) {
          if (and) { continue } else { return t }
        } else {
          if (and) { return f } else { continue }
        }
      } else {
        if (s.keyword === kwd.ALL) {
          if (and) { continue } else { return t }
        }

        ret = fn(entity, s)

        // console.log( entity.qualifiedName(), ret, s, selection.negate, "t", t, "f", f )

        if (ret === -1) {
          na = true
          continue
        } else if (ret === true) {
          if (and) { continue } else { return t }
        } else {
          if (and) { return f } else { continue }
        }
      }
    }

    if (na) {
      return -1
    } else {
      if (and) { return t } else { return f }
    }
  }
}

function filter (selection, fn) {
  if (selection.error) return selection

  const n = selection.rules.length
  if (n === 0) return selection

  const filtered = {
    operator: selection.operator,
    rules: []
  }
  if (selection.hasOwnProperty('negate')) {
    filtered.negate = selection.negate
  }

  for (let i = 0; i < n; ++i) {
    const s = selection.rules[ i ]
    if (s.hasOwnProperty('operator')) {
      const fs = filter(s, fn)
      if (fs !== null) filtered.rules.push(fs)
    } else if (!fn(s)) {
      filtered.rules.push(s)
    }
  }

  if (filtered.rules.length > 0) {
    // TODO maybe the filtered rules could be returned
    // in some case, but the way how tests are applied
    // e.g. when traversing a structure would also need
    // to change
    return selection
    // return filtered;
  } else {
    return null
  }
}

function makeAtomTest (selection, atomOnly) {
  if (atomOnly) {
    selection = filter(selection, function (s) {
      if (s.keyword !== undefined && !AtomOnlyKeywords.includes(s.keyword)) return true
      if (s.model !== undefined) return true
      if (s.chainname !== undefined) return true
      if (s.resname !== undefined) return true
      if (s.resno !== undefined) return true
      if (s.sstruc !== undefined) return true
      return false
    })
  }
  return makeTest(selection, atomTestFn)
}

function makeResidueTest (selection, residueOnly) {
  if (residueOnly) {
    selection = filter(selection, function (s) {
      if (s.keyword !== undefined && AtomOnlyKeywords.includes(s.keyword)) return true
      if (s.model !== undefined) return true
      if (s.chainname !== undefined) return true
      if (s.atomname !== undefined) return true
      if (s.element !== undefined) return true
      if (s.altloc !== undefined) return true
      return false
    })
  }
  return makeTest(selection, residueTestFn)
}

function makeChainTest (selection, chainOnly) {
  if (chainOnly) {
    selection = filter(selection, function (s) {
      if (s.keyword !== undefined && !ChainKeywords.includes(s.keyword)) return true
      // if( s.model!==undefined ) return true;
      if (s.resname !== undefined) return true
      if (s.resno !== undefined) return true
      if (s.atomname !== undefined) return true
      if (s.element !== undefined) return true
      if (s.altloc !== undefined) return true
      if (s.sstruc !== undefined) return true
      if (s.inscode !== undefined) return true
      return false
    })
  }
  return makeTest(selection, chainTestFn)
}

function makeModelTest (selection, modelOnly) {
  if (modelOnly) {
    selection = filter(selection, function (s) {
      if (s.keyword !== undefined) return true
      if (s.chainname !== undefined) return true
      if (s.resname !== undefined) return true
      if (s.resno !== undefined) return true
      if (s.atomname !== undefined) return true
      if (s.element !== undefined) return true
      if (s.altloc !== undefined) return true
      if (s.sstruc !== undefined) return true
      if (s.inscode !== undefined) return true
      return false
    })
  }
  return makeTest(selection, modelTestFn)
}

export {
  makeAtomTest,
  makeResidueTest,
  makeChainTest,
  makeModelTest
}