neuralogic.nn.module.gnn package

Submodules

neuralogic.nn.module.gnn.appnp module

class APPNPConv(output_name: str, feature_name: str, edge_name: str, k: int, alpha: float, activation: TransformationFunction = <neuralogic.core.constructs.function.function.TransformationFunction object>, aggregation: AggregationFunction = <neuralogic.core.constructs.function.function.AggregationFunction object>, add_self_loops: bool | None = None, normalize: bool = True)[source]

Bases: Module

Approximate Personalized Propagation of Neural Predictions layer from “Predict then Propagate: Graph Neural Networks meet Personalized PageRank”. Which can be expressed as:

\[\mathbf{x}^{0}_i = \mathbf{x}_i\]
\[\mathbf{x}^{k}_i = \alpha \cdot \mathbf{x}^0_i + (1 - \alpha) \cdot {agg}_{j \in \mathcal{N}(i)}(\mathbf{x}^{k - 1}_j)\]
\[\mathbf{x}^{\prime}_i = act(\mathbf{x}^{K}_i)\]

Where act is an activation function and agg aggregation function.

The first part of the second equation that is “\(\alpha \cdot \mathbf{x}^0_i\)” is expressed in the logic form as:

R.<output_name>__<k>(V.I) <= R.<feature_name>(V.I)[<alpha>].fixed()

The second part of the second equation that is “\((1 - \alpha) \cdot {agg}_{j \in \mathcal{N}(i)}(\mathbf{x}^{k - 1}_j)\)” is expressed as:

R.<output_name>__<k>(V.I) <= (R.<output_name>__<k-1>(V.J)[1 - <alpha>].fixed(), R.<edge_name>(V.J, V.I))

Examples

The whole computation of this module (parametrized as APPNPConv("h1", "h0", "_edge", 3, 0.1, Transformation.SIGMOID)) is as follows:

metadata = Metadata(transformation=Transformation.IDENTITY, aggregation=Aggregation.SUM)

(R.h1__1(V.I) <= R.h0(V.I)[0.1].fixed()) | metadata
(R.h1__1(V.I) <= (R.h0(V.J)[0.9].fixed(), R._edge(V.J, V.I))) | metadata
R.h1__1/1 [Transformation.IDENTITY]

(R.h1__2(V.I) <= <0.1> R.h0(V.I)) | metadata
(R.h1__2(V.I) <= (<0.9> R.h1__1(V.J), R._edge(V.J, V.I))) | metadata
R.h1__2/1 [Transformation.IDENTITY]

(R.h1(V.I) <= <0.1> R.h0(V.I)) | metadata
(R.h1(V.I) <= (<0.9> R.h1__2(V.J), R._edge(V.J, V.I))) | metadata
R.h1 / 1 [Transformation.SIGMOID]
Parameters:
  • output_name (str) – Output (head) predicate name of the module.

  • feature_name (str) – Feature predicate name to get features from.

  • edge_name (str) – Edge predicate name to use for neighborhood relations.

  • k (int) – Number of iterations

  • alpha (float) – Teleport probability

  • activation (TransformationFunction) – Activation function of the output. Default: Transformation.IDENTITY

  • aggregation (AggregationFunction) – Aggregation function of nodes’ neighbors. Default: Aggregation.SUM

neuralogic.nn.module.gnn.gatv2 module

class GATv2Conv(in_channels: int, out_channels: int, output_name: str, feature_name: str, edge_name: str, share_weights: bool = False, activation: TransformationFunction = <neuralogic.core.constructs.function.function.TransformationFunction object>, add_self_loops: bool = True, negative_slope: float = 0.2)[source]

Bases: Module

GATv2 layer from “How Attentive are Graph Attention Networks?”.

\[\alpha_{ij} = \mathrm{softmax}_{j \in \mathcal{N}(i) \cup \{i\}} \left( \mathbf{a}^{\top} \mathrm{LeakyReLU} \left( \mathbf{W}_{l} \mathbf{x}_{i} + \mathbf{W}_{r} \mathbf{x}_{j} \right) \right)\]
\[\mathbf{x}^{\prime}_{i} = act \left( \sum_{j \in \mathcal{N}(i) \cup \{i\}} \alpha_{ij} \mathbf{W}_{r} \mathbf{x}_{j} \right)\]

The score is one number per edge, so \(\mathbf{a}\) is a 1 x out_channels weight and it sits on the body of the rule that normalizes it, not on the head: a head weight is applied by the atom neuron after the aggregation, and the softmax aggregation casts every input it is given to a scalar. The normalization is Aggregation.SOFTMAX with agg_terms, which groups the groundings by the terms it is not given - so over the neighbours J for each target I, while staying one value per edge. That is the same shape Attention uses.

The edge appears as a hidden atom in both rules, and only to restrict the grounding: without it the score would ground for every pair of nodes and the softmax would normalize over the whole graph rather than over a neighbourhood. PyG’s GATv2 takes edge features through a projection rather than an edge weight, so there is nothing for the edge’s own value to mean here.

The slope defaults to PyG’s 0.2, not to the backend’s own 0.01: this module exists to be PyG’s layer, and quietly using a different slope made every comparison bend PyG instead. It is a per-rule slope now, so negative_slope says what it says.

Parameters:
  • in_channels (int) – Input feature size.

  • out_channels (int) – Output feature size.

  • output_name (str) – Output (head) predicate name of the module.

  • feature_name (str) – Feature predicate name to get features from.

  • edge_name (str) – Edge predicate name to use for neighborhood relations.

  • share_weights (bool) – Share weights in attention. Default: False

  • activation (TransformationFunction) – Activation function of the output. Default: Transformation.IDENTITY

  • add_self_loops (bool) – Let a node attend to itself, as PyG does. Default: True

  • negative_slope (float) – The LeakyReLU slope of the attention score. Default: 0.2, as in PyG.

neuralogic.nn.module.gnn.gcn module

class GCNConv(in_channels: int, out_channels: int, output_name: str, feature_name: str, edge_name: str, activation: TransformationFunction = <neuralogic.core.constructs.function.function.TransformationFunction object>, aggregation: AggregationFunction = <neuralogic.core.constructs.function.function.AggregationFunction object>, add_self_loops: bool | None = None, normalize: bool = True)[source]

Bases: Module

Graph Convolutional layer from “Semi-supervised Classification with Graph Convolutional Networks”.

\[\mathbf{X}^{\prime} = act \left( \mathbf{\hat{D}}^{-1/2} \mathbf{\hat{A}} \mathbf{\hat{D}}^{-1/2} \mathbf{X} \mathbf{W} \right)\]

where \(\mathbf{\hat{A}} = \mathbf{A} + \mathbf{I}\). The body combines by product, so an edge’s value is PyG’s edge_weight: it scales the message it carries and it enters the degree. Note __edge_count is a weighted **in-**degree despite its name - the two rules sum over V.X in the source position, and they sum the edge values rather than counting the edges, both of which is what gcn_norm does. Neither is visible on an undirected graph given both directions at 1.0, where in-degree is out-degree and summing ones is counting them, which is why the name survived being either.

Parameters:
  • in_channels (int) – Input feature size.

  • out_channels (int) – Output feature size.

  • output_name (str) – Output (head) predicate name of the module.

  • feature_name (str) – Feature predicate name to get features from.

  • edge_name (str) – Edge predicate name to use for neighborhood relations.

  • activation (TransformationFunction) – Activation function of the output. Default: Transformation.IDENTITY

  • aggregation (AggregationFunction) – Aggregation function of nodes’ neighbors. Default: Aggregation.SUM

  • add_self_loops (bool | None) – Add self loops if either set to True or None (if normalize is True). Default: None

  • normalize (bool) – Add normalization. Default : True

neuralogic.nn.module.gnn.gen module

class GENConv(in_channels: int, out_channels: int, output_name: str, feature_name: str, edge_name: str, aggregation: AggregationFunction = <neuralogic.core.constructs.function.softmax.SoftmaxAggregation object>, num_layers: int = 2, expansion: int = 2, eps: float = 1e-07, train_eps: bool = False, edge_dim: int | None = None)[source]

Bases: Module

GENConv layer from “DeeperGCN: All You Need to Train Deeper GCNs”.

Warning

This module cannot be evaluated on a graph whose features are vectors. Building a dataset for it throws ClassCastException: VectorValue cannot be cast to ScalarValue, so it computes nothing - it is not divergent from PyG, it is unusable. Every other GNN module here is checked against torch_geometric in tests/alignment/test_gnn_alignment.py; this one cannot be.

The cause is not a defect and lifting it would not be a fix. Aggregation.SOFTMAX normalises over groundings, so it wants one number per grounding - a softmax over a set of scalars, which is its only reading, and the implementation is built that way throughout: an n x n Jacobian and a scalar handed back to each input. The __gen_feat_agg rule below asks it to softmax vector-valued groundings instead.

What the module is written against is PyG’s SoftmaxAggregation, which is a different operator: softmax(x, index, dim=-2) then sum(x * alpha), i.e. one independent softmax per feature channel, weighting the neighbours separately for each. That is a per-channel softmax-weighted mean rather than attention. Offering it would mean adding an element-wise softmax aggregation as its own function - a decision about what the library should provide, not a bug to close.

GATv2Conv shows the shape that does work within the restriction: project the per-edge score to one number before the aggregation, with the weight on the rule’s body rather than its head.

Parameters:
  • in_channels (int) – Input feature size.

  • out_channels (int) – Output feature size.

  • output_name (str) – Output (head) predicate name of the module.

  • feature_name (str) – Feature predicate name to get features from.

  • edge_name (str) – Edge predicate name to use for neighborhood relations.

  • aggregation (AggregationFunction) – The aggregation function. Default: Aggregation.SOFTMAX

  • num_layers (int) – The number of MLP layers. Default: 2

  • expansion (int) – The expansion factor of hidden channels in MLP. Default: 2

  • eps (float) – \(\epsilon\)-value. Default: 0.0

  • train_eps (bool) – Is eps trainable parameter. Default: false

  • edge_dim (int | None) – Dimension of edge features (None is projection to in_channels is not needed). Default: None

neuralogic.nn.module.gnn.gin module

class GINConv(in_channels: int, out_channels: int, output_name: str, feature_name: str, edge_name: str, activation: TransformationFunction = <neuralogic.core.constructs.function.function.TransformationFunction object>, aggregation: AggregationFunction = <neuralogic.core.constructs.function.function.AggregationFunction object>)[source]

Bases: Module

Implements the Graph Isomorphism Network (GIN) convolution layer. GIN is a powerful GNN layer that can distinguish between different graph structures.

neuralogic.nn.module.gnn.gine module

class GINEConv(in_channels: int, feature_name: str, edge_name: str, nn_name: str, eps: float = 0.0, train_eps: bool = False, edge_dim: int | None = None)[source]

Bases: Module

GINEConv layer from “Strategies for Pre-training Graph Neural Networks”.

Parameters:
  • in_channels (int) – Input feature size.

  • out_channels (int) – Output feature size.

  • feature_name (str) – Feature predicate name to get features from.

  • edge_name (str) – Edge predicate name to use for neighborhood relations.

  • nn_name (str) – Neural network predicate name.

  • eps (float) – \(\epsilon\)-value. Default: 0.0

  • train_eps (bool) – Is eps trainable parameter. Default: false

  • edge_dim (int | None) – Dimension of edge features (None is projection to in_channels is not needed). Default: None

neuralogic.nn.module.gnn.gsage module

class SAGEConv(in_channels: int, out_channels: int, output_name: str, feature_name: str, edge_name: str, activation: TransformationFunction = <neuralogic.core.constructs.function.function.TransformationFunction object>, aggregation: AggregationFunction = <neuralogic.core.constructs.function.function.AggregationFunction object>)[source]

Bases: Module

GraphSAGE layer from “Inductive Representation Learning on Large Graphs”. Which can be expressed as:

\[\mathbf{x}^{\prime}_i = act(\mathbf{W}_1 \mathbf{x}_i + \mathbf{W}_2 \cdot {agg}_{j \in \mathcal{N}(i)}(\mathbf{x}_j)))\]

Where act is an activation function, agg aggregation function and W’s are learnable parameters. This equation is translated into the logic form as:

(R.<output_name>(V.I)[<W1>] <= (R.<feature_name>(V.J), R.<edge_name>(V.J, V.I))) | [<aggregation>, Transformation.IDENTITY]
(R.<output_name>(V.I)[<W2>] <= R.<feature_name>(V.I)) | [Transformation.IDENTITY]
R.<output_name> / 1 | [<activation>]
Parameters:
  • in_channels (int) – Input feature size.

  • out_channels (int) – Output feature size.

  • output_name (str) – Output (head) predicate name of the module.

  • feature_name (str) – Feature predicate name to get features from.

  • edge_name (str) – Edge predicate name to use for neighborhood relations.

  • activation (TransformationFunction) – Activation function of the output. Default: Transformation.IDENTITY

  • aggregation (AggregationFunction) – Aggregation function of nodes’ neighbors. Default: Aggregation.AVG

neuralogic.nn.module.gnn.res_gated module

class ResGatedGraphConv(in_channels: int, out_channels: int, output_name: str, feature_name: str, edge_name: str, gating_activation: TransformationFunction = <neuralogic.core.constructs.function.function.TransformationFunction object>, activation: TransformationFunction = <neuralogic.core.constructs.function.function.TransformationFunction object>, aggregation: AggregationFunction = <neuralogic.core.constructs.function.function.AggregationFunction object>)[source]

Bases: Module

Residual Gated Graph Convolutional layer from “Residual Gated Graph ConvNets”. Which can be expressed as:

\[\mathbf{x}^{\prime}_i = act(\mathbf{W}_1 \mathbf{x}_i + {agg}_{j \in \mathcal{N}(i)}(\eta_{i,j} \odot \mathbf{W}_2 \mathbf{x}_j))\]
\[\mathbf{\eta}_{i,j} = gating\_act(\mathbf{W}_3 \mathbf{x}_i + \mathbf{W}_4 \mathbf{x}_j)\]

Where act is an activation function, agg aggregation function, gating_act is a gating activation function and \(W_n\) are learnable parameters. This equation is translated into the logic form as:

(R.<output_name>__gate(V.I, V.J) <= (R.<feature_name>(V.I)[<W>], R.<feature_name>(V.J)[<W>])) | [Transformation.IDENTITY]
R.<output_name>__gate / 2 | [<activation>]

(R.<output_name>(V.I) <= R.<feature_name>(V.I)[<W>]) | [Transformation.IDENTITY]
(R.<output_name>(V.I) <= (
    R.<output_name>__gate(V.I, V.J), R.<feature_name>(V.J)[<W>], R.<edge_name>(V.J, V.I))
) | Metadata(activation="elementproduct-identity", aggregation=<aggregation>)

R.<output_name> / 1 | [<activation>]

Examples

The whole computation of this module (parametrized as ResGatedGraphConv(1, 2, "h1", "h0", "_edge")) is as follows:

metadata = Metadata(activation="elementproduct-identity", aggregation=Aggregation.SUM)

(R.h1__gate(V.I, V.J) <= (R.h0(V.I)[2, 1], R.h0(V.J)[2, 1])) | [Transformation.IDENTITY]
R.h1__gate / 2 | [Transformation.SIGMOID]

(R.h1(V.I) <= R.h0(V.I)[2, 1]) | [Transformation.IDENTITY]
(R.h1(V.I) <= (R.h1__gate(V.I, V.J), R.h0(V.J)[2, 1], R._edge(V.J, V.I))) | metadata
R.h1 / 1 | [Transformation.IDENTITY]
Parameters:
  • in_channels (int) – Input feature size.

  • out_channels (int) – Output feature size.

  • output_name (str) – Output (head) predicate name of the module.

  • feature_name (str) – Feature predicate name to get features from.

  • edge_name (str) – Edge predicate name to use for neighborhood relations.

  • gating_activation (TransformationFunction) – Gating activation function. Default: Transformation.SIGMOID

  • activation (TransformationFunction) – Activation function of the output. Default: Transformation.IDENTITY

  • aggregation (AggregationFunction) – Aggregation function of nodes’ neighbors. Default: Aggregation.SUM

neuralogic.nn.module.gnn.rgcn module

class RGCNConv(in_channels: int, out_channels: int, output_name: str, feature_name: str, edge_name: str | None, relations: ~typing.List[str], activation: ~neuralogic.core.constructs.function.function.TransformationFunction = <neuralogic.core.constructs.function.function.TransformationFunction object>, aggregation: ~neuralogic.core.constructs.function.function.AggregationFunction = <neuralogic.core.constructs.function.function.AggregationFunction object>)[source]

Bases: Module

Relational Graph Convolutional layer from Modeling Relational Data with Graph Convolutional Networks. Which can be expressed as:

\[\mathbf{x}^{\prime}_i = act(\mathbf{W_0} \cdot \mathbf{x}_i + \sum_{r \in \mathcal{R}} {agg}_{j \in \mathcal{N}_r(i)}(\mathbf{W_r} \cdot \mathbf{x}_j))\]

Where act is an activation function, agg aggregation function (by default average), \(W_0\) is a learnable root parameter and \(W_r\) is a learnable parameter for each relation.

The first part of the equation that is “\(\mathbf{W_0} \cdot \mathbf{x}_i\)” can be expressed in the logic form as:

R.<output_name>(V.I) <= R.<feature_name>(V.I)[<W0>]

Another part of the equation that is “\({agg}_{j \in \mathcal{N}_r(i)}(\mathbf{W_r} \cdot \mathbf{x}_j)\)” can be expressed as:

R.<output_name>(V.I) <= (R.<feature_name>(V.J)[<Wr>], R.<edge_name>(V.J, relation, V.I))

where “relation” is a constant name, or as:

R.<output_name>(V.I) <= (R.<feature_name>(V.J)[<Wr>], R.<relation>(V.J, V.I))

The outer summation, together with summing it with the first part, is handled by aggregation of all rules with the same head (and substitution).

Examples

The whole computation of this module (parametrized as RGCNConv(1, 2, "h1", "h0", "_edge", ["sibling", "parent"])) is as follows:

metadata = Metadata(activation=Transformation.IDENTITY, aggregation=Aggregation.AVG)

(R.h1(V.I) <= R.h0(V.I)[2, 1]) | metadata
(R.h1(V.I) <= (R.h0(V.J)[2, 1], R._edge(V.J, sibling, V.I))) | metadata
(R.h1(V.I) <= (R.h0(V.J)[2, 1], R._edge(V.J, parent, V.I))) | metadata
R.h1 / 1 [Transformation.IDENTITY]

Module parametrized as RGCNConv(1, 2, "h1", "h0", None, ["sibling", "parent"]) translates into:

metadata = Metadata(activation=Transformation.IDENTITY, aggregation=Aggregation.AVG)

(R.h1(V.I) <= R.h0(V.I)[2, 1]) | metadata
(R.h1(V.I) <= (R.h0(V.J)[2, 1], R.sibling(V.J, V.I))) | metadata
(R.h1(V.I) <= (R.h0(V.J)[2, 1], R.parent(V.J, V.I))) | metadata
R.h1 / 1 [Transformation.IDENTITY]
Parameters:
  • in_channels (int) – Input feature size.

  • out_channels (int) – Output feature size.

  • output_name (str) – Output (head) predicate name of the module.

  • feature_name (str) – Feature predicate name to get features from.

  • edge_name (str | None) – Edge predicate name to use for neighborhood relations. When None, elements from relations are used instead.

  • relations (List[str]) – List of relations’ names

  • activation (TransformationFunction) – Activation function of the output. Default: Transformation.IDENTITY

  • aggregation (AggregationFunction) – Aggregation function of nodes’ neighbors. Default: Aggregation.SUM

neuralogic.nn.module.gnn.sg module

class SGConv(in_channels: int, out_channels: int, output_name: str, feature_name: str, edge_name: str, k: int = 1, activation: TransformationFunction = <neuralogic.core.constructs.function.function.TransformationFunction object>, aggregation: AggregationFunction = <neuralogic.core.constructs.function.function.AggregationFunction object>, add_self_loops: bool | None = None, normalize: bool = True)[source]

Bases: Module

Simple Graph Convolutional layer from “Simplifying Graph Convolutional Networks”. Which can be expressed as:

\[\mathbf{X}^{\prime} = act \left( {\left( \mathbf{\hat{D}}^{-1/2} \mathbf{\hat{A}} \mathbf{\hat{D}}^{-1/2} \right)}^k \mathbf{X} \mathbf{W} \right)\]

where \(\mathbf{\hat{A}} = \mathbf{A} + \mathbf{I}\), act is an activation function and W is a learnable parameter. Each of the k hops walks one edge and carries one normalization factor, so the equation is translated into the logic form as:

R.<output_name>__edge(V.I, V.I)[1.0].fixed()
R.<output_name>__edge(V.I, V.J) <= R.<edge_name>(V.I, V.J)

(R.<output_name>__edge_count(V.I, V.J) <= R.<output_name>__edge(V.X, V.J)) | [Aggregation.SUM]
(R.<output_name>__edge_count(V.I, V.J) <= R.<output_name>__edge(V.X, V.I)) | [Aggregation.SUM]
R.<output_name>__edge_count / 2 | [Combination.PRODUCT, Transformation.INVERSE]

(R.<output_name>(V.I<0>)[<W>] <= (
    R.<feature_name>(V.I<k>),
    R.<output_name>__edge(V.I<1>, V.I<0>), Transformation.SQRT(R.<output_name>__edge_count(V.I<1>, V.I<0>)),
    ...,
    R.<output_name>__edge(V.I<k>, V.I<k-1>), Transformation.SQRT(R.<output_name>__edge_count(V.I<k>, V.I<k-1>)),
)) | [<aggregation>, Combination.PRODUCT]

R.<output_name> / 1 | [<activation>]

The body combines by product, which is what makes the normalization factors scale the features rather than shift them - and it is also why the edge is an ordinary valued atom rather than a hidden one: an edge’s value is then PyG’s edge_weight, and at the natural 1.0 it is the identity of a product, so the plain spelling of a graph costs nothing.

Note __edge_count is a weighted **in-**degree despite its name: the two rules sum over V.X in the source position, and they sum the edge values rather than counting the edges. Both are what gcn_norm does, and both are invisible on an undirected graph given both directions at 1.0 - where in-degree is out-degree and summing ones is counting them - which is why the name survived being either.

Examples

Module parametrized as SGConv(2, 3, "h1", "h0", "_edge", 1) translates into:

R.h1__edge(V.I, V.I)[1.0].fixed()
R.h1__edge(V.I, V.J) <= R._edge(V.I, V.J)
(R.h1__edge_count(V.I, V.J) <= R.h1__edge(V.X, V.J)) | [Aggregation.SUM]
(R.h1__edge_count(V.I, V.J) <= R.h1__edge(V.X, V.I)) | [Aggregation.SUM]
R.h1__edge_count / 2 | [Combination.PRODUCT, Transformation.INVERSE]
(R.h1(V.I0)[3, 2] <= (R.h0(V.I1), R.h1__edge(V.I1, V.I0), Transformation.SQRT(R.h1__edge_count(V.I1, V.I0)))) | [Aggregation.SUM, Combination.PRODUCT]
R.h1 / 1 | [Transformation.IDENTITY]

Setting normalize=False and add_self_loops=False drops both groups of extra rules and leaves the plain walk-and-sum this module used to be - which is not what PyG computes.

Parameters:
  • in_channels (int) – Input feature size.

  • out_channels (int) – Output feature size.

  • output_name (str) – Output (head) predicate name of the module.

  • feature_name (str) – Feature predicate name to get features from.

  • edge_name (str) – Edge predicate name to use for neighborhood relations.

  • k (int) – Number of hops. Default: 1

  • activation (TransformationFunction) – Activation function of the output. Default: Transformation.IDENTITY

  • aggregation (AggregationFunction) – Aggregation function of nodes’ neighbors. Default: Aggregation.SUM

  • add_self_loops (bool | None) – Add self loops if either set to True or None (if normalize is True). Default: None

  • normalize (bool) – Add symmetric normalization. Default: True

neuralogic.nn.module.gnn.tag module

class TAGConv(in_channels: int, out_channels: int, output_name: str, feature_name: str, edge_name: str, k: int = 2, activation: TransformationFunction = <neuralogic.core.constructs.function.function.TransformationFunction object>, aggregation: AggregationFunction = <neuralogic.core.constructs.function.function.AggregationFunction object>, normalize: bool = True)[source]

Bases: Module

Topology Adaptive Graph Convolutional layer from “Topology Adaptive Graph Convolutional Networks”. Which can be expressed as:

\[\mathbf{X}^{\prime} = act \left( \sum_{k=0}^K {\left( \mathbf{D}^{-1/2} \mathbf{A} \mathbf{D}^{-1/2} \right)}^k \mathbf{X} \mathbf{W}_k \right)\]

Where act is an activation function and Wk are learnable parameters, one per hop. Note this normalization is not the one GCNConv and SGConv use: there are no self-loops, so the degree is the plain degree rather than one more than it, matching PyG’s add_self_loops=False. The equation is translated into the logic form as:

(R.<output_name>__edge_count(V.I, V.J) <= R.<edge_name>(V.X, V.J)) | [Aggregation.SUM]
(R.<output_name>__edge_count(V.I, V.J) <= R.<edge_name>(V.X, V.I)) | [Aggregation.SUM]
R.<output_name>__edge_count / 2 | [Combination.PRODUCT, Transformation.INVERSE]

(R.<output_name>(V.I0)[<W0>] <= R.<feature_name>(V.I0)) | [<aggregation>, Combination.PRODUCT]
(R.<output_name>(V.I0)[<W1>] <= (R.<feature_name>(V.I1), R.<edge_name>(V.I1, V.I0), Transformation.SQRT(R.<output_name>__edge_count(V.I1, V.I0)))) | [<aggregation>, Combination.PRODUCT]
...
R.<output_name> / 1 | [<activation>]

Examples

Module parametrized as TAGConv(1, 2, "h1", "h0", "_edge", 1) translates into:

(R.h1__edge_count(V.I, V.J) <= R._edge(V.X, V.J)) | [Aggregation.SUM]
(R.h1__edge_count(V.I, V.J) <= R._edge(V.X, V.I)) | [Aggregation.SUM]
R.h1__edge_count / 2 | [Combination.PRODUCT, Transformation.INVERSE]
(R.h1(V.I0)[2, 1] <= R.h0(V.I0)) | [Aggregation.SUM, Combination.PRODUCT]
(R.h1(V.I0)[2, 1] <= (R.h0(V.I1), R._edge(V.I1, V.I0), Transformation.SQRT(R.h1__edge_count(V.I1, V.I0)))) | [Aggregation.SUM, Combination.PRODUCT]
R.h1 / 1 | [Transformation.IDENTITY]

Setting normalize=False drops the normalization rules and leaves the plain walk-and-sum this module used to be - which is not what PyG computes. An edge’s value is PyG’s edge_weight: it scales the message it carries and enters the degree, which despite the __edge_count name is a weighted **in-**degree, as in SGConv.

One difference from PyG that normalizing without self-loops leaves: a node of degree zero has no normalization atom at all, so its hop rules do not ground, where PyG makes that factor zero instead - agreeing on the value and not on whether the head exists.

Parameters:
  • in_channels (int) – Input feature size.

  • out_channels (int) – Output feature size.

  • output_name (str) – Output (head) predicate name of the module.

  • feature_name (str) – Feature predicate name to get features from.

  • edge_name (str) – Edge predicate name to use for neighborhood relations.

  • k (int) – Number of hops. Default: 2

  • activation (TransformationFunction) – Activation function of the output. Default: Transformation.IDENTITY

  • aggregation (AggregationFunction) – Aggregation function of nodes’ neighbors. Default: Aggregation.SUM

  • normalize (bool) – Add symmetric normalization. No self-loops are added either way, matching PyG. Default: True

Module contents

Graph Neural Network modules.

Individual GNN layer implementations are imported at the parent neuralogic.nn.module level via their respective submodules.