Local Simulators

Local simulators allow you to classically simulate your Circuit or OpenQasmProgram on local hardware, rather than sending it to the cloud for on-demand execution. Local simulators can run task batches in parallel using multithreading. Gate-based simulators are provided in the BraketSimulator.jl package.

Developers wishing to implement their own LocalSimulator should extend the simulate function. New simulator backends must be subtypes of the AbstractLocalSimulator type. Additionally, new simulators should "register" themselves in Braket._simulator_devices in their parent module's __init__ function so that they can be referred to by name, like so:

module NewLocalSimulator

using Braket

struct NewSimulator <: Braket.AbstractLocalSimulator 
# implementation of a new local simulator backend
# with name "new_simulator"
end

function Braket.simulate()
    # simulation implementation here
end

function __init__()
    Braket._simulator_devices[]["new_simulator"] = NewLocalSimulator()
end

end

Then users can use the new simulator like so:

using NewLocalSimulator, Braket

dev = LocalSimulator("new_simulator")
simulate(dev, task_specification, args...; kwargs...)
Braket._simulator_devicesConstant
_simulator_devices

A Ref{Dict} which records the ids of registered LocalSimulator backends so that they can be retrieved by this id. A new simulator backend should register itself in _simulator_devices in its module's __init__ function.

Braket.LocalQuantumTaskType
LocalQuantumTask(id::String, result::Union{GateModelQuantumTaskResult, AnalogHamiltonianSimulationQuantumTaskResult})

A quantum task which has been run locally using a LocalSimulator. The state of a LocalQuantumTask is always "COMPLETED" as the task object is only created once the loca simulation has finished.

Braket.LocalSimulatorType
LocalSimulator(backend::Union{String, AbstractBraketSimulator})

A quantum simulator which runs locally rather than sending the circuit(s) to the cloud to be executed on-demand. A LocalSimulator must be created with a backend – either a handle, in the form of a String, which uniquely identifies a simulator backend registered in _simulator_devices, or an already instantiated simulator object.

LocalSimulators should implement their own method for simulate if needed. They can process single tasks or task batches.

Braket.simulateFunction
simulate(d::LocalSimulator, task_spec::Union{Circuit, AbstractProgram}, args...; shots::Int=0, inputs::Dict{String, Float64} = Dict{String, Float64}(), kwargs...)

Simulate the execution of task_spec using the backend of `LocalSimulator d. args are additional arguments to be provided to the backend. inputs is used to set the value of any FreeParameter in task_spec and will override the existing inputs field of an OpenQasmProgram. Other kwargs will be passed to the backend simulator. Returns a LocalQuantumTask.

simulate(d::LocalSimulator, task_specs::Vector{T}, args...; kwargs...) where {T}

Simulate the execution of a batch of tasks specified by task_specs using the backend of `LocalSimulator d. args are additional arguments to be provided to the backend.

kwargs used by the LocalSimulator are:

  • shots::Int - the number of shots to run for all tasks in task_specs. Default is 0.
  • max_parallel::Int - the maximum number of simulations to execute simultaneously. Default is 32.
  • inputs::Union{Vector{Dict{String, Float64}}, Dict{String, Float64}} - used to set the value of any FreeParameter in each task specification. It must either be a Dict or a single-element Vector (in which case the same parameter values are used for all elements of task_specs or of the same length as task_specs (in which case the i-th specification is paired with the i-th input dictionary). Default is an empty dictionary.

Other kwargs are passed through to the backend simulator. Returns a LocalQuantumTaskBatch.

Note

Because Julia uses dynamic threading and Tasks can migrate between threads, each simulation is a Task which itself can spawn many more Tasks, as the internal implementation of LocalSimulator's backend may use threading where appropriate. On systems with many CPU cores, spawning too many Tasks may overwhelm the Julia scheduler and degrade performance. "Too many" depends on the particulars of your hardware, so on many-core systems you may need to tune this value for best performance.