class TypeBase(object):
class AutoType(TypeBase):
class BoolType(TypeBase):
class IntType(TypeBase):
class FloatingPointType(TypeBase):
class FloatType(FloatingPointType):
class DoubleType(FloatingPointType):
class FunctionType(TypeBase):
class ArrayType(TypeBase):
class IntegerArrayType(ArrayType):
class FloatArrayType(ArrayType):
    def __repr__(self):
    def __eq__(self, other):
    def __hash__(self):
    def __str__(self):
    def __str__(self):
    def __str__(self):
    def __str__(self):
    def __str__(self):
    def __str__(self):
    def __str__(self):
    def __init__(self, shape):
    def __str__(self):
    def __eq__(self, other):
    def __hash__(self):
def type_of(value):
def type_cast(dtype, value=None, top=False, bottom=False): ADD_OP = '+'
SUBTRACT_OP = '-'
UNARY_SUBTRACT_OP = '_'
MULTIPLY_OP = '*'
DIVIDE_OP = '/'
EXPONENTIATE_OP = 'exp'
EQUAL_OP = '=='
NOT_EQUAL_OP = '!='
GREATER_OP = '>'
GREATER_EQUAL_OP = '>='
LESS_OP = '<'
LESS_EQUAL_OP = '<='
UNARY_NEGATION_OP = '!'
AND_OP = '&&'
OR_OP = '||'
VARIABLE_OP = 'v'
EXTERNAL_OP = 'extern'
TERNARY_SELECT_OP = '?'
FIXPOINT_OP = 'fix'
FOR_OP = 'for'
STATE_GETTER_OP = '()'
BARRIER_OP = ','
INDEX_ACCESS_OP = 'access'
INDEX_UPDATE_OP = 'update'
SUBSCRIPT_OP = 'subscript'
ARITHMETIC_OPERATORS = [
    ADD_OP, SUBTRACT_OP, UNARY_SUBTRACT_OP, MULTIPLY_OP, DIVIDE_OP,
    EXPONENTIATE_OP, TERNARY_SELECT_OP, FIXPOINT_OP, FOR_OP, BARRIER_OP,
]
COMPARISON_OPERATORS = [
    EQUAL_OP, NOT_EQUAL_OP, GREATER_OP, LESS_OP, GREATER_EQUAL_OP,
    LESS_EQUAL_OP,
]
BOOLEAN_OPERATORS = COMPARISON_OPERATORS + [
    UNARY_NEGATION_OP, AND_OP, OR_OP
]
TRADITIONAL_OPERATORS = [
    ADD_OP, SUBTRACT_OP, UNARY_SUBTRACT_OP, MULTIPLY_OP, DIVIDE_OP,
    EQUAL_OP, NOT_EQUAL_OP, GREATER_OP, LESS_OP, GREATER_EQUAL_OP,
    LESS_EQUAL_OP, UNARY_NEGATION_OP, AND_OP, OR_OP
]
SPECIAL_OPERATORS = [
    TERNARY_SELECT_OP, FIXPOINT_OP
]
OPERATORS = BOOLEAN_OPERATORS + ARITHMETIC_OPERATORS + SPECIAL_OPERATORS
UNARY_OPERATORS = [UNARY_SUBTRACT_OP, UNARY_NEGATION_OP]
BINARY_OPERATORS = list(set(OPERATORS) - set(UNARY_OPERATORS))
ASSOCIATIVITY_OPERATORS = [ADD_OP, MULTIPLY_OP, EQUAL_OP, AND_OP, OR_OP]
COMMUTATIVITY_OPERATORS = ASSOCIATIVITY_OPERATORS
COMMUTATIVE_DISTRIBUTIVITY_OPERATOR_PAIRS = [(MULTIPLY_OP, ADD_OP)]
LEFT_DISTRIBUTIVITY_OPERATOR_PAIRS = \
    COMMUTATIVE_DISTRIBUTIVITY_OPERATOR_PAIRS
RIGHT_DISTRIBUTIVITY_OPERATOR_PAIRS = \
    COMMUTATIVE_DISTRIBUTIVITY_OPERATOR_PAIRS
LEFT_DISTRIBUTIVITY_OPERATORS, LEFT_DISTRIBUTION_OVER_OPERATORS = \
    list(zip(*LEFT_DISTRIBUTIVITY_OPERATOR_PAIRS))
RIGHT_DISTRIBUTIVITY_OPERATORS, RIGHT_DISTRIBUTION_OVER_OPERATORS = \
    list(zip(*RIGHT_DISTRIBUTIVITY_OPERATOR_PAIRS))
COMPARISON_NEGATE_DICT = {
    LESS_OP: GREATER_EQUAL_OP,
    LESS_EQUAL_OP: GREATER_OP,
    GREATER_OP: LESS_EQUAL_OP,
    GREATER_EQUAL_OP: LESS_OP,
    EQUAL_OP: NOT_EQUAL_OP,
    NOT_EQUAL_OP: EQUAL_OP,
}
COMPARISON_MIRROR_DICT = {
    LESS_OP: GREATER_OP,
    LESS_EQUAL_OP: GREATER_EQUAL_OP,
    GREATER_OP: LESS_OP,
    GREATER_EQUAL_OP: LESS_EQUAL_OP,
    EQUAL_OP: EQUAL_OP,
    NOT_EQUAL_OP: NOT_EQUAL_OP,
} @cached
def expression_factory(op, *args):
    from soap.expression import operators
    from soap.expression.arithmetic import (
        UnaryArithExpr, BinaryArithExpr, TernaryArithExpr, SelectExpr
    )
    from soap.expression.boolean import (
        UnaryBoolExpr, BinaryBoolExpr, TernaryBoolExpr
    )
    from soap.expression.fixpoint import FixExpr
    from soap.expression.linalg import Subscript, AccessExpr, UpdateExpr

    global op_expr_cls_map
    if not op_expr_cls_map:
        op_expr_cls_map = {
            operators.SUBSCRIPT_OP: Subscript,
            operators.INDEX_ACCESS_OP: AccessExpr,
            operators.INDEX_UPDATE_OP: UpdateExpr,
            operators.FIXPOINT_OP: FixExpr,
            operators.TERNARY_SELECT_OP: SelectExpr,
        }
    cls = op_expr_cls_map.get(op)
    if cls:
        return cls(*args)

    if op in operators.ARITHMETIC_OPERATORS:
        class_list = [UnaryArithExpr, BinaryArithExpr, TernaryArithExpr]
    elif op in operators.BOOLEAN_OPERATORS:
        class_list = [UnaryBoolExpr, BinaryBoolExpr, TernaryBoolExpr]
    else:
        raise ValueError('Unknown operator {}.'.format(op))
    try:
        cls = class_list[len(args) - 1]
    except IndexError:
        raise ValueError('Too many arguments.')
    return cls(op, *args) class Variable(ArithmeticMixin, BooleanMixin, BinaryExpression):
    """The variable class."""
    __slots__ = ()
    _str_brackets = False

    def __init__(self, name, dtype=auto_type):
        super().__init__(VARIABLE_OP, name, dtype)

    @property
    def name(self):
        return self.args[0]

    @property
    def dtype(self):
        return self.args[1]

    def format(self):
        return '{}'.format(self.name)

    def __repr__(self):
        return '{cls}({name!r}, {dtype!r})'.format(
            cls=self.__class__.__name__, name=self.name, dtype=self.dtype) class Subscript(Expression):
    __slots__ = ()
    _str_brackets = False

    def __init__(self, *subscript):
        super().__init__(SUBSCRIPT_OP, *subscript)

    def __iter__(self):
        return iter(self.args)

    def format(self):
        return ''.join('[' + a.format() + ']' for a in self.args)

    def __repr__(self):
        return '{}({!r})'.format(self.__class__.__name__, self.args) def parse(program, decl=None):
    decl = decl or {}
    visitor = _ProgramVisitor(decl)
    program = _preprocess(program)
    flow = visitor.parse(program)
    return ProgramFlow(flow) class IntegerInterval(Interval):
    """The interval containing integer values."""
    def __init__(self, v=None, top=False, bottom=False):
        super().__init__(v, top=top, bottom=bottom)
        try:
            if self.min not in (-inf, inf):
                self.min = mpz(self.min)
            if self.max not in (-inf, inf):
                self.max = mpz(self.max)
        except AttributeError:
            'The interval is a top or bottom.'

    def __truediv__(self, other):
        return FloatInterval(self) / other @cached
def label(expr, state, out_vars, context=None, fusion=True):
    from soap.semantics.state import MetaState

    if state is not None:
        if state and state.is_bottom():
            state = None

    context = context or LabelContext(expr)

    if isinstance(expr, collections.Mapping):
        out_vars = out_vars or expr.keys()
        expr = MetaState({k: v for k, v in expr.items() if k in out_vars})

    lab, env = LabelGenerator(context).execute(expr, state)

    if fusion and isinstance(expr, collections.Mapping):
        from soap.semantics.state.fusion import fusion
        env = fusion(env, out_vars)

    return LabelSemantics(lab, env) def Label(self, statement, bound, invariant):
    label_value = fresh_int(statement, _lmap=self.lmap)
    return self.label_class(
        statement, bound=bound, invariant=invariant,
        context_id=self.description, _label_value=label_value) def dependence_vector(iter_vars, iter_slices, source, sink, invariant=None):
    """
    Uses ISL for dependence testing and returns the dependence vector.

    iter_vars: Iteration variables
    iter_slices: Iteration starts, stops and steps; stop is inclusive!
    source, sink: Subscript objects
    invariant: Loop invariant
    """
    if len(source.args) != len(sink.args):
        raise ValueError('Source/sink subscript length mismatch.')

    inner_most_iter_var = iter_vars[-1]
    dist_vars = []
    constraints = []
    exists_vars = set()
    for var, iter_slice in zip(iter_vars, iter_slices):
        dist_var = '__dist_{}'.format(var.name)
        dist_vars.append(dist_var)

        lower, upper = iter_slice.start, iter_slice.stop
        constraints.append(
            '{dist_var} = __snk_{iter_var} - __src_{iter_var}'
            .format(dist_var=dist_var, iter_var=var))

        bound_cstr = ''
        if lower != -inf:
            bound_cstr += '{lower} <= '
        bound_cstr += '__src_{iter_var} {op} __snk_{iter_var}'
        if upper != inf:
            bound_cstr += ' < {upper}'
        compare_op = '<' if var == inner_most_iter_var else '<='
        bound_cstr = bound_cstr.format(
            iter_var=var, op=compare_op, lower=iter_slice.start,
            upper=iter_slice.stop)
        constraints.append(bound_cstr)

        step = iter_slice.step
        if step <= 0:
            raise NotImplementedError(
                'For now we only deal with positive step size.')
        if step == 1:
            continue
        for node_type in ['src', 'snk']:
            exists_vars.add(Variable(
                '__stride_{}_{}'.format(node_type, var.name), var.dtype))
            constraints.append(
                '__stride_{node_type}_{iter_var} * {step_size} = '
                '__{node_type}_{iter_var}'.format(
                    node_type=node_type, iter_var=var, step_size=step))

    for src_idx, snk_idx in zip(source.args, sink.args):
        if not (is_isl_expr(src_idx) and is_isl_expr(snk_idx)):
            raise NotImplementedError(
                'Non-linear expression cannot be handled by ISL.')
        src_idx = rename_var_in_expr(src_idx, iter_vars, '__src_{}')
        snk_idx = rename_var_in_expr(snk_idx, iter_vars, '__snk_{}')
        constraints.append('{} = {}'.format(src_idx, snk_idx))
        exists_vars |= expression_variables(src_idx)
        exists_vars |= expression_variables(snk_idx)
        exists_vars |= {
            Variable('__src_{}'.format(v.name), v.dtype) for v in iter_vars}
        exists_vars |= {
            Variable('__snk_{}'.format(v.name), v.dtype) for v in iter_vars}

    do_invar_vars = exists_vars if invariant else []
    for var in do_invar_vars:
        if var.name.startswith('__'):
            # __src_*, __snk_*, __stride_*
            continue
        lower, upper = invariant[var]
        if lower != -inf:
            constraints.append('{lower} <= {var}'.format(lower=lower, var=var))
        if upper != inf:
            constraints.append('{var} <= {upper}'.format(upper=upper, var=var))

    problem = \
        '{{ [{dist_vars}] : exists ( {exists_vars} : \n{constraints} \n) }}'
    problem = problem.format(
        dist_vars=', '.join(dist_vars),
        iter_vars=', '.join(v.name for v in iter_vars),
        constraints=' and \n'.join(constraints),
        exists_vars=', '.join(v.name for v in exists_vars))

    basic_set = islpy.BasicSet(problem)
    dist_vect_list = []
    basic_set.lexmin().foreach_point(dist_vect_list.append)

    if not dist_vect_list:
        raise ISLIndependenceException('Source and sink is independent.')

    if len(dist_vect_list) != 1:
        raise ValueError('The function lexmin() should return a single point.')

    raw_dist_vect = dist_vect_list.pop()
    dist_vect = []
    for i, (_, iter_slice) in enumerate(zip(iter_vars, iter_slices)):
        val = raw_dist_vect.get_coordinate_val(islpy.dim_type.set, i)
        val = val.to_python() / iter_slice.step
        dist_vect.append(val)
    return tuple(dist_vect) def dependence_distance(dist_vect, iter_slices):
    shape_prod = 1
    dist_sum = 0
    for dist, iter_slice in zip(reversed(dist_vect), reversed(iter_slices)):
        dist_sum += shape_prod * dist
        shape_prod *= iter_point_count(iter_slice)
    return dist_sum class ISLIndependenceException(Exception):
    """No dependence.  """ @cached
def schedule_graph(expr, out_vars=None, **kwargs):
    from soap.semantics import label
    from soap.semantics.schedule.graph import (
        SequentialScheduleGraph, LoopScheduleGraph
    )
    if isinstance(expr, FixExpr):
        return LoopScheduleGraph(expr, **kwargs)
    label, env = label(expr, None, out_vars, fusion=False)
    if not isinstance(expr, collections.Mapping):
        # expressions do not have out_vars, but have an output, in this case
        # ``label`` is its output variable
        out_vars = [label]
    return SequentialScheduleGraph(env, out_vars, **kwargs) class SequentialScheduleGraph(ScheduleGraph):
    def __init__(
            self, env, out_vars, round_values=None,
            sequentialize_loops=True, scheduler=None, recurrences=None):
        super().__init__(
            env, out_vars, round_values=round_values,
            sequentialize_loops=sequentialize_loops, scheduler=scheduler)
        self.recurrences = recurrences

    def recurrence_latency(self):
        try:
            return self._recurrence_latency
        except AttributeError:
            pass

        from soap.transformer.partition import PartitionLabel
        # create a new schedule with expressions compatible with
        # recurrences specification
        schedule = self.schedule()
        total_latency = self.max_latency(schedule)

        new_schedule = collections.defaultdict(list)
        for node, interval in schedule.items():
            expr = node
            if isinstance(node, PartitionLabel):
                continue
            if isinstance(node, Label):
                expr = self.env[node]
            if isinstance(expr, (AccessExpr, UpdateExpr)):
                expr = label_to_expr(node)
                var = expr.true_var()
                subscript = expr.subscript
                if isinstance(expr, AccessExpr):
                    expr = AccessExpr(var, subscript)
                elif isinstance(expr, UpdateExpr):
                    expr = UpdateExpr(var, subscript, None)
            # FIXME linearize subscript into canonical forms?
            new_schedule[expr].append(interval)

        # finds the maximum recurrence-weigthed latency
        # from any pair of from_expr and to_expr
        max_latency = 0
        for from_expr, to_expr, distance in self.recurrences:
            from_schedule = new_schedule.get(from_expr)
            if from_schedule is None:
                continue

            begin_latency = total_latency
            for from_begin, _ in from_schedule:
                begin_latency = min(begin_latency, from_begin)

            to_schedule = new_schedule.get(to_expr)
            if to_schedule is None:
                end_latency = total_latency
            else:
                end_latency = 0
                for _, to_end in to_schedule:
                    end_latency = max(end_latency, to_end)

            latency = end_latency - begin_latency
            if latency < 0:
                raise ValueError('Latency cannot be negative.')
            latency /= distance
            max_latency = max(max_latency, latency)

        self._recurrence_latency = max_latency
        return max_latency

    def sequential_latency(self):
        try:
            return self._sequential_latency
        except AttributeError:
            pass
        schedule = self.schedule()
        latency = self.max_latency(schedule)
        if self.round_values:
            try:
                latency = int(math.ceil(latency))
            except OverflowError:
                latency = float('inf')
        self._sequential_latency = latency
        return self._sequential_latency

    def latency(self):
        if self.recurrences:
            latency = self.recurrence_latency()
            if latency != 0:
                return latency
        return self.sequential_latency()

    def sequential_resource(self):
        try:
            return self._sequential_resource
        except AttributeError:
            pass

        try:
            control_points = self.control_points()
        except OverflowError:
            logger.warning('Unable to schedule code with infinite latency.')
            control_points = [({node}, 1) for node in self.nodes()]

        min_alloc_map = {}
        total_map = {}
        total_active_nodes = set()
        for active_nodes, cycles in control_points:
            alloc_map = {}
            for node in active_nodes:
                res_total_map, res_lower_map = self.node_resource(node)
                if node not in total_active_nodes:
                    resource_map_add(total_map, res_total_map)
                    total_active_nodes.add(node)
                resource_map_add(alloc_map, res_lower_map)
            resource_map_min(min_alloc_map, alloc_map)

        self._sequential_resource = (total_map, min_alloc_map)
        return self._sequential_resource

    resource = sequential_resource class LoopScheduleGraph(SequentialScheduleGraph):
    def __init__(
            self, fix_expr, round_values=None, sequentialize_loops=True,
            scheduler=None, **kwargs):
        extractor = ForLoopNestExtractor(fix_expr)
        is_pipelined = extractor.is_for_loop_nest
        iter_vars = extractor.iter_vars
        kernel = extractor.label_kernel
        out_vars = sorted(kernel, key=str)
        super().__init__(
            kernel, out_vars, round_values=round_values,
            sequentialize_loops=sequentialize_loops,
            scheduler=scheduler)
        self.is_pipelined = is_pipelined
        self.fix_expr = fix_expr
        self.iter_vars = iter_vars
        self.iter_slices = extractor.iter_slices
        self.is_for_loop = extractor.is_for_loop
        if self.is_for_loop:
            self.iter_slice = extractor.iter_slice
        self._init_loop_graph()

    def _init_loop_graph(self):
        if not self.is_pipelined:
            return
        loop_graph = self.graph.copy()
        recurrences = set()
        self._init_variable_loops(loop_graph, recurrences)
        self._init_array_loops(loop_graph, recurrences)
        self.loop_graph = loop_graph
        self.recurrences = frozenset(recurrences)

    def _init_variable_loops(self, loop_graph, recurrences):
        for from_node in self.graph.nodes():
            if not isinstance(from_node, InputVariable):
                continue
            if isinstance(from_node.dtype, ArrayType):
                continue

            out_var = Variable(from_node.name, from_node.dtype)
            if out_var not in self.env:
                continue

            # variable is input & output, should have a self-loop
            attr_dict = {
                'type': DependenceType.flow,
                'latency': 0,
                'distance': 1,
            }
            loop_graph.add_edge(from_node, out_var, attr_dict)
            recurrences.add((out_var, out_var, 1))

    def _init_array_loops(self, loop_graph, recurrences):
        nodes = self._array_nodes(self.graph)
        for from_node, to_node in itertools.product(nodes, repeat=2):
            self._add_array_loop(
                loop_graph, recurrences, from_node, to_node)

    def _add_array_loop(
            self, loop_graph, recurrences, from_node, to_node):
        # we do it for flow dependence only WAR and WAW are not dependences
        # that impact II, as read/write accesses can always be performed
        # consecutively.
        from_expr = label_to_expr(from_node)
        to_expr = label_to_expr(to_node)
        if from_expr.true_var() != to_expr.true_var():
            # access different arrays
            return

        from_op, to_op = from_expr.op, to_expr.op
        check = (from_op == operators.INDEX_ACCESS_OP and
                 to_op == operators.INDEX_UPDATE_OP)
        if not check:
            return
        dep_type = _edge_type_map[from_op, to_op]

        if dep_type == DependenceType.independent:
            # RAR is not a dependence
            return
        elif dep_type == DependenceType.flow:
            latency = self.node_latency(to_node)
        elif dep_type == DependenceType.anti:
            latency = 1 - self.node_latency(from_node)
        elif dep_type == DependenceType.output:
            latency = 1 + self.node_latency(to_node)
            latency -= self.node_latency(from_node)
        else:
            raise TypeError('Unrecognized dependence type.')

        source_expr = to_expr.subscript
        sink_expr = from_expr.subscript
        if subscripts_always_equal(source_expr, sink_expr):
            # quick hack for the case when read/write accesses same location,
            # Vivado HLS can simpliy iterate on a register
            latency = -self.node_latency(from_node)

        distance = dependence_eval(
            self.iter_vars, self.iter_slices, source_expr, sink_expr)
        if distance is None:
            # no dependence
            return

        attr_dict = {
            'type': dep_type,
            'latency': latency,
            'distance': distance,
        }
        loop_graph.add_edge(from_node, to_node, attr_dict)

        if dep_type != DependenceType.flow:
            return
        from_expr = AccessExpr(from_expr.true_var(), from_expr.subscript)
        to_expr = UpdateExpr(
            to_expr.true_var(), to_expr.subscript, Variable('__dont_care'))
        recurrences.add((from_expr, to_expr, distance))

    def init_graph(self):
        try:
            return self._init_graph
        except AttributeError:
            pass
        init_state = self.fix_expr.init_state
        if isinstance(init_state, Label):
            return None
        _, init_env = label(self.fix_expr.init_state, None, None, fusion=False)
        self._init_graph = SequentialScheduleGraph(
            init_env, init_env, round_values=self.round_values,
            sequentialize_loops=self.sequentialize_loops,
            scheduler=self.scheduler)
        return self._init_graph

    def resource_counts(self):
        try:
            return self._resource_counts
        except AttributeError:
            pass
        operator_map = collections.defaultdict(int)
        memory_map = collections.defaultdict(int)
        for node in self.graph.nodes():
            expr, dtype, op = self.node_expr(node)
            if expr is None:
                continue
            if op in [operators.INDEX_ACCESS_OP, operators.INDEX_UPDATE_OP]:
                memory_map[label_to_expr(node).true_var()] += 1
                continue
            operator_map[dtype, op] += 1
        self._resource_counts = (operator_map, memory_map)
        return self._resource_counts

    def initiation_interval(self):
        try:
            return self._initiation_interval
        except AttributeError:
            pass
        if not self.is_pipelined:
            self._initiation_interval = self.depth()
        else:
            _, access_map = self.resource_counts()
            res_mii = res_init_int(access_map)
            self._initiation_interval = rec_init_int_search(
                self.loop_graph, res_mii, round_values=self.round_values)
        return self._initiation_interval

    def depth(self):
        return self.sequential_latency()

    def trip_count(self):
        if not self.is_pipelined:
            if self.is_for_loop:
                return iter_point_count(self.iter_slice)
            else:
                logger.warning(
                    'Failed to find trip count for loop',
                    self.fix_expr.format())
                return float('inf')
        trip_counts = [iter_point_count(s) for s in self.iter_slices]
        return functools.reduce(lambda x, y: x * y, trip_counts)

    def loop_latency(self):
        try:
            return self._loop_latency
        except AttributeError:
            pass
        init_graph = self.init_graph()
        if init_graph:
            init_latency = init_graph.sequential_latency()
        else:
            init_latency = 0
        trip_count = self.trip_count()
        depth = self.depth()
        ii = self.initiation_interval()
        loop_latency = (trip_count - 1) * ii + depth
        self._loop_latency = init_latency + loop_latency
        logger.debug(
            'Initiation interval: {}, trip_count: {}, depth: {}, latency: {}'
            .format(ii, trip_count, depth, loop_latency))
        return self._loop_latency

    latency = loop_latency

    def loop_resource(self):
        try:
            return self._loop_resource
        except AttributeError:
            pass
        if not self.is_pipelined:
            total_map, alloc_map = self.sequential_resource()
        else:
            total_map, _ = self.resource_counts()
            alloc_map = {}
            ii = self.initiation_interval()
            alloc_map = {
                dtype_op: count / ii
                for dtype_op, count in total_map.items()}
            if self.round_values:
                resource_ceil(alloc_map)
            # add additional adders for incrementing loop nest iterators
            resource_map_add(total_map, {
                (int_type, operators.ADD_OP): len(self.iter_vars) - 1,
            })

        init_graph = self.init_graph()
        if init_graph:
            init_total_map, init_min_alloc_map = \
                init_graph.sequential_resource()
            resource_map_add(total_map, init_total_map)
            resource_map_min(alloc_map, init_min_alloc_map)

        self._loop_resource = (total_map, alloc_map)
        return self._loop_resource

    resource = loop_resource LATENCY_TABLE = DEVICE_LATENCY_TABLE[context.device, context.frequency] def flow_to_meta_state(flow):
    id_state = MetaState({v: v for v in flow.vars(output=False)})
    return id_state.transition(flow)
import unittest
from soap.context import context
from soap.datatype import (
    auto_type, int_type, float_type, FloatArrayType, ArrayType
)
from soap.expression import (
    operators, Variable, Subscript, expression_factory
)
from soap.parser import parse
from soap.semantics.error import IntegerInterval
from soap.semantics.functions.label import label
from soap.semantics.label import Label
from soap.semantics.schedule.distance import (
    dependence_vector, dependence_distance, ISLIndependenceException
)
from soap.semantics.schedule.common import schedule_graph
from soap.semantics.schedule.graph import (
    LoopScheduleGraph, SequentialScheduleGraph
)
from soap.semantics.schedule.table import LATENCY_TABLE
from soap.semantics.state import flow_to_meta_state
        #pragma soap input float a[30], int i
        #pragma soap output a
        a[i] = (a[i - 1] + (a[i - 2] + a[i - 3])) / 3;
        """
        graph = self._to_graph(program)
        im1 = expression_factory(
            operators.SUBTRACT_OP, self.i, IntegerInterval(1))
        access = expression_factory(
            operators.INDEX_ACCESS_OP, self.a, Subscript(im1))
        update = expression_factory(
            operators.INDEX_UPDATE_OP, self.a, Subscript(self.i), None)
        graph.recurrences = [(access, update, 1)]
        expect_latency = LATENCY_TABLE[float_type][operators.INDEX_ACCESS_OP]
        expect_latency += LATENCY_TABLE[float_type][operators.ADD_OP]
        expect_latency += LATENCY_TABLE[float_type][operators.DIVIDE_OP]
        expect_latency += LATENCY_TABLE[ArrayType][operators.INDEX_UPDATE_OP]
        self.assertEqual(graph.latency(), expect_latency)

    def test_loop_sequentialization(self):
        program = """
        #pragma soap input float x[30], float y[30]
        #pragma soap output z
        for (int i = 1; i < 30; i = i + 1)
            x[i] = x[i - 1] + 1;
        for (int j = 1; j < 20; j = j + 1)
            y[j] = y[j - 1] + 1;
        float z = x[0] + y[0];
        """
        loop_ii = LATENCY_TABLE[float_type][operators.INDEX_ACCESS_OP]
        loop_ii += LATENCY_TABLE[float_type][operators.ADD_OP]
        loop_ii += LATENCY_TABLE[ArrayType][operators.INDEX_UPDATE_OP]
        loop_depth = loop_ii
        loop_depth += LATENCY_TABLE[int_type][operators.SUBTRACT_OP]
        trip_count_i = 29
        trip_count_j = 19
        seq_loop_latency = (trip_count_i + trip_count_j - 2) * loop_ii
        seq_loop_latency += loop_depth * 2
        seq_latency = seq_loop_latency
        seq_latency += LATENCY_TABLE[float_type][operators.INDEX_ACCESS_OP]
        seq_latency += LATENCY_TABLE[float_type][operators.ADD_OP]

        graph = self._to_graph(program)
        graph.sequentialize_loops = True
        self.assertAlmostEqual(graph.latency(), seq_latency, delta=2)

        par_loop_latency = (max(trip_count_i, trip_count_j) - 1) * loop_ii
        par_loop_latency += loop_depth
        par_latency = par_loop_latency
        par_latency += LATENCY_TABLE[float_type][operators.INDEX_ACCESS_OP]
        par_latency += LATENCY_TABLE[float_type][operators.ADD_OP]

        graph = self._to_graph(program)
        graph.sequentialize_loops = False
        self.assertAlmostEqual(graph.latency(), par_latency, delta=2)


class TestFullSchedule(_CommonMixin):
    def assertStatisticsAlmostEqual(
            self, graph, expect_latency, expect_resource, delta=1):
        latency = graph.latency()
        self.assertAlmostEqual(latency, expect_latency, delta=delta)
        total_resource, resource = graph.resource()
        self.assertTrue(set(expect_resource) <= set(resource))
        for dtype_op, expect_count in expect_resource.items():
            count = resource[dtype_op]
            try:
                self.assertAlmostEqual(count, expect_count, delta=0.01)
            except AssertionError:
                raise AssertionError(
                    'Resource count mismatch for operator {}: {} != {}'
                    .format(dtype_op, count, expect_count))

    def test_simple_flow(self):
        program = """
        #pragma soap input float a[30]
        #pragma soap output a
        for (int i = 0; i < 10; i = i + 1)
            a[i + 3] = a[i] + i;
        """
        meta_state = flow_to_meta_state(parse(program))
        distance = 3
        trip_count = 10
        graph = schedule_graph(meta_state, [self.a])
        depth = LATENCY_TABLE[float_type][operators.INDEX_ACCESS_OP]
        depth += LATENCY_TABLE[float_type][operators.ADD_OP]
        depth += LATENCY_TABLE[ArrayType][operators.INDEX_UPDATE_OP]
        expect_ii = depth / distance
        expect_latency = expect_ii * (trip_count - 1) + depth
        expect_resource = {
            (int_type, operators.ADD_OP): 2 / expect_ii,
            (float_type, operators.ADD_OP): 1 / expect_ii,
        }
        self.assertStatisticsAlmostEqual(
            graph, expect_latency, expect_resource)

    def test_loop_nest_flow(self):
        program = """
        #pragma soap input float a[30]
        #pragma soap output a
        int j = 0;
        while (j < 10) {
            int i = 0;
            while (i < 10) {
                a[i + j + 3] = a[i + j] + i;
                i = i + 1;
            }
            j = j + 1;
        }
        """
        meta_state = flow_to_meta_state(parse(program))
        graph = schedule_graph(meta_state[self.a], [self.a])
        distance = 3
        trip_count = 10 * 10
        depth = LATENCY_TABLE[float_type][operators.INDEX_ACCESS_OP]
        depth += LATENCY_TABLE[float_type][operators.ADD_OP]
        depth += LATENCY_TABLE[ArrayType][operators.INDEX_UPDATE_OP]
        expect_ii = depth / distance
        add_latency = LATENCY_TABLE[int_type][operators.ADD_OP]
        expect_latency = expect_ii * (trip_count - 1) + depth + add_latency
        expect_resource = {

            (int_type, operators.ADD_OP): 3 / expect_ii,