def _benchmark():
    """generate different problem instances and solve them using the Gurobi backend"""
    yield 'n', 'd', 'method', 'duration', 'error', 'avg_n', 'avg_e', 'avg_d', 'avg_v'

    ns = [2**e for e in range(3, 9)]
    ds = [4, 6]
    fs = [rgraph_normal, rgraph_scale_free]
    rs = list(range(3))
    logging.info(f'{ns} x {ds} x {fs} x {rs}')

    configurations = list(product(ns, ds, fs, rs))
    logging.info(f'run benchmark for {len(configurations)} examples')

    for n, d, factory, _ in tqdm(reversed(configurations)):
        # ensure that for each configuration one simulation succeeds
        while True:
            try:
                # generate example
                # ensure that at least 85% of the nodes remain in the example and the mapping is non trivial
                while True:
                    lts_1, lts_2, ref_mapping = generate_example(factory, n, d)

                    if all(
                            map(lambda l: len(l.states) >= .85 * n,
                                (lts_1, lts_2))) and 0 < len(ref_mapping) < 26:
                        break

                # prepare simulator
                similarities = BetaDistributedSimilarity(1.,
                                                         12.,
                                                         LABELS_LC,
                                                         LABELS_UC,
                                                         ref_mapping,
                                                         threshold=.3)
                simulator = LPSimulator(similarities,
                                        gap_penalty=.25,
                                        backend='GUROBI_CMD')

                # compute and assess mapping
                duration, mapping = timeit(
                    lambda: trace(lts_1, lts_2, simulator))
                error = len(
                    set.symmetric_difference(ref_mapping, mapping)) / max(
                        len(ref_mapping), len(mapping))

                # LTS stats
                deg_1 = lts_degrees(lts_1)
                deg_2 = lts_degrees(lts_2)
                avg_n = (len(lts_1.states) + len(lts_2.states)) / 2
                avg_e = (len(lts_1.transitions) + len(lts_2.transitions)) / 2
                avg_d = (sum(deg_1) + sum(deg_2)) / (len(deg_1) + len(deg_2))
                avg_v = (variance(deg_1) + variance(deg_2)) / 2

                yield n, d, factory.__name__, duration, error, avg_n, avg_e, avg_d, avg_v
                break

            except Exception as error:
                logging.debug(f'{type(error)}: {error}')
def examples_original():
    """examples (labels changed) from original paper"""
    logging.getLogger().setLevel(logging.INFO)

    simulator = LPSimulator(SIMILARITIES_1, .25)

    examples = [
        (LTS_P, LTS_Q),  # example from fig 3
        (LTS_P, LTS_S),  # example from fig 4
    ]

    for i, (lts_1, lts_2) in enumerate(examples, start=1):
        m_1 = trace(lts_1, lts_2, simulator)
        m_2 = trace(lts_2, lts_1, simulator)

        logging.info(f'm_1 = {m_1}')
        logging.info(f'm_2 = {m_2}')
        logging.info(f'm_1 U m_2 = {merge(m_1, m_2)}')
def take_measurement(
        n_grid: np.int, n_rays: np.int, r_theta: np.float64
) -> (np.ndarray, np.ndarray, np.ndarray, np.ndarray):
    """
    Take a measurement with the tomograph from direction r_theta.


    Arguments:
    n_grid: number of cells of grid in each direction
    n_rays: number of parallel rays
    r_theta: direction of rays (in radians)

    Return:
    intensities: measured intensities for all <n_rays> rays of the measurement. intensities[n] contains the intensity for the n-th ray
    ray_indices: indices of rays that intersect a cell
    isect_indices: indices of intersected cells
    lengths: lengths of segments in intersected cells

    The tuple (ray_indices[n], isect_indices[n], lengths[n]) stores which ray has intersected which cell with which length. n runs from 0 to the amount of ray/cell intersections (-1) of this measurement.

    Raised Exceptions:
    -

    Side Effects:
    -
    """

    # compute ray direction in Cartesian coordinates
    cs = np.cos(r_theta)
    sn = np.sin(r_theta)
    r_dir = np.array([-cs, -sn])

    # compute start positions for rays
    r_pos = np.zeros((n_rays, 2))
    for i, g in enumerate(np.linspace(-0.99, 0.99, n_rays)):
        r_pos[i] = np.array([cs - sn * g, sn + cs * g])
    else:
        r_pos[0] = np.array([cs, sn])

    # compute measures intensities for each ray
    intensities = np.zeros(n_rays)
    for i, rs in enumerate(r_pos):
        intensities[i] = trace(rs, r_dir)
    # take exponential fall off into account
    intensities = np.log(1.0 / intensities)

    # compute traversal distance in each grid cell
    ray_indices, isect_indices, lengths = grid_intersect(n_grid, r_pos, r_dir)

    return intensities, ray_indices, isect_indices, lengths
def _simulate(alpha, beta, gap_penalty=.25):
    similarities = BetaDistributedSimilarity(alpha, beta, LTS_P.labels, LTS_Q.labels, REFERENCE_MAPPING, .3)

    s_def = LPSimulator(similarities, gap_penalty)
    s_min = LPSimulator(similarities, gap_penalty, strategy=min)
    s_max = LPSimulator(similarities, gap_penalty, strategy=max)
    s_avg = LPSimulator(similarities, gap_penalty, strategy=s_average)

    m_pq = trace(LTS_P, LTS_Q, s_def)
    m_qp = trace(LTS_Q, LTS_P, s_def)

    return [
        greedy_mapper(similarities, .3),
        m_pq,
        {(b, a) for a, b in m_qp},
        merge(m_pq, m_qp, resolution=cr_prune),
        merge(m_pq, m_qp, resolution=cr_ignore),
        merge(m_pq, m_qp, strict=True, resolution=cr_prune),
        merge(m_pq, m_qp, strict=True, resolution=cr_ignore),
        trace(LTS_P, LTS_Q, s_min, bisimulation=True),
        trace(LTS_P, LTS_Q, s_max, bisimulation=True),
        trace(LTS_P, LTS_Q, s_avg, bisimulation=True),
    ]
def examples_paper():
    """examples used for seminar paper"""
    logging.getLogger().setLevel(logging.INFO)

    configurations = [
        ('I_J_L2', LTS_I, LTS_J, SIMILARITIES_2, .25),
        ('Q_P_L3', LTS_Q, LTS_P, SIMILARITIES_3, .25),
        ('O_U_L1', LTS_O, LTS_U, SIMILARITIES_1, .25),
        ('P_Q_L1', LTS_P, LTS_Q, SIMILARITIES_1, .25),
        ('Q_P_L1', LTS_Q, LTS_P, SIMILARITIES_1, .25),
        ('P_S_L1', LTS_P, LTS_S, SIMILARITIES_1, .25),
        ('S_P_L1', LTS_S, LTS_P, SIMILARITIES_1, .25),
        ('P_Q_L3', LTS_P, LTS_Q, SIMILARITIES_3, .25),
        ('Y_X_L1', LTS_Y, LTS_X, SIMILARITIES_1, .25),
        ('Z_X_L1', LTS_Z, LTS_X, SIMILARITIES_1, .25),
    ]

    for name, lts_1, lts_2, similarities, gap_penalty in configurations:
        out_dir = WORKING_DIRECTORY.joinpath(f'example_{name}')
        render = partial(Digraph.render, directory=out_dir, format='pdf', cleanup=True)

        out_dir.mkdir(exist_ok=True)

        simulator = LPSimulator(similarities, gap_penalty, lp_path=out_dir.joinpath('simulation.lp'))
        simulation = simulator.simulate(lts_1, lts_2)
        mapping, record = trace(lts_1, lts_2, simulator, record=True)
        logging.info(f'mapping {mapping}')

        # store setup
        with out_dir.joinpath('setup.txt').open(mode='wt') as f:
            print(f'Example: {name}\n', file=f)

            print(f'LTS_1: {repr(lts_1)}', file=f)
            print(f'LTS_2: {repr(lts_2)}', file=f)
            print(f'Simulator: {repr(simulator)}\n', file=f)

            print(f'L{repr(similarities)[1:]}\n', file=f)
            print(f'Q{repr(simulation)[1:]}\n', file=f)
            print(f'M = {mapping}', file=f)

        # render similarities
        with out_dir.joinpath('similarities.tex').open(mode='wt') as f:
            print('\\caption{Label Similarities $L$}', file=f)
            f.write(similarities.fmt(tablefmt='latex', numalign='decimal', floatfmt='.1f'))

        # render simulation
        with out_dir.joinpath('simulation.tex').open(mode='wt') as f:
            print(f'\\caption{{Simulation $Q$ of $LTS_{lts_1.name}$ by $LTS_{lts_2.name}$, p={gap_penalty}}}', file=f)
            f.write(simulation.fmt(tablefmt='latex', numalign='decimal', floatfmt='.4f'))

        # render call graph
        dot = Digraph(engine='dot')
        dot.attr(dpi='300')
        dot.attr('node', shape='box', style='filled', fillcolor='grey90')
        record.dot(dot)
        render(dot, 'callgraph')

        record.tex(out_dir)
        with out_dir.joinpath('caption_callgraph.tex').open(mode='wt') as f:
            print(
                f'\\caption{{Callgraph of ${{\\textsc{{Trace}}}}\\left('
                f'{lts_1.initial_state}, {lts_2.initial_state}'
                f'\\right)$}}',
                file=f)

        # render mapping
        edge_kwargs = {}
        for color, eq_class in zip(cycle(PALETTE), equivalence_classes(mapping)):
            for label in eq_class:
                edge_kwargs[label] = {'color': color, 'penwidth': '2'}

        dot = Digraph(engine='dot')
        dot.attr(compound='true', dpi='150')
        dot.attr('node', shape='circle', style='filled', fillcolor='grey70')
        dot.attr('edge', arrowhead='normal')

        # invisible dummy cluster
        with dot.subgraph(name='cluster_global') as gc:
            gc.attr(style='invis')
            gc.node('global_void', label='', style='invis')

        for i, lts in enumerate((lts_1, lts_2), start=1):
            with dot.subgraph(name=f'cluster_lts_{i}') as lc:
                lc.attr(label=lts.name)
                lc.attr(style='filled,rounded', color='grey95')
                lts.dot(lc, prefix=f'cluster_lts_{i}', void='global_void', **edge_kwargs)

        render(dot, 'mapping'.lower())

        with out_dir.joinpath('caption_mapping.tex').open(mode='wt') as f:
            m_str = ', '.join(map(lambda t: '(' + ','.join(t) + ')', mapping))
            print('\\caption{Mapping \\\\ $', file=f, end='')
            print(f'M = \\left\\lbrace {m_str} \\right\\rbrace', file=f, end='')
            print('$}', file=f)