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

Update models according to changes during CECAM 2020 meeting #350

Merged
merged 20 commits into from
Jun 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 22 additions & 1 deletion openapi/index_openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1012,8 +1012,29 @@
},
"link_type": {
"title": "Link Type",
"type": "string",
"enum": [
"child",
"root",
"external",
"providers"
],
"description": "The link type of the represented resource in relation to this implementation. MUST be one of these values: 'child', 'root', 'external', 'providers'."
},
"aggregate": {
"title": "Aggregate",
"enum": [
"ok",
"test",
"staging",
"no"
],
"description": "A string indicating whether a client that is following links to aggregate results from different OPTIMADE implementations should follow this link or not.\nThis flag SHOULD NOT be indicated for links where :property:`link_type` is not :val:`child`.\n\nIf not specified, clients MAY assume that the value is :val:`ok`.\nIf specified, and the value is anything different than :val:`ok`, the client MUST assume that the server is suggesting not to follow the link during aggregation by default (also if the value is not among the known ones, in case a future specification adds new accepted values).\n\nSpecific values indicate the reason why the server is providing the suggestion.\nA client MAY follow the link anyway if it has reason to do so (e.g., if the client is looking for all test databases, it MAY follow the links marked with :property:`aggregate`=:val:`test`).\n\nIf specified, it MUST be one of the values listed in section Link Aggregate Options.",
"default": "ok"
},
"no_aggregate_reason": {
"title": "No Aggregate Reason",
"type": "string",
"description": "An OPTIONAL human-readable string indicating the reason for suggesting not to aggregate results following the link.\nIt SHOULD NOT be present if :property:`aggregate`=:val:`ok`."
}
},
"description": "Links endpoint resource object attributes"
Expand Down
87 changes: 74 additions & 13 deletions openapi/openapi.json

Large diffs are not rendered by default.

7 changes: 2 additions & 5 deletions optimade/adapters/structures/aiida.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from optimade.models import StructureResource as OptimadeStructure

from optimade.adapters.structures.utils import pad_cell, pad_positions
from optimade.adapters.structures.utils import pad_cell

try:
from aiida.orm.nodes.data.structure import StructureData, Kind, Site
Expand Down Expand Up @@ -54,16 +54,13 @@ def get_aiida_structure_data(optimade_structure: OptimadeStructure) -> Structure
Kind(symbols=symbols, weights=concentration, mass=mass, name=kind.name)
)

# Convert null/None values to float("nan")
cartesian_site_positions, _ = pad_positions(attributes.cartesian_site_positions)

# Add Sites
for index in range(attributes.nsites):
# range() to ensure 1-to-1 between kind and site
structure.append_site(
Site(
kind_name=attributes.species_at_sites[index],
position=cartesian_site_positions[index],
position=attributes.cartesian_site_positions[index],
)
)

Expand Down
3 changes: 2 additions & 1 deletion optimade/adapters/structures/ase.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from optimade.models import Species as OptimadeStructureSpecies
from optimade.models import StructureResource as OptimadeStructure
from optimade.models import StructureFeatures

from optimade.adapters.exceptions import ConversionError

Expand Down Expand Up @@ -31,7 +32,7 @@ def get_ase_atoms(optimade_structure: OptimadeStructure) -> Atoms:
attributes = optimade_structure.attributes

# Cannot handle partial occupancies
if "disorder" in attributes.structure_features:
if StructureFeatures.DISORDER in attributes.structure_features:
raise ConversionError(
"ASE cannot handle structures with partial occupancies, sorry."
)
Expand Down
9 changes: 4 additions & 5 deletions optimade/adapters/structures/cif.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

from optimade.adapters.structures.utils import (
cell_to_cellpar,
pad_positions,
fractional_coordinates,
)

Expand Down Expand Up @@ -78,9 +77,9 @@ def get_cif( # pylint: disable=too-many-locals,too-many-branches
# Since some structure viewers are having issues with cartesian coordinates,
# we calculate the fractional coordinates if this is a 3D structure and we have all the necessary information.
if not hasattr(attributes, "fractional_site_positions"):
sites, _ = pad_positions(attributes.cartesian_site_positions)
attributes.fractional_site_positions = fractional_coordinates(
cell=attributes.lattice_vectors, cartesian_positions=sites
cell=attributes.lattice_vectors,
cartesian_positions=attributes.cartesian_site_positions,
)

# NOTE: This is otherwise a bit ahead of its time, since this OPTIMADE property is part of an open PR.
Expand All @@ -102,9 +101,9 @@ def get_cif( # pylint: disable=too-many-locals,too-many-branches
)

if coord_type == "fract":
sites, _ = pad_positions(attributes.fractional_site_positions)
sites = attributes.fractional_site_positions
else:
sites, _ = pad_positions(attributes.cartesian_site_positions)
sites = attributes.cartesian_site_positions

species: Dict[str, OptimadeStructureSpecies] = {
species.name: species for species in attributes.species
Expand Down
8 changes: 3 additions & 5 deletions optimade/adapters/structures/jarvis.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from warnings import warn
from optimade.models import StructureResource as OptimadeStructure
from optimade.models import StructureFeatures
from optimade.adapters.exceptions import ConversionError
from optimade.adapters.structures.utils import pad_positions

try:
from jarvis.core.atoms import Atoms
Expand All @@ -28,16 +28,14 @@ def get_jarvis_atoms(optimade_structure: OptimadeStructure) -> Atoms:
attributes = optimade_structure.attributes

# Cannot handle partial occupancies
if "disorder" in attributes.structure_features:
if StructureFeatures.DISORDER in attributes.structure_features:
raise ConversionError(
"jarvis-tools cannot handle structures with partial occupancies."
)

cartesian_site_positions, _ = pad_positions(attributes.cartesian_site_positions)

return Atoms(
lattice_mat=attributes.lattice_vectors,
elements=[specie.name for specie in attributes.species],
coords=cartesian_site_positions,
coords=attributes.cartesian_site_positions,
cartesian=True,
)
12 changes: 5 additions & 7 deletions optimade/adapters/structures/proteindatabank.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
cell_to_cellpar,
cellpar_to_cell,
fractional_coordinates,
pad_positions,
scaled_cell,
)

Expand Down Expand Up @@ -80,9 +79,9 @@ def get_pdbx_mmcif( # pylint: disable=too-many-locals
# Since some structure viewers are having issues with cartesian coordinates,
# we calculate the fractional coordinates if this is a 3D structure and we have all the necessary information.
if not hasattr(attributes, "fractional_site_positions"):
sites, _ = pad_positions(attributes.cartesian_site_positions)
attributes.fractional_site_positions = fractional_coordinates(
cell=attributes.lattice_vectors, cartesian_positions=sites
cell=attributes.lattice_vectors,
cartesian_positions=attributes.cartesian_site_positions,
)

# TODO: The following lines are perhaps needed to create a "valid" PDBx/mmCIF file.
Expand Down Expand Up @@ -134,9 +133,9 @@ def get_pdbx_mmcif( # pylint: disable=too-many-locals
)

if coord_type == "fract":
sites, _ = pad_positions(attributes.fractional_site_positions)
sites = attributes.fractional_site_positions
else:
sites, _ = pad_positions(attributes.cartesian_site_positions)
sites = attributes.cartesian_site_positions

species: Dict[str, OptimadeStructureSpecies] = {
species.name: species for species in attributes.species
Expand Down Expand Up @@ -216,8 +215,7 @@ def get_pdb( # pylint: disable=too-many-locals
species.name: species for species in attributes.species
}

cartesian_site_positions, _ = pad_positions(attributes.cartesian_site_positions)
sites = np.asarray(cartesian_site_positions)
sites = np.asarray(attributes.cartesian_site_positions)
if rotation is not None:
sites = sites.dot(rotation)

Expand Down
10 changes: 2 additions & 8 deletions optimade/adapters/structures/pymatgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
from optimade.models import Species as OptimadeStructureSpecies
from optimade.models import StructureResource as OptimadeStructure

from optimade.adapters.structures.utils import pad_positions

try:
from pymatgen import Structure, Molecule

Expand Down Expand Up @@ -40,16 +38,14 @@ def _get_structure(optimade_structure: OptimadeStructure) -> Structure:

attributes = optimade_structure.attributes

cartesian_site_positions, _ = pad_positions(attributes.cartesian_site_positions)

return Structure(
lattice=attributes.lattice_vectors,
species=_pymatgen_species(
nsites=attributes.nsites,
species=attributes.species,
species_at_sites=attributes.species_at_sites,
),
coords=cartesian_site_positions,
coords=attributes.cartesian_site_positions,
coords_are_cartesian=True,
)

Expand All @@ -59,15 +55,13 @@ def _get_molecule(optimade_structure: OptimadeStructure) -> Molecule:

attributes = optimade_structure.attributes

cartesian_site_positions, _ = pad_positions(attributes.cartesian_site_positions)

return Molecule(
species=_pymatgen_species(
nsites=attributes.nsites,
species=attributes.species,
species_at_sites=attributes.species_at_sites,
),
coords=cartesian_site_positions,
coords=attributes.cartesian_site_positions,
)


Expand Down
9 changes: 0 additions & 9 deletions optimade/adapters/structures/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,15 +228,6 @@ def _pad_iter_of_iters(
return iterable, padded_iterable


def pad_positions(
positions: List[Vector3D], padding: float = None
) -> Tuple[List[Vector3D], bool]:
"""Turn any null/None values into a float in given list of positions"""
return _pad_iter_of_iters(
iterable=positions, padding=padding, outer=list, inner=tuple,
)


def pad_cell(
lattice_vectors: Tuple[Vector3D, Vector3D, Vector3D], padding: float = None
) -> Tuple[Tuple[Vector3D, Vector3D, Vector3D], bool]:
Expand Down
7 changes: 6 additions & 1 deletion optimade/models/entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from pydantic import BaseModel, Field, validator # pylint: disable=no-name-in-module

from .jsonapi import Relationships, Attributes, Resource
from .optimade_json import Relationship
from .optimade_json import Relationship, DataType


__all__ = (
Expand Down Expand Up @@ -150,6 +150,11 @@ class EntryInfoProperty(BaseModel):
"If the entry listing endpoint supports sorting, this key MUST be present for sortable properties with value `true`.",
)

type: Optional[DataType] = Field(
None,
description="Data type of value. Must equal a valid OPTIMADE data type as listed and defined under 'Data types'.",
)


class EntryInfoResource(BaseModel):

Expand Down
20 changes: 10 additions & 10 deletions optimade/models/index_metadb.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# pylint: disable=no-self-argument
from pydantic import Field, BaseModel, validator # pylint: disable=no-name-in-module
from enum import Enum

from pydantic import Field, BaseModel # pylint: disable=no-name-in-module
from typing import Union, Dict

from .jsonapi import BaseResource
Expand All @@ -14,6 +16,12 @@
)


class DefaultRelationship(Enum):
"""Enumeration of key(s) for relationship dictionary in IndexInfoResource"""

DEFAULT = "default"


class IndexInfoAttributes(BaseInfoAttributes):
"""Attributes for Base URL Info endpoint for an Index Meta-Database"""

Expand Down Expand Up @@ -46,18 +54,10 @@ class IndexInfoResource(BaseInfoResource):
"""Index Meta-Database Base URL Info endpoint resource"""

attributes: IndexInfoAttributes = Field(...)
relationships: Union[None, Dict[str, IndexRelationship]] = Field(
relationships: Union[None, Dict[DefaultRelationship, IndexRelationship]] = Field(
...,
description="Reference to the child identifier object under the links endpoint "
"that the provider has chosen as their 'default' OPTIMADE API database. "
"A client SHOULD present this database as the first choice when an end-user "
"chooses this provider.",
)

@validator("relationships")
def relationships_key_must_be_default(cls, value):
if value is not None and all([key != "default" for key in value]):
raise ValueError(
"if the relationships value is a dict, the key MUST be 'default'"
)
return value
51 changes: 41 additions & 10 deletions optimade/models/links.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# pylint: disable=no-self-argument
from enum import Enum

from pydantic import ( # pylint: disable=no-name-in-module
Field,
AnyUrl,
validator,
root_validator,
)
from typing import Union
from typing import Union, Optional

from .jsonapi import Link, Attributes
from .entries import EntryResource
Expand All @@ -17,6 +18,24 @@
)


class LinkType(Enum):
"""Enumeration of link_type values"""

CHILD = "child"
ROOT = "root"
EXTERNAL = "external"
PROVIDERS = "providers"


class Aggregate(Enum):
"""Enumeration of aggregate values"""

OK = "ok"
TEST = "test"
STAGING = "staging"
NO = "no"


class LinksResourceAttributes(Attributes):
"""Links endpoint resource object attributes"""

Expand All @@ -40,18 +59,30 @@ class LinksResourceAttributes(Attributes):
description="JSON API links object, pointing to a homepage URL for this implementation",
)

link_type: str = Field(
link_type: LinkType = Field(
...,
description="The link type of the represented resource in relation to this implementation. MUST be one of these values: 'child', 'root', 'external', 'providers'.",
)

@validator("link_type")
def link_type_must_be_in_specific_set(cls, value):
if value not in {"child", "root", "external", "providers"}:
raise ValueError(
"link_type MUST be either 'child, 'root', 'external', or 'providers'"
)
return value
aggregate: Optional[Aggregate] = Field(
"ok",
description="""A string indicating whether a client that is following links to aggregate results from different OPTIMADE implementations should follow this link or not.
This flag SHOULD NOT be indicated for links where :property:`link_type` is not :val:`child`.

If not specified, clients MAY assume that the value is :val:`ok`.
If specified, and the value is anything different than :val:`ok`, the client MUST assume that the server is suggesting not to follow the link during aggregation by default (also if the value is not among the known ones, in case a future specification adds new accepted values).

Specific values indicate the reason why the server is providing the suggestion.
A client MAY follow the link anyway if it has reason to do so (e.g., if the client is looking for all test databases, it MAY follow the links marked with :property:`aggregate`=:val:`test`).

If specified, it MUST be one of the values listed in section Link Aggregate Options.""",
)

no_aggregate_reason: Optional[str] = Field(
None,
description="""An OPTIONAL human-readable string indicating the reason for suggesting not to aggregate results following the link.
It SHOULD NOT be present if :property:`aggregate`=:val:`ok`.""",
)


class LinksResource(EntryResource):
Expand Down