include "fzn_neural_net.mzn";
include "fzn_neural_net_reif.mzn";


enum NEURON_TYPE = { NT_RELU, NT_STEP, NT_LINEAR, NT_SOFTPLUS };

/** @group globals.learning
  Constrain the output layer of a neural net to take the value defined by the input layer.

  @param inputs: an array of float variables
  @param input_ids: array[int] of node
  @param outputs: an array of float variables
  @param output_ids: array[int] of node
  @param bias: array[node] of float
  @param edge_weight: array[edge] of float
  @param edge_parent: array[edge] of neuron (start neuron for edge)
  @param first_edge: array[node] of 1..m+1
  @param neuron_type: { NT_RELU, NT_STEP, NT_LINEAR, NT_SOFTPLUS }
*/
predicate neural_net(array[int] of var float: inputs,
                     array[int] of int: input_ids,
                     array[int] of var float: outputs,
                     array[int] of int: output_ids,
                     array[int] of float: bias,
                     array[int] of float: edge_weight,
                     array[int] of int: edge_parent,
                     array[int] of int: first_edge,
                     NEURON_TYPE: neuron_type) =
    assert(index_set(inputs) == index_set(input_ids),
                 "neural_net: number of input vars not equal to number of input_ids") /\
    assert(index_set(outputs) == index_set(output_ids),
                 "neural_net: number of output vars not equal to number of output_ids") /\
          let { set of int: node = index_set(bias) } in
          let { set of int: edge = index_set(edge_weight) } in
          assert(index_set(edge_parent) == edge,
                 "neural_net: index sets of edge_weight and edge_parent do not agree") /\
    assert(index_set(first_edge) == node,
                 "neural_new: index sets of bias and first_edge do not agree") /\
          forall(i in index_set(input_ids))
    (assert(input_ids[i] in node,
                 "neural_net: input_ids\(i) = \(input_ids[i]) not a correct node number"))
    /\
          forall(i in index_set(output_ids))
    (assert(output_ids[i] in node,
                 "neural_net: output_ids\(i) = \(output_ids[i]) not a correct node number"))
    /\
          forall(i in index_set(first_edge))
    (assert(first_edge[i] in edge,
                 "neural_net: first_edge\(i) = \(first_edge[i]) not a correct edge number"))
    /\
          forall(i in index_set(edge_parent))
    (assert(edge_parent[i] in edge,
                 "neural_net: edge_parent\(i) = \(edge_parent[i]) not a correct node number"))
    /\
    assert(mincreasing(first_edge),
                 "neural_net: first_edge array is not in increasing order")
    /\
          fzn_neural_net(inputs,input_ids,outputs,output_ids,bias,
                         edge_weight,edge_parent,first_edge,neuron_type);

test mincreasing(array[int] of int: x) =
    forall(i in index_set(x) diff { max(index_set(x)) })
          (x[i] <= x[i+1]);

predicate neural_net(array[int] of var float: inputs,
                     array[int] of int: input_ids,
                     array[int] of var float: outputs,
                     array[int] of int: output_ids,
                     array[int] of float: bias,
                     array[int] of float: edge_weight,
                     array[int] of int: edge_parent,
                     array[int] of int: first_edge) =
    neural_net(inputs,input_ids,outputs,output_ids,bias,
               edge_weight,edge_parent,first_edge,NT_RELU);

%-----------------------------------------------------------------------------%
