slhaplot

Sat, 26 Feb 2011 23:21:12 +0100

author
Andy Buckley <andy@insectnation.org>
date
Sat, 26 Feb 2011 23:21:12 +0100
changeset 120
bffefe12df80
parent 116
e63729bbb044
child 121
402d6abf64b7
permissions
-rwxr-xr-x

Remove --show-gravitino: we'll always show it from now

     1 #! /usr/bin/env python
     3 # from __future__ import with_statement
     5 """\
     6 Usage: %prog [options] <spcfile>
     8 Make a SUSY mass spectrum plot from an SLHA or ISAWIG spectrum file. If the
     9 filename ends with .isa, it will be assumed to be an ISAWIG file, otherwise
    10 it will be assumed to be an SLHA file (for which the normal extension is .spc).
    12 Output is currently available as the make-plots .dat format or as LaTeX source
    13 using the PGF/TikZ graphics package. Both may be processed to make EPS or PDF
    14 images.
    16 TODOs:
    17   * Allow user control over aspect ratio / geometry
    18   * PNG output (use PIL if available?)
    19   * Use proper distinction between physical, plot-logical, and plot output coords
    20   * Use scaling to allow the y coordinates to be in units of 100 GeV in TikZ output.
    21   * Distribute decay arrow start/end positions along mass lines rather than always to/from their centres?
    22   * Drop make-plots support?
    23 """
    25 class XEdges(object):
    26     def __init__(self, left, offset=0.0, width=2.0):
    27         self.offset = offset
    28         self.left = left + offset
    29         self.width = width
    30     @property
    31     def right(self):
    32         return self.left + self.width
    33     @property
    34     def centre(self):
    35         return (self.left + self.right)/2.0
    38 class Label(object):
    39     def __init__(self, text, offset=None):
    40         self.text = text
    41         self.offset = None
    42     def __str__(self):
    43         return self.text
    46 ## Details classes for representing decided positions in a way independent of output format
    49 class ParticleDetails(object):
    50     def __init__(self, label, xnom, xoffset, color="black", labelpos="L", mass=None):
    51         self.label = label
    52         self.mass = mass
    53         self.xedges = XEdges(xnom, xoffset)
    54         self.color = color
    55         self.labelpos = labelpos
    58 class DecayDetails(object):
    59     def __init__(self, pidfrom, xyfrom, pidto, xyto, br, color="gray"): #, thickness=1px, label=None):
    60         self.pidfrom = pidfrom
    61         self.xyfrom = xyfrom
    62         self.pidto = pidto
    63         self.xyto = xyto
    64         self.br = br
    65         self.color = color
    66         #self.label = label
    69 class LabelDetails(object):
    70     def __init__(self, xy, texlabel, anchor="l", color="black"):
    71         self.xy = xy
    72         self.texlabel = texlabel
    73         ## Add non-TeX-based label rendering via this property, if needed
    74         self.textlabel = texlabel
    75         self.anchor = anchor
    76         self.color = color
    80 XHIGGS = 0.0
    81 XSLEPTON = 5.0
    82 XGAUGINO = 10.0
    83 XSUSYQCD = 15.0
    85 PDETAILS = {
    86     25 : ParticleDetails(Label(r"$h^0$"), XHIGGS, -0.2, color="blue"),
    87     35 : ParticleDetails(Label(r"$H^0$"), XHIGGS, -0.2, color="blue"),
    88     36 : ParticleDetails(Label(r"$A^0$"), XHIGGS, -0.2, color="blue"),
    89     37 : ParticleDetails(Label(r"$H^\pm$"), XHIGGS, 0.2, color="red"),
    90     1000011 : ParticleDetails(Label(r"$\tilde{\ell}_\text{L}$"), XSLEPTON, -0.2, color="blue"),
    91     2000011 : ParticleDetails(Label(r"$\tilde{\ell}_\text{R}$"), XSLEPTON, -0.2, color="blue"),
    92     1000015 : ParticleDetails(Label(r"$\tilde{\tau}_1$"), XSLEPTON, 0.2, color="red"),
    93     2000015 : ParticleDetails(Label(r"$\tilde{\tau}_2$"), XSLEPTON, 0.2, color="red"),
    94     1000012 : ParticleDetails(Label(r"$\tilde{\nu}_\text{L}$"), XSLEPTON, -0.2, color="blue"),
    95     1000016 : ParticleDetails(Label(r"$\tilde{\nu}_\tau$"), XSLEPTON, 0.2, color="red"),
    96     1000022 : ParticleDetails(Label(r"$\tilde{\chi}_1^0$"), XGAUGINO, -0.2, color="blue"),
    97     1000023 : ParticleDetails(Label(r"$\tilde{\chi}_2^0$"), XGAUGINO, -0.2, color="blue"),
    98     1000025 : ParticleDetails(Label(r"$\tilde{\chi}_3^0$"), XGAUGINO, -0.2, color="blue"),
    99     1000035 : ParticleDetails(Label(r"$\tilde{\chi}_4^0$"), XGAUGINO, -0.2, color="blue"),
   100     1000024 : ParticleDetails(Label(r"$\tilde{\chi}_1^\pm$"), XGAUGINO, 0.2, color="red"),
   101     1000037 : ParticleDetails(Label(r"$\tilde{\chi}_2^\pm$"), XGAUGINO, 0.2, color="red"),
   102     1000039 : ParticleDetails(Label(r"$\tilde{G}$"), XGAUGINO,  0.15, color="black!50!blue!30!green"),
   103     1000021 : ParticleDetails(Label(r"$\tilde{g}$"), XSUSYQCD, -0.3, color="black!50!blue!30!green"),
   104     1000001 : ParticleDetails(Label(r"$\tilde{q}_\text{L}$"), XSUSYQCD, -0.1, color="blue"),
   105     2000001 : ParticleDetails(Label(r"$\tilde{q}_\text{R}$"), XSUSYQCD, -0.1, color="blue"),
   106     1000005 : ParticleDetails(Label(r"$\tilde{b}_1$"), XSUSYQCD, 0.2, color="black!50!blue!30!green"),
   107     2000005 : ParticleDetails(Label(r"$\tilde{b}_2$"), XSUSYQCD, 0.2, color="black!50!blue!30!green"),
   108     1000006 : ParticleDetails(Label(r"$\tilde{t}_1$"), XSUSYQCD, 0.2, color="red"),
   109     2000006 : ParticleDetails(Label(r"$\tilde{t}_2$"), XSUSYQCD, 0.2, color="red")
   110 }
   113 import pyslha
   114 import sys, optparse
   115 parser = optparse.OptionParser(usage=__doc__, version=pyslha.__version__)
   116 parser.add_option("-o", "--out", metavar="FILE",
   117                   help="write output to FILE",
   118                   dest="OUTFILE", default=None)
   119 parser.add_option("-f", "--format", metavar="FORMAT",
   120                   choices=["makeplots", "tikz", "tikzfrag", "tikzpdf"],
   121                   help="format in which to write output. 'tikz' produces LaTeX source using the "
   122                   "TikZ graphics package to render the plot, 'tikzfrag' produces the same but "
   123                   "with the LaTeX preamble and document lines commented out to make it directly "
   124                   "includeable as a code fragment in LaTeX document source, 'tikzpdf' produces a "
   125                   "PDF file created by running pdflatex and pdfcrop on the 'tikz' output, and "
   126                   "'makeplots' produces a .dat file for processing with the make-plots command. "
   127                   "(default: %default)",
   128                   dest="FORMAT", default="tikzpdf")
   129 parser.add_option("--preamble", metavar="FILE",
   130                   help="specify a file to be inserted into LaTeX output as a special preamble",
   131                   dest="PREAMBLE", default=None)
   132 parser.add_option("--minbr", "--br", metavar="BR",
   133                   help="show decay lines for decays with a branching ratio of > BR, as either a "
   134                   "fraction or percentage (default: show none)",
   135                   dest="DECAYS_MINBR", default="1.1")
   136 parser.add_option("--decaystyle", choices=["const", "brwidth", "brcolor", "brwidth+brcolor"], metavar="STYLE",
   137                   help="drawing style of decay arrows, from const/brwidth. The 'const' style draws "
   138                   "all decay lines with the same width, 'brwidth' linearly scales the width of the "
   139                   "decay arrow according to the decay branching ratio. Other modes such as BR-dependent "
   140                   "colouring may be added later. (default: %default)",
   141                   dest="DECAYS_STYLE", default="brwidth+brcolor")
   142 parser.add_option("--labels", choices=["none", "merge", "shift"], metavar="MODE",
   143                   help="treatment of labels for particle IDs, from none/merge/shift. 'none' shows "
   144                   "no labels at all, 'merge' combines would-be-overlapping labels into a single "
   145                   "comma-separated list, and 'shift' vertically shifts the clashing labels to avoid "
   146                   "collisions (default: %default)",
   147                   dest="PARTICLES_LABELS", default="shift")
   148 # parser.add_option("--hide-gravitino", action="store_false",
   149 #                   help="hide the gravitino",
   150 #                   dest="SHOW_GRAVITINO", default=False)
   153 ## Run parser and sort out a few resulting details
   154 opts, args = parser.parse_args()
   155 opts.PARTICLES_LABELS_SHOW = (opts.PARTICLES_LABELS != "none")
   156 opts.PARTICLES_LABELS_MERGE = (opts.PARTICLES_LABELS == "merge")
   157 opts.PARTICLES_LABELS_SHIFT = (opts.PARTICLES_LABELS == "shift")
   158 #
   159 # if not opts.SHOW_GRAVITINO:
   160 #     del PDETAILS[1000039]
   161 #
   162 if opts.DECAYS_MINBR.endswith("%"):
   163     opts.DECAYS_MINBR = float(opts.DECAYS_MINBR[:-1]) / 100
   164 else:
   165     opts.DECAYS_MINBR = float(opts.DECAYS_MINBR)
   167 ## Check non-optional arguments
   168 if len(args) != 1:
   169     parser.print_help()
   170     sys.exit(1)
   171 opts.INFILE = args[0]
   174 ## Choose output file
   175 if opts.OUTFILE is None:
   176     import os
   177     o = os.path.basename(opts.INFILE)
   178     if o == "-":
   179         o = "out"
   180     if "." in o:
   181         o = o[:o.rindex(".")]
   182     ## Add format-specific suffix
   183     suffix = ".tex"
   184     if "pdf" in opts.FORMAT:
   185         suffix = ".pdf"
   186     elif opts.FORMAT == "makeplots":
   187         suffix = ".dat"
   188     opts.OUTFILE = o + suffix
   191 ## Read spectrum file
   192 BLOCKS, DECAYS = None, None
   193 if opts.INFILE == "-":
   194     intext = sys.stdin.read()
   195     BLOCKS, DECAYS = pyslha.readSLHA(intext)
   196 elif opts.INFILE.endswith(".isa"):
   197     BLOCKS, DECAYS = pyslha.readISAWIGFile(opts.INFILE)
   198 else:
   199     BLOCKS, DECAYS = pyslha.readSLHAFile(opts.INFILE)
   202 ## Set mass values in PDETAILS
   203 massblock = BLOCKS["MASS"]
   204 for pid in PDETAILS.keys():
   205     PDETAILS[pid].mass = abs(massblock.entries[pid])
   208 ## Decays
   209 DDETAILS = {}
   210 for pid, detail in sorted(PDETAILS.iteritems()):
   211     if DECAYS.has_key(pid):
   212         DDETAILS.setdefault(pid, {})
   213         xyfrom = (detail.xedges.centre, detail.mass)
   214         for d in DECAYS[pid].decays:
   215             if d.br > opts.DECAYS_MINBR:
   216                 for pid2 in d.ids:
   217                     if PDETAILS.has_key(pid2):
   218                         xyto = (PDETAILS[pid2].xedges.centre, PDETAILS[pid2].mass)
   219                         DDETAILS[pid][pid2] = DecayDetails(pid, xyfrom, pid2, xyto, d.br)
   220     if DDETAILS.has_key(pid) and not DDETAILS[pid]:
   221         del DDETAILS[pid]
   224 ## Labels
   225 PLABELS = []
   226 if opts.PARTICLES_LABELS_SHOW:
   227     class MultiLabel(object):
   228         def __init__(self, label=None, x=None, y=None, anchor=None):
   229             self.labels = [(label, x, y)] or []
   230             self.anchor = anchor or "l"
   232         def __len__(self):
   233             return len(self.labels)
   235         @property
   236         def joinedlabel(self):
   237             return r",\,".join(l[0] for l in self.labels)
   239         @property
   240         def avgx(self):
   241             return sum(l[1] for l in self.labels)/float(len(self))
   242         @property
   243         def minx(self):
   244             return min(l[1] for l in self.labels)/float(len(self))
   245         @property
   246         def maxx(self):
   247             return max(l[1] for l in self.labels)/float(len(self))
   249         @property
   250         def avgy(self):
   251             return sum(l[2] for l in self.labels)/float(len(self))
   252         @property
   253         def miny(self):
   254             return min(l[2] for l in self.labels)/float(len(self))
   255         @property
   256         def maxy(self):
   257             return max(l[2] for l in self.labels)/float(len(self))
   259         def add(self, label, x, y):
   260             self.labels.append((label, x, y))
   261             self.labels = sorted(self.labels, key=lambda l : l[2])
   262             return self
   263         def get(self):
   264             for i in self.labels:
   265                 yield i
   267     def rel_err(a, b):
   268         return abs((a-b)/(a+b)/2.0)
   270     ## Use max mass to work out the height of a text line in mass units
   271     maxmass = None
   272     for pid, pdetail in sorted(PDETAILS.iteritems()):
   273         maxmass = max(pdetail.mass, maxmass)
   274     text_height_in_mass_units = maxmass/22.0
   275     ##
   276     ## Merge colliding labels
   277     reallabels = []
   278     for pid, pdetail in sorted(PDETAILS.iteritems()):
   279         labelx = None
   280         offset = pdetail.label.offset or 0.2
   281         anchor = None
   282         if pdetail.xedges.offset <= 0:
   283             labelx = pdetail.xedges.left - offset
   284             anchor = "r"
   285         else:
   286             labelx = pdetail.xedges.right + offset
   287             anchor = "l"
   288         labely = pdetail.mass
   289         ## Avoid hitting the 0 mass line/border
   290         if labely < 0.6*text_height_in_mass_units:
   291             labely = 0.6*text_height_in_mass_units
   293         text = pdetail.label.text
   294         ## Check for collisions
   295         collision = False
   296         if opts.PARTICLES_LABELS_SHIFT or opts.PARTICLES_LABELS_MERGE:
   297             for i, rl in enumerate(reallabels):
   298                 if anchor == rl.anchor and abs(labelx - rl.avgx) < 0.5:
   299                     import math
   300                     if labely > rl.miny - text_height_in_mass_units and labely < rl.maxy + text_height_in_mass_units:
   301                         reallabels[i] = rl.add(text, labelx, labely)
   302                         collision = True
   303                         break
   304         if not collision:
   305             reallabels.append(MultiLabel(text, labelx, labely, anchor))
   306     ## Calculate position shifts and fill PLABELS
   307     for rl in reallabels:
   308         if len(rl) == 1 or opts.PARTICLES_LABELS_MERGE:
   309             PLABELS.append(LabelDetails((rl.avgx, rl.avgy), rl.joinedlabel, anchor=rl.anchor))
   310         else:
   311             num_gaps = len(rl)-1
   312             yrange_old = rl.maxy - rl.miny
   313             yrange_nom = num_gaps * text_height_in_mass_units
   314             yrange = max(yrange_old, yrange_nom)
   315             ydiff = yrange - yrange_old
   316             for i, (t, x, y) in enumerate(rl.get()):
   317                 ydiff_per_line = ydiff/num_gaps
   318                 # TODO: Further improvement using relative or average positions?
   319                 newy = y + (i - num_gaps/2.0) * ydiff_per_line
   320                 PLABELS.append(LabelDetails((x, newy), t, anchor=rl.anchor))
   323 ## Function for writing out the generated source
   324 def writeout(out, outfile=opts.OUTFILE):
   325     f = sys.stdout
   326     if outfile != "-":
   327         f = open(outfile, "w")
   328     f.write(out)
   329     if f is not sys.stdout:
   330         f.close()
   332 out = ""
   333 ## MAKE-PLOTS FORMAT
   334 if opts.FORMAT == "makeplots":
   336     ## Write plot header
   337     out += "# SUSY mass/decay spectrum plot, created by pyslha/slhaplot from %s\n" % opts.INFILE
   338     out += "# http://pypi.python.org/pypi/pyslha\n"
   339     out += "\n"
   340     out += "# BEGIN PLOT\n"
   341     if opts.PARTICLES_LABELS_MERGE:
   342         ## Need more space if labels are to be merged horizontally
   343         out += "XMin=-4\n"
   344         out += "XMax=20\n"
   345     else:
   346         out += "XMin=-3\n"
   347         out += "XMax=19\n"
   348     # if opts.LOGSCALE:
   349     #     out += "LogY=1\n"
   350     # else:
   351     #     out += "LogY=0\n"
   352         out += "YMin=0\n"
   353     out += "#XCustomTicks=1.0	Higgs	6.0	Sleptons	11.0	Gauginos	16.0	Squarks\n"
   354     out += "XCustomTicks=-10.0	~\n"
   355     out += "YLabel=Mass / GeV\n"
   356     out += "DrawSpecialFirst=1\n"
   357     out += "# END PLOT\n\n"
   360     ## Mass lines
   361     for pid, pdetail in sorted(PDETAILS.iteritems()):
   362         out += """
   363 # BEGIN HISTOGRAM %s
   364 ErrorBars=1
   365 LineWidth=1pt
   366 LineColor=%s
   367 %f	%f	%e	0
   368 # END HISTOGRAM\n""" % ("pid"+str(pid), pdetail.color,
   369                       pdetail.xedges.left, pdetail.xedges.right,
   370                       pdetail.mass)
   371     out += "\n"
   374     ## Decay arrows
   375     for pidfrom, todict in sorted(DDETAILS.iteritems()):
   376         for pidto, dd in sorted(todict.iteritems()):
   377             out += r"""
   378 # BEGIN SPECIAL decay_%d_%d
   379 \psset{arrowsize=0.1}
   380 \psline[linestyle=dashed,dash=3px 2px,linecolor=%s]{->}\physicscoor(%f,%f)\physicscoor(%f,%f)
   381 # END SPECIAL
   382 """ % (dd.pidfrom, dd.pidto, dd.color, dd.xyfrom[0], dd.xyfrom[1], dd.xyto[0], dd.xyto[1])
   385     ## Particle labels
   386     out += "\n\n"
   387     out += "# BEGIN SPECIAL labels\n"
   388     for ld in PLABELS:
   389         out += r"\rput[%s]\physicscoor(%f,%f){\small %s}" % (ld.anchor, ld.xy[0], ld.xy[1], ld.texlabel) + "\n"
   390     out += "# END SPECIAL\n"
   392     ## Write it out
   393     writeout(out)
   396 ## TIKZ FORMAT
   397 if "tikz" in opts.FORMAT:
   399     ## Comment out the preamble etc. if only the TikZ fragment is wanted
   400     c = ""
   401     if opts.FORMAT == "tikzfrag":
   402         c = "%"
   404     ## Write LaTeX header
   405     out += "%% http://pypi.python.org/pypi/pyslha\n\n"
   406     out += c + "\\documentclass[11pt]{article}\n"
   407     out += c + "\\usepackage{amsmath,amssymb}\n"
   408     out += c + "\\usepackage[margin=0cm,paperwidth=15.2cm,paperheight=9.8cm]{geometry}\n"
   409     out += c + "\\usepackage{tikz}\n"
   410     out += c + "\\pagestyle{empty}\n"
   411     out += c + "\n"
   412     ## Insert user-specified preamble file
   413     if opts.PREAMBLE is not None:
   414         out += c + "%% User-supplied preamble\n"
   415         try:
   416             fpre = open(opts.PREAMBLE, "r")
   417             for line in fpre:
   418                 out += c + line
   419         except:
   420             sys.stderr.write("Could not read preamble file %s -- fallback to using \\input\n" % opts.PREAMBLE)
   421             out += c + "\\input{%s}\n" % opts.PREAMBLE.replace(".tex", "")
   422     else:
   423         out += c + "%% Default preamble\n"
   424         out += c + "\\usepackage[osf]{mathpazo}\n"
   425     #
   426     out += c + "\n"
   427     out += c + "\\begin{document}\n"
   428     out += c + "\\thispagestyle{empty}\n\n"
   430     ## Get coord space size: horizontal range is fixed by make-plots
   431     xmin = -3.0
   432     xmax = 19.0
   433     if opts.PARTICLES_LABELS_MERGE:
   434         ## Need more space if labels are to be merged horizontally
   435         xmin -= 1.0
   436         xmax += 1.0
   437     xdiff = xmax - xmin
   438     XWIDTH = 22.0
   439     def scalex(x):
   440         return x * XWIDTH/xdiff
   442     ASPECTRATIO = 0.7 #0.618
   443     ydiff = ASPECTRATIO * XWIDTH
   444     ymin = 0.0
   445     ymax = ymin + ydiff
   446     ## Get range of masses needed
   447     maxmass = max(pd.mass for pid, pd in PDETAILS.iteritems())
   448     maxdisplaymass = maxmass * 1.1
   449     if maxdisplaymass % 100 != 0:
   450         maxdisplaymass = ((maxdisplaymass + 100) // 100) * 100
   451     yscale = (ymax-ymin)/maxdisplaymass
   453     ## Write TikZ header
   454     out += "\\centering\n"
   455     out += "\\begin{tikzpicture}[scale=0.6]\n"
   457     out += "  %% y-scalefactor (GeV -> coords) = %e\n\n" % yscale
   459     ## Draw the plot boundary and y-ticks
   460     out += "  %% Frame\n"
   461     out += "  \\draw (%f,%f) rectangle (%f,%f);\n" % (scalex(xmin), ymin, scalex(xmax), ymax)
   462     out += "  %% y-ticks\n"
   463     for mtick in xrange(0, int(maxdisplaymass) + 100, 100):
   464         ytick = mtick * yscale
   465         out += "  \\draw (%f,%f) node[left] {%d};\n" % (scalex(xmin), ytick, mtick)
   466         if mtick > 0 and mtick < maxdisplaymass:
   467             ## The 0.3 needs to be in the plot coords
   468             out += "  \\draw (%f,%f) -- (%f,%f);\n" % (scalex(xmin+0.3), ytick, scalex(xmin), ytick)
   469     out += "  \\draw (%f,%f) node[left,rotate=90] {Mass / GeV};\n" % (scalex(xmin-2.0), ymax)
   471     ## Decay arrows
   472     if DDETAILS:
   473         out += "\n  %% Decay arrows\n"
   474         for pidfrom, todict in sorted(DDETAILS.iteritems()):
   475             for pidto, dd in sorted(todict.iteritems()):
   476                 out += "  %% decay_%d_%d, BR=%0.1f%%\n" % (dd.pidfrom, dd.pidto, dd.br*100)
   478                 def scalethickness(br):
   479                     if opts.DECAYS_STYLE == "const":
   480                         return 0.8
   481                     elif "brwidth" in opts.DECAYS_STYLE:
   482                         return 1.0 * br
   483                     else:
   484                         raise Exception("Unexpected problem with unknown decay line style option: please contact the PySLHA authors!")
   486                 def scalecolor(br):
   487                     if opts.DECAYS_STYLE == "const":
   488                         return None
   489                     elif "brcolor" in opts.DECAYS_STYLE:
   490                         return "black!"+str(60*dd.br + 10)
   491                     else:
   492                         raise Exception("Unexpected problem with unknown decay line style option: please contact the PySLHA authors!")
   494                 out += "  \\draw[-stealth,line width=%0.2fpt,dashed,color=%s] (%f,%f) -- (%f,%f);\n" % \
   495                     (scalethickness(dd.br), scalecolor(dd.br) or dd.color,
   496                      scalex(dd.xyfrom[0]), yscale*dd.xyfrom[1], scalex(dd.xyto[0]), yscale*dd.xyto[1])
   499     ## Draw mass lines
   500     if PDETAILS:
   501         out += "\n  %% Particle lines\n"
   502         for pid, pdetail in sorted(PDETAILS.iteritems()):
   503             y = pdetail.mass*yscale
   504             out += "  %% pid%s\n" % str(pid)
   505             out += "  \\draw[color=%s,thick] (%f,%f) -- (%f,%f);\n" % \
   506                 (pdetail.color, scalex(pdetail.xedges.left), y, scalex(pdetail.xedges.right), y)
   508     ## Particle labels
   509     if PLABELS:
   510         out += "\n  %% Particle labels\n"
   511         for ld in PLABELS:
   512             anchors_pstricks_tikz = { "r" : "left", "l" : "right" }
   513             out += "  \\draw (%f,%f) node[%s] {\small %s};\n" % \
   514                 (scalex(ld.xy[0]), yscale*ld.xy[1], anchors_pstricks_tikz[ld.anchor], ld.texlabel)
   516     ## Write TikZ footer
   517     out += "\end{tikzpicture}\n\n"
   519     ## Write LaTeX footer
   520     out += c + "\end{document}\n"
   523     ## Write output
   524     if opts.FORMAT != "tikzpdf":
   525         writeout(out)
   526     else:
   527         ## Run LaTeX and pdfcrop
   528         import tempfile, shutil, subprocess
   529         tmpdir = tempfile.mkdtemp()
   530         writeout(out, os.path.join(tmpdir, "mytmp.tex"))
   531         ok = True
   532         ## Test for pdflatex
   533         if ok:
   534             p = subprocess.Popen(["which", "pdflatex"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
   535             rtn = p.wait()
   536             if rtn != 0:
   537                 sys.stderr.write("pdflatex could not be found: tikzpdf format mode cannot work\n")
   538                 ok = False
   539         ## Test for tikz package
   540         if ok:
   541             p = subprocess.Popen(["which", "kpsewhich"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
   542             rtn = p.wait()
   543             if rtn != 0:
   544                 sys.stderr.write("WARNING: kpsewhich could not be found: check for tikz package cannot be run\n")
   545             else:
   546                 p = subprocess.Popen(["kpsewhich", "tikz.sty"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
   547                 rtn = p.wait()
   548                 if rtn != 0:
   549                     sys.stderr.write("tikz.sty could not be found: tikzpdf format mode cannot work\n")
   550                     ok = False
   551         try:
   552             p = subprocess.Popen(["pdflatex", "\scrollmode\input", "mytmp.tex"],
   553                                  stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=tmpdir)
   554             p.wait()
   555             shutil.copyfile(os.path.join(tmpdir, "mytmp.pdf"), opts.OUTFILE)
   556         except Exception, e:
   557             sys.stderr.write("pdflatex could not be run: tikzpdf format mode cannot work\n")
   558             ok = False
   559         shutil.rmtree(tmpdir)
   560         if not ok:
   561             sys.exit(3)
   564 ## UNRECOGNISED FORMAT!
   565 else:
   566     print "Other formats not currently supported! How did we even get here?!"
   567     sys.exit(2)

mercurial