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

Addinstance function #138

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/api_reference/strategies/add_instance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# add_instance

::: oteapi_dlite.strategies.add_instance
98 changes: 98 additions & 0 deletions oteapi_dlite/strategies/add_instance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""Generic function strategy using DLite storage plugin."""
# pylint: disable=unused-argument,invalid-name
from typing import TYPE_CHECKING, Optional

import dlite
from dlite.utils import infer_dimensions
from oteapi.models import AttrDict, FunctionConfig, SessionUpdate
from pydantic import Field
from pydantic.dataclasses import dataclass

from oteapi_dlite.models import DLiteSessionUpdate
from oteapi_dlite.utils import get_collection, update_collection

if TYPE_CHECKING:
from typing import Any, Dict

from oteapi.interfaces import IFunctionStrategy


class AddInstanceConfig(AttrDict):
"""Configuration for adding an instance to the collection."""

datamodel: str = Field(
description="ID (URI or UUID) of the datamodel.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be mentioned that the data model must be available in dlite.storage_path.

)
property_values: dict = Field(
description="Dict with property values.",
)
dimensions: "Optional[dict]" = Field(
None,
description="Dict with dimension values. If not provided, the "
"dimensions will be inferred from `values`.",
)
label: str = Field(
...,
description="Label of DLite instance to serialise in the collection.",
)


class DLiteAddInstanceConfig(FunctionConfig):
"""DLite function strategy config."""

configuration: AddInstanceConfig = Field(
...,
description="Strategy-specific configuration for adding "
"an instance to the collection.",
)


@dataclass
class DLiteAddInstanceStrategy:
"""DLite function strategy to add an Instance to the collection.

**Registers strategies**:

- `("mediaType", "application/vnd.dlite-addinstance")`

"""

function_config: DLiteAddInstanceConfig

def initialize(
Copy link
Contributor

@jesper-friis jesper-friis Jun 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be even more useful to add the instance during the initialise phase?
Probably not... when thinking more about it.

self,
session: "Optional[Dict[str, Any]]" = None,
) -> "SessionUpdate":
"""Initialize."""
return SessionUpdate()

def get(
self, session: "Optional[Dict[str, Any]]" = None
) -> "DLiteSessionUpdate":
"""Execute the strategy.

This method will be called through the strategy-specific endpoint
of the OTE-API Services.

Parameters:
session: A session-specific dictionary context.

Returns:
SessionUpdate instance.
"""
config = self.function_config.configuration

coll = get_collection(session)
datamodel = dlite.get_instance(config.datamodel)
if config.dimensions is None:
dims = infer_dimensions(
datamodel, config.property_values, strict=True
)
else:
dims = config.dimensions
inst = datamodel(dimensions=dims, properties=config.values)

coll.add(label=config.label, inst=inst)

update_collection(coll)
return DLiteSessionUpdate(collection_id=coll.uuid)
1 change: 1 addition & 0 deletions oteapi_dlite/strategies/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ def get(
)

coll = get_collection(session, config.collection_id)

inst = coll[config.label]

# Save instance
Expand Down
1 change: 1 addition & 0 deletions requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
otelib~=0.3.0
pre-commit~=3.3
pylint~=2.17
pytest~=7.4
Expand Down
5 changes: 3 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ oteapi.parse =
oteapi_dlite.image/vnd.dlite-png = oteapi_dlite.strategies.parse_image:DLiteImageParseStrategy
oteapi_dlite.image/vnd.dlite-tiff = oteapi_dlite.strategies.parse_image:DLiteImageParseStrategy


oteapi.function =
oteapi_dlite.application/vnd.dlite-generate = oteapi_dlite.strategies.generate:DLiteGenerateStrategy
oteapi_dlite.application/vnd.dlite-generate = oteapi_dlite.strategies.generate:DLiteFunctionStrategy
oteapi_dlite.application/vnd.dlite-function = oteapi_dlite.strategies.function:DLiteFunctionStrategy
oteapi_dlite.application/vnd.dlite-addinstance = oteapi_dlite.strategies.add_instance:DLiteAddInstanceStrategy

oteapi.mapping =
oteapi_dlite.mappings = oteapi_dlite.strategies.mapping:DLiteMappingStrategy
Expand Down
13 changes: 13 additions & 0 deletions tests/output/test_add_instance.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"7bad2efc-cb40-420b-aef9-2f909d038b46": {
"meta": "http://onto-ns.com/meta/0.1/Result",
"dimensions": {
"natoms": 2,
"ncoords": 3
},
"properties": {
"potential_energy": 0,
"forces": [[0, 0, 0], [0, 0, 0]]
}
}
}
Comment on lines +1 to +13
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no point in committing a file that is generated.

But more worrying is that the properties are uninitialised. We should test that they are correctly assigned.

59 changes: 59 additions & 0 deletions tests/strategies/test_add_instance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Test script for the add_instance strategy - tested via otelib."""
import os
from pathlib import Path

import dlite
import numpy as np
from otelib import OTEClient

# Paths
thisdir = Path(__file__).resolve().parent
testdir = thisdir.parent
entitydir = testdir / "entities"
outdir = testdir / "output"

os.makedirs(outdir, exist_ok=True)
dlite.storage_path.append(entitydir)


values = {
"potential_energy": 3.2e-19,
"forces": [
[1.2, 2.3, 3.4],
[0.2, 3.4, 4.5],
],
}

# Create OTE client
client = OTEClient("python")

add_instance = client.create_function(
functionType="application/vnd.dlite-addinstance",
configuration={
"datamodel": "http://onto-ns.com/meta/0.1/Result",
"property_values": values,
"label": "result",
},
)


generate = client.create_function(
functionType="application/vnd.dlite-generate",
configuration={
"driver": "json",
"location": f"{outdir}/test_add_instance.json",
"options": "mode=w",
"label": "result",
},
)

pipeline = add_instance >> generate
pipeline.get()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[After running the pipeline, we should test that the file is created and has the expected content. For example

import numpy as np

inst = dlite.Instance.from_location("json", f"{outdir}/test_add_instance.json", options="mode=r")
assert np.allclose(inst.potential_energy, 3.2e-19)
assert np.allclose(inst.forses, [[1.2, 2.3, 3.4], [0.2, 3.4, 4.5]])



# Test that the created content is as expected
inst = dlite.Instance.from_location(
driver="json", location=f"{outdir}/test_add_instance.json", options="mode=r"
)
assert np.allclose(inst.potential_energy, 3.2e-19)
assert np.allclose(inst.forses, [[1.2, 2.3, 3.4], [0.2, 3.4, 4.5]])