1###################################
2# Parse the QMASM command line    #
3# By Scott Pakin <pakin@lanl.gov> #
4###################################
5
6import argparse
7import re
8import shlex
9import string
10import sys
11
12class ParseCommandLine(object):
13    def parse_command_line(self):
14        "Parse the QMASM command line.  Return an argparse.Namespace."
15
16        # Define all of our command-line arguments.
17        cl_parser = argparse.ArgumentParser(description="Assemble a symbolic Hamiltonian into a numeric one")
18        cl_parser.add_argument("input", nargs="*",
19                               help="file(s) from which to read a symbolic Hamiltonian")
20        cl_parser.add_argument("-v", "--verbose", action="count", default=0,
21                               help="increase output verbosity (can be specified repeatedly)")
22        cl_parser.add_argument("--run", action="store_true",
23                               help="run the program on the current solver")
24        cl_parser.add_argument("-o", "--output", metavar="FILE", default="<stdout>",
25                               help="file to which to write weights and strengths (default: none)")
26        cl_parser.add_argument("-O", type=int, nargs="?", const=1, default=0,
27                               metavar="LEVEL",
28                               help="optimize the layout; at -O1, remove unnecessary qubits")
29        cl_parser.add_argument("--pin", action="append",
30                               help="pin a set of qubits to a set of true or false values")
31        cl_parser.add_argument("--format", choices=["qubist", "ocean", "qbsolv", "qmasm", "minizinc", "bqpjson"], default="qubist",
32                               help="output-file format")
33        cl_parser.add_argument("--values", choices=["bools", "ints"], default="bools",
34                               help="output solution values as Booleans or integers (default: bools)")
35        cl_parser.add_argument("--profile", type=str, default=None, metavar="NAME",
36                               help="Profile name from dwave.conf to use")
37        cl_parser.add_argument("--solver", type=str, default=None, metavar="NAME",
38                               help='Solver name from dwave.conf to use or one of the special names "exact", "sim-anneal", "tabu", "kerberos[,<solver>]", or "qbsolv[,<solver>]"')
39        cl_parser.add_argument("--chain-strength", metavar="NEG_NUM", type=float,
40                               help="negative-valued chain strength (default: automatic)")
41        cl_parser.add_argument("--pin-weight", metavar="NEG_NUM", type=float,
42                               help="negative-valued pin weight (default: automatic)")
43        cl_parser.add_argument("--qubo", action="store_true",
44                               help="where supported, produce output files in QUBO rather than Ising format")
45        cl_parser.add_argument("--samples", metavar="POS_INT", type=int, default=1000,
46                               help="number of samples to take (default: 1000)")
47        cl_parser.add_argument("--anneal-time", metavar="POS_INT", type=int, default=None,
48                               help="annealing time in microseconds (default: automatic)")
49        cl_parser.add_argument("--spin-revs", metavar="POS_INT", type=int, default=0,
50                               help="number of spin-reversal transforms to perform (default: 0)")
51        cl_parser.add_argument("--topology-file", default=None, metavar="FILE",
52                               help="name of a file describing the topology (list of vertex pairs)")
53        cl_parser.add_argument("--postproc", choices=["none", "sample", "opt"],
54                               default="none",
55                               help='type of postprocessing to perform (default: "none")')
56        cl_parser.add_argument("--show", choices=["valid", "all", "best"], default="valid",
57                               help='show valid solutions, all solutions, or the best (even if invalid) solutions (default: "valid")')
58        cl_parser.add_argument("--always-embed", action="store_true",
59                               help="when writing an output file, embed the problem in the physical topology even when not required (default: false)")
60        cl_parser.add_argument("--composites", metavar="COMP1,COMP2,...",
61                               default="",
62                               help='wrap the solver within one or more composites (currently only "virtualgraph")')
63        cl_parser.add_argument("--pack-qubits", metavar="POS_INT", type=int,
64                               help='attempt to pack the problem into an N-qubit "corner" of the physical topology during embedding')
65        cl_parser.add_argument("--physical", action="store_true",
66                               help="map variables containing a number to the physical qubits represented by that number")
67        cl_parser.add_argument("--schedule", metavar="T,S,...", type=str,
68                               help="specify an annealing schedule as alternating lists of times (microseconds) and annealing fractions (0.0 to 1.0)")
69
70        # Parse the command line.
71        cl_args = cl_parser.parse_args()
72
73        # Perform a few sanity checks on the parameters.
74        if cl_args.chain_strength != None and cl_args.chain_strength >= 0.0:
75            self.warn("A non-negative chain strength (%.20g) was specified\n" % cl_args.chain_strength)
76        if cl_args.pin_weight != None and cl_args.pin_weight >= 0.0:
77            self.warn("A non-negative pin strength (%.20g) was specified\n" % cl_args.pin_weight)
78        if cl_args.spin_revs > cl_args.samples:
79            self.abend("The number of spin reversals is not allowed to exceed the number of samples")
80        self.parse_composite_string(cl_args.composites)  # Check for errors and discard the result.
81        self.parse_anneal_sched_string(cl_args.schedule)  # Check for errors and discard the result.
82        return cl_args
83
84    def parse_composite_string(self, cstr):
85        "Split the composites string into a list.  Abort on error."
86        comps = []
87        if cstr == "":
88            return comps
89        for c in cstr.split(","):
90            if c == "virtualgraph":
91                comps.append("VirtualGraph")
92            else:
93                self.abend('Unrecognized composite "%s"' % c)
94        return comps
95
96    def parse_anneal_sched_string(self, astr):
97        "Parse an annealing schedule into a list of (time, frac) tuples."
98        if astr == None:
99            return None
100        num_re = re.compile(r'[-+Ee.\d]+')  # All characters that can appear in a floating-point-number
101        nums = num_re.findall(astr)
102        if len(nums)%2 == 1:
103            self.abend('Failed to parse "%s" as alternating times and annealing fractions' % astr)
104        sched = []
105        for i in range(0, len(nums), 2):
106            try:
107                t = float(nums[i])
108            except ValueError:
109                self.abend('Failed to parse "%s" as a floating-point number' % nums[i])
110            try:
111                s = float(nums[i + 1])
112            except ValueError:
113                self.abend('Failed to parse "%s" as a floating-point number' % nums[i + 1])
114            sched.append((t, s))
115        if len(sched) < 2:
116            self.abend('Failed to parse "%s" into two or more (time, frac) pairs' % astr)
117        return sched
118
119    def get_command_line(self):
120        "Return the command line as a string, properly quoted."
121        return " ".join([shlex.quote(a) for a in sys.argv])
122
123    def report_command_line(self, cl_args):
124        "For provenance and debugging purposes, report our command line parameters."
125        # Output the command line as is.
126        verbosity = cl_args.verbose
127        if verbosity < 1:
128            return
129        sys.stderr.write("Command line provided:\n\n")
130        sys.stderr.write("    %s\n\n" % self.get_command_line())
131
132        # At higher levels of verbosity, output every single option.
133        if verbosity < 2:
134            return
135        sys.stderr.write("All QMASM parameters:\n\n")
136        params = vars(cl_args)
137        klen = max([len(a) for a in params.keys()])
138        klen = max(klen + 2, len("Option"))   # +2 for "--"
139        vlen = max([len(repr(a)) for a in params.values()])
140        klen = max(vlen, len("Value(s)"))
141        sys.stderr.write("    %-*s  %-*s\n" % (klen, "Option", vlen, "Value(s)"))
142        sys.stderr.write("    %s  %s\n" % ("-" * klen, "-" * vlen))
143        for k in sorted(params.keys()):
144            kname = k.replace("_", "-")
145            sys.stderr.write("    %-*s  %-*s\n" % (klen, "--" + kname, vlen, repr(params[k])))
146        sys.stderr.write("\n")
147