def element(block: Block, inputs, op_type, is_exit=False):
    """
    Add an element-wise operator at the end of given block.

    :param block: ios.Block
        The block to add the operator

    :param inputs: Sequence[Sequence[Value]]
        The inputs of the convolution. 'inputs' contains a list of terms. A term contains a list of values. The values
        in a term are added up. The terms are concatenated along with the channel dimension.

    :param op_type: str, must be one of 'mul' and 'add'
        The element-wise operation type string.

    :param is_exit: boolean, default False
        Whether this operator is the exit operator of the block.

    :return: Value
        A value represents the output of the operator.
    """
    name = new_name()
    rel = Element(name, name, inputs, op_type=op_type, output_shape=None)
    rel.infer_shape()
    for ti, term in enumerate(inputs):
        for vi, value in enumerate(term):
            value.node.uses.append((rel, ti, vi))
    if is_exit:
        block.exit_node = rel
    else:
        block.inner_nodes.append(rel)
    return Value(rel, 0, rel.output_shape[0])
def relu(block: Block, inputs, is_exit=False):
    """
    Add a Relu activation at the end of given block. This function is equivalent to activation(act_type='relu').

    :param block: ios.Block
        The block to add the operator

    :param inputs: Sequence[Sequence[Value]]
        The inputs of the convolution. 'inputs' contains a list of terms. A term contains a list of values. The values
        in a term are added up. The terms are concatenated along with the channel dimension.

    :param is_exit: boolean, default False
        Whether this operator is the exit operator of the block.

    :return: Value
        A value represents the output of the operator.
    """
    name = new_name()
    rel = Relu(name, name, inputs, None)
    rel.infer_shape()
    for ti, term in enumerate(inputs):
        for vi, value in enumerate(term):
            value.node.uses.append((rel, ti, vi))
    if is_exit:
        block.exit_node = rel
    else:
        block.inner_nodes.append(rel)
    return Value(rel, 0, rel.output_shape[0])
def identity(block: Block, inputs, is_exit=False):
    """
    Add Identity operator at the end of given block. Because the inputs can can do the addition and concatenation
    operation, this operator works as addition and concatenation.

    :param block: ios.Block
        The block to add the operator

    :param inputs: Sequence[Sequence[Value]]
        The inputs of the convolution. 'inputs' contains a list of terms. A term contains a list of values. The values
        in a term are added up. The terms are concatenated along with the channel dimension.

    :param is_exit: boolean, default False
        Whether this operator is the exit operator of the block.

    :return: Value
        A value represents the output of the operator.
    """
    name = new_name()
    ident = Identity(name, name, inputs, None)
    ident.infer_shape()
    for ti, term in enumerate(inputs):
        for vi, value in enumerate(term):
            value.node.uses.append((ident, ti, vi))
    if is_exit:
        block.exit_node = ident
    else:
        block.inner_nodes.append(ident)
    return Value(ident, 0, ident.output_shape[0])
def conv2d(block: Block,
           inputs,
           out_channels,
           kernel=(1, 1),
           stride=(1, 1),
           padding=(0, 0),
           groups=1,
           act="relu",
           is_exit=False):
    """
    Add a convolution operator to the end of given block.

    :param block: ios.Block
        The block to add the operator

    :param inputs: Sequence[Sequence[Value]]
        The inputs of the convolution. 'inputs' contains a list of terms. A term contains a list of values. The values
        in a term are added up. The terms are concatenated along with the channel dimension.

    :param out_channels:
        The number of output channels.

    :param kernel: Tuple[int, int], default (1, 1)
        The kernel size.

    :param stride: Tuple[int, int], default (1, 1)
        The stride size.

    :param padding: Tuple[int, int], default (0, 0)

    :param groups: int, default 1
        The number of groups. It must be a common factor of the input channels and output channels.

    :param act: str, default 'relu'
        The activation applied to the output of convolution.

    :param is_exit: boolean, default False
        Whether this operator is the exit operator of the block.

    :return: Value
        A value represents the output of the operator.
    """
    name = new_name()
    conv = Conv(name, name, inputs, out_channels, kernel, stride, padding,
                groups, act, None)
    conv.infer_shape()
    for ti, term in enumerate(inputs):
        for vi, value in enumerate(term):
            value.node.uses.append((conv, ti, vi))
    if is_exit:
        block.exit_node = conv
    else:
        block.inner_nodes.append(conv)
    return Value(conv, 0, out_channels)
def placeholder(output_shape):
    """
    Placeholder.

    :param output_shape: Tuple[int, int, int]
        The outpu shape of the placeholder, which should be consistent with the input of the network.
    :return: Value
        The value represents the placeholder output.
    """
    name = new_name()
    holder = Placeholder(name, name, output_shape)
    return Value(holder, 0, holder.output_shape[0])
def get_input(ssnodes, block, nid, idn):
    """
    Merge the inputs of ssnodes and drop duplicate inputs
    """
    terms = list(itertools.chain(*[nd.inputs for nd in ssnodes]))
    iterms = [
        tuple((-1 if value.node is block.enter_node else nid[value.node], value.begin, value.end) for value in term) for
        term in terms]
    iterms = dict.fromkeys(sorted(iterms, key=lambda iterm: (len(iterm), iterm)))  # git rid of duplicates terms
    terms = [[Value(block.enter_node if ivalue[0] == -1 else idn[ivalue[0]], ivalue[1], ivalue[2]) for ivalue in iterm]
             for iterm in iterms]
    return terms
 def get_new_terms(terms, new_node, do_sort=True):
     nterms = []
     for ti, term in enumerate(terms):
         nterm = []
         for vi, value in enumerate(term):
             nv = out_dict[value.node]
             nterm.append(Value(nv[0], nv[1] + value.begin, nv[1] + value.end))
         nterms.append(nterm)
     if do_sort:
         nterms = sorted(nterms, key=lambda nterm: (len(nterm), nterm[0].node.name))  # git rid of duplicates terms
     for ti, term in enumerate(nterms):
         for vi, value in enumerate(term):
             value.node.uses.append((new_node, ti, vi))
     return nterms
def sequential(block: Block, hint_name, nodes: List[Node], is_exit=False):
    """
    Add a sequential compound operator at the end of given block. The compound operator contains a sequence of operators
    that would be executed sequentially.

    :param block: ios.Block
        The block to add the operator

    :param hint_name: str
        Any string used to tell what the operator represents, which can be the same with other hint name.

    :param nodes: List[ios.Node]
        The sequence of the operators in the compound operator.

    :param is_exit: boolean, default False
        Whether this operator is the exit operator of the block.

    :return: Value
        A value represents the output of the last operator in the operator sequence.
    """
    name = new_name()
    if hint_name is None:
        hint_name = name
    for i in range(len(nodes) - 1):
        nodes[i].infer_shape()
        nodes[i + 1].inputs = [[Value(nodes[i], 0, nodes[i].output_shape[0])]]
    seq = Sequential(name, hint_name, nodes, None)
    seq.infer_shape()
    for ti, term in enumerate(nodes[0].inputs):
        for vi, value in enumerate(term):
            value.node.uses.append((seq, ti, vi))
    if is_exit:
        block.exit_node = seq
    else:
        block.inner_nodes.append(seq)
    return Value(seq, 0, seq.output_shape[0])
 def merge_inputs(inputs: List[List[Value]]):
     merge_inputs_flag = True
     if merge_inputs_flag:
         while True:  # merge input
             merged = False
             for i in range(1, len(inputs)):
                 if len(inputs[i - 1]) > 1 or len(inputs[i]) > 1:
                     continue
                 va, vb = inputs[i - 1][0], inputs[i][0]
                 if va.node == vb.node and va.end == vb.begin:
                     vc = Value(va.node, va.begin, vb.end)
                     inputs = inputs[:i - 1] + [[vc]] + inputs[i + 1:]
                     merged = True
                     break
             if not merged:
                 break
     return inputs
def pool2d(block: Block,
           inputs,
           pool_type,
           kernel=(1, 1),
           stride=(1, 1),
           padding=(0, 0),
           is_exit=False):
    """
    Add a pooling operator at the end of given block.

    :param block: ios.Block
        The block to add the operator

    :param inputs: Sequence[Sequence[Value]]
        The inputs of the convolution. 'inputs' contains a list of terms. A term contains a list of values. The values
        in a term are added up. The terms are concatenated along with the channel dimension.

    :param pool_type: str, must be one of 'max', 'avg', 'global_max' and 'global_avg'
        The pooling type string.

    :param kernel: Tuple[int, int], default (1, 1)
        The kernel size of the pooling operator.

    :param stride: Tuple[int, int], default (1, 1)
        The stride of the pooling operator.

    :param padding: Tuple[int, int], default (1, 1)
        THe padding size of the pooling operator.

    :param is_exit: boolean, default False
        Whether this operator is the exit operator of the block.

    :return: Value
        A value represents the output of the operator.
    """
    name = new_name()
    pool = Pool(name, name, inputs, pool_type, kernel, stride, padding, None)
    pool.infer_shape()
    for ti, term in enumerate(inputs):
        for vi, value in enumerate(term):
            value.node.uses.append((pool, ti, vi))
    if is_exit:
        block.exit_node = pool
    else:
        block.inner_nodes.append(pool)
    return Value(pool, 0, pool.output_shape[0])