TensorFlow GNN GraphObject incompatible with graph specification

I’m attempting to solve the Time Difference of Arrival (TDoA) multilateration problem using a Graph Neural Network, implemented in TensorFlow. The problem statement is as follows: given the known coordinates of 4 “receivers” and the difference in arrival times of some signal between any two such receivers, I wish to determine the entirely unknown coordinates of the signal source. There are many existing ways to solve this problem. I’m pursuing this particular direction out of pure curiosity.

I’ve decided to describe the problem as a fully-connected homogenous graph with 5 nodes, where the “feature” associated with each node is its 3-dimensional coordinates, and the feature associated with each edge is the associated pairwise arrival time difference (e.g. the feature associated with the edge between nodes 1 and 2 is the signal arrival time difference between transmitters 1 and 2). The 5th node is the signal source, and in practice we know neither its coordinates nor the feature associated with any of the edges connecting it to the remaining nodes (i.e. the distance between the source and any given transmitter).

While I’ve had graph theory, I’m entirely new to the theory of GNN’s, and I’m finding the TensorFlow GNN documentation to be a bit obtuse. I’ve begun working out a rough sketch of the code in Python, starting by defining the graph schema in a file graph_schema.pbtxt as follows:

node_sets {
        key: "node"
        value {
                description: "The nodes comprising the network (4 receivers + 1 signal source)."
                features {
                        key: "coordinates"
                        value: {
                                dtype: DT_FLOAT
                                shape: { dim: { size: 3 } }

edge_sets {
        key: "pseudorange"
        value {
                description: "The arrival time differences."
                source: "node"
                target: "node"

                features {
                        key: "differences"
                        value: {
                                dtype: DT_FLOAT

Again, each node is associated with a set of three dimensional coordinates, and each edge with a pairwise arrival time difference. I then attempt to create a graph specification from this schema, and begin producing some training data:

import tensorflow as tf
import tensorflow_gnn as tfgnn

import numpy as np

graph_schema = tfgnn.read_schema("./graph_schema.pbtxt")
graph_spec = tfgnn.create_graph_spec_from_schema_pb(graph_schema)

def make_graph_tensor():

    # Generate random coordinates for 5 nodes, where the 5th node is the "transmitter"
    node_coordinates = np.random.random(size=(5,3))

    # Compute the arrival times, pairwise arrival time differences
    c = 299792

    arrival_times = np.sum(np.sqrt((node_coordinates - node_coordinates[4])**2), axis=1) / c
    arrival_times = arrival_times[:, np.newaxis]

    time_differences = arrival_times - arrival_times.T
    time_differences = time_differences[np.triu_indices(len(time_differences), k=1)]

    # Define the node adjacency (fully connected graph)
    sources = [0,0,0,1,1,2,0,1,2,3]
    targets = [1,2,3,2,3,3,4,4,4,4]

    node_adjacency = tfgnn.Adjacency.from_indices(source=('node', tf.cast(sources, dtype=tf.int32)),
                                                  target=('node', tf.cast(targets, dtype=tf.int32)))

    # Create GraphTensor object
    network = tfgnn.NodeSet.from_fields(features={'coordinates': node_coordinates}, sizes=tf.shape(node_coordinates))
    pseudoranges = tfgnn.EdgeSet.from_fields(features={'differences': time_differences},

    return tfgnn.GraphTensor.from_pieces(node_sets={'node': network}, edge_sets={'pseudorange': pseudoranges})


In make_graph_tensor(), I attempt to create a GraphTensor object for one instance of randomly generated training data. One instance consists of some randomly-generated coordinates of 4 nodes + 1 signal source (held in node_coordinates), and 10 pairwise arrival time differences (held in time_differences). I then define the node adjacency such that the graph is fully connected, and attempt to create a GraphTensor object from the node coordinates, adjacency, and pairwise arrival time differences.

When I check that the resulting GraphTensor object is compatible with the graph specification using print(graph_spec.is_compatible_with(make_graph_tensor())), however, I get False. Unfortunately, it isn’t clear to me why this is. I initially expected it might have something to do with dimensions, so I tried changing shape: { dim: { size: 3 } } so that the feature shape would be inferred, but this did not help.

Why is the GraphObject incompatible with the graph specification resulting from the above schema?

Even if probably at this point you have already solved the problem,

The features that you represent with dtype: DT_FLOAT in the schema cannot match the features you generate in make_graph_tensor() if these are not generated as tf.float32.

I had the same problem and I solved it casting my features from tf.float64 to tf.float32.