Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
nomad-lab
nomad-FAIR
Commits
d9dc0b07
Commit
d9dc0b07
authored
Jun 12, 2020
by
Lauri Himanen
Browse files
Added the representative calculation selection as part of the /calculations route.
parent
72d05ad1
Pipeline
#76477
passed with stages
in 43 minutes and 38 seconds
Changes
1
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
nomad/app/api/encyclopedia.py
View file @
d9dc0b07
...
@@ -52,6 +52,18 @@ material_prop_map = {
...
@@ -52,6 +52,18 @@ material_prop_map = {
}
}
def
rgetattr
(
obj
,
attr_name
):
"""Used to perform attribute access based on a (possibly nested) attribute
name given as string.
"""
try
:
for
attr
in
attr_name
.
split
(
"."
):
obj
=
obj
[
attr
]
except
KeyError
:
return
None
return
obj
def
get_es_doc_values
(
es_doc
,
mapping
,
keys
=
None
):
def
get_es_doc_values
(
es_doc
,
mapping
,
keys
=
None
):
"""Used to form a material definition for "materials/<material_id>" from
"""Used to form a material definition for "materials/<material_id>" from
the given ElasticSearch root document.
the given ElasticSearch root document.
...
@@ -62,12 +74,7 @@ def get_es_doc_values(es_doc, mapping, keys=None):
...
@@ -62,12 +74,7 @@ def get_es_doc_values(es_doc, mapping, keys=None):
result
=
{}
result
=
{}
for
key
in
keys
:
for
key
in
keys
:
es_key
=
mapping
[
key
]
es_key
=
mapping
[
key
]
try
:
value
=
rgetattr
(
es_doc
,
es_key
)
value
=
es_doc
for
part
in
es_key
.
split
(
"."
):
value
=
getattr
(
value
,
part
)
except
AttributeError
:
value
=
None
result
[
key
]
=
value
result
[
key
]
=
value
return
result
return
result
...
@@ -88,8 +95,6 @@ material_result = api.model("material_result", {
...
@@ -88,8 +95,6 @@ material_result = api.model("material_result", {
"formula_reduced"
:
fields
.
String
,
"formula_reduced"
:
fields
.
String
,
"system_type"
:
fields
.
String
,
"system_type"
:
fields
.
String
,
"n_matches"
:
fields
.
Integer
,
"n_matches"
:
fields
.
Integer
,
# Representative properties shown on overview page
"representative_structure"
:
fields
.
String
,
# Bulk only
# Bulk only
"has_free_wyckoff_parameters"
:
fields
.
Boolean
,
"has_free_wyckoff_parameters"
:
fields
.
Boolean
,
"strukturbericht_designation"
:
fields
.
String
,
"strukturbericht_designation"
:
fields
.
String
,
...
@@ -117,17 +122,12 @@ class EncMaterialResource(Resource):
...
@@ -117,17 +122,12 @@ class EncMaterialResource(Resource):
# Parse request arguments
# Parse request arguments
args
=
material_query
.
parse_args
()
args
=
material_query
.
parse_args
()
prop
=
args
.
get
(
"property"
,
None
)
prop
=
args
.
get
(
"property"
,
None
)
repr_keys
=
set
([
"representative_structure"
])
if
prop
is
not
None
:
if
prop
is
not
None
:
if
prop
in
repr_keys
:
keys
=
[
prop
]
es_keys
=
[
"calc_id"
]
es_keys
=
[
material_prop_map
[
prop
]]
repr_keys
=
set
([
prop
])
else
:
keys
=
[
prop
]
es_keys
=
[
material_prop_map
[
prop
]]
else
:
else
:
keys
=
list
(
material_prop_map
.
keys
())
keys
=
list
(
material_prop_map
.
keys
())
es_keys
=
list
(
material_prop_map
.
values
())
+
[
"calc_id"
]
es_keys
=
list
(
material_prop_map
.
values
())
# Find the first public entry with this material id and take
# Find the first public entry with this material id and take
# information from there. In principle all other entries should have
# information from there. In principle all other entries should have
...
@@ -147,31 +147,14 @@ class EncMaterialResource(Resource):
...
@@ -147,31 +147,14 @@ class EncMaterialResource(Resource):
)
)
s
=
s
.
query
(
query
)
s
=
s
.
query
(
query
)
# The representative idealized structure simply comes from the first
# If a representative calculation is requested, all calculations are
# calculation when the calculations are alphabetically sorted by their
# returned in order to perform the scoring with a custom loop.
# calc_id. Thus we sort the results here if the representative
# Otherwise, only one representative entry is returned.
# structure is requested. Coming up with a good way to select the
s
=
s
.
extra
(
**
{
# representative one is pretty tricky in general, there are several
"_source"
:
{
"includes"
:
es_keys
},
# options:
"size"
:
10000
,
# - Lowest energy: This would be most intuitive, but the energy scales
"collapse"
:
{
"field"
:
"encyclopedia.material.material_id"
},
# between codes do not match, and the energy may not have been
})
# reported.
# - Volume that is closest to mean volume: how to calculate volume for
# molecules, surfaces, etc...
# - Random: We would want the representative visualization to be
# relatively stable.
if
"representative_structure"
in
repr_keys
:
s
=
s
.
extra
(
**
{
"sort"
:
[{
"calc_id"
:
{
"order"
:
"asc"
}}],
"_source"
:
{
"includes"
:
es_keys
},
"size"
:
1
,
})
else
:
s
=
s
.
extra
(
**
{
"collapse"
:
{
"field"
:
"encyclopedia.material.material_id"
},
"_source"
:
{
"includes"
:
es_keys
},
})
response
=
s
.
execute
()
response
=
s
.
execute
()
# No such material
# No such material
...
@@ -182,10 +165,6 @@ class EncMaterialResource(Resource):
...
@@ -182,10 +165,6 @@ class EncMaterialResource(Resource):
entry
=
response
[
0
]
entry
=
response
[
0
]
result
=
get_es_doc_values
(
entry
,
material_prop_map
,
keys
)
result
=
get_es_doc_values
(
entry
,
material_prop_map
,
keys
)
# Add representative properties
if
"representative_structure"
in
repr_keys
:
result
[
"representative_structure"
]
=
entry
.
calc_id
return
result
,
200
return
result
,
200
...
@@ -694,21 +673,6 @@ class EncSuggestionsResource(Resource):
...
@@ -694,21 +673,6 @@ class EncSuggestionsResource(Resource):
return
{
prop
:
suggestions
},
200
return
{
prop
:
suggestions
},
200
calcs_query
=
api
.
parser
()
calcs_query
.
add_argument
(
"page"
,
default
=
0
,
type
=
int
,
help
=
"The page number to return."
,
location
=
"args"
)
calcs_query
.
add_argument
(
"per_page"
,
default
=
10000
,
type
=
int
,
help
=
"The number of results per page"
,
location
=
"args"
)
calc_prop_map
=
{
calc_prop_map
=
{
"calc_id"
:
"calc_id"
,
"calc_id"
:
"calc_id"
,
"code_name"
:
"dft.code_name"
,
"code_name"
:
"dft.code_name"
,
...
@@ -737,10 +701,17 @@ calculation_result = api.model("calculation_result", {
...
@@ -737,10 +701,17 @@ calculation_result = api.model("calculation_result", {
"has_phonon_dos"
:
fields
.
Boolean
,
"has_phonon_dos"
:
fields
.
Boolean
,
"has_phonon_band_structure"
:
fields
.
Boolean
,
"has_phonon_band_structure"
:
fields
.
Boolean
,
})
})
representatives_result
=
api
.
model
(
"representatives_result"
,
{
"idealized_structure"
:
fields
.
String
,
"electronic_band_structure"
:
fields
.
String
,
"electronic_dos"
:
fields
.
String
,
"thermodynamical_properties"
:
fields
.
String
,
})
calculations_result
=
api
.
model
(
"calculations_result"
,
{
calculations_result
=
api
.
model
(
"calculations_result"
,
{
"total_results"
:
fields
.
Integer
,
"total_results"
:
fields
.
Integer
,
"pages"
:
fields
.
Nested
(
pages_result
),
"pages"
:
fields
.
Nested
(
pages_result
),
"results"
:
fields
.
List
(
fields
.
Nested
(
calculation_result
)),
"results"
:
fields
.
List
(
fields
.
Nested
(
calculation_result
)),
"representatives"
:
fields
.
Nested
(
representatives_result
,
skip_none
=
True
),
})
})
...
@@ -749,15 +720,12 @@ class EncCalculationsResource(Resource):
...
@@ -749,15 +720,12 @@ class EncCalculationsResource(Resource):
@
api
.
response
(
404
,
"Suggestion not found"
)
@
api
.
response
(
404
,
"Suggestion not found"
)
@
api
.
response
(
400
,
"Bad request"
)
@
api
.
response
(
400
,
"Bad request"
)
@
api
.
response
(
200
,
"Metadata send"
,
fields
.
Raw
)
@
api
.
response
(
200
,
"Metadata send"
,
fields
.
Raw
)
@
api
.
expect
(
calcs_query
,
validate
=
False
)
@
api
.
doc
(
"enc_calculations"
)
@
api
.
doc
(
"enc_calculations"
)
def
get
(
self
,
material_id
):
def
get
(
self
,
material_id
):
"""Used to return all calculations related to the given material.
"""Used to return all calculations related to the given material. Also
returns a representative calculation for each property shown in the
overview page.
"""
"""
args
=
calcs_query
.
parse_args
()
page
=
args
[
"page"
]
per_page
=
args
[
"per_page"
]
s
=
Search
(
index
=
config
.
elastic
.
index_name
)
s
=
Search
(
index
=
config
.
elastic
.
index_name
)
query
=
Q
(
query
=
Q
(
"bool"
,
"bool"
,
...
@@ -772,9 +740,9 @@ class EncCalculationsResource(Resource):
...
@@ -772,9 +740,9 @@ class EncCalculationsResource(Resource):
# The query is filtered already on the ES side so we don"t need to
# The query is filtered already on the ES side so we don"t need to
# transfer so much data.
# transfer so much data.
s
=
s
.
extra
(
**
{
s
=
s
.
extra
(
**
{
"_source"
:
{
"includes"
:
list
(
calc_prop_map
.
values
())},
"_source"
:
{
"includes"
:
list
(
calc_prop_map
.
values
())
+
[
"dft.xc_functional"
]
},
"size"
:
per_page
,
"size"
:
10000
,
"from"
:
page
,
"from"
:
0
,
})
})
response
=
s
.
execute
()
response
=
s
.
execute
()
...
@@ -782,6 +750,56 @@ class EncCalculationsResource(Resource):
...
@@ -782,6 +750,56 @@ class EncCalculationsResource(Resource):
if
len
(
response
)
==
0
:
if
len
(
response
)
==
0
:
abort
(
404
,
message
=
"There is no material {}"
.
format
(
material_id
))
abort
(
404
,
message
=
"There is no material {}"
.
format
(
material_id
))
# Add representative properties. It might be possible to write a custom
# ES scoring mechanism or aggregation to also perform the selection.
representatives
=
{}
def
calc_score
(
entry
):
"""Custom scoring function used to sort results by their
"quality". Currently built to mimic the scoring that was used
in the old Encyclopedia GUI.
"""
score
=
0
functional_score
=
{
"GGA"
:
100
}
code_score
=
{
"FHI-aims"
:
3
,
"VASP"
:
2
,
"Quantum Espresso"
:
1
,
}
code_name
=
entry
.
dft
.
code_name
functional
=
entry
.
dft
.
xc_functional
has_dos
=
rgetattr
(
entry
,
"encyclopedia.properties.electronic_band_structure"
)
is
not
None
has_bs
=
rgetattr
(
entry
,
"encyclopedia.properties.electronic_dos"
)
is
not
None
score
+=
functional_score
.
get
(
functional
,
0
)
score
+=
code_score
.
get
(
code_name
,
0
)
if
has_dos
and
has_bs
:
score
+=
10
return
score
# The calculations are first sorted by "quality"
sorted_calc
=
sorted
(
response
,
key
=
lambda
x
:
calc_score
(
x
),
reverse
=
True
)
# Get the requested representative properties
representatives
[
"idealized_structure"
]
=
sorted_calc
[
0
].
calc_id
thermo_found
=
False
bs_found
=
False
dos_found
=
False
for
calc
in
sorted_calc
:
if
rgetattr
(
calc
,
"encyclopedia.properties.thermodynamical_properties"
)
is
not
None
:
representatives
[
"thermodynamical_properties"
]
=
calc
.
calc_id
thermo_found
=
True
if
rgetattr
(
calc
,
"encyclopedia.properties.electronic_band_structure"
)
is
not
None
:
representatives
[
"electronic_band_structure"
]
=
calc
.
calc_id
bs_found
=
True
if
rgetattr
(
calc
,
"encyclopedia.properties.electronic_dos"
)
is
not
None
:
representatives
[
"electronic_dos"
]
=
calc
.
calc_id
dos_found
=
True
if
thermo_found
and
bs_found
and
dos_found
:
break
# Create result JSON
# Create result JSON
results
=
[]
results
=
[]
for
entry
in
response
:
for
entry
in
response
:
...
@@ -796,10 +814,7 @@ class EncCalculationsResource(Resource):
...
@@ -796,10 +814,7 @@ class EncCalculationsResource(Resource):
result
=
{
result
=
{
"total_results"
:
len
(
results
),
"total_results"
:
len
(
results
),
"results"
:
results
,
"results"
:
results
,
"pages"
:
{
"representatives"
:
representatives
,
"per_page"
:
per_page
,
"page"
:
page
,
}
}
}
return
result
,
200
return
result
,
200
...
@@ -1119,12 +1134,8 @@ class EncCalculationResource(Resource):
...
@@ -1119,12 +1134,8 @@ class EncCalculationResource(Resource):
# Add references that are to be read from the archive
# Add references that are to be read from the archive
for
ref
in
references
:
for
ref
in
references
:
arch_path
=
response
[
0
]
arch_path
=
response
[
0
]
try
:
arch_path
=
rgetattr
(
arch_path
,
es_properties
[
ref
])
for
attr
in
es_properties
[
ref
].
split
(
"."
):
if
arch_path
is
not
None
:
arch_path
=
arch_path
[
attr
]
except
KeyError
:
pass
else
:
arch_properties
[
ref
]
=
arch_path
arch_properties
[
ref
]
=
arch_path
del
es_properties
[
ref
]
del
es_properties
[
ref
]
...
@@ -1166,13 +1177,8 @@ class EncCalculationResource(Resource):
...
@@ -1166,13 +1177,8 @@ class EncCalculationResource(Resource):
# Add results from ES
# Add results from ES
for
prop
,
es_source
in
es_properties
.
items
():
for
prop
,
es_source
in
es_properties
.
items
():
value
=
response
[
0
]
value
=
rgetattr
(
response
[
0
],
es_source
)
try
:
if
value
is
not
None
:
for
attr
in
es_source
.
split
(
"."
):
value
=
value
[
attr
]
except
KeyError
:
pass
else
:
if
isinstance
(
value
,
AttrDict
):
if
isinstance
(
value
,
AttrDict
):
value
=
value
.
to_dict
()
value
=
value
.
to_dict
()
result
[
prop
]
=
value
result
[
prop
]
=
value
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment