aboutsummaryrefslogtreecommitdiffstats
path: root/bsfs
diff options
context:
space:
mode:
authorMatthias Baumgartner <dev@igsor.net>2023-01-15 20:57:42 +0100
committerMatthias Baumgartner <dev@igsor.net>2023-01-15 20:57:42 +0100
commitccaee71e2b6135d3b324fe551c8652940b67aab3 (patch)
treeb480189eccac8699b8c75763db7b38e838f38055 /bsfs
parent60257ed3c2aa6ea2891f362a691bde9d7ef17831 (diff)
downloadbsfs-ccaee71e2b6135d3b324fe551c8652940b67aab3.tar.gz
bsfs-ccaee71e2b6135d3b324fe551c8652940b67aab3.tar.bz2
bsfs-ccaee71e2b6135d3b324fe551c8652940b67aab3.zip
Feature as Literal instead of Predicate subtype
Diffstat (limited to 'bsfs')
-rw-r--r--bsfs/schema/__init__.py7
-rw-r--r--bsfs/schema/schema.py4
-rw-r--r--bsfs/schema/serialize.py83
-rw-r--r--bsfs/schema/types.py162
4 files changed, 133 insertions, 123 deletions
diff --git a/bsfs/schema/__init__.py b/bsfs/schema/__init__.py
index 31d7d61..f53512e 100644
--- a/bsfs/schema/__init__.py
+++ b/bsfs/schema/__init__.py
@@ -10,8 +10,11 @@ import typing
# inner-module imports
from .schema import Schema
from .serialize import from_string, to_string
-from .types import Literal, Node, Predicate, Vertex, \
- ROOT_FEATURE, ROOT_LITERAL, ROOT_NODE, ROOT_NUMBER, ROOT_PREDICATE, ROOT_VERTEX
+from .types import Literal, Node, Predicate, Vertex, Feature, \
+ ROOT_VERTEX, ROOT_NODE, ROOT_LITERAL, \
+ ROOT_NUMBER, ROOT_TIME, \
+ ROOT_ARRAY, ROOT_FEATURE, \
+ ROOT_PREDICATE
# exports
__all__: typing.Sequence[str] = (
diff --git a/bsfs/schema/schema.py b/bsfs/schema/schema.py
index bc50d4e..8d9a821 100644
--- a/bsfs/schema/schema.py
+++ b/bsfs/schema/schema.py
@@ -70,7 +70,9 @@ class Schema():
predicates.add(types.ROOT_PREDICATE)
# add minimally necessary types to the schema
literals.add(types.ROOT_NUMBER)
- predicates.add(types.ROOT_FEATURE)
+ literals.add(types.ROOT_TIME)
+ literals.add(types.ROOT_ARRAY)
+ literals.add(types.ROOT_FEATURE)
# FIXME: ensure that types derive from the right root?
diff --git a/bsfs/schema/serialize.py b/bsfs/schema/serialize.py
index a566d65..8b31737 100644
--- a/bsfs/schema/serialize.py
+++ b/bsfs/schema/serialize.py
@@ -35,13 +35,27 @@ def from_string(schema_str: str) -> schema.Schema:
graph.parse(data=schema_str, format='turtle')
# helper functions
+ # FIXME: type annotation
+ def _fetch_value(subject: URI, predicate: rdflib.URIRef, value_factory) -> typing.Optional[typing.Any]:
+ """Fetch the object of a given subject and predicate.
+ Raises a `errors.ConsistencyError` if multiple objects match.
+ """
+ values = list(graph.objects(rdflib.URIRef(subject), predicate))
+ if len(values) == 0:
+ return None
+ if len(values) == 1:
+ return value_factory(values[0])
+ raise errors.ConsistencyError(
+ f'{subject} has multiple values for predicate {str(predicate)}, expected zero or one')
+
def _convert(value):
"""Convert the subject type from rdflib to a bsfs native type."""
if isinstance(value, rdflib.Literal):
return value.value
if isinstance(value, rdflib.URIRef):
return URI(value)
- raise errors.UnreachableError(f'expected Literal or URIRef, found {typename(value)}')
+ # value is neither a node nor a literal, but e.g. a blank node
+ raise errors.BackendError(f'expected Literal or URIRef, found {typename(value)}')
def _fetch_hierarchically(factory, curr):
"""Walk through a rdfs:subClassOf hierarchy, creating symbols along the way."""
@@ -71,30 +85,36 @@ def from_string(schema_str: str) -> schema.Schema:
raise errors.ConsistencyError('inconsistent nodes')
# fetch literals
- literals = set(_fetch_hierarchically(types.Literal, types.ROOT_LITERAL))
+ def _build_literal(uri, parent, **annotations):
+ """Literal factory."""
+ # break out on root feature type
+ if uri == types.ROOT_FEATURE.uri:
+ return types.ROOT_FEATURE
+ # handle feature types
+ if isinstance(parent, types.Feature):
+ # clean annotations
+ annotations.pop(ns.bsfs.dimension, None)
+ annotations.pop(ns.bsfs.dtype, None)
+ annotations.pop(ns.bsfs.distance, None)
+ # get dimension
+ dimension = _fetch_value(uri, rdflib.URIRef(ns.bsfs.dimension), int)
+ # get dtype
+ dtype = _fetch_value(uri, rdflib.URIRef(ns.bsfs.dtype), URI)
+ # get distance
+ distance = _fetch_value(uri, rdflib.URIRef(ns.bsfs.distance), URI)
+ # return feature
+ return parent.child(URI(uri), dtype=dtype, dimension=dimension, distance=distance, **annotations)
+ # handle non-feature types
+ return parent.child(URI(uri), **annotations)
+
+ literals = set(_fetch_hierarchically(_build_literal, types.ROOT_LITERAL))
literals_lut = {lit.uri: lit for lit in literals}
if len(literals_lut) != len(literals):
raise errors.ConsistencyError('inconsistent literals')
# fetch predicates
- # FIXME: type annotation
- def _fetch_value(subject: URI, predicate: rdflib.URIRef, value_factory) -> typing.Optional[typing.Any]:
- """Fetch the object of a given subject and predicate.
- Raises a `errors.ConsistencyError` if multiple objects match.
- """
- values = list(graph.objects(rdflib.URIRef(subject), predicate))
- if len(values) == 0:
- return None
- if len(values) == 1:
- return value_factory(values[0])
- raise errors.ConsistencyError(
- f'{subject} has multiple values for predicate {str(predicate)}, expected zero or one')
-
def _build_predicate(uri, parent, **annotations):
"""Predicate factory."""
- # break out on root feature type
- if uri == types.ROOT_FEATURE.uri:
- return types.ROOT_FEATURE
# clean annotations
annotations.pop(ns.rdfs.domain, None)
annotations.pop(ns.rdfs.range, None)
@@ -113,23 +133,9 @@ def from_string(schema_str: str) -> schema.Schema:
rng = nodes_lut.get(rng, literals_lut.get(rng))
# get unique
unique = _fetch_value(uri, rdflib.URIRef(ns.bsfs.unique), bool)
- # handle feature types
- if isinstance(parent, types.Feature):
- # clean annotations
- annotations.pop(ns.bsfs.dimension, None)
- annotations.pop(ns.bsfs.dtype, None)
- annotations.pop(ns.bsfs.distance, None)
- # get dimension
- dimension = _fetch_value(uri, rdflib.URIRef(ns.bsfs.dimension), int)
- # get dtype
- dtype = _fetch_value(uri, rdflib.URIRef(ns.bsfs.dtype), URI)
- # get distance
- distance = _fetch_value(uri, rdflib.URIRef(ns.bsfs.distance), URI)
- # return feature
- return parent.child(URI(uri), domain=dom, range=rng, unique=unique,
- dtype=dtype, dimension=dimension, distance=distance, **annotations)
- # handle non-feature predicate
+ # build predicate
return parent.child(URI(uri), domain=dom, range=rng, unique=unique, **annotations)
+
predicates = _fetch_hierarchically(_build_predicate, types.ROOT_PREDICATE)
return schema.Schema(predicates, nodes, literals)
@@ -214,9 +220,12 @@ def to_string(schema_inst: schema.Schema, fmt: str = 'turtle') -> str:
def _parse(node: types._Type) -> T_TRIPLE:
"""Emit all properties of a type."""
- if isinstance(node, types._Type): # pylint: disable=protected-access
- # NOTE: all nodes are _Type
- yield from _type(node)
+ # check arg
+ if not isinstance(node, types._Type): # pylint: disable=protected-access
+ raise TypeError(node)
+ # emit _Type essentials
+ yield from _type(node)
+ # emit properties of derived types
if isinstance(node, types.Predicate):
yield from _predicate(node)
if isinstance(node, types.Feature):
diff --git a/bsfs/schema/types.py b/bsfs/schema/types.py
index 95dc66a..3a2e10c 100644
--- a/bsfs/schema/types.py
+++ b/bsfs/schema/types.py
@@ -226,10 +226,70 @@ class Node(Vertex):
class Literal(Vertex):
"""Literal type."""
parent: typing.Optional['Literal']
- def __init__(self, uri: URI, parent: typing.Optional['Literal'] ,**kwargs):
+ def __init__(self, uri: URI, parent: typing.Optional['Literal'], **kwargs):
super().__init__(uri, parent, **kwargs)
+class Feature(Literal):
+ """Feature type."""
+
+ # Number of feature vector dimensions.
+ dimension: int
+
+ # Feature vector datatype.
+ dtype: URI
+
+ # Distance measure to compare feature vectors.
+ distance: URI
+
+ def __init__(
+ self,
+ # Type members
+ uri: URI,
+ parent: typing.Optional[Literal],
+ # Feature members
+ dimension: int,
+ dtype: URI,
+ distance: URI,
+ **kwargs,
+ ):
+ super().__init__(uri, parent, **kwargs)
+ self.dimension = int(dimension)
+ self.dtype = URI(dtype)
+ self.distance = URI(distance)
+
+ def __hash__(self) -> int:
+ return hash((super().__hash__(), self.dimension, self.dtype, self.distance))
+
+ def __eq__(self, other: typing.Any) -> bool:
+ return super().__eq__(other) \
+ and self.dimension == other.dimension \
+ and self.dtype == other.dtype \
+ and self.distance == other.distance
+
+ def child(
+ self,
+ uri: URI,
+ dimension: typing.Optional[int] = None,
+ dtype: typing.Optional[URI] = None,
+ distance: typing.Optional[URI] = None,
+ **kwargs,
+ ):
+ """Return a child of the current class."""
+ if dimension is None:
+ dimension = self.dimension
+ if dtype is None:
+ dtype = self.dtype
+ if distance is None:
+ distance = self.distance
+ return super().child(
+ uri=uri,
+ dimension=dimension,
+ dtype=dtype,
+ distance=distance,
+ **kwargs,
+ )
+
class Predicate(_Type):
"""Predicate base type."""
@@ -304,77 +364,6 @@ class Predicate(_Type):
)
-class Feature(Predicate):
- """Feature base type."""
-
- # Number of feature vector dimensions.
- dimension: int
-
- # Feature vector datatype.
- dtype: URI
-
- # Distance measure to compare feature vectors.
- distance: URI
-
- def __init__(
- self,
- # Type members
- uri: URI,
- parent: typing.Optional[Predicate],
- # Predicate members
- domain: Node,
- range: Literal, # pylint: disable=redefined-builtin
- unique: bool,
- # Feature members
- dimension: int,
- dtype: URI,
- distance: URI,
- **kwargs,
- ):
- super().__init__(uri, parent, domain, range, unique, **kwargs)
- self.dimension = int(dimension)
- self.dtype = URI(dtype)
- self.distance = URI(distance)
-
- def __hash__(self) -> int:
- return hash((super().__hash__(), self.dimension, self.dtype, self.distance))
-
- def __eq__(self, other: typing.Any) -> bool:
- return super().__eq__(other) \
- and self.dimension == other.dimension \
- and self.dtype == other.dtype \
- and self.distance == other.distance
-
- def child(
- self,
- uri: URI,
- domain: typing.Optional[Node] = None,
- range: typing.Optional[Vertex] = None, # pylint: disable=redefined-builtin
- unique: typing.Optional[bool] = None,
- dimension: typing.Optional[int] = None,
- dtype: typing.Optional[URI] = None,
- distance: typing.Optional[URI] = None,
- **kwargs,
- ):
- """Return a child of the current class."""
- if dimension is None:
- dimension = self.dimension
- if dtype is None:
- dtype = self.dtype
- if distance is None:
- distance = self.distance
- return super().child(
- uri=uri,
- domain=domain,
- range=range,
- unique=unique,
- dimension=dimension,
- dtype=dtype,
- distance=distance,
- **kwargs,
- )
-
-
# essential vertices
ROOT_VERTEX = Vertex(
uri=ns.bsfs.Vertex,
@@ -396,24 +385,31 @@ ROOT_NUMBER = Literal(
parent=ROOT_LITERAL,
)
-# essential predicates
-ROOT_PREDICATE = Predicate(
- uri=ns.bsfs.Predicate,
- parent=None,
- domain=ROOT_NODE,
- range=ROOT_VERTEX,
- unique=False,
+ROOT_TIME = Literal(
+ uri=ns.bsfs.Time,
+ parent=ROOT_LITERAL,
+ )
+
+ROOT_ARRAY = Literal(
+ uri=ns.bsfs.Array,
+ parent=ROOT_LITERAL,
)
ROOT_FEATURE = Feature(
uri=ns.bsfs.Feature,
- parent=ROOT_PREDICATE,
- domain=ROOT_NODE,
- range=ROOT_LITERAL,
- unique=False,
+ parent=ROOT_ARRAY,
dimension=1,
dtype=ns.bsfs.f16,
distance=ns.bsfs.euclidean,
)
+# essential predicates
+ROOT_PREDICATE = Predicate(
+ uri=ns.bsfs.Predicate,
+ parent=None,
+ domain=ROOT_NODE,
+ range=ROOT_VERTEX,
+ unique=False,
+ )
+
## EOF ##