Types¶
Types are the fundamental building block of Metagraph. They allow dispatching based on types defined in algorithm signatures.
Types also have properties which can affect dispatching.
Types vs. Objects¶
Types are not the same as data objects. Data objects hold the actual data – node IDs, weights, edges, etc. Types describe what the data objects mean and contain methods to compute properties about the data objects.
For example, a Python list
is a data object. It can be thought of as a Vector, but the list
has
no method to report its dtype property. Vectors must have a dtype. To work around this limitation,
a PythonVectorType
can be created which knows how to compute a dtype given a Python list
.
In this example, the Python list
knows nothing about the PythonVectorType
class, while the
PythonVectorType
does know about the Python list
.
Abstract Types¶
Abstract types describe a generic kind of data container with potentially many equivalent representations.
Abstract types can define allowable properties which allow for specialized versions of the type.
This is an example for the abstract type EdgeMap which describes a set of edges and their associated data, similar to a Graph, but more limited. An EdgeMap has a dtype and can be directed or undirected. It may or may not have negative weights.
class EdgeMap(AbstractType):
properties = {
"is_directed": [True, False],
"dtype": DTYPE_CHOICES,
"has_negative_weights": [True, False, None],
}
unambiguous_subcomponents = {EdgeSet}
An abstract type can also define unambiguous_subcomponents
, which is a set
of
other abstract types which this type is allowed to be translated into.
Concrete Types¶
Concrete types describe a specific data object which fits under the abstract type category. Many such representations can exist, but all must represent identical data, allowing for translation between data objects of the same abstract type.
Concrete types can define properties which only apply to the specific data object.
These are specified in the allowed_props
attribute. It must be a dict
similar
to abstract properties.
This is a mock example of what a ScipyMatrix type might look like. It would subclass ConcreteType
and indicate which abstract class it belongs to (in this case Matrix
).
class ScipyMatrixType(ConcreteType, abstract=Matrix):
value_type = scipy.sparse.spmatrix
@classmethod
def _compute_abstract_properties(
cls, obj, props: List[str], known_props: Dict[str, Any]
) -> Dict[str, Any]:
ret = known_props.copy()
# fast properties
for prop in {"dtype"} - ret.keys():
if prop == "dtype":
ret[prop] = dtypes.dtypes_simplified[obj.dtype]
return ret
@classmethod
def assert_equal(cls, obj1, obj2, aprops1, aprops2, cprops1, cprops2, *, rel_tol=1e-9, abs_tol=0.0):
assert obj1.shape == obj2.shape, f"{obj1.shape} != {obj2.shape}"
assert aprops1 == aprops2, f"abstract property mismatch: {props1} != {props2}"
# additional assertions ...
In the example above, there is a value_type
attribute pointing to the data object –
scipy.sparse.spmatrix
. This is the most common form for a concrete type, pointing
to exactly one data class.
If more than one data class can be used with a concrete type, value_type
is not provided
and instead the author must override is_typeclass_of
so the system can properly figure out
which concrete type to use for every data object.
If any abstract properties are defined for the associated abstract type, _compute_abstract_properties
must be written to compute those properties for a given object.
Concrete properties are defined in the allowed_props
attribute. If this is specified,
_compute_concrete_properties
must be written to compute those properties for a given object.
Finally, it is recommended to write the assert_equal
method for comparing two data objects
of this type. Doing so allows these objects to be used in testing.
Wrappers¶
Often, the data object by itself does not contain enough information to be fully understood by Metagraph. A wrapper is needed around the data object to contain additional information. This wrapper will still need a separate concrete type which describes it.
To aid plugin authors, a standard pattern exists to create wrappers. A wrapper must subclass
Wrapper
and indicate the abstract type it belongs to. It should have its own constructor
and otherwise add methods and attributes as necessary to satisfy the concept of the abstract
type.
Within the wrapper class definition, an inner class named TypeMixin
must be written.
This inner class is created exactly like ConcreteType
except for the following:
It does not subclass
ConcreteType
It does not define the abstract class (that is done in the Wrapper definition)
It does not define
value_type
All other parts of ConcreteType
are defined within the inner TypeMixin
class:
allowed_props
_compute_abstract_properties
_compute_concrete_properties
assert_equal
etc.
When the wrapper is registered with Metagraph, this TypeMixin
class will be converted into
a proper ConcreteType
and set as the .Type
attribute on the wrapper. The value_type
will point to the wrapper class, linking the two objects.
Wrapper Convenience Methods¶
Several common resolver methods are made available as shortcuts on wrappers.
.translate(dst)
will translate to another type.run(algo_name, *args, **kwargs)
will run an algorithm using the wrapper as the first argument
This example shows equivalent calls:
y = mg.translate(x, "NetworkXGraph")
y = x.translate("NetworkXGraph")
pr = mg.algos.centrality.pagerank(x, damping=0.75)
pr = x.run("centrality.pagerank", damping=0.75)