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
ConcreteTypeIt 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)