# Path: brainforge/util/typing.py
# def zX(*dims, dtype=floatX):
#     return np.zeros(dims, dtype=dtype)
# 
# def zX_like(array, dtype=floatX):
#     return zX(*array.shape, dtype=dtype)


import numpy as np
from ..util.typing import zX, zX_like
        fx, fy, fc, nf = fshape
        if mode == "valid":
            return nf, iy - fy + 1, ix - fx + 1
        elif mode == "full":
            return nf, iy + fy - 1, ix + fx - 1
        else:
            raise RuntimeError("Unsupported mode:", mode)

    def __str__(self):
        return "Convolution"


class MaxPoolOp:

    def __str__(self):
        return "MaxPool"

    @staticmethod
    def predict(A):
        return np.max([
            A[:, :, 0::2, 0::2],
            A[:, :, 0::2, 1::2],
            A[:, :, 1::2, 0::2],
            A[:, :, 1::2, 1::2],
        ], axis=0)

    @staticmethod
    def forward(A, fdim):
        im, ic, iy, ix = A.shape
        oy, ox = iy // fdim, ix // fdim

# Path: brainforge/util/typing.py
# def zX(*dims, dtype=floatX):
#     return np.zeros(dims, dtype=dtype)
# 
# def zX_like(array, dtype=floatX):
#     return zX(*array.shape, dtype=dtype)


# Path: brainforge/atomic/tensor_op.py
import numpy as np
from ..util.typing import zX, zX_like

        fx, fy, fc, nf = fshape
        if mode == "valid":
            return nf, iy - fy + 1, ix - fx + 1
        elif mode == "full":
            return nf, iy + fy - 1, ix + fx - 1
        else:
            raise RuntimeError("Unsupported mode:", mode)

    def __str__(self):
        return "Convolution"


class MaxPoolOp:

    def __str__(self):
        return "MaxPool"

    @staticmethod
    def predict(A):
        return np.max([
            A[:, :, 0::2, 0::2],
            A[:, :, 0::2, 1::2],
            A[:, :, 1::2, 0::2],
            A[:, :, 1::2, 1::2],
        ], axis=0)

    @staticmethod
    def forward(A, fdim):
        im, ic, iy, ix = A.shape
        oy, ox = iy // fdim, ix // fdim

        output = zX(im, ic, oy, ox)