Пример #1
0
class Unsummarize(Transform):
    """
    Undo a summarization.
    Useful when used in combination with other transforms, 
    e.g. -v{gluballs,pluck,unsummarize}
    """
    _name = "Unsummarize"
    _args = [
        Arg("pno", int, "id of particle to unsummarize", default=None),
        Arg("vno", int, "id of vertex to unsummarize", default=None)
    ]

    def __call__(self, graph_view):
        if self.options["pno"] is None and self.options["vno"] is None:
            retry = False
            for obj in list(graph_view.particles) + list(graph_view.vertices):
                if isinstance(obj, Summary):
                    obj.undo_summary()
        else:
            if not self.options["pno"] is None:
                for obj in list(graph_view.particles):
                    if isinstance(obj, Summary):
                        if self.options["pno"] in obj.particle_numbers:
                            vs, ve = obj.start_vertex, obj.end_vertex
                            if isinstance(vs, Summary):
                                vs.undo_summary()
                            if isinstance(ve, Summary):
                                ve.undo_summary()
                            obj.undo_summary()
            if not self.options["vno"] is None:
                for obj in list(graph_view.vertices):
                    if isinstance(obj, Summary):
                        if self.options["vno"] in obj.vertex_number:
                            obj.undo_summary()
Пример #2
0
class LineWidthPt(Style):
    """
    Make the particle line width dependent on the transverse momentum.
    """
    _name = "LineWidthPt"
    _args = [
        Arg("scale", float, "scale of the line effects", default=1.0),
        Arg("min", float, "minimal width of a line", default=0.1),
    ]

    def __call__(self, layout):
        if isinstance(layout, FeynmanLayout):
            elements = layout.edges
        else:
            elements = layout.nodes
            for edge in layout.edges:
                particle = edge.going
                if hasattr(particle, "pt"):
                    edge.style_args["stroke-width"] = self.options[
                        "min"] + self.options["scale"] * ln(particle.pt +
                                                            1) * 0.1

        for element in elements:
            particle = element.item
            if hasattr(particle, "pt"):
                element.style_args["stroke-width"] = self.options[
                    "min"] + +self.options["scale"] * ln(particle.pt + 1) * 0.1
Пример #3
0
class Jets(Transform):
    _name = "Jets"
    _args = [
        Arg("algorithm",
            str,
            "Jet Algorithm",
            default="antikt",
            choices=_jet_algos),
        Arg("r", float, "Delta R for the Jet Algorithm", default=0.4),
        Arg("n_max", int, "Maximum number of jets to form", default=5),
        Arg("tracks",
            Arg.bool,
            "Only cluster charged particles",
            default=False)
    ]

    def __call__(self, graph_view):
        from mcviz.jet import cluster_jets, JetAlgorithms
        from math import hypot
        track_jets = self.options["tracks"]
        if track_jets:
            raise Exception("Unimplemented!")

        def pselect(p):
            return p.final_state and not p.invisible and (p.charge != 0 if
                                                          track_jets else True)

        final_state_particles = [p for p in graph_view.particles if pselect(p)]
        jets = cluster_jets(final_state_particles,
                            getattr(JetAlgorithms, self.options["algorithm"]))
        print "Converted %i final state particles into %i jets" % (
            len(final_state_particles), len(jets))

        def pt(jet):
            return hypot(*jet.p[:2])

        for i, jet in enumerate(sorted(jets, key=pt, reverse=True)):
            print "Created jet: np=%2i, %r, %r" % (len(
                jet.particles), jet.p, jet.e)
            if i >= self.options["n_max"]:
                break
            jet_start_vertices = set(p.start_vertex for p in jet.particles)
            jet_end_vertices = set(p.end_vertex for p in jet.particles)
            vs_summary = graph_view.summarize_vertices(jet_start_vertices)
            ve_summary = graph_view.summarize_vertices(jet_end_vertices)
            p_summary = graph_view.summarize_particles(jet.particles)
            vs_summary.tag("jet")
            ve_summary.tag("jet")
            p_summary.tag("jet")
            p_summary.tag("jet_{0:d}".format(i))
Пример #4
0
class DualDecongestedHad(DualLayout):
    """
    UNDOCUMENTED
    Takes the all-to-all connections at the hadronization step and replaces it
    with a all-to-one-to-all vertex labelled "A miracle occurs"
    """
    _name = "DualDecongestedHad"
    _args = [
        Arg("label",
            str,
            "label for the hadronization vertex",
            default="A miracle occurs")
    ]

    def get_vertex(self, vertex, node_style=None):
        if vertex.hadronization:
            items = []
            had_node = LayoutNode(vertex, label=self.options["label"])
            had_node.width = 5.0
            had_node.height = 1.0
            items.append(had_node)

            for particle in vertex.incoming:
                items.append(LayoutEdge(vertex, particle, vertex))
            for particle in vertex.outgoing:
                items.append(LayoutEdge(vertex, vertex, particle))

            return items
        else:
            return super(DualDecongestedHad,
                         self).get_vertex(vertex, node_style)
Пример #5
0
class FancyLines(Style):
    """
    Draw particle lines with arrows on them, and draw gluons with curls
    """
    _name = "FancyLines"
    _args = [
        Arg("scale", float, "scale of the line effects", default=1.0),
    ]

    def __call__(self, layout):
        """ set fancy line types, curly gluons, wavy photons etc."""
        for edge in layout.edges:
            particle = edge.item
            edge.style_args["scale"] = 0.2 * self.options["scale"]
            if not hasattr(particle, "gluon"):
                return
            # colouring
            if "jet" in particle.tags:
                edge.style_line_type = "jet"
            elif "cluster" in particle.tags:
                edge.style_line_type = "hadron"
                edge.style_args["scale"] = 0.2 * self.options["scale"]
                edge.style_args["stroke-width"] = 0.2
            elif "cut_summary" in particle.tags:
                edge.style_line_type = "cut"
                try:
                    edge.style_args["n"] = edge.n_represented
                except AttributeError:
                    pass  # Edge has no n_represented property
            elif particle.gluon:
                edge.style_args["scale"] = 0.2 * self.options["scale"]
                edge.style_line_type = "gluon"
            elif particle.photon:
                if particle.final_state:
                    edge.style_line_type = "final_photon"
                else:
                    edge.style_line_type = "photon"
            elif particle.invisible:
                edge.style_line_type = "invisible"
            elif particle.squark:
                edge.style_line_type = "sfermion"
            elif particle.colored:
                edge.style_line_type = "fermion"
            elif particle.lepton:
                edge.style_line_type = "fermion"
            elif particle.boson:
                edge.style_line_type = "boson"
            elif particle.gluino:
                edge.style_args["scale"] = 0.2 * self.options["scale"]
                edge.style_line_type = "gluino"
            elif particle.chargino:
                edge.style_line_type = "chargino"
            elif particle.slepton:
                edge.style_line_type = "sfermion"
            else:
                edge.style_line_type = "hadron"
Пример #6
0
class GraphvizEngine(LayoutEngine):
    _global_args = ["dump_dot"]
    _args = [Arg("extra", str, "extra graphviz options", default="")]
    _base = True

    def graphviz_pass(self, engine, graphviz_options, dot_data):
        log.debug("dot_data hash: 0x%0X", hash(dot_data))

        # Dump the whole dot file before passing it to graphviz if requested
        if self.options["dump_dot"]:
            log.debug("Data passed to %s:" % engine)
            # TODO: flush log FD
            print dot_data

        # Process the DOT data with graphviz
        log.verbose("Calling '%s' with options %s" %
                    (engine, graphviz_options))
        with timer("run graphviz", log.VERBOSE):
            output, errors = run_graphviz(engine, dot_data, graphviz_options)
        errors = map(str.strip, errors.split("\n"))
        errors = filter(lambda e: e and not "Warning: gvrender" in e, errors)
        if errors:
            log.warning("********* GraphViz Output **********")
            for error in errors:
                log.warning(error)
            log.warning("************************************")
        if not output.strip():
            log.error("No output from %s " % engine)
            log.error("There may be too many constraints on the graph.")
        if self.options["dump_dot"]:
            log.debug("Data received from %s:" % engine)
            print output
        return output

    def __call__(self, layout):
        engine = self._name
        opts = self.options["extra"].split()
        if not any(opt.startswith("-T") for opt in opts):
            opts.append("-Tplain")
        plain = self.graphviz_pass(engine, opts, self.dot(layout))
        layout.update_from_plain(plain)

    def dot(self, layout):
        out = ["digraph pythia {"]
        out.append('dpi=1;')
        if layout.width and layout.height:
            out.append('size="%s,%s!";' % (layout.width, layout.height))
        if layout.ratio:
            out.append("ratio=%s;" % layout.ratio)
        out.append(layout.dot)
        out.append("}")
        return "\n".join(out)
Пример #7
0
class Highlight(Style):
    """
    Colour particles matching some criteria.
    e.g. -sHighlight:6:color=blue highlights top quarks in blue,
         -sHighlight:param=eta:0:2.5 highlights central particles,
         -sHighlight:1000000:2000015 highlights susy particles
    """
    _name = "Highlight"
    _args = [
        Arg("start", float, "start pdgid to highlight", default=6),
        Arg("end", float, "end pdgid", default=0),
        Arg("color", str, "highlight color", default="red"),
        Arg("param", str, "parameter", default="pdgid"),
    ]

    def __call__(self, layout):
        """ highlight all particles in rage of interest """
        start = self.options["start"]
        if self.options["end"] == 0:
            end = start
        else:
            end = self.options["end"]
        color = self.options["color"]
        param = self.options["param"]

        for edge in layout.edges:
            try:
                if start <= abs(getattr(edge.item, param)) <= end:
                    edge.style_args["stroke"] = edge.style_args["fill"] = color
            except AttributeError:  # looks like the item doesn't have that param
                pass

        for node in layout.nodes:
            try:
                # label in Inline; particle in Dual
                if start <= abs(getattr(node.item, param)) <= end:
                    node.style_args["fill"] = color
            except AttributeError:  # looks like the item doesn't have that param
                pass
Пример #8
0
class DotEngine(GraphvizEngine):
    _name = "dot"
    _args = [
        Arg("orientation",
            str,
            "orientation of the graph",
            "TB",
            choices=["LR", "RL", "TB", "BT"])
    ]

    def dot(self, layout):
        """ tuning parameters specific to dot:
          * rankdir (G) - direction of layout LR, RL, TB, BT 

          * nodesep (G, 0.25) - minimum in-rank separation
          * ranksep (G, 0.5) - minimum separation between ranks

          * aspect (G, unset) - if set can narrow layouts
          * compound (G, false) - allow edges between clusters

          * ordering (G) - force ordering of in/out particles per vertex

          * remincross (G) - reminimize cluster (??)
          * mclimit (G, 1) - tweak number of iterations
          * searchsize (G, 30) - optimization parameter, bigger better

          * rank (S) -  same, min, max, source or sink (force to that rank)
          * group (N) - if same at start and end, try to connect straight
          * constraint (E, true) - edge used for ranking 
          * minlen (E, 1) - minumum edge length in rank difference
          * weight (E, 1) - heavier is shorter, 0 is minimum
          """

        out = ["digraph pythia {"]
        #out.append(layout.options["extra_dot"])
        out.append('rankdir="%s";' % self.options["orientation"])
        #out.append('ranksep=10;')
        out.append('dpi=1;')
        if layout.width and layout.height:
            out.append('size="%s,%s!";' % (layout.width, layout.height))
        if layout.ratio:
            out.append("ratio=%s;" % layout.ratio)

        out.append("edge [labelangle=90]")

        out.append(layout.dot)

        out.append("}")
        return "\n".join(out)
Пример #9
0
class ThickenColor(Style):
    """
    This can be used to make a single (anti)colour line very thick, so that it 
    can be easily seen.
    """

    _name = "ThickenColor"
    _args = [Arg("color_id", int, "id of the color to thicken")]

    def __call__(self, layout):
        color_id = self.options["color_id"]
        for edge in layout.edges:
            particle = edge.item
            if color_id in (particle.color, particle.anticolor):
                edge.style_args["stroke-width"] = 0.5
Пример #10
0
class LabelSizePt(Style):
    """
    Make the label size dependent on the transverse momentum.
    """
    _name = "LabelSizePt"
    _args = [
        Arg("scale", float, "scale of the line effects", default=1.0),
    ]

    def __call__(self, layout):
        for element in list(layout.edges) + list(layout.nodes):
            particle = element.item
            if not hasattr(particle, "pt"):
                continue
            element.label_size = self.options["scale"] * ln(particle.pt +
                                                            1) * 0.5 + 0.5
Пример #11
0
class StdPainter(Painter):
    _global_args = ("output_file", )
    _args = (Arg("output_file", str, "output filename", default="mcviz.svg"), )
    _base = True

    def write_data(self, data_string):
        output_file = self.options["output_file"]
        # Dump the data to stdout if required
        log.debug("data hash: 0x%0X", hash(data_string))
        if output_file == "-":
            print data_string
        elif hasattr(output_file, "write"):
            output_file.write(data_string.encode("UTF-8"))
        else:
            # Write the data to file otherwise
            log.info('writing "%s"' % output_file)
            with open(output_file, "w") as f:
                f.write(data_string.encode("UTF-8"))

    def recalculate_boundingbox(self):
        # if we do not know the scale; (i.e. bounding box) calculate it
        x_min, y_min, x_max, y_max = 0, 0, 0, 0

        def get_minmax(x, y):
            return min(x, x_min), min(y, y_min), max(x, x_max), max(y, y_max)

        for edge in self.layout.edges:
            if edge.spline:
                x0, x1, y0, y1 = edge.spline.boundingbox
                x_min, y_min, x_max, y_max = get_minmax(x0, y0)
                x_min, y_min, x_max, y_max = get_minmax(x1, y0)
                x_min, y_min, x_max, y_max = get_minmax(x0, y1)
                x_min, y_min, x_max, y_max = get_minmax(x1, y1)

        for node in self.layout.nodes:
            if node.center:
                x_min, y_min, x_max, y_max = get_minmax(
                    node.center.x, node.center.y)
        wx = x_max - x_min
        wy = y_max - y_min
        if not self.layout.width:
            self.layout.width = 100
            self.layout.height = 100
            self.layout.scale = 1
        else:
            self.layout.scale = min(self.width / wx, self.height / wy)
Пример #12
0
class DemoOptionSet(CommandLineOptionSet):
    _name = "demo"
    _args = [Arg("layout", str, "choose a layout to demo: Feynman, Dual or InlineLabels", 
                 choices=("Feynman", "Dual", "InlineLabels"), default="Dual")]
    def __call__(self, tools):
        for tool_type in sorted(tool_types.keys()):
            tools[tool_type] = []
        setdefault(tools, "painter", "navisvg")
        setdefault(tools, "layout", self.options["layout"])
        setdefault(tools, "layout", "FixIni")
        tools["style"].append(ToolSetting("Default"))
        tools["style"].append(ToolSetting("SimpleColors"))
        tools["style"].append(ToolSetting("FancyLines"))
        tools["transform"].append(ToolSetting("NoKinks"))
        tools["transform"].append(ToolSetting("Gluballs"))
        tools["transform"].append(ToolSetting("Chainmail"))
        log.info("--demo is equivalent to '-pnavisvg:mcviz.svg -sSimpleColors -sFancyLines -tNoKinks -tGluballs -tChainmail -l%s -lFixIni'" % self.options["layout"])
        return super(DemoOptionSet, self).__call__(tools)
Пример #13
0
class FixedInitialLayout(BaseLayout):
    """
    Place all of the initial vertices on the same rank.
    """
    _name = "FixIni"
    _args = [
        Arg("stretch",
            float,
            "pull initial vertices apart, 1 is max",
            default=0.8)
    ]

    def process(self):
        sg_options = self.subgraph_options.setdefault("initial", [])
        # rank=source means "put these on the first rank" to graphviz
        sg_options.append('rank="source"')
        super(FixedInitialLayout, self).process()

        initial = self.subgraphs["initial"]
        initial_pairs = len(initial) // 2
        for i, p in enumerate(initial):
            pair = i // 2
            if self.width and self.height:
                # Attempt to fix the initial particles on the left and right
                # of the graph.
                stretch = self.options["stretch"] * self.width / 2.0
                xposition = stretch + (i % 2) * (self.width - 2 * stretch)
                yposition = (1 + pair) * self.height / (initial_pairs + 1)
                p.dot_args["pos"] = "%s,%s!" % (xposition, yposition)

    def process_node(self, obj):
        if isinstance(obj.item, ViewVertex):
            if obj.item.initial:
                obj.subgraph = "initial"
        elif obj.item.start_vertex.initial:
            if obj.dot_args.get("group", "") != "particlelabels":
                obj.subgraph = "initial"
        return super(FixedInitialLayout, self).process_node(obj)
Пример #14
0
class DualLayout(BaseLayout, FundamentalTool):
    """
    The Dual layout, so named because it is the "Dual" in the graph sense of
    Feynman diagrams, shows particles as nodes.
    """
    _name = "Dual"
    _args = [
        Arg("helper_vertices",
            Arg.bool,
            "add helper vertices if there are many-to-many vertices",
            default=True)
    ]

    def get_subgraph(self, particle):
        if particle.initial_state:
            return "initial"

    def get_particle(self, particle):
        lo = LayoutNode(particle)
        lo.subgraph = self.get_subgraph(particle)

        lo.label = particle.pdgid
        lo.label_size = self.options["label_size"]

        if particle.initial_state:
            # Big red initial vertices
            lo.width = lo.height = 1.0
        elif "cluster" in particle.tags:
            lo.label = "cluster (%.4g %seV)" % self.graph.units.pick_mag(
                particle.pt)
        elif "cut_summary" in particle.tags:
            pass  #lo.label = None
        elif "jet" in particle.tags:
            jet_id = 0
            for tag in particle.tags:
                if tag != 'jet' and tag[:4] == 'jet_':
                    jet_id = int(tag[4:]) + 1
            lo.label = "jet {0:d} ({1:.4g} {2:s}eV)".format(
                jet_id, *self.graph.units.pick_mag(particle.pt))

        return lo

    def get_vertex(self, vertex, node_style=None):
        items = []

        if "cut_summary" in vertex.tags and len(vertex.incoming) > 1:
            return None

        need_help = False
        if self.options["helper_vertices"]:
            if len(vertex.incoming) > 1 and len(vertex.outgoing) > 1:
                need_help = True
            elif vertex.vacuum:
                need_help = True

        if need_help:
            helper_node = LayoutNode(vertex)
            helper_node.width = 0.5
            helper_node.height = 0.5
            items.append(helper_node)

            for particle in vertex.incoming:
                items.append(LayoutEdge(vertex, particle, vertex))
            for particle in vertex.outgoing:
                items.append(LayoutEdge(vertex, vertex, particle))
            return items

        for particle in vertex.outgoing:
            for mother in particle.mothers:
                items.append(LayoutEdge(vertex, mother, particle))

        return items
Пример #15
0
class PhiLayout(FeynmanLayout):
    _name = "Phi"
    _args = [Arg("scale", float, "length scale", default=1.0)]

    def process(self):
        super(PhiLayout, self).process()
        incoming_counter = 0
        cx, cy = 0, 0
        DR = 50

        # create a tree that is used to generate the visuals
        coming = {}
        going = {}
        for edge in self.edges:
            coming.setdefault(edge.coming, []).append(edge)
            going.setdefault(edge.going, []).append(edge)

        nodes_remaining = list(self.nodes)
        item_nodes = {}
        for node in self.nodes:
            item_nodes[node.item] = node
            if not node.item in going:
                xposition = 10 * DR * 1 if incoming_counter % 2 == 0 else -1
                yposition = cy  # - 10 * DR * 1 if incoming_counter % 2 == 0 else -1
                incoming_counter += 1
                node.center = Point2D(xposition, yposition)
                node.dot_args["pos"] = "%s,%s!" % node.center.tuple()
                node.dot_args["pin"] = "true"
                nodes_remaining.remove(node)

        while nodes_remaining:
            for node in list(nodes_remaining):
                pinned = [
                    edge for edge in going[node.item]
                    if item_nodes[edge.coming].center
                ]
                if pinned:
                    nongluons = [p for p in pinned if not p.item.gluon]
                    if nongluons:
                        edge = nongluons[0]
                        force_scale = None
                    else:
                        edge = pinned[0]
                        force_scale = 0.1

                    phi = edge.item.phi
                    px, py, pz = edge.item.p
                    pt = edge.item.pt
                    e = edge.item.e
                    signum = 1 if sin(phi) * px + cos(phi) * py > 0 else -1
                    theta = atan2(pz, pt * signum)

                    #phi = 0
                    #phi = theta

                    #scale = ln(edge.item.e)
                    scale = e / 1000.0 + pt * 10
                    if scale < 0.1:
                        scale = 0.1
                    elif scale > 100:
                        scale = 100
                    if force_scale:
                        scale = force_scale
                    scale *= self.options["scale"]
                    delta = Point2D(DR * sin(phi), DR * cos(phi)) * scale

                    node.center = item_nodes[edge.coming].center + delta
                    node.dot_args["pos"] = "%s,%s!" % node.center.tuple()
                    node.dot_args["pin"] = "true"
                    nodes_remaining.remove(node)
                    break
Пример #16
0
class CircleLayout(FeynmanLayout):
    _name = "Circle"
    _args = [
        Arg("scale", float, "length scale", default=1.0),
        Arg("view",
            str,
            "view on the event",
            choices=["front", "side", "eta_pt"],
            default="side"),
        Arg("phi", float, "rotate the view in phi [radian]", default=0.0),
        Arg("pt",
            float,
            "minimum pT for a particle to be fixed [GeV]",
            default=0.0),
    ]

    def process(self):
        super(CircleLayout, self).process()

        # create a tree that is used to generate the visuals
        coming = {}
        going = {}
        for edge in self.edges:
            coming.setdefault(edge.coming, []).append(edge)
            going.setdefault(edge.going, []).append(edge)

        nodes_remaining = list(self.nodes)

        item_nodes = {}
        for node in self.nodes:
            item_nodes[node.item] = node

        rotate_phi = self.options["phi"]
        min_fix_pt = self.options["pt"]
        D = self.options["scale"]

        def rotate_phi_proj(momentum, dims):
            p0 = cos(rotate_phi) * momentum[0] + sin(rotate_phi) * momentum[1]
            p1 = -sin(rotate_phi) * momentum[0] + cos(rotate_phi) * momentum[1]
            p = p0, p1, momentum[2]
            return Point2D(p[dims[0]], p[dims[1]])

        if self.options["view"] == "front":
            pfunc = lambda m: rotate_phi_proj(m, (0, 1))
            mfunc = lambda m: rotate_phi_proj(m, (0, 1))
        elif self.options["view"] == "side":
            pfunc = lambda m: rotate_phi_proj(m, (2, 1))
            mfunc = lambda m: rotate_phi_proj(m, (2, 1))
        elif self.options["view"] == "eta_pt":
            mfunc = lambda m: rotate_phi_proj(m, (2, 1))

            def pfunc(momentum):
                p0, p1 = rotate_phi_proj(momentum, (0, 1)).tuple()
                pt = sqrt(p0**2 + p1**2)
                y = pt * (1 if p0 > 0 else -1)
                if all(momentum[i] == 0 for i in (0, 1, 2)):
                    return 0, 0
                if pt == 0:
                    x = -momentum[2] / abs(momentum[2]) * 5
                else:
                    x = -ln(tan(atan2(pt, momentum[2]) / 2.))
                return Point2D(x, y)

        for node in self.nodes:
            recursive_fix = all(p.final_state and p.pt < min_fix_pt
                                for p in node.item.outgoing)
            nofix = node.item.final and list(
                node.item.incoming)[0].pt < min_fix_pt
            if not nofix and (node.item.final or recursive_fix):
                p = list(node.item.incoming)[0]
                phi, pt, e = p.phi, p.pt, p.e

                scale = pt
                if scale < 1:
                    scale = 1
                elif scale > 100:
                    scale = 100
                scale *= self.options["scale"]

                momentum = mfunc(p.p) * scale

                sv = p.start_vertex
                sv_p = [
                    sum(par.p[i] for par in sv.outgoing) for i in (0, 1, 2)
                ]
                svn = item_nodes[sv]

                dlvl = get_max_descendant_levels(sv) + 1 - (1 if recursive_fix
                                                            else 0)

                if sv_p[0] == 0 and sv_p[1] == 0:
                    continue

                stretch = 1.0
                assert 0 < stretch <= 1
                pos = pfunc(sv_p) * (D / sqrt(dlvl))
                pos.y = pos.y / (abs(pos.y) / D)**(
                    stretch) * 10  # signum modified by stretch
                svn.center = pos
                svn.dot_args["pos"] = "%s,%s!" % svn.center.tuple()
                svn.dot_args["pin"] = "true"
                if svn in nodes_remaining:
                    nodes_remaining.remove(svn)

                node.center = pos + momentum
                node.dot_args["pos"] = "%s,%s!" % node.center.tuple()
                node.dot_args["pin"] = "true"
                nodes_remaining.remove(node)

            elif node.item.initial:
                p = list(node.item.outgoing)[0]
                node.center = pfunc(p.p) * 1.2 * D
                node.dot_args["pos"] = "%s,%s!" % node.center.tuple()
                node.dot_args["pin"] = "true"
                nodes_remaining.remove(node)

                sv = p.end_vertex
                sv_p = [
                    sum(par.p[i] for par in sv.incoming) for i in (0, 1, 2)
                ]
                svn = item_nodes[sv]
                pos = pfunc(sv_p) * D
                svn.center = pos
                svn.dot_args["pos"] = "%s,%s!" % svn.center.tuple()
                svn.dot_args["pin"] = "true"
                if svn in nodes_remaining:
                    nodes_remaining.remove(svn)

        return

        while nodes_remaining:
            for node in list(nodes_remaining):
                pinned = [
                    True for edge in coming[node.item]
                    if item_nodes[edge.going].center
                ]
                if pinned:
                    nongluons = [p for p in pinned if not p.item.gluon]
                    if nongluons:
                        edge = nongluons[0]
                        force_scale = None
                    else:
                        edge = pinned[0]
                        force_scale = 0.1

                    phi = edge.item.phi
                    px, py, pz = edge.item.p
                    pt = edge.item.pt
                    e = edge.item.e
                    signum = 1 if sin(phi) * px + cos(phi) * py > 0 else -1
                    theta = atan2(pz, pt * signum)

                    #phi = 0
                    #phi = theta

                    #scale = ln(edge.item.e)
                    scale = e / 1000.0 + pt * 10
                    if scale < 0.1:
                        scale = 0.1
                    elif scale > 100:
                        scale = 100
                    if force_scale:
                        scale = force_scale
                    scale *= self.options["scale"]
                    delta = Point2D(DR * sin(phi), DR * cos(phi)) * scale
                    node.center = item_nodes[edge.coming].center + delta
                    node.dot_args["pos"] = "%s,%s!" % node.center.tuple()
                    node.dot_args["pin"] = "true"
                    nodes_remaining.remove(node)
                    break
Пример #17
0
class FeynmanLayout(BaseLayout, FundamentalTool):
    """
    Produces something analagous to the familiar Feynman diagram. Beware though,
    event records are not really like feynman diagrams.
    """
    _name = "Feynman"

    _args = [Arg("gluid", Arg.bool, "label gluons")]

    dummy_number = -1000

    def get_subgraph(self, vertex):
        if vertex.initial:
            return "initial"
        elif vertex.connecting:
            return "connecting"

    def process(self):

        # TODO: do something with the edge ordering. It is currently nonsensical
        def ordering(edge):
            order = (1 if edge.item.gluon else 0 if edge.item.color else
                     2 if edge.item.anticolor else None)
            return edge.going.reference, order, edge.item.reference

        self.edges.sort(key=ordering)

    def get_vertex(self, vertex, node_style=None):

        lo = LayoutNode(vertex, width=0.1, height=0.1)
        lo.label = None
        lo.label_size = self.options["label_size"]
        lo.subgraph = self.get_subgraph(vertex)

        if node_style:
            lo.dot_args.update(node_style)

        # Put clusters in the same graphviz "group".
        if "after_cluster" in vertex.tags:
            lo.dot_args["group"] = "cluster_%i" % vertex.cluster_index
        elif (all("after_cluster" in p.tags for p in vertex.outgoing)
              and vertex.outgoing):
            cluster_particle = (p for p in vertex.outgoing
                                if "after_cluster" in p.tags).next()
            lo.dot_args[
                "group"] = "cluster_%i" % cluster_particle.cluster_index

        if vertex.initial:
            # Big red initial vertices
            lo.width = lo.height = 1.0
        elif vertex.final:
            # Don't show final particle vertices
            lo.show = False

        elif "cut_summary" in vertex.tags:
            return None

        elif "summary" in vertex.tags:
            lo.width = lo.height = 1.0

        elif vertex.hadronization:
            # Big white hardronization vertices
            lo.width = lo.height = 1.0

        else:
            nr_particles = len(vertex.incoming) + len(vertex.outgoing)
            lo.width = lo.height = nr_particles * 0.04

        return lo

    def get_particle(self, particle):

        if "cut_summary" in particle.tags:
            dummy = ViewVertex(self.graph)
            dummy.order_number = self.dummy_number
            self.dummy_number -= 1
            lo = LayoutEdge(particle, particle.start_vertex, dummy)
        else:
            lo = LayoutEdge(particle, particle.start_vertex,
                            particle.end_vertex)
        lo.label_size = self.options["label_size"]

        if "cluster" in particle.tags:
            lo.label = "cluster (%.4g %seV)" % self.graph.units.pick_mag(
                particle.pt)
        elif (particle.gluon or particle.photon) and not self.options["gluid"]:
            lo.label = None
        elif "cut_summary" in particle.tags:
            lo.label = None
        elif "jet" in particle.tags:
            jet_id = 0
            for tag in particle.tags:
                if tag != 'jet' and tag[:4] == 'jet_':
                    jet_id = int(tag[4:]) + 1
            if not jet_id: print(particle.tags)
            lo.label = "jet {0:d} ({1:.4g} {2:s}eV)".format(
                jet_id, *self.graph.units.pick_mag(particle.pt))
        else:
            lo.label = particle.pdgid

        #lo.dot_args["weight"] = log10(particle.e+1)*0.1 + 1

        return lo
Пример #18
0
class Cut(Transform):
    """
    Cut away particles from the 'outside' of the graph (default). When
    'final_state' is set to force, also cut on 'inner' particles.

    Instead of taking the value of the parameter of a particle
    directly, it can be checked against the value of its mother or
    daugther when the corresponding boolean flags are true. E.g. to
    draw all particles with at least one mother with pt > 5 GeV, do:

    '-tCut:cut=5:param=pt:mothers=True'

    'final_state' is automatically set to 'False' if 'mothers' or
    'daugthers' is True.

    """
    _name = "Cut"
    _args = [
        Arg("cut", float, "cut value", default=5),
        Arg("param", str, "parameter to cut on", default="pt"),
        Arg("abs", Arg.bool, "take abs value of param", default=True),
        Arg("reverse", Arg.bool, "reverse cut", default=False),
        Arg("exact", Arg.bool, "exact cut", default=False),
        Arg("mothers", Arg.bool, "cut on mother parameters", default=False),
        Arg("daughters", Arg.bool, "cut on daughter parameters",
            default=False),
        Arg("final_state",
            Arg.bool,
            "cut on final state particles only",
            default=True),
    ]

    def __call__(self, graph_view):
        cut = self.options["cut"]
        param = self.options["param"]
        take_abs = self.options["abs"]
        reverse = self.options["reverse"]
        exact = self.options["exact"]
        mothers = self.options["mothers"]
        daughters = self.options["daughters"]

        # when cutting on daugthers don't restrict on final state particles
        final_state = self.options["final_state"] if not daughters else False

        if final_state:
            particles = [p for p in graph_view.particles if p.final_state]
        else:
            particles = graph_view.particles

        passed_tag = "passed_cut"

        def cutter(p):

            reject = True

            if hasattr(p, param):
                value = getattr(p, param)
                if take_abs: value = abs(value)

                if exact:
                    reject = (value != cut)
                else:
                    reject = (value <= cut)

            return (reject if not reverse else not reject)

        keep = set()

        def mark(item, depth):
            keep.add(item)
            item.tag(passed_tag)

        particles_to_walk = set()

        for particle in particles:

            if mothers:
                loop_over = particle.mothers
            elif daughters:
                loop_over = particle.daughters
            else:
                loop_over = [particle]

            for p in loop_over:
                if not cutter(p):
                    particles_to_walk.add(p)
                    # also display the particle whose mother or daughter passed the cut
                    if mothers or daughters:
                        particles_to_walk.add(particle)

        for p in particles_to_walk:
            graph_view.walk(p,
                            vertex_action=mark,
                            particle_action=mark,
                            ascend=True)

        def pruner(item, depth):
            if passed_tag in item.tags:
                return ()

        for vertex in graph_view.vertices:
            if passed_tag in vertex.tags:
                cut_daughters = [
                    p for p in vertex.outgoing if passed_tag not in p.tags
                ]
                #print vertex, cut_daughters
                if len(cut_daughters) > 0:
                    vsummary = graph_view.summarize_vertices(
                        set(d.end_vertex for d in cut_daughters))
                    vsummary.tag("cut_summary")
                    psummary = graph_view.summarize_particles(
                        set(cut_daughters))
                    psummary.tag("cut_summary")

        for p in graph_view.particles:
            if p in keep:
                continue

            if p.start_vertex in keep:  # and not p.start_vertex.hadronization:
                # Don't discard
                continue

            graph_view.drop(p)

        #Clean out 'dangling' vertices
        for vertex in graph_view.vertices:
            if not vertex.incoming and not vertex.outgoing:
                graph_view.drop(vertex)
Пример #19
0

@Transform.decorate("NoLoops")
def contract_loops(graph_view):
    """
    Drop loops from the graph
    """
    for particle in graph_view.particles:
        if particle.start_vertex == particle.end_vertex:
            graph_view.drop(particle)


@Transform.decorate("Pluck",
                    args=[
                        Arg("start",
                            float,
                            "start of range to keep",
                            default=6),
                        Arg("end", float, "end of range", default=0),
                        Arg("param",
                            str,
                            "parameter of interest",
                            default="pdgid"),
                        Arg("keep_down",
                            int,
                            "max depth to descend from vertex",
                            default=8),
                        Arg("keep_up",
                            int,
                            "max depth to ascend from vertex",
                            default=20)
                    ])
Пример #20
0
class BaseLayout(Layout):
    """
    Class that encapsulates the layout and styling information of the graph
    """
    _args = [Arg("x", int, "width"), Arg("y", int, "height"), 
             Arg("ratio", float, "aspect ratio"),
             Arg("label_size", float, "size of labels (1.0 is normal)", 1.0),]
    _base = True

    def __call__(self, graph):
        self.graph = graph
        
        self.width, self.height = self.options["x"], self.options["y"]
        self.ratio = self.options["ratio"]
        self.scale = 1.0
        self.label_size = self.options["label_size"]
        self.units = graph.units

        self.subgraphs = {None: []}
        self.subgraph_options = {}
        self.edges = []

        # create node and edge objects from the graph
        self.fill_objects(graph)

        # do custom processing of the layout objects here
        self.process()

        return self

    def fill_objects(self, graph):
        for vertex in graph.vertices:
            self.add_object(self.get_vertex(vertex))

        for particle in graph.particles:
            self.add_object(self.get_particle(particle))

    def process_node(self, node):
        return node

    def process_edge(self, edge):
        return edge

    def add_object(self, obj):
        if obj is None:
            return
        elif hasattr(obj, "__iter__"):
            for o in obj:
                self.add_object(o)
            return
            
        obj.item.layout_objects.append(obj)
        if isinstance(obj, LayoutNode):
            obj = self.process_node(obj)
            if obj:
                self.subgraphs.setdefault(obj.subgraph, []).append(obj)
        elif isinstance(obj, LayoutEdge):
            obj = self.process_edge(obj)
            if obj:
                self.edges.append(obj)
        else:
            raise NotImplementedError()

    def process(self):
        pass

    @property
    def nodes(self):
        return chain(*self.subgraphs.values())

    @property
    def subgraph_names(self):
        names = sorted(self.subgraphs.keys())
        # move "None" (sorted always first) to the back
        return names[1:] + [None]

    @property
    def dot(self):
        out = []
        for name in self.subgraph_names:
            nodelist = self.subgraphs.get(name, [])
            subgraph = self.subgraph_options.get(name, [])
            subgraph.extend(node.dot for node in nodelist)
            if name:
                subgraph.insert(0, "subgraph %s {" % name)
                subgraph.append("}")
            out.extend(subgraph)

        for edge in self.edges:
            out.append(edge.dot)

        return "\n".join(out)

    def update_from_plain(self, plain):
        data = PlainOutput(plain)
        self.width, self.height = data.width, data.height
        self.scale = data.scale

        for edge in self.edges:
            edge.spline = data.edge_lines.get(edge.reference, None)
            edge.label_center = data.edge_label.get(edge.reference, None)

        for node in self.nodes:
            if node.item.reference in data.nodes:
                node.center, size = data.nodes[node.item.reference]
                node.width, node.height = size