UNPKG

qps-client

Version:
960 lines (800 loc) 22.7 kB
var DDP = require("ddp"); var login = require("ddp-login"); var spawn = require("child_process").spawn; var EJSON = require("ejson"); var fs = require("fs"); var path = require("path"); var envPaths = require("env-paths"); var QuantumCircuit = require("quantum-circuit"); var shellExec = function(command, toStdin, callback) { var args = command.split(" "); var cmd = args[0]; var error = null; args.shift(); var result = ""; var child = spawn(cmd, args); if(toStdin) { child.stdin.setEncoding("utf-8"); child.stdin.write(toStdin); child.stdin.end(); } child.stdout.on("data", function(message) { result += message; }); child.stderr.on("data", function(message) { result += message; }); child.on("error", function(err) { error = err; }); child.on("close", function(code, signal) { if(code == 0) { callback(null, result); } else { if(!error) { error = new Error(result); } callback(error); } }); }; var parseFixedColTable = function(tableRaw) { var tableList = tableRaw.trim().split("\n"); // Extract column names and indexes var cols = []; if(tableList.length) { var firstRow = tableList[0].trim(); var colIndexes = []; var insideString = false; for(var i = 0; i < firstRow.length; i++) { if(firstRow[i] == " " && i < firstRow.length - 1 && firstRow[i + 1] == " ") { insideString = false; } else { if(!insideString) { colIndexes.push(i); insideString = true; } } } var lastCol = colIndexes.length - 1; for(var i = 0; i < colIndexes.length; i++) { var colStart = colIndexes[i]; var colEnd = i < lastCol ? colIndexes[i + 1] : firstRow.length; var name = firstRow.substring(colStart, colEnd).trim().toLowerCase(); name = name.split(" ").join("_"); cols.push({ name: name, colStart: colStart, colEnd: colEnd }); } tableList.shift(); } // Extract data var rows = []; tableList.map(function(rowRaw) { var row = {}; cols.map(function(col) { row[col.name] = rowRaw.substring(col.colStart, col.colEnd).trim(); }); rows.push(row); }); return rows; }; var parseRigettiReservations = function(reservationsRaw) { var result = { current: [], upcoming: [] }; var reservationTables = reservationsRaw.trim().split("\n\n"); reservationTables.map(function(tableRaw) { var reservationsList = tableRaw.trim().split("\n"); if(reservationsList.length) { var tableType = reservationsList[0].trim(); reservationsList.shift(); } reservationsRaw = reservationsList.join("\n"); var reservations = parseFixedColTable(reservationsRaw); reservations.map(function(reservation, index) { for(var propertyName in reservation) { var propertyValue = reservation[propertyName]; if(propertyName == "price") { propertyValue = propertyValue.split("$").join(""); propertyValue = parseFloat(propertyValue); } reservations[index][propertyName] = propertyValue; } }); switch(tableType) { case "CURRENTLY RUNNING COMPUTE BLOCKS": result["current"] = reservations; break; case "UPCOMING COMPUTE BLOCKS": result["upcoming"] = reservations; break; } }); return result; }; var parseRigettiLattices = function(latticesRaw) { var lattices = []; var latticesList = latticesRaw.trim().split("LATTICE"); latticesList.map(function(latticeRaw) { var item = null; var latticeList = latticeRaw.trim().split("\n"); latticeList.map(function(latticeItemRaw) { var latticeItemList = latticeItemRaw.trim().split(":"); if(latticeItemList.length == 2) { var propertyName = latticeItemList[0].trim().toLowerCase(); propertyName = propertyName.split(" ").join("_"); propertyName = propertyName.split("(").join(""); propertyName = propertyName.split(".)").join(""); var propertyValue = latticeItemList[1].trim(); if(propertyName == "number_of_qubits") { propertyValue = parseInt(propertyValue); } if(propertyName == "qubits") { propertyValue = propertyValue.split(","); propertyValue.map(function(qubit, qubitIndex) { propertyValue[qubitIndex] = parseInt(qubit); }); } if(propertyName == "price_per_min") { propertyValue = propertyValue.split("$").join(""); propertyValue = parseFloat(propertyValue); } if(!item) { item = {}; } item[propertyName] = propertyValue; } }); if(item) { lattices.push(item); } }); var devices = {}; lattices.map(function(lattice) { if(!devices[lattice.device]) { devices[lattice.device] = { lattices: [] }; } devices[lattice.device].lattices.push(lattice); }); var deviceList = []; for(var device in devices) { deviceList.push({ name: device, lattices: devices[device].lattices }); } return deviceList; }; var updateRigettiReservationInfo = function(info) { info.devices.map(function(device) { if(device.lattices) { device.lattices.map(function(lattice) { // Upcoming var upcoming = []; if(info.reservations && info.reservations.upcoming) { upcoming = info.reservations.upcoming.filter(function(reservation) { return reservation.lattice == lattice.name; }); } if(upcoming.length) { lattice.reservation = "upcoming"; } // Current var current = []; if(info.reservations && info.reservations.current) { current = info.reservations.current.filter(function(reservation) { return reservation.lattice == lattice.name; }); } if(current.length) { lattice.reservation = "current"; } }); } }); return info; }; var parseQiskitBackends = function(backendsRaw) { return (backendsRaw + "").trim().split("\n"); }; var QPSClient = function(host, port, ssl, account, pass, backends, pythonExecutable, toasterExecutable) { host = host || ""; port = port || 80; ssl = ssl || false; account = account || null; pass = pass || null; backends = backends || []; pythonExecutable = pythonExecutable || "python"; toasterExecutable = toasterExecutable || "qubit-toaster"; var devMode = process.env.DEV_MODE || false; var tokenEnvVar = "QPS_LOGIN_TOKEN"; var paths = envPaths("qps-client"); var configFilename = path.resolve(paths.config, ".qps-config.json"); var writeConfig = function() { var config = { token: process.env[tokenEnvVar] }; try { if(!fs.existsSync(paths.config)) { fs.mkdirSync(paths.config); } } catch(e) { // Failed but not reporting - writing will fail and will be reported. } try { fs.writeFileSync(configFilename, JSON.stringify(config), "utf8"); } catch(e) { console.log("Warning: Error writing configuration file. " + e.message); } }; var readConfig = function() { var configRaw = ""; try { if(fs.existsSync(configFilename)) { configRaw = fs.readFileSync(configFilename, "utf8"); } } catch(e) { console.log("Warning: Error reading configuration file. " + e.message); } var config = {}; if(configRaw) { try { config = JSON.parse(configRaw); } catch(e) { console.log("Warning: Error parsing configuration file \"" + configFilename + "\". " + e.message); } }; process.env[tokenEnvVar] = config.token ? config.token : ""; }; readConfig(); var ddpClient = new DDP({ host: host, port: port, ssl: ssl }); ddpClient.connect(function (err, wasReconnect) { if(err) { console.log("Unable to connect.", err.message ? err.message : ""); ddpClient.close(); return; } if(wasReconnect) { console.log("Reconected."); } else { console.log("Connected."); } ddpClient.on("message", function(msg) { var message = EJSON.parse(msg); if(message && message.command) { switch(message.command) { case "run_qvm": { var circuit = new QuantumCircuit(); circuit.load(message.circuit); var pythonCode = circuit.exportPyquil("", false, null, null, message.lattice, message.asQVM); updateBackendsOutput("rigetti", "Running " + circuit.numQubits + " qubit circuit...\n", "busy"); shellExec(pythonExecutable + " -", pythonCode, function(e, r) { var output = ""; var outputType = ""; if(e) { output = e.message; outputType = "error"; } else { output = r; outputType = "success"; } updateBackendsOutput("rigetti", output, outputType); }); }; break; case "run_qiskit": { var circuit = new QuantumCircuit(); circuit.load(message.circuit); var pythonCode = circuit.exportQiskit("", false, null, null, message.provider, message.backend); updateBackendsOutput("qiskit", "Running " + circuit.numQubits + " qubit circuit...\n", "busy"); shellExec(pythonExecutable + " -", pythonCode, function(e, r) { var output = ""; var outputType = ""; if(e) { output = e.message; outputType = "error"; } else { output = r; outputType = "success"; } updateBackendsOutput("qiskit", output, outputType); }); }; break; case "run_ionq": { var circuit = new QuantumCircuit(); circuit.load(message.circuit); var pythonCode = circuit.exportQiskit("", false, null, null, message.provider, message.backend); updateBackendsOutput("ionq", "Running " + circuit.numQubits + " qubit circuit...\n", "busy"); shellExec(pythonExecutable + " -", pythonCode, function(e, r) { var output = ""; var outputType = ""; if(e) { output = e.message; outputType = "error"; } else { output = r; outputType = "success"; } updateBackendsOutput("ionq", output, outputType); }); }; break; case "run_toaster": { var circuit = new QuantumCircuit(); circuit.load(message.circuit); var toasterCode = JSON.stringify(circuit.exportRaw()); updateBackendsOutput("qubit-toaster", "Running " + circuit.numQubits + " qubit circuit...\n", "busy"); shellExec(toasterExecutable + " --shots 1024 -", toasterCode, function(e, r) { var output = ""; var outputType = ""; if(e) { output = e.message; outputType = "error"; } else { output = r; outputType = "success"; } updateBackendsOutput("qubit-toaster", output, outputType); }); }; break; } } }); login(ddpClient, { // Options below are the defaults env: tokenEnvVar, // Name of an environment variable to check for a // token. If a token is found and is good, // authentication will require no user interaction. method: "account", // Login method: account, email, username or token account: account, // Prompt for account info by default pass: pass, // Prompt for password by default retry: 5, // Number of login attempts to make plaintext: false // Do not fallback to plaintext password compatibility // for older non-bcrypt accounts }, function(error, userInfo) { if(error) { // Something went wrong... console.log(error.message); ddpClient.close(); } else { // We are now logged in, with userInfo.token as our session auth token. process.env[tokenEnvVar] = userInfo.token || ""; console.log("Login successful."); writeConfig(); updateBackends(backends); } } ); }); var updateBackendsOutput = function(backendType, message, messageType) { ddpClient.call( "updateBackendsOutput", [backendType, message, messageType], function(err, res) { if(err) { console.log(err); } }, function() { } ); }; var detectBackends = function() { var backendList = []; var pythonCode = ""; console.log("Auto detecting backends..."); // Qubit Toaster var toasterExecutable = "qubit-toaster"; shellExec(toasterExecutable + " -v", null, function(e, result) { if(e) { // No toaster } else { // Found toaster console.log("Found Qubit Toaster"); backendList.push("qubit-toaster"); } // Rigetti pythonCode = "import pyquil\n"; shellExec(pythonExecutable + " -", pythonCode, function(e, result) { if(e) { // No pyquil } else { // Found pyquil console.log("Found pyQuil"); backendList.push("rigetti-qvm"); backendList.push("rigetti-qpu"); } // Qiskit pythonCode = "import qiskit\n"; shellExec(pythonExecutable + " -", pythonCode, function(e, result) { if(e) { // No qiskit } else { // Found qiskit console.log("Found Qiskit"); backendList.push("qiskit-aer"); backendList.push("qiskit-ibmq"); } pythonCode = "import qiskit_ionq\n"; shellExec(pythonExecutable + " -", pythonCode, function(e, result) { if(e) { console.log(e); // No ionq } else { // Found ionq console.log("Found IONQ (Qiskit)"); backendList.push("qiskit-ionq"); } // Cirq pythonCode = "import cirq\n"; shellExec(pythonExecutable + " -", pythonCode, function(e, result) { if(e) { // No cirq } else { // Found cirq console.log("Found Cirq"); backendList.push("google-cirq"); } if(backendList.length) { if(backendList.indexOf("rigetti-qpu") >= 0) { shellExec("qcs", null, function(e, r) { if(e) { backendList.splice(backendList.indexOf("rigetti-qpu"), 1); } updateBackends(backendList); }); } else { updateBackends(backendList); } } else { console.log("No backends found. Did you activated your virtual environment?"); } }); }); }); }); }); }; var updateBackends = function(backendList) { if(!backendList || !backendList.map || !backendList.length) { detectBackends(); return; } var backendInfo = {}; console.log("Backends:"); backendList.map(function(backend) { switch(backend) { case "qubit-toaster": { console.log(backend); backendInfo.qubitToaster = { status: "OK" }; ddpClient.call( "updateBackends", [backendInfo], function(err, res) { if(err) { console.log(err); } }, function() { } ); }; break; case "qiskit-aer": { console.log(backend); // !!! implement Aer presence check backendInfo.qiskitAer = { backends: [] }; pythonCode = ""; pythonCode += "from qiskit import Aer\n"; pythonCode += "for backend in Aer.backends():\n"; pythonCode += " print(backend.name())\n"; pythonCode += "\n"; shellExec(pythonExecutable + " -", pythonCode, function(e, bcks) { var output = ""; if(e) { console.log(e); } else { backendInfo.qiskitAer.backends = parseQiskitBackends(bcks); ddpClient.call( "updateBackends", [backendInfo], function(err, res) { if(err) { console.log(err); } }, function() { } ); } }); }; break; case "qiskit-ibmq": { console.log(backend); // !!! implement IBMQ presence check backendInfo.qiskitIBMQ = { backends: [] }; pythonCode = ""; pythonCode += "from qiskit import IBMQ\n"; pythonCode += "IBMQ.load_account()\n"; pythonCode += "provider = IBMQ.get_provider(hub=\"ibm-q\", group=\"open\", project=\"main\")\n"; pythonCode += "backends = provider.backends(filters=lambda x: x.configuration().n_qubits >= 5)\n"; pythonCode += "for backend in backends:\n"; pythonCode += " print(backend.name())\n"; pythonCode += "\n"; shellExec(pythonExecutable + " -", pythonCode, function(e, bcks) { var output = ""; if(e) { console.log(e); } else { backendInfo.qiskitIBMQ.backends = parseQiskitBackends(bcks); ddpClient.call( "updateBackends", [backendInfo], function(err, res) { if(err) { console.log(err); } }, function() { } ); } }); }; break; case "qiskit-ionq": { console.log(backend); // !!! implement IONQ presence check backendInfo.qiskitIONQ = { backends: [] }; pythonCode = ""; pythonCode += "from qiskit_ionq import IonQProvider\n"; pythonCode += "provider = IonQProvider()\n"; pythonCode += "backends = provider.backends()\n"; pythonCode += "for backend in backends:\n"; pythonCode += " print(backend)\n"; pythonCode += "\n"; shellExec(pythonExecutable + " -", pythonCode, function(e, bcks) { var output = ""; if(e) { console.log(e); } else { backendInfo.qiskitIONQ.backends = parseQiskitBackends(bcks); ddpClient.call( "updateBackends", [backendInfo], function(err, res) { if(err) { console.log(err); } }, function() { } ); } }); }; break; case "rigetti-qvm": { console.log(backend); // !!! Implement QVM presence check backendInfo.rigettiQvm = { status: "OK" }; ddpClient.call( "updateBackends", [backendInfo], function(err, res) { if(err) { console.log(err); } }, function() { } ); }; break; case "rigetti-qpu": { console.log(backend); backendInfo.rigettiQpu = { }; if(devMode) { backendInfo.rigettiQpu.devices = parseRigettiLattices(_lattices); backendInfo.rigettiQpu.reservations = parseRigettiReservations(_reservations); backendInfo.rigettiQpu = updateRigettiReservationInfo(backendInfo.rigettiQpu); ddpClient.call( "updateBackends", [backendInfo], function(err, res) { if(err) { console.log(err); } }, function() { } ); } else { shellExec("qcs", null, function(e, r) { if(e) { console.log("Warning: \"rigetti-qpu\" backend not found. It is available only inside quantum machine image. Error running \"qcs\" CLI:", e.message); } else { shellExec("qcs lattices", null, function(e, lattices) { if(e) { console.log("Error reading lattices:", e.message); } else { backendInfo.rigettiQpu.devices = parseRigettiLattices(lattices); shellExec("qcs reservations", null, function(e, reservations) { if(e) { console.log("Error reading reservations:", e.message); } else { backendInfo.rigettiQpu.reservations = parseRigettiReservations(reservations); backendInfo.rigettiQpu = updateRigettiReservationInfo(backendInfo.rigettiQpu); ddpClient.call( "updateBackends", [backendInfo], function(err, res) { if(err) { console.log(err); } }, function() { } ); } }); } }); } }); } }; break; case "google-cirq": { console.log(backend); // Not implemented yet }; break; } }); }; }; if(typeof module != "undefined" && module.exports) { module.exports = QPSClient; } else { this.QPSClient = QPSClient; } // output from QCS CLI - used when env DEV_MODE=1 var _reservations = ` CURRENTLY RUNNING COMPUTE BLOCKS ID START END DURATION LATTICE PRICE 975 2019-01-16 13:45 CET 2019-01-16 14:00 CET 15.00m Aspen-1-5Q-C $20.00 UPCOMING COMPUTE BLOCKS ID START END DURATION LATTICE PRICE 976 2019-01-16 18:30 CET 2019-01-16 18:45 CET 15.00m Aspen-1-2Q-B $20.00 `; /* var _reservations = ` UPCOMING COMPUTE BLOCKS ID START END DURATION LATTICE PRICE 903 2019-01-11 10:30 CET 2019-01-11 10:45 CET 15.00m Aspen-1-4Q-B $70.00 904 2019-01-11 10:45 CET 2019-01-11 11:00 CET 15.00m Aspen-1-2Q-B $20.00 `; */ var _lattices = ` LATTICE Name: Aspen-1-2Q-B Device: Aspen-1 Number of qubits: 2 Qubits: 14,15 Price (per min.): $1.33 LATTICE Name: Aspen-1-3Q-B Device: Aspen-1 Number of qubits: 3 Qubits: 14,15,16 Price (per min.): $2.50 LATTICE Name: Aspen-1-4Q-B Device: Aspen-1 Number of qubits: 4 Qubits: 1,14,15,16 Price (per min.): $4.67 LATTICE Name: Aspen-1-5Q-B Device: Aspen-1 Number of qubits: 5 Qubits: 0,1,14,15,16 Price (per min.): $6.08 LATTICE Name: Aspen-1-6Q-B Device: Aspen-1 Number of qubits: 6 Qubits: 10,11,14,15,16,17 Price (per min.): $7.50 LATTICE Name: Aspen-1-7Q-B Device: Aspen-1 Number of qubits: 7 Qubits: 1,10,11,14,15,16,17 Price (per min.): $8.92 LATTICE Name: Aspen-1-8Q-B Device: Aspen-1 Number of qubits: 8 Qubits: 0,1,10,11,14,15,16,17 Price (per min.): $10.33 LATTICE Name: Aspen-1-9Q-B Device: Aspen-1 Number of qubits: 9 Qubits: 0,1,10,11,13,14,15,16,17 Price (per min.): $11.75 LATTICE Name: Aspen-1-16Q-A Device: Aspen-1 Number of qubits: 16 Qubits: 0,1,2,3,4,5,6,7,10,11,12,13,14,15,16,17 Price (per min.): $21.67 LATTICE Name: Aspen-1-2Q-C Device: Aspen-1 Number of qubits: 2 Qubits: 2,3 Price (per min.): $1.33 LATTICE Name: Aspen-1-3Q-C Device: Aspen-1 Number of qubits: 3 Qubits: 2,3,4 Price (per min.): $2.50 LATTICE Name: Aspen-1-4Q-C Device: Aspen-1 Number of qubits: 4 Qubits: 1,2,3,4 Price (per min.): $4.67 LATTICE Name: Aspen-1-5Q-C Device: Aspen-1 Number of qubits: 5 Qubits: 0,1,2,3,4 Price (per min.): $6.08 LATTICE Name: Aspen-1-6Q-C Device: Aspen-1 Number of qubits: 6 Qubits: 0,1,2,3,4,5 Price (per min.): $7.50 LATTICE Name: Aspen-1-7Q-C Device: Aspen-1 Number of qubits: 7 Qubits: 0,1,2,3,4,5,6 Price (per min.): $8.92 LATTICE Name: Aspen-1-8Q-C Device: Aspen-1 Number of qubits: 8 Qubits: 0,1,2,3,4,5,6,7 Price (per min.): $10.33 LATTICE Name: Aspen-1-10Q-C Device: Aspen-1 Number of qubits: 10 Qubits: 0,1,2,3,4,5,6,7,15,16 Price (per min.): $13.17 LATTICE Name: Aspen-1-10Q-B Device: Aspen-1 Number of qubits: 10 Qubits: 1,2,10,11,12,13,14,15,16,17 Price (per min.): $13.17 `; // output from running on lattice (bell state at qubits 14 and 15) /* {14: array([0, 1, 1, 1]), 15: array([0, 1, 1, 1])} */