Commit 6070aaf7 authored by Lauri Himanen's avatar Lauri Himanen
Browse files

Refactored lattice parameters to a nested datastructure for search, added more...

Refactored lattice parameters to a nested datastructure for search, added more information for the groups API route.
parent 65b4d8b1
Pipeline #75617 passed with stages
in 36 minutes and 26 seconds
......@@ -450,12 +450,14 @@ class EncMaterialsResource(Resource):
group_result = api.model("group_result", {
"calculations_list": fields.List(fields.String),
"calculations": fields.List(fields.String),
"energies": fields.List(fields.Float),
"volumes": fields.List(fields.Float),
"energy_minimum": fields.Float,
"group_hash": fields.String,
"group_type": fields.String,
"nr_of_calculations": fields.Integer,
"representative_calculation_id": fields.String,
"representative_calc_id": fields.String,
})
groups_result = api.model("groups_result", {
"total_groups": fields.Integer(allow_null=False),
......@@ -465,6 +467,7 @@ group_source = {
"includes": [
"calc_id",
"encyclopedia.properties.energies.energy_total",
"encyclopedia.material.idealized_structure.cell_volume",
]
}
......@@ -490,6 +493,7 @@ class EncGroupsResource(Resource):
],
must=[
Q("exists", field="encyclopedia.properties.energies.energy_total"),
Q("exists", field="encyclopedia.material.idealized_structure.cell_volume"),
],
should=[
Q("exists", field="encyclopedia.method.group_eos_hash"),
......@@ -539,12 +543,16 @@ class EncGroupsResource(Resource):
def get_group(group, group_type, group_hash):
hits = group.energies.hits
calculations = [doc.calc_id for doc in hits]
energies = [doc.encyclopedia.properties.energies.energy_total for doc in hits]
volumes = [doc.encyclopedia.material.idealized_structure.cell_volume for doc in hits]
group_dict = {
"group_hash": group_hash,
"group_type": group_type,
"nr_of_calculations": len(calculations),
"representative_calculation_id": hits[0].calc_id,
"calculations_list": calculations,
"representative_calc_id": hits[0].calc_id,
"calculations": calculations,
"energies": energies,
"volumes": volumes,
"energy_minimum": hits[0].encyclopedia.properties.energies.energy_total,
}
return group_dict
......@@ -699,16 +707,78 @@ class EncCalculationsResource(Resource):
return result, 200
@ns.route("/materials/<string:material_id>/cells")
statistics_query = api.model("statistics_query", {
"calculations": fields.List(fields.String),
})
statistics = api.model("statistics", {
"min": fields.Float,
"max": fields.Float,
"avg": fields.Float,
})
statistics_result = api.model("statistics_result", {
"cell_volume": fields.Nested(statistics),
})
@ns.route("/materials/<string:material_id>/statistics")
class EncCellsResource(Resource):
@api.response(404, "Suggestion not found")
@api.response(400, "Bad request")
@api.response(200, "Metadata send", fields.Raw)
@api.expect(statistics_query, validate=False)
@api.marshal_with(statistics_result, skip_none=True)
@api.doc("enc_cells")
def get(self, material_id):
"""Used to return cell information related to the given material.
"""Used to return statistics related to the specified material and
calculations.
"""
return {"results": []}, 200
# Get query parameters as json
try:
data = marshal(request.get_json(), statistics_query)
except Exception as e:
abort(400, message=str(e))
# Find entries for the given material.
bool_query = Q(
"bool",
filter=[
Q("term", published=True),
Q("term", with_embargo=False),
Q("term", encyclopedia__material__material_id=material_id),
Q("terms", calc_id=data["calculations"]),
]
)
s = Search(index=config.elastic.index_name)
s = s.query(bool_query)
# Add statistics aggregations
cell_volume_agg = A(
"stats",
field="encyclopedia.material.idealized_structure.cell_volume",
)
s.aggs.bucket("cell_volume_stats", cell_volume_agg)
# Don't return individual documents
s = s.extra(**{
"size": 0,
})
# No hits on the top query level
response = s.execute()
if response.hits.total == 0:
abort(404, message="Could not find matching calculations.")
# Return results
cell_volume_stats = response.aggs.cell_volume_stats
result = {
"cell_volume": {
"min": cell_volume_stats.min,
"max": cell_volume_stats.max,
"avg": cell_volume_stats.avg
}
}
return result, 200
wyckoff_variables_result = api.model("wyckoff_variables_result", {
......@@ -716,20 +786,27 @@ wyckoff_variables_result = api.model("wyckoff_variables_result", {
"y": fields.Float,
"z": fields.Float,
})
wyckoff_set_result = api.model("wyckoff_set_result", {
"wyckoff_letter": fields.String,
"indices": fields.List(fields.Integer),
"element": fields.String,
"variables": fields.List(fields.Nested(wyckoff_variables_result)),
})
lattice_parameters = api.model("lattice_parameters", {
"a": fields.Float,
"b": fields.Float,
"c": fields.Float,
"alpha": fields.Float,
"beta": fields.Float,
"gamma": fields.Float,
})
idealized_structure_result = api.model("idealized_structure_result", {
"atom_labels": fields.List(fields.String),
"atom_positions": fields.List(fields.List(fields.Float)),
"lattice_vectors": fields.List(fields.List(fields.Float)),
"lattice_vectors_primitive": fields.List(fields.List(fields.Float)),
"lattice_parameters": fields.List(fields.Float),
"lattice_parameters": fields.Nested(lattice_parameters),
"periodicity": fields.List(fields.Boolean),
"number_of_atoms": fields.Integer,
"cell_volume": fields.Float,
......
......@@ -61,6 +61,60 @@ class WyckoffSet(MSection):
variables = SubSection(sub_section=WyckoffVariables.m_def, repeats=False)
class LatticeParameters(MSection):
m_def = Section(
a_flask=dict(skip_none=True),
description="""
Lattice parameters of the idealized cell. The lattice parameters can
only be reported consistently after idealization and may not perfectly
correspond to the original simulation cell.
""",
a_search="lattice_parameters"
)
a = Quantity(
type=float,
description="""
Length of the first basis vector.
""",
a_search=Search()
)
b = Quantity(
type=float,
description="""
Length of the second basis vector.
""",
a_search=Search()
)
c = Quantity(
type=float,
description="""
Length of the third basis vector.
""",
a_search=Search()
)
alpha = Quantity(
type=float,
description="""
Angle between second and third basis vector.
""",
a_search=Search()
)
beta = Quantity(
type=float,
description="""
Angle between first and third basis vector.
""",
a_search=Search()
)
gamma = Quantity(
type=float,
description="""
Angle between first and second basis vector.
""",
a_search=Search()
)
class IdealizedStructure(MSection):
m_def = Section(
a_flask=dict(skip_none=True),
......@@ -70,7 +124,8 @@ class IdealizedStructure(MSection):
visualizing the material and for calculating the structural properties.
The properties of the idealized structure may slightly vary from the
original structure used in the calculation.
"""
""",
a_search="idealized_structure",
)
atom_labels = Quantity(
type=str,
......@@ -105,15 +160,6 @@ class IdealizedStructure(MSection):
idealized to match the detected symmemtry properties.
"""
)
lattice_parameters = Quantity(
type=np.dtype(np.float64),
shape=[6],
description="""
Lattice parameters of the idealized cell. The lattice parameters can
only be reported consistently after idealization and may not perfectly
correspond to the original simulation cell.
"""
)
periodicity = Quantity(
type=np.bool,
shape=[3],
......@@ -135,9 +181,11 @@ class IdealizedStructure(MSection):
Volume of the idealized cell. The cell volume can only be reported
consistently after idealization and may not perfectly correspond to the
original simulation cell.
"""
""",
a_search=Search()
)
wyckoff_sets = SubSection(sub_section=WyckoffSet.m_def, repeats=True)
lattice_parameters = SubSection(sub_section=LatticeParameters.m_def)
class Bulk(MSection):
......
......@@ -32,6 +32,7 @@ from nomad.datamodel.encyclopedia import (
IdealizedStructure,
WyckoffSet,
WyckoffVariables,
LatticeParameters,
)
from nomad.normalizing.encyclopedia.context import Context
from nomad.parsing.legacy import Backend
......@@ -128,7 +129,14 @@ class MaterialBulkNormalizer(MaterialNormalizer):
def lattice_parameters(self, ideal: IdealizedStructure, std_atoms: Atoms) -> None:
cell_normalized = std_atoms.get_cell() * 1E-10
ideal.lattice_parameters = atomutils.get_lattice_parameters(cell_normalized)
param_values = atomutils.get_lattice_parameters(cell_normalized)
param_section = ideal.m_create(LatticeParameters)
param_section.a = float(param_values[0])
param_section.b = float(param_values[1])
param_section.c = float(param_values[2])
param_section.alpha = float(param_values[3])
param_section.beta = float(param_values[4])
param_section.gamma = float(param_values[5])
def mass_density(self, properties: Properties, repr_system: Atoms) -> None:
mass = atomutils.get_summed_atomic_mass(repr_system.get_atomic_numbers())
......@@ -425,16 +433,19 @@ class Material2DNormalizer(MaterialNormalizer):
ideal.lattice_vectors_primitive = cell_prim
def lattice_parameters(self, ideal: IdealizedStructure, std_atoms: Atoms, periodicity: np.array) -> None:
# 2D systems only have three lattice parameter: two length and angle between them
# 2D systems only have three lattice parameter: two lengths and angle between them
periodic_indices = np.where(np.array(periodicity) == True)[0] # noqa: E712
cell = std_atoms.get_cell()
a_vec = cell[periodic_indices[0], :] * 1e-10
b_vec = cell[periodic_indices[1], :] * 1e-10
a = np.linalg.norm(a_vec)
b = np.linalg.norm(b_vec)
alpha = np.clip(np.dot(a_vec, b_vec) / (a * b), -1.0, 1.0)
alpha = np.arccos(alpha)
ideal.lattice_parameters = np.array([a, b, 0.0, alpha, 0.0, 0.0])
gamma = np.clip(np.dot(a_vec, b_vec) / (a * b), -1.0, 1.0)
gamma = np.arccos(gamma)
param_section = ideal.m_create(LatticeParameters)
param_section.a = float(a)
param_section.b = float(b)
param_section.gamma = float(gamma)
def periodicity(self, ideal: IdealizedStructure, std_atoms: Atoms) -> None:
# MatID already provides the correct periodicity
......@@ -533,7 +544,8 @@ class Material1DNormalizer(MaterialNormalizer):
periodic_indices = np.where(np.array(periodicity) == True)[0] # noqa: E712
cell = std_atoms.get_cell()
a = np.linalg.norm(cell[periodic_indices[0], :]) * 1e-10
ideal.lattice_parameters = np.array([a, 0.0, 0.0, 0.0, 0.0, 0.0])
params = ideal.m_create(LatticeParameters)
params.a = float(a)
def periodicity(self, ideal: IdealizedStructure, prim_atoms: Atoms) -> None:
# Get dimension of system by also taking into account the covalent radii
......
......@@ -71,7 +71,7 @@ def test_1d_metainfo(one_d: EntryArchive):
assert ideal.atom_positions is not None
assert ideal.lattice_vectors is not None
assert np.array_equal(ideal.periodicity, [True, False, False])
assert np.allclose(ideal.lattice_parameters, [4.33793652e-10, 0, 0, 0, 0, 0], atol=0)
assert ideal.lattice_parameters.a == pytest.approx(4.33793652e-10)
def test_2d_metainfo(two_d: EntryArchive):
......@@ -92,7 +92,12 @@ def test_2d_metainfo(two_d: EntryArchive):
assert ideal.lattice_vectors is not None
assert ideal.lattice_vectors_primitive is not None
assert np.array_equal(ideal.periodicity, [True, True, False])
assert np.allclose(ideal.lattice_parameters, [2.46559821e-10, 2.46559821e-10, 0, 120 / 180 * np.pi, 0, 0], atol=0)
assert ideal.lattice_parameters.a == pytest.approx(2.46559821e-10)
assert ideal.lattice_parameters.b == pytest.approx(2.46559821e-10)
assert ideal.lattice_parameters.c is None
assert ideal.lattice_parameters.alpha is None
assert ideal.lattice_parameters.beta is None
assert ideal.lattice_parameters.gamma == pytest.approx(120 / 180 * np.pi)
def test_bulk_metainfo(bulk: EntryArchive):
......
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