Re: Macro help/strategy for writing a tiny DSL for circuit-model quantum computing?
Alex-- The reason I'm starting with macros is that they're the high-risk/less-understood part of this; the rest is more or less straightforward, so I want to get a vertical slice working as soon as possible and expand from there. 1. Start with data - what are the entities (bits? gates?), and how do you > represent them? It's ok (actually better) for these to be verbose. Plan for > extension (maps are open, which is why they're so common in representing > data in Clojure). > Mostly done, as above--gates as keywords (with optional parameters), bits as integers, operations as a sequence of gate and bit, so (:H 1) or (:CNOT 1 2) or ((:RX (/ pi 2)) 3). Honestly if I could just output a sequence of those in the end, I'm Ok with that; I could scan that for the highest-index bit and know that's how much I'd need. Yes, there are extra fun things to add for representation and such, but that's the bare-bone version that would get the job done. > 2. Create functions that manipulate that take and return that data. > 3. Decide what you actually want to write to capture the domain operations > - how do you represent these in "function-like" forms. > Right, and as above I'd like the gates represented as functions, so (H 1) would be represented as (:H 1), and you could do (map (partial CNOT 1) [2 3 4]) to output [(:CNOT 1 2) (:CNOT 1 3) (:CNOT 1 4)]. > 4. Write macros that translate #3 into #2 using #1. > Aaaand this is where I falter, in both Clojure and Hy. We'll leave Hy aside, but in Clojure if I just do something like (defmacro defgate [gate nbits] (cond (= nbits 1) `(do (defn ~gate [bit#] (list ~(keyword gate) bit#)) ... similar for 2-bit, 3-bit gates, and a similar thing for parameterized gates) I can happily do something like (defn bellpair [b1 b2] (H b1) (CNOT b1 b2)) And (bellpair 1 2) will of course just give me (CNOT b1 b2) because clojure just returns the last thing in a sequence. Okay, there are ways to do this and trivially get that result ("make the whole thing a vector!" ie (defn bellpair [b1 b2] [(H b1...) and wrap that in a macro, but that runs into trouble if trivially done when mixed with other Clojure code. My best success so far has been something like (def code-atom (atom [])) (defn emit [op] (swap! code-atom conj op)) (defmacro defgate [gate nbits] (cond (= nbits 1) `(do (defn ~gate [bit#] (emit (list ~(keyword gate) bit#) ... ... (defmacro qcomp [form] `(do (reset! code-atom []) (eval ~form) @code-atom)) But this is a problem because I don't like the stateful "need an atom" implementation, and also we get issues like (qcomp [(H 1) (CNOT 1 2)]) work fine, but (qcomp (map H [1 2 3])) fails with ArityException. So it's not clear to me the prettiest way to go with this translation from "functional form" to "data structure". The "emit" solution just seems ugly, but I also don't want to walk a structure representing the code tree and selectively pick out operations and manually expand partial function applications, so I was wondering what suggestions anyone would have. -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/clojure/6cf8eb90-8cee-4a55-9a92-71b2163fcad6%40googlegroups.com. For more options, visit https://groups.google.com/d/optout.
Re: Macro help/strategy for writing a tiny DSL for circuit-model quantum computing?
My general advice would be not to start with macros. 1. Start with data - what are the entities (bits? gates?), and how do you represent them? It's ok (actually better) for these to be verbose. Plan for extension (maps are open, which is why they're so common in representing data in Clojure). 2. Create functions that manipulate that take and return that data. 3. Decide what you actually want to write to capture the domain operations - how do you represent these in "function-like" forms. 4. Write macros that translate #3 into #2 using #1. 5. Bask in the warm glow of a job well done. On Wednesday, May 15, 2019 at 1:56:30 PM UTC-5, Vic Putz wrote: > > ...which sounds a LOT more complex than it is. I don't care about > simulation--there are great Python-based packages for that. I don't even > care about verification or optimization yet; I just want to generate a data > structure that's exportable in, say, EDN that I can write a parser for in > Python. > > So... I'm dabbling in quantum computing, and the way these things work, > loosely, is that you have a fairly small address space (Google's > Bristlecone has 72 bits, and that's HUGE), and you basically apply "gates" > (atomic operations) to one, two, or occasionally three bits at a time. > That's basically it--you can have larger operations, but at the end of they > day they get broken down into 1/2/3-qubit gates. > > Right now the available kits use python to create these, and it just feels > clunky... not even like writing assembler, but like writing Python code to > write assembler, and there's no separation of data structure from execution > environment. I figure at the end of the day you're just making lists of > operations; if only there was a language suited for list processing! :) > > Example: here's Python code for a "bell pair" (when measured, both bits > are either 0 or 1), from ProjectQ, one of the Python libraries: > > # The H operator is defined as an object elsewhere > # because ProjectQ mixes the construction of the quantum > # code with its execution: > # create a main compiler engine > eng = MainEngine() > > # allocate one qubit > b1 = eng.allocate_qubit() > b2 = eng.allocate_qubit() > > # put it in superposition > H | b1 > CNOT | (b1, b2) > > # measure > Measure | q1 > Measure | q2 > > eng.flush() > # print the result: > print("Measured: {}{}".format(int(q1),int(q2))) > > > What I'd like to see is something more like > > ;; declare H to be a 1-bit gate and CNOT to be a 2-bit gate > ;; note these don't have to DO anything here as long as they can > ;; be interpreted in the Python backends > (defgate H 1) > (defgate CNOT 2) > > (defq-fn bell-pair [b1 b2] > (H b1) > (CNOT b1 b2)) > > (defq-pgm run-bell-pair [] > (let [b1 (allocate-bit 1) ;; variable of one qubit > b2 (allocate-bit 1)] ;; same > (bell-pair b1 b2) > (measure b1 b2)) > > ...where gates behave like functions (so you can use partial, map, etc, > like (map (partial CNOT 1) [b1 b2 b3]) > > And doing say (bell-pair 1 2) would spit out EDN > > ((:H 1) > (:CNOT 1 2)) > > and (run-bell-pair) might parse everything, include extra data about the > necessary machine, and reassign variables to allocated bits > > {:machine {:bitsize 2} ; calculated because only two bits were allocated > in all the subsidiary code > :code ((:block {:name bell-pair > :code (:H 0) (:CNOT 0 1) (:measure 0 1)}))} > > That sort of thing (the ":block" idea is mostly to delineate "where you > are" in the output program, and to group operations for display). Then I > could write a parser in Python for the simulators and such (or rewrite the > Clojure code for Hylang, which is its own exercise, but I could use the > Python libs directly then). There are other things I'd like (allocate > multi-bit variables, and apply gates to individual bits of those, like to > have the idea of reusing bits once they're measured and done with, that > sort of thing) but this is a start. > > On the surface, this looks really easy--you're just generating and > traversing data structures--but it's funny how hard I'm finding it because > it's key that you can execute proper computational code within (to do > things like determine angles for parameterized gates, apply gates in > complex ways to sequences, etc). > > I created a defgate macro that generated function stubs for gates that > created EDN for gates; that was easy. But properly doing the "defq-fn" > macro is eluding me. I want it to return a list of the gates' data > structures, but if I just eval the given form, I'll only get the last > result and (do...) to get a sequence is tricky because we're mixing gates > and clojure code. I tried a solution where the generated "gate" functions > would call an "emit" function which appended to a module-level variable, > and that srta worked, but it's not elegant (and not threadsafe). > > Ideally the defq-fn macro would create a
Macro help/strategy for writing a tiny DSL for circuit-model quantum computing?
...which sounds a LOT more complex than it is. I don't care about simulation--there are great Python-based packages for that. I don't even care about verification or optimization yet; I just want to generate a data structure that's exportable in, say, EDN that I can write a parser for in Python. So... I'm dabbling in quantum computing, and the way these things work, loosely, is that you have a fairly small address space (Google's Bristlecone has 72 bits, and that's HUGE), and you basically apply "gates" (atomic operations) to one, two, or occasionally three bits at a time. That's basically it--you can have larger operations, but at the end of they day they get broken down into 1/2/3-qubit gates. Right now the available kits use python to create these, and it just feels clunky... not even like writing assembler, but like writing Python code to write assembler, and there's no separation of data structure from execution environment. I figure at the end of the day you're just making lists of operations; if only there was a language suited for list processing! :) Example: here's Python code for a "bell pair" (when measured, both bits are either 0 or 1), from ProjectQ, one of the Python libraries: # The H operator is defined as an object elsewhere # because ProjectQ mixes the construction of the quantum # code with its execution: # create a main compiler engine eng = MainEngine() # allocate one qubit b1 = eng.allocate_qubit() b2 = eng.allocate_qubit() # put it in superposition H | b1 CNOT | (b1, b2) # measure Measure | q1 Measure | q2 eng.flush() # print the result: print("Measured: {}{}".format(int(q1),int(q2))) What I'd like to see is something more like ;; declare H to be a 1-bit gate and CNOT to be a 2-bit gate ;; note these don't have to DO anything here as long as they can ;; be interpreted in the Python backends (defgate H 1) (defgate CNOT 2) (defq-fn bell-pair [b1 b2] (H b1) (CNOT b1 b2)) (defq-pgm run-bell-pair [] (let [b1 (allocate-bit 1) ;; variable of one qubit b2 (allocate-bit 1)] ;; same (bell-pair b1 b2) (measure b1 b2)) ...where gates behave like functions (so you can use partial, map, etc, like (map (partial CNOT 1) [b1 b2 b3]) And doing say (bell-pair 1 2) would spit out EDN ((:H 1) (:CNOT 1 2)) and (run-bell-pair) might parse everything, include extra data about the necessary machine, and reassign variables to allocated bits {:machine {:bitsize 2} ; calculated because only two bits were allocated in all the subsidiary code :code ((:block {:name bell-pair :code (:H 0) (:CNOT 0 1) (:measure 0 1)}))} That sort of thing (the ":block" idea is mostly to delineate "where you are" in the output program, and to group operations for display). Then I could write a parser in Python for the simulators and such (or rewrite the Clojure code for Hylang, which is its own exercise, but I could use the Python libs directly then). There are other things I'd like (allocate multi-bit variables, and apply gates to individual bits of those, like to have the idea of reusing bits once they're measured and done with, that sort of thing) but this is a start. On the surface, this looks really easy--you're just generating and traversing data structures--but it's funny how hard I'm finding it because it's key that you can execute proper computational code within (to do things like determine angles for parameterized gates, apply gates in complex ways to sequences, etc). I created a defgate macro that generated function stubs for gates that created EDN for gates; that was easy. But properly doing the "defq-fn" macro is eluding me. I want it to return a list of the gates' data structures, but if I just eval the given form, I'll only get the last result and (do...) to get a sequence is tricky because we're mixing gates and clojure code. I tried a solution where the generated "gate" functions would call an "emit" function which appended to a module-level variable, and that srta worked, but it's not elegant (and not threadsafe). Ideally the defq-fn macro would create a function that returns a sequence of gates, one for every "gate function" executed in the execution of the function, but no other output (I may be making a specious distinction between defq-fn and defq-pgm, since I'd like to be able to allocate and "free" bits within qfns). Aaaanyway, long story. But what's the best/correct approach here? I'm new to macros, but game to flail wildly. Ideally any solution would be sorta-portable to Hylang (I'm doing the same exercise there and running into even stranger problems as for example "map" just returns a "map object" rather than simply being lazily evaluated, but I'm trying). And yeah, for "real" QC applications there's a lot missing here, but one has to start somewhere :) Vic -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this