def __init__(
        self,
        data_info: dict,
        embed_size: int = 80,
        aggregators: str = "mean max min std",
        scalers: str = "identity amplification attenuation",
        dropout: float = 0.3,
        batch_norm: bool = True,
        residual: bool = True,
        num_mlp_layers: int = 1,
        num_layers: int = 4,
        readout: str = "mean",
    ):
        """Principal Neighbourhood Aggregation

        Parameters
        ----------
        data_info : dict
            The information about the input dataset.
        embed_size : int
            Embedding size.
        aggregators : str
            Aggregation function names separated by space, can include mean, max, min, std, sum
        scalers : str
            Scaler function names separated by space, can include identity, amplification, and attenuation
        dropout : float
            Dropout rate.
        batch_norm : bool
            Whether to use batch normalization.
        residual : bool
            Whether to use residual connection.
        num_mlp_layers : int
            Number of MLP layers to use after message aggregation in each PNA layer.
        num_layers : int
            Number of PNA layers.
        readout : str
            Readout for computing graph-level representations, can be 'sum' or 'mean'.
        """
        super(PNA, self).__init__()
        self.data_info = data_info
        self.embed_size = embed_size
        self.dropout = dropout
        self.batch_norm = batch_norm
        self.residual = residual
        self.num_mlp_layers = num_mlp_layers
        self.num_layers = num_layers
        self.readout = readout

        if aggregators is None:
            aggregators = ["mean", "max", "min", "std"]
        else:
            aggregators = [agg.strip() for agg in aggregators.split(" ")]
            assert set(aggregators).issubset(
                {"mean", "max", "min", "std", "sum"}
            ), "Expect aggregators to be a subset of ['mean', 'max', 'min', 'std', 'sum'], \
                    got {}".format(
                aggregators
            )
        if scalers is None:
            scalers = ["identity", "amplification", "attenuation"]
        else:
            scalers = [scl.strip() for scl in scalers.split(" ")]
            assert set(scalers).issubset(
                {"identity", "amplification", "attenuation"}
            ), "Expect scalers to be a subset of ['identity', 'amplification', 'attenuation'], \
                    got {}".format(
                scalers
            )
        self.aggregators = aggregators
        self.scalers = scalers

        if data_info["name"] in ["ogbg-molhiv", "ogbg-molpcba"]:
            self.node_encoder = AtomEncoder(embed_size)
        else:
            # Handle other datasets
            self.node_encoder = nn.Linear(
                data_info["node_feat_size"], embed_size
            )
        self.conv_layers = nn.ModuleList(
            [
                SimplePNAConv(
                    feat_size=embed_size,
                    aggregators=aggregators,
                    scalers=scalers,
                    delta=data_info["delta"],
                    dropout=dropout,
                    batch_norm=batch_norm,
                    residual=residual,
                    num_mlp_layers=num_mlp_layers,
                )
                for _ in range(num_layers)
            ]
        )

        if readout == "sum":
            self.pool = SumPooling()
        elif readout == "mean":
            self.pool = AvgPooling()
        else:
            raise ValueError(
                "Expect readout to be 'sum' or 'mean', got {}".format(readout)
            )
        self.pred = MLP(
            embed_size, data_info["out_size"], decreasing_hidden_size=True
        )
Principal Neighbourhood Aggregation

        Parameters
        ----------
        data_info : dict
            The information about the input dataset.
        embed_size : int
            Embedding size.
        aggregators : str
            Aggregation function names separated by space, can include mean, max, min, std, sum
        scalers : str
            Scaler function names separated by space, can include identity, amplification, and attenuation
        dropout : float
            Dropout rate.
        batch_norm : bool
            Whether to use batch normalization.
        residual : bool
            Whether to use residual connection.
        num_mlp_layers : int
            Number of MLP layers to use after message aggregation in each PNA layer.
        num_layers : int
            Number of PNA layers.
        readout : str
            Readout for computing graph-level representations, can be 'sum' or 'mean'.
        
dglgo/dglgo/model/graph_encoder/pna.py