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

Add qualifiers #129

Merged
merged 39 commits into from
Dec 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
5cc9d04
create mega qedge
rjawesome Sep 29, 2022
d3bfab4
Work on freezing and unfreezing
rjawesome Oct 1, 2022
6db8ada
Add freezing for QNode
rjawesome Oct 4, 2022
06b39bd
Migrate mega q edge to q edge
rjawesome Oct 4, 2022
752d31c
remove tests for removed storeEdges function
rjawesome Oct 4, 2022
610fea3
minor fixes
rjawesome Oct 4, 2022
fd43ae2
fix some tests
rjawesome Oct 11, 2022
f519789
feat: support x-bte qualifiers
tokebe Oct 14, 2022
8a52c3a
use input/output node (needs more testing)
rjawesome Oct 15, 2022
11446ec
Fixes for Input/Output
rjawesome Oct 20, 2022
22bdeed
Move unfreeze to constructor
rjawesome Oct 21, 2022
c629e08
fix: allow qualifier_constraints
tokebe Oct 21, 2022
c475e0a
style: formatting
tokebe Oct 21, 2022
31a22b0
fix: merge latest from main, fix some behavior, fix tests
tokebe Oct 24, 2022
bf8413d
fix: resolve check, qXEdge -> qEdge, formatting
tokebe Oct 31, 2022
0cb8df8
Merge branch 'combine-qedge' of https://github.com/biothings/bte_trap…
tokebe Oct 31, 2022
11b5858
test: fix test references
tokebe Oct 31, 2022
cf80a4e
test: rename test data file
tokebe Oct 31, 2022
04d5456
Merge branch 'combine-qedge' of https://github.com/biothings/bte_trap…
tokebe Oct 31, 2022
033d30c
fix: qualifiers optional for ease of test writing
tokebe Oct 31, 2022
97adb19
feat: support qualifier-constraints in QEdge
tokebe Nov 3, 2022
89e5f2f
fix: undefined qualifier support
tokebe Nov 3, 2022
3a0ca1a
adjust templates for predicate changes with qualifier-refactor
colleenXu Nov 5, 2022
7ce6956
fix: strip biolink prefix on qualifier constraints
tokebe Nov 7, 2022
499853d
Merge branch 'add-qualifiers' of https://github.com/biothings/bte_tra…
tokebe Nov 7, 2022
db5b9bc
fix: pass qualifier constraints
tokebe Nov 15, 2022
d995447
fix: qualifier reversing
tokebe Nov 15, 2022
fc0c642
feat: qualifier duplicate type validation
tokebe Nov 15, 2022
a820bda
fix: merged result count for logging
tokebe Dec 1, 2022
fbc95d5
fix: don't use too-general categories
tokebe Dec 1, 2022
606e8b1
fix: simpler method using equivalentIDs semanticType
tokebe Dec 2, 2022
6b8ae9d
Merge branch 'combine-qedge' of https://github.com/biothings/bte_trap…
tokebe Dec 2, 2022
3c80f50
fix: filtering w/ no qualifiers, qualified predicates, reversing
tokebe Dec 7, 2022
3fc1d93
fix: use qualifiers in hash, hash order-agnostic
tokebe Dec 7, 2022
825d8d6
fix: hash sort when undefined, rm duplicate methods
tokebe Dec 7, 2022
1f94388
fix: iteration of potentially undefined response
tokebe Dec 7, 2022
32b6bc1
test: fix tests
tokebe Dec 7, 2022
7af3960
feat: qualifier-filter for creative mode template matching
tokebe Dec 14, 2022
e51bb36
perf: node category expansion, curie intersection code
tokebe Dec 21, 2022
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
File renamed without changes.
40 changes: 22 additions & 18 deletions __test__/integration/QEdge2BTEEdgeHandler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,28 @@ const QEdge = require('../../src/query_edge');
const NodeUpdateHandler = require('../../src/update_nodes');

describe('Testing NodeUpdateHandler Module', () => {
const gene_node1 = new QNode('n1', { categories: ['Gene'], ids: ['NCBIGene:1017'] });
const gene_node1 = new QNode({ id: 'n1', categories: ['Gene'], ids: ['NCBIGene:1017'] });
const node1_equivalent_ids = {
'NCBIGene:1017': {
db_ids: {
NCBIGene: ['1017'],
SYMBOL: ['CDK2'],
'NCBIGene:1017': [
{
semanticTypes: [],
db_ids: {
NCBIGene: ['1017'],
SYMBOL: ['CDK2'],
},
},
},
],
};

const gene_node2 = new QNode('n2', { categories: ['Gene'], ids: ['NCBIGene:1017', 'NCBIGene:1018'] });
const gene_node1_with_id_annotated = new QNode('n1', { categories: ['Gene'], ids: ['NCBIGene:1017'] });
const gene_node2 = new QNode({ id: 'n2', categories: ['Gene'], ids: ['NCBIGene:1017', 'NCBIGene:1018'] });
const gene_node1_with_id_annotated = new QNode({ id: 'n1', categories: ['Gene'], ids: ['NCBIGene:1017'] });
gene_node1_with_id_annotated.setEquivalentIDs(node1_equivalent_ids);
//gene_node2.setEquivalentIDs(node2_equivalent_ids);
const chemical_node1 = new QNode('n3', { categories: ['SmallMolecule'] });
const edge1 = new QEdge('e01', { subject: gene_node1, object: chemical_node1 });
const edge2 = new QEdge('e02', { subject: gene_node1_with_id_annotated, object: chemical_node1 });
const edge3 = new QEdge('e04', { subject: gene_node2, object: chemical_node1 });
const edge4 = new QEdge('e05', { object: gene_node2, subject: chemical_node1 });
const chemical_node1 = new QNode({ id: 'n3', categories: ['SmallMolecule'] });
const edge1 = new QEdge({ id: 'e01', subject: gene_node1, object: chemical_node1 });
const edge2 = new QEdge({ id: 'e02', subject: gene_node1_with_id_annotated, object: chemical_node1 });
const edge3 = new QEdge({ id: 'e04', subject: gene_node2, object: chemical_node1 });
const edge4 = new QEdge({ id: 'e05', object: gene_node2, subject: chemical_node1 });

describe('Testing _getCuries function', () => {
test('test edge with one curie input return an array of one', () => {
Expand All @@ -36,11 +39,12 @@ describe('Testing NodeUpdateHandler Module', () => {
expect(res.Gene.length).toEqual(2);
});

test('test edge with input node annotated should return an empty array', () => {
const nodeUpdater = new NodeUpdateHandler([edge2]);
const res = nodeUpdater._getCuries([edge2]);
expect(res).toEqual({});
});
// test deprecated: proper update handling outside of updater ensures minimal redundancy
// test('test edge with input node annotated should return an empty array', () => {
// const nodeUpdater = new NodeUpdateHandler([edge2]);
// const res = nodeUpdater._getCuries([edge2]);
// expect(res).toEqual({});
// });

test('test edge with input on object end should be handled', () => {
const nodeUpdater = new NodeUpdateHandler([edge4]);
Expand Down
115 changes: 79 additions & 36 deletions __test__/integration/QueryEdge.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ const QNode = require('../../src/query_node');
const QEdge = require('../../src/query_edge');

describe('Testing QueryEdge Module', () => {
const gene_node1 = new QNode('n1', { categories: ['Gene'], ids: ['NCBIGene:1017'] });
const type_node = new QNode('n2', { categories: ['SmallMolecule'] });
const disease1_node = new QNode('n1', { categories: ['Disease'], ids: ['MONDO:000123'] });
const gene_node1 = new QNode({ id: 'n1', categories: ['Gene'], ids: ['NCBIGene:1017'] });
const type_node = new QNode({ id: 'n2', categories: ['SmallMolecule'] });
const disease1_node = new QNode({ id: 'n1', categories: ['Disease'], ids: ['MONDO:000123'] });
const node1_equivalent_ids = {
'NCBIGene:1017': {
db_ids: {
Expand All @@ -14,15 +14,15 @@ describe('Testing QueryEdge Module', () => {
},
};

const gene_node2 = new QNode('n2', { categories: 'Gene', ids: ['NCBIGene:1017', 'NCBIGene:1018'] });
const gene_node1_with_id_annotated = new QNode('n1', { categories: ['Gene'], ids: ['NCBIGene:1017'] });
const gene_node2 = new QNode({ id: 'n2', categories: 'Gene', ids: ['NCBIGene:1017', 'NCBIGene:1018'] });
const gene_node1_with_id_annotated = new QNode({ id: 'n1', categories: ['Gene'], ids: ['NCBIGene:1017'] });
gene_node1_with_id_annotated.setEquivalentIDs(node1_equivalent_ids);
const chemical_node1 = new QNode('n3', { categories: ['SmallMolecule'] });
const edge1 = new QEdge('e01', { subject: gene_node1, object: chemical_node1 });
const edge2 = new QEdge('e02', { subject: gene_node1_with_id_annotated, object: chemical_node1 });
const edge3 = new QEdge('e04', { subject: gene_node2, object: chemical_node1 });
const edge4 = new QEdge('e05', { object: gene_node2, subject: chemical_node1 });
const edge5 = new QEdge('e06', { object: gene_node1_with_id_annotated, subject: chemical_node1 });
const chemical_node1 = new QNode({ id: 'n3', categories: ['SmallMolecule'] });
const edge1 = new QEdge({ id: 'e01', subject: gene_node1, object: chemical_node1 });
const edge2 = new QEdge({ id: 'e02', subject: gene_node1_with_id_annotated, object: chemical_node1 });
const edge3 = new QEdge({ id: 'e04', subject: gene_node2, object: chemical_node1 });
const edge4 = new QEdge({ id: 'e05', object: gene_node2, subject: chemical_node1 });
const edge5 = new QEdge({ id: 'e06', object: gene_node1_with_id_annotated, subject: chemical_node1 });

describe('Testing isReversed function', () => {
test('test if only the object of the edge has curie defined, should return true', () => {
Expand All @@ -36,9 +36,9 @@ describe('Testing QueryEdge Module', () => {
});

test('test if both subject and object curie not defined, should return false', () => {
const node1 = new QNode('n1', { categories: ['Gene'] });
const node2 = new QNode('n2', { categories: ['SmallMolecule'] });
const edge = new QEdge('e01', { subject: node1, object: node2 });
const node1 = new QNode({ id: 'n1', categories: ['Gene'] });
const node2 = new QNode({ id: 'n2', categories: ['SmallMolecule'] });
const edge = new QEdge({ id: 'e01', subject: node1, object: node2 });
expect(edge.isReversed()).toBeFalsy();
});
});
Expand Down Expand Up @@ -77,39 +77,41 @@ describe('Testing QueryEdge Module', () => {
});

test('test return false if both subject and object has no curies specified', () => {
const node1 = new QNode('n1', { categories: ['Gene'] });
const node2 = new QNode('n2', { categories: ['SmallMolecule'] });
const edge = new QEdge('e01', { subject: node1, object: node2 });
const node1 = new QNode({ id: 'n1', categories: ['Gene'] });
const node2 = new QNode({ id: 'n2', categories: ['SmallMolecule'] });
const edge = new QEdge({ id: 'e01', subject: node1, object: node2 });
expect(edge.hasInput()).toBeFalsy();
});
});

describe('Testing hasInputResolved function', () => {
test('test return true if subject has input resolved', () => {
const res = edge2.hasInputResolved();
expect(res).toBeTruthy();
});
// Removed because new QEdge has different implementation for hasInputResolved
// describe("Testing hasInputResolved function", () => {
// test("test return true if subject has input resolved", () => {
// const res = edge2.hasInputResolved();
// expect(res).toBeTruthy();
// });

test('test return false if both subject and object do not have input resolved', () => {
const res = edge1.hasInputResolved();
expect(res).toBeFalsy();
});
// test("test return false if both subject and object do not have input resolved", () => {
// const res = edge1.hasInputResolved();
// expect(res).toBeFalsy();
// });

test("test return true if subject doesn't have input resolved, but object does", () => {
const res = edge5.hasInputResolved();
expect(res).toBeTruthy();
});
});
// test("test return true if subject doesn't have input resolved, but object does", () => {
// const res = edge5.hasInputResolved();
// expect(res).toBeTruthy();
// });

// })

describe('Testing getPredicate function', () => {
test('test get reverse predicate if query is reversed', () => {
const edge = new QEdge('e01', { subject: type_node, object: disease1_node, predicates: ['biolink:treats'] });
const edge = new QEdge({ id: 'e01', subject: type_node, object: disease1_node, predicates: ['biolink:treats'] });
const res = edge.getPredicate();
expect(res).toContain('treated_by');
});

test('test get reverse predicate if query is reversed and expanded', () => {
const edge = new QEdge('e01', { subject: type_node, object: disease1_node, predicates: ['biolink:affects'] });
const edge = new QEdge({ id: 'e01', subject: type_node, object: disease1_node, predicates: ['biolink:affects'] });
const res = edge.getPredicate();
expect(res).toContain('affected_by');
expect(res).toContain('disrupted_by');
Expand All @@ -118,7 +120,8 @@ describe('Testing QueryEdge Module', () => {

describe('Testing expandPredicates function', () => {
test('All predicates are correctly expanded if in biolink model', () => {
const edge = new QEdge('e01', {
const edge = new QEdge({
id: 'e01',
subject: type_node,
object: disease1_node,
predicates: ['biolink:contributes_to'],
Expand All @@ -129,7 +132,8 @@ describe('Testing QueryEdge Module', () => {
});

test('Multiple predicates can be resolved', () => {
const edge = new QEdge('e01', {
const edge = new QEdge({
id: 'e01',
subject: type_node,
object: disease1_node,
predicates: ['biolink:contributes_to'],
Expand All @@ -142,7 +146,8 @@ describe('Testing QueryEdge Module', () => {
});

test('Predicates not in biolink model should return itself', () => {
const edge = new QEdge('e01', {
const edge = new QEdge({
id: 'e01',
subject: type_node,
object: disease1_node,
predicates: 'biolink:contributes_to',
Expand All @@ -153,4 +158,42 @@ describe('Testing QueryEdge Module', () => {
expect(res).toContain('amelio');
});
});

describe('chooseLowerEntityValue', () => {
test('Should reverse if subject has more curies', () => {
const qEdgeClone = new QEdge(edge1.freeze());
qEdgeClone.subject.entity_count = 2;
qEdgeClone.object.entity_count = 1;

qEdgeClone.chooseLowerEntityValue();

expect(qEdgeClone.isReversed()).toBeTruthy();
});

test("Shouldn't reverse if object has more curies", () => {
const qEdgeClone = new QEdge(edge1.freeze());
qEdgeClone.subject.entity_count = 1;
qEdgeClone.object.entity_count = 2;

qEdgeClone.chooseLowerEntityValue();

expect(qEdgeClone.isReversed()).toBeFalsy();
});

test("Shouldn't reverse if both have same number", () => {
const qEdgeClone = new QEdge(edge1.freeze());
qEdgeClone.subject.entity_count = 2;
qEdgeClone.object.entity_count = 2;

qEdgeClone.chooseLowerEntityValue();

expect(qEdgeClone.isReversed()).toBeFalsy();
});
});

test('getHashedEdgeRepresentation', () => {
const qEdge1 = new QEdge({ id: 'e01', subject: type_node, object: disease1_node, predicates: ['biolink:treats'] });
const qEdge2 = new QEdge(qEdge1.freeze(), true);
expect(qEdge1.getHashedEdgeRepresentation()).not.toEqual(qEdge2.getHashedEdgeRepresentation());
});
});
31 changes: 10 additions & 21 deletions __test__/integration/QueryGraphHandler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,22 +240,14 @@ describe('Testing QueryGraphHandler Module', () => {
});
});

describe('test _storeEdges function', () => {
describe('test calculateEdges function', () => {
test('test storeEdges with one hop query', async () => {
let handler = new QueryGraphHandler(OneHopQuery);
let edges = await handler._storeEdges();
expect(edges).toHaveProperty('e01');
expect(edges).not.toHaveProperty('e02');
expect(edges.e01).toBeInstanceOf(QEdge);
expect(edges.e01.getSubject()).toBeInstanceOf(QNode2);
});

test('test storeEdges with multi hop query', async () => {
let handler = new QueryGraphHandler(FourHopQuery);
let edges = await handler._storeEdges();
expect(edges).toHaveProperty('e01');
expect(edges).toHaveProperty('e02');
expect(edges.e01).toBeInstanceOf(QEdge);
await handler.calculateEdges();
expect(handler.edges).toHaveProperty('e01');
expect(handler.edges).not.toHaveProperty('e02');
expect(handler.edges.e01).toBeInstanceOf(QEdge);
expect(handler.edges.e01.getInputNode()).toBeInstanceOf(QNode2);
});
});

Expand All @@ -264,23 +256,20 @@ describe('Testing QueryGraphHandler Module', () => {
let handler = new QueryGraphHandler(ThreeHopExplainQuery);
let edges = await handler.calculateEdges();
expect(Object.keys(edges)).toHaveLength(3);
expect(edges[0]).toHaveLength(1);
expect(edges[1]).toHaveLength(1);
expect(edges[2]).toHaveLength(1);
});
});
describe('test cycle/duplicate edge detection for query graphs', () => {
test('Duplicate Edge Graph #1', async () => {
const handler = new QueryGraphHandler(QueryWithDuplicateEdge1);
expect(handler.calculateEdges()).rejects.toThrow(InvalidQueryGraphError);
await expect(handler.calculateEdges()).rejects.toThrow(InvalidQueryGraphError);
});
test('Query Graph Cycle #1', async () => {
const handler = new QueryGraphHandler(QueryWithCycle1);
expect(handler.calculateEdges()).rejects.toThrow(InvalidQueryGraphError);
await expect(handler.calculateEdges()).rejects.toThrow(InvalidQueryGraphError);
});
test('Query Graph Cycle #2', async () => {
const handler = new QueryGraphHandler(QueryWithCycle2);
expect(handler.calculateEdges()).rejects.toThrow(InvalidQueryGraphError);
await expect(handler.calculateEdges()).rejects.toThrow(InvalidQueryGraphError);
});
});

Expand All @@ -293,7 +282,7 @@ describe('Testing QueryGraphHandler Module', () => {
const handler = new QueryGraphHandler(QueryWithNullPredicate);
const edges = await handler.calculateEdges();
// if this is undefined (not null) then smartapi-kg treats as if the field doesn't exist (desired behavior)
expect(edges[0][0].getPredicate()).toBe(undefined);
expect(edges[0].getPredicate()).toBe(undefined);
});
test('Graph without any ids', async () => {
const handler = new QueryGraphHandler(QueryWithNullIds);
Expand Down
Loading