Skip to content

Commit 10ab05b

Browse files
authored
Merge pull request #167 from JuliaRobotics/jt/develop_merge
megamerge - trying to tie up loose ends
2 parents 1c24c89 + 36c4fa6 commit 10ab05b

29 files changed

+1327
-168
lines changed

.travis.yml

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
language: julia
2+
sudo: required
3+
dist: trusty
24

35
os:
46
- linux
@@ -13,37 +15,35 @@ julia:
1315
- 1.3
1416
- nightly
1517

16-
notifications:
17-
email: false
18-
19-
matrix:
20-
allow_failures:
21-
- julia: nightly
22-
23-
# script:
24-
# - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi
25-
# - julia --project --check-bounds=yes -e 'using UUIDs; write("Project.toml", replace(read("Project.toml", String), r"uuid = .*?\n" =>"uuid = \"$(uuid4())\"\n"));
26-
# import Pkg; Pkg.build("DistributedFactorGraphs"); Pkg.test("DistributedFactorGraphs"; coverage=true)'
27-
28-
# script:
29-
# - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi
30-
# - julia --check-bounds=yes -e 'using Pkg; Pkg.clone(pwd()); Pkg.build("DistributedFactorGraphs"); Pkg.test("DistributedFactorGraphs"; coverage=true)'
31-
32-
# can be used if master of package is required
33-
# script:
34-
# - julia --project --color=yes --check-bounds=yes -e 'using Pkg; Pkg.add(PackageSpec(name="MetaGraphs", rev="master")); Pkg.build(); Pkg.test(coverage=true)'
35-
36-
after_success:
37-
- julia -e 'using Pkg; cd(Pkg.dir("DistributedFactorGraphs")); Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder()); Coveralls.submit(process_folder())'
18+
env:
19+
- IIF_TEST=false
3820

3921
jobs:
4022
include:
23+
- julia: 1.2
24+
env: IIF_TEST=true
4125
- stage: "Documentation"
4226
julia: 1.0
4327
os: linux
4428
script:
45-
- julia -e 'import Pkg; Pkg.add("Documenter"); Pkg.add("DataFrames")'
29+
- julia -e 'import Pkg; Pkg.add("Documenter"); Pkg.add("Neo4j"); Pkg.add("GraphPlot")'
4630
- julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd()));
4731
Pkg.instantiate()'
4832
- julia --project=docs/ docs/make.jl
4933
after_success: skip
34+
35+
notifications:
36+
email: false
37+
38+
matrix:
39+
allow_failures:
40+
- julia: nightly
41+
- env: IIF_TEST=true
42+
43+
# Set the password for Neo4j to neo4j:test
44+
before_script:
45+
- sleep 10
46+
- curl -v POST http://neo4j:neo4j@localhost:7474/user/neo4j/password -d"password=test"
47+
48+
after_success:
49+
- julia -e 'using Pkg; cd(Pkg.dir("DistributedFactorGraphs")); Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder()); Coveralls.submit(process_folder())'

Project.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ julia = "0.7, 1"
3030
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
3131
GraphPlot = "a2cc645c-3eea-5389-862e-a155d0052231"
3232
Neo4j = "d2adbeaf-5838-5367-8a2f-e46d570981db"
33+
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
3334

3435
[targets]
35-
test = ["Test", "GraphPlot", "Neo4j"]
36+
test = ["Test", "GraphPlot", "Neo4j", "Pkg"]

src/BigData.jl

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
2+
# TODO move to ...
3+
4+
5+
# """
6+
# $(TYPEDEF)
7+
# Abstract parent struct for big data entry.
8+
# """
9+
# abstract type AbstractBigDataEntry end
10+
11+
# --- AbstractBigData Interfaces ---
12+
# fields to implement:
13+
# - key::Symbol
14+
# available methods:
15+
# - addBigDataEntry!
16+
# - getBigDataEntry
17+
# - updateBigDataEntry!
18+
# - deleteBigDataEntry!
19+
# - getBigDataEntries
20+
# - getBigDataKeys
21+
22+
export addBigDataEntry!,
23+
getBigDataEntry,
24+
updateBigDataEntry!,
25+
deleteBigDataEntry!,
26+
getBigDataEntries,
27+
getBigDataKeys,
28+
MongodbBigDataEntry,
29+
FileBigDataEntry
30+
31+
# Methods
32+
33+
#TODO should this return Bool or the modified Variable?
34+
"""
35+
$(SIGNATURES)
36+
Add Big Data Entry to a DFG variable
37+
"""
38+
function addBigDataEntry!(var::AbstractDFGVariable, bde::AbstractBigDataEntry)::Bool
39+
haskey(var.bigData,bde.key) && @warn "$(bde.key) already exists in variable, overwriting!"
40+
var.bigData[bde.key] = bde
41+
return true
42+
end
43+
44+
"""
45+
$(SIGNATURES)
46+
Add Big Data Entry to distrubuted factor graph.
47+
Should be extended if DFG variable is not returned by by reference.
48+
"""
49+
function addBigDataEntry!(dfg::AbstractDFG, label::Symbol, bde::AbstractBigDataEntry)::Bool
50+
return addBigDataEntry!(getVariable(dfg, label), bde)
51+
end
52+
53+
"""
54+
$(SIGNATURES)
55+
Get big data entry
56+
"""
57+
function getBigDataEntry(var::AbstractDFGVariable, key::Symbol)::AbstractBigDataEntry
58+
return var.bigData[key]
59+
end
60+
function getBigDataEntry(dfg::AbstractDFG, label::Symbol, key::Symbol)::AbstractBigDataEntry
61+
return getBigDataEntry(getVariable(dfg, label), key)
62+
end
63+
64+
"""
65+
$(SIGNATURES)
66+
Update big data entry
67+
"""
68+
function updateBigDataEntry!(var::AbstractDFGVariable, bde::AbstractBigDataEntry)::Bool#TODO should this return Bool?
69+
!haskey(var.bigData,bde.key) && (@error "$(bde.key) does not exist in variable!"; return false)
70+
var.bigData[bde.key] = bde
71+
return true
72+
end
73+
function updateBigDataEntry!(dfg::AbstractDFG, label::Symbol, bde::AbstractBigDataEntry)::Bool
74+
updateBigDataEntry!(getVariable(dfg, label), bde)
75+
end
76+
77+
"""
78+
$(SIGNATURES)
79+
Delete big data entry
80+
"""
81+
function deleteBigDataEntry!(var::AbstractDFGVariable, key::Symbol)::AbstractBigDataEntry #users responsibility to delete big data in db before deleting entry
82+
bde = getBigDataEntry(var, key)
83+
delete!(var.bigData, key)
84+
return bde
85+
end
86+
87+
function deleteBigDataEntry!(dfg::AbstractDFG, label::Symbol, key::Symbol)::AbstractBigDataEntry #users responsibility to delete big data in db before deleting entry
88+
deleteBigDataEntry!(getVariable(dfg, label), key)
89+
end
90+
91+
"""
92+
$(SIGNATURES)
93+
Get big data entries, Vector{AbstractBigDataEntry}
94+
"""
95+
function getBigDataEntries(var::AbstractDFGVariable)::Vector{AbstractBigDataEntry}
96+
#or should we return the iterator, Base.ValueIterator{Dict{Symbol,AbstractBigDataEntry}}?
97+
collect(values(var.bigData))
98+
end
99+
function getBigDataEntries(dfg::AbstractDFG, label::Symbol)::Vector{AbstractBigDataEntry}
100+
#or should we return the iterator, Base.ValueIterator{Dict{Symbol,AbstractBigDataEntry}}?
101+
getBigDataEntries(getVariable(dfg, label))
102+
end
103+
104+
105+
"""
106+
$(SIGNATURES)
107+
getBigDataKeys
108+
"""
109+
function getBigDataKeys(var::AbstractDFGVariable)::Vector{Symbol}
110+
collect(keys(var.bigData))
111+
end
112+
function getBigDataKeys(dfg::AbstractDFG, label::Symbol)::Vector{Symbol}
113+
getBigDataKeys(getVariable(dfg, label))
114+
end
115+
116+
117+
118+
# Types <: AbstractBigDataEntry
119+
"""
120+
$(TYPEDEF)
121+
BigDataEntry in MongoDB.
122+
"""
123+
struct MongodbBigDataEntry <: AbstractBigDataEntry
124+
key::Symbol
125+
oid::NTuple{12, UInt8} #mongodb object id
126+
#maybe other fields such as:
127+
#flags::Bool ready, valid, locked, permissions
128+
#MIMEType::Symbol
129+
end
130+
131+
132+
"""
133+
$(TYPEDEF)
134+
BigDataEntry in a file.
135+
"""
136+
struct FileBigDataEntry <: AbstractBigDataEntry
137+
key::Symbol
138+
filename::String
139+
end

src/CloudGraphsDFG/services/CloudGraphsDFG.jl

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ function _getDuplicatedEmptyDFG(dfg::CloudGraphsDFG)::CloudGraphsDFG
5454
length(_getLabelsFromCyphonQuery(dfg.neo4jInstance, "(node:$(dfg.userId):$(dfg.robotId):$(sessionId))")) == 0 && break
5555
end
5656
@debug "Unique+empty copy session name: $sessionId"
57-
return CloudGraphsDFG{typeof(dfg.solverParams)}(dfg.neo4jInstance.connection, dfg.userId, dfg.robotId, sessionId, dfg.encodePackedTypeFunc, dfg.getPackedTypeFunc, dfg.decodePackedTypeFunc, solverParams=deepcopy(dfg.solverParams), description="(Copy of) $(dfg.description)", useCache=dfg.useCache)
57+
return CloudGraphsDFG{typeof(dfg.solverParams)}(dfg.neo4jInstance.connection, dfg.userId, dfg.robotId, sessionId, dfg.encodePackedTypeFunc, dfg.getPackedTypeFunc, dfg.decodePackedTypeFunc, dfg.rebuildFactorMetadata!, solverParams=deepcopy(dfg.solverParams), description="(Copy of) $(dfg.description)", useCache=dfg.useCache)
5858
end
5959

6060
# Accessors
@@ -67,6 +67,11 @@ function setSolverParams(dfg::CloudGraphsDFG, solverParams::T)::T where T <: Abs
6767
return dfg.solverParams = solverParams
6868
end
6969

70+
function getSerializationModule(dfg::CloudGraphsDFG)::Module where G <: AbstractDFG
71+
# TODO: If we need to specialize this for RoME etc, here is where we'll change it.
72+
return Main
73+
end
74+
7075
"""
7176
$(SIGNATURES)
7277
True if the variable or factor exists in the graph.
@@ -158,9 +163,7 @@ Add a DFGVariable to a DFG.
158163
"""
159164
function addVariable!(dfg::CloudGraphsDFG, variable::DFGVariable)::Bool
160165
if exists(dfg, variable)
161-
@warn "Variable '$(variable.label)' already exists in the graph, so updating it."
162-
updateVariable!(dfg, variable)
163-
return true
166+
error("Variable '$(variable.label)' already exists in the factor graph")
164167
end
165168
props = Dict{String, Any}()
166169
props["label"] = string(variable.label)
@@ -192,9 +195,7 @@ Add a DFGFactor to a DFG.
192195
"""
193196
function addFactor!(dfg::CloudGraphsDFG, variables::Vector{DFGVariable}, factor::DFGFactor)::Bool
194197
if exists(dfg, factor)
195-
@warn "Factor '$(factor.label)' already exist in the graph, so updating it."
196-
updateFactor!(dfg, variables, factor)
197-
return true
198+
error("Factor '$(factor.label)' already exists in the factor graph")
198199
end
199200

200201
# Update the variable ordering
@@ -363,9 +364,7 @@ Update a complete DFGVariable in the DFG.
363364
"""
364365
function updateVariable!(dfg::CloudGraphsDFG, variable::DFGVariable)::DFGVariable
365366
if !exists(dfg, variable)
366-
@warn "Variable '$(variable.label)' doesn't exist in the graph, so adding it."
367-
addVariable!(dfg, variable)
368-
return variable
367+
error("Variable label '$(variable.label)' does not exist in the factor graph")
369368
end
370369
nodeId = _tryGetNeoNodeIdFromNodeLabel(dfg.neo4jInstance, dfg.userId, dfg.robotId, dfg.sessionId, variable.label)
371370
# Update the node ID
@@ -409,9 +408,7 @@ Update a complete DFGFactor in the DFG.
409408
"""
410409
function updateFactor!(dfg::CloudGraphsDFG, factor::DFGFactor)::DFGFactor
411410
if !exists(dfg, factor)
412-
@warn "Factor '$(factor.label)' doesn't exist in the graph, so adding it."
413-
addFactor!(dfg, factor)
414-
return factor
411+
error("Factor label '$(factor.label)' does not exist in the factor graph")
415412
end
416413
nodeId = _tryGetNeoNodeIdFromNodeLabel(dfg.neo4jInstance, dfg.userId, dfg.robotId, dfg.sessionId, factor.label)
417414
# Update the _internalId
@@ -552,9 +549,15 @@ deleteFactor!(dfg::CloudGraphsDFG, factor::DFGFactor)::DFGFactor = deleteFactor!
552549
List the DFGVariables in the DFG.
553550
Optionally specify a label regular expression to retrieves a subset of the variables.
554551
"""
555-
function getVariables(dfg::CloudGraphsDFG, regexFilter::Union{Nothing, Regex}=nothing)::Vector{DFGVariable}
552+
function getVariables(dfg::CloudGraphsDFG, regexFilter::Union{Nothing, Regex}=nothing; tags::Vector{Symbol}=Symbol[])::Vector{DFGVariable}
556553
variableIds = getVariableIds(dfg, regexFilter)
557-
return map(vId->getVariable(dfg, vId), variableIds)
554+
# TODO: Optimize to use tags in query here!
555+
variables = map(vId->getVariable(dfg, vId), variableIds)
556+
if length(tags) > 0
557+
mask = map(v -> length(intersect(v.tags, tags)) > 0, variables )
558+
return variables[mask]
559+
end
560+
return variables
558561
end
559562

560563
"""
@@ -629,15 +632,19 @@ function isFullyConnected(dfg::CloudGraphsDFG)::Bool
629632
# Total nodes
630633
varIds = getVariableIds(dfg)
631634
factIds = getFactorIds(dfg)
632-
totalNodes = length(varIds) + length(factIds)
633-
if length(varIds) == 0
634-
return false
635-
end
635+
length(varIds) + length(factIds) == 0 && return false
636636

637637
# Total connected nodes - thank you Neo4j for 0..* awesomeness!!
638-
connectedList = _getLabelsFromCyphonQuery(dfg.neo4jInstance, "(n:$(dfg.userId):$(dfg.robotId):$(dfg.sessionId):$(varIds[1]))-[FACTORGRAPH*]-(node:$(dfg.userId):$(dfg.robotId):$(dfg.sessionId))")
639-
640-
return length(connectedList) == totalNodes
638+
query = """
639+
match (n:$(dfg.userId):$(dfg.robotId):$(dfg.sessionId):$(varIds[1]))-[FACTORGRAPH*]-(node:$(dfg.userId):$(dfg.robotId):$(dfg.sessionId))
640+
WHERE n:VARIABLE OR n:FACTOR OR node:VARIABLE OR node:FACTOR
641+
WITH collect(n)+collect(node) as nodelist
642+
unwind nodelist as nodes
643+
return count(distinct nodes)"""
644+
@debug "[Querying] $query"
645+
result = _queryNeo4j(dfg.neo4jInstance, query)
646+
# Neo4j.jl data structure sometimes feels brittle... like below
647+
return result.results[1]["data"][1]["row"][1] == length(varIds) + length(factIds)
641648
end
642649

643650
#Alias
@@ -655,10 +662,10 @@ function getNeighbors(dfg::CloudGraphsDFG, node::T; ready::Union{Nothing, Int}=n
655662
query = "(n:$(dfg.userId):$(dfg.robotId):$(dfg.sessionId):$(node.label))--(node) where node:VARIABLE or node:FACTOR "
656663
if ready != nothing || backendset != nothing
657664
if ready != nothing
658-
query = query + "and node.ready = $(ready)"
665+
query = query * "and node.ready = $(ready)"
659666
end
660667
if backendset != nothing
661-
query = query + "and node.backendset = $(backendset)"
668+
query = query * "and node.backendset = $(backendset)"
662669
end
663670
end
664671
neighbors = _getLabelsFromCyphonQuery(dfg.neo4jInstance, query)
@@ -677,10 +684,10 @@ function getNeighbors(dfg::CloudGraphsDFG, label::Symbol; ready::Union{Nothing,
677684
query = "(n:$(dfg.userId):$(dfg.robotId):$(dfg.sessionId):$(label))--(node) where node:VARIABLE or node:FACTOR "
678685
if ready != nothing || backendset != nothing
679686
if ready != nothing
680-
query = query + "and node.ready = $(ready)"
687+
query = query * "and node.ready = $(ready)"
681688
end
682689
if backendset != nothing
683-
query = query + "and node.backendset = $(backendset)"
690+
query = query * "and node.backendset = $(backendset)"
684691
end
685692
end
686693
neighbors = _getLabelsFromCyphonQuery(dfg.neo4jInstance, query)

src/CloudGraphsDFG/services/CommonFunctions.jl

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
alphaOnlyMatchRegex = r"^[a-zA-Z0-9_]*$"
22

3+
"""
4+
$(SIGNATURES)
5+
Returns the transaction for a given query.
6+
NOTE: Must commit(transaction) after you're done.
7+
"""
8+
function _queryNeo4j(neo4jInstance::Neo4jInstance, query::String)
9+
loadtx = transaction(neo4jInstance.connection)
10+
result = loadtx(query; submit=true)
11+
if length(result.errors) > 0
12+
error(string(result.errors))
13+
end
14+
# Have to finish the transaction
15+
commit(loadtx)
16+
return result
17+
end
18+
319
"""
420
$(SIGNATURES)
521
Returns the list of CloudGraph nodes that matches the Cyphon query.
@@ -11,16 +27,9 @@ If orderProperty is not ==, then 'order by n.{orderProperty} will be appended to
1127
So can make orderProperty = label or id.
1228
"""
1329
function _getLabelsFromCyphonQuery(neo4jInstance::Neo4jInstance, matchCondition::String, orderProperty::String="")::Vector{Symbol}
14-
# 2. Perform the transaction
15-
loadtx = transaction(neo4jInstance.connection)
1630
query = "match $matchCondition return distinct(node.label) $(orderProperty != "" ? "order by node.$orderProperty" : "")";
17-
nodes = loadtx(query; submit=true)
18-
if length(nodes.errors) > 0
19-
error(string(nodes.errors))
20-
end
21-
nodeIds = map(node -> node["row"][1], nodes.results[1]["data"])
22-
# Have to finish the transaction
23-
commit(loadtx)
31+
result = _queryNeo4j(neo4jInstance, query)
32+
nodeIds = map(node -> node["row"][1], result.results[1]["data"])
2433
return Symbol.(nodeIds)
2534
end
2635

0 commit comments

Comments
 (0)