def linear_forward(A_prev, W, b):
    """ Calculates and produces the linear function on the weights, bias' and previous layers activations.

    Preconditions:
    A_prev: numpy ndarray whose # rows = W # columns and # columns = # of data samples
    W: numpy ndarray whose # columns = A_prev # rows
    b: numpy ndarray whose rows = W rows and has 1 column 

    Parameters:
    A_prev: The previous layer's activation's, each column is for a different data sample
    W: The weights of a layer in the network
    b: The biases of a layer in the network

    Postconditions:
    Z: numpy ndarray containing linear transformations of the previous layers activations or input layer.
    linear_cache: Tuple containing A_prev, W, and b (used for back prop)
    """

    # The linear calculation is the weights dot producted with the previous activations plus the biases.
    Z = W @ A_prev + b
    linear_cache = (A_prev, W, b)

    return Z, linear_cache