import torch
import torch.nn as nn

class BilinearLayer(layers.Layer):
    def __init__(self, units, r=32, activation=None):
        super().__init__()
        self.units = units
        self.r = r
        self.activation = tf.keras.activations.get(activation)

    def build(self, input_shape):
        d_f = int(input_shape[0][-1])
        d_g = int(input_shape[1][-1])

        self.W1 = self.add_weight(shape=(d_f, self.units), dtype=tf.float32)
        self.W2 = self.add_weight(shape=(d_g, self.units), dtype=tf.float32)
        self.W3 = self.add_weight(shape=(d_f, self.r), dtype=tf.float32)
        self.W4 = self.add_weight(shape=(d_g, self.r), dtype=tf.float32)
        self.V  = self.add_weight(shape=(self.r, self.units), dtype=tf.float32)
        self.b  = self.add_weight(shape=(self.units,), initializer="zeros", dtype=tf.float32)

    def call(self, inputs):
        f, g = inputs
        f = tf.cast(f, tf.float32)
        g = tf.cast(g, tf.float32)

        h = tf.matmul(f, self.W1) * tf.matmul(g, self.W2)
        b = tf.matmul(tf.matmul(f, self.W3) * tf.matmul(g, self.W4), self.V)
        out = h + b + self.b

        return self.activation(out) if self.activation else outx2)