Skip to content

Commit 4780fdf

Browse files
authored
feature: Add parsing support for barrier/reset/delay/duration (#52)
* change: Add parsing for delay * change: Add Reset, Barrier, Delay instructions and tests * change: Test for Barrier * change: Tests for other no-ops and reorg * change: More tests for no-ops * fix: Support for mu-s, dt * fix: Include new docstrings
1 parent 23991af commit 4780fdf

10 files changed

+202
-39
lines changed

docs/src/circuits.md

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ BraketSimulator.Circuit
1212
BraketSimulator.Operator
1313
BraketSimulator.QuantumOperator
1414
BraketSimulator.FreeParameter
15+
BraketSimulator.Reset
16+
BraketSimulator.Barrier
17+
BraketSimulator.Delay
1518
BraketSimulator.Measure
1619
BraketSimulator.Instruction
1720
BraketSimulator.QubitSet

src/Quasar.jl

+70-28
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module Quasar
22

33
using ..BraketSimulator
4-
using Automa, AbstractTrees, DataStructures
4+
using Automa, AbstractTrees, DataStructures, Dates
55
using DataStructures: Stack
66
using BraketSimulator: Control, Instruction, Result, bind_value!, remap, qubit_count, Circuit
77

@@ -71,9 +71,7 @@ const qasm_tokens = [
7171
:arrow_token => re"->",
7272
:reset_token => re"reset",
7373
:delay_token => re"delay",
74-
:stretch_token => re"stretch",
75-
:barrier_token => re"barrier",
76-
:duration_token => re"duration",
74+
:barrier_token => re"barrier",
7775
:void => re"void",
7876
:const_token => re"const",
7977
:assignment => re"=|-=|\+=|\*=|/=|^=|&=|\|=|<<=|>>=",
@@ -110,12 +108,18 @@ const qasm_tokens = [
110108
:string_token => '"' * rep(re"[ !#-~]" | re"\\\\\"") * '"' | '\'' * rep(re"[ -&(-~]" | ('\\' * re"[ -~]")) * '\'',
111109
:newline => re"\r?\n",
112110
:spaces => re"[\t ]+",
113-
:durationof_token => re"durationof", # this MUST be lower than duration_token to preempt duration
114-
:classical_type => re"bool|uint|int|float|angle|complex|array|bit",
115-
:duration_value => (float | integer) * re"ns|µs|us|ms|s",
111+
:classical_type => re"bool|uint|int|float|angle|complex|array|bit|stretch|duration",
112+
:durationof_token => re"durationof", # this MUST be lower than classical_type to preempt duration
113+
:duration_literal => (float | integer) * re"dt|ns|us|ms|s|\xce\xbc\x73", # transcode'd μs
116114
:forbidden_keyword => re"cal|defcal|extern",
117115
]
118116

117+
const dt_type = Ref{DataType}()
118+
119+
function __init__()
120+
dt_type[] = Nanosecond
121+
end
122+
119123
@eval @enum Token error $(first.(qasm_tokens)...)
120124
make_tokenizer((error,
121125
[Token(i) => j for (i,j) in enumerate(last.(qasm_tokens))]
@@ -369,6 +373,15 @@ function parse_classical_type(tokens, stack, start, qasm)
369373
first(array_tokens)[end] == comma && popfirst!(array_tokens)
370374
size = parse_expression(array_tokens, stack, start, qasm)
371375
return QasmExpression(:classical_type, SizedArray(eltype, size))
376+
elseif var_type == "duration"
377+
@warn "duration expression encountered -- currently `duration` is a no-op"
378+
# TODO: add proper parsing of duration expressions, including
379+
# support for units and algebraic durations like 2*a.
380+
return QasmExpression(:classical_type, :duration)
381+
elseif var_type == "stretch"
382+
@warn "stretch expression encountered -- currently `stretch` is a no-op"
383+
# TODO: add proper parsing of stretch expressions
384+
return QasmExpression(:classical_type, :stretch)
372385
else
373386
!any(triplet->triplet[end] == semicolon, tokens) && push!(tokens, (-1, Int32(-1), semicolon))
374387
size = is_sized ? parse_expression(tokens, stack, start, qasm) : QasmExpression(:integer_literal, -1)
@@ -427,6 +440,21 @@ parse_oct_literal(token, qasm) = QasmExpression(:integer_literal, tryparse(I
427440
parse_bin_literal(token, qasm) = QasmExpression(:integer_literal, tryparse(Int, qasm[token[1]:token[1]+token[2]-1]))
428441
parse_float_literal(token, qasm) = QasmExpression(:float_literal, tryparse(Float64, qasm[token[1]:token[1]+token[2]-1]))
429442
parse_boolean_literal(token, qasm) = QasmExpression(:boolean_literal, tryparse(Bool, qasm[token[1]:token[1]+token[2]-1]))
443+
function parse_duration_literal(token, qasm)
444+
str = String(codeunits(qasm)[token[1]:token[1]+token[2]-1])
445+
duration = if endswith(str, "ns")
446+
Nanosecond(tryparse(Int, chop(str, tail=2)))
447+
elseif endswith(str, "ms")
448+
Millisecond(tryparse(Int, chop(str, tail=2)))
449+
elseif endswith(str, "us") || endswith(str, "μs")
450+
Microsecond(tryparse(Int, chop(str, tail=2)))
451+
elseif endswith(str, "s")
452+
Second(tryparse(Int, chop(str, tail=1)))
453+
elseif endswith(str, "dt")
454+
dt_type[](tryparse(Int, chop(str, tail=2)))
455+
end
456+
QasmExpression(:duration_literal, duration)
457+
end
430458
function parse_irrational_literal(token, qasm)
431459
raw_string = String(codeunits(qasm)[token[1]:token[1]+token[2]-1])
432460
raw_string == "pi" && return QasmExpression(:irrational_literal, π)
@@ -463,7 +491,7 @@ function extract_braced_block(tokens::Vector{Tuple{Int64, Int32, Token}}, stack,
463491
next_token[end] == rbracket && (closers_met += 1)
464492
push!(braced_tokens, next_token)
465493
end
466-
pop!(braced_tokens) # closing }
494+
pop!(braced_tokens) # closing ]
467495
push!(braced_tokens, (-1, Int32(-1), semicolon))
468496
return braced_tokens
469497
end
@@ -511,6 +539,7 @@ function parse_list_expression(tokens::Vector{Tuple{Int64, Int32, Token}}, stack
511539
end
512540

513541
function parse_literal(tokens::Vector{Tuple{Int64, Int32, Token}}, stack, start, qasm)
542+
tokens[1][end] == duration_literal && return parse_duration_literal(popfirst!(tokens), qasm)
514543
tokens[1][end] == string_token && return parse_string_literal(popfirst!(tokens), qasm)
515544
tokens[1][end] == hex && return parse_hex_literal(popfirst!(tokens), qasm)
516545
tokens[1][end] == oct && return parse_oct_literal(popfirst!(tokens), qasm)
@@ -634,7 +663,7 @@ function parse_expression(tokens::Vector{Tuple{Int64, Int32, Token}}, stack, sta
634663
token_name = parse_bracketed_expression(pushfirst!(tokens, start_token), stack, start, qasm)
635664
elseif start_token[end] == classical_type
636665
token_name = parse_classical_type(pushfirst!(tokens, start_token), stack, start, qasm)
637-
elseif start_token[end] (string_token, integer_token, float_token, hex, oct, bin, irrational, dot, boolean)
666+
elseif start_token[end] (string_token, integer_token, float_token, hex, oct, bin, irrational, dot, boolean, duration_literal)
638667
token_name = parse_literal(pushfirst!(tokens, start_token), stack, start, qasm)
639668
elseif start_token[end] (mutable, readonly, const_token)
640669
token_name = parse_identifier(start_token, qasm)
@@ -644,7 +673,6 @@ function parse_expression(tokens::Vector{Tuple{Int64, Int32, Token}}, stack, sta
644673
token_name = QasmExpression(:n_dims, QasmExpression(:integer_literal, parse(Int, dim)))
645674
end
646675
head(token_name) == :empty && throw(QasmParseError("unable to parse line with start token $(start_token[end])", stack, start, qasm))
647-
648676
next_token = first(tokens)
649677
if next_token[end] == semicolon || next_token[end] == comma || start_token[end] (lbracket, lbrace)
650678
expr = token_name
@@ -657,7 +685,7 @@ function parse_expression(tokens::Vector{Tuple{Int64, Int32, Token}}, stack, sta
657685
unary_op_symbol (:~, :!, :-) || throw(QasmParseError("invalid unary operator $unary_op_symbol.", stack, start, qasm))
658686
next_expr = parse_expression(tokens, stack, start, qasm)
659687
# apply unary op to next_expr
660-
if head(next_expr) (:identifier, :indexed_identifier, :integer_literal, :float_literal, :string_literal, :irrational_literal, :boolean_literal, :complex_literal, :function_call, :cast)
688+
if head(next_expr) (:identifier, :indexed_identifier, :integer_literal, :float_literal, :string_literal, :irrational_literal, :boolean_literal, :complex_literal, :function_call, :cast, :duration_literal)
661689
expr = QasmExpression(:unary_op, unary_op_symbol, next_expr)
662690
elseif head(next_expr) == :binary_op
663691
# replace first argument
@@ -1005,36 +1033,37 @@ function parse_qasm(clean_tokens::Vector{Tuple{Int64, Int32, Token}}, qasm::Stri
10051033
line_exprs = collect(Iterators.reverse(line_body))[2:end]
10061034
push!(stack, QasmExpression(:return, line_exprs))
10071035
elseif token == box
1008-
@warn "box expression encountered -- currently boxed and delayed expressions are not supported"
1036+
@warn "box expression encountered -- currently `box` is a no-op"
10091037
box_expr = QasmExpression(:box)
10101038
parse_block_body(box_expr, clean_tokens, stack, start, qasm)
10111039
push!(stack, box_expr)
10121040
elseif token == reset_token
10131041
@warn "reset expression encountered -- currently `reset` is a no-op"
10141042
eol = findfirst(triplet->triplet[end] == semicolon, clean_tokens)
10151043
reset_tokens = splice!(clean_tokens, 1:eol)
1016-
targets = parse_expression(reset_tokens, stack, start, qasm)
1044+
targets = parse_list_expression(reset_tokens, stack, start, qasm)
10171045
push!(stack, QasmExpression(:reset, targets))
10181046
elseif token == barrier_token
10191047
@warn "barrier expression encountered -- currently `barrier` is a no-op"
10201048
eol = findfirst(triplet->triplet[end] == semicolon, clean_tokens)
10211049
barrier_tokens = splice!(clean_tokens, 1:eol)
1022-
targets = parse_expression(barrier_tokens, stack, start, qasm)
1050+
targets = parse_list_expression(barrier_tokens, stack, start, qasm)
10231051
push!(stack, QasmExpression(:barrier, targets))
1024-
elseif token == duration_token
1025-
@warn "duration expression encountered -- currently `duration` is a no-op"
1026-
eol = findfirst(triplet->triplet[end] == semicolon, clean_tokens)
1027-
duration_tokens = splice!(clean_tokens, 1:eol)
1028-
# TODO: add proper parsing of duration expressions, including
1029-
# support for units and algebraic durations like 2*a.
1030-
#dur_expr = parse_expression(duration_tokens, stack, start, qasm)
1031-
push!(stack, QasmExpression(:duration))
1032-
elseif token == stretch_token
1033-
@warn "stretch expression encountered -- currently `stretch` is a no-op"
1052+
elseif token == delay_token
1053+
@warn "delay expression encountered -- currently `delay` is a no-op"
10341054
eol = findfirst(triplet->triplet[end] == semicolon, clean_tokens)
1035-
stretch_tokens = splice!(clean_tokens, 1:eol)
1036-
stretch_expr = parse_expression(stretch_tokens, stack, start, qasm)
1037-
push!(stack, QasmExpression(:stretch, stretch_expr))
1055+
delay_tokens = splice!(clean_tokens, 1:eol)
1056+
delay_expr = QasmExpression(:delay)
1057+
# format is delay[duration]; or delay[duration] targets;
1058+
delay_duration = extract_braced_block(delay_tokens, stack, start, qasm)
1059+
push!(delay_expr, QasmExpression(:duration, parse_expression(delay_duration, stack, start, qasm)))
1060+
target_expr = QasmExpression(:targets)
1061+
if first(delay_tokens)[end] != semicolon # targets present
1062+
targets = parse_list_expression(delay_tokens, stack, start, qasm)
1063+
push!(target_expr, targets)
1064+
end
1065+
push!(delay_expr, target_expr)
1066+
push!(stack, delay_expr)
10381067
elseif token == end_token
10391068
push!(stack, QasmExpression(:end))
10401069
elseif token == identifier || token == builtin_gate
@@ -1327,7 +1356,7 @@ function evaluate(v::V, expr::QasmExpression) where {V<:AbstractVisitor}
13271356
step::Int = evaluate(v, raw_step)
13281357
stop::Int = evaluate(v, raw_stop)
13291358
return StepRange(start, step, stop)
1330-
elseif head(expr) (:integer_literal, :float_literal, :string_literal, :complex_literal, :irrational_literal, :boolean_literal)
1359+
elseif head(expr) (:integer_literal, :float_literal, :string_literal, :complex_literal, :irrational_literal, :boolean_literal, :duration_literal)
13311360
return expr.args[1]
13321361
elseif head(expr) == :array_literal
13331362
return [evaluate(v, arg) for arg in convert(Vector{QasmExpression}, expr.args)]
@@ -1630,8 +1659,21 @@ function (v::AbstractVisitor)(program_expr::QasmExpression)
16301659
elseif head(program_expr) == :version
16311660
return v
16321661
elseif head(program_expr) == :reset
1662+
targets = program_expr.args[1]::QasmExpression
1663+
target_qubits = evaluate(v, targets)
1664+
push!(v, [BraketSimulator.Instruction(BraketSimulator.Reset(), t) for t in target_qubits])
16331665
return v
16341666
elseif head(program_expr) == :barrier
1667+
targets = program_expr.args[1]::QasmExpression
1668+
target_qubits = evaluate(v, targets)
1669+
push!(v, [BraketSimulator.Instruction(BraketSimulator.Barrier(), t) for t in target_qubits])
1670+
return v
1671+
elseif head(program_expr) == :delay
1672+
duration_expr = program_expr.args[1].args[1]::QasmExpression
1673+
targets = program_expr.args[2].args[1]::QasmExpression
1674+
target_qubits = evaluate(v, targets)
1675+
duration = evaluate(v, duration_expr)
1676+
push!(v, [BraketSimulator.Instruction(BraketSimulator.Delay(duration), t) for t in target_qubits])
16351677
return v
16361678
elseif head(program_expr) == :stretch
16371679
return v

src/dm_simulator.jl

+8-4
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,15 @@ function _evolve_op!(
148148
) where {T<:Complex,S<:AbstractDensityMatrix{T},N<:Noise}
149149
apply_noise!(op, dms.density_matrix, target...)
150150
end
151-
# Measure operators are no-ops for now as measurement is handled at the end
152-
# of simulation, in the results computation step. If/when mid-circuit
153-
# measurement is supported, this operation will collapse the density
154-
# matrix on the measured qubits.
151+
# Measure, barrier, reset, and delay operators are no-ops for now as
152+
# measurement is handled at the end of simulation, in the results
153+
# computation step. If/when mid-circuit # measurement is supported,
154+
# this operation will collapse the density # matrix on the measured qubits.
155+
# Barrier, reset, and delay are also to-do implementations.
155156
_evolve_op!(dms::DensityMatrixSimulator{T,S}, m::Measure, args...) where {T<:Complex,S<:AbstractDensityMatrix{T}} = return
157+
_evolve_op!(dms::DensityMatrixSimulator{T,S}, b::Barrier, args...) where {T<:Complex,S<:AbstractDensityMatrix{T}} = return
158+
_evolve_op!(dms::DensityMatrixSimulator{T,S}, r::Reset, args...) where {T<:Complex,S<:AbstractDensityMatrix{T}} = return
159+
_evolve_op!(dms::DensityMatrixSimulator{T,S}, d::Delay, args...) where {T<:Complex,S<:AbstractDensityMatrix{T}} = return
156160

157161
"""
158162
evolve!(dms::DensityMatrixSimulator{T, S<:AbstractMatrix{T}}, operations::Vector{Instruction}) -> DensityMatrixSimulator{T, S}

src/gate_kernels.jl

+3
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,9 @@ apply_gate!(::Val{false}, g::I, state_vec::AbstractStateVector{T}, qubits::Int..
282282
apply_gate!(::Val{true}, g::I, state_vec::AbstractStateVector{T}, qubits::Int...) where {T<:Complex} =
283283
return
284284
apply_gate!(::Measure, state_vec, args...) = return
285+
apply_gate!(::Reset, state_vec, args...) = return
286+
apply_gate!(::Barrier, state_vec, args...) = return
287+
apply_gate!(::Delay, state_vec, args...) = return
285288

286289
function apply_gate!(
287290
g_matrix::Union{SMatrix{2,2,T}, Diagonal{T,SVector{2,T}}},

src/gates.jl

-3
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,6 @@ qubit_count(g::Unitary) = convert(Int, log2(size(g.matrix, 1)))
115115
Parametrizable(g::AngledGate) = Parametrized()
116116
Parametrizable(g::Gate) = NonParametrized()
117117
parameters(g::AngledGate) = collect(filter(a->a isa FreeParameter, angles(g)))
118-
parameters(g::Gate) = FreeParameter[]
119-
bind_value!(g::G, params::Dict{Symbol, <:Real}) where {G<:Gate} = bind_value!(Parametrizable(g), g, params)
120-
bind_value!(::NonParametrized, g::G, params::Dict{Symbol, <:Real}) where {G<:Gate} = g
121118
# nosemgrep
122119
function bind_value!(::Parametrized, g::G, params::Dict{Symbol, <:Real}) where {G<:AngledGate}
123120
new_angles = map(angles(g)) do angle

src/noises.jl

+1-3
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,9 @@ end
141141
Base.:(==)(c1::MultiQubitPauliChannel{N}, c2::MultiQubitPauliChannel{M}) where {N,M} = N == M && c1.probabilities == c2.probabilities
142142

143143
Parametrizable(g::Noise) = NonParametrized()
144-
parameters(g::Noise) = parameters(Parametrizable(g), g)
144+
parameters(g::Noise) = parameters(Parametrizable(g), g)
145145
parameters(::Parametrized, g::N) where {N<:Noise} = filter(x->x isa FreeParameter, [getproperty(g, fn) for fn in fieldnames(N)])
146146
parameters(::NonParametrized, g::Noise) = FreeParameter[]
147-
bind_value!(n::N, params::Dict{Symbol, <:Real}) where {N<:Noise} = bind_value!(Parametrizable(n), n, params)
148-
bind_value!(::NonParametrized, n::N, params::Dict{Symbol, <:Real}) where {N<:Noise} = n
149147

150148
# nosemgrep
151149
function bind_value!(::Parametrized, g::N, params::Dict{Symbol, <:Real}) where {N<:Noise}

src/operators.jl

+46
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ function Base.getindex(p::PauliEigenvalues{N}, i::Int)::Float64 where N
5454
end
5555
Base.getindex(p::PauliEigenvalues{N}, ix::Vector{Int}) where {N} = [p[i] for i in ix]
5656

57+
parameters(o::QuantumOperator) = FreeParameter[]
58+
5759
"""
5860
Measure(index) <: QuantumOperator
5961
@@ -66,3 +68,47 @@ Measure() = Measure(-1)
6668
Parametrizable(m::Measure) = NonParametrized()
6769
qubit_count(::Type{Measure}) = 1
6870
qubit_count(m::Measure) = qubit_count(Measure)
71+
72+
"""
73+
Reset(index) <: QuantumOperator
74+
75+
Represents an active reset operation on targeted qubit, stored in the classical register at `index`.
76+
For now, this is a no-op.
77+
"""
78+
struct Reset <: QuantumOperator
79+
index::Int
80+
end
81+
Reset() = Reset(-1)
82+
Parametrizable(m::Reset) = NonParametrized()
83+
qubit_count(::Type{Reset}) = 1
84+
qubit_count(m::Reset) = qubit_count(Reset)
85+
86+
"""
87+
Barrier(index) <: QuantumOperator
88+
89+
Represents a barrier operation on targeted qubit, stored in the classical register at `index`.
90+
For now, this is a no-op.
91+
"""
92+
struct Barrier <: QuantumOperator
93+
index::Int
94+
end
95+
Barrier() = Barrier(-1)
96+
Parametrizable(m::Barrier) = NonParametrized()
97+
qubit_count(::Type{Barrier}) = 1
98+
qubit_count(m::Barrier) = qubit_count(Barrier)
99+
100+
"""
101+
Delay(index, duration::Time) <: QuantumOperator
102+
103+
Represents a delay operation for `duration` on targeted qubit,
104+
stored in the classical register at `index`.
105+
For now, this is a no-op.
106+
"""
107+
struct Delay <: QuantumOperator
108+
index::Int
109+
duration::Dates.Period
110+
end
111+
Delay(duration::Dates.Period) = Delay(-1, duration)
112+
Parametrizable(m::Delay) = NonParametrized()
113+
qubit_count(::Type{Delay}) = 1
114+
qubit_count(m::Delay) = qubit_count(Delay)

src/schemas.jl

+2
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,6 @@ end
2222
Instruction(o::O, target) where {O<:Operator} = Instruction{O}(o, QubitSet(target...))
2323
Base.:(==)(ix1::Instruction{O}, ix2::Instruction{O}) where {O<:Operator} = (ix1.operator == ix2.operator && ix1.target == ix2.target)
2424
bind_value!(ix::Instruction{O}, param_values::Dict{Symbol, <:Real}) where {O<:Operator} = Instruction{O}(bind_value!(ix.operator, param_values), ix.target)
25+
bind_value!(o::O, params::Dict{Symbol, <:Real}) where {O<:Operator} = bind_value!(Parametrizable(o), o, params)
26+
bind_value!(::NonParametrized, o::O, params::Dict{Symbol, <:Real}) where {O<:Operator} = o
2527
remap(@nospecialize(ix::Instruction{O}), mapping::Dict{<:Integer, <:Integer}) where {O} = Instruction{O}(copy(ix.operator), [mapping[q] for q in ix.target])

0 commit comments

Comments
 (0)