Skip to content

Commit

Permalink
Update TableConfigCollection to add GemdQuery config generator
Browse files Browse the repository at this point in the history
  • Loading branch information
kroenlein committed Oct 1, 2024
1 parent 430e889 commit 35a2546
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 29 deletions.
2 changes: 1 addition & 1 deletion src/citrine/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "3.7.1"
__version__ = "3.8.0"
12 changes: 6 additions & 6 deletions src/citrine/gemd_queries/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ class AllRealFilter(Serializable['AllRealFilter'], PropertyFilterType):
Parameters
----------
lower: str
lower: float
The lower bound on this filter range.
upper: str
upper: float
The upper bound on this filter range.
unit: str
The units associated with the floating point values for this filter.
Expand All @@ -48,15 +48,15 @@ class AllIntegerFilter(Serializable['AllIntegerFilter'], PropertyFilterType):
Parameters
----------
lower: str
lower: int
The lower bound on this filter range.
upper: str
upper: int
The upper bound on this filter range.
"""

lower = properties.Float('lower')
upper = properties.Float('upper')
lower = properties.Integer('lower')
upper = properties.Integer('upper')
typ = properties.String('type', default="all_integer_filter", deserializable=False)


Expand Down
59 changes: 58 additions & 1 deletion src/citrine/gemtables/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def get_type(cls, data) -> Type[Serializable]:
raise ValueError("Can only get types from dicts with a 'type' key")
types: List[Type[Serializable]] = [
TerminalMaterialInfo, AttributeByTemplate, AttributeByTemplateAfterProcessTemplate,
AttributeByTemplateAndObjectTemplate, LocalAttribute,
AttributeByTemplateAndObjectTemplate, LocalAttribute, LocalAttributeAndObject,
IngredientIdentifierByProcessTemplateAndName, IngredientLabelByProcessAndName,
IngredientLabelsSetByProcessAndName, IngredientQuantityByProcessAndName,
TerminalMaterialIdentifier, AttributeInOutput,
Expand Down Expand Up @@ -339,6 +339,63 @@ def __init__(self,
self.type_selector = type_selector


class LocalAttributeAndObject(Serializable['LocalAttributeAndObject'], Variable):
"""[ALPHA] Attribute marked by an attribute template for the root of a material history tree.
Parameters
----------
name: str
a short human-readable name to use when referencing the variable
headers: list[str]
sequence of column headers
template: Union[UUID, str, LinkByUID, AttributeTemplate]
attribute template that identifies the attribute to assign to the variable
object_template: Union[UUID, str, LinkByUID, AttributeTemplate]
attribute template that identifies the attribute to assign to the variable
attribute_constraints: List[Tuple[Union[UUID, str, LinkByUID, AttributeTemplate], Bounds]]
Optional
constraints on object attributes in the target object that must be satisfied. Constraints
are expressed as Bounds. Attributes are expressed with links. The attribute that the
variable is being set to may be the target of a constraint as well.
type_selector: DataObjectTypeSelector
strategy for selecting data object types to consider when matching, defaults to PREFER_RUN
"""

name = properties.String('name')
headers = properties.List(properties.String, 'headers')
template = properties.Object(LinkByUID, 'template')
object_template = properties.Object(LinkByUID, 'object_template')
attribute_constraints = properties.Optional(
properties.List(
properties.SpecifiedMixedList(
[properties.Object(LinkByUID), properties.Object(BaseBounds)]
)
), 'attribute_constraints')
type_selector = properties.Enumeration(DataObjectTypeSelector, "type_selector")
typ = properties.String('type', default="local_attribute_and_object", deserializable=False)

attribute_type = Union[UUID, str, LinkByUID, AttributeTemplate]
object_type = Union[UUID, str, LinkByUID, BaseTemplate]
constraint_type = Tuple[attribute_type, BaseBounds]

def __init__(self,
name: str,
*,
headers: List[str],
template: attribute_type,
object_template: object_type,
attribute_constraints: Optional[List[constraint_type]] = None,
type_selector: DataObjectTypeSelector = DataObjectTypeSelector.PREFER_RUN):
self.name = name
self.headers = headers
self.template = _make_link_by_uid(template)
self.object_template = _make_link_by_uid(object_template)
self.attribute_constraints = None if attribute_constraints is None \
else [(_make_link_by_uid(x[0]), x[1]) for x in attribute_constraints]
self.type_selector = type_selector


class IngredientIdentifierByProcessTemplateAndName(
Serializable['IngredientIdentifierByProcessAndName'], Variable):
"""Ingredient identifier associated with a process template and a name.
Expand Down
106 changes: 96 additions & 10 deletions src/citrine/resources/table_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,18 @@
from citrine._serialization import properties
from citrine._session import Session
from citrine._utils.functions import format_escaped_url, _pad_positional_args
from citrine.resources.dataset import DatasetCollection
from citrine.resources.data_concepts import CITRINE_SCOPE, _make_link_by_uid
from citrine.resources.process_template import ProcessTemplate
from citrine.gemd_queries.gemd_query import GemdQuery
from citrine.gemtables.columns import Column, MeanColumn, IdentityColumn, OriginalUnitsColumn, \
ConcatColumn
from citrine.gemtables.rows import Row
from citrine.gemtables.variables import Variable, IngredientIdentifierByProcessTemplateAndName, \
IngredientQuantityByProcessAndName, IngredientQuantityDimension, \
IngredientIdentifierInOutput, IngredientQuantityInOutput, \
from citrine.gemtables.variables import (
Variable, IngredientIdentifierByProcessTemplateAndName, IngredientQuantityByProcessAndName,
IngredientQuantityDimension, IngredientIdentifierInOutput, IngredientQuantityInOutput,
IngredientLabelsSetByProcessAndName, IngredientLabelsSetInOutput
)

from typing import TYPE_CHECKING
if TYPE_CHECKING: # pragma: no cover
Expand Down Expand Up @@ -53,6 +55,17 @@ class TableConfigInitiator(BaseEnumeration):
UI = "UI"


class TableFromGemdQueryAlgorithm(BaseEnumeration):
"""The algorithm to use in automatically building a Table Configuration.
* UNSPECIFIED corresponds to initial default state; includes bubbling up process attributes
* MULTISTEP_MATERIALS corresponds keeping all attributes local to a material node / row
"""

UNSPECIFIED = "unspecified"
MULTISTEP_MATERIALS = "multistep_materials"


class TableConfig(Resource["TableConfig"]):
"""
The Table Configuration used to build GEM Tables.
Expand All @@ -73,7 +86,8 @@ class TableConfig(Resource["TableConfig"]):
Column definitions, which describe how the variables are shaped into the table
gemd_query: Optional[GemdQuery]
The query used to define the materials underpinning this table
generation_algorithm: TableFromGemdQueryAlgorithm
Which algorithm was used to generate the config based on the GemdQuery results
"""

# FIXME (DML): rename this (this is dependent on the server side)
Expand All @@ -100,15 +114,27 @@ def _get_dups(lst: List) -> List:
rows = properties.List(properties.Object(Row), "rows")
columns = properties.List(properties.Object(Column), "columns")
gemd_query = properties.Optional(properties.Object(GemdQuery), "gemd_query")

def __init__(self, name: str, *, description: str, datasets: List[UUID],
variables: List[Variable], rows: List[Row], columns: List[Column]):
generation_algorithm = properties.Optional(
properties.Enumeration(TableFromGemdQueryAlgorithm), "generation_algorithm"
)

def __init__(self, name: str,
*,
description: str,
datasets: List[UUID],
variables: List[Variable],
rows: List[Row],
columns: List[Column],
gemd_query: GemdQuery = None,
generation_algorithm: Optional[TableFromGemdQueryAlgorithm] = None):
self.name = name
self.description = description
self.datasets = datasets
self.rows = rows
self.variables = variables
self.columns = columns
self.gemd_query = gemd_query
self.generation_algorithm = generation_algorithm

# Note that these validations only apply at construction time. The current intended usage
# is for this object to be created holistically; if changed, then these will need
Expand Down Expand Up @@ -461,8 +487,9 @@ def get_for_table(self, table: "GemTable") -> TableConfig: # noqa: F821
"""
# the route to fetch the config is built off the display table route tree
path = (f'projects/{self.project_id}/display-tables/{table.uid}/versions/{table.version}'
'/definition')
path = format_escaped_url(
'projects/{}/display-tables/{}/versions/{}/definition',
self.project_id, table.uid, table.version)
data = self.session.get_resource(path)
return self.build(data)

Expand Down Expand Up @@ -538,6 +565,65 @@ def default_for_material(
ambiguous = [(Variable.build(v), Column.build(c)) for v, c in data['ambiguous']]
return config, ambiguous

def from_query(
self,
gemd_query: GemdQuery,
*,
name: str = None,
description: str = None,
algorithm: Optional[TableFromGemdQueryAlgorithm] = None,
register_config: bool = False
) -> Tuple[TableConfig, List[Tuple[Variable, Column]]]:
"""
Build a TableConfig based on the results of a database query.
Parameters
----------
gemd_query: GemdQuery
What content should end up in the table
name: str, optional
The name for the table config. Defaults to autogenerated message.
description: str, optional
The description of the table config. Defaults to autogenerated message.
algorithm: TableBuildAlgorithm, optional
The algorithm to use in generating a Table Configuration from the sample material
history. If unspecified, uses the webservice's default.
register_config: bool, optional
Whether to register the config
Returns
-------
List[Tuple[Variable, Column]]
A table config as well as addition variables/columns which would result in
ambiguous matches if included in the config.
"""
if name is None:
collection = DatasetCollection(
session=self.session,
team_id=self.team_id
)
name = (f"Automatic Table for Dataset: "
f"{', '.join([collection.get(x).name for x in gemd_query.datasets])}")

params = {"name": name}
if description is not None:
params['description'] = description
if algorithm is not None:
params['algorithm'] = algorithm
data = self.session.post_resource(
format_escaped_url('teams/{}/table-configs/from-query', self.team_id),
params=params,
json=gemd_query.dump()
)
config = TableConfig.build(data['config'])
ambiguous = [(Variable.build(v), Column.build(c)) for v, c in data['ambiguous']]

if register_config:
return self.register(config), ambiguous
else:
return config, ambiguous

def preview(self, *,
table_config: TableConfig,
preview_materials: List[LinkByUID] = None
Expand All @@ -552,7 +638,7 @@ def preview(self, *,
List of links to the material runs to use as terminal materials in the preview
"""
path = path = format_escaped_url(
path = format_escaped_url(
"teams/{}/ara-definitions/preview",
self.team_id
)
Expand Down
1 change: 1 addition & 0 deletions tests/gemtable/test_variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
AttributeByTemplateAndObjectTemplate(name="density", headers=["density"], attribute_template=LinkByUID(scope="template", id="density"), object_template=LinkByUID(scope="template", id="object")),
AttributeInOutput(name="density", headers=["density"], attribute_template=LinkByUID(scope="template", id="density"), process_templates=[LinkByUID(scope="template", id="object")]),
LocalAttribute(name="density", headers=["density"], template=LinkByUID(scope="templates", id="density"), attribute_constraints=[[LinkByUID(scope="templates", id="density"), RealBounds(0, 100, "g/cm**3")]]),
LocalAttributeAndObject(name="density", headers=["density"], template=LinkByUID(scope="templates", id="density"), object_template=LinkByUID(scope="templates", id="object"), attribute_constraints=[[LinkByUID(scope="templates", id="density"), RealBounds(0, 100, "g/cm**3")]]),
IngredientIdentifierByProcessTemplateAndName(name="ingredient id", headers=["density"], process_template=LinkByUID(scope="template", id="process"), ingredient_name="ingredient", scope="scope"),
IngredientIdentifierInOutput(name="ingredient id", headers=["ingredient id"], ingredient_name="ingredient", process_templates=[LinkByUID(scope="template", id="object")], scope="scope"),
LocalIngredientIdentifier(name="ingredient id", headers=["ingredient id"], ingredient_name="ingredient", scope="scope"),
Expand Down
Loading

0 comments on commit 35a2546

Please sign in to comment.