diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9fa470c50..1078edc0c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,6 +55,14 @@ jobs: POSTGRES_PASSWORD: postgres - run: lerna bootstrap - run: yarn build + - name: javascript:build + run: |- + cd apps/www + bundle exec rails javascript:build + - name: css:build + run: |- + cd apps/www + bundle exec rails css:build - run: yarn test:ci env: RAILS_ENV: test diff --git a/Dockerfile_cirq_backend b/Dockerfile_cirq_backend new file mode 100644 index 000000000..017d79621 --- /dev/null +++ b/Dockerfile_cirq_backend @@ -0,0 +1,106 @@ +# 1. The Qniapp is built as follows: +# $ git clone https://github.com/qniapp/qni.git +# $ cd qni +# $ git fetch origin cirq-backend +# $ git checkout cirq-backend +# $ docker build -f Dockerfile_cirq_backend . -t qni_cirq_backend +# 2. Then run by: +# $ docker run -p 3000:3000 --gpus all --rm -it qni_cirq_backend +# 3. access http://127.0.0.1:3000 in your browser + +# Troubleshooting +# If the port 3000 is already used, change 3000 to 4000 (for example) +# $ docker run -p 4000:3000 --gpus all --rm -it qni_cirq_backend +# and access http://127.0.0.1:4000 in your browser + +FROM nvidia/cuda:11.5.1-devel-ubuntu20.04 + +RUN apt update +RUN apt -y upgrade +RUN apt install -y sudo +ENV DEBIAN_FRONTEND=noninteractive +RUN apt install -y tzdata +# set your timezone +ENV TZ Asia/Tokyo +RUN echo "${TZ}" > /etc/timezone \ + && rm /etc/localtime \ + && ln -s /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \ + && dpkg-reconfigure -f noninteractive tzdata + +RUN apt install -y build-essential +RUN apt install -y git wget time curl libssl-dev zlib1g-dev libpq-dev +RUN apt install -y redis-server +RUN apt install -y ng-common ng-cjk emacs-nox +RUN apt install -y postgresql postgresql-contrib + +## cuda +RUN apt -y install cuda-drivers +RUN wget https://developer.download.nvidia.com/compute/cuquantum/redist/cuquantum/linux-x86_64/cuquantum-linux-x86_64-22.03.0.40-archive.tar.xz +RUN tar xvfJ cuquantum-linux-x86_64-22.03.0.40-archive.tar.xz -C /tmp +RUN cd /tmp/cuquantum-linux-x86_64-22.03.0.40-archive ; tar cf - . | (cd /usr/local; tar vxf -) + +## node.js +RUN apt install -y nodejs npm && npm install n -g && n stable && apt purge -y nodejs npm + +## npm +RUN curl -qL https://www.npmjs.com/install.sh | sh + +## yarn +RUN npm install -g yarn + +## ruby +RUN wget https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.4.tar.gz && tar xvfz ruby-2.7.4.tar.gz && cd ruby-2.7.4 && ./configure && make && make install + +## python3 +RUN apt install -y python3 python3-pip +RUN sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 1 + +ARG DOCKER_UID=1000 +ARG DOCKER_USER=docker +ARG DOCKER_PASSWORD=docker +RUN useradd -u $DOCKER_UID -m $DOCKER_USER --shell /bin/bash && echo "$DOCKER_USER:$DOCKER_PASSWORD" | chpasswd && echo "$DOCKER_USER ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers + +USER ${DOCKER_USER} +RUN echo "\n\ +[user]\n\ + email = ${GIT_EMAIL}\n\ + name = ${GIT_NAME}\n\ +" > /home/$DOCKER_USER/.gitconfig + +SHELL ["/bin/bash", "-l", "-c"] + +# qsim +ENV PATH=/usr/local/cuda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/$DOCKER_USER/.rbenv/bin:/home/$DOCKER_USER/.local/bin +# for qsimcirq +ENV CUQUANTUM_DIR=/usr/local +RUN cd /home/$DOCKER_USER && pip3 install pybind11 pytest numpy sympy cirq +RUN cd /home/$DOCKER_USER && git clone https://github.com/quantumlib/qsim.git && cd qsim && make # && make run-py-tests # to use gpu qsim must be build locally + +RUN cd /home/$DOCKER_USER && echo "cd /home/$DOCKER_USER" >> ~/.bashrc +RUN cd /home/$DOCKER_USER && git clone https://github.com/rbenv/rbenv.git ~/.rbenv +RUN cd /home/$DOCKER_USER && git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build +RUN cd /home/$DOCKER_USER && echo "export PATH=$PATH:$HOME/.rbenv/bin" >> ~/.bashrc +RUN cd /home/$DOCKER_USER && echo "export CUQUANTUM_DIR=/usr/local" >> ~/.bashrc +RUN cd /home/$DOCKER_USER && echo "export LANG=C.UTF-8" >> ~/.bashrc +RUN cd /home/$DOCKER_USER && echo "export LC_CTYPE=C.UTF-8" >> ~/.bashrc +RUN cd /home/$DOCKER_USER && echo "export LD_LIBRARY_PATH=${CUQUANTUM_DIR}/lib64:${LD_LIBRARY_PATH}" >> ~/.bashrc +RUN cd /home/$DOCKER_USER && git clone https://github.com/qniapp/qni.git +RUN cd /home/$DOCKER_USER && cd qni && git fetch origin cirq-backend +RUN cd /home/$DOCKER_USER && cd qni && git checkout cirq-backend +RUN cd /home/$DOCKER_USER && cd qni && git log -1 + +## settings for rails +RUN cd /home/$DOCKER_USER && cd qni/apps/www && bundle config set path 'vendor/cache' && bundle install && yarn install +RUN cd /home/$DOCKER_USER && cd qni && yarn build && cd apps/www && ./bin/rails css:build && ./bin/rails javascript:build + +## settings for postgresql +RUN sudo -u postgres service postgresql start && sudo -u postgres psql --command "CREATE USER docker WITH SUPERUSER PASSWORD 'docker';" && sudo -u postgres createdb -O docker docker +RUN cd /home/$DOCKER_USER && sudo -u postgres service postgresql start && cd qni/apps/www && ./bin/rails db:create && ./bin/rails db:migrate && ./bin/rails db:fixtures:load + +RUN cd /home/$DOCKER_USER && echo -e "#!/usr/bin/env bash\n\ +export PYTHONPATH=/home/docker/qsim/ \n\ +sudo -u postgres service postgresql start \n\ +cd /home/${DOCKER_USER} ; source ~/.bashrc ; cd qni/apps/www \n\ +./bin/rails s -b 0.0.0.0" > /tmp/startup.sh +RUN chmod 744 /tmp/startup.sh +#ENTRYPOINT ["/bin/sh", "-c", "/tmp/startup.sh"] diff --git a/Dockerfile_cuquantum b/Dockerfile_cuquantum new file mode 100644 index 000000000..708589d1b --- /dev/null +++ b/Dockerfile_cuquantum @@ -0,0 +1,105 @@ +# 1. The Qniapp is built as follows: +# $ git clone https://github.com/qniapp/qni.git +# $ cd qni +# $ docker build -f Dockerfile_cuquantum . -t qni_cuquantum +# 2. Then run by: +# $ docker run -p 3000:3000 --gpus all --rm -it qni_cuquantum +# 3. access http://127.0.0.1:3000 in your browser + +# Troubleshooting +# If the port 3000 is already used, change 3000 to 4000 (for example) +# $ docker run -p 4000:3000 --gpus all --rm -it qni_cuquantum +# and access http://127.0.0.1:4000 in your browser + +FROM nvidia/cuda:11.5.1-devel-ubuntu20.04 + +RUN apt update +RUN apt -y upgrade +RUN apt install -y sudo +ENV DEBIAN_FRONTEND=noninteractive +RUN apt install -y tzdata +# set your timezone +ENV TZ Asia/Tokyo +RUN echo "${TZ}" > /etc/timezone \ + && rm /etc/localtime \ + && ln -s /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \ + && dpkg-reconfigure -f noninteractive tzdata + +RUN apt install -y build-essential +RUN apt install -y git wget time curl libssl-dev zlib1g-dev libpq-dev +RUN apt install -y redis-server +RUN apt install -y ng-common ng-cjk emacs-nox +RUN apt install -y postgresql postgresql-contrib + +## cuda +RUN apt -y install cuda-drivers +RUN apt -y install libcutensor1 libcutensor-dev libcutensor-doc +RUN wget https://developer.download.nvidia.com/compute/cuquantum/redist/cuquantum/linux-x86_64/cuquantum-linux-x86_64-22.03.0.40-archive.tar.xz +RUN tar xvfJ cuquantum-linux-x86_64-22.03.0.40-archive.tar.xz -C /tmp +RUN cd /tmp/cuquantum-linux-x86_64-22.03.0.40-archive ; tar cf - . | (cd /usr/local; tar vxf -) + +## node.js +RUN apt install -y nodejs npm && npm install n -g && n stable && apt purge -y nodejs npm + +## npm +RUN curl -qL https://www.npmjs.com/install.sh | sh + +## yarn +RUN npm install -g yarn + +## ruby +RUN wget https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.4.tar.gz && tar xvfz ruby-2.7.4.tar.gz && cd ruby-2.7.4 && ./configure && make && make install + +## python3 +RUN apt install -y python3 python3-pip +RUN sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 1 + +ARG DOCKER_UID=1000 +ARG DOCKER_USER=docker +ARG DOCKER_PASSWORD=docker +RUN useradd -u $DOCKER_UID -m $DOCKER_USER --shell /bin/bash && echo "$DOCKER_USER:$DOCKER_PASSWORD" | chpasswd && echo "$DOCKER_USER ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers + +USER ${DOCKER_USER} +RUN echo "\n\ +[user]\n\ + email = ${GIT_EMAIL}\n\ + name = ${GIT_NAME}\n\ +" > /home/$DOCKER_USER/.gitconfig + +SHELL ["/bin/bash", "-l", "-c"] + +# cuQuantum +ENV CUQUANTUM_ROOT=/usr/local +ENV CUSTATEVEC_ROOT=/usr/local +ENV CUTENSOR_ROOT=/usr +ENV CUDA_PATH=/usr/local/cuda +ENV CUQUANTUM_IGNORE_SOLVER=1 +RUN cd /home/$DOCKER_USER && pip3 install cupy-cuda115 numpy scipy cython +RUN cd /home/$DOCKER_USER && git clone https://github.com/NVIDIA/cuQuantum.git && cd cuQuantum/python && pip3 install -v . + +# Qni +RUN cd /home/$DOCKER_USER && echo "cd /home/$DOCKER_USER" >> ~/.bashrc +RUN cd /home/$DOCKER_USER && git clone https://github.com/rbenv/rbenv.git ~/.rbenv +RUN cd /home/$DOCKER_USER && git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build +RUN cd /home/$DOCKER_USER && echo "export PATH=$PATH:$HOME/.rbenv/bin" >> ~/.bashrc +RUN cd /home/$DOCKER_USER && echo "export CUQUANTUM_ROOT=/usr/local" >> ~/.bashrc +RUN cd /home/$DOCKER_USER && echo "export LD_LIBRARY_PATH=${CUQUANTUM_ROOT}/lib64:${LD_LIBRARY_PATH}" >> ~/.bashrc +RUN cd /home/$DOCKER_USER && git clone https://github.com/nakatamaho/qni.git +RUN cd /home/$DOCKER_USER && cd qni && git fetch origin cuquantum-backend +RUN cd /home/$DOCKER_USER && cd qni && git checkout cuquantum-backend + +## settings for rails +RUN cd /home/$DOCKER_USER && cd qni/apps/www && bundle config set path 'vendor/cache' && bundle install && yarn install +RUN cd /home/$DOCKER_USER && cd qni && yarn build && cd apps/www && ./bin/rails css:build && ./bin/rails javascript:build + +## settings for postgresql +RUN sudo -u postgres service postgresql start && sudo -u postgres psql --command "CREATE USER docker WITH SUPERUSER PASSWORD 'docker';" && sudo -u postgres createdb -O docker docker +RUN cd /home/$DOCKER_USER && sudo -u postgres service postgresql start && cd qni/apps/www && ./bin/rails db:create && ./bin/rails db:migrate && ./bin/rails db:fixtures:load + +RUN cd /home/$DOCKER_USER && echo -e "#!/usr/bin/env bash\n\ +export PYTHONIOENCODING=utf-8 \n\ +sudo -u postgres service postgresql start \n\ +cd /home/${DOCKER_USER} ; source ~/.bashrc ; cd qni/apps/www \n\ +./bin/rails s -b 0.0.0.0" > /tmp/startup.sh +RUN chmod 744 /tmp/startup.sh +ENTRYPOINT ["/bin/sh", "-c", "/tmp/startup.sh"] diff --git a/apps/www/Gemfile b/apps/www/Gemfile index 1db9c550d..51aa85980 100644 --- a/apps/www/Gemfile +++ b/apps/www/Gemfile @@ -35,6 +35,7 @@ gem 'better_html' gem 'bootsnap', '>= 1.4.2', require: false gem 'grover' gem 'serviceworker-rails' +gem 'pycall' group :development, :test do gem 'byebug', platforms: %i[mri mingw x64_mingw] diff --git a/apps/www/Gemfile.lock b/apps/www/Gemfile.lock index b5e387cd5..6effa219e 100644 --- a/apps/www/Gemfile.lock +++ b/apps/www/Gemfile.lock @@ -165,6 +165,7 @@ GEM public_suffix (4.0.6) puma (5.6.4) nio4r (~> 2.0) + pycall (1.4.1) racc (1.6.0) rack (2.2.3) rack-mini-profiler (2.3.3) @@ -297,6 +298,7 @@ DEPENDENCIES listen (>= 3.0.5, < 3.2) pg puma (~> 5.6) + pycall rack-mini-profiler rails (~> 6.1) rbtrace diff --git a/apps/www/app/assets/javascripts/serviceworker.js b/apps/www/app/assets/javascripts/serviceworker.js index 3fb6201ca..97941b585 100644 --- a/apps/www/app/assets/javascripts/serviceworker.js +++ b/apps/www/app/assets/javascripts/serviceworker.js @@ -9,26 +9,77 @@ self.addEventListener( const steps = e.data.steps const targets = e.data.targets const simulator = new Simulator('0'.repeat(qubitCount)) + const backend = e.data.backend Util.notNull(qubitCount) Util.notNull(stepIndex) Util.notNull(steps) Util.notNull(targets) - steps.forEach((operations, i) => { - simulator.runStep(operations) - - self.postMessage({ - type: 'step', - step: i, - amplitudes: i === stepIndex ? simulator.amplitudes(targets) : [], - blochVectors: simulator.blochVectors, - measuredBits: simulator.measuredBits, - flags: simulator.flags - }) - }) + if (backend) { + const json = e.data.json + Util.notNull(json) + + runBackend(json, qubitCount, stepIndex, steps, targets, backend) + } else { + runQniSimulator(qubitCount, stepIndex, steps, targets) + } self.postMessage({type: 'finish'}) }, false ) + +function runQniSimulator(qubitCount, stepIndex, steps, targets) { + const simulator = new Simulator('0'.repeat(qubitCount)) + + steps.forEach((operations, i) => { + simulator.runStep(operations) + + self.postMessage({ + type: 'step', + step: i, + amplitudes: i === stepIndex ? simulator.amplitudes(targets) : {}, + blochVectors: simulator.blochVectors, + measuredBits: simulator.measuredBits, + flags: simulator.flags + }) + }) +} + +function runBackend(json, qubitCount, stepIndex, steps, targets, backend) { + const params = new URLSearchParams({ + id: json, + qubitCount: qubitCount, + stepIndex: stepIndex, + steps: JSON.stringify(steps), + targets: targets, + backend: backend + }) + + fetch(`/backend.json?${params}`, { + method: 'GET' + }) + .then(response => { + if (!response.ok) { + throw new Error("Failed to connect to Qni's backend endpoint.") + } + return response.json() + }) + .then(response => { + for (let i = 0; i < response.length; i++) { + const stepResult = response[i] + self.postMessage({ + type: 'step', + step: i, + amplitudes: stepResult['amplitudes'], + blochVectors: stepResult['blochVectors'], + measuredBits: stepResult['measuredBits'], + flags: {} + }) + } + }) + .catch(error => { + console.error(error) + }) +} diff --git a/apps/www/app/controllers/backend_controller.rb b/apps/www/app/controllers/backend_controller.rb new file mode 100644 index 000000000..73a588e7a --- /dev/null +++ b/apps/www/app/controllers/backend_controller.rb @@ -0,0 +1,20 @@ +require 'cirq' + +class BackendController < ApplicationController + # rubocop:disable Metrics/AbcSize + def show + backend = params[:backend] + circuit_id = params[:id] + qubit_count = params[:qubitCount].to_i + step_index = params[:stepIndex].to_i + steps = params[:steps] ? JSON.parse(params[:steps]) : [] + targets = params[:targets].split(',').map(&:to_i) if params[:targets] + + raise "Unsupported backend: #{backend}" unless backend == 'cirq' + + @step_results = + Cirq.new(circuit_id: circuit_id, qubit_count: qubit_count, step_index: step_index, steps: steps, targets: targets) + .run + end + # rubocop:enable Metrics/AbcSize +end diff --git a/apps/www/app/controllers/cirq_controller.rb b/apps/www/app/controllers/cirq_controller.rb new file mode 100644 index 000000000..0a177cb8e --- /dev/null +++ b/apps/www/app/controllers/cirq_controller.rb @@ -0,0 +1,3 @@ +class CirqController < ApplicationController + def show; end +end diff --git a/apps/www/app/models/concerns/routing.rb b/apps/www/app/models/concerns/routing.rb index 1ec9745d8..efdac9ba8 100644 --- a/apps/www/app/models/concerns/routing.rb +++ b/apps/www/app/models/concerns/routing.rb @@ -3,15 +3,9 @@ module Routing extend ActiveSupport::Concern - included do - include Rails.application.routes.url_helpers - end + included { include Rails.application.routes.url_helpers } def default_url_options - if Rails.env.production? - { host: 'qniapp.net' } - else - { host: 'localhost', port: 3000 } - end + Rails.env.production? ? { host: 'qniapp.net' } : { host: 'localhost', port: 3000 } end end diff --git a/apps/www/app/views/backend/show.json.jbuilder b/apps/www/app/views/backend/show.json.jbuilder new file mode 100644 index 000000000..1b6944d1a --- /dev/null +++ b/apps/www/app/views/backend/show.json.jbuilder @@ -0,0 +1 @@ +json.array! @step_results diff --git a/apps/www/app/views/cirq/show.html.erb b/apps/www/app/views/cirq/show.html.erb new file mode 100644 index 000000000..5c3a74407 --- /dev/null +++ b/apps/www/app/views/cirq/show.html.erb @@ -0,0 +1,201 @@ +<% content_for :meta_ogp do %> + + + <% if @json %> + + + <% end %> + <% if @circuit&.social_image&.attached? %> + + <% end %> +<% end %> + +
+ + +
+ <%= share_modal %> + <%= menu %> +
+ +
+ +
+ <%= icon :mixer_horizontal %> +
+
+
+ + <%= palette_md %> + + + +
+ <%= + run_circuit_button class: + 'run-circuit-button--mobile inline-block md:hidden w-10 h-10 shrink-0 mr-2 bg-white rounded-full drop-shadow-md' + %> + <%= palette %> +
+ + <%= circle_notation %> +
+ + <%= + run_circuit_button class: + 'hidden md:inline-block fixed bottom-0 right-0 mr-6 mb-8 w-16 h-16 text-white bg-purple-500 rounded-full drop-shadow-md z-40' + %> +
+
+ +<%= palette_help_templates %> + + + + diff --git a/apps/www/config/environments/production.rb b/apps/www/config/environments/production.rb index 30e69d127..adc0fe1e9 100644 --- a/apps/www/config/environments/production.rb +++ b/apps/www/config/environments/production.rb @@ -11,7 +11,7 @@ config.eager_load = true # Full error reports are disabled and caching is turned on. - config.consider_all_requests_local = false + config.consider_all_requests_local = false config.action_controller.perform_caching = true # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] @@ -66,9 +66,9 @@ # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') if ENV['RAILS_LOG_TO_STDOUT'].present? - logger = ActiveSupport::Logger.new($stdout) + logger = ActiveSupport::Logger.new($stdout) logger.formatter = config.log_formatter - config.logger = ActiveSupport::TaggedLogging.new(logger) + config.logger = ActiveSupport::TaggedLogging.new(logger) end # Do not dump schema after migrations. diff --git a/apps/www/config/routes.rb b/apps/www/config/routes.rb index 0cd297706..8ed12f2e2 100644 --- a/apps/www/config/routes.rb +++ b/apps/www/config/routes.rb @@ -1,7 +1,12 @@ # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html -Rails.application.routes.draw do - resources :circuits, only: %i[index] if Rails.env.development? +Rails + .application + .routes + .draw do + resources :circuits, only: %i[index] if Rails.env.development? - get 'svg/(:json)', to: 'circuit_svg#show', as: :circuit_svg, constraints: { json: /.*/ } - get '/(:json)', to: 'circuits#show', as: :circuit, constraints: { json: /.*/ } -end + get '/backend', to: 'backend#show' + get '/cirq/(:json)', to: 'cirq#show', as: :cirq + get 'svg/(:json)', to: 'circuit_svg#show', as: :circuit_svg, constraints: { json: /.*/ } + get '/(:json)', to: 'circuits#show', as: :circuit, constraints: { json: /.*/ } + end diff --git a/apps/www/lib/cirq.rb b/apps/www/lib/cirq.rb new file mode 100644 index 000000000..b5d1ce6d2 --- /dev/null +++ b/apps/www/lib/cirq.rb @@ -0,0 +1,325 @@ +# coding: utf-8 +require 'pycall/import' +include PyCall::Import + +PyCall.exec(<<~PYTHON) +import cirq +import io,sys +import numpy as np +import qsimcirq +from sympy import * +from sympy.parsing.sympy_parser import parse_expr, standard_transformations, implicit_multiplication_application, convert_xor +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') +from cirq.circuits import InsertStrategy + +class cirqbridge: + def __init__(self): + return + + def lookup_measurement_label(self,_circuit_from_qni,label): + numofdevices=0 + print("measurement_label: lookup for ", label) + sys.stdout.flush() + print("_circuit", _circuit_from_qni) + sys.stdout.flush() + counter = 0 + label_found = 0 + for _i in _circuit_from_qni: + if label_found == 1: + break +# print("_i, counter ", _i, counter) +# sys.stdout.flush() + if _i == []: + continue + for _p in range(len(_i)): + if 'flag' in _i[_p]: + if _i[_p]['type'] == "Measure" and _i[_p]['flag'] != label: + counter = counter + 1 + elif _i[_p]['type'] == "Measure" and _i[_p]['flag'] == label: + print("found flag _i[%d]" % _p, _i[_p]['flag']) + label_found = 1 + break + sys.stdout.flush() + print("counter", counter) + label='m' + str(counter) + return label + + def build_circuit(self,numofqubits,_circuit_from_qni): +# print("build_circuit") +# sys.stdout.flush() + transformations = (standard_transformations + (implicit_multiplication_application,) + (convert_xor,)) + circuit_from_qni = [] + for a in _circuit_from_qni: + circuit_from_qni.append(a) + sys.stdout.flush() + qubits = cirq.LineQubit.range(numofqubits) + c = cirq.Circuit() + m = 0 + measurement_moment = [] + _current_index = 0 + for column_qni in circuit_from_qni: + print("circuit column", column_qni) + sys.stdout.flush() + moment = [] + measurement_moment.append([]) + if len(column_qni)==0: # null or invalid step is converted to I gate + _c = [cirq.I(qubits[0])] + moment.append(_c) + for circuit_qni in column_qni: + if circuit_qni['type'] == u'H': + if "if" in circuit_qni: # classical control + targetqubits=[ qubits[index] for index in circuit_qni['targets'] ] + label=self.lookup_measurement_label(_circuit_from_qni, circuit_qni['if']) + _c=[cirq.H(index).with_classical_controls(label) for index in targetqubits] + else: + targetqubits=[ qubits[index] for index in circuit_qni['targets'] ] + if not "controls" in circuit_qni: + _c=[cirq.H(index) for index in targetqubits] + else: + controledqubits=[ qubits[index] for index in circuit_qni['controls'] ] + _c=[ cirq.ControlledOperation(controledqubits, cirq.H(index)) for index in targetqubits ] + elif circuit_qni['type'] == u'X': + if "if" in circuit_qni: # classical control + targetqubits=[ qubits[index] for index in circuit_qni['targets'] ] + label=self.lookup_measurement_label(_circuit_from_qni, circuit_qni['if']) + _c=[cirq.X(index).with_classical_controls(label) for index in targetqubits] + else: + targetqubits=[ qubits[index] for index in circuit_qni['targets'] ] + if not "controls" in circuit_qni: + _c=[ cirq.X(index) for index in targetqubits] + else: + controledqubits=[ qubits[index] for index in circuit_qni['controls'] ] + _c=[ cirq.ControlledOperation(controledqubits, cirq.X(index)) for index in targetqubits ] + elif circuit_qni['type'] == u'Y': + targetqubits=[ qubits[index] for index in circuit_qni['targets'] ] + if not "controls" in circuit_qni: + _c=[ cirq.Y(index) for index in targetqubits] + else: + controledqubits=[ qubits[index] for index in circuit_qni['controls'] ] + _c=[ cirq.ControlledOperation(controledqubits, cirq.Y(index)) for index in targetqubits ] + elif circuit_qni['type'] == u'Z': + targetqubits=[ qubits[index] for index in circuit_qni['targets'] ] + if not "controls" in circuit_qni: + _c=[cirq.Z(index) for index in targetqubits] + else: + controledqubits=[ qubits[index] for index in circuit_qni['controls'] ] + _c=[ cirq.ControlledOperation(controledqubits, cirq.Z(index)) for index in targetqubits ] + elif circuit_qni['type'] == u'Rx': + _angle = circuit_qni['angle'].replace(u'π','pi') + expr = parse_expr(_angle, transformations=transformations) + angle = float(expr.evalf()) + targetqubits=[ qubits[index] for index in circuit_qni['targets'] ] + if not "controls" in circuit_qni: + _c=[ cirq.rx(angle).on(index) for index in targetqubits] + else: + controledqubits=[ qubits[index] for index in circuit_qni['controls'] ] + _c=[ cirq.ControlledOperation(controledqubits, cirq.rx(angle).on(index)) for index in targetqubits ] + elif circuit_qni['type'] == u'Ry': + _angle = circuit_qni['angle'].replace(u'π','pi') + expr = parse_expr(_angle, transformations=transformations) + angle = float(expr.evalf()) + targetqubits=[ qubits[index] for index in circuit_qni['targets'] ] + if not "controls" in circuit_qni: + _c= [ cirq.ry(angle).on(index) for index in targetqubits] + else: + controledqubits=[ qubits[index] for index in circuit_qni['controls'] ] + _c =[ cirq.ControlledOperation(controledqubits, cirq.ry(angle).on(index)) for index in targetqubits ] + elif circuit_qni['type'] == u'Rz': + _angle = circuit_qni['angle'].replace(u'π','pi') + expr = parse_expr(_angle, transformations=transformations) + angle = float(expr.evalf()) + targetqubits=[ qubits[index] for index in circuit_qni['targets'] ] + if not "controls" in circuit_qni: + _c = [ cirq.rz(angle).on(index) for index in targetqubits] + else: + controledqubits=[ qubits[index] for index in circuit_qni['controls'] ] + _c = [ cirq.ControlledOperation(controledqubits, cirq.rz(angle).on(index)) for index in targetqubits ] + elif circuit_qni['type'] == u'P': + _angle = circuit_qni['angle'].replace(u'π','pi') + '/ pi' + expr = parse_expr(_angle, transformations=transformations) + angle = float(expr.evalf()) + targetqubits=[ qubits[index] for index in circuit_qni['targets'] ] + if not "controls" in circuit_qni: + _c = [ cirq.ZPowGate(exponent=angle).on(index) for index in targetqubits] + else: + controledqubits=[ qubits[index] for index in circuit_qni['controls'] ] + _c = [ cirq.ControlledOperation(controledqubits, cirq.ZPowGate(exponent=angle).on(index)) for index in targetqubits ] + elif circuit_qni['type'] == u'T': + targetqubits=[ qubits[index] for index in circuit_qni['targets'] ] + if not "controls" in circuit_qni: + _c = [ cirq.Z(index)**0.25 for index in targetqubits] + else: + controledqubits=[ qubits[index] for index in circuit_qni['controls'] ] + _c = [ cirq.ControlledOperation(controledqubits, cirq.Z(index)**0.25) for index in targetqubits ] + elif circuit_qni['type'] == u'X^½': + targetqubits=[ qubits[index] for index in circuit_qni['targets'] ] + if not "controls" in circuit_qni: + _c = [ cirq.X(index)**0.5 for index in targetqubits] + else: + controledqubits=[ qubits[index] for index in circuit_qni['controls'] ] + _c = [ cirq.ControlledOperation(controledqubits, cirq.X(index)**0.5) for index in targetqubits ] + elif circuit_qni['type'] == u'•': + if "controls" in circuit_qni: + print("control is not supported for CZ gate", circuit_qni['type']) + sys.stdout.flush() + exit(1) + targetqubits=[ qubits[index] for index in circuit_qni['targets'] ] + if len(targetqubits) == 2: + _c = [ cirq.CZ(qubits[circuit_qni['targets'][0]], qubits[circuit_qni['targets'][1]]) ] + elif len(targetqubits) < 2: + print("the number of target qubits must be larger than 2") + sys.stdout.flush() + exit(1) + else: + # we regard the first and the second qubit as the target qubits, + # and others are controlled qubits. + controledqubits = [] + for _i in range(len(circuit_qni['targets'])-2): + controledqubits.append(qubits[circuit_qni['targets'][_i+2]]) + sys.stdout.flush() + _c = [ cirq.ControlledOperation(controledqubits, cirq.CZ(qubits[circuit_qni['targets'][0]], qubits[circuit_qni['targets'][1]])) ] + elif circuit_qni['type'] == u'|0>': + targetqubits=[ qubits[index] for index in circuit_qni['targets'] ] + _c = [ cirq.ops.reset(index) for index in targetqubits] + elif circuit_qni['type'] == u'|1>': + targetqubits=[ qubits[index] for index in circuit_qni['targets'] ] + _c = [ cirq.ops.reset(index) for index in targetqubits] + _c.append([ cirq.X(index) for index in targetqubits]) + elif circuit_qni['type'] == u'Measure': + targetqubits=[ qubits[index] for index in circuit_qni['targets'] ] + _c = [ cirq.measure(targetqubits[index], key = 'm' + str(m + index)) for index in range(len(targetqubits))] + __m = [ 'm' + str(m + index) for index in range(len(targetqubits))] + _m = [[__m[index], circuit_qni['targets'][index]] for index in range(len(targetqubits))] + measurement_moment[_current_index].append(_m) + m = m + len(targetqubits) + elif circuit_qni['type'] == u'Swap': + targetqubit0=qubits[circuit_qni['targets'][0]] + targetqubit1=qubits[circuit_qni['targets'][1]] + _c = [] + _c.append(cirq.SWAP(targetqubit0,targetqubit1)) + elif circuit_qni['type'] == u'Bloch': + targetqubits=[ qubits[index] for index in circuit_qni['targets'] ] + _c = [ cirq.ops.I(index) for index in targetqubits] # add a dummy gate to count Bloch operation as a step + elif circuit_qni['type'] == u'': + pass #nop + else: + print("unsupported gate", circuit_qni['type']) + sys.stdout.flush() + exit(1) + for __c in _c: + moment.append(__c) + c.append(moment, strategy=InsertStrategy.NEW_THEN_INLINE) + _current_index = _current_index + 1 +# print(c) +# sys.stdout.flush() + return c, measurement_moment + + def run_circuit_until_step_index(self, c, measurement_moment, until, steps): + print("run_circuit_until_step_index") + print("circuit:") + print(c) + sys.stdout.flush() +# print("until (corrected):", until) +# sys.stdout.flush() +# print("steps(len):", len(steps)) +# sys.stdout.flush() + print("steps:", steps) + sys.stdout.flush() + cirq_simulator = cirq.Simulator() + _data = [] + counter = -1 + sleep_flag = 0 # we need padding for |1> because implimented as R + X + for _counter, step in enumerate(cirq_simulator.simulate_moment_steps(c)): + if sleep_flag == 0: + counter = counter + 1 + dic = {} + dic[':blochVectors']={} + dic[':measuredBits'] = {} + if sleep_flag == 0: + for _d in steps[counter]: + if 'type' in _d: + if _d['type'] == u'|1>': + sleep_flag = 1 + else: + sleep_flag = 0 + if sleep_flag == 1: + continue + + print("current step[%d]" % counter, steps[counter]) + sys.stdout.flush() + if steps[counter] == []: + pass + else: + bloch_index = -1 + for _bloch_index in range(len(steps[counter])): + if steps[counter][_bloch_index]['type'] == 'Bloch': + bloch_index = _bloch_index +# print("bloch_index ", bloch_index) +# sys.stdout.flush() + if bloch_index != -1: + for _bloch_target in steps[counter][bloch_index]['targets']: + blochxyz=cirq.qis.bloch_vector_from_state_vector(step.state_vector(),_bloch_target) + dic[':blochVectors'][_bloch_target] = blochxyz + print("bloch sphere: ", blochxyz) + sys.stdout.flush() + if counter == until: + dic[':amplitude'] = step.state_vector() + print("amplitudes: ", step.state_vector()) + sys.stdout.flush() + _data.append(dic) + if len(step.measurements) != 0: + for i in range(len(measurement_moment)): + if len(measurement_moment[i]) !=0: +# print("searching key", measurement_moment[i]) + for k in range(len(measurement_moment[i])): + for j in range(len(measurement_moment[i][k])): + _key = measurement_moment[i][k][j][0] + _qubit = measurement_moment[i][k][j][1] + _step = i + if _key not in step.measurements: + break + _value= step.measurements[_key][0] + print("step: ", _step, "key:", _key, "target qubit", _qubit, "value ", _value) + sys.stdout.flush() + _data[i][':measuredBits'][_qubit] = _value +# print("_data ", _data) +# sys.stdout.flush() + return _data + +PYTHON + +class Cirq + def initialize(circuit_id:, qubit_count:, step_index:, steps:, targets:) + @circuit_id = circuit_id + @qubit_count = qubit_count + @step_index = step_index + @steps = steps + @targets = targets + end + + def run + cirqbridge = PyCall.eval('cirqbridge').call + cirq_circuit, measurement_moment = cirqbridge.build_circuit(@qubit_count, @steps) + _result_list = cirqbridge.run_circuit_until_step_index(cirq_circuit, measurement_moment, @step_index, @steps).to_a + result_list = [] + for var in _result_list do + hash = {} + a = var.to_h + hash[:measuredBits]=a[":measuredBits"].to_h + hash[:blochVectors]={} + for b in a[":blochVectors"].to_h do + c = Array[b[1][0].to_f, b[1][1].to_f, b[1][2].to_f] + hash[:blochVectors][b[0]] = c + end + result_list.push(hash) + end + _amplitudes=_result_list[@step_index][':amplitude'] + amplitudes = {} + for num in 0.._amplitudes.size-1 do + amplitudes.store(num,Array[_amplitudes[num].real.to_f,_amplitudes[num].imag.to_f]) + end + result_list[@step_index][:amplitudes]=amplitudes + p result_list + result_list + end +end diff --git a/apps/www/test/controllers/backend_controller_test.rb b/apps/www/test/controllers/backend_controller_test.rb new file mode 100644 index 000000000..87540d08c --- /dev/null +++ b/apps/www/test/controllers/backend_controller_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' + +class BackendControllerTest < ActionDispatch::IntegrationTest + test 'should get show' do + get backend_url(format: :json, backend: 'cirq') + assert_response :success + end +end diff --git a/apps/www/test/controllers/cirq_controller_test.rb b/apps/www/test/controllers/cirq_controller_test.rb new file mode 100644 index 000000000..7534d7d35 --- /dev/null +++ b/apps/www/test/controllers/cirq_controller_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' + +class CirqControllerTest < ActionDispatch::IntegrationTest + test 'should get show' do + get cirq_url + assert_response :success + end +end diff --git a/packages/elements/src/quantum-circuit-element.ts b/packages/elements/src/quantum-circuit-element.ts index 415500117..9d7f1398e 100644 --- a/packages/elements/src/quantum-circuit-element.ts +++ b/packages/elements/src/quantum-circuit-element.ts @@ -1257,7 +1257,8 @@ export class QuantumCircuitElement extends HoverableMixin(HTMLElement) { } private get urlJson(): string { - const json = window.location.href.toString().split(window.location.host)[1].slice(1) + const json = window.location.href.toString().split('/').pop() + Util.notNull(json) return decodeURIComponent(json) } diff --git a/packages/elements/src/quantum-simulator-element.ts b/packages/elements/src/quantum-simulator-element.ts index 25211dd0c..afbc82ccb 100644 --- a/packages/elements/src/quantum-simulator-element.ts +++ b/packages/elements/src/quantum-simulator-element.ts @@ -17,6 +17,7 @@ type MessageEventData = { } export class QuantumSimulatorElement extends HTMLElement { + @attr backend = '' @attr updateUrl = false @target circuit!: QuantumCircuitElement @@ -28,7 +29,7 @@ export class QuantumSimulatorElement extends HTMLElement { declare worker: Worker connectedCallback(): void { - this.worker = new Worker('./serviceworker.js') + this.worker = new Worker('/serviceworker.js') this.visibleQubitCircleKets = [] this.worker.addEventListener('message', this.handleServiceWorkerMessage.bind(this)) @@ -145,11 +146,14 @@ export class QuantumSimulatorElement extends HTMLElement { const qubitCount = maxControlTargetBit >= 0 ? maxControlTargetBit + 1 : 1 this.circleNotation.qubitCount = qubitCount + const backend = this.backend.trim() this.worker.postMessage({ + json: this.circuit.toJson(), qubitCount, stepIndex, steps: serializedSteps, - targets: this.visibleQubitCircleKets + targets: this.visibleQubitCircleKets, + backend: backend !== '' ? backend : null }) }