Skip to content

HLP (Hyperlink prediction)

hypertorch.hlp

Node2VecMode = Literal['precomputed', 'joint'] module-attribute

Training mode for Node2Vec-based hyperlink prediction encoders.

HlpModule

Bases: LightningModule

A LightningModule for HLP models with optional negative sampling.

Attributes:

Name Type Description
encoder Module | None

Optional encoder module. Defaults to None as not all HLP model use an encoder.

decoder Module

Decoder module to use to predict whether hyperedges are positive or negative.

loss_fn Module

Loss function.

metrics_log_kwargs dict[str, Any]

Additional keyword arguments to pass to all self.log calls for metrics. Useful for configuring distributed synchronization behavior of torchmetrics. Defaults to None.

train_metrics MetricCollection | None

Optional metric collection for training.

val_metrics MetricCollection | None

Optional metric collection for validation.

test_metrics MetricCollection | None

Optional metric collection for testing.

Source code in hypertorch/hlp/common.py
class HlpModule(L.LightningModule):
    """
    A LightningModule for HLP models with optional negative sampling.

    Attributes:
        encoder: Optional encoder module. Defaults to ``None`` as not
            all HLP model use an encoder.
        decoder: Decoder module to use to predict whether hyperedges are positive or negative.
        loss_fn: Loss function.
        metrics_log_kwargs: Additional keyword arguments to pass to all ``self.log`` calls
            for metrics. Useful for configuring distributed synchronization behavior of
            ``torchmetrics``. Defaults to ``None``.
        train_metrics: Optional metric collection for training.
        val_metrics: Optional metric collection for validation.
        test_metrics: Optional metric collection for testing.
    """

    def __init__(
        self,
        decoder: nn.Module,
        loss_fn: nn.Module,
        encoder: nn.Module | None = None,
        metrics: MetricCollection | None = None,
        metrics_log_kwargs: dict[str, Any] | None = None,
        negative_sampler: NegativeSampler | None = None,
        negative_sampling_schedule: NegativeSamplingSchedule = "every_epoch",
        negative_sampling_every_n: int = 1,
    ):
        """
        Initialize the HLP Lightning module.

        Args:
            decoder: Decoder module used to score hyperedges.
            loss_fn: Loss function.
            encoder: Optional encoder module. Defaults to ``None``.
            metrics: Optional metric collection cloned independently per stage.
                Defaults to ``None``.
            metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
                Defaults to ``None``.
            negative_sampler: Optional negative sampler. Defaults to ``None``.
            negative_sampling_schedule: Schedule controlling when negatives are sampled.
                Defaults to ``"every_epoch"``.
            negative_sampling_every_n: Epoch interval for ``"every_n_epochs"`` scheduling.
                Defaults to ``1``.
        """
        super().__init__()
        self.encoder: nn.Module | None = encoder
        self.decoder: nn.Module = decoder
        self.loss_fn: nn.Module = loss_fn
        self.metrics_log_kwargs: dict[str, Any] = metrics_log_kwargs or {}

        if metrics is not None:
            self.train_metrics: MetricCollection | None = metrics.clone(
                prefix=stage_metric_prefix(Stage.TRAIN)
            )
            self.val_metrics: MetricCollection | None = metrics.clone(
                prefix=stage_metric_prefix(Stage.VAL)
            )
            self.test_metrics: MetricCollection | None = metrics.clone(
                prefix=stage_metric_prefix(Stage.TEST)
            )
        else:
            self.train_metrics: MetricCollection | None = None
            self.val_metrics: MetricCollection | None = None
            self.test_metrics: MetricCollection | None = None

        self.__negative_sampling_scheduler: NegativeSamplingScheduler | None = None
        if negative_sampler is not None:
            self.__negative_sampling_scheduler = NegativeSamplingScheduler(
                negative_sampler,
                negative_sampling_schedule,
                negative_sampling_every_n,
            )

    @property
    def negative_sampling_config(self) -> dict[str, Any]:
        """
        Return the configured negative-sampling options.

        Returns:
            config: Scheduler configuration, or an empty dictionary when negative sampling is off.
        """
        if self.__negative_sampling_scheduler is None:
            return {}
        return self.__negative_sampling_scheduler.config

    def _compute_loss(
        self,
        scores: Tensor,
        labels: Tensor,
        batch_size: int,
        stage: Stage,
    ) -> Tensor:
        """
        Compute and log loss based on scores and labels.

        Args:
            scores: The predicted scores from the model.
            labels: The true labels corresponding to the scores.
            batch_size: The size of the current batch, used for logging.
            stage: The current stage (train/val/test) for logging purposes.

        Returns:
            loss: The computed loss tensor.
        """
        loss = self.loss_fn(scores, labels)
        self.log(
            name=stage_metric_name(stage, "loss"),
            value=loss,
            prog_bar=True,
            batch_size=batch_size,
            **self.metrics_log_kwargs,
        )
        return loss

    def _compute_metrics(
        self,
        scores: Tensor,
        labels: Tensor,
        batch_size: int,
        stage: Stage,
    ) -> None:
        """
        Compute and log metrics based on scores and labels.

        Uses class-based torchmetrics with proper multi-batch accumulation:
        1. ``update()`` accumulates predictions/targets across batches.
        2. Passing the MetricCollection to ``self.log_dict()`` tells Lightning to call
            ``compute()`` at epoch end and ``reset()`` automatically.

        Args:
            scores: The predicted scores (logits) from the model.
            labels: The true labels corresponding to the scores.
            batch_size: The size of the current batch, used for logging.
            stage: The current stage (train/val/test) for logging purposes.
        """
        stage_metrics = self._get_stage_metrics(stage)
        if stage_metrics is None:
            return  # No metrics to compute
        self._configure_metric_distributed_available(stage_metrics)

        # Apply sigmoid to convert logits to probabilities as BinaryAUROC
        # and BinaryAveragePrecision expect probabilities in [0, 1]
        preds = torch.sigmoid(scores)
        targets = labels.long()

        # Accumulate predictions/targets for this batch
        stage_metrics.update(preds, targets)

        self.log_dict(
            stage_metrics,
            prog_bar=True,
            on_step=False,
            on_epoch=True,  # Compute and log metrics at epoch end for proper accumulation
            batch_size=batch_size,
            **self.metrics_log_kwargs,
        )

    def _get_stage_metrics(self, stage: Stage) -> MetricCollection | None:
        """
        Return the metric collection for the given stage, or ``None``.

        Args:
            stage: The current stage (train/val/test) for which to get metrics.

        Returns:
            metrics: The metric collection corresponding to the given stage, or ``None``
                if no metrics are configured.
        """
        match stage:
            case Stage.TRAIN:
                return self.train_metrics
            case Stage.VAL:
                return self.val_metrics
            case Stage.TEST:
                return self.test_metrics
            case _:
                raise ValueError(f"Unrecognized stage: {stage}")

    def _configure_metric_distributed_available(self, metrics: MetricCollection) -> None:
        """
        Make torchmetrics sync decisions follow the active Lightning trainer.

        TorchMetrics checks ``jit_distributed_available()`` by default. After DDP
        training, the process group can still be alive while a separate test trainer is
        intentionally single-device. In that case metric states must not all-gather.

        Args:
            metrics: The metric collection to configure.
        """
        for metric in metrics.values(copy_state=False):
            metric.distributed_available_fn = self._distributed_available_fn

    def _distributed_available_fn(self) -> bool:
        """
        Return whether metrics should synchronize for the current trainer.

        This is needed as ``distributed_available_fn`` defaults to a check of
        ``torch.distributed.is_available()`` and ``torch.distributed.is_initialized()``.
        In our case, we can have a single-device test trainer after DDP training,
        so we want to disable metric synchronization if the trainer is not multi-process, not
        only if ``torch.distributed`` is not initialized.
        The issue is that without ``trainer.world_size > 1``, the single-device trainer
        tries to sync across DDP process group and hangs because it's the only one in that group.

        Returns:
            True when the attached trainer is multi-process and torch.distributed is initialized.
        """
        if self._trainer is None:
            return False
        return (
            self._trainer.world_size > 1
            and torch.distributed.is_available()
            and torch.distributed.is_initialized()
        )

    def _target_scores_and_labels(self, scores: Tensor, batch: HData) -> tuple[Tensor, Tensor]:
        """
        Select supervised hyperedge scores and labels from a full-context HLP batch.

        Args:
            scores: Model scores for all hyperedges in ``batch``.
            batch: Input HData batch containing ``target_hyperedge_mask``.

        Returns:
            target_scores: Scores for supervised target hyperedges.
            target_labels: Labels for supervised target hyperedges.

        Raises:
            ValueError: If the score tensor does not align with the batch hyperedge mask.
        """
        target_hyperedge_mask = batch.target_hyperedge_mask
        target_scores = scores[target_hyperedge_mask]
        target_labels = batch.y[target_hyperedge_mask]
        return target_scores, target_labels

    def _should_sample_negatives(self) -> bool:
        """
        Whether to resample negatives for the current epoch.
        """
        if self.__negative_sampling_scheduler is None:
            raise ValueError(
                "Asked to check negative sampling schedule but no negative sampler is configured."
            )
        return self.__negative_sampling_scheduler.should_sample(self.current_epoch)

    def _sample_negatives(self, batch: HData) -> HData:
        """
        Sample fresh negatives if the schedule requires it, otherwise return cache.

        Args:
            batch: The current batch of data for which to sample negatives.

        Returns:
            negatives: A batch of negative samples, either freshly sampled or from cache.
        """
        if self.__negative_sampling_scheduler is None:
            raise ValueError("Asked to sample negatives but no negative sampler is not configured.")
        return self.__negative_sampling_scheduler.sample(batch, self.current_epoch)

negative_sampling_config property

Return the configured negative-sampling options.

Returns:

Name Type Description
config dict[str, Any]

Scheduler configuration, or an empty dictionary when negative sampling is off.

__init__(decoder, loss_fn, encoder=None, metrics=None, metrics_log_kwargs=None, negative_sampler=None, negative_sampling_schedule='every_epoch', negative_sampling_every_n=1)

Initialize the HLP Lightning module.

Parameters:

Name Type Description Default
decoder Module

Decoder module used to score hyperedges.

required
loss_fn Module

Loss function.

required
encoder Module | None

Optional encoder module. Defaults to None.

None
metrics MetricCollection | None

Optional metric collection cloned independently per stage. Defaults to None.

None
metrics_log_kwargs dict[str, Any] | None

Additional keyword arguments passed to metric log calls. Defaults to None.

None
negative_sampler NegativeSampler | None

Optional negative sampler. Defaults to None.

None
negative_sampling_schedule NegativeSamplingSchedule

Schedule controlling when negatives are sampled. Defaults to "every_epoch".

'every_epoch'
negative_sampling_every_n int

Epoch interval for "every_n_epochs" scheduling. Defaults to 1.

1
Source code in hypertorch/hlp/common.py
def __init__(
    self,
    decoder: nn.Module,
    loss_fn: nn.Module,
    encoder: nn.Module | None = None,
    metrics: MetricCollection | None = None,
    metrics_log_kwargs: dict[str, Any] | None = None,
    negative_sampler: NegativeSampler | None = None,
    negative_sampling_schedule: NegativeSamplingSchedule = "every_epoch",
    negative_sampling_every_n: int = 1,
):
    """
    Initialize the HLP Lightning module.

    Args:
        decoder: Decoder module used to score hyperedges.
        loss_fn: Loss function.
        encoder: Optional encoder module. Defaults to ``None``.
        metrics: Optional metric collection cloned independently per stage.
            Defaults to ``None``.
        metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
            Defaults to ``None``.
        negative_sampler: Optional negative sampler. Defaults to ``None``.
        negative_sampling_schedule: Schedule controlling when negatives are sampled.
            Defaults to ``"every_epoch"``.
        negative_sampling_every_n: Epoch interval for ``"every_n_epochs"`` scheduling.
            Defaults to ``1``.
    """
    super().__init__()
    self.encoder: nn.Module | None = encoder
    self.decoder: nn.Module = decoder
    self.loss_fn: nn.Module = loss_fn
    self.metrics_log_kwargs: dict[str, Any] = metrics_log_kwargs or {}

    if metrics is not None:
        self.train_metrics: MetricCollection | None = metrics.clone(
            prefix=stage_metric_prefix(Stage.TRAIN)
        )
        self.val_metrics: MetricCollection | None = metrics.clone(
            prefix=stage_metric_prefix(Stage.VAL)
        )
        self.test_metrics: MetricCollection | None = metrics.clone(
            prefix=stage_metric_prefix(Stage.TEST)
        )
    else:
        self.train_metrics: MetricCollection | None = None
        self.val_metrics: MetricCollection | None = None
        self.test_metrics: MetricCollection | None = None

    self.__negative_sampling_scheduler: NegativeSamplingScheduler | None = None
    if negative_sampler is not None:
        self.__negative_sampling_scheduler = NegativeSamplingScheduler(
            negative_sampler,
            negative_sampling_schedule,
            negative_sampling_every_n,
        )

CommonNeighborsHlpModule

Bases: HlpModule

A LightningModule for the CommonNeighbors model with optional negative sampling.

Attributes:

Name Type Description
encoder Module | None

Optional encoder module inherited from HlpModule. Defaults to None.

decoder Module

Common-neighbor decoder inherited from HlpModule.

loss_fn Module

Loss function inherited from HlpModule.

metrics_log_kwargs dict[str, Any]

Metric logging keyword arguments inherited from HlpModule.

train_metrics MetricCollection | None

Optional training metrics inherited from HlpModule.

val_metrics MetricCollection | None

Optional validation metrics inherited from HlpModule.

test_metrics MetricCollection | None

Optional test metrics inherited from HlpModule.

node_to_neighbors dict[int, Neighborhood]

Precomputed training-world node neighborhoods.

automatic_optimization bool

Disabled because this module has no trainable optimization.

Source code in hypertorch/hlp/common_neighbors_hlp.py
class CommonNeighborsHlpModule(HlpModule):
    """
    A LightningModule for the CommonNeighbors model with optional negative sampling.

    Attributes:
        encoder: Optional encoder module inherited from ``HlpModule``. Defaults to ``None``.
        decoder: Common-neighbor decoder inherited from ``HlpModule``.
        loss_fn: Loss function inherited from ``HlpModule``.
        metrics_log_kwargs: Metric logging keyword arguments inherited from ``HlpModule``.
        train_metrics: Optional training metrics inherited from ``HlpModule``.
        val_metrics: Optional validation metrics inherited from ``HlpModule``.
        test_metrics: Optional test metrics inherited from ``HlpModule``.
        node_to_neighbors: Precomputed training-world node neighborhoods.
        automatic_optimization: Disabled because this module has no trainable optimization.
    """

    def __init__(
        self,
        train_hdata: HData,
        aggregation: Literal["mean", "min", "sum"] = "mean",
        decoder: nn.Module | None = None,
        loss_fn: nn.Module | None = None,
        metrics: MetricCollection | None = None,
        metrics_log_kwargs: dict[str, Any] | None = None,
    ):
        """
        Initialize the CommonNeighbors HLP module.

        Args:
            train_hdata: Training data used to precompute neighborhoods.
            aggregation: Common-neighbor aggregation method. Defaults to ``"mean"``.
            decoder: Optional decoder module. Defaults to ``CommonNeighbors``.
            loss_fn: Optional loss function. Defaults to ``BCEWithLogitsLoss``.
            metrics: Optional metric collection for evaluation. Defaults to ``None``.
            metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
                Defaults to ``None``.
        """
        super().__init__(
            decoder=decoder if decoder is not None else CommonNeighbors(aggregation),
            loss_fn=loss_fn if loss_fn is not None else nn.BCEWithLogitsLoss(),
            metrics=metrics,
            metrics_log_kwargs=metrics_log_kwargs,
        )

        # Pre-compute neighbors of training nodes based on training edges only
        # to create a "known world" for the model to make predictions from
        self.node_to_neighbors: dict[int, Neighborhood] = Hypergraph.from_hyperedge_index(
            hyperedge_index=HyperedgeIndex(
                hyperedge_index=train_hdata.hyperedge_index,
            )
            .to_global(global_node_ids=train_hdata.global_node_ids)
            .item
        ).neighbors_of_all()

        # Disable automatic optimization since there is no training
        self.automatic_optimization: bool = False

    def forward(self, candidate_nodes: Tensor, hyperedge_index: Tensor) -> Tensor:
        """
        Compute common neighbor scores for the given hyperedges.

        Args:
            candidate_nodes: Tensor containing node IDs to score of shape ``(num_nodes,)``.
            hyperedge_index: Tensor containing incidence information for the hyperedges to score.

        Returns:
            scores: A 1-D tensor of shape (num_hyperedges,) with CN scores.
        """
        return self.decoder(
            candidate_nodes=candidate_nodes,
            hyperedge_index=hyperedge_index,
            node_to_neighbors=self.node_to_neighbors,
        )

    def on_fit_start(self) -> None:
        """
        Warn users if they are running unnecessary training epochs.
        """
        if self.trainer.max_epochs is None or self.trainer.max_epochs > 0:
            warnings.warn(
                f"{self.__class__.__name__} is a non-trainable heuristic model. "
                "No optimization occurs. Set max_epochs=0 in your trainer for instant evaluation.",
                UserWarning,
                stacklevel=2,
            )

    def training_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Return a zero loss for the non-trainable heuristic.

        Args:
            batch: Input batch, unused.
            batch_idx: Batch index, unused.

        Returns:
            loss: Zero scalar tensor on the module device.
        """
        return torch.tensor(0.0, dtype=torch.float, device=self.device)

    def validation_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Return a zero validation loss for the non-trainable heuristic.

        Args:
            batch: Input batch, unused.
            batch_idx: Batch index, unused.

        Returns:
            loss: Zero scalar tensor on the module device.
        """
        return torch.tensor(0.0, dtype=torch.float, device=self.device)

    def test_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Evaluate a test batch.

        Args:
            batch: Test batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Test loss.
        """
        return self.__step(batch, stage=Stage.TEST)

    def predict_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Predict common-neighbor scores for a batch.

        Args:
            batch: Prediction batch.
            batch_idx: Batch index, unused.

        Returns:
            scores: Predicted hyperedge scores.
        """
        return self.forward(
            # Use the global node IDs from the batch to predict scores for the hyperedges
            # as the model is trained on the node IDs from the training data
            candidate_nodes=batch.global_node_ids,
            hyperedge_index=batch.hyperedge_index,
        )

    def configure_optimizers(self) -> None:
        """
        Return no optimizer for the non-trainable heuristic.

        Returns:
            optimizer: No optimizer is configured, so always returns ``None``.
        """
        # No training, so no optimizers needed

    def __step(self, batch: HData, stage: Stage) -> Tensor:
        """
        Shared evaluation logic for all stages.

        Args:
            batch: `HData` object containing the hypergraph.
            stage: The current stage of evaluation
                (e.g., ``Stage.TRAIN``, ``Stage.VAL``, ``Stage.TEST``).

        Returns:
            loss: The computed loss.
        """
        scores = self.forward(
            # Use the global node IDs from the batch to compute scores for the hyperedges
            # as the model is trained on the node IDs from the training data
            candidate_nodes=batch.global_node_ids,
            hyperedge_index=batch.hyperedge_index,
        )
        target_scores, target_labels = self._target_scores_and_labels(scores, batch)
        batch_size = target_labels.size(0)

        loss = self._compute_loss(target_scores, target_labels, batch_size, stage)
        self._compute_metrics(target_scores, target_labels, batch_size, stage)

        return loss

__init__(train_hdata, aggregation='mean', decoder=None, loss_fn=None, metrics=None, metrics_log_kwargs=None)

Initialize the CommonNeighbors HLP module.

Parameters:

Name Type Description Default
train_hdata HData

Training data used to precompute neighborhoods.

required
aggregation Literal['mean', 'min', 'sum']

Common-neighbor aggregation method. Defaults to "mean".

'mean'
decoder Module | None

Optional decoder module. Defaults to CommonNeighbors.

None
loss_fn Module | None

Optional loss function. Defaults to BCEWithLogitsLoss.

None
metrics MetricCollection | None

Optional metric collection for evaluation. Defaults to None.

None
metrics_log_kwargs dict[str, Any] | None

Additional keyword arguments passed to metric log calls. Defaults to None.

None
Source code in hypertorch/hlp/common_neighbors_hlp.py
def __init__(
    self,
    train_hdata: HData,
    aggregation: Literal["mean", "min", "sum"] = "mean",
    decoder: nn.Module | None = None,
    loss_fn: nn.Module | None = None,
    metrics: MetricCollection | None = None,
    metrics_log_kwargs: dict[str, Any] | None = None,
):
    """
    Initialize the CommonNeighbors HLP module.

    Args:
        train_hdata: Training data used to precompute neighborhoods.
        aggregation: Common-neighbor aggregation method. Defaults to ``"mean"``.
        decoder: Optional decoder module. Defaults to ``CommonNeighbors``.
        loss_fn: Optional loss function. Defaults to ``BCEWithLogitsLoss``.
        metrics: Optional metric collection for evaluation. Defaults to ``None``.
        metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
            Defaults to ``None``.
    """
    super().__init__(
        decoder=decoder if decoder is not None else CommonNeighbors(aggregation),
        loss_fn=loss_fn if loss_fn is not None else nn.BCEWithLogitsLoss(),
        metrics=metrics,
        metrics_log_kwargs=metrics_log_kwargs,
    )

    # Pre-compute neighbors of training nodes based on training edges only
    # to create a "known world" for the model to make predictions from
    self.node_to_neighbors: dict[int, Neighborhood] = Hypergraph.from_hyperedge_index(
        hyperedge_index=HyperedgeIndex(
            hyperedge_index=train_hdata.hyperedge_index,
        )
        .to_global(global_node_ids=train_hdata.global_node_ids)
        .item
    ).neighbors_of_all()

    # Disable automatic optimization since there is no training
    self.automatic_optimization: bool = False

forward(candidate_nodes, hyperedge_index)

Compute common neighbor scores for the given hyperedges.

Parameters:

Name Type Description Default
candidate_nodes Tensor

Tensor containing node IDs to score of shape (num_nodes,).

required
hyperedge_index Tensor

Tensor containing incidence information for the hyperedges to score.

required

Returns:

Name Type Description
scores Tensor

A 1-D tensor of shape (num_hyperedges,) with CN scores.

Source code in hypertorch/hlp/common_neighbors_hlp.py
def forward(self, candidate_nodes: Tensor, hyperedge_index: Tensor) -> Tensor:
    """
    Compute common neighbor scores for the given hyperedges.

    Args:
        candidate_nodes: Tensor containing node IDs to score of shape ``(num_nodes,)``.
        hyperedge_index: Tensor containing incidence information for the hyperedges to score.

    Returns:
        scores: A 1-D tensor of shape (num_hyperedges,) with CN scores.
    """
    return self.decoder(
        candidate_nodes=candidate_nodes,
        hyperedge_index=hyperedge_index,
        node_to_neighbors=self.node_to_neighbors,
    )

on_fit_start()

Warn users if they are running unnecessary training epochs.

Source code in hypertorch/hlp/common_neighbors_hlp.py
def on_fit_start(self) -> None:
    """
    Warn users if they are running unnecessary training epochs.
    """
    if self.trainer.max_epochs is None or self.trainer.max_epochs > 0:
        warnings.warn(
            f"{self.__class__.__name__} is a non-trainable heuristic model. "
            "No optimization occurs. Set max_epochs=0 in your trainer for instant evaluation.",
            UserWarning,
            stacklevel=2,
        )

training_step(batch, batch_idx)

Return a zero loss for the non-trainable heuristic.

Parameters:

Name Type Description Default
batch HData

Input batch, unused.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Zero scalar tensor on the module device.

Source code in hypertorch/hlp/common_neighbors_hlp.py
def training_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Return a zero loss for the non-trainable heuristic.

    Args:
        batch: Input batch, unused.
        batch_idx: Batch index, unused.

    Returns:
        loss: Zero scalar tensor on the module device.
    """
    return torch.tensor(0.0, dtype=torch.float, device=self.device)

validation_step(batch, batch_idx)

Return a zero validation loss for the non-trainable heuristic.

Parameters:

Name Type Description Default
batch HData

Input batch, unused.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Zero scalar tensor on the module device.

Source code in hypertorch/hlp/common_neighbors_hlp.py
def validation_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Return a zero validation loss for the non-trainable heuristic.

    Args:
        batch: Input batch, unused.
        batch_idx: Batch index, unused.

    Returns:
        loss: Zero scalar tensor on the module device.
    """
    return torch.tensor(0.0, dtype=torch.float, device=self.device)

test_step(batch, batch_idx)

Evaluate a test batch.

Parameters:

Name Type Description Default
batch HData

Test batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Test loss.

Source code in hypertorch/hlp/common_neighbors_hlp.py
def test_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Evaluate a test batch.

    Args:
        batch: Test batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Test loss.
    """
    return self.__step(batch, stage=Stage.TEST)

predict_step(batch, batch_idx)

Predict common-neighbor scores for a batch.

Parameters:

Name Type Description Default
batch HData

Prediction batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
scores Tensor

Predicted hyperedge scores.

Source code in hypertorch/hlp/common_neighbors_hlp.py
def predict_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Predict common-neighbor scores for a batch.

    Args:
        batch: Prediction batch.
        batch_idx: Batch index, unused.

    Returns:
        scores: Predicted hyperedge scores.
    """
    return self.forward(
        # Use the global node IDs from the batch to predict scores for the hyperedges
        # as the model is trained on the node IDs from the training data
        candidate_nodes=batch.global_node_ids,
        hyperedge_index=batch.hyperedge_index,
    )

configure_optimizers()

Return no optimizer for the non-trainable heuristic.

Returns:

Name Type Description
optimizer None

No optimizer is configured, so always returns None.

Source code in hypertorch/hlp/common_neighbors_hlp.py
def configure_optimizers(self) -> None:
    """
    Return no optimizer for the non-trainable heuristic.

    Returns:
        optimizer: No optimizer is configured, so always returns ``None``.
    """

__step(batch, stage)

Shared evaluation logic for all stages.

Parameters:

Name Type Description Default
batch HData

HData object containing the hypergraph.

required
stage Stage

The current stage of evaluation (e.g., Stage.TRAIN, Stage.VAL, Stage.TEST).

required

Returns:

Name Type Description
loss Tensor

The computed loss.

Source code in hypertorch/hlp/common_neighbors_hlp.py
def __step(self, batch: HData, stage: Stage) -> Tensor:
    """
    Shared evaluation logic for all stages.

    Args:
        batch: `HData` object containing the hypergraph.
        stage: The current stage of evaluation
            (e.g., ``Stage.TRAIN``, ``Stage.VAL``, ``Stage.TEST``).

    Returns:
        loss: The computed loss.
    """
    scores = self.forward(
        # Use the global node IDs from the batch to compute scores for the hyperedges
        # as the model is trained on the node IDs from the training data
        candidate_nodes=batch.global_node_ids,
        hyperedge_index=batch.hyperedge_index,
    )
    target_scores, target_labels = self._target_scores_and_labels(scores, batch)
    batch_size = target_labels.size(0)

    loss = self._compute_loss(target_scores, target_labels, batch_size, stage)
    self._compute_metrics(target_scores, target_labels, batch_size, stage)

    return loss

GCNEncoderConfig

Bases: TypedDict

Configuration for the GCN encoder in GCNHlpModule.

Attributes:

Name Type Description
in_channels int

Number of input features per node.

out_channels int

Number of output features (embedding size) per node.

hidden_channels NotRequired[int]

Number of hidden units in the intermediate GCN layers.

num_layers NotRequired[int]

Number of GCN layers. Defaults to 2.

drop_rate NotRequired[float]

Dropout rate applied after each hidden GCN layer. Defaults to 0.0.

bias NotRequired[bool]

Whether to include bias terms. Defaults to True.

improved NotRequired[bool]

Whether to use the improved GCN normalization. Defaults to False.

add_self_loops NotRequired[bool]

Whether to add self-loops before convolution. Defaults to True.

normalize NotRequired[bool]

Whether to normalize the adjacency matrix in GCNConv. Defaults to True.

cached NotRequired[bool]

Whether to cache the normalized graph in GCNConv. Defaults to False.

graph_reduction_strategy NotRequired[GraphReductionStrategy]

Strategy for reducing the hypergraph to a graph. Defaults to "clique_expansion".

num_nodes NotRequired[int]

Total number of nodes in the hypergraph. This is useful when setting is transductive but train dataset may not contain all hyperedges where some nodes appear, to ensure consistent encoding across splits.

activation_fn NotRequired[ActivationFn]

Activation function to use after each hidden layer. Defaults to nn.ReLU.

activation_fn_kwargs NotRequired[dict]

Keyword arguments for the activation function. Defaults to empty dict.

Source code in hypertorch/hlp/gcn_hlp.py
class GCNEncoderConfig(TypedDict):
    """
    Configuration for the GCN encoder in GCNHlpModule.

    Attributes:
        in_channels: Number of input features per node.
        out_channels: Number of output features (embedding size) per node.
        hidden_channels: Number of hidden units in the intermediate GCN layers.
        num_layers: Number of GCN layers. Defaults to ``2``.
        drop_rate: Dropout rate applied after each hidden GCN layer. Defaults to ``0.0``.
        bias: Whether to include bias terms. Defaults to ``True``.
        improved: Whether to use the improved GCN normalization. Defaults to ``False``.
        add_self_loops: Whether to add self-loops before convolution. Defaults to ``True``.
        normalize: Whether to normalize the adjacency matrix in ``GCNConv``. Defaults to ``True``.
        cached: Whether to cache the normalized graph in ``GCNConv``. Defaults to ``False``.
        graph_reduction_strategy: Strategy for reducing the hypergraph to a graph.
            Defaults to ``"clique_expansion"``.
        num_nodes: Total number of nodes in the hypergraph. This is useful when setting is
            transductive but train dataset may not contain all hyperedges where some nodes appear,
            to ensure consistent encoding across splits.
        activation_fn: Activation function to use after each hidden layer. Defaults to ``nn.ReLU``.
        activation_fn_kwargs: Keyword arguments for the activation function. Defaults to empty dict.
    """

    in_channels: int
    out_channels: int
    hidden_channels: NotRequired[int]
    num_layers: NotRequired[int]
    drop_rate: NotRequired[float]
    bias: NotRequired[bool]
    improved: NotRequired[bool]
    add_self_loops: NotRequired[bool]
    normalize: NotRequired[bool]
    cached: NotRequired[bool]
    graph_reduction_strategy: NotRequired[GraphReductionStrategy]
    num_nodes: NotRequired[int]
    activation_fn: NotRequired[ActivationFn]
    activation_fn_kwargs: NotRequired[dict]

GCNHlpModule

Bases: HlpModule

A LightningModule for GCN-based HLP.

Uses a graph reduction of the input hypergraph to run GCN over nodes, aggregates node embeddings per hyperedge, and scores each hyperedge with a linear decoder.

Attributes:

Name Type Description
encoder Module | None

GCN encoder module inherited from HlpModule.

decoder Module

SLP decoder module inherited from HlpModule.

loss_fn Module

Loss function inherited from HlpModule.

metrics_log_kwargs dict[str, Any]

Metric logging keyword arguments inherited from HlpModule.

train_metrics MetricCollection | None

Optional training metrics inherited from HlpModule.

val_metrics MetricCollection | None

Optional validation metrics inherited from HlpModule.

test_metrics MetricCollection | None

Optional test metrics inherited from HlpModule.

encoder_config GCNEncoderConfig

Configuration for the GCN encoder.

aggregation Literal['mean', 'max', 'min', 'sum']

Method to aggregate node embeddings per hyperedge. Defaults to "mean".

lr float

Learning rate for the optimizer. Defaults to 0.001.

weight_decay float

L2 regularization. Defaults to 0.0.

Source code in hypertorch/hlp/gcn_hlp.py
class GCNHlpModule(HlpModule):
    """
    A LightningModule for GCN-based HLP.

    Uses a graph reduction of the input hypergraph to run GCN over nodes,
    aggregates node embeddings per hyperedge, and scores each hyperedge with a linear decoder.

    Attributes:
        encoder: GCN encoder module inherited from ``HlpModule``.
        decoder: SLP decoder module inherited from ``HlpModule``.
        loss_fn: Loss function inherited from ``HlpModule``.
        metrics_log_kwargs: Metric logging keyword arguments inherited from ``HlpModule``.
        train_metrics: Optional training metrics inherited from ``HlpModule``.
        val_metrics: Optional validation metrics inherited from ``HlpModule``.
        test_metrics: Optional test metrics inherited from ``HlpModule``.
        encoder_config: Configuration for the GCN encoder.
        aggregation: Method to aggregate node embeddings per hyperedge. Defaults to ``"mean"``.
        lr: Learning rate for the optimizer. Defaults to ``0.001``.
        weight_decay: L2 regularization. Defaults to ``0.0``.
    """

    def __init__(
        self,
        encoder_config: GCNEncoderConfig,
        aggregation: Literal["mean", "max", "min", "sum"] = "mean",
        loss_fn: nn.Module | None = None,
        lr: float = 0.001,
        weight_decay: float = 0.0,
        metrics: MetricCollection | None = None,
        metrics_log_kwargs: dict[str, Any] | None = None,
    ):
        """
        Initialize the GCN HLP module.

        Args:
            encoder_config: Configuration for the GCN encoder.
            aggregation: Method used to aggregate node embeddings per hyperedge.
                Defaults to ``"mean"``.
            loss_fn: Optional loss function. Defaults to ``BCEWithLogitsLoss``.
            lr: Learning rate for the optimizer. Defaults to ``0.001``.
            weight_decay: L2 regularization. Defaults to ``0.0``.
            metrics: Optional metric collection for evaluation. Defaults to ``None``.
            metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
                Useful for configuring distributed synchronization behavior
                of ``torchmetrics``. Defaults to ``None``.
        """
        encoder = GCN(
            in_channels=encoder_config["in_channels"],
            out_channels=encoder_config["out_channels"],
            hidden_channels=encoder_config.get("hidden_channels"),
            num_layers=encoder_config.get("num_layers", 2),
            drop_rate=encoder_config.get("drop_rate", 0.0),
            bias=encoder_config.get("bias", True),
            activation_fn=encoder_config.get("activation_fn"),
            activation_fn_kwargs=encoder_config.get("activation_fn_kwargs"),
            improved=encoder_config.get("improved", False),
            add_self_loops=encoder_config.get("add_self_loops", True),
            normalize=encoder_config.get("normalize", True),
            cached=encoder_config.get("cached", False),
        )
        decoder = SLP(in_channels=encoder_config["out_channels"], out_channels=1)

        super().__init__(
            encoder=encoder,
            decoder=decoder,
            loss_fn=loss_fn if loss_fn is not None else nn.BCEWithLogitsLoss(),
            metrics=metrics,
            metrics_log_kwargs=metrics_log_kwargs,
        )

        self.encoder_config: GCNEncoderConfig = encoder_config
        self.aggregation: Literal["mean", "max", "min", "sum"] = aggregation
        self.lr: float = lr
        self.weight_decay: float = weight_decay

    def forward(self, x: Tensor, hyperedge_index: Tensor) -> Tensor:
        """
        Reduce the hypergraph to a graph, encode nodes with GCN, aggregate per hyperedge, and score.

        Args:
            x: Node feature matrix of shape ``(num_nodes, in_channels)``.
            hyperedge_index: Hyperedge connectivity of shape ``(2, num_incidences)``.

        Returns:
            scores: Logit scores of shape ``(num_hyperedges,)``.

        Raises:
            ValueError: If the encoder is not defined for this module.
        """
        if self.encoder is None:
            raise ValueError("Encoder is not defined for this HLP module.")

        # Reduce hypergraph to graph and remove self-loops
        reduced_edge_index = HyperedgeIndex(hyperedge_index).reduce(
            strategy=self.encoder_config.get(
                "graph_reduction_strategy",
                GraphReductionStrategyEnum.CLIQUE_EXPANSION,
            ),
            num_nodes=self.encoder_config.get("num_nodes"),
        )
        edge_index = EdgeIndex(reduced_edge_index).remove_selfloops().item

        # Encode nodes with GCN
        node_embeddings: Tensor = self.encoder(x, edge_index)

        # Aggregate node embeddings per hyperedge
        hyperedge_embeddings = HyperedgeAggregator(hyperedge_index, node_embeddings).pool(
            self.aggregation
        )

        return self.decoder(hyperedge_embeddings).squeeze(-1)

    def training_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a training step.

        Args:
            batch: Training batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Training loss.
        """
        return self.__eval_step(batch, Stage.TRAIN)

    def validation_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a validation step.

        Args:
            batch: Validation batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Validation loss.
        """
        return self.__eval_step(batch, Stage.VAL)

    def test_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a test step.

        Args:
            batch: Test batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Test loss.
        """
        return self.__eval_step(batch, Stage.TEST)

    def predict_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Predict hyperedge scores for a batch.

        Args:
            batch: Prediction batch.
            batch_idx: Batch index, unused.

        Returns:
            scores: Predicted hyperedge scores.
        """
        return self.forward(batch.x, batch.hyperedge_index)

    def configure_optimizers(self) -> optim.Adam:
        """
        Configure the optimizer.

        Returns:
            optimizer: Adam optimizer.
        """
        return optim.Adam(self.parameters(), lr=self.lr, weight_decay=self.weight_decay)

    def __eval_step(self, batch: HData, stage: Stage) -> Tensor:
        """
        Run shared evaluation logic for a stage.

        Args:
            batch: Input batch.
            stage: Current evaluation stage.

        Returns:
            loss: Computed loss.
        """
        scores = self.forward(batch.x, batch.hyperedge_index)
        target_scores, target_labels = self._target_scores_and_labels(scores, batch)
        batch_size = target_labels.size(0)

        loss = self._compute_loss(target_scores, target_labels, batch_size, stage)
        self._compute_metrics(target_scores, target_labels, batch_size, stage)
        return loss

__init__(encoder_config, aggregation='mean', loss_fn=None, lr=0.001, weight_decay=0.0, metrics=None, metrics_log_kwargs=None)

Initialize the GCN HLP module.

Parameters:

Name Type Description Default
encoder_config GCNEncoderConfig

Configuration for the GCN encoder.

required
aggregation Literal['mean', 'max', 'min', 'sum']

Method used to aggregate node embeddings per hyperedge. Defaults to "mean".

'mean'
loss_fn Module | None

Optional loss function. Defaults to BCEWithLogitsLoss.

None
lr float

Learning rate for the optimizer. Defaults to 0.001.

0.001
weight_decay float

L2 regularization. Defaults to 0.0.

0.0
metrics MetricCollection | None

Optional metric collection for evaluation. Defaults to None.

None
metrics_log_kwargs dict[str, Any] | None

Additional keyword arguments passed to metric log calls. Useful for configuring distributed synchronization behavior of torchmetrics. Defaults to None.

None
Source code in hypertorch/hlp/gcn_hlp.py
def __init__(
    self,
    encoder_config: GCNEncoderConfig,
    aggregation: Literal["mean", "max", "min", "sum"] = "mean",
    loss_fn: nn.Module | None = None,
    lr: float = 0.001,
    weight_decay: float = 0.0,
    metrics: MetricCollection | None = None,
    metrics_log_kwargs: dict[str, Any] | None = None,
):
    """
    Initialize the GCN HLP module.

    Args:
        encoder_config: Configuration for the GCN encoder.
        aggregation: Method used to aggregate node embeddings per hyperedge.
            Defaults to ``"mean"``.
        loss_fn: Optional loss function. Defaults to ``BCEWithLogitsLoss``.
        lr: Learning rate for the optimizer. Defaults to ``0.001``.
        weight_decay: L2 regularization. Defaults to ``0.0``.
        metrics: Optional metric collection for evaluation. Defaults to ``None``.
        metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
            Useful for configuring distributed synchronization behavior
            of ``torchmetrics``. Defaults to ``None``.
    """
    encoder = GCN(
        in_channels=encoder_config["in_channels"],
        out_channels=encoder_config["out_channels"],
        hidden_channels=encoder_config.get("hidden_channels"),
        num_layers=encoder_config.get("num_layers", 2),
        drop_rate=encoder_config.get("drop_rate", 0.0),
        bias=encoder_config.get("bias", True),
        activation_fn=encoder_config.get("activation_fn"),
        activation_fn_kwargs=encoder_config.get("activation_fn_kwargs"),
        improved=encoder_config.get("improved", False),
        add_self_loops=encoder_config.get("add_self_loops", True),
        normalize=encoder_config.get("normalize", True),
        cached=encoder_config.get("cached", False),
    )
    decoder = SLP(in_channels=encoder_config["out_channels"], out_channels=1)

    super().__init__(
        encoder=encoder,
        decoder=decoder,
        loss_fn=loss_fn if loss_fn is not None else nn.BCEWithLogitsLoss(),
        metrics=metrics,
        metrics_log_kwargs=metrics_log_kwargs,
    )

    self.encoder_config: GCNEncoderConfig = encoder_config
    self.aggregation: Literal["mean", "max", "min", "sum"] = aggregation
    self.lr: float = lr
    self.weight_decay: float = weight_decay

forward(x, hyperedge_index)

Reduce the hypergraph to a graph, encode nodes with GCN, aggregate per hyperedge, and score.

Parameters:

Name Type Description Default
x Tensor

Node feature matrix of shape (num_nodes, in_channels).

required
hyperedge_index Tensor

Hyperedge connectivity of shape (2, num_incidences).

required

Returns:

Name Type Description
scores Tensor

Logit scores of shape (num_hyperedges,).

Raises:

Type Description
ValueError

If the encoder is not defined for this module.

Source code in hypertorch/hlp/gcn_hlp.py
def forward(self, x: Tensor, hyperedge_index: Tensor) -> Tensor:
    """
    Reduce the hypergraph to a graph, encode nodes with GCN, aggregate per hyperedge, and score.

    Args:
        x: Node feature matrix of shape ``(num_nodes, in_channels)``.
        hyperedge_index: Hyperedge connectivity of shape ``(2, num_incidences)``.

    Returns:
        scores: Logit scores of shape ``(num_hyperedges,)``.

    Raises:
        ValueError: If the encoder is not defined for this module.
    """
    if self.encoder is None:
        raise ValueError("Encoder is not defined for this HLP module.")

    # Reduce hypergraph to graph and remove self-loops
    reduced_edge_index = HyperedgeIndex(hyperedge_index).reduce(
        strategy=self.encoder_config.get(
            "graph_reduction_strategy",
            GraphReductionStrategyEnum.CLIQUE_EXPANSION,
        ),
        num_nodes=self.encoder_config.get("num_nodes"),
    )
    edge_index = EdgeIndex(reduced_edge_index).remove_selfloops().item

    # Encode nodes with GCN
    node_embeddings: Tensor = self.encoder(x, edge_index)

    # Aggregate node embeddings per hyperedge
    hyperedge_embeddings = HyperedgeAggregator(hyperedge_index, node_embeddings).pool(
        self.aggregation
    )

    return self.decoder(hyperedge_embeddings).squeeze(-1)

training_step(batch, batch_idx)

Run a training step.

Parameters:

Name Type Description Default
batch HData

Training batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Training loss.

Source code in hypertorch/hlp/gcn_hlp.py
def training_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a training step.

    Args:
        batch: Training batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Training loss.
    """
    return self.__eval_step(batch, Stage.TRAIN)

validation_step(batch, batch_idx)

Run a validation step.

Parameters:

Name Type Description Default
batch HData

Validation batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Validation loss.

Source code in hypertorch/hlp/gcn_hlp.py
def validation_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a validation step.

    Args:
        batch: Validation batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Validation loss.
    """
    return self.__eval_step(batch, Stage.VAL)

test_step(batch, batch_idx)

Run a test step.

Parameters:

Name Type Description Default
batch HData

Test batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Test loss.

Source code in hypertorch/hlp/gcn_hlp.py
def test_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a test step.

    Args:
        batch: Test batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Test loss.
    """
    return self.__eval_step(batch, Stage.TEST)

predict_step(batch, batch_idx)

Predict hyperedge scores for a batch.

Parameters:

Name Type Description Default
batch HData

Prediction batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
scores Tensor

Predicted hyperedge scores.

Source code in hypertorch/hlp/gcn_hlp.py
def predict_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Predict hyperedge scores for a batch.

    Args:
        batch: Prediction batch.
        batch_idx: Batch index, unused.

    Returns:
        scores: Predicted hyperedge scores.
    """
    return self.forward(batch.x, batch.hyperedge_index)

configure_optimizers()

Configure the optimizer.

Returns:

Name Type Description
optimizer Adam

Adam optimizer.

Source code in hypertorch/hlp/gcn_hlp.py
def configure_optimizers(self) -> optim.Adam:
    """
    Configure the optimizer.

    Returns:
        optimizer: Adam optimizer.
    """
    return optim.Adam(self.parameters(), lr=self.lr, weight_decay=self.weight_decay)

__eval_step(batch, stage)

Run shared evaluation logic for a stage.

Parameters:

Name Type Description Default
batch HData

Input batch.

required
stage Stage

Current evaluation stage.

required

Returns:

Name Type Description
loss Tensor

Computed loss.

Source code in hypertorch/hlp/gcn_hlp.py
def __eval_step(self, batch: HData, stage: Stage) -> Tensor:
    """
    Run shared evaluation logic for a stage.

    Args:
        batch: Input batch.
        stage: Current evaluation stage.

    Returns:
        loss: Computed loss.
    """
    scores = self.forward(batch.x, batch.hyperedge_index)
    target_scores, target_labels = self._target_scores_and_labels(scores, batch)
    batch_size = target_labels.size(0)

    loss = self._compute_loss(target_scores, target_labels, batch_size, stage)
    self._compute_metrics(target_scores, target_labels, batch_size, stage)
    return loss

HGNNHlpModule

Bases: HlpModule

A LightningModule for HGNN-based Hyperedge Link Prediction.

Uses HGNN as an encoder to produce structure-aware node embeddings via spectral hypergraph convolution, aggregates them per hyperedge, and scores each hyperedge with a linear decoder.

Attributes:

Name Type Description
encoder Module | None

HGNN encoder module inherited from HlpModule.

decoder Module

SLP decoder module inherited from HlpModule.

loss_fn Module

Loss function inherited from HlpModule.

metrics_log_kwargs dict[str, Any]

Metric logging keyword arguments inherited from HlpModule.

train_metrics MetricCollection | None

Optional training metrics inherited from HlpModule.

val_metrics MetricCollection | None

Optional validation metrics inherited from HlpModule.

test_metrics MetricCollection | None

Optional test metrics inherited from HlpModule.

aggregation Literal['mean', 'max', 'min', 'sum']

Method to aggregate node embeddings per hyperedge. Defaults to "mean".

lr float

Learning rate for the optimizer. Defaults to 0.001.

weight_decay float

L2 regularization. Defaults to 5e-4.

Source code in hypertorch/hlp/hgnn_hlp.py
class HGNNHlpModule(HlpModule):
    """
    A LightningModule for HGNN-based Hyperedge Link Prediction.

    Uses HGNN as an encoder to produce structure-aware node embeddings via
    spectral hypergraph convolution, aggregates them per hyperedge,
    and scores each hyperedge with a linear decoder.

    Attributes:
        encoder: HGNN encoder module inherited from ``HlpModule``.
        decoder: SLP decoder module inherited from ``HlpModule``.
        loss_fn: Loss function inherited from ``HlpModule``.
        metrics_log_kwargs: Metric logging keyword arguments inherited from ``HlpModule``.
        train_metrics: Optional training metrics inherited from ``HlpModule``.
        val_metrics: Optional validation metrics inherited from ``HlpModule``.
        test_metrics: Optional test metrics inherited from ``HlpModule``.
        aggregation: Method to aggregate node embeddings per hyperedge. Defaults to ``"mean"``.
        lr: Learning rate for the optimizer. Defaults to ``0.001``.
        weight_decay: L2 regularization. Defaults to ``5e-4``.
    """

    def __init__(
        self,
        encoder_config: HGNNEncoderConfig,
        aggregation: Literal["mean", "max", "min", "sum"] = "mean",
        loss_fn: nn.Module | None = None,
        lr: float = 0.001,
        weight_decay: float = 5e-4,
        metrics: MetricCollection | None = None,
        metrics_log_kwargs: dict[str, Any] | None = None,
    ):
        """
        Initialize the HGNN HLP module.

        Args:
            encoder_config: Configuration for the HGNN encoder.
            aggregation: Method used to aggregate node embeddings per hyperedge.
                Defaults to ``"mean"``.
            loss_fn: Optional loss function. Defaults to ``BCEWithLogitsLoss``.
            lr: Learning rate for the optimizer. Defaults to ``0.001``.
            weight_decay: L2 regularization. Defaults to ``5e-4``.
            metrics: Optional metric collection for evaluation. Defaults to ``None``.
            metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
                Useful for configuring distributed synchronization behavior
                of ``torchmetrics``. Defaults to ``None``.
        """
        encoder = HGNN(
            in_channels=encoder_config["in_channels"],
            hidden_channels=encoder_config["hidden_channels"],
            num_classes=encoder_config["out_channels"],
            bias=encoder_config.get("bias", True),
            use_batch_normalization=encoder_config.get("use_batch_normalization", False),
            drop_rate=encoder_config.get("drop_rate", 0.5),
        )
        decoder = SLP(in_channels=encoder_config["out_channels"], out_channels=1)

        super().__init__(
            encoder=encoder,
            decoder=decoder,
            loss_fn=loss_fn if loss_fn is not None else nn.BCEWithLogitsLoss(),
            metrics=metrics,
            metrics_log_kwargs=metrics_log_kwargs,
        )

        self.aggregation: Literal["mean", "max", "min", "sum"] = aggregation
        self.lr: float = lr
        self.weight_decay: float = weight_decay

    def forward(self, x: Tensor, hyperedge_index: Tensor) -> Tensor:
        """
        Run the full HGNN-based hyperedge link prediction pipeline.

        The pipeline has three stages:
            1. Encode: HGNN applies two rounds of ``D_n^{-1/2} H D_e^{-1} H^T D_n^{-1/2}``
            smoothing to propagate information through the hypergraph topology (nodes ->
            hyperedges -> nodes). The output is a structure-aware node embedding matrix of
            shape ``(num_nodes, out_channels)``.
            2. Aggregate: For each hyperedge being scored, pool the embeddings of its member
            nodes using the configured strategy (mean/max/min/sum). This produces a hyperedge
            embedding that summarizes the collective representation of the hyperedge's nodes.
            Shape: ``(num_hyperedges, out_channels)``.
            3. Decode: A single linear layer (SLP) projects each hyperedge embedding to a
            scalar score representing the likelihood that the hyperedge is a real (positive)
            hyperedge. Shape: ``(num_hyperedges,)``.

        Examples:
            Given 5 nodes with 8 features and 2 hyperedges:

                >>> x.shape  # (5, 8) - all nodes in the hypergraph
                >>> hyperedge_index = [[0, 1, 2, 3, 4],  # node IDs
                ...                    [0, 0, 0, 1, 1]]  # hyperedge IDs

            The forward pass:

                >>> HGNN encodes all 5 nodes using the hypergraph Laplacian.
                ...   ``node_embeddings.shape = (5, out_channels)``
                >>> Aggregate per hyperedge:
                ...   - hyperedge 0: pool(emb[0], emb[1], emb[2])
                ...   - hyperedge 1: pool(emb[3], emb[4])
                ...   ``hyperedge_embeddings.shape = (2, out_channels)``
                >>> Decode: one scalar per hyperedge -> ``scores.shape = (2,)``

        Args:
            x: Node feature matrix of shape ``(num_nodes, in_channels)``.
                Must contain **all** nodes referenced in ``hyperedge_index``.
            hyperedge_index: Hyperedge connectivity of shape ``(2, num_incidences)``,
                with row 0 containing global node IDs and row 1 hyperedge IDs.

        Returns:
            scores: Logit scores of shape ``(num_hyperedges,)``. Pass through sigmoid to get
            probabilities, or use directly with ``BCEWithLogitsLoss``.

        Raises:
            ValueError: If the encoder is not defined for this module.
        """
        if self.encoder is None:
            raise ValueError("Encoder is not defined for this HLP module.")

        # Encode: two-hop HGNN smoothing (nodes -> hyperedges -> nodes), no graph reduction
        # Example: x: (num_nodes, in_channels)
        #          -> node_embeddings: (num_nodes, out_channels)
        node_embeddings: Tensor = self.encoder(x, hyperedge_index)

        # Aggregate: pool node embeddings per hyperedge
        # shape: (num_hyperedges, out_channels)
        hyperedge_embeddings = HyperedgeAggregator(hyperedge_index, node_embeddings).pool(
            self.aggregation
        )

        # Decode: linear projection to scalar score per hyperedge
        # shape: (num_hyperedges, 1) -> squeeze -> (num_hyperedges,)
        scores: Tensor = self.decoder(hyperedge_embeddings).squeeze(-1)
        return scores

    def training_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a training step.

        Args:
            batch: Training batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Training loss.
        """
        return self.__eval_step(batch, Stage.TRAIN)

    def validation_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a validation step.

        Args:
            batch: Validation batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Validation loss.
        """
        return self.__eval_step(batch, Stage.VAL)

    def test_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a test step.

        Args:
            batch: Test batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Test loss.
        """
        return self.__eval_step(batch, Stage.TEST)

    def predict_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Predict hyperedge scores for a batch.

        Args:
            batch: Prediction batch.
            batch_idx: Batch index, unused.

        Returns:
            scores: Predicted hyperedge scores.
        """
        return self.forward(batch.x, batch.hyperedge_index)

    def configure_optimizers(self) -> optim.Adam:
        """
        Configure the optimizer.

        Returns:
            optimizer: Adam optimizer.
        """
        return optim.Adam(self.parameters(), lr=self.lr, weight_decay=self.weight_decay)

    def __eval_step(self, batch: HData, stage: Stage) -> Tensor:
        """
        Run shared evaluation logic for a stage.

        Args:
            batch: Input batch.
            stage: Current evaluation stage.

        Returns:
            loss: Computed loss.
        """
        scores = self.forward(batch.x, batch.hyperedge_index)
        target_scores, target_labels = self._target_scores_and_labels(scores, batch)
        batch_size = target_labels.size(0)

        loss = self._compute_loss(target_scores, target_labels, batch_size, stage)
        self._compute_metrics(target_scores, target_labels, batch_size, stage)
        return loss

__init__(encoder_config, aggregation='mean', loss_fn=None, lr=0.001, weight_decay=0.0005, metrics=None, metrics_log_kwargs=None)

Initialize the HGNN HLP module.

Parameters:

Name Type Description Default
encoder_config HGNNEncoderConfig

Configuration for the HGNN encoder.

required
aggregation Literal['mean', 'max', 'min', 'sum']

Method used to aggregate node embeddings per hyperedge. Defaults to "mean".

'mean'
loss_fn Module | None

Optional loss function. Defaults to BCEWithLogitsLoss.

None
lr float

Learning rate for the optimizer. Defaults to 0.001.

0.001
weight_decay float

L2 regularization. Defaults to 5e-4.

0.0005
metrics MetricCollection | None

Optional metric collection for evaluation. Defaults to None.

None
metrics_log_kwargs dict[str, Any] | None

Additional keyword arguments passed to metric log calls. Useful for configuring distributed synchronization behavior of torchmetrics. Defaults to None.

None
Source code in hypertorch/hlp/hgnn_hlp.py
def __init__(
    self,
    encoder_config: HGNNEncoderConfig,
    aggregation: Literal["mean", "max", "min", "sum"] = "mean",
    loss_fn: nn.Module | None = None,
    lr: float = 0.001,
    weight_decay: float = 5e-4,
    metrics: MetricCollection | None = None,
    metrics_log_kwargs: dict[str, Any] | None = None,
):
    """
    Initialize the HGNN HLP module.

    Args:
        encoder_config: Configuration for the HGNN encoder.
        aggregation: Method used to aggregate node embeddings per hyperedge.
            Defaults to ``"mean"``.
        loss_fn: Optional loss function. Defaults to ``BCEWithLogitsLoss``.
        lr: Learning rate for the optimizer. Defaults to ``0.001``.
        weight_decay: L2 regularization. Defaults to ``5e-4``.
        metrics: Optional metric collection for evaluation. Defaults to ``None``.
        metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
            Useful for configuring distributed synchronization behavior
            of ``torchmetrics``. Defaults to ``None``.
    """
    encoder = HGNN(
        in_channels=encoder_config["in_channels"],
        hidden_channels=encoder_config["hidden_channels"],
        num_classes=encoder_config["out_channels"],
        bias=encoder_config.get("bias", True),
        use_batch_normalization=encoder_config.get("use_batch_normalization", False),
        drop_rate=encoder_config.get("drop_rate", 0.5),
    )
    decoder = SLP(in_channels=encoder_config["out_channels"], out_channels=1)

    super().__init__(
        encoder=encoder,
        decoder=decoder,
        loss_fn=loss_fn if loss_fn is not None else nn.BCEWithLogitsLoss(),
        metrics=metrics,
        metrics_log_kwargs=metrics_log_kwargs,
    )

    self.aggregation: Literal["mean", "max", "min", "sum"] = aggregation
    self.lr: float = lr
    self.weight_decay: float = weight_decay

forward(x, hyperedge_index)

Run the full HGNN-based hyperedge link prediction pipeline.

The pipeline has three stages
  1. Encode: HGNN applies two rounds of D_n^{-1/2} H D_e^{-1} H^T D_n^{-1/2} smoothing to propagate information through the hypergraph topology (nodes -> hyperedges -> nodes). The output is a structure-aware node embedding matrix of shape (num_nodes, out_channels).
  2. Aggregate: For each hyperedge being scored, pool the embeddings of its member nodes using the configured strategy (mean/max/min/sum). This produces a hyperedge embedding that summarizes the collective representation of the hyperedge's nodes. Shape: (num_hyperedges, out_channels).
  3. Decode: A single linear layer (SLP) projects each hyperedge embedding to a scalar score representing the likelihood that the hyperedge is a real (positive) hyperedge. Shape: (num_hyperedges,).

Examples:

Given 5 nodes with 8 features and 2 hyperedges:

>>> x.shape  # (5, 8) - all nodes in the hypergraph
>>> hyperedge_index = [[0, 1, 2, 3, 4],  # node IDs
...                    [0, 0, 0, 1, 1]]  # hyperedge IDs

The forward pass:

>>> HGNN encodes all 5 nodes using the hypergraph Laplacian.
...   ``node_embeddings.shape = (5, out_channels)``
>>> Aggregate per hyperedge:
...   - hyperedge 0: pool(emb[0], emb[1], emb[2])
...   - hyperedge 1: pool(emb[3], emb[4])
...   ``hyperedge_embeddings.shape = (2, out_channels)``
>>> Decode: one scalar per hyperedge -> ``scores.shape = (2,)``

Parameters:

Name Type Description Default
x Tensor

Node feature matrix of shape (num_nodes, in_channels). Must contain all nodes referenced in hyperedge_index.

required
hyperedge_index Tensor

Hyperedge connectivity of shape (2, num_incidences), with row 0 containing global node IDs and row 1 hyperedge IDs.

required

Returns:

Name Type Description
scores Tensor

Logit scores of shape (num_hyperedges,). Pass through sigmoid to get

Tensor

probabilities, or use directly with BCEWithLogitsLoss.

Raises:

Type Description
ValueError

If the encoder is not defined for this module.

Source code in hypertorch/hlp/hgnn_hlp.py
def forward(self, x: Tensor, hyperedge_index: Tensor) -> Tensor:
    """
    Run the full HGNN-based hyperedge link prediction pipeline.

    The pipeline has three stages:
        1. Encode: HGNN applies two rounds of ``D_n^{-1/2} H D_e^{-1} H^T D_n^{-1/2}``
        smoothing to propagate information through the hypergraph topology (nodes ->
        hyperedges -> nodes). The output is a structure-aware node embedding matrix of
        shape ``(num_nodes, out_channels)``.
        2. Aggregate: For each hyperedge being scored, pool the embeddings of its member
        nodes using the configured strategy (mean/max/min/sum). This produces a hyperedge
        embedding that summarizes the collective representation of the hyperedge's nodes.
        Shape: ``(num_hyperedges, out_channels)``.
        3. Decode: A single linear layer (SLP) projects each hyperedge embedding to a
        scalar score representing the likelihood that the hyperedge is a real (positive)
        hyperedge. Shape: ``(num_hyperedges,)``.

    Examples:
        Given 5 nodes with 8 features and 2 hyperedges:

            >>> x.shape  # (5, 8) - all nodes in the hypergraph
            >>> hyperedge_index = [[0, 1, 2, 3, 4],  # node IDs
            ...                    [0, 0, 0, 1, 1]]  # hyperedge IDs

        The forward pass:

            >>> HGNN encodes all 5 nodes using the hypergraph Laplacian.
            ...   ``node_embeddings.shape = (5, out_channels)``
            >>> Aggregate per hyperedge:
            ...   - hyperedge 0: pool(emb[0], emb[1], emb[2])
            ...   - hyperedge 1: pool(emb[3], emb[4])
            ...   ``hyperedge_embeddings.shape = (2, out_channels)``
            >>> Decode: one scalar per hyperedge -> ``scores.shape = (2,)``

    Args:
        x: Node feature matrix of shape ``(num_nodes, in_channels)``.
            Must contain **all** nodes referenced in ``hyperedge_index``.
        hyperedge_index: Hyperedge connectivity of shape ``(2, num_incidences)``,
            with row 0 containing global node IDs and row 1 hyperedge IDs.

    Returns:
        scores: Logit scores of shape ``(num_hyperedges,)``. Pass through sigmoid to get
        probabilities, or use directly with ``BCEWithLogitsLoss``.

    Raises:
        ValueError: If the encoder is not defined for this module.
    """
    if self.encoder is None:
        raise ValueError("Encoder is not defined for this HLP module.")

    # Encode: two-hop HGNN smoothing (nodes -> hyperedges -> nodes), no graph reduction
    # Example: x: (num_nodes, in_channels)
    #          -> node_embeddings: (num_nodes, out_channels)
    node_embeddings: Tensor = self.encoder(x, hyperedge_index)

    # Aggregate: pool node embeddings per hyperedge
    # shape: (num_hyperedges, out_channels)
    hyperedge_embeddings = HyperedgeAggregator(hyperedge_index, node_embeddings).pool(
        self.aggregation
    )

    # Decode: linear projection to scalar score per hyperedge
    # shape: (num_hyperedges, 1) -> squeeze -> (num_hyperedges,)
    scores: Tensor = self.decoder(hyperedge_embeddings).squeeze(-1)
    return scores

training_step(batch, batch_idx)

Run a training step.

Parameters:

Name Type Description Default
batch HData

Training batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Training loss.

Source code in hypertorch/hlp/hgnn_hlp.py
def training_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a training step.

    Args:
        batch: Training batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Training loss.
    """
    return self.__eval_step(batch, Stage.TRAIN)

validation_step(batch, batch_idx)

Run a validation step.

Parameters:

Name Type Description Default
batch HData

Validation batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Validation loss.

Source code in hypertorch/hlp/hgnn_hlp.py
def validation_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a validation step.

    Args:
        batch: Validation batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Validation loss.
    """
    return self.__eval_step(batch, Stage.VAL)

test_step(batch, batch_idx)

Run a test step.

Parameters:

Name Type Description Default
batch HData

Test batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Test loss.

Source code in hypertorch/hlp/hgnn_hlp.py
def test_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a test step.

    Args:
        batch: Test batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Test loss.
    """
    return self.__eval_step(batch, Stage.TEST)

predict_step(batch, batch_idx)

Predict hyperedge scores for a batch.

Parameters:

Name Type Description Default
batch HData

Prediction batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
scores Tensor

Predicted hyperedge scores.

Source code in hypertorch/hlp/hgnn_hlp.py
def predict_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Predict hyperedge scores for a batch.

    Args:
        batch: Prediction batch.
        batch_idx: Batch index, unused.

    Returns:
        scores: Predicted hyperedge scores.
    """
    return self.forward(batch.x, batch.hyperedge_index)

configure_optimizers()

Configure the optimizer.

Returns:

Name Type Description
optimizer Adam

Adam optimizer.

Source code in hypertorch/hlp/hgnn_hlp.py
def configure_optimizers(self) -> optim.Adam:
    """
    Configure the optimizer.

    Returns:
        optimizer: Adam optimizer.
    """
    return optim.Adam(self.parameters(), lr=self.lr, weight_decay=self.weight_decay)

__eval_step(batch, stage)

Run shared evaluation logic for a stage.

Parameters:

Name Type Description Default
batch HData

Input batch.

required
stage Stage

Current evaluation stage.

required

Returns:

Name Type Description
loss Tensor

Computed loss.

Source code in hypertorch/hlp/hgnn_hlp.py
def __eval_step(self, batch: HData, stage: Stage) -> Tensor:
    """
    Run shared evaluation logic for a stage.

    Args:
        batch: Input batch.
        stage: Current evaluation stage.

    Returns:
        loss: Computed loss.
    """
    scores = self.forward(batch.x, batch.hyperedge_index)
    target_scores, target_labels = self._target_scores_and_labels(scores, batch)
    batch_size = target_labels.size(0)

    loss = self._compute_loss(target_scores, target_labels, batch_size, stage)
    self._compute_metrics(target_scores, target_labels, batch_size, stage)
    return loss

HGNNEncoderConfig

Bases: TypedDict

Configuration for the HGNN encoder in HGNNHlpModule.

Attributes:

Name Type Description
in_channels int

Number of input features per node.

hidden_channels int

Number of hidden units in the intermediate HGNN layer.

out_channels int

Number of output features (embedding size) per node.

bias NotRequired[bool]

Whether to include bias terms. Defaults to True.

use_batch_normalization NotRequired[bool]

Whether to use batch normalization. Defaults to False.

drop_rate NotRequired[float]

Dropout rate. Defaults to 0.5.

Source code in hypertorch/hlp/hgnn_hlp.py
class HGNNEncoderConfig(TypedDict):
    """
    Configuration for the HGNN encoder in HGNNHlpModule.

    Attributes:
        in_channels: Number of input features per node.
        hidden_channels: Number of hidden units in the intermediate HGNN layer.
        out_channels: Number of output features (embedding size) per node.
        bias: Whether to include bias terms. Defaults to ``True``.
        use_batch_normalization: Whether to use batch normalization. Defaults to ``False``.
        drop_rate: Dropout rate. Defaults to ``0.5``.
    """

    in_channels: int
    hidden_channels: int
    out_channels: int
    bias: NotRequired[bool]
    use_batch_normalization: NotRequired[bool]
    drop_rate: NotRequired[float]

HNHNEncoderConfig

Bases: TypedDict

Configuration for the HNHN encoder in HNHNHlpModule.

Attributes:

Name Type Description
in_channels int

Number of input features per node.

hidden_channels int

Number of hidden units in the intermediate HNHN layer.

out_channels int

Number of output features (embedding size) per node.

bias NotRequired[bool]

Whether to include bias terms. Defaults to True.

use_batch_normalization NotRequired[bool]

Whether to use batch normalization. Defaults to False.

drop_rate NotRequired[float]

Dropout rate. Defaults to 0.5.

Source code in hypertorch/hlp/hnhn_hlp.py
class HNHNEncoderConfig(TypedDict):
    """
    Configuration for the HNHN encoder in HNHNHlpModule.

    Attributes:
        in_channels: Number of input features per node.
        hidden_channels: Number of hidden units in the intermediate HNHN layer.
        out_channels: Number of output features (embedding size) per node.
        bias: Whether to include bias terms. Defaults to ``True``.
        use_batch_normalization: Whether to use batch normalization. Defaults to ``False``.
        drop_rate: Dropout rate. Defaults to ``0.5``.
    """

    in_channels: int
    hidden_channels: int
    out_channels: int
    bias: NotRequired[bool]
    use_batch_normalization: NotRequired[bool]
    drop_rate: NotRequired[float]

HNHNHlpModule

Bases: HlpModule

A LightningModule for HNHN-based Hyperedge Link Prediction.

Uses HNHN as an encoder to produce node embeddings through explicit hyperedge neurons, aggregates them per hyperedge, and scores each hyperedge with a linear decoder.

Attributes:

Name Type Description
encoder Module | None

HNHN encoder module inherited from HlpModule.

decoder Module

SLP decoder module inherited from HlpModule.

loss_fn Module

Loss function inherited from HlpModule.

metrics_log_kwargs dict[str, Any]

Metric logging keyword arguments inherited from HlpModule.

train_metrics MetricCollection | None

Optional training metrics inherited from HlpModule.

val_metrics MetricCollection | None

Optional validation metrics inherited from HlpModule.

test_metrics MetricCollection | None

Optional test metrics inherited from HlpModule.

aggregation Literal['mean', 'max', 'min', 'sum']

Method to aggregate node embeddings per hyperedge. Defaults to "mean".

lr float

Learning rate for the optimizer. Defaults to 0.01.

weight_decay float

L2 regularization. Defaults to 5e-4.

scheduler_step_size int

Step size for learning rate scheduler. Defaults to 100.

scheduler_gamma float

Multiplicative factor for learning rate decay. Defaults to 0.51.

Source code in hypertorch/hlp/hnhn_hlp.py
class HNHNHlpModule(HlpModule):
    """
    A LightningModule for HNHN-based Hyperedge Link Prediction.

    Uses HNHN as an encoder to produce node embeddings through explicit
    hyperedge neurons, aggregates them per hyperedge, and scores each
    hyperedge with a linear decoder.

    Attributes:
        encoder: HNHN encoder module inherited from ``HlpModule``.
        decoder: SLP decoder module inherited from ``HlpModule``.
        loss_fn: Loss function inherited from ``HlpModule``.
        metrics_log_kwargs: Metric logging keyword arguments inherited from ``HlpModule``.
        train_metrics: Optional training metrics inherited from ``HlpModule``.
        val_metrics: Optional validation metrics inherited from ``HlpModule``.
        test_metrics: Optional test metrics inherited from ``HlpModule``.
        aggregation: Method to aggregate node embeddings per hyperedge. Defaults to ``"mean"``.
        lr: Learning rate for the optimizer. Defaults to ``0.01``.
        weight_decay: L2 regularization. Defaults to ``5e-4``.
        scheduler_step_size: Step size for learning rate scheduler. Defaults to ``100``.
        scheduler_gamma: Multiplicative factor for learning rate decay. Defaults to ``0.51``.
    """

    def __init__(
        self,
        encoder_config: HNHNEncoderConfig,
        aggregation: Literal["mean", "max", "min", "sum"] = "mean",
        loss_fn: nn.Module | None = None,
        lr: float = 0.01,
        weight_decay: float = 5e-4,
        scheduler_step_size: int = 100,
        scheduler_gamma: float = 0.51,
        metrics: MetricCollection | None = None,
        metrics_log_kwargs: dict[str, Any] | None = None,
    ):
        """
        Initialize the HNHN HLP module.

        Args:
            encoder_config: Configuration for the HNHN encoder.
            aggregation: Method used to aggregate node embeddings per hyperedge.
                Defaults to ``"mean"``.
            loss_fn: Optional loss function. Defaults to ``BCEWithLogitsLoss``.
            lr: Learning rate for the optimizer. Defaults to ``0.01``.
            weight_decay: L2 regularization. Defaults to ``5e-4``.
            scheduler_step_size: Step size for the learning rate scheduler. Defaults to ``100``.
            scheduler_gamma: Multiplicative factor for learning rate decay. Defaults to ``0.51``.
            metrics: Optional metric collection for evaluation. Defaults to ``None``.
            metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
                Useful for configuring distributed synchronization behavior
                of ``torchmetrics``. Defaults to ``None``.
        """
        encoder = HNHN(
            in_channels=encoder_config["in_channels"],
            hidden_channels=encoder_config["hidden_channels"],
            num_classes=encoder_config["out_channels"],
            bias=encoder_config.get("bias", True),
            use_batch_normalization=encoder_config.get("use_batch_normalization", False),
            drop_rate=encoder_config.get("drop_rate", 0.5),
        )
        decoder = SLP(in_channels=encoder_config["out_channels"], out_channels=1)

        super().__init__(
            encoder=encoder,
            decoder=decoder,
            loss_fn=loss_fn if loss_fn is not None else nn.BCEWithLogitsLoss(),
            metrics=metrics,
            metrics_log_kwargs=metrics_log_kwargs,
        )

        self.aggregation: Literal["mean", "max", "min", "sum"] = aggregation
        self.lr: float = lr
        self.weight_decay: float = weight_decay
        self.scheduler_step_size: int = scheduler_step_size
        self.scheduler_gamma: float = scheduler_gamma

    def forward(self, x: Tensor, hyperedge_index: Tensor) -> Tensor:
        """
        Run the full HNHN-based hyperedge link prediction pipeline.

        Args:
            x: Node feature matrix of shape ``(num_nodes, in_channels)``.
            hyperedge_index: Hyperedge connectivity of shape ``(2, num_incidences)``.

        Returns:
            scores: Logit scores of shape ``(num_hyperedges,)``.

        Raises:
            ValueError: If the encoder is not defined for this module.
        """
        if self.encoder is None:
            raise ValueError("Encoder is not defined for this HLP module.")

        node_embeddings: Tensor = self.encoder(x, hyperedge_index)
        hyperedge_embeddings = HyperedgeAggregator(hyperedge_index, node_embeddings).pool(
            self.aggregation
        )
        scores: Tensor = self.decoder(hyperedge_embeddings).squeeze(-1)
        return scores

    def training_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a training step.

        Args:
            batch: Training batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Training loss.
        """
        return self.__eval_step(batch, Stage.TRAIN)

    def validation_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a validation step.

        Args:
            batch: Validation batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Validation loss.
        """
        return self.__eval_step(batch, Stage.VAL)

    def test_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a test step.

        Args:
            batch: Test batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Test loss.
        """
        return self.__eval_step(batch, Stage.TEST)

    def predict_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Predict hyperedge scores for a batch.

        Args:
            batch: Prediction batch.
            batch_idx: Batch index, unused.

        Returns:
            scores: Predicted hyperedge scores.
        """
        return self.forward(batch.x, batch.hyperedge_index)

    def configure_optimizers(self) -> tuple[list[optim.Adam], list[optim.lr_scheduler.StepLR]]:
        """
        Configure the optimizer and scheduler.

        Returns:
            optimizers_and_schedulers: Optimizer and scheduler lists.
        """
        optimizer = optim.Adam(self.parameters(), lr=self.lr, weight_decay=self.weight_decay)
        scheduler = optim.lr_scheduler.StepLR(
            optimizer, step_size=self.scheduler_step_size, gamma=self.scheduler_gamma
        )
        return [optimizer], [scheduler]

    def __eval_step(self, batch: HData, stage: Stage) -> Tensor:
        """
        Run shared evaluation logic for a stage.

        Args:
            batch: Input batch.
            stage: Current evaluation stage.

        Returns:
            loss: Computed loss.
        """
        scores = self.forward(batch.x, batch.hyperedge_index)
        target_scores, target_labels = self._target_scores_and_labels(scores, batch)
        batch_size = target_labels.size(0)

        loss = self._compute_loss(target_scores, target_labels, batch_size, stage)
        self._compute_metrics(target_scores, target_labels, batch_size, stage)
        return loss

__init__(encoder_config, aggregation='mean', loss_fn=None, lr=0.01, weight_decay=0.0005, scheduler_step_size=100, scheduler_gamma=0.51, metrics=None, metrics_log_kwargs=None)

Initialize the HNHN HLP module.

Parameters:

Name Type Description Default
encoder_config HNHNEncoderConfig

Configuration for the HNHN encoder.

required
aggregation Literal['mean', 'max', 'min', 'sum']

Method used to aggregate node embeddings per hyperedge. Defaults to "mean".

'mean'
loss_fn Module | None

Optional loss function. Defaults to BCEWithLogitsLoss.

None
lr float

Learning rate for the optimizer. Defaults to 0.01.

0.01
weight_decay float

L2 regularization. Defaults to 5e-4.

0.0005
scheduler_step_size int

Step size for the learning rate scheduler. Defaults to 100.

100
scheduler_gamma float

Multiplicative factor for learning rate decay. Defaults to 0.51.

0.51
metrics MetricCollection | None

Optional metric collection for evaluation. Defaults to None.

None
metrics_log_kwargs dict[str, Any] | None

Additional keyword arguments passed to metric log calls. Useful for configuring distributed synchronization behavior of torchmetrics. Defaults to None.

None
Source code in hypertorch/hlp/hnhn_hlp.py
def __init__(
    self,
    encoder_config: HNHNEncoderConfig,
    aggregation: Literal["mean", "max", "min", "sum"] = "mean",
    loss_fn: nn.Module | None = None,
    lr: float = 0.01,
    weight_decay: float = 5e-4,
    scheduler_step_size: int = 100,
    scheduler_gamma: float = 0.51,
    metrics: MetricCollection | None = None,
    metrics_log_kwargs: dict[str, Any] | None = None,
):
    """
    Initialize the HNHN HLP module.

    Args:
        encoder_config: Configuration for the HNHN encoder.
        aggregation: Method used to aggregate node embeddings per hyperedge.
            Defaults to ``"mean"``.
        loss_fn: Optional loss function. Defaults to ``BCEWithLogitsLoss``.
        lr: Learning rate for the optimizer. Defaults to ``0.01``.
        weight_decay: L2 regularization. Defaults to ``5e-4``.
        scheduler_step_size: Step size for the learning rate scheduler. Defaults to ``100``.
        scheduler_gamma: Multiplicative factor for learning rate decay. Defaults to ``0.51``.
        metrics: Optional metric collection for evaluation. Defaults to ``None``.
        metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
            Useful for configuring distributed synchronization behavior
            of ``torchmetrics``. Defaults to ``None``.
    """
    encoder = HNHN(
        in_channels=encoder_config["in_channels"],
        hidden_channels=encoder_config["hidden_channels"],
        num_classes=encoder_config["out_channels"],
        bias=encoder_config.get("bias", True),
        use_batch_normalization=encoder_config.get("use_batch_normalization", False),
        drop_rate=encoder_config.get("drop_rate", 0.5),
    )
    decoder = SLP(in_channels=encoder_config["out_channels"], out_channels=1)

    super().__init__(
        encoder=encoder,
        decoder=decoder,
        loss_fn=loss_fn if loss_fn is not None else nn.BCEWithLogitsLoss(),
        metrics=metrics,
        metrics_log_kwargs=metrics_log_kwargs,
    )

    self.aggregation: Literal["mean", "max", "min", "sum"] = aggregation
    self.lr: float = lr
    self.weight_decay: float = weight_decay
    self.scheduler_step_size: int = scheduler_step_size
    self.scheduler_gamma: float = scheduler_gamma

forward(x, hyperedge_index)

Run the full HNHN-based hyperedge link prediction pipeline.

Parameters:

Name Type Description Default
x Tensor

Node feature matrix of shape (num_nodes, in_channels).

required
hyperedge_index Tensor

Hyperedge connectivity of shape (2, num_incidences).

required

Returns:

Name Type Description
scores Tensor

Logit scores of shape (num_hyperedges,).

Raises:

Type Description
ValueError

If the encoder is not defined for this module.

Source code in hypertorch/hlp/hnhn_hlp.py
def forward(self, x: Tensor, hyperedge_index: Tensor) -> Tensor:
    """
    Run the full HNHN-based hyperedge link prediction pipeline.

    Args:
        x: Node feature matrix of shape ``(num_nodes, in_channels)``.
        hyperedge_index: Hyperedge connectivity of shape ``(2, num_incidences)``.

    Returns:
        scores: Logit scores of shape ``(num_hyperedges,)``.

    Raises:
        ValueError: If the encoder is not defined for this module.
    """
    if self.encoder is None:
        raise ValueError("Encoder is not defined for this HLP module.")

    node_embeddings: Tensor = self.encoder(x, hyperedge_index)
    hyperedge_embeddings = HyperedgeAggregator(hyperedge_index, node_embeddings).pool(
        self.aggregation
    )
    scores: Tensor = self.decoder(hyperedge_embeddings).squeeze(-1)
    return scores

training_step(batch, batch_idx)

Run a training step.

Parameters:

Name Type Description Default
batch HData

Training batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Training loss.

Source code in hypertorch/hlp/hnhn_hlp.py
def training_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a training step.

    Args:
        batch: Training batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Training loss.
    """
    return self.__eval_step(batch, Stage.TRAIN)

validation_step(batch, batch_idx)

Run a validation step.

Parameters:

Name Type Description Default
batch HData

Validation batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Validation loss.

Source code in hypertorch/hlp/hnhn_hlp.py
def validation_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a validation step.

    Args:
        batch: Validation batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Validation loss.
    """
    return self.__eval_step(batch, Stage.VAL)

test_step(batch, batch_idx)

Run a test step.

Parameters:

Name Type Description Default
batch HData

Test batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Test loss.

Source code in hypertorch/hlp/hnhn_hlp.py
def test_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a test step.

    Args:
        batch: Test batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Test loss.
    """
    return self.__eval_step(batch, Stage.TEST)

predict_step(batch, batch_idx)

Predict hyperedge scores for a batch.

Parameters:

Name Type Description Default
batch HData

Prediction batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
scores Tensor

Predicted hyperedge scores.

Source code in hypertorch/hlp/hnhn_hlp.py
def predict_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Predict hyperedge scores for a batch.

    Args:
        batch: Prediction batch.
        batch_idx: Batch index, unused.

    Returns:
        scores: Predicted hyperedge scores.
    """
    return self.forward(batch.x, batch.hyperedge_index)

configure_optimizers()

Configure the optimizer and scheduler.

Returns:

Name Type Description
optimizers_and_schedulers tuple[list[Adam], list[StepLR]]

Optimizer and scheduler lists.

Source code in hypertorch/hlp/hnhn_hlp.py
def configure_optimizers(self) -> tuple[list[optim.Adam], list[optim.lr_scheduler.StepLR]]:
    """
    Configure the optimizer and scheduler.

    Returns:
        optimizers_and_schedulers: Optimizer and scheduler lists.
    """
    optimizer = optim.Adam(self.parameters(), lr=self.lr, weight_decay=self.weight_decay)
    scheduler = optim.lr_scheduler.StepLR(
        optimizer, step_size=self.scheduler_step_size, gamma=self.scheduler_gamma
    )
    return [optimizer], [scheduler]

__eval_step(batch, stage)

Run shared evaluation logic for a stage.

Parameters:

Name Type Description Default
batch HData

Input batch.

required
stage Stage

Current evaluation stage.

required

Returns:

Name Type Description
loss Tensor

Computed loss.

Source code in hypertorch/hlp/hnhn_hlp.py
def __eval_step(self, batch: HData, stage: Stage) -> Tensor:
    """
    Run shared evaluation logic for a stage.

    Args:
        batch: Input batch.
        stage: Current evaluation stage.

    Returns:
        loss: Computed loss.
    """
    scores = self.forward(batch.x, batch.hyperedge_index)
    target_scores, target_labels = self._target_scores_and_labels(scores, batch)
    batch_size = target_labels.size(0)

    loss = self._compute_loss(target_scores, target_labels, batch_size, stage)
    self._compute_metrics(target_scores, target_labels, batch_size, stage)
    return loss

HGNNPEncoderConfig

Bases: TypedDict

Configuration for the HGNN+ encoder in HGNNPHlpModule.

Attributes:

Name Type Description
in_channels int

Number of input features per node.

hidden_channels int

Number of hidden units in the intermediate HGNN+ layer.

out_channels int

Number of output features (embedding size) per node.

bias NotRequired[bool]

Whether to include bias terms. Defaults to True.

use_batch_normalization NotRequired[bool]

Whether to use batch normalization. Defaults to False.

drop_rate NotRequired[float]

Dropout rate. Defaults to 0.5.

Source code in hypertorch/hlp/hgnnp_hlp.py
class HGNNPEncoderConfig(TypedDict):
    """
    Configuration for the HGNN+ encoder in HGNNPHlpModule.

    Attributes:
        in_channels: Number of input features per node.
        hidden_channels: Number of hidden units in the intermediate HGNN+ layer.
        out_channels: Number of output features (embedding size) per node.
        bias: Whether to include bias terms. Defaults to ``True``.
        use_batch_normalization: Whether to use batch normalization. Defaults to ``False``.
        drop_rate: Dropout rate. Defaults to ``0.5``.
    """

    in_channels: int
    hidden_channels: int
    out_channels: int
    bias: NotRequired[bool]
    use_batch_normalization: NotRequired[bool]
    drop_rate: NotRequired[float]

HGNNPHlpModule

Bases: HlpModule

A LightningModule for HGNN+-based Hyperedge Link Prediction.

Uses HGNN+ as an encoder to produce structure-aware node embeddings via row-stochastic hypergraph convolution, aggregates them per hyperedge, and scores each hyperedge with a linear decoder.

Attributes:

Name Type Description
encoder Module | None

HGNN+ encoder module inherited from HlpModule.

decoder Module

SLP decoder module inherited from HlpModule.

loss_fn Module

Loss function inherited from HlpModule.

metrics_log_kwargs dict[str, Any]

Metric logging keyword arguments inherited from HlpModule.

train_metrics MetricCollection | None

Optional training metrics inherited from HlpModule.

val_metrics MetricCollection | None

Optional validation metrics inherited from HlpModule.

test_metrics MetricCollection | None

Optional test metrics inherited from HlpModule.

aggregation Literal['mean', 'max', 'min', 'sum']

Method to aggregate node embeddings per hyperedge. Defaults to "mean".

lr float

Learning rate for the optimizer. Defaults to 0.01.

weight_decay float

L2 regularization. Defaults to 5e-4.

Source code in hypertorch/hlp/hgnnp_hlp.py
class HGNNPHlpModule(HlpModule):
    """
    A LightningModule for HGNN+-based Hyperedge Link Prediction.

    Uses HGNN+ as an encoder to produce structure-aware node embeddings via
    row-stochastic hypergraph convolution, aggregates them per hyperedge,
    and scores each hyperedge with a linear decoder.

    Attributes:
        encoder: HGNN+ encoder module inherited from ``HlpModule``.
        decoder: SLP decoder module inherited from ``HlpModule``.
        loss_fn: Loss function inherited from ``HlpModule``.
        metrics_log_kwargs: Metric logging keyword arguments inherited from ``HlpModule``.
        train_metrics: Optional training metrics inherited from ``HlpModule``.
        val_metrics: Optional validation metrics inherited from ``HlpModule``.
        test_metrics: Optional test metrics inherited from ``HlpModule``.
        aggregation: Method to aggregate node embeddings per hyperedge. Defaults to ``"mean"``.
        lr: Learning rate for the optimizer. Defaults to ``0.01``.
        weight_decay: L2 regularization. Defaults to ``5e-4``.
    """

    def __init__(
        self,
        encoder_config: HGNNPEncoderConfig,
        aggregation: Literal["mean", "max", "min", "sum"] = "mean",
        loss_fn: nn.Module | None = None,
        lr: float = 0.01,
        weight_decay: float = 5e-4,
        metrics: MetricCollection | None = None,
        metrics_log_kwargs: dict[str, Any] | None = None,
    ):
        """
        Initialize the HGNN+ HLP module.

        Args:
            encoder_config: Configuration for the HGNN+ encoder.
            aggregation: Method used to aggregate node embeddings per hyperedge.
                Defaults to ``"mean"``.
            loss_fn: Optional loss function. Defaults to ``BCEWithLogitsLoss``.
            lr: Learning rate for the optimizer. Defaults to ``0.01``.
            weight_decay: L2 regularization. Defaults to ``5e-4``.
            metrics: Optional metric collection for evaluation. Defaults to ``None``.
            metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
                Useful for configuring distributed synchronization behavior
                of ``torchmetrics``. Defaults to ``None``.
        """
        encoder = HGNNP(
            in_channels=encoder_config["in_channels"],
            hidden_channels=encoder_config["hidden_channels"],
            num_classes=encoder_config["out_channels"],
            bias=encoder_config.get("bias", True),
            use_batch_normalization=encoder_config.get("use_batch_normalization", False),
            drop_rate=encoder_config.get("drop_rate", 0.5),
        )
        decoder = SLP(in_channels=encoder_config["out_channels"], out_channels=1)

        super().__init__(
            encoder=encoder,
            decoder=decoder,
            loss_fn=loss_fn if loss_fn is not None else nn.BCEWithLogitsLoss(),
            metrics=metrics,
            metrics_log_kwargs=metrics_log_kwargs,
        )

        self.aggregation: Literal["mean", "max", "min", "sum"] = aggregation
        self.lr: float = lr
        self.weight_decay: float = weight_decay

    def forward(self, x: Tensor, hyperedge_index: Tensor) -> Tensor:
        """
        Run the full HGNN+-based hyperedge link prediction pipeline.

        The pipeline has three stages:
            1. Encode: HGNN+ applies two rounds of ``D_v^{-1} H D_e^{-1} H^T``
            smoothing to propagate information through the hypergraph topology with
            two-stage mean aggregation. The output is a structure-aware node
            embedding matrix of shape ``(num_nodes, out_channels)``.
            2. Aggregate: For each hyperedge being scored, pool the embeddings of its member
            nodes using the configured strategy (mean/max/min/sum). This produces a hyperedge
            embedding of shape ``(num_hyperedges, out_channels)``.
            3. Decode: A single linear layer projects each hyperedge embedding to a
            scalar score. Shape: ``(num_hyperedges,)``.

        Args:
            x: Node feature matrix of shape ``(num_nodes, in_channels)``.
                Must contain **all** nodes referenced in ``hyperedge_index``.
            hyperedge_index: Hyperedge connectivity of shape ``(2, num_incidences)``,
                with row 0 containing global node IDs and row 1 hyperedge IDs.

        Returns:
            scores: Logit scores of shape ``(num_hyperedges,)``.

        Raises:
            ValueError: If the encoder is not defined for this module.
        """
        if self.encoder is None:
            raise ValueError("Encoder is not defined for this HLP module.")

        # Encode: produce node embeddings using HGNN+, no graph reduction is applied
        # Example: x: (num_nodes, in_channels)
        #          -> node_embeddings: (num_nodes, out_channels), out_channels)
        node_embeddings: Tensor = self.encoder(x, hyperedge_index)

        # Aggregate: pool node embeddings per hyperedge
        # shape: (num_hyperedges, out_channels)
        hyperedge_embeddings = HyperedgeAggregator(hyperedge_index, node_embeddings).pool(
            self.aggregation
        )

        # Decode: linear projection to scalar score per hyperedge
        # shape: (num_hyperedges, 1) -> squeeze -> (num_hyperedges,)
        scores: Tensor = self.decoder(hyperedge_embeddings).squeeze(-1)
        return scores

    def training_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a training step.

        Args:
            batch: Training batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Training loss.
        """
        return self.__eval_step(batch, Stage.TRAIN)

    def validation_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a validation step.

        Args:
            batch: Validation batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Validation loss.
        """
        return self.__eval_step(batch, Stage.VAL)

    def test_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a test step.

        Args:
            batch: Test batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Test loss.
        """
        return self.__eval_step(batch, Stage.TEST)

    def predict_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Predict hyperedge scores for a batch.

        Args:
            batch: Prediction batch.
            batch_idx: Batch index, unused.

        Returns:
            scores: Predicted hyperedge scores.
        """
        return self.forward(batch.x, batch.hyperedge_index)

    def configure_optimizers(self) -> optim.Adam:
        """
        Configure the optimizer.

        Returns:
            optimizer: Adam optimizer.
        """
        return optim.Adam(self.parameters(), lr=self.lr, weight_decay=self.weight_decay)

    def __eval_step(self, batch: HData, stage: Stage) -> Tensor:
        """
        Run shared evaluation logic for a stage.

        Args:
            batch: Input batch.
            stage: Current evaluation stage.

        Returns:
            loss: Computed loss.
        """
        scores = self.forward(batch.x, batch.hyperedge_index)
        target_scores, target_labels = self._target_scores_and_labels(scores, batch)
        batch_size = target_labels.size(0)

        loss = self._compute_loss(target_scores, target_labels, batch_size, stage)
        self._compute_metrics(target_scores, target_labels, batch_size, stage)
        return loss

__init__(encoder_config, aggregation='mean', loss_fn=None, lr=0.01, weight_decay=0.0005, metrics=None, metrics_log_kwargs=None)

Initialize the HGNN+ HLP module.

Parameters:

Name Type Description Default
encoder_config HGNNPEncoderConfig

Configuration for the HGNN+ encoder.

required
aggregation Literal['mean', 'max', 'min', 'sum']

Method used to aggregate node embeddings per hyperedge. Defaults to "mean".

'mean'
loss_fn Module | None

Optional loss function. Defaults to BCEWithLogitsLoss.

None
lr float

Learning rate for the optimizer. Defaults to 0.01.

0.01
weight_decay float

L2 regularization. Defaults to 5e-4.

0.0005
metrics MetricCollection | None

Optional metric collection for evaluation. Defaults to None.

None
metrics_log_kwargs dict[str, Any] | None

Additional keyword arguments passed to metric log calls. Useful for configuring distributed synchronization behavior of torchmetrics. Defaults to None.

None
Source code in hypertorch/hlp/hgnnp_hlp.py
def __init__(
    self,
    encoder_config: HGNNPEncoderConfig,
    aggregation: Literal["mean", "max", "min", "sum"] = "mean",
    loss_fn: nn.Module | None = None,
    lr: float = 0.01,
    weight_decay: float = 5e-4,
    metrics: MetricCollection | None = None,
    metrics_log_kwargs: dict[str, Any] | None = None,
):
    """
    Initialize the HGNN+ HLP module.

    Args:
        encoder_config: Configuration for the HGNN+ encoder.
        aggregation: Method used to aggregate node embeddings per hyperedge.
            Defaults to ``"mean"``.
        loss_fn: Optional loss function. Defaults to ``BCEWithLogitsLoss``.
        lr: Learning rate for the optimizer. Defaults to ``0.01``.
        weight_decay: L2 regularization. Defaults to ``5e-4``.
        metrics: Optional metric collection for evaluation. Defaults to ``None``.
        metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
            Useful for configuring distributed synchronization behavior
            of ``torchmetrics``. Defaults to ``None``.
    """
    encoder = HGNNP(
        in_channels=encoder_config["in_channels"],
        hidden_channels=encoder_config["hidden_channels"],
        num_classes=encoder_config["out_channels"],
        bias=encoder_config.get("bias", True),
        use_batch_normalization=encoder_config.get("use_batch_normalization", False),
        drop_rate=encoder_config.get("drop_rate", 0.5),
    )
    decoder = SLP(in_channels=encoder_config["out_channels"], out_channels=1)

    super().__init__(
        encoder=encoder,
        decoder=decoder,
        loss_fn=loss_fn if loss_fn is not None else nn.BCEWithLogitsLoss(),
        metrics=metrics,
        metrics_log_kwargs=metrics_log_kwargs,
    )

    self.aggregation: Literal["mean", "max", "min", "sum"] = aggregation
    self.lr: float = lr
    self.weight_decay: float = weight_decay

forward(x, hyperedge_index)

Run the full HGNN+-based hyperedge link prediction pipeline.

The pipeline has three stages
  1. Encode: HGNN+ applies two rounds of D_v^{-1} H D_e^{-1} H^T smoothing to propagate information through the hypergraph topology with two-stage mean aggregation. The output is a structure-aware node embedding matrix of shape (num_nodes, out_channels).
  2. Aggregate: For each hyperedge being scored, pool the embeddings of its member nodes using the configured strategy (mean/max/min/sum). This produces a hyperedge embedding of shape (num_hyperedges, out_channels).
  3. Decode: A single linear layer projects each hyperedge embedding to a scalar score. Shape: (num_hyperedges,).

Parameters:

Name Type Description Default
x Tensor

Node feature matrix of shape (num_nodes, in_channels). Must contain all nodes referenced in hyperedge_index.

required
hyperedge_index Tensor

Hyperedge connectivity of shape (2, num_incidences), with row 0 containing global node IDs and row 1 hyperedge IDs.

required

Returns:

Name Type Description
scores Tensor

Logit scores of shape (num_hyperedges,).

Raises:

Type Description
ValueError

If the encoder is not defined for this module.

Source code in hypertorch/hlp/hgnnp_hlp.py
def forward(self, x: Tensor, hyperedge_index: Tensor) -> Tensor:
    """
    Run the full HGNN+-based hyperedge link prediction pipeline.

    The pipeline has three stages:
        1. Encode: HGNN+ applies two rounds of ``D_v^{-1} H D_e^{-1} H^T``
        smoothing to propagate information through the hypergraph topology with
        two-stage mean aggregation. The output is a structure-aware node
        embedding matrix of shape ``(num_nodes, out_channels)``.
        2. Aggregate: For each hyperedge being scored, pool the embeddings of its member
        nodes using the configured strategy (mean/max/min/sum). This produces a hyperedge
        embedding of shape ``(num_hyperedges, out_channels)``.
        3. Decode: A single linear layer projects each hyperedge embedding to a
        scalar score. Shape: ``(num_hyperedges,)``.

    Args:
        x: Node feature matrix of shape ``(num_nodes, in_channels)``.
            Must contain **all** nodes referenced in ``hyperedge_index``.
        hyperedge_index: Hyperedge connectivity of shape ``(2, num_incidences)``,
            with row 0 containing global node IDs and row 1 hyperedge IDs.

    Returns:
        scores: Logit scores of shape ``(num_hyperedges,)``.

    Raises:
        ValueError: If the encoder is not defined for this module.
    """
    if self.encoder is None:
        raise ValueError("Encoder is not defined for this HLP module.")

    # Encode: produce node embeddings using HGNN+, no graph reduction is applied
    # Example: x: (num_nodes, in_channels)
    #          -> node_embeddings: (num_nodes, out_channels), out_channels)
    node_embeddings: Tensor = self.encoder(x, hyperedge_index)

    # Aggregate: pool node embeddings per hyperedge
    # shape: (num_hyperedges, out_channels)
    hyperedge_embeddings = HyperedgeAggregator(hyperedge_index, node_embeddings).pool(
        self.aggregation
    )

    # Decode: linear projection to scalar score per hyperedge
    # shape: (num_hyperedges, 1) -> squeeze -> (num_hyperedges,)
    scores: Tensor = self.decoder(hyperedge_embeddings).squeeze(-1)
    return scores

training_step(batch, batch_idx)

Run a training step.

Parameters:

Name Type Description Default
batch HData

Training batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Training loss.

Source code in hypertorch/hlp/hgnnp_hlp.py
def training_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a training step.

    Args:
        batch: Training batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Training loss.
    """
    return self.__eval_step(batch, Stage.TRAIN)

validation_step(batch, batch_idx)

Run a validation step.

Parameters:

Name Type Description Default
batch HData

Validation batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Validation loss.

Source code in hypertorch/hlp/hgnnp_hlp.py
def validation_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a validation step.

    Args:
        batch: Validation batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Validation loss.
    """
    return self.__eval_step(batch, Stage.VAL)

test_step(batch, batch_idx)

Run a test step.

Parameters:

Name Type Description Default
batch HData

Test batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Test loss.

Source code in hypertorch/hlp/hgnnp_hlp.py
def test_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a test step.

    Args:
        batch: Test batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Test loss.
    """
    return self.__eval_step(batch, Stage.TEST)

predict_step(batch, batch_idx)

Predict hyperedge scores for a batch.

Parameters:

Name Type Description Default
batch HData

Prediction batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
scores Tensor

Predicted hyperedge scores.

Source code in hypertorch/hlp/hgnnp_hlp.py
def predict_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Predict hyperedge scores for a batch.

    Args:
        batch: Prediction batch.
        batch_idx: Batch index, unused.

    Returns:
        scores: Predicted hyperedge scores.
    """
    return self.forward(batch.x, batch.hyperedge_index)

configure_optimizers()

Configure the optimizer.

Returns:

Name Type Description
optimizer Adam

Adam optimizer.

Source code in hypertorch/hlp/hgnnp_hlp.py
def configure_optimizers(self) -> optim.Adam:
    """
    Configure the optimizer.

    Returns:
        optimizer: Adam optimizer.
    """
    return optim.Adam(self.parameters(), lr=self.lr, weight_decay=self.weight_decay)

__eval_step(batch, stage)

Run shared evaluation logic for a stage.

Parameters:

Name Type Description Default
batch HData

Input batch.

required
stage Stage

Current evaluation stage.

required

Returns:

Name Type Description
loss Tensor

Computed loss.

Source code in hypertorch/hlp/hgnnp_hlp.py
def __eval_step(self, batch: HData, stage: Stage) -> Tensor:
    """
    Run shared evaluation logic for a stage.

    Args:
        batch: Input batch.
        stage: Current evaluation stage.

    Returns:
        loss: Computed loss.
    """
    scores = self.forward(batch.x, batch.hyperedge_index)
    target_scores, target_labels = self._target_scores_and_labels(scores, batch)
    batch_size = target_labels.size(0)

    loss = self._compute_loss(target_scores, target_labels, batch_size, stage)
    self._compute_metrics(target_scores, target_labels, batch_size, stage)
    return loss

HyperGCNHlpModule

Bases: HlpModule

A LightningModule for HyperGCN-based Hyperedge Link Prediction.

Uses HyperGCN as an encoder to produce structure-aware node embeddings via graph convolution on the full hypergraph, aggregates them per hyperedge, and scores each hyperedge with a linear decoder.

Attributes:

Name Type Description
encoder Module | None

HyperGCN encoder module inherited from HlpModule.

decoder Module

SLP decoder module inherited from HlpModule.

loss_fn Module

Loss function inherited from HlpModule.

metrics_log_kwargs dict[str, Any]

Metric logging keyword arguments inherited from HlpModule.

train_metrics MetricCollection | None

Optional training metrics inherited from HlpModule.

val_metrics MetricCollection | None

Optional validation metrics inherited from HlpModule.

test_metrics MetricCollection | None

Optional test metrics inherited from HlpModule.

aggregation Literal['mean', 'max', 'min', 'sum']

Method to aggregate node embeddings per hyperedge. Defaults to "mean".

lr float

Learning rate for the optimizer. Defaults to 0.01.

weight_decay float

L2 regularization. Defaults to 5e-4.

Source code in hypertorch/hlp/hypergcn_hlp.py
class HyperGCNHlpModule(HlpModule):
    """
    A LightningModule for HyperGCN-based Hyperedge Link Prediction.

    Uses HyperGCN as an encoder to produce structure-aware node embeddings via
    graph convolution on the full hypergraph, aggregates them per hyperedge,
    and scores each hyperedge with a linear decoder.

    Attributes:
        encoder: HyperGCN encoder module inherited from ``HlpModule``.
        decoder: SLP decoder module inherited from ``HlpModule``.
        loss_fn: Loss function inherited from ``HlpModule``.
        metrics_log_kwargs: Metric logging keyword arguments inherited from ``HlpModule``.
        train_metrics: Optional training metrics inherited from ``HlpModule``.
        val_metrics: Optional validation metrics inherited from ``HlpModule``.
        test_metrics: Optional test metrics inherited from ``HlpModule``.
        aggregation: Method to aggregate node embeddings per hyperedge. Defaults to ``"mean"``.
        lr: Learning rate for the optimizer. Defaults to ``0.01``.
        weight_decay: L2 regularization. Defaults to ``5e-4``.
    """

    def __init__(
        self,
        encoder_config: HyperGCNEncoderConfig,
        aggregation: Literal["mean", "max", "min", "sum"] = "mean",
        loss_fn: nn.Module | None = None,
        lr: float = 0.01,
        weight_decay: float = 5e-4,
        metrics: MetricCollection | None = None,
        metrics_log_kwargs: dict[str, Any] | None = None,
    ):
        """
        Initialize the HyperGCN HLP module.

        Args:
            encoder_config: Configuration for the HyperGCN encoder.
            aggregation: Method used to aggregate node embeddings per hyperedge.
                Defaults to ``"mean"``.
            loss_fn: Optional loss function. Defaults to ``BCEWithLogitsLoss``.
            lr: Learning rate for the optimizer. Defaults to ``0.01``.
            weight_decay: L2 regularization. Defaults to ``5e-4``.
            metrics: Optional metric collection for evaluation. Defaults to ``None``.
            metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
                Useful for configuring distributed synchronization behavior
                of ``torchmetrics``. Defaults to ``None``.
        """
        encoder = HyperGCN(
            in_channels=encoder_config["in_channels"],
            hidden_channels=encoder_config["hidden_channels"],
            num_classes=encoder_config["out_channels"],
            bias=encoder_config.get("bias", True),
            use_batch_normalization=encoder_config.get("use_batch_normalization", False),
            drop_rate=encoder_config.get("drop_rate", 0.5),
            use_mediator=encoder_config.get("use_mediator", False),
            fast=encoder_config.get("fast", True),
            seed=encoder_config.get("seed"),
        )
        decoder = SLP(in_channels=encoder_config["out_channels"], out_channels=1)

        super().__init__(
            encoder=encoder,
            decoder=decoder,
            loss_fn=loss_fn if loss_fn is not None else nn.BCEWithLogitsLoss(),
            metrics=metrics,
            metrics_log_kwargs=metrics_log_kwargs,
        )

        self.aggregation: Literal["mean", "max", "min", "sum"] = aggregation
        self.lr: float = lr
        self.weight_decay: float = weight_decay

    def forward(self, x: Tensor, hyperedge_index: Tensor) -> Tensor:
        """
        Encode node features via HyperGCN, aggregate per hyperedge, and score.

        Steps:
            1. Encode: HyperGCN builds a GCN Laplacian from ``hyperedge_index``
               and applies message passing to produce structure-aware node embeddings.
            2. Aggregate: For each hyperedge, aggregate its member nodes' embeddings
               using the configured pooling method (mean/max/min/sum).
            3. Decode: A linear layer scores each hyperedge embedding.

        Examples:
            Given 5 nodes with 3 features and 2 hyperedges:

                >>> x.shape  # (5, 3) — all nodes in the hypergraph
                >>> hyperedge_index = [[0, 1, 2, 3, 4],  # node IDs (global)
                ...                    [0, 0, 0, 1, 1]]  # hyperedge IDs

            The forward pass:

                >>> HyperGCN encodes all 5 nodes using the full graph Laplacian.
                ...   ``node_embeddings.shape = (5, out_channels)``
                >>> Aggregate per hyperedge:
                ...   - hyperedge 0: pool(emb[0], emb[1], emb[2])
                ...   - hyperedge 1: pool(emb[3], emb[4])
                >>> Decode: one scalar score per hyperedge → ``scores.shape = (2,)``

        Args:
            x: Node feature matrix of shape ``(num_nodes, in_channels)``.
                Must contain **all** nodes in the hypergraph.
            hyperedge_index: Hyperedge connectivity of shape ``(2, num_incidences)``
                with **global** node IDs.

        Returns:
            scores: Scores of shape ``(num_hyperedges,)``.

        Raises:
            ValueError: If the encoder is not defined for this module.
        """
        if self.encoder is None:
            raise ValueError("Encoder is not defined for this HLP module.")

        # Encode: HyperGCN applies Laplacian-based message passing
        # Example: x: (num_nodes, in_channels)
        #          -> node_embeddings: (num_nodes, out_channels)
        node_embeddings: Tensor = self.encoder(x, hyperedge_index)

        # Aggregate: pool node embeddings per hyperedge
        # shape: (num_hyperedges, out_channels)
        hyperedge_embeddings = HyperedgeAggregator(hyperedge_index, node_embeddings).pool(
            self.aggregation
        )

        # Decode: linear projection to scalar score per hyperedge
        # shape: (num_hyperedges, 1) -> squeeze -> (num_hyperedges,)
        scores: Tensor = self.decoder(hyperedge_embeddings).squeeze(-1)
        return scores

    def training_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a training step.

        Args:
            batch: Training batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Training loss.
        """
        return self.__eval_step(batch, Stage.TRAIN)

    def validation_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a validation step.

        Args:
            batch: Validation batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Validation loss.
        """
        return self.__eval_step(batch, Stage.VAL)

    def test_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a test step.

        Args:
            batch: Test batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Test loss.
        """
        return self.__eval_step(batch, Stage.TEST)

    def predict_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Predict hyperedge scores for a batch.

        Args:
            batch: Prediction batch.
            batch_idx: Batch index, unused.

        Returns:
            scores: Predicted hyperedge scores.
        """
        return self.forward(batch.x, batch.hyperedge_index)

    def configure_optimizers(self) -> optim.Adam:
        """
        Configure the optimizer.

        Returns:
            optimizer: Adam optimizer.
        """
        return optim.Adam(self.parameters(), lr=self.lr, weight_decay=self.weight_decay)

    def __eval_step(self, batch: HData, stage: Stage) -> Tensor:
        """
        Run shared evaluation logic for a stage.

        Args:
            batch: Input batch.
            stage: Current evaluation stage.

        Returns:
            loss: Computed loss.
        """
        scores = self.forward(batch.x, batch.hyperedge_index)
        target_scores, target_labels = self._target_scores_and_labels(scores, batch)
        batch_size = target_labels.size(0)

        loss = self._compute_loss(target_scores, target_labels, batch_size, stage)
        self._compute_metrics(target_scores, target_labels, batch_size, stage)
        return loss

__init__(encoder_config, aggregation='mean', loss_fn=None, lr=0.01, weight_decay=0.0005, metrics=None, metrics_log_kwargs=None)

Initialize the HyperGCN HLP module.

Parameters:

Name Type Description Default
encoder_config HyperGCNEncoderConfig

Configuration for the HyperGCN encoder.

required
aggregation Literal['mean', 'max', 'min', 'sum']

Method used to aggregate node embeddings per hyperedge. Defaults to "mean".

'mean'
loss_fn Module | None

Optional loss function. Defaults to BCEWithLogitsLoss.

None
lr float

Learning rate for the optimizer. Defaults to 0.01.

0.01
weight_decay float

L2 regularization. Defaults to 5e-4.

0.0005
metrics MetricCollection | None

Optional metric collection for evaluation. Defaults to None.

None
metrics_log_kwargs dict[str, Any] | None

Additional keyword arguments passed to metric log calls. Useful for configuring distributed synchronization behavior of torchmetrics. Defaults to None.

None
Source code in hypertorch/hlp/hypergcn_hlp.py
def __init__(
    self,
    encoder_config: HyperGCNEncoderConfig,
    aggregation: Literal["mean", "max", "min", "sum"] = "mean",
    loss_fn: nn.Module | None = None,
    lr: float = 0.01,
    weight_decay: float = 5e-4,
    metrics: MetricCollection | None = None,
    metrics_log_kwargs: dict[str, Any] | None = None,
):
    """
    Initialize the HyperGCN HLP module.

    Args:
        encoder_config: Configuration for the HyperGCN encoder.
        aggregation: Method used to aggregate node embeddings per hyperedge.
            Defaults to ``"mean"``.
        loss_fn: Optional loss function. Defaults to ``BCEWithLogitsLoss``.
        lr: Learning rate for the optimizer. Defaults to ``0.01``.
        weight_decay: L2 regularization. Defaults to ``5e-4``.
        metrics: Optional metric collection for evaluation. Defaults to ``None``.
        metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
            Useful for configuring distributed synchronization behavior
            of ``torchmetrics``. Defaults to ``None``.
    """
    encoder = HyperGCN(
        in_channels=encoder_config["in_channels"],
        hidden_channels=encoder_config["hidden_channels"],
        num_classes=encoder_config["out_channels"],
        bias=encoder_config.get("bias", True),
        use_batch_normalization=encoder_config.get("use_batch_normalization", False),
        drop_rate=encoder_config.get("drop_rate", 0.5),
        use_mediator=encoder_config.get("use_mediator", False),
        fast=encoder_config.get("fast", True),
        seed=encoder_config.get("seed"),
    )
    decoder = SLP(in_channels=encoder_config["out_channels"], out_channels=1)

    super().__init__(
        encoder=encoder,
        decoder=decoder,
        loss_fn=loss_fn if loss_fn is not None else nn.BCEWithLogitsLoss(),
        metrics=metrics,
        metrics_log_kwargs=metrics_log_kwargs,
    )

    self.aggregation: Literal["mean", "max", "min", "sum"] = aggregation
    self.lr: float = lr
    self.weight_decay: float = weight_decay

forward(x, hyperedge_index)

Encode node features via HyperGCN, aggregate per hyperedge, and score.

Steps
  1. Encode: HyperGCN builds a GCN Laplacian from hyperedge_index and applies message passing to produce structure-aware node embeddings.
  2. Aggregate: For each hyperedge, aggregate its member nodes' embeddings using the configured pooling method (mean/max/min/sum).
  3. Decode: A linear layer scores each hyperedge embedding.

Examples:

Given 5 nodes with 3 features and 2 hyperedges:

>>> x.shape  # (5, 3) — all nodes in the hypergraph
>>> hyperedge_index = [[0, 1, 2, 3, 4],  # node IDs (global)
...                    [0, 0, 0, 1, 1]]  # hyperedge IDs

The forward pass:

>>> HyperGCN encodes all 5 nodes using the full graph Laplacian.
...   ``node_embeddings.shape = (5, out_channels)``
>>> Aggregate per hyperedge:
...   - hyperedge 0: pool(emb[0], emb[1], emb[2])
...   - hyperedge 1: pool(emb[3], emb[4])
>>> Decode: one scalar score per hyperedge → ``scores.shape = (2,)``

Parameters:

Name Type Description Default
x Tensor

Node feature matrix of shape (num_nodes, in_channels). Must contain all nodes in the hypergraph.

required
hyperedge_index Tensor

Hyperedge connectivity of shape (2, num_incidences) with global node IDs.

required

Returns:

Name Type Description
scores Tensor

Scores of shape (num_hyperedges,).

Raises:

Type Description
ValueError

If the encoder is not defined for this module.

Source code in hypertorch/hlp/hypergcn_hlp.py
def forward(self, x: Tensor, hyperedge_index: Tensor) -> Tensor:
    """
    Encode node features via HyperGCN, aggregate per hyperedge, and score.

    Steps:
        1. Encode: HyperGCN builds a GCN Laplacian from ``hyperedge_index``
           and applies message passing to produce structure-aware node embeddings.
        2. Aggregate: For each hyperedge, aggregate its member nodes' embeddings
           using the configured pooling method (mean/max/min/sum).
        3. Decode: A linear layer scores each hyperedge embedding.

    Examples:
        Given 5 nodes with 3 features and 2 hyperedges:

            >>> x.shape  # (5, 3) — all nodes in the hypergraph
            >>> hyperedge_index = [[0, 1, 2, 3, 4],  # node IDs (global)
            ...                    [0, 0, 0, 1, 1]]  # hyperedge IDs

        The forward pass:

            >>> HyperGCN encodes all 5 nodes using the full graph Laplacian.
            ...   ``node_embeddings.shape = (5, out_channels)``
            >>> Aggregate per hyperedge:
            ...   - hyperedge 0: pool(emb[0], emb[1], emb[2])
            ...   - hyperedge 1: pool(emb[3], emb[4])
            >>> Decode: one scalar score per hyperedge → ``scores.shape = (2,)``

    Args:
        x: Node feature matrix of shape ``(num_nodes, in_channels)``.
            Must contain **all** nodes in the hypergraph.
        hyperedge_index: Hyperedge connectivity of shape ``(2, num_incidences)``
            with **global** node IDs.

    Returns:
        scores: Scores of shape ``(num_hyperedges,)``.

    Raises:
        ValueError: If the encoder is not defined for this module.
    """
    if self.encoder is None:
        raise ValueError("Encoder is not defined for this HLP module.")

    # Encode: HyperGCN applies Laplacian-based message passing
    # Example: x: (num_nodes, in_channels)
    #          -> node_embeddings: (num_nodes, out_channels)
    node_embeddings: Tensor = self.encoder(x, hyperedge_index)

    # Aggregate: pool node embeddings per hyperedge
    # shape: (num_hyperedges, out_channels)
    hyperedge_embeddings = HyperedgeAggregator(hyperedge_index, node_embeddings).pool(
        self.aggregation
    )

    # Decode: linear projection to scalar score per hyperedge
    # shape: (num_hyperedges, 1) -> squeeze -> (num_hyperedges,)
    scores: Tensor = self.decoder(hyperedge_embeddings).squeeze(-1)
    return scores

training_step(batch, batch_idx)

Run a training step.

Parameters:

Name Type Description Default
batch HData

Training batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Training loss.

Source code in hypertorch/hlp/hypergcn_hlp.py
def training_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a training step.

    Args:
        batch: Training batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Training loss.
    """
    return self.__eval_step(batch, Stage.TRAIN)

validation_step(batch, batch_idx)

Run a validation step.

Parameters:

Name Type Description Default
batch HData

Validation batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Validation loss.

Source code in hypertorch/hlp/hypergcn_hlp.py
def validation_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a validation step.

    Args:
        batch: Validation batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Validation loss.
    """
    return self.__eval_step(batch, Stage.VAL)

test_step(batch, batch_idx)

Run a test step.

Parameters:

Name Type Description Default
batch HData

Test batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Test loss.

Source code in hypertorch/hlp/hypergcn_hlp.py
def test_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a test step.

    Args:
        batch: Test batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Test loss.
    """
    return self.__eval_step(batch, Stage.TEST)

predict_step(batch, batch_idx)

Predict hyperedge scores for a batch.

Parameters:

Name Type Description Default
batch HData

Prediction batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
scores Tensor

Predicted hyperedge scores.

Source code in hypertorch/hlp/hypergcn_hlp.py
def predict_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Predict hyperedge scores for a batch.

    Args:
        batch: Prediction batch.
        batch_idx: Batch index, unused.

    Returns:
        scores: Predicted hyperedge scores.
    """
    return self.forward(batch.x, batch.hyperedge_index)

configure_optimizers()

Configure the optimizer.

Returns:

Name Type Description
optimizer Adam

Adam optimizer.

Source code in hypertorch/hlp/hypergcn_hlp.py
def configure_optimizers(self) -> optim.Adam:
    """
    Configure the optimizer.

    Returns:
        optimizer: Adam optimizer.
    """
    return optim.Adam(self.parameters(), lr=self.lr, weight_decay=self.weight_decay)

__eval_step(batch, stage)

Run shared evaluation logic for a stage.

Parameters:

Name Type Description Default
batch HData

Input batch.

required
stage Stage

Current evaluation stage.

required

Returns:

Name Type Description
loss Tensor

Computed loss.

Source code in hypertorch/hlp/hypergcn_hlp.py
def __eval_step(self, batch: HData, stage: Stage) -> Tensor:
    """
    Run shared evaluation logic for a stage.

    Args:
        batch: Input batch.
        stage: Current evaluation stage.

    Returns:
        loss: Computed loss.
    """
    scores = self.forward(batch.x, batch.hyperedge_index)
    target_scores, target_labels = self._target_scores_and_labels(scores, batch)
    batch_size = target_labels.size(0)

    loss = self._compute_loss(target_scores, target_labels, batch_size, stage)
    self._compute_metrics(target_scores, target_labels, batch_size, stage)
    return loss

HyperGCNEncoderConfig

Bases: TypedDict

Configuration for the HyperGCN encoder in HyperGCNHlpModule.

Attributes:

Name Type Description
in_channels int

Number of input features per node.

hidden_channels int

Number of hidden units in the intermediate HyperGCN layer.

out_channels int

Number of output features (embedding size) per node.

bias NotRequired[bool]

Whether to include bias terms. Defaults to True.

use_batch_normalization NotRequired[bool]

Whether to use batch normalization. Defaults to False.

drop_rate NotRequired[float]

Dropout rate. Defaults to 0.5.

use_mediator NotRequired[bool]

Whether to use mediator nodes for hyperedge-to-edge conversion. Defaults to False.

fast NotRequired[bool]

Whether to cache the graph structure after first computation. Defaults to True.

seed NotRequired[int]

Optional random seed for the random reduction of hyperedges to edges. Defaults to None.

Source code in hypertorch/hlp/hypergcn_hlp.py
class HyperGCNEncoderConfig(TypedDict):
    """
    Configuration for the HyperGCN encoder in HyperGCNHlpModule.

    Attributes:
        in_channels: Number of input features per node.
        hidden_channels: Number of hidden units in the intermediate HyperGCN layer.
        out_channels: Number of output features (embedding size) per node.
        bias: Whether to include bias terms. Defaults to ``True``.
        use_batch_normalization: Whether to use batch normalization. Defaults to ``False``.
        drop_rate: Dropout rate. Defaults to ``0.5``.
        use_mediator: Whether to use mediator nodes for hyperedge-to-edge conversion.
            Defaults to ``False``.
        fast: Whether to cache the graph structure after first computation.
            Defaults to ``True``.
        seed: Optional random seed for the random reduction of hyperedges to edges.
            Defaults to ``None``.
    """

    in_channels: int
    hidden_channels: int
    out_channels: int
    bias: NotRequired[bool]
    use_batch_normalization: NotRequired[bool]
    drop_rate: NotRequired[float]
    use_mediator: NotRequired[bool]
    fast: NotRequired[bool]
    seed: NotRequired[int]

MLPHlpModule

Bases: HlpModule

A LightningModule for MLP-based Hyperlink Prediction.

Uses an MLP encoder to produce node embeddings, aggregates them per hyperedge via mean pooling, and scores each hyperedge with a linear decoder.

Attributes:

Name Type Description
encoder Module | None

MLP encoder module inherited from HlpModule.

decoder Module

SLP decoder module inherited from HlpModule.

loss_fn Module

Loss function inherited from HlpModule.

metrics_log_kwargs dict[str, Any]

Metric logging keyword arguments inherited from HlpModule.

train_metrics MetricCollection | None

Optional training metrics inherited from HlpModule.

val_metrics MetricCollection | None

Optional validation metrics inherited from HlpModule.

test_metrics MetricCollection | None

Optional test metrics inherited from HlpModule.

aggregation Literal['mean', 'max', 'min', 'sum']

Method to aggregate node embeddings per hyperedge. Defaults to "mean".

lr float

Learning rate for the optimizer. Defaults to 0.001.

Source code in hypertorch/hlp/mlp_hlp.py
class MLPHlpModule(HlpModule):
    """
    A LightningModule for MLP-based Hyperlink Prediction.

    Uses an MLP encoder to produce node embeddings, aggregates them per hyperedge
    via mean pooling, and scores each hyperedge with a linear decoder.

    Attributes:
        encoder: MLP encoder module inherited from ``HlpModule``.
        decoder: SLP decoder module inherited from ``HlpModule``.
        loss_fn: Loss function inherited from ``HlpModule``.
        metrics_log_kwargs: Metric logging keyword arguments inherited from ``HlpModule``.
        train_metrics: Optional training metrics inherited from ``HlpModule``.
        val_metrics: Optional validation metrics inherited from ``HlpModule``.
        test_metrics: Optional test metrics inherited from ``HlpModule``.
        aggregation: Method to aggregate node embeddings per hyperedge. Defaults to ``"mean"``.
        lr: Learning rate for the optimizer. Defaults to ``0.001``.
    """

    def __init__(
        self,
        encoder_config: MLPEncoderConfig,
        aggregation: Literal["mean", "max", "min", "sum"] = "mean",
        loss_fn: nn.Module | None = None,
        lr: float = 0.001,
        metrics: MetricCollection | None = None,
        metrics_log_kwargs: dict[str, Any] | None = None,
    ):
        """
        Initialize the MLP HLP module.

        Args:
            encoder_config: Configuration for the MLP encoder.
            aggregation: Method used to aggregate node embeddings per hyperedge.
                Defaults to ``"mean"``.
            loss_fn: Optional loss function. Defaults to ``BCEWithLogitsLoss``.
            lr: Learning rate for the optimizer. Defaults to ``0.001``.
            metrics: Optional metric collection for evaluation. Defaults to ``None``.
            metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
                Useful for configuring distributed synchronization behavior
                of ``torchmetrics``. Defaults to ``None``.
        """
        # The encoder outputs node embeddings of shape (num_nodes, out_channels).
        encoder = MLP(
            in_channels=encoder_config["in_channels"],
            hidden_channels=encoder_config.get("hidden_channels"),
            out_channels=encoder_config.get("out_channels", 1),
            num_layers=encoder_config.get("num_layers", 1),
            activation_fn=encoder_config.get("activation_fn"),
            activation_fn_kwargs=encoder_config.get("activation_fn_kwargs"),
            normalization_fn=encoder_config.get("normalization_fn"),
            normalization_fn_kwargs=encoder_config.get("normalization_fn_kwargs"),
            bias=encoder_config.get("bias", True),
            drop_rate=encoder_config.get("drop_rate", 0.0),
        )

        # The decoder takes in the aggregated hyperedge embeddings of shape
        # (num_hyperedges, encoder_config.out_channels)
        # and produces a score for each hyperedge of shape (num_hyperedges, 1).
        decoder = SLP(in_channels=encoder_config.get("out_channels", 1), out_channels=1)

        super().__init__(
            encoder=encoder,
            decoder=decoder,
            loss_fn=loss_fn if loss_fn is not None else nn.BCEWithLogitsLoss(),
            metrics=metrics,
            metrics_log_kwargs=metrics_log_kwargs,
        )

        self.aggregation: Literal["mean", "max", "min", "sum"] = aggregation
        self.lr: float = lr

    def forward(self, x: Tensor, hyperedge_index: Tensor) -> Tensor:
        """
        Encode node features, aggregate per hyperedge via mean pooling, and score.

        Examples:
            Given 4 nodes with 3 features each and 2 hyperedges:
                >>> x = [[0.1, 0.2, 0.3],   # node 0
                ...      [0.4, 0.5, 0.6],   # node 1
                ...      [0.7, 0.8, 0.9],   # node 2
                ...      [1.0, 1.1, 1.2]]   # node 3

                >>> # hyperedge 0 = {node 0, node 1, node 2}
                >>> # hyperedge 1 = {node 2, node 3}
                >>> hyperedge_index = [[0, 1, 2, 2, 3],   # node ids
                ...                    [0, 0, 0, 1, 1]]   # hyperedge ids

            The forward pass:

                >>> Encoder maps each node to an embedding vector.
                >>> Aggregate embeddings by summing them per hyperedge:
                ...   - hyperedge 0: emb[0] + emb[1] + emb[2]
                ...   - hyperedge 1: emb[2] + emb[3]
                >>> Sums are divided by the number of nodes per hyperedge (mean pooling):
                ...   - hyperedge 0: (emb[0] + emb[1] + emb[2]) / 3
                ...   - hyperedge 1: (emb[2] + emb[3]) / 2
                >>> Decoder scores each hyperedge embedding, producing one scalar per hyperedge.

        Args:
            x: Node feature matrix of shape ``(num_nodes, in_channels)``.
            hyperedge_index: Hyperedge connectivity of shape ``(2, num_incidences)``.

        Returns:
            scores: Scores of shape ``(num_hyperedges,)``.

        Raises:
            ValueError: If the encoder is not defined for this module.
        """
        if self.encoder is None:
            raise ValueError("Encoder is not defined for this HLP module.")

        # Encode: map each node raw features to an embedding vector.
        # x: (num_nodes, in_channels) -> node_embeddings: (num_nodes, out_channels)
        # Example: in_channels=3, out_channels=2
        #          -> node 0: [0.1, 0.2, 0.3] -> [e00, e01]
        #          -> node 1: [0.4, 0.5, 0.6] -> [e10, e11]
        #          -> node 2: [0.7, 0.8, 0.9] -> [e20, e21]
        #          -> node 3: [1.0, 1.1, 1.2] -> [e30, e31]
        node_embeddings: Tensor = self.encoder(x)

        # Aggregate: for each hyperedge, aggregate the embeddings of its member nodes.
        # Example::
        # - hyperedge 0 contains node 0, 1, 2 -> aggregate([e00, e01], [e10, e11], [e20, e21])
        #                                         -> [pooled_0, pooled_1]
        # - hyperedge 1 contains node 2, 3 -> aggregate([e20, e21], [e30, e31])
        #                                  -> [pooled_0, pooled_1]
        # shape: (num_hyperedges, out_channels)
        hyperedge_embeddings = HyperedgeAggregator(hyperedge_index, node_embeddings).pool(
            self.aggregation,
        )

        # Decode: score each hyperedge embedding, producing one scalar per hyperedge.
        # Example:
        # - hyperedge 0: [pooled_0, pooled_1] -> score_0
        # - hyperedge 1: [pooled_0, pooled_1] -> score_1
        # shape: (2,)
        scores: Tensor = self.decoder(hyperedge_embeddings).squeeze(-1)
        return scores

    def training_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a training step.

        Args:
            batch: Training batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Training loss.
        """
        scores = self.forward(batch.x, batch.hyperedge_index)
        target_scores, target_labels = self._target_scores_and_labels(scores, batch)
        batch_size = target_labels.size(0)

        loss = self._compute_loss(target_scores, target_labels, batch_size, Stage.TRAIN)
        self._compute_metrics(target_scores, target_labels, batch_size, Stage.TRAIN)
        return loss

    def validation_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a validation step.

        Args:
            batch: Validation batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Validation loss.
        """
        return self.__eval_step(batch, Stage.VAL)

    def test_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a test step.

        Args:
            batch: Test batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Test loss.
        """
        return self.__eval_step(batch, Stage.TEST)

    def predict_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Predict hyperedge scores for a batch.

        Args:
            batch: Prediction batch.
            batch_idx: Batch index, unused.

        Returns:
            scores: Predicted hyperedge scores.
        """
        return self.forward(batch.x, batch.hyperedge_index)

    def configure_optimizers(self) -> optim.Adam:
        """
        Configure the optimizer.

        Returns:
            optimizer: Adam optimizer.
        """
        return optim.Adam(self.parameters(), lr=self.lr)

    def __eval_step(self, batch: HData, stage: Stage) -> Tensor:
        """
        Run shared evaluation logic for a stage.

        Args:
            batch: Input batch.
            stage: Current evaluation stage.

        Returns:
            loss: Computed loss.
        """
        scores = self.forward(batch.x, batch.hyperedge_index)
        target_scores, target_labels = self._target_scores_and_labels(scores, batch)
        batch_size = target_labels.size(0)

        loss = self._compute_loss(target_scores, target_labels, batch_size, stage)
        self._compute_metrics(target_scores, target_labels, batch_size, stage)
        return loss

__init__(encoder_config, aggregation='mean', loss_fn=None, lr=0.001, metrics=None, metrics_log_kwargs=None)

Initialize the MLP HLP module.

Parameters:

Name Type Description Default
encoder_config MLPEncoderConfig

Configuration for the MLP encoder.

required
aggregation Literal['mean', 'max', 'min', 'sum']

Method used to aggregate node embeddings per hyperedge. Defaults to "mean".

'mean'
loss_fn Module | None

Optional loss function. Defaults to BCEWithLogitsLoss.

None
lr float

Learning rate for the optimizer. Defaults to 0.001.

0.001
metrics MetricCollection | None

Optional metric collection for evaluation. Defaults to None.

None
metrics_log_kwargs dict[str, Any] | None

Additional keyword arguments passed to metric log calls. Useful for configuring distributed synchronization behavior of torchmetrics. Defaults to None.

None
Source code in hypertorch/hlp/mlp_hlp.py
def __init__(
    self,
    encoder_config: MLPEncoderConfig,
    aggregation: Literal["mean", "max", "min", "sum"] = "mean",
    loss_fn: nn.Module | None = None,
    lr: float = 0.001,
    metrics: MetricCollection | None = None,
    metrics_log_kwargs: dict[str, Any] | None = None,
):
    """
    Initialize the MLP HLP module.

    Args:
        encoder_config: Configuration for the MLP encoder.
        aggregation: Method used to aggregate node embeddings per hyperedge.
            Defaults to ``"mean"``.
        loss_fn: Optional loss function. Defaults to ``BCEWithLogitsLoss``.
        lr: Learning rate for the optimizer. Defaults to ``0.001``.
        metrics: Optional metric collection for evaluation. Defaults to ``None``.
        metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
            Useful for configuring distributed synchronization behavior
            of ``torchmetrics``. Defaults to ``None``.
    """
    # The encoder outputs node embeddings of shape (num_nodes, out_channels).
    encoder = MLP(
        in_channels=encoder_config["in_channels"],
        hidden_channels=encoder_config.get("hidden_channels"),
        out_channels=encoder_config.get("out_channels", 1),
        num_layers=encoder_config.get("num_layers", 1),
        activation_fn=encoder_config.get("activation_fn"),
        activation_fn_kwargs=encoder_config.get("activation_fn_kwargs"),
        normalization_fn=encoder_config.get("normalization_fn"),
        normalization_fn_kwargs=encoder_config.get("normalization_fn_kwargs"),
        bias=encoder_config.get("bias", True),
        drop_rate=encoder_config.get("drop_rate", 0.0),
    )

    # The decoder takes in the aggregated hyperedge embeddings of shape
    # (num_hyperedges, encoder_config.out_channels)
    # and produces a score for each hyperedge of shape (num_hyperedges, 1).
    decoder = SLP(in_channels=encoder_config.get("out_channels", 1), out_channels=1)

    super().__init__(
        encoder=encoder,
        decoder=decoder,
        loss_fn=loss_fn if loss_fn is not None else nn.BCEWithLogitsLoss(),
        metrics=metrics,
        metrics_log_kwargs=metrics_log_kwargs,
    )

    self.aggregation: Literal["mean", "max", "min", "sum"] = aggregation
    self.lr: float = lr

forward(x, hyperedge_index)

Encode node features, aggregate per hyperedge via mean pooling, and score.

Examples:

Given 4 nodes with 3 features each and 2 hyperedges: >>> x = [[0.1, 0.2, 0.3], # node 0 ... [0.4, 0.5, 0.6], # node 1 ... [0.7, 0.8, 0.9], # node 2 ... [1.0, 1.1, 1.2]] # node 3

>>> # hyperedge 0 = {node 0, node 1, node 2}
>>> # hyperedge 1 = {node 2, node 3}
>>> hyperedge_index = [[0, 1, 2, 2, 3],   # node ids
...                    [0, 0, 0, 1, 1]]   # hyperedge ids

The forward pass:

>>> Encoder maps each node to an embedding vector.
>>> Aggregate embeddings by summing them per hyperedge:
...   - hyperedge 0: emb[0] + emb[1] + emb[2]
...   - hyperedge 1: emb[2] + emb[3]
>>> Sums are divided by the number of nodes per hyperedge (mean pooling):
...   - hyperedge 0: (emb[0] + emb[1] + emb[2]) / 3
...   - hyperedge 1: (emb[2] + emb[3]) / 2
>>> Decoder scores each hyperedge embedding, producing one scalar per hyperedge.

Parameters:

Name Type Description Default
x Tensor

Node feature matrix of shape (num_nodes, in_channels).

required
hyperedge_index Tensor

Hyperedge connectivity of shape (2, num_incidences).

required

Returns:

Name Type Description
scores Tensor

Scores of shape (num_hyperedges,).

Raises:

Type Description
ValueError

If the encoder is not defined for this module.

Source code in hypertorch/hlp/mlp_hlp.py
def forward(self, x: Tensor, hyperedge_index: Tensor) -> Tensor:
    """
    Encode node features, aggregate per hyperedge via mean pooling, and score.

    Examples:
        Given 4 nodes with 3 features each and 2 hyperedges:
            >>> x = [[0.1, 0.2, 0.3],   # node 0
            ...      [0.4, 0.5, 0.6],   # node 1
            ...      [0.7, 0.8, 0.9],   # node 2
            ...      [1.0, 1.1, 1.2]]   # node 3

            >>> # hyperedge 0 = {node 0, node 1, node 2}
            >>> # hyperedge 1 = {node 2, node 3}
            >>> hyperedge_index = [[0, 1, 2, 2, 3],   # node ids
            ...                    [0, 0, 0, 1, 1]]   # hyperedge ids

        The forward pass:

            >>> Encoder maps each node to an embedding vector.
            >>> Aggregate embeddings by summing them per hyperedge:
            ...   - hyperedge 0: emb[0] + emb[1] + emb[2]
            ...   - hyperedge 1: emb[2] + emb[3]
            >>> Sums are divided by the number of nodes per hyperedge (mean pooling):
            ...   - hyperedge 0: (emb[0] + emb[1] + emb[2]) / 3
            ...   - hyperedge 1: (emb[2] + emb[3]) / 2
            >>> Decoder scores each hyperedge embedding, producing one scalar per hyperedge.

    Args:
        x: Node feature matrix of shape ``(num_nodes, in_channels)``.
        hyperedge_index: Hyperedge connectivity of shape ``(2, num_incidences)``.

    Returns:
        scores: Scores of shape ``(num_hyperedges,)``.

    Raises:
        ValueError: If the encoder is not defined for this module.
    """
    if self.encoder is None:
        raise ValueError("Encoder is not defined for this HLP module.")

    # Encode: map each node raw features to an embedding vector.
    # x: (num_nodes, in_channels) -> node_embeddings: (num_nodes, out_channels)
    # Example: in_channels=3, out_channels=2
    #          -> node 0: [0.1, 0.2, 0.3] -> [e00, e01]
    #          -> node 1: [0.4, 0.5, 0.6] -> [e10, e11]
    #          -> node 2: [0.7, 0.8, 0.9] -> [e20, e21]
    #          -> node 3: [1.0, 1.1, 1.2] -> [e30, e31]
    node_embeddings: Tensor = self.encoder(x)

    # Aggregate: for each hyperedge, aggregate the embeddings of its member nodes.
    # Example::
    # - hyperedge 0 contains node 0, 1, 2 -> aggregate([e00, e01], [e10, e11], [e20, e21])
    #                                         -> [pooled_0, pooled_1]
    # - hyperedge 1 contains node 2, 3 -> aggregate([e20, e21], [e30, e31])
    #                                  -> [pooled_0, pooled_1]
    # shape: (num_hyperedges, out_channels)
    hyperedge_embeddings = HyperedgeAggregator(hyperedge_index, node_embeddings).pool(
        self.aggregation,
    )

    # Decode: score each hyperedge embedding, producing one scalar per hyperedge.
    # Example:
    # - hyperedge 0: [pooled_0, pooled_1] -> score_0
    # - hyperedge 1: [pooled_0, pooled_1] -> score_1
    # shape: (2,)
    scores: Tensor = self.decoder(hyperedge_embeddings).squeeze(-1)
    return scores

training_step(batch, batch_idx)

Run a training step.

Parameters:

Name Type Description Default
batch HData

Training batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Training loss.

Source code in hypertorch/hlp/mlp_hlp.py
def training_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a training step.

    Args:
        batch: Training batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Training loss.
    """
    scores = self.forward(batch.x, batch.hyperedge_index)
    target_scores, target_labels = self._target_scores_and_labels(scores, batch)
    batch_size = target_labels.size(0)

    loss = self._compute_loss(target_scores, target_labels, batch_size, Stage.TRAIN)
    self._compute_metrics(target_scores, target_labels, batch_size, Stage.TRAIN)
    return loss

validation_step(batch, batch_idx)

Run a validation step.

Parameters:

Name Type Description Default
batch HData

Validation batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Validation loss.

Source code in hypertorch/hlp/mlp_hlp.py
def validation_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a validation step.

    Args:
        batch: Validation batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Validation loss.
    """
    return self.__eval_step(batch, Stage.VAL)

test_step(batch, batch_idx)

Run a test step.

Parameters:

Name Type Description Default
batch HData

Test batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Test loss.

Source code in hypertorch/hlp/mlp_hlp.py
def test_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a test step.

    Args:
        batch: Test batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Test loss.
    """
    return self.__eval_step(batch, Stage.TEST)

predict_step(batch, batch_idx)

Predict hyperedge scores for a batch.

Parameters:

Name Type Description Default
batch HData

Prediction batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
scores Tensor

Predicted hyperedge scores.

Source code in hypertorch/hlp/mlp_hlp.py
def predict_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Predict hyperedge scores for a batch.

    Args:
        batch: Prediction batch.
        batch_idx: Batch index, unused.

    Returns:
        scores: Predicted hyperedge scores.
    """
    return self.forward(batch.x, batch.hyperedge_index)

configure_optimizers()

Configure the optimizer.

Returns:

Name Type Description
optimizer Adam

Adam optimizer.

Source code in hypertorch/hlp/mlp_hlp.py
def configure_optimizers(self) -> optim.Adam:
    """
    Configure the optimizer.

    Returns:
        optimizer: Adam optimizer.
    """
    return optim.Adam(self.parameters(), lr=self.lr)

__eval_step(batch, stage)

Run shared evaluation logic for a stage.

Parameters:

Name Type Description Default
batch HData

Input batch.

required
stage Stage

Current evaluation stage.

required

Returns:

Name Type Description
loss Tensor

Computed loss.

Source code in hypertorch/hlp/mlp_hlp.py
def __eval_step(self, batch: HData, stage: Stage) -> Tensor:
    """
    Run shared evaluation logic for a stage.

    Args:
        batch: Input batch.
        stage: Current evaluation stage.

    Returns:
        loss: Computed loss.
    """
    scores = self.forward(batch.x, batch.hyperedge_index)
    target_scores, target_labels = self._target_scores_and_labels(scores, batch)
    batch_size = target_labels.size(0)

    loss = self._compute_loss(target_scores, target_labels, batch_size, stage)
    self._compute_metrics(target_scores, target_labels, batch_size, stage)
    return loss

MLPEncoderConfig

Bases: TypedDict

Configuration for the MLP encoder in MLPHlpModule.

Attributes:

Name Type Description
in_channels int

Number of input features per node.

out_channels NotRequired[int]

Number of output features (embedding size) per node.

num_layers NotRequired[int]

Number of layers in the MLP encoder.

hidden_channels NotRequired[int | None]

Optional number of hidden units per layer. If None, no hidden layers are used and the encoder is a simple linear layer.

activation_fn NotRequired[ActivationFn | None]

Optional activation function class to use in the MLP encoder. If None, no activation function is applied.

activation_fn_kwargs NotRequired[dict | None]

Optional dictionary of keyword arguments to pass to the activation function constructor.

normalization_fn NotRequired[NormalizationFn | None]

Optional normalization function class to use in the MLP encoder. If None, no normalization is applied.

normalization_fn_kwargs NotRequired[dict | None]

Optional dictionary of keyword arguments to pass to the normalization function constructor.

bias NotRequired[bool]

Whether to include bias terms in the MLP layers. Defaults to True.

drop_rate NotRequired[float]

Dropout rate to apply after each MLP layer (except the last one). Defaults to 0.0 (no dropout).

Source code in hypertorch/hlp/mlp_hlp.py
class MLPEncoderConfig(TypedDict):
    """
    Configuration for the MLP encoder in MLPHlpModule.

    Attributes:
        in_channels: Number of input features per node.
        out_channels: Number of output features (embedding size) per node.
        num_layers: Number of layers in the MLP encoder.
        hidden_channels: Optional number of hidden units per layer. If ``None``, no hidden layers
            are used and the encoder is a simple linear layer.
        activation_fn: Optional activation function class to use in the MLP encoder.
            If ``None``, no activation function is applied.
        activation_fn_kwargs: Optional dictionary of keyword arguments to pass to the activation
            function constructor.
        normalization_fn: Optional normalization function class to use in the MLP encoder.
            If ``None``, no normalization is applied.
        normalization_fn_kwargs: Optional dictionary of keyword arguments to pass to the
            normalization function constructor.
        bias: Whether to include bias terms in the MLP layers. Defaults to ``True``.
        drop_rate: Dropout rate to apply after each MLP layer (except the last one).
            Defaults to ``0.0`` (no dropout).
    """

    in_channels: int
    out_channels: NotRequired[int]
    num_layers: NotRequired[int]
    hidden_channels: NotRequired[int | None]
    activation_fn: NotRequired[ActivationFn | None]
    activation_fn_kwargs: NotRequired[dict | None]
    normalization_fn: NotRequired[NormalizationFn | None]
    normalization_fn_kwargs: NotRequired[dict | None]
    bias: NotRequired[bool]
    drop_rate: NotRequired[float]

NHPEncoderConfig

Bases: TypedDict

Configuration for the NHP encoder/scorer to be used for hyperedge link prediction.

Attributes:

Name Type Description
in_channels int

Number of input features per node.

hidden_channels NotRequired[int]

Number of hidden channels for incidence embeddings. Defaults to 512.

activation_fn NotRequired[ActivationFn | None]

Optional activation function. Defaults to None.

activation_fn_kwargs NotRequired[dict | None]

Keyword arguments for the activation function. Defaults to None.

aggregation NotRequired[Literal['mean', 'maxmin']]

Hyperedge scoring aggregation. "maxmin" uses the paper's element-wise range representation; "mean" uses mean pooling. Defaults to "maxmin".

bias NotRequired[bool]

Whether to include bias terms. Defaults to True.

Source code in hypertorch/hlp/nhp_hlp.py
class NHPEncoderConfig(TypedDict):
    """
    Configuration for the NHP encoder/scorer to be used for hyperedge link prediction.

    Attributes:
        in_channels: Number of input features per node.
        hidden_channels: Number of hidden channels for incidence embeddings. Defaults to ``512``.
        activation_fn: Optional activation function. Defaults to ``None``.
        activation_fn_kwargs: Keyword arguments for the activation function. Defaults to ``None``.
        aggregation: Hyperedge scoring aggregation. ``"maxmin"`` uses the paper's
            element-wise range representation; ``"mean"`` uses mean pooling.
            Defaults to ``"maxmin"``.
        bias: Whether to include bias terms. Defaults to ``True``.
    """

    in_channels: int
    hidden_channels: NotRequired[int]
    activation_fn: NotRequired[ActivationFn | None]
    activation_fn_kwargs: NotRequired[dict | None]
    aggregation: NotRequired[Literal["mean", "maxmin"]]
    bias: NotRequired[bool]

NHPHlpModule

Bases: HlpModule

A LightningModule for undirected NHP hyperedge link prediction.

NHP encodes and scores candidate hyperedges in a single pass. Unlike encoder wrappers that produce reusable global node embeddings, NHP builds candidate-specific incidence embeddings before pooling and scoring each hyperedge.

Attributes:

Name Type Description
encoder Module | None

NHP scorer inherited from HlpModule.

decoder Module

Identity decoder inherited from HlpModule.

loss_fn Module

Loss function inherited from HlpModule.

metrics_log_kwargs dict[str, Any]

Metric logging keyword arguments inherited from HlpModule.

train_metrics MetricCollection | None

Optional training metrics inherited from HlpModule.

val_metrics MetricCollection | None

Optional validation metrics inherited from HlpModule.

test_metrics MetricCollection | None

Optional test metrics inherited from HlpModule.

lr float

Learning rate for the optimizer. Defaults to 0.001.

weight_decay float

L2 regularization. Defaults to 5e-4.

Source code in hypertorch/hlp/nhp_hlp.py
class NHPHlpModule(HlpModule):
    """
    A LightningModule for undirected NHP hyperedge link prediction.

    NHP encodes and scores candidate hyperedges in a single pass.
    Unlike encoder wrappers that produce reusable global node embeddings,
    NHP builds candidate-specific incidence embeddings before pooling and scoring each hyperedge.

    Attributes:
        encoder: NHP scorer inherited from ``HlpModule``.
        decoder: Identity decoder inherited from ``HlpModule``.
        loss_fn: Loss function inherited from ``HlpModule``.
        metrics_log_kwargs: Metric logging keyword arguments inherited from ``HlpModule``.
        train_metrics: Optional training metrics inherited from ``HlpModule``.
        val_metrics: Optional validation metrics inherited from ``HlpModule``.
        test_metrics: Optional test metrics inherited from ``HlpModule``.
        lr: Learning rate for the optimizer. Defaults to ``0.001``.
        weight_decay: L2 regularization. Defaults to ``5e-4``.
    """

    def __init__(
        self,
        encoder_config: NHPEncoderConfig,
        loss_fn: nn.Module | None = None,
        lr: float = 0.001,
        weight_decay: float = 5e-4,
        metrics: MetricCollection | None = None,
        metrics_log_kwargs: dict[str, Any] | None = None,
    ):
        """
        Initialize the NHP HLP module.

        Args:
            encoder_config: Configuration for the NHP encoder/scorer.
            loss_fn: Optional loss function. Defaults to ``NHPRankingLoss``.
            lr: Learning rate for the optimizer. Defaults to ``0.001``.
            weight_decay: L2 regularization. Defaults to ``5e-4``.
            metrics: Optional metric collection for evaluation. Defaults to ``None``.
            metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
                Useful for configuring distributed synchronization behavior
                of ``torchmetrics``. Defaults to ``None``.
        """
        encoder = NHP(
            in_channels=encoder_config["in_channels"],
            hidden_channels=encoder_config.get("hidden_channels", 512),
            activation_fn=encoder_config.get("activation_fn"),
            activation_fn_kwargs=encoder_config.get("activation_fn_kwargs"),
            aggregation=encoder_config.get("aggregation", "maxmin"),
            bias=encoder_config.get("bias", True),
        )

        super().__init__(
            encoder=encoder,
            decoder=nn.Identity(),
            loss_fn=loss_fn if loss_fn is not None else NHPRankingLoss(),
            metrics=metrics,
            metrics_log_kwargs=metrics_log_kwargs,
        )

        self.lr: float = lr
        self.weight_decay: float = weight_decay

    def forward(self, x: Tensor, hyperedge_index: Tensor) -> Tensor:
        """
        Encode and score each candidate hyperedge.

        Args:
            x: Node feature matrix of shape ``(num_nodes, in_channels)``.
            hyperedge_index: Hyperedge connectivity of shape ``(2, num_incidences)``.

        Returns:
            scores: Scores of shape ``(num_hyperedges,)``.

        Raises:
            ValueError: If the encoder is not defined for this module.
        """
        if self.encoder is None:
            raise ValueError("Encoder is not defined for this HLP module.")
        return self.encoder(x, hyperedge_index)

    def training_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a training step.

        Args:
            batch: Training batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Training loss.
        """
        return self.__eval_step(batch, Stage.TRAIN)

    def validation_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a validation step.

        Args:
            batch: Validation batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Validation loss.
        """
        return self.__eval_step(batch, Stage.VAL)

    def test_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a test step.

        Args:
            batch: Test batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Test loss.
        """
        return self.__eval_step(batch, Stage.TEST)

    def predict_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Predict hyperedge scores for a batch.

        Args:
            batch: Prediction batch.
            batch_idx: Batch index, unused.

        Returns:
            scores: Predicted hyperedge scores.
        """
        return self.forward(batch.x, batch.hyperedge_index)

    def configure_optimizers(self) -> optim.Adam:
        """
        Configure the optimizer.

        Returns:
            optimizer: Adam optimizer.
        """
        return optim.Adam(self.parameters(), lr=self.lr, weight_decay=self.weight_decay)

    def __eval_step(self, batch: HData, stage: Stage) -> Tensor:
        """
        Run shared evaluation logic for a stage.

        Args:
            batch: Input batch.
            stage: Current evaluation stage.

        Returns:
            loss: Computed loss.
        """
        scores = self.forward(batch.x, batch.hyperedge_index)
        target_scores, target_labels = self._target_scores_and_labels(scores, batch)
        batch_size = target_labels.size(0)

        loss = self._compute_loss(target_scores, target_labels, batch_size, stage)
        self._compute_metrics(target_scores, target_labels, batch_size, stage)
        return loss

__init__(encoder_config, loss_fn=None, lr=0.001, weight_decay=0.0005, metrics=None, metrics_log_kwargs=None)

Initialize the NHP HLP module.

Parameters:

Name Type Description Default
encoder_config NHPEncoderConfig

Configuration for the NHP encoder/scorer.

required
loss_fn Module | None

Optional loss function. Defaults to NHPRankingLoss.

None
lr float

Learning rate for the optimizer. Defaults to 0.001.

0.001
weight_decay float

L2 regularization. Defaults to 5e-4.

0.0005
metrics MetricCollection | None

Optional metric collection for evaluation. Defaults to None.

None
metrics_log_kwargs dict[str, Any] | None

Additional keyword arguments passed to metric log calls. Useful for configuring distributed synchronization behavior of torchmetrics. Defaults to None.

None
Source code in hypertorch/hlp/nhp_hlp.py
def __init__(
    self,
    encoder_config: NHPEncoderConfig,
    loss_fn: nn.Module | None = None,
    lr: float = 0.001,
    weight_decay: float = 5e-4,
    metrics: MetricCollection | None = None,
    metrics_log_kwargs: dict[str, Any] | None = None,
):
    """
    Initialize the NHP HLP module.

    Args:
        encoder_config: Configuration for the NHP encoder/scorer.
        loss_fn: Optional loss function. Defaults to ``NHPRankingLoss``.
        lr: Learning rate for the optimizer. Defaults to ``0.001``.
        weight_decay: L2 regularization. Defaults to ``5e-4``.
        metrics: Optional metric collection for evaluation. Defaults to ``None``.
        metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
            Useful for configuring distributed synchronization behavior
            of ``torchmetrics``. Defaults to ``None``.
    """
    encoder = NHP(
        in_channels=encoder_config["in_channels"],
        hidden_channels=encoder_config.get("hidden_channels", 512),
        activation_fn=encoder_config.get("activation_fn"),
        activation_fn_kwargs=encoder_config.get("activation_fn_kwargs"),
        aggregation=encoder_config.get("aggregation", "maxmin"),
        bias=encoder_config.get("bias", True),
    )

    super().__init__(
        encoder=encoder,
        decoder=nn.Identity(),
        loss_fn=loss_fn if loss_fn is not None else NHPRankingLoss(),
        metrics=metrics,
        metrics_log_kwargs=metrics_log_kwargs,
    )

    self.lr: float = lr
    self.weight_decay: float = weight_decay

forward(x, hyperedge_index)

Encode and score each candidate hyperedge.

Parameters:

Name Type Description Default
x Tensor

Node feature matrix of shape (num_nodes, in_channels).

required
hyperedge_index Tensor

Hyperedge connectivity of shape (2, num_incidences).

required

Returns:

Name Type Description
scores Tensor

Scores of shape (num_hyperedges,).

Raises:

Type Description
ValueError

If the encoder is not defined for this module.

Source code in hypertorch/hlp/nhp_hlp.py
def forward(self, x: Tensor, hyperedge_index: Tensor) -> Tensor:
    """
    Encode and score each candidate hyperedge.

    Args:
        x: Node feature matrix of shape ``(num_nodes, in_channels)``.
        hyperedge_index: Hyperedge connectivity of shape ``(2, num_incidences)``.

    Returns:
        scores: Scores of shape ``(num_hyperedges,)``.

    Raises:
        ValueError: If the encoder is not defined for this module.
    """
    if self.encoder is None:
        raise ValueError("Encoder is not defined for this HLP module.")
    return self.encoder(x, hyperedge_index)

training_step(batch, batch_idx)

Run a training step.

Parameters:

Name Type Description Default
batch HData

Training batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Training loss.

Source code in hypertorch/hlp/nhp_hlp.py
def training_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a training step.

    Args:
        batch: Training batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Training loss.
    """
    return self.__eval_step(batch, Stage.TRAIN)

validation_step(batch, batch_idx)

Run a validation step.

Parameters:

Name Type Description Default
batch HData

Validation batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Validation loss.

Source code in hypertorch/hlp/nhp_hlp.py
def validation_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a validation step.

    Args:
        batch: Validation batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Validation loss.
    """
    return self.__eval_step(batch, Stage.VAL)

test_step(batch, batch_idx)

Run a test step.

Parameters:

Name Type Description Default
batch HData

Test batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Test loss.

Source code in hypertorch/hlp/nhp_hlp.py
def test_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a test step.

    Args:
        batch: Test batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Test loss.
    """
    return self.__eval_step(batch, Stage.TEST)

predict_step(batch, batch_idx)

Predict hyperedge scores for a batch.

Parameters:

Name Type Description Default
batch HData

Prediction batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
scores Tensor

Predicted hyperedge scores.

Source code in hypertorch/hlp/nhp_hlp.py
def predict_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Predict hyperedge scores for a batch.

    Args:
        batch: Prediction batch.
        batch_idx: Batch index, unused.

    Returns:
        scores: Predicted hyperedge scores.
    """
    return self.forward(batch.x, batch.hyperedge_index)

configure_optimizers()

Configure the optimizer.

Returns:

Name Type Description
optimizer Adam

Adam optimizer.

Source code in hypertorch/hlp/nhp_hlp.py
def configure_optimizers(self) -> optim.Adam:
    """
    Configure the optimizer.

    Returns:
        optimizer: Adam optimizer.
    """
    return optim.Adam(self.parameters(), lr=self.lr, weight_decay=self.weight_decay)

__eval_step(batch, stage)

Run shared evaluation logic for a stage.

Parameters:

Name Type Description Default
batch HData

Input batch.

required
stage Stage

Current evaluation stage.

required

Returns:

Name Type Description
loss Tensor

Computed loss.

Source code in hypertorch/hlp/nhp_hlp.py
def __eval_step(self, batch: HData, stage: Stage) -> Tensor:
    """
    Run shared evaluation logic for a stage.

    Args:
        batch: Input batch.
        stage: Current evaluation stage.

    Returns:
        loss: Computed loss.
    """
    scores = self.forward(batch.x, batch.hyperedge_index)
    target_scores, target_labels = self._target_scores_and_labels(scores, batch)
    batch_size = target_labels.size(0)

    loss = self._compute_loss(target_scores, target_labels, batch_size, stage)
    self._compute_metrics(target_scores, target_labels, batch_size, stage)
    return loss

NHPRankingLoss

Bases: Module

Ranking loss that pushes positive hyperedges above sampled negatives.

Examples:

>>> logits = [2.0, 1.0, -1.0]
>>> labels = [1.0, 1.0, 0.0]
>>> loss = NHPRankingLoss()
>>> loss(logits, labels)
>>> loss.ndim
... 0
Source code in hypertorch/nn/loss.py
class NHPRankingLoss(nn.Module):
    """
    Ranking loss that pushes positive hyperedges above sampled negatives.

    Examples:
        >>> logits = [2.0, 1.0, -1.0]
        >>> labels = [1.0, 1.0, 0.0]
        >>> loss = NHPRankingLoss()
        >>> loss(logits, labels)
        >>> loss.ndim
        ... 0
    """

    def forward(self, logits: Tensor, labels: Tensor) -> Tensor:
        """
        Compute the ranking loss.

        Args:
            logits: Logit scores for each candidate hyperedge, of shape ``(num_hyperedges,)``.
            labels: Binary labels indicating positive (1) and negative (0) hyperedges, of shape
                ``(num_hyperedges,)``.

        Returns:
            loss: Scalar loss value.

        Raises:
            ValueError: If ``logits`` and ``labels`` do not contain both positive and
                negative hyperedges.
        """
        # Split logits by label as we need to compare positive scores against negative scores.
        # Example: logits = [2.0, 1.0, -1.0]
        #          labels = [1.0, 1.0, 0.0]
        #          -> positive_logits = [2.0, 1.0]
        #          -> negative_logits = [-1.0]
        positive_logits = logits[labels == 1]
        negative_logits = logits[labels == 0]

        positive_scores = torch.sigmoid(positive_logits)
        negative_scores = torch.sigmoid(negative_logits)
        if positive_scores.numel() == 0 or negative_scores.numel() == 0:
            raise ValueError("NHPRankingLoss requires both positive and negative hyperedges.")

        # Objective: enforce that each positive score is higher than the average negative score.
        # For each positive score pos_i:
        #   margin_i = mean(negative_scores) - pos_i
        # We interpret margin_i as follows:
        # - If pos_i > mean(negatives), then margin_i < 0    -> desirable
        # - If pos_i <= mean(negatives), then margin_i >= 0  -> violation
        margins = negative_scores.mean(dtype=torch.float) - positive_scores

        # Then softplus(margin_i):
        # - Is ~0 when margin_i is strongly negative (good ranking).
        # - Grows smoothly when margin_i > 0 (penalizing violations).
        # Final loss is the average over all positive samples.
        return F.softplus(margins).mean(dtype=torch.float)

forward(logits, labels)

Compute the ranking loss.

Parameters:

Name Type Description Default
logits Tensor

Logit scores for each candidate hyperedge, of shape (num_hyperedges,).

required
labels Tensor

Binary labels indicating positive (1) and negative (0) hyperedges, of shape (num_hyperedges,).

required

Returns:

Name Type Description
loss Tensor

Scalar loss value.

Raises:

Type Description
ValueError

If logits and labels do not contain both positive and negative hyperedges.

Source code in hypertorch/nn/loss.py
def forward(self, logits: Tensor, labels: Tensor) -> Tensor:
    """
    Compute the ranking loss.

    Args:
        logits: Logit scores for each candidate hyperedge, of shape ``(num_hyperedges,)``.
        labels: Binary labels indicating positive (1) and negative (0) hyperedges, of shape
            ``(num_hyperedges,)``.

    Returns:
        loss: Scalar loss value.

    Raises:
        ValueError: If ``logits`` and ``labels`` do not contain both positive and
            negative hyperedges.
    """
    # Split logits by label as we need to compare positive scores against negative scores.
    # Example: logits = [2.0, 1.0, -1.0]
    #          labels = [1.0, 1.0, 0.0]
    #          -> positive_logits = [2.0, 1.0]
    #          -> negative_logits = [-1.0]
    positive_logits = logits[labels == 1]
    negative_logits = logits[labels == 0]

    positive_scores = torch.sigmoid(positive_logits)
    negative_scores = torch.sigmoid(negative_logits)
    if positive_scores.numel() == 0 or negative_scores.numel() == 0:
        raise ValueError("NHPRankingLoss requires both positive and negative hyperedges.")

    # Objective: enforce that each positive score is higher than the average negative score.
    # For each positive score pos_i:
    #   margin_i = mean(negative_scores) - pos_i
    # We interpret margin_i as follows:
    # - If pos_i > mean(negatives), then margin_i < 0    -> desirable
    # - If pos_i <= mean(negatives), then margin_i >= 0  -> violation
    margins = negative_scores.mean(dtype=torch.float) - positive_scores

    # Then softplus(margin_i):
    # - Is ~0 when margin_i is strongly negative (good ranking).
    # - Grows smoothly when margin_i > 0 (penalizing violations).
    # Final loss is the average over all positive samples.
    return F.softplus(margins).mean(dtype=torch.float)

Node2VecGCNHlpConfig

Bases: TypedDict

Configuration for the GCN model.

Attributes:

Name Type Description
out_channels int

Dimension of the output node embeddings from the GCN layers.

hidden_channels NotRequired[int]

Dimension of the hidden node embeddings in the GCN layers.

num_layers NotRequired[int]

Number of GCN layers. Must be at least 1. Defaults to 2.

drop_rate NotRequired[float]

Dropout rate applied after each GCN layer (except the last one). Defaults to 0.0 (no dropout).

bias NotRequired[bool]

Whether to include a bias term in the GCN layers. Defaults to True.

improved NotRequired[bool]

Whether to use the improved version of GCNConv. Defaults to False.

add_self_loops NotRequired[bool]

Whether to add self-loops to the input graph. Defaults to True.

normalize NotRequired[bool]

Whether to symmetrically normalize the adjacency matrix in GCNConv. Defaults to True.

cached NotRequired[bool]

Whether to cache the normalized adjacency matrix in GCNConv. Only applicable if the graph structure does not change between epochs. Defaults to False.

graph_reduction_strategy NotRequired[GraphReductionStrategy]

Strategy for reducing the hyperedge graph. Defaults to clique_expansion.

num_nodes NotRequired[int]

Total number of nodes in the hypergraph. This is useful when setting is transductive but train dataset may not contain all hyperedges where some nodes appear, to ensure consistent encoding across splits.

activation_fn NotRequired[ActivationFn]

Activation function to use after each hidden layer. Defaults to nn.ReLU.

activation_fn_kwargs NotRequired[dict]

Keyword arguments for the activation function. Defaults to empty dict.

Source code in hypertorch/hlp/node2vec_common.py
class Node2VecGCNHlpConfig(TypedDict):
    """
    Configuration for the GCN model.

    Attributes:
        out_channels: Dimension of the output node embeddings from the GCN layers.
        hidden_channels: Dimension of the hidden node embeddings in the GCN layers.
        num_layers: Number of GCN layers. Must be at least 1. Defaults to ``2``.
        drop_rate: Dropout rate applied after each GCN layer (except the last one).
            Defaults to ``0.0`` (no dropout).
        bias: Whether to include a bias term in the GCN layers. Defaults to ``True``.
        improved: Whether to use the improved version of GCNConv. Defaults to ``False``.
        add_self_loops: Whether to add self-loops to the input graph. Defaults to ``True``.
        normalize: Whether to symmetrically normalize the adjacency matrix in GCNConv.
            Defaults to ``True``.
        cached: Whether to cache the normalized adjacency matrix in GCNConv.
            Only applicable if the graph structure does not change between epochs.
            Defaults to ``False``.
        graph_reduction_strategy: Strategy for reducing the hyperedge graph.
            Defaults to ``clique_expansion``.
        num_nodes: Total number of nodes in the hypergraph. This is useful when setting is
            transductive but train dataset may not contain all hyperedges where some nodes appear,
            to ensure consistent encoding across splits.
        activation_fn: Activation function to use after each hidden layer.
            Defaults to ``nn.ReLU``.
        activation_fn_kwargs: Keyword arguments for the activation function.
            Defaults to empty dict.
    """

    out_channels: int
    hidden_channels: NotRequired[int]
    num_layers: NotRequired[int]
    drop_rate: NotRequired[float]
    bias: NotRequired[bool]
    improved: NotRequired[bool]
    add_self_loops: NotRequired[bool]
    normalize: NotRequired[bool]
    cached: NotRequired[bool]
    graph_reduction_strategy: NotRequired[GraphReductionStrategy]
    num_nodes: NotRequired[int]
    activation_fn: NotRequired[ActivationFn]
    activation_fn_kwargs: NotRequired[dict]

Node2VecHlpConfig

Bases: TypedDict

Configuration for the Node2Vec encoder.

Attributes:

Name Type Description
context_size NotRequired[int]

Skip-gram context size for Node2Vec. For example, if context_size=2 and walk_length=5, then for a random walk [v0, v1, v2, v3, v4], the context for v2 would be [v0, v1, v3, v4] as we take neighbors within distance 2 in the walk. The pairs generated by skip-gram would be [(v2, v0), (v2, v1), (v2, v3), (v2, v4)]. Rule of thumb: Graphs with strong local structure (5-10), Graphs with communities/long-range patterns (10-20). Defaults to 10.

walk_length NotRequired[int]

Length of each random walk.

num_walks_per_node NotRequired[int]

Number of walks sampled per node.

p NotRequired[float]

Node2Vec return parameter. Controls the probability of stepping back to the node visited in the previous step. Lower values of p make immediate backtracking more likely, while higher values discourage returning to the previous node.

q NotRequired[float]

Node2Vec in-out parameter. Controls whether walks stay near the source node or explore further outward. Lower values of q bias the walk toward DFS-like exploration and structural similarity, while higher values bias it toward BFS-like exploration and local community structure and homophily.

num_negative_samples NotRequired[int]

Number of negative samples per positive walk context. If set to X, then for each positive pair (u, v) generated from the random walks, X negative pairs (u, v_neg) will be generated, where v_neg is a node sampled uniformly at random from all nodes in the graph. Defaults to 1, meaning one negative sample per positive pair.

num_nodes NotRequired[int]

Number of nodes in the stable node space. Defaults to the number of nodes in the hyperedge_index if not provided.

train_hyperedge_index NotRequired[Tensor]

Training hypereddge index used to build the Node2Vec walk graph. Required in joint mode.

graph_reduction_strategy NotRequired[GraphReductionStrategy]

Strategy for reducing the hyperedge graph. Defaults to clique_expansion.

random_walk_batch_size NotRequired[int]

Batch size used by the walk sampler in joint mode.

node2vec_loss_weight NotRequired[float]

Weight applied to the Node2Vec walk loss in joint mode. This is to decide how much the loss of Node2Vec contributes to the overall loss in joint training, relative to the HLP loss. Defaults to 1.0 (equal weighting). Set to a higher value to prioritize learning good node embeddings, or a lower value to prioritize the HLP loss. Ignored in precomputed mode.

sparse NotRequired[bool]

Whether to use sparse gradients in the Node2Vec encoder. Defaults to False.

Source code in hypertorch/hlp/node2vec_common.py
class Node2VecHlpConfig(TypedDict):
    """
    Configuration for the Node2Vec encoder.

    Attributes:
        context_size: Skip-gram context size for Node2Vec.
            For example, if ``context_size=2`` and ``walk_length=5``, then for a
            random walk ``[v0, v1, v2, v3, v4]``,
            the context for ``v2`` would be ``[v0, v1, v3, v4]`` as we take neighbors within
            distance 2 in the walk.
            The pairs generated by skip-gram would be ``[(v2, v0), (v2, v1), (v2, v3), (v2, v4)]``.
            Rule of thumb: Graphs with strong local structure (5-10), Graphs with
            communities/long-range patterns (10-20).
            Defaults to ``10``.
        walk_length: Length of each random walk.
        num_walks_per_node: Number of walks sampled per node.
        p: Node2Vec return parameter. Controls the probability of stepping back to the node visited
            in the previous step. Lower values of ``p`` make immediate backtracking more likely,
            while higher values discourage returning to the previous node.
        q: Node2Vec in-out parameter. Controls whether walks stay near the source node or explore
            further outward. Lower values of ``q`` bias the walk toward DFS-like exploration and
            structural similarity, while higher values bias it toward BFS-like exploration and
            local community structure and homophily.
        num_negative_samples: Number of negative samples per positive walk context.
            If set to ``X``, then for each positive pair ``(u, v)`` generated from the random walks,
            ``X`` negative pairs ``(u, v_neg)`` will be generated,
            where ``v_neg`` is a node sampled uniformly at random from all nodes in the graph.
            Defaults to ``1``, meaning one negative sample per positive pair.
        num_nodes: Number of nodes in the stable node space. Defaults to the number of nodes
            in the ``hyperedge_index`` if not provided.
        train_hyperedge_index: Training hypereddge index used to build the Node2Vec walk graph.
            Required in ``joint`` mode.
        graph_reduction_strategy: Strategy for reducing the hyperedge graph.
            Defaults to ``clique_expansion``.
        random_walk_batch_size: Batch size used by the walk sampler in joint mode.
        node2vec_loss_weight: Weight applied to the Node2Vec walk loss in joint mode.
            This is to decide how much the loss of Node2Vec contributes to the overall loss in
            joint training, relative to the HLP loss.
            Defaults to ``1.0`` (equal weighting). Set to a higher value to prioritize learning
            good node embeddings, or a lower value to prioritize the HLP loss.
            Ignored in precomputed mode.
        sparse: Whether to use sparse gradients in the Node2Vec encoder. Defaults to ``False``.
    """

    context_size: NotRequired[int]
    walk_length: NotRequired[int]
    num_walks_per_node: NotRequired[int]
    p: NotRequired[float]
    q: NotRequired[float]
    num_negative_samples: NotRequired[int]
    num_nodes: NotRequired[int]
    train_hyperedge_index: NotRequired[Tensor]
    graph_reduction_strategy: NotRequired[GraphReductionStrategy]
    random_walk_batch_size: NotRequired[int]
    node2vec_loss_weight: NotRequired[float]
    sparse: NotRequired[bool]

Node2VecGCNEncoderConfig

Bases: TypedDict

Configuration for the Node2Vec encoder in Node2VecGCNHlpModule.

Attributes:

Name Type Description
mode NotRequired[Node2VecMode]

Whether to use precomputed node embeddings from x or train a Node2Vec encoder jointly inside the module.

num_features int

Dimension of the node embeddings consumed by the decoder.

node2vec_config Node2VecHlpConfig

Shared Node2Vec configuration used in joint mode, or metadata for validating precomputed embeddings.

gcn_config Node2VecGCNHlpConfig

Configuration for the GCN layers.

Source code in hypertorch/hlp/node2vecgcn_hlp.py
class Node2VecGCNEncoderConfig(TypedDict):
    """
    Configuration for the Node2Vec encoder in ``Node2VecGCNHlpModule``.

    Attributes:
        mode: Whether to use precomputed node embeddings from ``x`` or train a Node2Vec encoder
            jointly inside the module.
        num_features: Dimension of the node embeddings consumed by the decoder.
        node2vec_config: Shared Node2Vec configuration used in joint mode, or metadata for
            validating precomputed embeddings.
        gcn_config: Configuration for the GCN layers.
    """

    mode: NotRequired[Node2VecMode]
    num_features: int
    node2vec_config: Node2VecHlpConfig
    gcn_config: Node2VecGCNHlpConfig

Node2VecGCNHlpModule

Bases: HlpModule

A LightningModule for Node2Vec-based Hyperedge Link Prediction with GCN encoder.

Supports two modes
  • precomputed: use node embeddings already stored in batch.x.
  • joint: train a Node2Vec encoder jointly with the GCN layers and hyperedge decoder.

Attributes:

Name Type Description
encoder Module | None

Optional Node2Vec-GCN encoder inherited from HlpModule.

decoder Module

SLP decoder module inherited from HlpModule.

loss_fn Module

Loss function inherited from HlpModule.

metrics_log_kwargs dict[str, Any]

Metric logging keyword arguments inherited from HlpModule.

train_metrics MetricCollection | None

Optional training metrics inherited from HlpModule.

val_metrics MetricCollection | None

Optional validation metrics inherited from HlpModule.

test_metrics MetricCollection | None

Optional test metrics inherited from HlpModule.

mode Node2VecMode

Whether to use precomputed or joint Node2Vec embeddings.

embedding_dim int

Node embedding dimension consumed by the GCN stack.

node2vec_hlp_config Node2VecHlpConfig

Node2Vec HLP configuration.

gcn_hlp_config Node2VecGCNHlpConfig

GCN HLP configuration.

precomputed_gcn_encoder GCN | None

GCN encoder used in precomputed mode.

aggregation Literal['mean', 'max', 'min', 'sum']

Method to aggregate node embeddings per hyperedge. Defaults to "mean".

lr float

Learning rate for the optimizer. Defaults to 0.001.

weight_decay float

Weight decay for the optimizer. Defaults to 0.0.

random_walk_batch_size int

Batch size used for Node2Vec walk loss in joint mode. Defaults to 128.

node2vec_loss_weight float

Weight applied to Node2Vec walk loss in joint mode. Defaults to 1.0.

Source code in hypertorch/hlp/node2vecgcn_hlp.py
class Node2VecGCNHlpModule(HlpModule):
    """
    A LightningModule for Node2Vec-based Hyperedge Link Prediction with GCN encoder.

    Supports two modes:
        - ``precomputed``: use node embeddings already stored in ``batch.x``.
        - ``joint``: train a Node2Vec encoder jointly with the GCN layers and hyperedge decoder.

    Attributes:
        encoder: Optional Node2Vec-GCN encoder inherited from ``HlpModule``.
        decoder: SLP decoder module inherited from ``HlpModule``.
        loss_fn: Loss function inherited from ``HlpModule``.
        metrics_log_kwargs: Metric logging keyword arguments inherited from ``HlpModule``.
        train_metrics: Optional training metrics inherited from ``HlpModule``.
        val_metrics: Optional validation metrics inherited from ``HlpModule``.
        test_metrics: Optional test metrics inherited from ``HlpModule``.
        mode: Whether to use precomputed or joint Node2Vec embeddings.
        embedding_dim: Node embedding dimension consumed by the GCN stack.
        node2vec_hlp_config: Node2Vec HLP configuration.
        gcn_hlp_config: GCN HLP configuration.
        precomputed_gcn_encoder: GCN encoder used in precomputed mode.
        aggregation: Method to aggregate node embeddings per hyperedge. Defaults to ``"mean"``.
        lr: Learning rate for the optimizer. Defaults to ``0.001``.
        weight_decay: Weight decay for the optimizer. Defaults to ``0.0``.
        random_walk_batch_size: Batch size used for Node2Vec walk loss in joint mode.
            Defaults to ``128``.
        node2vec_loss_weight: Weight applied to Node2Vec walk loss in joint mode.
            Defaults to ``1.0``.
    """

    def __init__(
        self,
        encoder_config: Node2VecGCNEncoderConfig,
        aggregation: Literal["mean", "max", "min", "sum"] = "mean",
        loss_fn: nn.Module | None = None,
        lr: float = 0.001,
        weight_decay: float = 0.0,
        metrics: MetricCollection | None = None,
        metrics_log_kwargs: dict[str, Any] | None = None,
    ):
        """
        Initialize the Node2Vec-GCN HLP module.

        Args:
            encoder_config: Configuration for Node2Vec embeddings and GCN layers.
            aggregation: Method used to aggregate node embeddings per hyperedge.
                Defaults to ``mean``.
            loss_fn: Optional HLP loss function. Defaults to ``BCEWithLogitsLoss``.
            lr: Learning rate for the optimizer. Defaults to ``0.001``.
            weight_decay: Weight decay for the optimizer. Defaults to ``0.0``.
            metrics: Optional metric collection for evaluation. Defaults to ``None``.
            metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
                Useful for configuring distributed synchronization behavior of
                ``torchmetrics``. Defaults to ``None``.
        """
        self.mode: Node2VecMode = encoder_config.get("mode", NODE2VEC_JOINT_MODE)
        self.embedding_dim: int = encoder_config["num_features"]

        self.node2vec_hlp_config: Node2VecHlpConfig = encoder_config["node2vec_config"]
        self.gcn_hlp_config: Node2VecGCNHlpConfig = encoder_config["gcn_config"]

        node2vecgcn_encoder = (
            self.__build_node2vecgcn_encoder(
                embedding_dim=self.embedding_dim,
                node2vec_config=self.node2vec_hlp_config,
                gcn_config=self.gcn_hlp_config,
                mode=self.mode,
            )
            if self.mode == NODE2VEC_JOINT_MODE
            else None
        )

        decoder = SLP(in_channels=self.gcn_hlp_config["out_channels"], out_channels=1)

        super().__init__(
            encoder=node2vecgcn_encoder,
            decoder=decoder,
            loss_fn=loss_fn if loss_fn is not None else nn.BCEWithLogitsLoss(),
            metrics=metrics,
            metrics_log_kwargs=metrics_log_kwargs,
        )

        self.precomputed_gcn_encoder: GCN | None = (
            self.__build_gcn_encoder(self.embedding_dim, self.gcn_hlp_config)
            if self.mode == NODE2VEC_PRECOMPUTED_MODE
            else None
        )

        self.aggregation: Literal["mean", "max", "min", "sum"] = aggregation
        self.lr: float = lr
        self.weight_decay: float = weight_decay
        self.random_walk_batch_size: int = self.node2vec_hlp_config.get(
            "random_walk_batch_size", 128
        )
        self.node2vec_loss_weight: float = self.node2vec_hlp_config.get("node2vec_loss_weight", 1.0)

        self.__walk_loader_state: Node2VecWalkLoaderState = Node2VecWalkLoaderState()

    def forward(
        self,
        x: Tensor,
        hyperedge_index: Tensor,
        global_node_ids: Tensor | None = None,
    ) -> Tensor:
        """
        Score hyperedges from Node2Vec embeddings refined by GCN.

        Args:
            x: Node feature or precomputed embedding matrix.
            hyperedge_index: Hyperedge incidence tensor.
            global_node_ids: Optional global node IDs for joint Node2Vec lookup.
                Defaults to ``None``.

        Returns:
            scores: Predicted hyperedge scores.

        Raises:
            ValueError: If the configured mode cannot supply node embeddings.
        """
        gcn_edge_index = self.__to_gcn_edge_index(hyperedge_index)

        if self.mode == NODE2VEC_JOINT_MODE:
            encoder = _to_node2vec_encoder(self.encoder, self.mode)
            _validate_global_node_ids(encoder.num_embeddings, global_node_ids, self.mode)
            node_embeddings = encoder(batch=global_node_ids, edge_index=gcn_edge_index)
        else:
            if x.size(1) != self.embedding_dim:
                raise ValueError(
                    f"Expected precomputed node embeddings with dimension "
                    f"{self.embedding_dim}, got {x.size(1)}."
                )
            if self.precomputed_gcn_encoder is None:
                raise ValueError("Precomputed GCN encoder is not initialized.")
            node_embeddings = self.precomputed_gcn_encoder(x, gcn_edge_index)

        hyperedge_embeddings = HyperedgeAggregator(
            hyperedge_index,
            node_embeddings,
        ).pool(self.aggregation)

        return self.decoder(hyperedge_embeddings).squeeze(-1)

    def training_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a training step.

        In joint mode this combines HLP loss with one stochastic Node2Vec walk loss batch.

        Args:
            batch: Training batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Training loss.
        """
        scores = self.forward(batch.x, batch.hyperedge_index, batch.global_node_ids)
        target_scores, target_labels = self._target_scores_and_labels(scores, batch)
        batch_size = target_labels.size(0)

        if self.mode == NODE2VEC_JOINT_MODE:
            positive_random_walk, negative_random_walk = _next_walk_batch(
                mode=self.mode,
                encoder=self.encoder,
                batch_size=self.random_walk_batch_size,
                state=self.__walk_loader_state,
            )
            positive_random_walk = positive_random_walk.to(self.device)
            negative_random_walk = negative_random_walk.to(self.device)

            hlp_loss = self.loss_fn(target_scores, target_labels)
            node2vec_loss = _to_node2vec_encoder(self.encoder, self.mode).loss(
                positive_random_walk, negative_random_walk
            )
            loss = hlp_loss + (self.node2vec_loss_weight * node2vec_loss)

            self.log(
                stage_metric_name(Stage.TRAIN, "hlp_loss"),
                hlp_loss,
                prog_bar=True,
                batch_size=batch_size,
                **self.metrics_log_kwargs,
            )
            self.log(
                stage_metric_name(Stage.TRAIN, "node2vec_loss"),
                node2vec_loss,
                prog_bar=True,
                batch_size=batch_size,
                **self.metrics_log_kwargs,
            )
            self.log(
                stage_metric_name(Stage.TRAIN, "loss"),
                loss,
                prog_bar=True,
                batch_size=batch_size,
                **self.metrics_log_kwargs,
            )
        else:
            loss = self._compute_loss(target_scores, target_labels, batch_size, Stage.TRAIN)

        self._compute_metrics(target_scores, target_labels, batch_size, Stage.TRAIN)
        return loss

    def validation_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a validation step.

        Args:
            batch: Validation batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Validation loss.
        """
        return self.__eval_step(batch, Stage.VAL)

    def test_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a test step.

        Args:
            batch: Test batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Test loss.
        """
        return self.__eval_step(batch, Stage.TEST)

    def predict_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Predict hyperedge scores for a batch.

        Args:
            batch: Prediction batch.
            batch_idx: Batch index, unused.

        Returns:
            scores: Predicted hyperedge scores.
        """
        return self.forward(batch.x, batch.hyperedge_index, batch.global_node_ids)

    def configure_optimizers(self) -> optim.Adam:
        """
        Configure the optimizer.

        Returns:
            optimizer: Adam optimizer.
        """
        return optim.Adam(self.parameters(), lr=self.lr, weight_decay=self.weight_decay)

    def __build_gcn_encoder(self, embedding_dim: int, gcn_config: Node2VecGCNHlpConfig) -> GCN:
        """
        Build a GCN encoder for precomputed mode.

        Args:
            embedding_dim: Input embedding dimension.
            gcn_config: HLP-side GCN configuration.

        Returns:
            encoder: GCN encoder.
        """
        return GCN(**_to_gcn_config(embedding_dim, gcn_config))

    def __build_node2vecgcn_encoder(
        self,
        embedding_dim: int,
        node2vec_config: Node2VecHlpConfig,
        gcn_config: Node2VecGCNHlpConfig,
        mode: Node2VecMode,
    ) -> Node2VecGCN:
        """
        Build the joint Node2Vec-GCN encoder.

        Args:
            embedding_dim: Node2Vec embedding dimension.
            node2vec_config: Node2Vec HLP configuration.
            gcn_config: HLP-side GCN configuration.
            mode: Node2Vec training mode used in validation errors.

        Returns:
            encoder: Joint Node2Vec-GCN encoder.
        """
        _validate_walk_length_and_context_size(
            walk_length=node2vec_config.get("walk_length", 20),
            context_size=node2vec_config.get("context_size", 10),
        )

        edge_index, num_nodes = _to_node2vec_edge_index(node2vec_config, mode)

        model_node2vec_config: Node2VecConfig = {
            "edge_index": edge_index,
            "embedding_dim": embedding_dim,
            "walk_length": node2vec_config.get("walk_length", 20),
            "context_size": node2vec_config.get("context_size", 10),
            "num_walks_per_node": node2vec_config.get("num_walks_per_node", 10),
            "p": node2vec_config.get("p", 1.0),
            "q": node2vec_config.get("q", 1.0),
            "num_negative_samples": node2vec_config.get("num_negative_samples", 1),
            "num_nodes": num_nodes,
            "sparse": node2vec_config.get("sparse", False),
        }

        return Node2VecGCN(
            node2vec_config=model_node2vec_config,
            gcn_config=_to_gcn_config(embedding_dim, gcn_config),
        )

    def __eval_step(self, batch: HData, stage: Stage) -> Tensor:
        """
        Run shared evaluation logic for a stage.

        Args:
            batch: Input batch.
            stage: Current evaluation stage.

        Returns:
            loss: Computed loss.
        """
        scores = self.forward(batch.x, batch.hyperedge_index, batch.global_node_ids)
        target_scores, target_labels = self._target_scores_and_labels(scores, batch)
        batch_size = target_labels.size(0)

        loss = self._compute_loss(target_scores, target_labels, batch_size, stage)
        self._compute_metrics(target_scores, target_labels, batch_size, stage)
        return loss

    def __to_gcn_edge_index(self, hyperedge_index: Tensor) -> Tensor:
        """
        Reduce a hyperedge index to the graph edge index used by GCN.

        Args:
            hyperedge_index: Hyperedge incidence tensor.

        Returns:
            edge_index: Reduced graph edge index without self-loops.
        """
        graph_reduction_strategy = self.gcn_hlp_config.get(
            "graph_reduction_strategy", GraphReductionStrategyEnum.CLIQUE_EXPANSION
        )
        reduced_gcn_edge_index = HyperedgeIndex(hyperedge_index).reduce(
            strategy=graph_reduction_strategy,
            num_nodes=self.gcn_hlp_config.get("num_nodes"),
        )
        return EdgeIndex(reduced_gcn_edge_index).remove_selfloops().item

__init__(encoder_config, aggregation='mean', loss_fn=None, lr=0.001, weight_decay=0.0, metrics=None, metrics_log_kwargs=None)

Initialize the Node2Vec-GCN HLP module.

Parameters:

Name Type Description Default
encoder_config Node2VecGCNEncoderConfig

Configuration for Node2Vec embeddings and GCN layers.

required
aggregation Literal['mean', 'max', 'min', 'sum']

Method used to aggregate node embeddings per hyperedge. Defaults to mean.

'mean'
loss_fn Module | None

Optional HLP loss function. Defaults to BCEWithLogitsLoss.

None
lr float

Learning rate for the optimizer. Defaults to 0.001.

0.001
weight_decay float

Weight decay for the optimizer. Defaults to 0.0.

0.0
metrics MetricCollection | None

Optional metric collection for evaluation. Defaults to None.

None
metrics_log_kwargs dict[str, Any] | None

Additional keyword arguments passed to metric log calls. Useful for configuring distributed synchronization behavior of torchmetrics. Defaults to None.

None
Source code in hypertorch/hlp/node2vecgcn_hlp.py
def __init__(
    self,
    encoder_config: Node2VecGCNEncoderConfig,
    aggregation: Literal["mean", "max", "min", "sum"] = "mean",
    loss_fn: nn.Module | None = None,
    lr: float = 0.001,
    weight_decay: float = 0.0,
    metrics: MetricCollection | None = None,
    metrics_log_kwargs: dict[str, Any] | None = None,
):
    """
    Initialize the Node2Vec-GCN HLP module.

    Args:
        encoder_config: Configuration for Node2Vec embeddings and GCN layers.
        aggregation: Method used to aggregate node embeddings per hyperedge.
            Defaults to ``mean``.
        loss_fn: Optional HLP loss function. Defaults to ``BCEWithLogitsLoss``.
        lr: Learning rate for the optimizer. Defaults to ``0.001``.
        weight_decay: Weight decay for the optimizer. Defaults to ``0.0``.
        metrics: Optional metric collection for evaluation. Defaults to ``None``.
        metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
            Useful for configuring distributed synchronization behavior of
            ``torchmetrics``. Defaults to ``None``.
    """
    self.mode: Node2VecMode = encoder_config.get("mode", NODE2VEC_JOINT_MODE)
    self.embedding_dim: int = encoder_config["num_features"]

    self.node2vec_hlp_config: Node2VecHlpConfig = encoder_config["node2vec_config"]
    self.gcn_hlp_config: Node2VecGCNHlpConfig = encoder_config["gcn_config"]

    node2vecgcn_encoder = (
        self.__build_node2vecgcn_encoder(
            embedding_dim=self.embedding_dim,
            node2vec_config=self.node2vec_hlp_config,
            gcn_config=self.gcn_hlp_config,
            mode=self.mode,
        )
        if self.mode == NODE2VEC_JOINT_MODE
        else None
    )

    decoder = SLP(in_channels=self.gcn_hlp_config["out_channels"], out_channels=1)

    super().__init__(
        encoder=node2vecgcn_encoder,
        decoder=decoder,
        loss_fn=loss_fn if loss_fn is not None else nn.BCEWithLogitsLoss(),
        metrics=metrics,
        metrics_log_kwargs=metrics_log_kwargs,
    )

    self.precomputed_gcn_encoder: GCN | None = (
        self.__build_gcn_encoder(self.embedding_dim, self.gcn_hlp_config)
        if self.mode == NODE2VEC_PRECOMPUTED_MODE
        else None
    )

    self.aggregation: Literal["mean", "max", "min", "sum"] = aggregation
    self.lr: float = lr
    self.weight_decay: float = weight_decay
    self.random_walk_batch_size: int = self.node2vec_hlp_config.get(
        "random_walk_batch_size", 128
    )
    self.node2vec_loss_weight: float = self.node2vec_hlp_config.get("node2vec_loss_weight", 1.0)

    self.__walk_loader_state: Node2VecWalkLoaderState = Node2VecWalkLoaderState()

forward(x, hyperedge_index, global_node_ids=None)

Score hyperedges from Node2Vec embeddings refined by GCN.

Parameters:

Name Type Description Default
x Tensor

Node feature or precomputed embedding matrix.

required
hyperedge_index Tensor

Hyperedge incidence tensor.

required
global_node_ids Tensor | None

Optional global node IDs for joint Node2Vec lookup. Defaults to None.

None

Returns:

Name Type Description
scores Tensor

Predicted hyperedge scores.

Raises:

Type Description
ValueError

If the configured mode cannot supply node embeddings.

Source code in hypertorch/hlp/node2vecgcn_hlp.py
def forward(
    self,
    x: Tensor,
    hyperedge_index: Tensor,
    global_node_ids: Tensor | None = None,
) -> Tensor:
    """
    Score hyperedges from Node2Vec embeddings refined by GCN.

    Args:
        x: Node feature or precomputed embedding matrix.
        hyperedge_index: Hyperedge incidence tensor.
        global_node_ids: Optional global node IDs for joint Node2Vec lookup.
            Defaults to ``None``.

    Returns:
        scores: Predicted hyperedge scores.

    Raises:
        ValueError: If the configured mode cannot supply node embeddings.
    """
    gcn_edge_index = self.__to_gcn_edge_index(hyperedge_index)

    if self.mode == NODE2VEC_JOINT_MODE:
        encoder = _to_node2vec_encoder(self.encoder, self.mode)
        _validate_global_node_ids(encoder.num_embeddings, global_node_ids, self.mode)
        node_embeddings = encoder(batch=global_node_ids, edge_index=gcn_edge_index)
    else:
        if x.size(1) != self.embedding_dim:
            raise ValueError(
                f"Expected precomputed node embeddings with dimension "
                f"{self.embedding_dim}, got {x.size(1)}."
            )
        if self.precomputed_gcn_encoder is None:
            raise ValueError("Precomputed GCN encoder is not initialized.")
        node_embeddings = self.precomputed_gcn_encoder(x, gcn_edge_index)

    hyperedge_embeddings = HyperedgeAggregator(
        hyperedge_index,
        node_embeddings,
    ).pool(self.aggregation)

    return self.decoder(hyperedge_embeddings).squeeze(-1)

training_step(batch, batch_idx)

Run a training step.

In joint mode this combines HLP loss with one stochastic Node2Vec walk loss batch.

Parameters:

Name Type Description Default
batch HData

Training batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Training loss.

Source code in hypertorch/hlp/node2vecgcn_hlp.py
def training_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a training step.

    In joint mode this combines HLP loss with one stochastic Node2Vec walk loss batch.

    Args:
        batch: Training batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Training loss.
    """
    scores = self.forward(batch.x, batch.hyperedge_index, batch.global_node_ids)
    target_scores, target_labels = self._target_scores_and_labels(scores, batch)
    batch_size = target_labels.size(0)

    if self.mode == NODE2VEC_JOINT_MODE:
        positive_random_walk, negative_random_walk = _next_walk_batch(
            mode=self.mode,
            encoder=self.encoder,
            batch_size=self.random_walk_batch_size,
            state=self.__walk_loader_state,
        )
        positive_random_walk = positive_random_walk.to(self.device)
        negative_random_walk = negative_random_walk.to(self.device)

        hlp_loss = self.loss_fn(target_scores, target_labels)
        node2vec_loss = _to_node2vec_encoder(self.encoder, self.mode).loss(
            positive_random_walk, negative_random_walk
        )
        loss = hlp_loss + (self.node2vec_loss_weight * node2vec_loss)

        self.log(
            stage_metric_name(Stage.TRAIN, "hlp_loss"),
            hlp_loss,
            prog_bar=True,
            batch_size=batch_size,
            **self.metrics_log_kwargs,
        )
        self.log(
            stage_metric_name(Stage.TRAIN, "node2vec_loss"),
            node2vec_loss,
            prog_bar=True,
            batch_size=batch_size,
            **self.metrics_log_kwargs,
        )
        self.log(
            stage_metric_name(Stage.TRAIN, "loss"),
            loss,
            prog_bar=True,
            batch_size=batch_size,
            **self.metrics_log_kwargs,
        )
    else:
        loss = self._compute_loss(target_scores, target_labels, batch_size, Stage.TRAIN)

    self._compute_metrics(target_scores, target_labels, batch_size, Stage.TRAIN)
    return loss

validation_step(batch, batch_idx)

Run a validation step.

Parameters:

Name Type Description Default
batch HData

Validation batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Validation loss.

Source code in hypertorch/hlp/node2vecgcn_hlp.py
def validation_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a validation step.

    Args:
        batch: Validation batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Validation loss.
    """
    return self.__eval_step(batch, Stage.VAL)

test_step(batch, batch_idx)

Run a test step.

Parameters:

Name Type Description Default
batch HData

Test batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Test loss.

Source code in hypertorch/hlp/node2vecgcn_hlp.py
def test_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a test step.

    Args:
        batch: Test batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Test loss.
    """
    return self.__eval_step(batch, Stage.TEST)

predict_step(batch, batch_idx)

Predict hyperedge scores for a batch.

Parameters:

Name Type Description Default
batch HData

Prediction batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
scores Tensor

Predicted hyperedge scores.

Source code in hypertorch/hlp/node2vecgcn_hlp.py
def predict_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Predict hyperedge scores for a batch.

    Args:
        batch: Prediction batch.
        batch_idx: Batch index, unused.

    Returns:
        scores: Predicted hyperedge scores.
    """
    return self.forward(batch.x, batch.hyperedge_index, batch.global_node_ids)

configure_optimizers()

Configure the optimizer.

Returns:

Name Type Description
optimizer Adam

Adam optimizer.

Source code in hypertorch/hlp/node2vecgcn_hlp.py
def configure_optimizers(self) -> optim.Adam:
    """
    Configure the optimizer.

    Returns:
        optimizer: Adam optimizer.
    """
    return optim.Adam(self.parameters(), lr=self.lr, weight_decay=self.weight_decay)

__build_gcn_encoder(embedding_dim, gcn_config)

Build a GCN encoder for precomputed mode.

Parameters:

Name Type Description Default
embedding_dim int

Input embedding dimension.

required
gcn_config Node2VecGCNHlpConfig

HLP-side GCN configuration.

required

Returns:

Name Type Description
encoder GCN

GCN encoder.

Source code in hypertorch/hlp/node2vecgcn_hlp.py
def __build_gcn_encoder(self, embedding_dim: int, gcn_config: Node2VecGCNHlpConfig) -> GCN:
    """
    Build a GCN encoder for precomputed mode.

    Args:
        embedding_dim: Input embedding dimension.
        gcn_config: HLP-side GCN configuration.

    Returns:
        encoder: GCN encoder.
    """
    return GCN(**_to_gcn_config(embedding_dim, gcn_config))

__build_node2vecgcn_encoder(embedding_dim, node2vec_config, gcn_config, mode)

Build the joint Node2Vec-GCN encoder.

Parameters:

Name Type Description Default
embedding_dim int

Node2Vec embedding dimension.

required
node2vec_config Node2VecHlpConfig

Node2Vec HLP configuration.

required
gcn_config Node2VecGCNHlpConfig

HLP-side GCN configuration.

required
mode Node2VecMode

Node2Vec training mode used in validation errors.

required

Returns:

Name Type Description
encoder Node2VecGCN

Joint Node2Vec-GCN encoder.

Source code in hypertorch/hlp/node2vecgcn_hlp.py
def __build_node2vecgcn_encoder(
    self,
    embedding_dim: int,
    node2vec_config: Node2VecHlpConfig,
    gcn_config: Node2VecGCNHlpConfig,
    mode: Node2VecMode,
) -> Node2VecGCN:
    """
    Build the joint Node2Vec-GCN encoder.

    Args:
        embedding_dim: Node2Vec embedding dimension.
        node2vec_config: Node2Vec HLP configuration.
        gcn_config: HLP-side GCN configuration.
        mode: Node2Vec training mode used in validation errors.

    Returns:
        encoder: Joint Node2Vec-GCN encoder.
    """
    _validate_walk_length_and_context_size(
        walk_length=node2vec_config.get("walk_length", 20),
        context_size=node2vec_config.get("context_size", 10),
    )

    edge_index, num_nodes = _to_node2vec_edge_index(node2vec_config, mode)

    model_node2vec_config: Node2VecConfig = {
        "edge_index": edge_index,
        "embedding_dim": embedding_dim,
        "walk_length": node2vec_config.get("walk_length", 20),
        "context_size": node2vec_config.get("context_size", 10),
        "num_walks_per_node": node2vec_config.get("num_walks_per_node", 10),
        "p": node2vec_config.get("p", 1.0),
        "q": node2vec_config.get("q", 1.0),
        "num_negative_samples": node2vec_config.get("num_negative_samples", 1),
        "num_nodes": num_nodes,
        "sparse": node2vec_config.get("sparse", False),
    }

    return Node2VecGCN(
        node2vec_config=model_node2vec_config,
        gcn_config=_to_gcn_config(embedding_dim, gcn_config),
    )

__eval_step(batch, stage)

Run shared evaluation logic for a stage.

Parameters:

Name Type Description Default
batch HData

Input batch.

required
stage Stage

Current evaluation stage.

required

Returns:

Name Type Description
loss Tensor

Computed loss.

Source code in hypertorch/hlp/node2vecgcn_hlp.py
def __eval_step(self, batch: HData, stage: Stage) -> Tensor:
    """
    Run shared evaluation logic for a stage.

    Args:
        batch: Input batch.
        stage: Current evaluation stage.

    Returns:
        loss: Computed loss.
    """
    scores = self.forward(batch.x, batch.hyperedge_index, batch.global_node_ids)
    target_scores, target_labels = self._target_scores_and_labels(scores, batch)
    batch_size = target_labels.size(0)

    loss = self._compute_loss(target_scores, target_labels, batch_size, stage)
    self._compute_metrics(target_scores, target_labels, batch_size, stage)
    return loss

__to_gcn_edge_index(hyperedge_index)

Reduce a hyperedge index to the graph edge index used by GCN.

Parameters:

Name Type Description Default
hyperedge_index Tensor

Hyperedge incidence tensor.

required

Returns:

Name Type Description
edge_index Tensor

Reduced graph edge index without self-loops.

Source code in hypertorch/hlp/node2vecgcn_hlp.py
def __to_gcn_edge_index(self, hyperedge_index: Tensor) -> Tensor:
    """
    Reduce a hyperedge index to the graph edge index used by GCN.

    Args:
        hyperedge_index: Hyperedge incidence tensor.

    Returns:
        edge_index: Reduced graph edge index without self-loops.
    """
    graph_reduction_strategy = self.gcn_hlp_config.get(
        "graph_reduction_strategy", GraphReductionStrategyEnum.CLIQUE_EXPANSION
    )
    reduced_gcn_edge_index = HyperedgeIndex(hyperedge_index).reduce(
        strategy=graph_reduction_strategy,
        num_nodes=self.gcn_hlp_config.get("num_nodes"),
    )
    return EdgeIndex(reduced_gcn_edge_index).remove_selfloops().item

Node2VecSLPEncoderConfig

Bases: TypedDict

Configuration for the Node2Vec encoder in Node2VecSLPHlpModule.

Attributes:

Name Type Description
mode NotRequired[Node2VecMode]

Whether to use precomputed node embeddings from x or train a Node2Vec encoder jointly inside the module. Defaults to "joint".

num_features int

Dimension of the node embeddings consumed by the decoder.

node2vec_config Node2VecHlpConfig

Shared Node2Vec configuration used in joint mode, or metadata for validating precomputed embeddings.

Source code in hypertorch/hlp/node2vecslp_hlp.py
class Node2VecSLPEncoderConfig(TypedDict):
    """
    Configuration for the Node2Vec encoder in ``Node2VecSLPHlpModule``.

    Attributes:
        mode: Whether to use precomputed node embeddings from ``x`` or train a Node2Vec encoder
            jointly inside the module. Defaults to ``"joint"``.
        num_features: Dimension of the node embeddings consumed by the decoder.
        node2vec_config: Shared Node2Vec configuration used in joint mode, or metadata for
            validating precomputed embeddings.
    """

    mode: NotRequired[Node2VecMode]
    num_features: int
    node2vec_config: Node2VecHlpConfig

Node2VecSLPHlpModule

Bases: HlpModule

A LightningModule for Node2Vec-based Hyperedge Link Prediction.

Supports two modes
  • precomputed: use node embeddings already stored in batch.x.
  • joint: train a Node2Vec encoder jointly with the hyperedge decoder.

Attributes:

Name Type Description
encoder Module | None

Optional Node2Vec encoder inherited from HlpModule.

decoder Module

SLP decoder module inherited from HlpModule.

loss_fn Module

Loss function inherited from HlpModule.

metrics_log_kwargs dict[str, Any]

Metric logging keyword arguments inherited from HlpModule.

train_metrics MetricCollection | None

Optional training metrics inherited from HlpModule.

val_metrics MetricCollection | None

Optional validation metrics inherited from HlpModule.

test_metrics MetricCollection | None

Optional test metrics inherited from HlpModule.

mode Node2VecMode

Whether to use precomputed or joint Node2Vec embeddings.

embedding_dim int

Node embedding dimension consumed by the decoder.

aggregation Literal['mean', 'max', 'min', 'sum']

Method to aggregate node embeddings per hyperedge. Defaults to mean.

lr float

Learning rate for the optimizer. Defaults to 0.001.

weight_decay float

Weight decay for the optimizer. Defaults to 0.0.

random_walk_batch_size int

Batch size used for Node2Vec walk loss in joint mode. Defaults to 128.

node2vec_loss_weight float

Weight applied to Node2Vec walk loss in joint mode. Defaults to 1.0.

Source code in hypertorch/hlp/node2vecslp_hlp.py
class Node2VecSLPHlpModule(HlpModule):
    """
    A LightningModule for Node2Vec-based Hyperedge Link Prediction.

    Supports two modes:
        - ``precomputed``: use node embeddings already stored in ``batch.x``.
        - ``joint``: train a Node2Vec encoder jointly with the hyperedge decoder.

    Attributes:
        encoder: Optional Node2Vec encoder inherited from ``HlpModule``.
        decoder: SLP decoder module inherited from ``HlpModule``.
        loss_fn: Loss function inherited from ``HlpModule``.
        metrics_log_kwargs: Metric logging keyword arguments inherited from ``HlpModule``.
        train_metrics: Optional training metrics inherited from ``HlpModule``.
        val_metrics: Optional validation metrics inherited from ``HlpModule``.
        test_metrics: Optional test metrics inherited from ``HlpModule``.
        mode: Whether to use precomputed or joint Node2Vec embeddings.
        embedding_dim: Node embedding dimension consumed by the decoder.
        aggregation: Method to aggregate node embeddings per hyperedge.
            Defaults to ``mean``.
        lr: Learning rate for the optimizer. Defaults to ``0.001``.
        weight_decay: Weight decay for the optimizer. Defaults to ``0.0``.
        random_walk_batch_size: Batch size used for Node2Vec walk loss in joint mode.
            Defaults to ``128``.
        node2vec_loss_weight: Weight applied to Node2Vec walk loss in joint mode.
            Defaults to ``1.0``.
    """

    def __init__(
        self,
        encoder_config: Node2VecSLPEncoderConfig,
        aggregation: Literal["mean", "max", "min", "sum"] = "mean",
        loss_fn: nn.Module | None = None,
        lr: float = 0.001,
        weight_decay: float = 0.0,
        metrics: MetricCollection | None = None,
        metrics_log_kwargs: dict[str, Any] | None = None,
    ):
        """
        Initialize the Node2Vec-SLP HLP module.

        Args:
            encoder_config: Configuration for Node2Vec embeddings.
            aggregation: Method used to aggregate node embeddings per hyperedge.
                Defaults to ``mean``.
            loss_fn: Optional HLP loss function. Defaults to ``BCEWithLogitsLoss``.
            lr: Learning rate for the optimizer. Defaults to ``0.001``.
            weight_decay: Weight decay for the optimizer. Defaults to ``0.0``.
            metrics: Optional metric collection for evaluation. Defaults to ``None``.
            metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
                Useful for configuring distributed synchronization behavior of
                ``torchmetrics``. Defaults to ``None``.
        """
        self.mode: Node2VecMode = encoder_config.get("mode", NODE2VEC_JOINT_MODE)
        self.embedding_dim: int = encoder_config["num_features"]
        node2vec_config = encoder_config["node2vec_config"]

        encoder = (
            self.__build_node2vec_encoder(self.embedding_dim, node2vec_config, self.mode)
            if self.mode == NODE2VEC_JOINT_MODE
            else None
        )

        decoder = SLP(in_channels=self.embedding_dim, out_channels=1)

        super().__init__(
            encoder=encoder,
            decoder=decoder,
            loss_fn=loss_fn if loss_fn is not None else nn.BCEWithLogitsLoss(),
            metrics=metrics,
            metrics_log_kwargs=metrics_log_kwargs,
        )

        self.aggregation: Literal["mean", "max", "min", "sum"] = aggregation
        self.lr: float = lr
        self.weight_decay: float = weight_decay
        self.random_walk_batch_size: int = node2vec_config.get("random_walk_batch_size", 128)
        self.node2vec_loss_weight: float = node2vec_config.get("node2vec_loss_weight", 1.0)

        self.__walk_loader_state: Node2VecWalkLoaderState = Node2VecWalkLoaderState()

    def forward(
        self,
        x: Tensor,
        hyperedge_index: Tensor,
        global_node_ids: Tensor | None = None,
    ) -> Tensor:
        """
        Score hyperedges from precomputed or jointly trained Node2Vec embeddings.

        Args:
            x: Node feature or precomputed embedding matrix.
            hyperedge_index: Hyperedge incidence tensor.
            global_node_ids: Optional global node IDs for joint Node2Vec lookup.
                Defaults to ``None``.

        Returns:
            scores: Predicted hyperedge scores.

        Raises:
            ValueError: If the configured mode cannot supply node embeddings.
        """
        # Encode: get node embeddings from precomputation or joint encoder
        if self.mode == NODE2VEC_JOINT_MODE:
            encoder = _to_node2vec_encoder(self.encoder, self.mode)
            _validate_global_node_ids(encoder.num_embeddings, global_node_ids, self.mode)
            node_embeddings = encoder(batch=global_node_ids)
        else:
            if x.size(1) != self.embedding_dim:
                raise ValueError(
                    f"Expected precomputed node embeddings with dimension "
                    f"{self.embedding_dim}, got {x.size(1)}."
                )
            node_embeddings = x

        # Aggregate: pool node embeddings per hyperedge
        # shape: (num_hyperedges, embedding_dim)
        hyperedge_embeddings = HyperedgeAggregator(hyperedge_index, node_embeddings).pool(
            self.aggregation
        )

        # Decode: linear projection to scalar score per hyperedge
        # shape: (num_hyperedges, 1) -> squeeze -> (num_hyperedges,)
        scores: Tensor = self.decoder(hyperedge_embeddings).squeeze(-1)
        return scores

    def training_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a training step.
        In joint mode this combines HLP loss with one stochastic Node2Vec walk loss batch.

        Args:
            batch: Training batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Training loss.
        """
        scores = self.forward(batch.x, batch.hyperedge_index, batch.global_node_ids)
        target_scores, target_labels = self._target_scores_and_labels(scores, batch)
        batch_size = target_labels.size(0)

        if self.mode == NODE2VEC_JOINT_MODE:
            # Node2Vec.loss() is already a stochastic objective over sampled walks,
            # so one walk batch is a standard SGD estimate, not a logically different loss,
            # meaning we can optimize training by using a single walk batch per training step,
            # instead of averaging over multiple walk batches.
            positive_random_walk, negative_random_walk = _next_walk_batch(
                mode=self.mode,
                encoder=self.encoder,
                batch_size=self.random_walk_batch_size,
                state=self.__walk_loader_state,
            )
            positive_random_walk = positive_random_walk.to(self.device)
            negative_random_walk = negative_random_walk.to(self.device)

            hlp_loss = self.loss_fn(target_scores, target_labels)
            node2vec_loss = _to_node2vec_encoder(self.encoder, self.mode).loss(
                positive_random_walk,
                negative_random_walk,
            )
            loss = hlp_loss + (self.node2vec_loss_weight * node2vec_loss)

            self.log(
                stage_metric_name(Stage.TRAIN, "hlp_loss"),
                hlp_loss,
                prog_bar=True,
                batch_size=batch_size,
                **self.metrics_log_kwargs,
            )
            self.log(
                stage_metric_name(Stage.TRAIN, "node2vec_loss"),
                node2vec_loss,
                prog_bar=True,
                batch_size=batch_size,
                **self.metrics_log_kwargs,
            )
            self.log(
                stage_metric_name(Stage.TRAIN, "loss"),
                loss,
                prog_bar=True,
                batch_size=batch_size,
                **self.metrics_log_kwargs,
            )
        else:
            loss = self._compute_loss(target_scores, target_labels, batch_size, Stage.TRAIN)

        self._compute_metrics(target_scores, target_labels, batch_size, Stage.TRAIN)
        return loss

    def validation_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a validation step.

        Args:
            batch: Validation batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Validation loss.
        """
        return self.__eval_step(batch, Stage.VAL)

    def test_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a test step.

        Args:
            batch: Test batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Test loss.
        """
        return self.__eval_step(batch, Stage.TEST)

    def predict_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Predict hyperedge scores for a batch.

        Args:
            batch: Prediction batch.
            batch_idx: Batch index, unused.

        Returns:
            scores: Predicted hyperedge scores.
        """
        return self.forward(batch.x, batch.hyperedge_index, batch.global_node_ids)

    def configure_optimizers(self) -> optim.Adam:
        """
        Configure the optimizer.

        Returns:
            optimizer: Adam optimizer.
        """
        return optim.Adam(self.parameters(), lr=self.lr, weight_decay=self.weight_decay)

    def __build_node2vec_encoder(
        self,
        embedding_dim: int,
        node2vec_config: Node2VecHlpConfig,
        mode: Node2VecMode,
    ) -> Node2Vec:
        """
        Build the joint-mode Node2Vec encoder.

        Args:
            embedding_dim: Node2Vec embedding dimension.
            node2vec_config: Node2Vec HLP configuration.
            mode: Node2Vec training mode used in validation errors.

        Returns:
            encoder: Node2Vec encoder.
        """
        _validate_walk_length_and_context_size(
            walk_length=node2vec_config.get("walk_length", 20),
            context_size=node2vec_config.get("context_size", 10),
        )

        edge_index, num_nodes = _to_node2vec_edge_index(node2vec_config, mode)

        return Node2Vec(
            edge_index=edge_index,
            embedding_dim=embedding_dim,
            walk_length=node2vec_config.get("walk_length", 20),
            context_size=node2vec_config.get("context_size", 10),
            num_walks_per_node=node2vec_config.get("num_walks_per_node", 10),
            p=node2vec_config.get("p", 1.0),
            q=node2vec_config.get("q", 1.0),
            num_negative_samples=node2vec_config.get("num_negative_samples", 1),
            num_nodes=num_nodes,
            sparse=node2vec_config.get("sparse", False),
        )

    def __eval_step(self, batch: HData, stage: Stage) -> Tensor:
        """
        Run shared evaluation logic for a stage.

        Args:
            batch: Input batch.
            stage: Current evaluation stage.

        Returns:
            loss: Computed loss.
        """
        scores = self.forward(batch.x, batch.hyperedge_index, batch.global_node_ids)
        target_scores, target_labels = self._target_scores_and_labels(scores, batch)
        batch_size = target_labels.size(0)

        loss = self._compute_loss(target_scores, target_labels, batch_size, stage)
        self._compute_metrics(target_scores, target_labels, batch_size, stage)
        return loss

__init__(encoder_config, aggregation='mean', loss_fn=None, lr=0.001, weight_decay=0.0, metrics=None, metrics_log_kwargs=None)

Initialize the Node2Vec-SLP HLP module.

Parameters:

Name Type Description Default
encoder_config Node2VecSLPEncoderConfig

Configuration for Node2Vec embeddings.

required
aggregation Literal['mean', 'max', 'min', 'sum']

Method used to aggregate node embeddings per hyperedge. Defaults to mean.

'mean'
loss_fn Module | None

Optional HLP loss function. Defaults to BCEWithLogitsLoss.

None
lr float

Learning rate for the optimizer. Defaults to 0.001.

0.001
weight_decay float

Weight decay for the optimizer. Defaults to 0.0.

0.0
metrics MetricCollection | None

Optional metric collection for evaluation. Defaults to None.

None
metrics_log_kwargs dict[str, Any] | None

Additional keyword arguments passed to metric log calls. Useful for configuring distributed synchronization behavior of torchmetrics. Defaults to None.

None
Source code in hypertorch/hlp/node2vecslp_hlp.py
def __init__(
    self,
    encoder_config: Node2VecSLPEncoderConfig,
    aggregation: Literal["mean", "max", "min", "sum"] = "mean",
    loss_fn: nn.Module | None = None,
    lr: float = 0.001,
    weight_decay: float = 0.0,
    metrics: MetricCollection | None = None,
    metrics_log_kwargs: dict[str, Any] | None = None,
):
    """
    Initialize the Node2Vec-SLP HLP module.

    Args:
        encoder_config: Configuration for Node2Vec embeddings.
        aggregation: Method used to aggregate node embeddings per hyperedge.
            Defaults to ``mean``.
        loss_fn: Optional HLP loss function. Defaults to ``BCEWithLogitsLoss``.
        lr: Learning rate for the optimizer. Defaults to ``0.001``.
        weight_decay: Weight decay for the optimizer. Defaults to ``0.0``.
        metrics: Optional metric collection for evaluation. Defaults to ``None``.
        metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
            Useful for configuring distributed synchronization behavior of
            ``torchmetrics``. Defaults to ``None``.
    """
    self.mode: Node2VecMode = encoder_config.get("mode", NODE2VEC_JOINT_MODE)
    self.embedding_dim: int = encoder_config["num_features"]
    node2vec_config = encoder_config["node2vec_config"]

    encoder = (
        self.__build_node2vec_encoder(self.embedding_dim, node2vec_config, self.mode)
        if self.mode == NODE2VEC_JOINT_MODE
        else None
    )

    decoder = SLP(in_channels=self.embedding_dim, out_channels=1)

    super().__init__(
        encoder=encoder,
        decoder=decoder,
        loss_fn=loss_fn if loss_fn is not None else nn.BCEWithLogitsLoss(),
        metrics=metrics,
        metrics_log_kwargs=metrics_log_kwargs,
    )

    self.aggregation: Literal["mean", "max", "min", "sum"] = aggregation
    self.lr: float = lr
    self.weight_decay: float = weight_decay
    self.random_walk_batch_size: int = node2vec_config.get("random_walk_batch_size", 128)
    self.node2vec_loss_weight: float = node2vec_config.get("node2vec_loss_weight", 1.0)

    self.__walk_loader_state: Node2VecWalkLoaderState = Node2VecWalkLoaderState()

forward(x, hyperedge_index, global_node_ids=None)

Score hyperedges from precomputed or jointly trained Node2Vec embeddings.

Parameters:

Name Type Description Default
x Tensor

Node feature or precomputed embedding matrix.

required
hyperedge_index Tensor

Hyperedge incidence tensor.

required
global_node_ids Tensor | None

Optional global node IDs for joint Node2Vec lookup. Defaults to None.

None

Returns:

Name Type Description
scores Tensor

Predicted hyperedge scores.

Raises:

Type Description
ValueError

If the configured mode cannot supply node embeddings.

Source code in hypertorch/hlp/node2vecslp_hlp.py
def forward(
    self,
    x: Tensor,
    hyperedge_index: Tensor,
    global_node_ids: Tensor | None = None,
) -> Tensor:
    """
    Score hyperedges from precomputed or jointly trained Node2Vec embeddings.

    Args:
        x: Node feature or precomputed embedding matrix.
        hyperedge_index: Hyperedge incidence tensor.
        global_node_ids: Optional global node IDs for joint Node2Vec lookup.
            Defaults to ``None``.

    Returns:
        scores: Predicted hyperedge scores.

    Raises:
        ValueError: If the configured mode cannot supply node embeddings.
    """
    # Encode: get node embeddings from precomputation or joint encoder
    if self.mode == NODE2VEC_JOINT_MODE:
        encoder = _to_node2vec_encoder(self.encoder, self.mode)
        _validate_global_node_ids(encoder.num_embeddings, global_node_ids, self.mode)
        node_embeddings = encoder(batch=global_node_ids)
    else:
        if x.size(1) != self.embedding_dim:
            raise ValueError(
                f"Expected precomputed node embeddings with dimension "
                f"{self.embedding_dim}, got {x.size(1)}."
            )
        node_embeddings = x

    # Aggregate: pool node embeddings per hyperedge
    # shape: (num_hyperedges, embedding_dim)
    hyperedge_embeddings = HyperedgeAggregator(hyperedge_index, node_embeddings).pool(
        self.aggregation
    )

    # Decode: linear projection to scalar score per hyperedge
    # shape: (num_hyperedges, 1) -> squeeze -> (num_hyperedges,)
    scores: Tensor = self.decoder(hyperedge_embeddings).squeeze(-1)
    return scores

training_step(batch, batch_idx)

Run a training step. In joint mode this combines HLP loss with one stochastic Node2Vec walk loss batch.

Parameters:

Name Type Description Default
batch HData

Training batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Training loss.

Source code in hypertorch/hlp/node2vecslp_hlp.py
def training_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a training step.
    In joint mode this combines HLP loss with one stochastic Node2Vec walk loss batch.

    Args:
        batch: Training batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Training loss.
    """
    scores = self.forward(batch.x, batch.hyperedge_index, batch.global_node_ids)
    target_scores, target_labels = self._target_scores_and_labels(scores, batch)
    batch_size = target_labels.size(0)

    if self.mode == NODE2VEC_JOINT_MODE:
        # Node2Vec.loss() is already a stochastic objective over sampled walks,
        # so one walk batch is a standard SGD estimate, not a logically different loss,
        # meaning we can optimize training by using a single walk batch per training step,
        # instead of averaging over multiple walk batches.
        positive_random_walk, negative_random_walk = _next_walk_batch(
            mode=self.mode,
            encoder=self.encoder,
            batch_size=self.random_walk_batch_size,
            state=self.__walk_loader_state,
        )
        positive_random_walk = positive_random_walk.to(self.device)
        negative_random_walk = negative_random_walk.to(self.device)

        hlp_loss = self.loss_fn(target_scores, target_labels)
        node2vec_loss = _to_node2vec_encoder(self.encoder, self.mode).loss(
            positive_random_walk,
            negative_random_walk,
        )
        loss = hlp_loss + (self.node2vec_loss_weight * node2vec_loss)

        self.log(
            stage_metric_name(Stage.TRAIN, "hlp_loss"),
            hlp_loss,
            prog_bar=True,
            batch_size=batch_size,
            **self.metrics_log_kwargs,
        )
        self.log(
            stage_metric_name(Stage.TRAIN, "node2vec_loss"),
            node2vec_loss,
            prog_bar=True,
            batch_size=batch_size,
            **self.metrics_log_kwargs,
        )
        self.log(
            stage_metric_name(Stage.TRAIN, "loss"),
            loss,
            prog_bar=True,
            batch_size=batch_size,
            **self.metrics_log_kwargs,
        )
    else:
        loss = self._compute_loss(target_scores, target_labels, batch_size, Stage.TRAIN)

    self._compute_metrics(target_scores, target_labels, batch_size, Stage.TRAIN)
    return loss

validation_step(batch, batch_idx)

Run a validation step.

Parameters:

Name Type Description Default
batch HData

Validation batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Validation loss.

Source code in hypertorch/hlp/node2vecslp_hlp.py
def validation_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a validation step.

    Args:
        batch: Validation batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Validation loss.
    """
    return self.__eval_step(batch, Stage.VAL)

test_step(batch, batch_idx)

Run a test step.

Parameters:

Name Type Description Default
batch HData

Test batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Test loss.

Source code in hypertorch/hlp/node2vecslp_hlp.py
def test_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a test step.

    Args:
        batch: Test batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Test loss.
    """
    return self.__eval_step(batch, Stage.TEST)

predict_step(batch, batch_idx)

Predict hyperedge scores for a batch.

Parameters:

Name Type Description Default
batch HData

Prediction batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
scores Tensor

Predicted hyperedge scores.

Source code in hypertorch/hlp/node2vecslp_hlp.py
def predict_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Predict hyperedge scores for a batch.

    Args:
        batch: Prediction batch.
        batch_idx: Batch index, unused.

    Returns:
        scores: Predicted hyperedge scores.
    """
    return self.forward(batch.x, batch.hyperedge_index, batch.global_node_ids)

configure_optimizers()

Configure the optimizer.

Returns:

Name Type Description
optimizer Adam

Adam optimizer.

Source code in hypertorch/hlp/node2vecslp_hlp.py
def configure_optimizers(self) -> optim.Adam:
    """
    Configure the optimizer.

    Returns:
        optimizer: Adam optimizer.
    """
    return optim.Adam(self.parameters(), lr=self.lr, weight_decay=self.weight_decay)

__build_node2vec_encoder(embedding_dim, node2vec_config, mode)

Build the joint-mode Node2Vec encoder.

Parameters:

Name Type Description Default
embedding_dim int

Node2Vec embedding dimension.

required
node2vec_config Node2VecHlpConfig

Node2Vec HLP configuration.

required
mode Node2VecMode

Node2Vec training mode used in validation errors.

required

Returns:

Name Type Description
encoder Node2Vec

Node2Vec encoder.

Source code in hypertorch/hlp/node2vecslp_hlp.py
def __build_node2vec_encoder(
    self,
    embedding_dim: int,
    node2vec_config: Node2VecHlpConfig,
    mode: Node2VecMode,
) -> Node2Vec:
    """
    Build the joint-mode Node2Vec encoder.

    Args:
        embedding_dim: Node2Vec embedding dimension.
        node2vec_config: Node2Vec HLP configuration.
        mode: Node2Vec training mode used in validation errors.

    Returns:
        encoder: Node2Vec encoder.
    """
    _validate_walk_length_and_context_size(
        walk_length=node2vec_config.get("walk_length", 20),
        context_size=node2vec_config.get("context_size", 10),
    )

    edge_index, num_nodes = _to_node2vec_edge_index(node2vec_config, mode)

    return Node2Vec(
        edge_index=edge_index,
        embedding_dim=embedding_dim,
        walk_length=node2vec_config.get("walk_length", 20),
        context_size=node2vec_config.get("context_size", 10),
        num_walks_per_node=node2vec_config.get("num_walks_per_node", 10),
        p=node2vec_config.get("p", 1.0),
        q=node2vec_config.get("q", 1.0),
        num_negative_samples=node2vec_config.get("num_negative_samples", 1),
        num_nodes=num_nodes,
        sparse=node2vec_config.get("sparse", False),
    )

__eval_step(batch, stage)

Run shared evaluation logic for a stage.

Parameters:

Name Type Description Default
batch HData

Input batch.

required
stage Stage

Current evaluation stage.

required

Returns:

Name Type Description
loss Tensor

Computed loss.

Source code in hypertorch/hlp/node2vecslp_hlp.py
def __eval_step(self, batch: HData, stage: Stage) -> Tensor:
    """
    Run shared evaluation logic for a stage.

    Args:
        batch: Input batch.
        stage: Current evaluation stage.

    Returns:
        loss: Computed loss.
    """
    scores = self.forward(batch.x, batch.hyperedge_index, batch.global_node_ids)
    target_scores, target_labels = self._target_scores_and_labels(scores, batch)
    batch_size = target_labels.size(0)

    loss = self._compute_loss(target_scores, target_labels, batch_size, stage)
    self._compute_metrics(target_scores, target_labels, batch_size, stage)
    return loss

VilLainEncoderConfig

Bases: TypedDict

Configuration for VilLainHlpModule.

Attributes:

Name Type Description
num_nodes int

Total number of trainable nodes.

embedding_dim NotRequired[int]

Returned node and hyperedge embedding dimension. Defaults to 128.

labels_per_subspace NotRequired[int]

Number of virtual labels per subspace. Defaults to 2.

training_steps NotRequired[int]

Propagation steps used for VilLain loss. Defaults to 4.

generation_steps NotRequired[int]

Propagation steps averaged by forward. Defaults to 100.

tau NotRequired[float]

Gumbel-Softmax temperature. Defaults to 1.0.

eps NotRequired[float]

Numerical stability constant. Defaults to 1e-10.

villain_loss_weight NotRequired[float]

Weight applied to VilLain self-supervision. Defaults to 1.0.

Source code in hypertorch/hlp/villain_hlp.py
class VilLainEncoderConfig(TypedDict):
    """
    Configuration for ``VilLainHlpModule``.

    Attributes:
        num_nodes: Total number of trainable nodes.
        embedding_dim: Returned node and hyperedge embedding dimension. Defaults to ``128``.
        labels_per_subspace: Number of virtual labels per subspace. Defaults to ``2``.
        training_steps: Propagation steps used for VilLain loss. Defaults to ``4``.
        generation_steps: Propagation steps averaged by ``forward``. Defaults to ``100``.
        tau: Gumbel-Softmax temperature. Defaults to ``1.0``.
        eps: Numerical stability constant. Defaults to ``1e-10``.
        villain_loss_weight: Weight applied to VilLain self-supervision. Defaults to ``1.0``.
    """

    num_nodes: int
    embedding_dim: NotRequired[int]
    labels_per_subspace: NotRequired[int]
    training_steps: NotRequired[int]
    generation_steps: NotRequired[int]
    tau: NotRequired[float]
    eps: NotRequired[float]
    villain_loss_weight: NotRequired[float]

VilLainHlpModule

Bases: HlpModule

Feature-free VilLain Hyperedge Link Prediction module.

Attributes:

Name Type Description
encoder Module | None

VilLain encoder module inherited from HlpModule.

decoder Module

SLP decoder module inherited from HlpModule.

loss_fn Module

Loss function inherited from HlpModule.

metrics_log_kwargs dict[str, Any]

Metric logging keyword arguments inherited from HlpModule.

train_metrics MetricCollection | None

Optional training metrics inherited from HlpModule.

val_metrics MetricCollection | None

Optional validation metrics inherited from HlpModule.

test_metrics MetricCollection | None

Optional test metrics inherited from HlpModule.

embedding_dim int

VilLain embedding dimension.

embedding_mode Literal['node', 'hyperedge']

Whether to return node or hyperedge embeddings from the VilLain encoder. Defaults to "node".

aggregation Literal['mean', 'max', 'min', 'maxmin', 'sum']

Aggregation method to pool node embeddings into hyperedge embeddings when embedding_mode="node". Ignored when embedding_mode="hyperedge". Defaults to "maxmin".

lr float

Learning rate for the optimizer. Defaults to 0.01.

weight_decay float

Weight decay for the optimizer. Defaults to 0.0.

villain_loss_weight float

Weight applied to VilLain self-supervision. Defaults to 1.0.

Source code in hypertorch/hlp/villain_hlp.py
class VilLainHlpModule(HlpModule):
    """
    Feature-free VilLain Hyperedge Link Prediction module.

    Attributes:
        encoder: VilLain encoder module inherited from ``HlpModule``.
        decoder: SLP decoder module inherited from ``HlpModule``.
        loss_fn: Loss function inherited from ``HlpModule``.
        metrics_log_kwargs: Metric logging keyword arguments inherited from ``HlpModule``.
        train_metrics: Optional training metrics inherited from ``HlpModule``.
        val_metrics: Optional validation metrics inherited from ``HlpModule``.
        test_metrics: Optional test metrics inherited from ``HlpModule``.
        embedding_dim: VilLain embedding dimension.
        embedding_mode: Whether to return node or hyperedge embeddings from the VilLain encoder.
            Defaults to ``"node"``.
        aggregation: Aggregation method to pool node embeddings into hyperedge embeddings
            when ``embedding_mode="node"``. Ignored when ``embedding_mode="hyperedge"``.
            Defaults to ``"maxmin"``.
        lr: Learning rate for the optimizer. Defaults to ``0.01``.
        weight_decay: Weight decay for the optimizer. Defaults to ``0.0``.
        villain_loss_weight: Weight applied to VilLain self-supervision. Defaults to ``1.0``.
    """

    def __init__(
        self,
        encoder_config: VilLainEncoderConfig,
        embedding_mode: Literal["node", "hyperedge"] = "node",
        aggregation: Literal["mean", "max", "min", "maxmin", "sum"] = "maxmin",
        loss_fn: nn.Module | None = None,
        lr: float = 0.01,
        weight_decay: float = 0.0,
        metrics: MetricCollection | None = None,
        metrics_log_kwargs: dict[str, Any] | None = None,
    ):
        """
        Initialize the VilLain HLP module.

        Args:
            encoder_config: Configuration for the VilLain encoder.
            embedding_mode: Whether to score from node-derived or hyperedge embeddings.
                Defaults to ``"node"``.
            aggregation: Aggregation method used when ``embedding_mode="node"``.
                Defaults to ``"maxmin"``.
            loss_fn: Optional HLP loss function. Defaults to ``BCEWithLogitsLoss``.
            lr: Learning rate for the optimizer. Defaults to ``0.01``.
            weight_decay: Weight decay for the optimizer. Defaults to ``0.0``.
            metrics: Optional metric collection for evaluation. Defaults to ``None``.
            metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
                Useful for configuring distributed synchronization behavior of
                ``torchmetrics``. Defaults to ``None``.
        """
        self.embedding_dim: int = encoder_config.get("embedding_dim", 128)
        self.aggregation: Literal["mean", "max", "min", "maxmin", "sum"] = aggregation
        self.lr: float = lr
        self.weight_decay: float = weight_decay
        self.villain_loss_weight: float = encoder_config.get("villain_loss_weight", 1.0)
        self.embedding_mode: Literal["node", "hyperedge"] = embedding_mode

        encoder = VilLain(
            num_nodes=encoder_config["num_nodes"],
            embedding_dim=self.embedding_dim,
            labels_per_subspace=encoder_config.get("labels_per_subspace", 2),
            training_steps=encoder_config.get("training_steps", 4),
            generation_steps=encoder_config.get("generation_steps", 100),
            tau=encoder_config.get("tau", 1.0),
            eps=encoder_config.get("eps", 1e-10),
        )
        decoder = SLP(in_channels=self.embedding_dim, out_channels=1)

        super().__init__(
            encoder=encoder,
            decoder=decoder,
            loss_fn=loss_fn if loss_fn is not None else nn.BCEWithLogitsLoss(),
            metrics=metrics,
            metrics_log_kwargs=metrics_log_kwargs,
        )

    def forward(
        self,
        hyperedge_index: Tensor,
        global_node_ids: Tensor | None = None,
        num_hyperedges: int | None = None,
    ) -> Tensor:
        """
        Score hyperedges with VilLain-generated embeddings.

        Args:
            hyperedge_index: Hyperedge incidence tensor.
            global_node_ids: Optional global node IDs for transductive embedding lookup.
                Defaults to ``None``.
            num_hyperedges: Optional explicit number of hyperedges. Defaults to ``None``.

        Returns:
            scores: Predicted hyperedge scores.
        """
        encoder = self.__to_villain_encoder()

        match self.embedding_mode:
            case "hyperedge":
                hyperedge_embeddings = encoder.hyperedge_embeddings(
                    hyperedge_index=hyperedge_index,
                    node_ids=global_node_ids,
                    num_hyperedges=num_hyperedges,
                )
            case _:
                node_embeddings = encoder.node_embeddings(
                    hyperedge_index=hyperedge_index,
                    node_ids=global_node_ids,
                    num_hyperedges=num_hyperedges,
                )
                hyperedge_embeddings = HyperedgeAggregator(
                    hyperedge_index=hyperedge_index,
                    node_embeddings=node_embeddings,
                    num_hyperedges=num_hyperedges,
                ).pool(self.aggregation)

        scores: Tensor = self.decoder(hyperedge_embeddings).squeeze(-1)
        return scores

    def training_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a training step with HLP and VilLain self-supervised losses.

        Args:
            batch: Training batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Combined training loss.
        """
        scores = self.forward(
            hyperedge_index=batch.hyperedge_index,
            global_node_ids=batch.global_node_ids,
            num_hyperedges=batch.num_hyperedges,
        )
        target_scores, target_labels = self._target_scores_and_labels(scores, batch)
        batch_size = target_labels.size(0)

        hlp_loss = self.loss_fn(target_scores, target_labels)
        villain_loss, villain_loss_parts = self.__to_villain_encoder().loss(
            hyperedge_index=batch.hyperedge_index,
            node_ids=batch.global_node_ids,
            num_hyperedges=batch.num_hyperedges,
        )
        loss = hlp_loss + (self.villain_loss_weight * villain_loss)

        self.log(
            stage_metric_name(Stage.TRAIN, "hlp_loss"),
            hlp_loss,
            prog_bar=True,
            batch_size=batch_size,
            **self.metrics_log_kwargs,
        )
        self.log(
            stage_metric_name(Stage.TRAIN, "villain_loss"),
            villain_loss,
            prog_bar=True,
            batch_size=batch_size,
            **self.metrics_log_kwargs,
        )
        self.log(
            stage_metric_name(Stage.TRAIN, "local_loss"),
            villain_loss_parts["local_loss"],
            prog_bar=False,
            batch_size=batch_size,
            **self.metrics_log_kwargs,
        )
        self.log(
            stage_metric_name(Stage.TRAIN, "global_loss"),
            villain_loss_parts["global_loss"],
            prog_bar=False,
            batch_size=batch_size,
            **self.metrics_log_kwargs,
        )
        self.log(
            stage_metric_name(Stage.TRAIN, "loss"),
            loss,
            prog_bar=True,
            batch_size=batch_size,
            **self.metrics_log_kwargs,
        )

        self._compute_metrics(target_scores, target_labels, batch_size, Stage.TRAIN)
        return loss

    def validation_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a validation step.

        Args:
            batch: Validation batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Validation loss.
        """
        return self.__eval_step(batch, Stage.VAL)

    def test_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Run a test step.

        Args:
            batch: Test batch.
            batch_idx: Batch index, unused.

        Returns:
            loss: Test loss.
        """
        return self.__eval_step(batch, Stage.TEST)

    def predict_step(self, batch: HData, batch_idx: int) -> Tensor:
        """
        Predict hyperedge scores for a batch.

        Args:
            batch: Prediction batch.
            batch_idx: Batch index, unused.

        Returns:
            scores: Predicted hyperedge scores.
        """
        return self.forward(
            hyperedge_index=batch.hyperedge_index,
            global_node_ids=batch.global_node_ids,
            num_hyperedges=batch.num_hyperedges,
        )

    def configure_optimizers(self) -> optim.Adam:
        """
        Configure the optimizer.

        Returns:
            optimizer: Adam optimizer.
        """
        return optim.Adam(self.parameters(), lr=self.lr, weight_decay=self.weight_decay)

    def __eval_step(self, batch: HData, stage: Stage) -> Tensor:
        """
        Run shared evaluation logic for a stage.

        Args:
            batch: Input batch.
            stage: Current evaluation stage.

        Returns:
            loss: Computed loss.
        """
        scores = self.forward(
            hyperedge_index=batch.hyperedge_index,
            global_node_ids=batch.global_node_ids,
            num_hyperedges=batch.num_hyperedges,
        )
        target_scores, target_labels = self._target_scores_and_labels(scores, batch)
        batch_size = target_labels.size(0)

        loss = self._compute_loss(target_scores, target_labels, batch_size, stage)
        self._compute_metrics(target_scores, target_labels, batch_size, stage)
        return loss

    def __to_villain_encoder(self) -> VilLain:
        """
        Return the configured VilLain encoder.

        Returns:
            encoder: VilLain encoder instance.

        Raises:
            ValueError: If the configured encoder is missing or has the wrong type.
        """
        if self.encoder is None or not isinstance(self.encoder, VilLain):
            raise ValueError("VilLain requires a VilLain encoder, but none was provided.")
        return self.encoder

__init__(encoder_config, embedding_mode='node', aggregation='maxmin', loss_fn=None, lr=0.01, weight_decay=0.0, metrics=None, metrics_log_kwargs=None)

Initialize the VilLain HLP module.

Parameters:

Name Type Description Default
encoder_config VilLainEncoderConfig

Configuration for the VilLain encoder.

required
embedding_mode Literal['node', 'hyperedge']

Whether to score from node-derived or hyperedge embeddings. Defaults to "node".

'node'
aggregation Literal['mean', 'max', 'min', 'maxmin', 'sum']

Aggregation method used when embedding_mode="node". Defaults to "maxmin".

'maxmin'
loss_fn Module | None

Optional HLP loss function. Defaults to BCEWithLogitsLoss.

None
lr float

Learning rate for the optimizer. Defaults to 0.01.

0.01
weight_decay float

Weight decay for the optimizer. Defaults to 0.0.

0.0
metrics MetricCollection | None

Optional metric collection for evaluation. Defaults to None.

None
metrics_log_kwargs dict[str, Any] | None

Additional keyword arguments passed to metric log calls. Useful for configuring distributed synchronization behavior of torchmetrics. Defaults to None.

None
Source code in hypertorch/hlp/villain_hlp.py
def __init__(
    self,
    encoder_config: VilLainEncoderConfig,
    embedding_mode: Literal["node", "hyperedge"] = "node",
    aggregation: Literal["mean", "max", "min", "maxmin", "sum"] = "maxmin",
    loss_fn: nn.Module | None = None,
    lr: float = 0.01,
    weight_decay: float = 0.0,
    metrics: MetricCollection | None = None,
    metrics_log_kwargs: dict[str, Any] | None = None,
):
    """
    Initialize the VilLain HLP module.

    Args:
        encoder_config: Configuration for the VilLain encoder.
        embedding_mode: Whether to score from node-derived or hyperedge embeddings.
            Defaults to ``"node"``.
        aggregation: Aggregation method used when ``embedding_mode="node"``.
            Defaults to ``"maxmin"``.
        loss_fn: Optional HLP loss function. Defaults to ``BCEWithLogitsLoss``.
        lr: Learning rate for the optimizer. Defaults to ``0.01``.
        weight_decay: Weight decay for the optimizer. Defaults to ``0.0``.
        metrics: Optional metric collection for evaluation. Defaults to ``None``.
        metrics_log_kwargs: Additional keyword arguments passed to metric log calls.
            Useful for configuring distributed synchronization behavior of
            ``torchmetrics``. Defaults to ``None``.
    """
    self.embedding_dim: int = encoder_config.get("embedding_dim", 128)
    self.aggregation: Literal["mean", "max", "min", "maxmin", "sum"] = aggregation
    self.lr: float = lr
    self.weight_decay: float = weight_decay
    self.villain_loss_weight: float = encoder_config.get("villain_loss_weight", 1.0)
    self.embedding_mode: Literal["node", "hyperedge"] = embedding_mode

    encoder = VilLain(
        num_nodes=encoder_config["num_nodes"],
        embedding_dim=self.embedding_dim,
        labels_per_subspace=encoder_config.get("labels_per_subspace", 2),
        training_steps=encoder_config.get("training_steps", 4),
        generation_steps=encoder_config.get("generation_steps", 100),
        tau=encoder_config.get("tau", 1.0),
        eps=encoder_config.get("eps", 1e-10),
    )
    decoder = SLP(in_channels=self.embedding_dim, out_channels=1)

    super().__init__(
        encoder=encoder,
        decoder=decoder,
        loss_fn=loss_fn if loss_fn is not None else nn.BCEWithLogitsLoss(),
        metrics=metrics,
        metrics_log_kwargs=metrics_log_kwargs,
    )

forward(hyperedge_index, global_node_ids=None, num_hyperedges=None)

Score hyperedges with VilLain-generated embeddings.

Parameters:

Name Type Description Default
hyperedge_index Tensor

Hyperedge incidence tensor.

required
global_node_ids Tensor | None

Optional global node IDs for transductive embedding lookup. Defaults to None.

None
num_hyperedges int | None

Optional explicit number of hyperedges. Defaults to None.

None

Returns:

Name Type Description
scores Tensor

Predicted hyperedge scores.

Source code in hypertorch/hlp/villain_hlp.py
def forward(
    self,
    hyperedge_index: Tensor,
    global_node_ids: Tensor | None = None,
    num_hyperedges: int | None = None,
) -> Tensor:
    """
    Score hyperedges with VilLain-generated embeddings.

    Args:
        hyperedge_index: Hyperedge incidence tensor.
        global_node_ids: Optional global node IDs for transductive embedding lookup.
            Defaults to ``None``.
        num_hyperedges: Optional explicit number of hyperedges. Defaults to ``None``.

    Returns:
        scores: Predicted hyperedge scores.
    """
    encoder = self.__to_villain_encoder()

    match self.embedding_mode:
        case "hyperedge":
            hyperedge_embeddings = encoder.hyperedge_embeddings(
                hyperedge_index=hyperedge_index,
                node_ids=global_node_ids,
                num_hyperedges=num_hyperedges,
            )
        case _:
            node_embeddings = encoder.node_embeddings(
                hyperedge_index=hyperedge_index,
                node_ids=global_node_ids,
                num_hyperedges=num_hyperedges,
            )
            hyperedge_embeddings = HyperedgeAggregator(
                hyperedge_index=hyperedge_index,
                node_embeddings=node_embeddings,
                num_hyperedges=num_hyperedges,
            ).pool(self.aggregation)

    scores: Tensor = self.decoder(hyperedge_embeddings).squeeze(-1)
    return scores

training_step(batch, batch_idx)

Run a training step with HLP and VilLain self-supervised losses.

Parameters:

Name Type Description Default
batch HData

Training batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Combined training loss.

Source code in hypertorch/hlp/villain_hlp.py
def training_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a training step with HLP and VilLain self-supervised losses.

    Args:
        batch: Training batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Combined training loss.
    """
    scores = self.forward(
        hyperedge_index=batch.hyperedge_index,
        global_node_ids=batch.global_node_ids,
        num_hyperedges=batch.num_hyperedges,
    )
    target_scores, target_labels = self._target_scores_and_labels(scores, batch)
    batch_size = target_labels.size(0)

    hlp_loss = self.loss_fn(target_scores, target_labels)
    villain_loss, villain_loss_parts = self.__to_villain_encoder().loss(
        hyperedge_index=batch.hyperedge_index,
        node_ids=batch.global_node_ids,
        num_hyperedges=batch.num_hyperedges,
    )
    loss = hlp_loss + (self.villain_loss_weight * villain_loss)

    self.log(
        stage_metric_name(Stage.TRAIN, "hlp_loss"),
        hlp_loss,
        prog_bar=True,
        batch_size=batch_size,
        **self.metrics_log_kwargs,
    )
    self.log(
        stage_metric_name(Stage.TRAIN, "villain_loss"),
        villain_loss,
        prog_bar=True,
        batch_size=batch_size,
        **self.metrics_log_kwargs,
    )
    self.log(
        stage_metric_name(Stage.TRAIN, "local_loss"),
        villain_loss_parts["local_loss"],
        prog_bar=False,
        batch_size=batch_size,
        **self.metrics_log_kwargs,
    )
    self.log(
        stage_metric_name(Stage.TRAIN, "global_loss"),
        villain_loss_parts["global_loss"],
        prog_bar=False,
        batch_size=batch_size,
        **self.metrics_log_kwargs,
    )
    self.log(
        stage_metric_name(Stage.TRAIN, "loss"),
        loss,
        prog_bar=True,
        batch_size=batch_size,
        **self.metrics_log_kwargs,
    )

    self._compute_metrics(target_scores, target_labels, batch_size, Stage.TRAIN)
    return loss

validation_step(batch, batch_idx)

Run a validation step.

Parameters:

Name Type Description Default
batch HData

Validation batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Validation loss.

Source code in hypertorch/hlp/villain_hlp.py
def validation_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a validation step.

    Args:
        batch: Validation batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Validation loss.
    """
    return self.__eval_step(batch, Stage.VAL)

test_step(batch, batch_idx)

Run a test step.

Parameters:

Name Type Description Default
batch HData

Test batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
loss Tensor

Test loss.

Source code in hypertorch/hlp/villain_hlp.py
def test_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Run a test step.

    Args:
        batch: Test batch.
        batch_idx: Batch index, unused.

    Returns:
        loss: Test loss.
    """
    return self.__eval_step(batch, Stage.TEST)

predict_step(batch, batch_idx)

Predict hyperedge scores for a batch.

Parameters:

Name Type Description Default
batch HData

Prediction batch.

required
batch_idx int

Batch index, unused.

required

Returns:

Name Type Description
scores Tensor

Predicted hyperedge scores.

Source code in hypertorch/hlp/villain_hlp.py
def predict_step(self, batch: HData, batch_idx: int) -> Tensor:
    """
    Predict hyperedge scores for a batch.

    Args:
        batch: Prediction batch.
        batch_idx: Batch index, unused.

    Returns:
        scores: Predicted hyperedge scores.
    """
    return self.forward(
        hyperedge_index=batch.hyperedge_index,
        global_node_ids=batch.global_node_ids,
        num_hyperedges=batch.num_hyperedges,
    )

configure_optimizers()

Configure the optimizer.

Returns:

Name Type Description
optimizer Adam

Adam optimizer.

Source code in hypertorch/hlp/villain_hlp.py
def configure_optimizers(self) -> optim.Adam:
    """
    Configure the optimizer.

    Returns:
        optimizer: Adam optimizer.
    """
    return optim.Adam(self.parameters(), lr=self.lr, weight_decay=self.weight_decay)

__eval_step(batch, stage)

Run shared evaluation logic for a stage.

Parameters:

Name Type Description Default
batch HData

Input batch.

required
stage Stage

Current evaluation stage.

required

Returns:

Name Type Description
loss Tensor

Computed loss.

Source code in hypertorch/hlp/villain_hlp.py
def __eval_step(self, batch: HData, stage: Stage) -> Tensor:
    """
    Run shared evaluation logic for a stage.

    Args:
        batch: Input batch.
        stage: Current evaluation stage.

    Returns:
        loss: Computed loss.
    """
    scores = self.forward(
        hyperedge_index=batch.hyperedge_index,
        global_node_ids=batch.global_node_ids,
        num_hyperedges=batch.num_hyperedges,
    )
    target_scores, target_labels = self._target_scores_and_labels(scores, batch)
    batch_size = target_labels.size(0)

    loss = self._compute_loss(target_scores, target_labels, batch_size, stage)
    self._compute_metrics(target_scores, target_labels, batch_size, stage)
    return loss

__to_villain_encoder()

Return the configured VilLain encoder.

Returns:

Name Type Description
encoder VilLain

VilLain encoder instance.

Raises:

Type Description
ValueError

If the configured encoder is missing or has the wrong type.

Source code in hypertorch/hlp/villain_hlp.py
def __to_villain_encoder(self) -> VilLain:
    """
    Return the configured VilLain encoder.

    Returns:
        encoder: VilLain encoder instance.

    Raises:
        ValueError: If the configured encoder is missing or has the wrong type.
    """
    if self.encoder is None or not isinstance(self.encoder, VilLain):
        raise ValueError("VilLain requires a VilLain encoder, but none was provided.")
    return self.encoder