Commit 8de11a91 authored by Lauri Himanen's avatar Lauri Himanen
Browse files

Included element search.

parent 42aea06e
......@@ -16,14 +16,17 @@
The encyclopedia API of the nomad@FAIRDI APIs.
'''
import re
from math import gcd
from functools import reduce
import numpy as np
from flask_restplus import Resource, abort, fields, marshal
from flask import request
from elasticsearch_dsl import Search, Q
from nomad import config
from nomad.units import ureg
from nomad.atomutils import get_hill_decomposition, get_formula_string
from nomad.atomutils import get_hill_decomposition
from .api import api
ns = api.namespace('encyclopedia', description='Access encyclopedia metadata.')
......@@ -125,7 +128,7 @@ materials_query = api.model('materials_input', {
'search_by': fields.Nested(api.model('search_query', {
"exclusive": fields.Boolean(default=False),
"formula": fields.String,
"element": fields.List(fields.String),
"element": fields.String,
"page": fields.Integer(default=1),
"per_page": fields.Integer(default=25),
"pagination": fields.Boolean,
......@@ -234,10 +237,12 @@ class EncMaterialsResource(Resource):
# Create query for elements or formula
search_by = data["search_by"]
formula = search_by["formula"]
elements = search_by["element"]
exclusive = search_by["exclusive"]
if formula is not None:
# The given formula is reformatted with the Hill system
# Here we determine a list of atom types. The types may occur
# multiple times and at multiple places.
element_list = []
matches = re_formula.finditer(formula)
for match in matches:
......@@ -248,20 +253,46 @@ class EncMaterialsResource(Resource):
if count == "":
element_list.append(symbol)
else:
element_list += [[symbol] * int(count)]
element_list += [symbol] * int(count)
# The given list of species is reformatted with the Hill system
# into a query string. The counts are reduced by the greatest
# common divisor.
names, counts = get_hill_decomposition(element_list)
greatest_common_divisor = reduce(gcd, counts)
reduced_counts = np.array(counts) / greatest_common_divisor
query_string = []
for name, count in zip(names, reduced_counts):
if count == 1:
query_string.append(name)
else:
query_string.append("{}{}".format(name, count))
query_string = " ".join(query_string)
# With exclusive search we look for exact match
if exclusive:
hill_formula = get_formula_string(names, counts)
filters.append(Q("term", **{"encyclopedia.material.formula": hill_formula}))
filters.append(Q("term", **{"encyclopedia.material.species_and_counts.keyword": query_string}))
# With non-exclusive search we look for match that includes at
# least all parts of the formula, possibly even more.
else:
parts = ["{}{}".format(name, count) for name, count in zip(names, counts)]
musts.append(Q(
"match",
encyclopedia__material__formula_parts={"query": " ".join(parts), "operator": "and"}
encyclopedia__material__species_and_counts={"query": query_string, "operator": "and"}
))
elif elements is not None:
# The given list of species is reformatted with the Hill system into a query string
species, _ = get_hill_decomposition(elements.split(","))
query_string = " ".join(species)
# With exclusive search we look for exact match
if exclusive:
filters.append(Q("term", **{"encyclopedia.material.species.keyword": query_string}))
# With non-exclusive search we look for match that includes at
# least all species, possibly even more.
else:
musts.append(Q(
"match",
encyclopedia__material__species={"query": query_string, "operator": "and"}
))
# Prepare the final boolean query that combines the different queries
......
......@@ -2,7 +2,7 @@ import numpy as np
from nomad.metainfo import MSection, Section, SubSection, Quantity, MEnum, Reference
from nomad.datamodel.metainfo.public import section_k_band, section_dos, section_thermodynamical_properties
from nomad.metainfo.search_extension import Search
from elasticsearch_dsl import Text
from elasticsearch_dsl import Text, Keyword
class WyckoffVariables(MSection):
......@@ -289,17 +289,25 @@ class Material(MSection):
type=str,
description="""
Formula giving the composition and occurrences of the elements in the
Hill notation whre the number of occurences have been divided by the
Hill notation where the number of occurences have been divided by the
greatest common divisor.
""",
)
formula_parts = Quantity(
species_and_counts = Quantity(
type=str,
description="""
The formula separated into individual terms containing both the atom
type and count. Used for searching parts of a formula.
""",
a_search=Search(mapping=Text(multi=True, fields={'keyword': Keyword()}))
)
species = Quantity(
type=str,
description="""
The formula separated into individual terms for easier search.
The formula separated into individual terms containing only unique atom
species. Used for searching materials containing specific elements.
""",
a_search=Search(mapping=Text())
a_search=Search(mapping=Text(multi=True, fields={'keyword': Keyword()}))
)
# Bulk-specific properties
......
......@@ -13,7 +13,7 @@
# limitations under the License.
from typing import Dict, List
from math import gcd as gcd
from math import gcd
from functools import reduce
from abc import abstractmethod
import re
......@@ -68,14 +68,17 @@ class MaterialNormalizer():
formula = atomutils.get_formula_string(names, counts)
material.formula = formula
def formula_parts(self, material: Material, names: List[str], counts: List[int]) -> None:
parts = ["{}{}".format(name, count) for name, count in zip(names, counts)]
material.formula_parts = " ".join(parts)
def formula_reduced(self, material: Material, names: list, counts_reduced: list) -> None:
formula = atomutils.get_formula_string(names, counts_reduced)
material.formula_reduced = formula
def species_and_counts(self, material: Material, names: List[str], counts: List[int]) -> None:
parts = ["{}{}".format(name, count) for name, count in zip(names, counts)]
material.species_and_counts = " ".join(parts)
def species(self, material: Material, names: List[str]) -> None:
material.species = " ".join(names)
def material_id(self, material: Material, spg_number: int, wyckoff_sets: List[WyckoffSet]) -> None:
# Create and store hash based on SHA512
norm_hash_string = atomutils.get_symmetry_string(spg_number, wyckoff_sets)
......@@ -386,8 +389,9 @@ class MaterialBulkNormalizer(MaterialNormalizer):
self.crystal_system(bulk, sec_symmetry)
self.lattice_vectors_primitive(ideal, prim_atoms)
self.formula(material, names, counts)
self.formula_parts(material, names, counts)
self.formula_reduced(material, names, reduced_counts)
self.species(material, names)
self.species_and_counts(material, names, reduced_counts)
self.has_free_wyckoff_parameters(bulk, symmetry_analyzer)
self.lattice_parameters(ideal, std_atoms)
self.material_name(material, names, reduced_counts)
......
......@@ -19,10 +19,11 @@ import datetime
from nomad.metainfo.metainfo import (
MSection, MCategory, Section, Quantity, SubSection, Definition, Package, DeriveError,
MetainfoError, Environment, MResource, Datetime, units, Annotation, SectionAnnotation,
MetainfoError, Environment, MResource, Datetime, Annotation, SectionAnnotation,
DefinitionAnnotation, Reference, MProxy, derived)
from nomad.metainfo.example import Run, VaspRun, System, SystemHash, Parsing, SCC, m_package as example_package
from nomad import utils
from nomad.units import ureg
from tests import utils as test_utils
......@@ -389,9 +390,9 @@ class TestM1:
def test_unit_conversion(self):
system = System()
system.atom_positions = [[1, 2, 3]] * units.angstrom
assert system.atom_positions.units == units.meter
assert system.atom_positions[0][0] < 0.1 * units.meter
system.atom_positions = [[1, 2, 3]] * ureg.angstrom
assert system.atom_positions.units == ureg.meter
assert system.atom_positions[0][0] < 0.1 * ureg.meter
def test_synonym(self):
system = System()
......@@ -542,7 +543,7 @@ class TestM1:
scc.energy_total_0 = 1.0
scc.an_int = 1
assert scc.energy_total_0.m == 1.0 # pylint: disable=no-member
assert scc.energy_total_0 == 1.0 * units.J
assert scc.energy_total_0 == 1.0 * ureg.J
assert scc.m_to_dict()['energy_total_0'] == 1.0
assert scc.an_int == 1
assert scc.an_int.__class__ == np.int32
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment